Skip to content

Instantly share code, notes, and snippets.

@hqman
Forked from lark1115/SKILL.md
Created March 29, 2026 14:02
Show Gist options
  • Select an option

  • Save hqman/9cfca8592f632755a34ea92dd172796e to your computer and use it in GitHub Desktop.

Select an option

Save hqman/9cfca8592f632755a34ea92dd172796e to your computer and use it in GitHub Desktop.
cmux-multi-agent: Envelope-based multi-agent messaging protocol for cmux (Claude Code + Codex CLI)
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
agent-handoff.sh <target-surface> <json-file|- > [--from <surface>] [--cid <id>] [--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)))"
dry_run=0
while [[ $# -gt 0 ]]; do
case "$1" in
--from)
from_surface="${2:-}"
shift 2
;;
--cid)
cid="${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
)"
envelope="[FROM=${from_surface} TO=${target_surface} TYPE=REQ CID=${cid}] ${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\n' "$from_surface" "$target_surface" "$cid"
#!/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
#!/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>] <body>

Required for TYPE=REQ:

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

Optional (any type):

  • TS=<iso8601> — Timestamp

Types

Type Direction Purpose
HELLO initiator → peer Announce identity at session start
HELLO_ACK peer → initiator Confirm identity
REQ sender → receiver Task request
ACK receiver → sender Acknowledge receipt (within 30s)
RES receiver → sender Task result
ERR either → either Error report
INFO either → either Informational, no response needed

Handoff Payload (for agent-handoff.sh)

The script wraps a JSON payload inside an envelope and delivers via cmux send.

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] {"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] result in /tmp/cmux-res-abc.md
interface:
display_name: "cmux Multi-Agent Ops"
short_description: "Coordinate Claude/Codex handoffs in cmux panes"
default_prompt: |
Use $cmux-multi-agent to coordinate Claude and Codex across cmux panes.
Treat cmux send as the primary channel and always use envelope-based messages:
[FROM=<surface_ref> TO=<surface_ref> CID=<id> TYPE=<HELLO|HELLO_ACK|REQ|ACK|RES|ERR> TS=<iso8601>] <body>
When you receive a cmux message:
1) Run `cmux identify --json` and read your own `surface_ref`.
2) Parse envelope fields (FROM, TO, CID, TYPE, TS).
3) If TO does not match your `surface_ref`, ignore the message.
4) If envelope is malformed, send `TYPE=ERR` with the same CID (or `CID=unknown`) and a one-line reason.
ACK behavior:
- `TYPE=REQ` MUST contain a CID. If CID is missing, reply with `TYPE=ERR CID=unknown` and reason "missing CID".
- For `TYPE=REQ`, return `TYPE=ACK` with the same CID within 30 seconds, then work on the task.
- After completing the task, send `TYPE=RES` with the same CID and concise output.
- If waiting on dependency/user input, still ACK first, then send progress updates as needed.
HELLO behavior:
- For `TYPE=HELLO`, reply immediately with `TYPE=HELLO_ACK` and include:
`agent_name`, `window_ref`, `workspace_ref`, `pane_ref`, `surface_ref`, `reply_surface`.
- On first outbound message in a new session, send `TYPE=HELLO` before `TYPE=REQ`.
Delivery commands:
- `cmux send --surface <target_surface> "<envelope + body>"`
- `cmux send-key --surface <target_surface> enter`
- `cmux send` payload must be single-line only.
- For long or multi-line outputs, write to a file and send only a one-line pointer:
`[FROM=... TO=... TYPE=RES CID=...] result in /tmp/cmux-res-<cid>.md`
Do not assume notify delivery for protocol correctness.

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>] <body>

Required for TYPE=REQ:

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

Optional (any type):

  • TS=<iso8601> — Timestamp for debugging

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-20260224-125310-a3f] apollo-skillsのinstall.shを改善して
[FROM=surface:6 TO=surface:4 TYPE=ACK CID=hoff-20260224-125310-a3f] 了解、作業開始
[FROM=surface:6 TO=surface:4 TYPE=RES CID=hoff-20260224-125310-a3f] 改善案: ...
[FROM=surface:6 TO=surface:4 TYPE=ERR CID=unknown] 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
  • No polling or screen scraping needed

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] -------|  (within 30s)
  |                             | (works on task)
  |<-- [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 within 30 seconds of receiving a TYPE=REQ
  • Receiver sends TYPE=RES when the task is complete
  • If a REQ lacks CID, receiver sends TYPE=ERR CID=unknown with reason "missing CID"
  • If no ACK within 30 seconds: sender resends once with (RETRY) appended
  • If still no ACK: escalate to user
  • Do NOT poll with read-screen. Wait for the response as terminal input.

5) Driver / Navigator

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

6) read-screen Usage (Diagnostics Only)

cmux read-screen should NOT be used as the primary response mechanism.

Valid uses:

  • Checking if a peer agent is alive (idle prompt visible?)
  • Debugging when communication breaks down
  • User-initiated inspection
cmux read-screen --surface <peer_surface> --lines 30

7) Optional Gist Log

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

Feed one concise turn summary via stdin per exchange.

name cmux-multi-agent
description Coordinate multi-agent workflows across cmux panes (Claude Code + Codex) using envelope-based messaging, HELLO handshake, and ACK/RES discipline over cmux send. Use when two or more agents must delegate work, exchange status, and avoid cross-pane ambiguity.

cmux Multi-Agent Ops

Run this skill when Claude and Codex are active in separate cmux surfaces and need deterministic messaging.

Quick Start

# 1) identify your own location
cmux identify --json
# → 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)
cmux send --surface surface:6 \
  "[FROM=surface:4 TO=surface:6 TYPE=HELLO] agent=Claude pane=pane:5 workspace=workspace:2"
cmux send-key --surface surface:6 enter
# → peer replies with TYPE=HELLO_ACK

# 4) send a task request
cmux send --surface surface:6 \
  "[FROM=surface:4 TO=surface:6 TYPE=REQ CID=hoff-20260224-001] install.shを改善して。context: repo=apollo880/skills"
cmux send-key --surface surface:6 enter
# → peer replies with TYPE=ACK CID=hoff-20260224-001, then TYPE=RES CID=hoff-20260224-001

Envelope Format

Every message uses a single-line envelope header:

[FROM=<surface> TO=<surface> TYPE=<type>] <body>

Required for TYPE=REQ: CID=<id> (correlation ID, echoed in ACK/RES). Optional: TS=<iso8601> (timestamp).

Types: HELLO, HELLO_ACK, REQ, ACK, RES, ERR, INFO

Single-Line Constraint

cmux send injects terminal input — multi-line messages fragment into separate inputs. 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=hoff-20260224-001] result in /tmp/cmux-res-abc.md

Protocol

  1. HELLO first — Exchange TYPE=HELLO / HELLO_ACK before any task. Both agents must know each other's surface ref.
  2. Envelope always — Every cmux send message includes [FROM=... TO=... TYPE=...].
  3. cmux send as primary channel — Use cmux send + cmux send-key enter for all agent messaging. Not cmux notify.
  4. ACK → RES flow — Receiver sends TYPE=ACK within 30 seconds, then TYPE=RES when done. Sender does NOT poll with read-screen.
  5. One retry max — If no ACK within 30s, resend once with (RETRY) appended. Then escalate to user.
  6. Driver / Navigator — Default: Codex=Driver (implementation), Claude=Navigator (design/review). Reversible.

Scripts

  • scripts/agent-handoff.sh Purpose: Wrap a JSON payload in an envelope and deliver via cmux send (primary channel). Auto-detects sender surface. Auto-generates CID.

  • scripts/agent-ping.sh Purpose: Send a ping to check if peer is responsive. NOTE: Only works if target agent is in an active conversation.

  • scripts/gist-comment.sh Purpose: Append a turn log to a GitHub Gist comment thread.

References

Reference Use
references/protocol.md Full messaging protocol (HELLO, envelope, ACK/RES)
references/handoff-schema.md Envelope format and JSON payload schema

Guardrails

  • Always start with cmux identify --json before sending.
  • Always include FROM and TO in every message.
  • Do not use cmux notify as the primary agent channel.
  • Do not use cmux read-screen as the primary response mechanism.
  • Do not retry indefinitely; one retry max, then escalate.
  • Keep messages on a single line; use file references for large payloads.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment