send-message.sh located the REPL input box with grep -E '❯|^>|│ >'. That set is
Claude Code's box. A pi seat renders a bare U+2500 rule with no glyph, so on every
idle pi seat the capture succeeded, the grep matched nothing, status stayed
"unconfirmed", and the tool exited 2 "may be UNDELIVERED" with the paste and the
Enter both landed. The stderr tells the operator to retry, and that retry is the
duplicate delivery reported against the same tool.
Confirmation is now runtime-agnostic: our message tail sits on the input line
(located by cursor row, no glyph) before Enter and has left it after. That
transition is positive proof of submission.
Absence still proves nothing, which is the guard the 2026-08 fix was reaching for
and got backwards. Two positive checks keep it:
- a prompt box that IS locatable and still carries our tail => draft, exit 2.
This covers the cursor-row blind spot: a cooked pane whose foreground process
never reads stdin echoes the paste through the kernel line discipline and
moves the cursor off it on Enter, which by cursor row alone is indistinguishable
from a real submit.
- no draft ever observed on the input line => unconfirmed, non-zero.
Tests, both red-first against the shipping blob d397907:
test-send-message-glyph-agnostic.sh (new, 6 fixtures) 4/6 -> 6/6
test-send-message-verdict.sh (fixture 2 reshaped, 2b added) 3/4 -> 4/4
Fixture 2 of the verdict suite asserted exit 2 for a glyphless pane that submits
and was labelled "false-positive FIXED". A pi seat is that fixture, so the suite
was locking the bug in. It is reshaped deliberately, and the guard it was credited
with moves to new fixture 2b (glyphless AND non-submitting, raw/no-echo) so the
"never infer delivered from absence" property is tested positively rather than as
a side effect.
Measured on tmux 3.7b (sb-it-1-dt), 3.5a (fomo-lin), and dragon-lin.
Co-authored-by: scooby <[email protected]>
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.