CI 2604 red on scripts/pipefail-early-exit.test.mjs. Two lines I added in the previous commit tripped its scan of send-message.sh: [ "$(printf '%s\n' "$rule_lines" | grep -c .)" -eq 2 ] || return 1 top=$(printf '%s\n' "$rule_lines" | head -1) The second is a real violation of the rule the guard enforces: `head` exits after its count, the producer takes SIGPIPE, and under `set -euo pipefail` the caller aborts with rc=141 and no output. That is the same failure mode test-mosaic-worktree-large-repo.sh exists to pin. The first is the guard reading `-eq` as an early-exit grep flag (`-[A-Za-z]*q` matches `-eq`), so `grep ... | ... -eq 2` looks like `grep -q` on one line. A false positive, but the fix removes the shape either way. Both go away by splitting the captured line numbers with parameter expansion instead of a second pass through the pipe. With one rule captured, both halves resolve to the same value and the new `[ "$top" != "$bottom" ]` test rejects it, which is the answer that case wanted anyway. Verified: node --test scripts/pipefail-early-exit.test.mjs green on the load-bearing assertion; test-send-message-verdict.sh still PASS=5 FAIL=0; test-send-message-socket.sh rc=0; agent-send.test.sh PASS=19 FAIL=0.
Inter-Agent tmux Comms — Standard & Tooling
Reliable, self-identifying messaging between Mosaic agents running in tmux panes (Claude Code / Codex / OpenCode REPLs), across hosts.
The addressing standard (required)
Every cross-agent tmux message MUST begin with an addressing preamble:
[<src_host>:<src_session> -> <dst_host>:<dst_session>] <message>
host=hostname -sof the machine the agent runs on (e.g.web1,sb-it-mgr-0-lt).session= the tmux session name (e.g.mos-claude,rev0-4,installer-1).- Replies FLIP the preamble: the recipient answers with
[<dst> -> <src>] ....
Why: a fresh or context-wiped agent always knows who sent a message and to whom. No ambiguity about origin or lane after a tmux wipe / session restart.
Example exchange:
[web1:mos-claude -> sb-it-mgr-0-lt:installer-1] status on #29?
[sb-it-mgr-0-lt:installer-1 -> web1:mos-claude] Q2 done, opening PR #34.
The helper: agent-send.sh
Prepends the preamble automatically (auto-detecting your own host:session) and
delivers reliably to local OR remote panes.
# Local target (same host, default tmux server)
agent-send.sh -s <dst_session> -m "message"
# Local target on a Mosaic fleet socket
agent-send.sh -L mosaic-fleet -s '=coder0' -m "message"
# Remote target (over ssh)
agent-send.sh -H user@host -s <dst_session> -m "message"
# From a file / stdin
agent-send.sh -H user@host -s <dst_session> -f msg.txt
echo "msg" | agent-send.sh -s <dst_session>
Key flags: -L named tmux socket · -s dst session (required) · -H ssh target for remote · -n dst
hostname for the preamble (else auto-resolved) · -m/-f/stdin body · -S
override source label · -v verbose · -r N Enter-flush attempts.
For durable fleet use, prefer exact tmux targets such as =coder0. The helper
normalizes exact session targets to pane-qualified targets internally so pane
commands do not fall back to tmux's prefix matching behavior.
Named socket isolation
Durable Mosaic fleets should use a dedicated tmux socket, for example:
tmux -L mosaic-fleet ls
agent-send.sh -L mosaic-fleet -s '=coder0' -m "status?"
send-message.sh -L mosaic-fleet -t '=coder0' -m "raw pane message"
This keeps fleet operations away from the user's default tmux server. It is the safe rollout path on hosts that already have manual tmux sessions.
Why a helper exists (the submission gotcha)
Pasting into an interactive REPL via raw tmux send-keys is unreliable: a
trailing Enter is frequently swallowed and the message sits as an unsubmitted
draft ("Press up to edit queued messages"). Over an ssh -> nested tmux hop the
plain Enter keyname often does not register at all — C-m is needed.
send-message.sh solves this for a local pane: bracketed-paste the body
(so multi-line content doesn't submit early), pause, then send Enter as its own
keystroke and flush with a second, verifying against a draft heuristic.
agent-send.sh solves the remote case by shipping send-message.sh over ssh
(ssh host bash -s -- ... < send-message.sh) and running it local to the target
pane — so the reliable send-keys always happens on the pane's own host. The remote
needs only bash + tmux + base64; no mosaic install required there. The
message crosses the wire as base64 (-b) to avoid all shell-quoting hazards.
Files
agent-send.sh— inter-agent wrapper (preamble + local/remote dispatch).send-message.sh— low-level reliable single-pane submitter (-bbase64 input).auto-submit-drafts.sh— watchdog that flushes stable unsubmitted prompt drafts on a coordinator pane (default targetmos-claude); run it as a long-lived process alongside the coordinator session.agent-send.test.sh— regression + grammar lock foragent-send.sh.test-send-message-socket.sh— smoke test for named-socket isolation.
Distribution
These live in the installed framework copy at
~/.config/mosaic/tools/tmux/. install.sh rsyncs the framework source tree
to each host, so to propagate permanently, land both files in the framework
source repo and re-run the installer on each host. Until then, agent-send.sh
already works against any reachable host because it ships send-message.sh over
ssh per-send — no pre-install on the target host is needed to send to it.