Files
stack/tools/mos-comms/mos-comms.sh
Mos (W-jarvis) 7b1ad5ca82 tools: add mos-comms cross-agent git-relay package
Portable bash utility for two Mos orchestrators on separate hosts to relay
via a shared git branch: one-file-per-message (conflict-free), mechanical
systemd-timer poll, STATIC tmux wake-injection (no untrusted data in send-keys),
3x rebase-retry send, first-poll baseline (no history dump). See
tools/mos-comms/MOS-COMMS-PROTOCOL.md for the spec, threat model, and install.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:02:30 -05:00

292 lines
8.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Portable, append-only git relay for deliberate cross-agent communication.
set -euo pipefail
CONFIG_PATH="${MOS_COMMS_CONFIG:-$HOME/.config/mos-comms/mos-comms.config}"
CONFIG_PRESENT=0
if [[ -f "$CONFIG_PATH" ]]; then
# Configuration is local operator input. It must not be committed to the relay repo.
# shellcheck source=/dev/null
source "$CONFIG_PATH"
CONFIG_PRESENT=1
fi
AGENT_NAME="${AGENT_NAME:-}"
COMMS_REMOTE="${COMMS_REMOTE:-}"
COMMS_REPO_DIR="${COMMS_REPO_DIR:-$HOME/.local/state/mos-comms/repo}"
COMMS_BRANCH="${COMMS_BRANCH:-mos-comms}"
TMUX_SOCKET="${TMUX_SOCKET:-}"
TMUX_SESSION="${TMUX_SESSION:-mos-claude}"
STATE_DIR="${STATE_DIR:-$HOME/.local/state/mos-comms}"
GIT_CRED_HELPER="${GIT_CRED_HELPER:-}"
usage() {
cat <<'EOF'
Usage: mos-comms.sh setup | poll | read | send <text> | selftest
Messages are append-only files on the configured shared git branch. `poll` is
mechanical: it only checks git and emits a fixed tmux notification.
EOF
}
die() {
printf 'mos-comms: %s\n' "$*" >&2
exit 1
}
warn() {
printf 'mos-comms: warning: %s\n' "$*" >&2
}
gitc() {
if [[ -n "$GIT_CRED_HELPER" ]]; then
git -c credential.helper="$GIT_CRED_HELPER" "$@"
else
git "$@"
fi
}
require_identity() {
[[ -n "$AGENT_NAME" ]] || die 'AGENT_NAME is required in the local config'
[[ "$AGENT_NAME" =~ ^[A-Za-z0-9.-]+$ ]] || die 'AGENT_NAME may contain only letters, numbers, dots, and hyphens'
}
require_remote() {
[[ -n "$COMMS_REMOTE" ]] || die 'COMMS_REMOTE is required in the local config'
}
ensure_state_dir() {
mkdir -p "$STATE_DIR"
chmod 700 "$STATE_DIR"
}
remote_branch_exists() {
gitc -C "$COMMS_REPO_DIR" show-ref --verify --quiet "refs/remotes/origin/$COMMS_BRANCH"
}
setup() {
require_remote
require_identity
mkdir -p "$(dirname "$COMMS_REPO_DIR")"
if [[ ! -d "$COMMS_REPO_DIR/.git" ]]; then
gitc clone "$COMMS_REMOTE" "$COMMS_REPO_DIR"
fi
gitc -C "$COMMS_REPO_DIR" remote set-url origin "$COMMS_REMOTE"
gitc -C "$COMMS_REPO_DIR" fetch origin
if remote_branch_exists; then
# Preserve any local append-only commit that is awaiting a retry; do not
# reset a checked-out branch to origin here.
if gitc -C "$COMMS_REPO_DIR" show-ref --verify --quiet "refs/heads/$COMMS_BRANCH"; then
gitc -C "$COMMS_REPO_DIR" checkout "$COMMS_BRANCH"
else
gitc -C "$COMMS_REPO_DIR" checkout --track -b "$COMMS_BRANCH" "origin/$COMMS_BRANCH"
fi
gitc -C "$COMMS_REPO_DIR" branch --set-upstream-to="origin/$COMMS_BRANCH" "$COMMS_BRANCH"
else
gitc -C "$COMMS_REPO_DIR" checkout -B "$COMMS_BRANCH"
fi
mkdir -p "$COMMS_REPO_DIR/comms"
ensure_state_dir
}
ensure_remote_branch() {
if ! remote_branch_exists; then
warn "origin branch '$COMMS_BRANCH' does not exist yet"
return 1
fi
}
message_is_from_self() {
local file="$1"
[[ "$file" == *"__from-${AGENT_NAME}__"* ]]
}
changed_message_files() {
local from_sha="$1"
local to_sha="$2"
if [[ -n "$from_sha" ]] && gitc -C "$COMMS_REPO_DIR" cat-file -e "$from_sha^{commit}" 2>/dev/null; then
gitc -C "$COMMS_REPO_DIR" diff --name-only "$from_sha" "$to_sha" -- comms/
else
gitc -C "$COMMS_REPO_DIR" ls-tree -r --name-only "$to_sha" -- comms/
fi
}
tmuxc() {
if [[ -n "$TMUX_SOCKET" ]]; then
tmux -L "$TMUX_SOCKET" "$@"
else
tmux "$@"
fi
}
send() {
require_identity
require_remote
[[ $# -ge 1 ]] || die 'send requires message text'
setup
local body="$*"
local utc filename path
utc="$(date -u +%Y%m%dT%H%M%SZ)"
filename="${utc}__from-${AGENT_NAME}__${RANDOM}${RANDOM}.md"
path="$COMMS_REPO_DIR/comms/$filename"
umask 077
{
printf '%s\n' '---'
printf 'from: %s\n' "$AGENT_NAME"
printf '%s\n' 'to: all'
printf 'utc: %s\n' "$utc"
printf '%s\n\n' '---'
printf '%s\n' "$body"
} >"$path"
gitc -C "$COMMS_REPO_DIR" add -- "comms/$filename"
if ! gitc -C "$COMMS_REPO_DIR" config user.name >/dev/null; then
gitc -C "$COMMS_REPO_DIR" config user.name "$AGENT_NAME mos-comms"
fi
if ! gitc -C "$COMMS_REPO_DIR" config user.email >/dev/null; then
gitc -C "$COMMS_REPO_DIR" config user.email "$AGENT_NAME@localhost"
fi
gitc -C "$COMMS_REPO_DIR" commit -m "comms: ${AGENT_NAME} ${utc}"
local attempt
for attempt in 1 2 3; do
if remote_branch_exists; then
gitc -C "$COMMS_REPO_DIR" pull --rebase origin "$COMMS_BRANCH"
fi
if gitc -C "$COMMS_REPO_DIR" push origin "HEAD:refs/heads/$COMMS_BRANCH"; then
printf 'sent: %s\n' "$filename"
return 0
fi
if [[ "$attempt" -lt 3 ]]; then
warn "push rejected; retrying ($attempt/3)"
gitc -C "$COMMS_REPO_DIR" fetch origin
fi
done
die 'push was rejected after 3 attempts; local message commit remains available for retry'
}
poll() {
require_identity
require_remote
setup
gitc -C "$COMMS_REPO_DIR" fetch origin
ensure_remote_branch || return 0
local head marker last_notified
head="$(gitc -C "$COMMS_REPO_DIR" rev-parse "origin/$COMMS_BRANCH")"
marker="$STATE_DIR/last_notified_sha"
if [[ ! -f "$marker" ]]; then
printf '%s\n' "$head" >"$marker"
printf 'poll: baseline recorded; existing history was not notified\n'
return 0
fi
last_notified="$(<"$marker")"
if [[ "$last_notified" == "$head" ]]; then
return 0
fi
local file count=0
while IFS= read -r file; do
[[ -n "$file" ]] || continue
if ! message_is_from_self "$file"; then
((count += 1))
fi
done < <(changed_message_files "$last_notified" "$head")
if ((count > 0)); then
# Intentionally static except for an integer: never inject peer data into tmux.
tmuxc send-keys -t "$TMUX_SESSION" "[mos-comms] $count new peer message(s) — run: mos-comms.sh read"
tmuxc send-keys -t "$TMUX_SESSION" Enter
fi
printf '%s\n' "$head" >"$marker"
}
read_messages() {
require_identity
require_remote
setup
gitc -C "$COMMS_REPO_DIR" fetch origin
ensure_remote_branch || return 0
local head marker last_read file found=0
head="$(gitc -C "$COMMS_REPO_DIR" rev-parse "origin/$COMMS_BRANCH")"
marker="$STATE_DIR/last_read_sha"
last_read=""
if [[ -f "$marker" ]]; then
last_read="$(<"$marker")"
fi
while IFS= read -r file; do
[[ -n "$file" ]] || continue
if message_is_from_self "$file"; then
continue
fi
found=1
printf '%s\n' "===== $file ====="
# Peer content is printed as data. It is never evaluated or sent to tmux.
gitc -C "$COMMS_REPO_DIR" show "origin/$COMMS_BRANCH:$file"
printf '\n'
done < <(changed_message_files "$last_read" "$head")
printf '%s\n' "$head" >"$marker"
if ((found == 0)); then
printf 'No new peer messages.\n'
fi
}
selftest() {
local remote_ok=0 branch_ok=0 tmux_ok=0 config_ok=0
if ((CONFIG_PRESENT == 1)); then
printf 'PASS config present: %s\n' "$CONFIG_PATH"
config_ok=1
else
printf 'FAIL config present: %s\n' "$CONFIG_PATH"
fi
if [[ -z "$COMMS_REMOTE" ]]; then
printf 'WARN remote reachable: COMMS_REMOTE is not configured\n'
elif gitc ls-remote "$COMMS_REMOTE" >/dev/null 2>&1; then
printf 'PASS remote reachable\n'
remote_ok=1
else
printf 'FAIL remote reachable\n'
fi
if ((remote_ok == 1)) && gitc ls-remote --exit-code --heads "$COMMS_REMOTE" "$COMMS_BRANCH" >/dev/null 2>&1; then
printf 'PASS branch resolves: %s\n' "$COMMS_BRANCH"
branch_ok=1
elif [[ -z "$COMMS_REMOTE" ]]; then
printf 'WARN branch resolves: skipped without a remote\n'
else
printf 'FAIL branch resolves: %s\n' "$COMMS_BRANCH"
fi
if tmuxc has-session -t "$TMUX_SESSION" 2>/dev/null; then
printf 'PASS tmux session exists: %s\n' "$TMUX_SESSION"
tmux_ok=1
else
printf 'FAIL tmux session exists: %s\n' "$TMUX_SESSION"
fi
# Report all checks without making a no-remote first-run unusable.
if ((config_ok == 1 && remote_ok == 1 && branch_ok == 1 && tmux_ok == 1)); then
printf 'PASS selftest complete\n'
else
printf 'FAIL selftest incomplete (see checks above)\n'
fi
}
case "${1:-}" in
setup) [[ $# -eq 1 ]] || usage; setup ;;
poll) [[ $# -eq 1 ]] || usage; poll ;;
read) [[ $# -eq 1 ]] || usage; read_messages ;;
send) shift; send "$@" ;;
selftest) [[ $# -eq 1 ]] || usage; selftest ;;
-h|--help|help) usage ;;
*) usage; exit 2 ;;
esac