| name | agents-talk |
| description | Cross-agent messaging protocol via `tools agents`. Use when the main agent is about to spawn subagents that should be able to message each other (or back to the main agent) during their work. Teaches the login (auto-registers) / message / discover / listen pattern, the stream-vs-once receive modes, and the no-loss delivery contract. |
/agents-talk — cross-agent communication protocol
You're about to spawn (or have just been spawned as) one of N agents that need to exchange messages while they work. This skill teaches how. All communication goes through tools agents. No MCP server is required.
Mental model in one sentence
There's a shared feed.jsonl per session under ~/.genesis-tools/agents/<session>/. Anyone can append events (login auto-registers, message). Each agent runs a long-lived login process that tails the feed, filters lines addressed to them, and emits them as JSONL on stdout — which the harness Monitor tool follows.
Topology (the only one you need)
┌──────────────────────────┐
┌──────────► │ feed.jsonl │ ◄──────────┐
│ │ (append-only, seq #) │ │
│ └──────────────────────────┘ │
│ ▲ ▲ │
│ │ append │ append │
│ │ │ │
│ ┌────┴────┐ ┌─────────┐ ┌────┴────┐ │
└──┤ lead │ │ research│ │ reviewer├──┘
│ (main_) │ │ er │ │ │
└─────────┘ └─────────┘ └─────────┘
login login login
(stream) (once-loop (once-loop
or stream) or stream)
Every agent is symmetric: anyone can message anyone, anyone can broadcast. The "lead" is just an agent with main_ prefix and is_main:true in the registry.
Main agent flow (you're the orchestrator)
tools agents login --agent-main --agent-name lead
tools agents message --from lead --to researcher \
--body 'find recent React Compiler benchmarks and post the findings'
tools agents message --from lead \
--body 'team status check'
tools agents discover
tools agents listen
tools agents login --agent-name lead --kinds message,error,approval_request
tools agents login --agent-name lead --filter '.op=="approval_request"'
tools agents login --agent-main --agent-name lead --debug
Subagent flow (you've just been spawned)
Your spawn prompt should already include your --agent-name. The very first thing you do:
tools agents login --agent-name researcher
while true; do
tools agents login --agent-name researcher --once
done
⚠️ Monitor only stdout, never stderr. login's stdout is the JSONL event stream. Stderr is for diagnostics (auto-registration notices, the resume-hint on exit, warnings). If you pipe with 2>&1 you'll see log lines in your event stream and the agent will treat them as malformed events. Correct form: tools agents login --agent-name X | <pipeline> — no 2>&1. If using a Monitor harness directly, point it at stdout only.
⚠️ Idle teammates do NOT wake on agents-channel traffic (Claude Code)
Empirically verified 2026-07-13 (two live probe tests + teammate-transcript forensics):
- A teammate whose turn has ENDED (idle) is not re-invoked by Monitor events on its login stream, and the pending Monitor notification is not injected at wake either — transcript inspection showed no monitor-event entry while idle nor in the wake bundle. At best it trickles in mid-turn once the teammate is already active again (observed arriving AFTER the teammate had manually read the output file). The login background process stays alive and keeps writing events to its output file; the harness just never turns that into a wake for a subagent.
- Background-task completion (
login --once exiting when a message arrives) does not wake an idle teammate either.
- The only channel that wakes an idle teammate is the harness-native
SendMessage tool.
- The MAIN session is different: its Monitor events DO re-invoke it between turns. The asymmetry affects teammates only.
- Consequence for the woken teammate: it must actively drain — Read the login task's output file, or run
tools agents login --agent-name <me> --once — because the missed events will never be replayed into its context by the harness.
Dual-channel protocol (required whenever the recipient may be idle):
- Payload goes on the agents channel (durable, cursor-tracked, no loss):
tools agents message --from lead --to researcher --body '...'
- Immediately follow with a harness wake nudge:
SendMessage(to: "researcher", "agents-mail waiting — drain your stream")
- The woken teammate drains explicitly — Reads the login task's output file or runs
tools agents login --agent-name researcher --once — then replies on the agents channel. (Do not wait for the Monitor notification: it was dropped, not queued.)
Never put the payload only in SendMessage (not durable, not cursor-tracked) and never rely on the agents channel alone to wake an idle teammate. A teammate that is mid-turn WILL receive Monitor events normally between tool calls — the nudge is only load-bearing for idle recipients, but since the sender can't know, always send it.
login writes received events to stdout as JSONL lines. Each line is one event you should react to.
tools agents message --from researcher --to reviewer \
--body 'I found library X has a critical bug in v2.1'
tools agents message --from researcher \
--body 'finding #1 ready for review'
tools agents message --from reviewer --reply 0001 \
--body 'confirmed — also affects v2.0'
tools agents message --from reviewer --reply 0001
tools agents request --from reviewer --to lead --body 'Approve the auth change?'
Long-lived Codex teammates
tools codex spawn creates a persistent app-server session and auto-registers codex_<name> on this same bus. Do
not manually log that identity in from the orchestrator. The driver observes inbound controls; the model receives with
the seeded tools agents login --agent-name codex_<name> --once --session <id> command.
Sessions are read-only unless you deliberately choose a write policy:
tools codex spawn --name implementer --write ask --prompt 'Implement the bounded change'
tools codex spawn --name implementer --write allow --prompt 'Implement the bounded change'
Omit --write (or use --write deny) for reviewers. Use --write ask as the default when edits are needed; use
--write allow only when the task and writable roots are tightly bounded.
What you receive on the login stream
Each event is a JSON line. The most important type values:
| type | meaning |
|---|
message | someone sent you (or broadcast) something. There is no separate respond/ack event — a reply is a message with in_reply_to set to the original message_id (empty body = pure ack). |
logged_in / logged_out | a peer joined or left. Main agents see real joins/leaves by default; non-main peers see nothing unless the swarm was started with --debug. --once-mode polling churn is always hidden. |
You never see your own sends. The CLI filters out events where from_agent_id == your id before they reach your stream — no echo-prevention logic needed on your end.
The tool dedupes for you. Delivery uses a monotonic per-agent cursor (slots/<agent_id>.cursor), not a content hash — a crashed-and-reconnected receive process resumes from its last-acked seq instead of re-emitting already-seen events.
Receive-mode comparison
| --stream (default, CC) | --once (any host) |
|---|
| Lifetime | Long-running, ~8h sanity cap | Returns when a message arrives or harness kills it |
| Harness tool | Monitor follows stdout | Caller re-invokes in a loop |
| Resume after exit | Continues where it left off | Continues where it left off |
| Cost | One held process per agent | One short-lived call per receive |
| Best for | Always-on receive during a task | Polling pattern when Monitor isn't available |
On exit (signal, cap, or crash), the tool prints a tools agents login ... resume command on stderr so the caller knows exactly how to continue.
Session resolution
The CLI auto-detects the session in this order:
--session <id> explicit
$CLAUDE_CODE_SESSION_ID env var (set by Claude Code; subagent Bash inherits it)
- Single session active (feed touched) in the last 60 seconds
- Otherwise: a friendly error asking for
--session or $CLAUDE_CODE_SESSION_ID
You normally don't pass --session — it's automatic.
Common pitfalls
- Don't poll the file yourself with
cat. Use login (or login --once). The tool handles cursors, dedup, and filtering.
- There's no separate register step.
login --agent-name X auto-registers X the first time it's called — just spawn the subagent and have it call login directly.
- Don't message agents that aren't registered. You'll get an error. Call
discover if unsure.
- Don't expect mid-tool-call interrupts. Stream-mode
login delivers between tool calls (via Monitor), login --once returns when next called. Neither preempts a running tool.
- Don't expect an idle teammate to wake on an agents-channel message. See the idle-teammates section above — pair every send to a possibly-idle teammate with a harness
SendMessage nudge.
- One main per session. A second
login --agent-main errors. Use a different --agent-name for additional coordinators.
- Receiver filters intentionally advance that receiver's cursor past non-matches. Use a dedicated monitor identity
when you may later need the unfiltered stream.
Quick reference
| Command | Purpose |
|---|
tools agents login --agent-main --agent-name lead [--debug] | Auto-register + attach as main, stream mode (optionally enable verbose lifecycle for the whole swarm) |
tools agents login --agent-name X | Auto-register + attach as X, stream mode (Monitor follows stdout) |
tools agents login --agent-name X --once | Auto-register + attach, one-shot mode (poll loop) |
tools agents login --agent-id Y --agent-name X | Attach with a chosen id |
tools agents message --from X --to Y --body '...' | Direct |
tools agents message --from X --body '...' | Broadcast (every peer except the sender) |
tools agents message --from X --reply 0001 --body '...' | Reply (auto-routes to the original sender) |
tools agents message --from X --reply 0001 | Pure ack (no body) |
tools agents message --from X --to Y --body-file <path> | Long/multi-section body — write it to a file first (avoids shell-quoting breaks from embedded '/`/$(...) truncating --body) |
tools agents request --from X --to Y --body '...' | Send and block until a correlated reply arrives |
tools agents login --agent-name X --kinds message,error | Receiver-side event/body-kind filter |
tools agents login --agent-name X --filter '.op=="approval_request"' | Receiver-side structured-body filter |
tools agents discover | List all agents in session |
tools agents listen | Human-facing color-formatted feed follower (sees everything) |
ID formats (per session)
agent_id for subagents: agt_0001 → agt_ffff (monotonic, 4-hex zero-padded)
agent_id for the main agent: main_<sessionSlug> (derived from session id; recognizable at a glance)
message_id: 0001 → ffff (monotonic, 4-hex, same cap as agent_id)
seq: monotonic feed sequence number (decimal, unbounded for v1 practical use)