Skip to content

Instantly share code, notes, and snippets.

@lark1115
Created March 3, 2026 22:28
Show Gist options
  • Select an option

  • Save lark1115/a30d04db6661643b5b72785ccddb3d11 to your computer and use it in GitHub Desktop.

Select an option

Save lark1115/a30d04db6661643b5b72785ccddb3d11 to your computer and use it in GitHub Desktop.
cmux-multi-agent skill v2 — 3エージェント討論で改善されたマルチエージェント通信プロトコル
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
agent-handoff.sh <target-surface> <json-file|- > [--from <surface>] [--cid <id>] [--prev <epoch_ms>] [--dry-run]
Send a task request to another agent via cmux send (terminal input injection).
The JSON payload is wrapped in an envelope with FROM/TO/TYPE=REQ.
Examples:
agent-handoff.sh surface:6 payload.json
echo '{"task":"run tests","context":"repo=cmux"}' | \
agent-handoff.sh surface:6 - --from surface:4
EOF
}
if [[ $# -lt 2 ]]; then
usage
exit 2
fi
target_surface="$1"
payload_src="$2"
shift 2
from_surface=""
cid="hoff-$(date +%Y%m%d-%H%M%S)-$(printf '%03x' $((RANDOM % 4096)))"
prev_ts=""
dry_run=0
while [[ $# -gt 0 ]]; do
case "$1" in
--from)
from_surface="${2:-}"
shift 2
;;
--cid)
cid="${2:-}"
shift 2
;;
--prev)
prev_ts="${2:-}"
shift 2
;;
--dry-run)
dry_run=1
shift
;;
*)
echo "Unknown option: $1" >&2
usage
exit 2
;;
esac
done
# Auto-detect sender surface if not specified
if [[ -z "$from_surface" ]]; then
from_surface="$(cmux identify --json | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("caller",{}).get("surface_ref") or d.get("focused",{}).get("surface_ref") or "")')"
fi
if [[ -z "$from_surface" ]]; then
echo "Could not resolve --from surface from cmux identify" >&2
exit 1
fi
# Read payload
if [[ "$payload_src" == "-" ]]; then
payload="$(cat)"
else
payload="$(cat "$payload_src")"
fi
# Validate JSON: must have task + context
validated_payload="$(python3 - "$payload" <<'PY'
import json
import sys
raw = sys.argv[1]
data = json.loads(raw)
missing = [k for k in ("task", "context") if not data.get(k)]
if missing:
raise SystemExit(f"missing required keys: {', '.join(missing)}")
print(json.dumps(data, separators=(",", ":"), ensure_ascii=False))
PY
)"
# Generate TS (epoch ms, macOS-compatible)
ts="$(python3 -c 'import time;print(int(time.time()*1000))')"
prev_part=""
if [[ -n "$prev_ts" ]]; then
prev_part=" PREV=${prev_ts}"
fi
envelope="[FROM=${from_surface} TO=${target_surface} TYPE=REQ CID=${cid} TS=${ts}${prev_part}] ${validated_payload}"
if [[ "$dry_run" -eq 1 ]]; then
printf 'DRY_RUN: %s\n' "$envelope"
exit 0
fi
# Send via primary channel (terminal input injection)
cmux send --surface "$target_surface" "$envelope"
cmux send-key --surface "$target_surface" enter
printf 'SENT from=%s to=%s cid=%s ts=%s\n' "$from_surface" "$target_surface" "$cid" "$ts"
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
agent-ping.sh <target-surface> [--timeout <seconds>] [--from-surface <surface-ref>] [--prefix <name>]
Examples:
agent-ping.sh surface:1
agent-ping.sh surface:1 --timeout 8 --prefix CODEX
EOF
}
if [[ $# -lt 1 ]]; then
usage
exit 2
fi
target_surface="$1"
shift
timeout_s=5
from_surface=""
prefix="AGENT"
while [[ $# -gt 0 ]]; do
case "$1" in
--timeout)
timeout_s="${2:-}"
shift 2
;;
--from-surface)
from_surface="${2:-}"
shift 2
;;
--prefix)
prefix="${2:-}"
shift 2
;;
*)
echo "Unknown option: $1" >&2
usage
exit 2
;;
esac
done
if [[ -z "$from_surface" ]]; then
from_surface="$(cmux identify --json | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("caller",{}).get("surface_ref") or d.get("focused",{}).get("surface_ref") or "")')"
fi
if [[ -z "$from_surface" ]]; then
echo "Could not resolve from-surface from cmux identify" >&2
exit 1
fi
nonce="$(date +%s%N)"
ping_title="${prefix}_PING_${nonce}"
pong_title="${prefix}_PONG_${nonce}"
body="from=${from_surface};nonce=${nonce};reply_with=cmux notify --title ${pong_title} --body ack:${nonce} --surface ${from_surface}"
cmux notify --title "$ping_title" --body "$body" --surface "$target_surface"
start="$(date +%s)"
while true; do
if cmux list-notifications --json | awk -F'|' -v t="$pong_title" '$5 == t { found=1 } END { exit(found ? 0 : 1) }'; then
echo "OK $pong_title"
exit 0
fi
now="$(date +%s)"
if (( now - start >= timeout_s )); then
echo "TIMEOUT waiting for $pong_title" >&2
exit 1
fi
sleep 1
done

Collaboration Ethics

Share Findings Immediately

If you find an answer before your peer responds, send it via INFO right away. Do NOT sit on results waiting for their reply. If they disagree, reconcile — but withholding is worse than being corrected.

Cross-Validate Before Presenting to User

Never present conclusions as final without peer validation. Send findings, get confirmation or correction, then report jointly.

Co-Author Attribution

When collaborating on git commits, include all contributing agents:

  • Codex: Co-authored-by: Codex <noreply@openai.com> (canonical, from codex-rs/core/src/commit_attribution.rs)
  • Claude: Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
gist-comment.sh <gist-id> <message-file|- > [--prefix <text>]
Examples:
gist-comment.sh c524bf0d3ab446e6f2111b78c3d09abd notes.md --prefix "[Codex]"
echo "Turn summary" | gist-comment.sh c524bf0d3ab446e6f2111b78c3d09abd -
EOF
}
if [[ $# -lt 2 ]]; then
usage
exit 2
fi
gist_id="$1"
message_src="$2"
shift 2
prefix=""
while [[ $# -gt 0 ]]; do
case "$1" in
--prefix)
prefix="${2:-}"
shift 2
;;
*)
echo "Unknown option: $1" >&2
usage
exit 2
;;
esac
done
if [[ "$message_src" == "-" ]]; then
msg="$(cat)"
else
msg="$(cat "$message_src")"
fi
if [[ -n "$prefix" ]]; then
body="${prefix} ${msg}"
else
body="${msg}"
fi
gh api -X POST "/gists/${gist_id}/comments" -f "body=${body}" >/dev/null
echo "COMMENTED gist=${gist_id}"

Envelope & Handoff Schema

Envelope Format

Every agent-to-agent message uses a single-line envelope header:

[FROM=<surface> TO=<surface> TYPE=<type> [CID=<id>] TS=<epoch_ms> [PREV=<epoch_ms>]] <body>

Required:

  • FROM, TO, TYPE, TSTS is sender's epoch milliseconds

Required for TYPE=REQ:

  • CID=<id> — Correlation ID. ACK/RES MUST echo the same CID.

Recommended:

  • PREV=<epoch_ms> — TS of the last envelope received from the other party. Omit on first message.

Generate TS: python3 -c 'import time;print(int(time.time()*1000))'

Types

Type Direction Purpose
HELLO initiator → peer Announce identity at session start
HELLO_ACK peer → initiator Confirm identity
REQ sender → receiver Task request (include size=S|M|L)
ACK receiver → sender Acknowledge receipt (SHOULD within 30s, MUST before heavy work)
RES receiver → sender Task result
ERR either → either Error report
INFO either → either Informational: progress, BUSY notification, no response needed

Handoff Payload (for agent-handoff.sh)

The script wraps a JSON payload inside an envelope and delivers via cmux send. TS is auto-generated. PREV can be passed via --prev flag.

Required Keys

  • task: One-line action request.
  • context: Execution context (repo/path/branch/constraints).

Optional Keys

  • expected_output: What a completed response should include.

CID (Envelope, not payload)

CID is set via the --cid flag of agent-handoff.sh (auto-generated if omitted). It appears in the envelope, not in the JSON payload.

Minimal Example

bash scripts/agent-handoff.sh surface:6 - <<'JSON'
{"task":"Review install.sh and suggest improvements","context":"repo=apollo880/skills"}
JSON

Produces:

[FROM=surface:4 TO=surface:6 TYPE=REQ CID=hoff-20260224-125310 TS=1740412800123] {"task":"Review install.sh and suggest improvements","context":"repo=apollo880/skills"}

Long Payloads

cmux send is single-line. For large results, write to a temp file and reference it:

[FROM=surface:6 TO=surface:4 TYPE=RES CID=hoff-20260224-125310 TS=1740412890789 PREV=1740412800123] done: /tmp/cmux-res-abc.md

OSS Contribution Policy

When filing issues or PRs on external OSS repos:

  • Check existing issues/PRs before filing new ones.
  • Follow the repo's PR format (check recent merged PRs for style).
  • Verify the bug is reproducible on the current release before filing.
  • Always get user confirmation before creating an issue or PR.
  • Write in the repo's primary language (usually English for OSS).
  • Fork from user's account; do not push to upstream directly.

Multi-Agent Messaging Protocol (cmux)

Use this protocol when Claude and Codex collaborate in separate cmux surfaces.

0) HELLO Handshake (Session Start)

Before any task exchange, both agents MUST establish identity.

Initiator sends:

cmux send --surface <peer_surface> \
  "[FROM=<my_surface> TO=<peer_surface> TYPE=HELLO] agent=<name> pane=<pane_ref> workspace=<ws_ref>"
cmux send-key --surface <peer_surface> enter

Responder replies:

cmux send --surface <initiator_surface> \
  "[FROM=<my_surface> TO=<initiator_surface> TYPE=HELLO_ACK] agent=<name> pane=<pane_ref> workspace=<ws_ref>"
cmux send-key --surface <initiator_surface> enter

After HELLO/HELLO_ACK exchange, both agents know each other's surface ref and can communicate.

If the initiator needs to discover the peer surface first:

cmux list-panes --json
cmux list-pane-surfaces --pane <pane> --json

1) Envelope Format

Every message sent via cmux send MUST use the envelope header:

[FROM=<sender_surface> TO=<target_surface> TYPE=<type> [CID=<id>] TS=<epoch_ms> [PREV=<epoch_ms>]] <body>

Required:

  • FROM, TO, TYPE, TSTS is sender's epoch milliseconds (python3 -c 'import time;print(int(time.time()*1000))')

Required for TYPE=REQ:

  • CID=<id> — Correlation ID. ACK/RES MUST echo the same CID. Auto-generated by agent-handoff.sh.

Recommended:

  • PREV=<epoch_ms> — TS of the last envelope received from the other party. Omit on first message of session.

TS - PREV = sender's work time since last received message. Enables self-reflection, peer insight, and historical timing profiling.

Types:

  • HELLO — Session start identity announcement
  • HELLO_ACK — Acknowledge identity
  • REQ — Task request
  • ACK — Received, working on it
  • RES — Task response / result
  • ERR — Error or malformed message report
  • INFO — Informational, no response expected

Examples:

[FROM=surface:4 TO=surface:6 TYPE=REQ CID=hoff-001 TS=1740412800123] size=M details:/tmp/req-001.md output:/tmp/res-001.md
[FROM=surface:6 TO=surface:4 TYPE=ACK CID=hoff-001 TS=1740412815456 PREV=1740412800123] 受領、作業開始
[FROM=surface:6 TO=surface:4 TYPE=INFO CID=hoff-001 TS=1740412850000 PREV=1740412800123] BUSY est=60s reason=reading large file
[FROM=surface:6 TO=surface:4 TYPE=RES CID=hoff-001 TS=1740412890789 PREV=1740412800123] done:/tmp/res-001.md
[FROM=surface:6 TO=surface:4 TYPE=ERR CID=unknown TS=1740412900000] envelope parse error: missing FROM

Single-Line Constraint

cmux send injects text as terminal input. Multi-line messages arrive as separate inputs. Keep all messages on a single line. For large payloads, write to a file and reference it:

[FROM=surface:4 TO=surface:6 TYPE=RES CID=hoff-20260224-001] See /tmp/cmux-result-abc123.md

2) Primary Channel: cmux send (Terminal Input Injection)

All agent-to-agent messages use cmux send + cmux send-key enter:

cmux send --surface <target_surface> "<envelope message>"
cmux send-key --surface <target_surface> enter

Why:

  • Directly appears as input in the peer agent's terminal
  • The peer agent receives it as a "user message" and naturally processes it
  • Delivery is queued even when the peer is busy, but receive timing is non-deterministic
  • Completion should be tracked with ACK/RES (not polling loops)

3) Supplemental Channel: cmux notify (Best-Effort)

Use cmux notify only for human-visible UI cues or logs, NOT for agent communication:

cmux notify --title "<TITLE>" --body "<summary>" --surface <target_surface>

Important:

  • Do NOT rely on notify for agent-to-agent messaging
  • Agents have no event loop to poll notifications
  • Use only for breadcrumbs visible to the human operator

4) ACK / Response Flow

Sender                          Receiver
  |--- [TYPE=REQ CID=x] ------>|
  |                             | (processes)
  |<-- [TYPE=ACK CID=x] -------|  (SHOULD within 30s, MUST before heavy work)
  |    (continue own work)      | (works on task)
  |<-- [TYPE=INFO CID=x] ------|  (optional progress for M/L tasks)
  |<-- [TYPE=RES CID=x] -------|

Rules:

  • TYPE=REQ MUST include a CID. Receiver echoes the same CID in ACK and RES.
  • Receiver MUST send TYPE=ACK before starting heavy work. SHOULD target: within 30 seconds.
  • Receiver sends TYPE=RES when the task is complete, or TYPE=ERR if it fails.
  • If a REQ lacks CID, receiver sends TYPE=ERR CID=unknown with reason "missing CID".
  • For M/L tasks, receiver SHOULD send TYPE=INFO CID=<id> progress updates.
  • Sender escalation: 30s wait → 45-60s INFO ping → 90s+ escalate to user.

5) Driver / Navigator

Default: Driver=Codex (implementation), Navigator=Claude (design/review/quality gate). Can be reversed based on task characteristics.

6) Asynchronous Principle (Non-Blocking)

After sending a message, continue your own work. Do not block your whole turn waiting for immediate reply.

  • Sender: send REQ, then proceed with local work until ACK/RES arrives
  • Receiver: send ACK, do the task, then close with RES (or ERR)
  • Both sides: keep state by CID, not by synchronous lockstep

7) read-screen Usage (Diagnostics / Verification)

cmux read-screen is a support tool, not the primary protocol.

Valid uses:

  • Checking if a peer agent is alive (idle prompt visible?)
  • Debugging when communication breaks down
  • Verifying whether a probe/info message appeared during queue tests
  • User-initiated inspection
cmux read-screen --surface <peer_surface> --lines 30

8) Anti-Patterns to Avoid

  • sleep N + repeated cmux read-screen as a primary wait strategy
  • Treating read-screen polling as a substitute for ACK/RES lifecycle
  • Sending multi-line payloads directly through cmux send
  • Using cmux notify for protocol-critical delivery

Preferred pattern: envelope for control-plane, file pointers for data-plane.

9) Optional Gist Log

skills/cmux-multi-agent/scripts/gist-comment.sh <gist-id> - --prefix "[Codex]"

Feed one concise turn summary via stdin per exchange.

Available Scripts

agent-handoff.sh

Wraps a JSON payload in an envelope and delivers via cmux send. Auto-detects sender surface. Auto-generates CID.

bash scripts/agent-handoff.sh <target_surface> - <<'JSON'
{"task":"Review install.sh and suggest improvements","context":"repo=apollo880/skills"}
JSON

agent-ping.sh

Diagnostic-only responsiveness check via cmux notify. Not for protocol messaging — use cmux send for all agent communication.

gist-comment.sh

Append a turn log to a GitHub Gist comment thread.

scripts/gist-comment.sh <gist-id> - --prefix "[Claude]"

Feed one concise turn summary via stdin per exchange.

validate-envelope.sh

Validates envelope format before sending. Checks required fields, CID presence for REQ, and single-line constraint.

scripts/validate-envelope.sh "<envelope_message>"
# Exit 0 = valid, Exit 1 = invalid with error message
name cmux-multi-agent
description Coordinate multi-agent workflows across cmux panes (Claude Code + Codex + Gemini) using envelope messaging over cmux send. Use when two or more agents must delegate work, exchange status, and keep task ownership unambiguous.

cmux Multi-Agent Ops

Run this skill when multiple coding agents coordinate across cmux surfaces and need deterministic handoff with minimal ambiguity.

CRITICAL: 5 Non-Negotiable Rules

Violating ANY of these rules breaks the protocol. No exceptions.

  1. HELLO before anything else. You MUST NOT send any message to a peer until the HELLO->HELLO_ACK handshake is complete. Check /tmp/cmux-greeted-<my_surface>.txt before every send.

  2. ACK every REQ within 30 seconds. When you receive a TYPE=REQ, send TYPE=ACK BEFORE starting any work. Not after. Not "when convenient." BEFORE.

  3. Close every REQ. Every REQ you ACK MUST eventually receive RES or ERR. No silent drops. No forgotten tasks.

  4. Check for messages at every checkpoint. You MUST check for incoming messages (a) after any tool call taking >10 seconds, or (b) after every 3 consecutive tool calls — whichever comes first. You SHOULD also check if 30 seconds of uninterrupted work have passed. If a message is waiting, process it before continuing.

  5. URGENT messages interrupt immediately. When you see PRIORITY=URGENT in an envelope, STOP current work after finishing your current tool call, process the urgent message, then resume previous work.

Quick Start

# 1) identify yourself
cmux identify --json
# -> capture caller.surface_ref / pane_ref / workspace_ref

# 2) discover peer agent
cmux list-panes --json
cmux list-pane-surfaces --pane pane:7 --json

# 3) HELLO handshake (first contact)
#    validate envelope before sending:
MSG="[FROM=surface:4 TO=surface:6 TYPE=HELLO TS=$(python3 -c 'import time;print(int(time.time()*1000))')]  agent=Claude pane=pane:5 workspace=workspace:2"
scripts/validate-envelope.sh "$MSG" && \
cmux send --surface surface:6 "$MSG" && \
cmux send-key --surface surface:6 enter
# -> peer replies with TYPE=HELLO_ACK when ready

# 4) send a task request via agent-handoff.sh (MANDATORY for REQ)
scripts/agent-handoff.sh surface:6 - <<'JSON'
{"task":"Review install.sh and suggest improvements","context":"repo=apollo880/skills"}
JSON
# -> script handles envelope generation, cmux send + send-key automatically
# -> continue your own work; ACK and RES arrive asynchronously

Envelope Format

Every message is a single line:

[FROM=<surface> TO=<surface> TYPE=<type> [PRIORITY=<level>] [CID=<id>] TS=<epoch_ms> [PREV=<epoch_ms>]] <body>

Required fields:

  • FROM — sender's surface ref
  • TO — target's surface ref
  • TYPE — one of: HELLO, HELLO_ACK, REQ, ACK, RES, ERR, INFO
  • TS — sender's epoch milliseconds: $(python3 -c 'import time;print(int(time.time()*1000))')

Conditional fields:

  • CID — required for REQ, ACK, RES, ERR (correlation ID)
  • PRIORITYURGENT, NORMAL (default, can be omitted), or LOW
  • PREV — TS of last envelope received from the other party (omit on first message)

Single-Line Constraint

cmux send injects terminal input — multi-line messages fragment. Always send on a single line. For large payloads, write to a file and send the path:

[FROM=surface:6 TO=surface:4 TYPE=RES CID=task-001 TS=...] done:/tmp/task-res-001.md

Message Priority

Priority When to Use Receiver Behavior
URGENT User-initiated redirects, error escalations, cancellation requests MUST interrupt current work. Finish current tool call, then process immediately.
NORMAL Standard REQ/RES/INFO flow (default — can be omitted) Process at next checkpoint or when idle.
LOW Background updates, non-blocking informational May defer until current task completes. Do not starve beyond 120s.

Cancellation

To cancel an in-progress task, send a PRIORITY=URGENT REQ:

[FROM=surface:11 TO=surface:75 TYPE=REQ PRIORITY=URGENT CID=cancel-001 TS=...] action=cancel target_cid=task-001 reason=requirements-changed

Receiver MUST: ACK the cancel request CID within 30s, stop work on target CID, send ERR for the target CID (reason=cancelled to properly close it in the state machine), clean up, then send RES for the cancel request CID.

Lifecycle State Machine

Every CID follows this state progression:

new --> acked --> running --> closed
  • REQ creates a CID in new state
  • ACK transitions to acked (MUST happen before heavy work)
  • Work begins: running state
  • RES or ERR transitions to closed (exactly once per CID)

Invalid behaviors:

  • Sending RES or ERR without prior ACK
  • Reusing an active CID for unrelated work
  • Closing the same CID twice
  • Sending REQ before HELLO handshake

Protocol Lifecycle

Sender                          Receiver
  |--- [TYPE=HELLO] ---------->|
  |<-- [TYPE=HELLO_ACK] -------|
  |                             |
  |--- [TYPE=REQ CID=x] ------>|
  |                             | (receives, state: new)
  |<-- [TYPE=ACK CID=x] -------|  (MUST within 30s, state: acked)
  |    (continue own work)      | (works on task, state: running)
  |<-- [TYPE=INFO CID=x] ------|  (progress updates for M/L tasks)
  |<-- [TYPE=RES CID=x] -------|  (or TYPE=ERR, state: closed)

Control Plane vs Data Plane

Control plane (envelope via cmux send): ownership, state transitions, correlation, brief status.

Data plane (files): detailed task descriptions, large outputs, structured artifacts.

[TYPE=REQ CID=abc] details:/tmp/req-abc.md output:/tmp/res-abc.md
[TYPE=RES CID=abc] done:/tmp/res-abc.md

Pre-Send Validation (MANDATORY)

Step 0: Validate envelope format

For REQ messages, use scripts/agent-handoff.sh (MANDATORY — do not manually construct REQ envelopes):

scripts/agent-handoff.sh <target_surface> - <<< '{"task":"...", "context":"..."}'

For all other message types (ACK, RES, ERR, INFO, HELLO, HELLO_ACK), validate before sending:

scripts/validate-envelope.sh "<your_envelope_message>"
# Exit 0 = valid. Exit 1 = fix errors before sending.

If validation fails, fix the message. Do NOT send invalid envelopes.

Step 1: Auto-Greet gate

Check greet file. If target not listed, complete HELLO handshake first (see Auto-Greet below).

Step 2: Self-check

Before pressing send, verify:

  1. FROM, TO, TYPE, and TS are present
  2. CID is present for REQ/ACK/RES/ERR
  3. Message is a single line
  4. You have already sent ACK for any pending REQ from this peer

Checkpoint Discipline

Large tasks cause "hyperfocus" — you become absorbed and stop checking for messages.

The Checkpoint Rule

You MUST check for incoming messages when EITHER of these conditions is met:

  1. After any tool call that takes >10 seconds (file reads, web searches, builds)
  2. After every 3 consecutive tool calls regardless of individual duration

Whichever comes first. Additionally, you SHOULD check if 30 seconds of uninterrupted work have passed as a safety net.

How to Check

Look at your input for any text matching the envelope pattern: [FROM=surface:... TO=surface:... TYPE=...]

If you see an envelope:

  • PRIORITY=URGENT: process it NOW, then resume your work
  • TYPE=REQ: send ACK immediately, then either process or continue current work
  • TYPE=INFO: note it, continue current work

Task Sizing

Include size=S|M|L in REQ body:

Size Duration Checkpoint Required?
S 0-30 sec No mid-task check needed
M 30 sec - 2 min Yes — check at each sub-task boundary
L 2+ min MUST decompose into sub-tasks first

BUSY Notification

When a task cannot be split, notify the peer before starting:

[FROM=surface:4 TO=surface:6 TYPE=INFO CID=abc TS=...] BUSY est=90s reason=reading large file

MUST send when estimated block time >= 60 seconds.

Progress Signaling

  • M tasks: send INFO CID=... if work exceeds initial estimate
  • L tasks: MUST send INFO CID=... at each sub-task boundary
  • Include ETA: [TYPE=INFO CID=x TS=...] ETA: ~30s remaining, outline done

Auto-Greet (MANDATORY gate)

HARD RULE: You MUST NOT send any REQ, INFO, or other envelope to a target before completing the HELLO->HELLO_ACK handshake. No exceptions.

Pre-send checklist (execute EVERY time before cmux send)

  1. Resolve target. Run cmux list-workspaces + cmux list-pane-surfaces --workspace <ws> to confirm workspace, pane, and surface. Never assume from memory.
  2. Check greet file. Read /tmp/cmux-greeted-<my_surface>.txt. If <target_workspace>:<target_surface> is NOT listed -> greet first. If listed -> skip to send.
  3. HELLO handshake (blocking).
    • Write protocol summary to /tmp/cmux-hello-<target_surface>.md (under 800 words)
    • Send HELLO:
      cmux send --workspace <ws> --surface <surface> "[FROM=... TO=... TYPE=HELLO TS=...] agent=<name> protocol:/tmp/cmux-hello-<surface>.md"
      cmux send-key --workspace <ws> --surface <surface> enter
      
    • Append <target_workspace>:<target_surface> to greet file
    • WAIT for HELLO_ACK. Do NOT send REQ until ACK received.

Greet file lifecycle

  • Per-session file in /tmp/, cleaned up on reboot
  • Key targets by workspace:surface (not surface alone)
  • If peer silent for 90s+, remove entry and re-greet

Common Mistakes

WRONG — Sending REQ without HELLO:

cmux send --surface surface:6 "[FROM=surface:4 TO=surface:6 TYPE=REQ CID=task-001 ...]"
# VIOLATION: No HELLO handshake completed with surface:6

WRONG — Starting work before sending ACK:

# Receives REQ -> immediately starts implementing -> sends ACK 2 minutes later
# VIOLATION: ACK MUST be sent BEFORE starting work

WRONG — Ignoring messages during work:

# Working on Task A for 3 minutes straight without checking
# URGENT message from PM arrived 90 seconds ago, unprocessed
# VIOLATION: Must check at every checkpoint; URGENT must interrupt

WRONG — Sending RES without prior ACK:

# Receives REQ -> does the work -> sends RES (skipped ACK)
# VIOLATION: State machine requires new -> acked -> running -> closed

RIGHT — Proper checkpoint behavior:

# Working on Task A (size=L)
# Sub-task 1 done -> check messages -> none -> continue
# Sub-task 2 done -> check messages -> URGENT from PM -> process it -> resume Task A
# Sub-task 3 done -> check messages -> new REQ -> send ACK -> finish Task A -> RES -> work new REQ

Anti-Patterns

  • sleep N && cmux read-screen loops — Defeats async design. Send and continue your work.
  • cmux notify for agent messaging — Agents cannot poll notifications. Use cmux send only.
  • Multi-line payloads via cmux send — Will fragment. Write to file, send path.
  • Unbounded read-screen polling — Never spin-wait for a response.
  • Batching message reads — Do not ignore messages until you finish a task. Check at every checkpoint.

read-screen Usage

Allowed only for:

  • One-shot liveness check (is peer alive?)
  • Debug/incident evidence collection

Never use read-screen as a substitute for ACK/RES protocol.

Sender Escalation

When you send REQ and don't get ACK:

Time Elapsed Action
30s Wait (peer may be busy)
45-60s Send one INFO ping with same CID
90s+ Escalate to user/operator

Do not retry indefinitely.

Guardrails

  • Before every send: Run Pre-Send Validation (Step 0-2). No skipping.
  • Cross-workspace sends: Run cmux list-workspaces to verify workspace->ref mapping.
  • Context changes: Re-run cmux identify --json.
  • Always: Include FROM and TO in every message.
  • Never: Use cmux notify for agent communication.
  • Never: Send multi-line bodies via cmux send.
  • Always: Keep CID stable per task; do not recycle within a session.
  • Always: Close every REQ with RES or ERR.

Scripts

Script Purpose Usage
scripts/agent-handoff.sh MANDATORY for REQ. Wraps payload in envelope, auto-detects surface, auto-generates CID scripts/agent-handoff.sh <surface> - < payload.json
scripts/validate-envelope.sh MANDATORY for non-REQ sends. Validates envelope format scripts/validate-envelope.sh "<envelope>"
scripts/agent-ping.sh Diagnostic liveness check only Not for protocol messaging
scripts/gist-comment.sh Append turn log to Gist scripts/gist-comment.sh <gist-id>

References

Reference Content
references/protocol.md Full envelope protocol and lifecycle details
references/handoff-schema.md Message and payload schema guidance
references/collaboration-ethics.md Findings sharing, cross-validation, attribution
references/oss-policy.md Guidelines for filing issues/PRs on external repos
references/timing-log.md Latency logging format and analysis metrics
references/scripts.md Available helper scripts and detailed usage

Timing Log

Append envelope timing to /tmp/cmux-agent-latency.jsonl for post-session analysis.

Format

{"ts":1740412800123,"from":"surface:20","to":"surface:21","type":"REQ","cid":"abc"}
{"ts":1740412830456,"from":"surface:21","to":"surface:20","type":"ACK","cid":"abc","prev":1740412800123}
{"ts":1740412890000,"from":"surface:21","to":"surface:20","type":"RES","cid":"abc","prev":1740412830456}

Key Metrics

  • ACK_latency = ACK.ts - REQ.ts — how long until receiver acknowledged
  • RES_latency = RES.ts - ACK.ts — how long the actual work took
  • E2E = RES.ts - REQ.ts — total round-trip time

Usage

  • Use past records to improve future size=S|M|L estimates
  • Share timing data with collaborating agents when relevant
  • TS - PREV = how long you worked since the last received message (self-reflection)
#!/bin/bash
# validate-envelope.sh — Validate cmux multi-agent envelope format
# Usage: scripts/validate-envelope.sh "<envelope_message>"
# Exit 0 = valid, Exit 1 = invalid (with error message on stderr)
set -euo pipefail
MSG="${1:-}"
if [[ -z "$MSG" ]]; then
echo "Usage: validate-envelope.sh '<envelope_message>'" >&2
exit 1
fi
ERRORS=()
# Check envelope bracket wrapper
if ! [[ "$MSG" =~ ^\[.+\] ]]; then
ERRORS+=("Missing envelope brackets [...]")
fi
# Check required fields
if ! [[ "$MSG" =~ FROM=surface:[0-9]+ ]]; then
ERRORS+=("Missing or invalid FROM=surface:N")
fi
if ! [[ "$MSG" =~ TO=surface:[0-9]+ ]]; then
ERRORS+=("Missing or invalid TO=surface:N")
fi
if ! [[ "$MSG" =~ TYPE=(HELLO|HELLO_ACK|REQ|ACK|RES|ERR|INFO) ]]; then
ERRORS+=("Missing or invalid TYPE (must be HELLO|HELLO_ACK|REQ|ACK|RES|ERR|INFO)")
fi
if ! [[ "$MSG" =~ TS=[0-9]+ ]]; then
ERRORS+=("Missing TS=<epoch_ms>")
fi
# Check CID requirement for REQ/ACK/RES/ERR
if [[ "$MSG" =~ TYPE=REQ ]] && ! [[ "$MSG" =~ CID= ]]; then
ERRORS+=("TYPE=REQ requires CID=<id>")
fi
if [[ "$MSG" =~ TYPE=ACK ]] && ! [[ "$MSG" =~ CID= ]]; then
ERRORS+=("TYPE=ACK requires CID=<id> (echo sender's CID)")
fi
if [[ "$MSG" =~ TYPE=RES ]] && ! [[ "$MSG" =~ CID= ]]; then
ERRORS+=("TYPE=RES requires CID=<id> (echo sender's CID)")
fi
# Check PRIORITY value if present
if [[ "$MSG" =~ PRIORITY= ]] && ! [[ "$MSG" =~ PRIORITY=(URGENT|NORMAL|LOW) ]]; then
ERRORS+=("Invalid PRIORITY (must be URGENT|NORMAL|LOW)")
fi
# Check single-line constraint
LINE_COUNT=$(echo "$MSG" | wc -l | tr -d ' ')
if [[ "$LINE_COUNT" -gt 1 ]]; then
ERRORS+=("Multi-line message detected ($LINE_COUNT lines). Must be single line.")
fi
# Report results
if [[ ${#ERRORS[@]} -gt 0 ]]; then
echo "INVALID envelope (${#ERRORS[@]} error(s)):" >&2
for err in "${ERRORS[@]}"; do
echo " - $err" >&2
done
exit 1
fi
echo "OK"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment