| name | agent-chorus |
| description | Start or join a local XYZ discussion shared by two or more Claude, Codex, or other agent sessions through a compact six-digit ID. Use when a prompt says “Join XYZ AgentChorus #123456 as agent number two… — use the agent-chorus skill” (older invitations omit the trailing clause, and the legacy phrase “Join XYZ agent2agent #123456…” still refers to this skill — accept all three), when the user asks sessions to talk to each other, or when a participant needs to send, route, inspect, watch, drive, or close a serialized AgentChorus turn. Supports read-only 2–3 minute monitoring, a background-watch doorbell that wakes a live session on its turn, and explicitly authorized hands-free turn commands. Stores one canonical conversation outside Git while retaining legacy relay-system lookup and NEXT: routing; it is not the Producer/Reviewer artifact-review relay. |
AgentChorus (formerly Agent2Agent)
Compatibility (Gen 2 Phase 0 rename)
The skill was renamed Agent2Agent → AgentChorus (2026-08-23, issue #193 Phase 0; possible legal
conflict on the old name). Unchanged on purpose — these are stable interfaces, not branding:
- Invitations: new invitations print "Join XYZ AgentChorus #ID…". The legacy phrase
"Join XYZ agent2agent #ID…" still refers to this skill — accept both; the discussion ID is what
routes, not the name.
- Deprecated CLI shim:
scripts/agent2agent.py still works (warns, delegates to
agent_chorus.py) for one release.
- Store: the default store directory remains
Agent2Agent-Transcripts/ — live discussions
continue in place; no migration in this phase.
- Environment variables:
AGENT2AGENT_HOME, AGENT2AGENT_ROOT, AGENT2AGENT_CONFIG, etc.
keep their names (stable interface consumed by wrappers and tests).
- Transcript format: the
AGENT2AGENT-ID: header key inside conversation.md is unchanged so
existing discussions remain readable.
Use the bundled scripts/agent_chorus.py for every state change. It keeps a stable agent1 through
agentN roster, one active NEXT: writer, and one durable conversation.md outside the Git
working tree. By default the store is Agent2Agent-Transcripts/ beside the canonical repository;
--store, AGENT2AGENT_HOME, or the user config file may override it. Never place the store
inside the coordinated repository. Existing repository-local relay-system/<date>/ discussions
remain discoverable and are advanced in place without copying.
When the operator wants one durable store across repositories whose parents differ, persist it
instead of repeating a machine path in every command:
"$AGENT_CHORUS" configure-store \
--path /private/path/to/Agent2Agent-Transcripts
Locating the helper
Every command below uses "$AGENT_CHORUS" for the helper. Resolve it from this skill's own
directory — the folder that contains this SKILL.md, which the harness reports when it loads
the skill:
AGENT_CHORUS="<this skill's directory>/scripts/agent_chorus.py"
That holds for a copy install (~/.claude/skills/agent-chorus/, a project's .claude/skills/),
a symlink install (the link resolves to the repository copy), and a session started outside any
Git repository. Inside an XYZ-forge clone the same path is
$(git rev-parse --show-toplevel)/skills/agent-chorus/scripts/agent_chorus.py; do not use that
form anywhere else — it prints fatal: not a git repository from a non-repo folder and a
nonexistent path from any other repo. Quote the variable: paths may contain spaces.
The helper needs only the Python 3 standard library and git, on macOS or Linux (it uses
fcntl locking). If python3 fails with Fatal Python error: init_fs_encoding … No module named 'encodings', the harness's shell resolved python3 to an interpreter it cannot read; run the
helper with an explicit interpreter instead, for example /usr/bin/python3 "$AGENT_CHORUS" ….
Pass --root /path/to/repo (or set AGENT2AGENT_ROOT) whenever the discussion concerns a
repository other than the one the skill lives in — under a copy install the default root is the
skill's parent folder, which is rarely the repository under discussion.
Telemetry
The helper records metadata-only telemetry (byte counts, citation counts, flags, seat identities
— never message text) to runtime/telemetry.jsonl beside each discussion and to a SQLite index
in the store. It is ON by default during the pilot window declared in TELEMETRY.md and opt-in
otherwise; AGENT2AGENT_TELEMETRY=0 turns it off in either case and telemetry purge removes
everything. join --model <name> records which model occupies a seat; telemetry audit --id N
proves no transcript content leaked. Mention to the operator that telemetry is on when starting a
discussion inside the window.
Start
Agent 1 is the producer of the handoff, not a courier. Before starting, skim the recent human-agent
conversation and the relevant local evidence. Infer the discussion goal, scope, questions, and done
condition when they are clear. When the intended outcome cannot be inferred safely, ask the human
one focused clarification at a time and do not start until the packet can be completed without
inventing consequential scope. Default to two participants unless the user requests more.
Prepare a UTF-8 Markdown packet with exactly these headings, in this order, and useful content under
each one:
## Goal
## Scope
## Context and current state
## Evidence and artifacts
## Constraints and safety boundaries
## Questions for participants
## Requested outcome / done condition
Include the material the invited agents need to answer without asking the human to copy and paste a
second block. Prefer concise synthesis plus repo-relative paths, issue/PR links, commands, and
observed results. Do not put secrets, credentials, or unrelated conversation into the packet. Write
it to a temporary file and pass that file to start; the helper validates and embeds it as Turn 1.
"$AGENT_CHORUS" start \
--subject "subject line here" \
--packet-file /safe/path/to/context-packet.md \
--agents 2
To supersede a previous discussion atomically, pass --supersedes <old_id>. The helper acquires the
old discussion's lock, sets its status to Closed with SUPERSEDED-BY: <new_id>, appends an atomic
supersession turn, invalidates any active watchers, and seeds the new discussion with SUPERSEDES: <old_id>:
"$AGENT_CHORUS" start \
--subject "revised architecture proposal" \
--packet-file /safe/path/to/context-packet.md \
--supersedes 123456
When the user asks for a 2-minute / 30-minute doorbell, include --timed-watch. It persists on
the discussion and adds an explicit background-watch request to every pasteable invitation. Omit it
otherwise. Return every invitation printed by the helper verbatim so each non-initiator can join
once at startup:
Join XYZ AgentChorus #123456 as agent number two to discuss: "subject line here" — use the agent-chorus skill
Timed two-minute doorbell requested: when waiting, start a background watch that checks every 120 seconds for 1,800 seconds.
Return only the compact invitations printed by the helper; do not append a separate “context to
paste” block. Turn 1 already contains the prepared packet as agent1. For a roster larger than two,
the helper prints one invitation
for every seat from agent2 through agentN. agent2 owns the live turn; later seats may join
immediately, receive DECISION: wait, and arm a doorbell without changing the serialized NEXT:
owner.
The generated conversation.md is both the live canvas and raw transcript. Do not create a second
summary canvas or ask the user to relay its contents. Runtime locks and watch markers live under the
session's runtime/ directory and are not transcript content.
Inspect status
Use the seat-agnostic status view when the operator needs the roster, current writer, or doorbell
liveness without joining as a participant:
"$AGENT_CHORUS" status \
--id 123456
status is strictly read-only: it creates no lock or sidecar and does not refresh an existing
doorbell marker. not observed/manual means only that no watch sidecar exists; it is not evidence
that the participant is absent.
Join an invitation
Parse the six-digit ID, plain-language agent number, quoted subject, and any timed-doorbell request.
Do not create a second file. Resolve and validate the existing discussion read-only first:
"$AGENT_CHORUS" join \
--id 123456 \
--agent 2 \
--expect-subject "subject line here"
DECISION: take-turn: read the returned relay file, formulate a useful response to the whole
discussion—including the prepared packet in Turn 1—then use send or close.
DECISION: wait: do not write. Tell the user which participant owns NEXT:.
DECISION: closed: do not write. Report that the discussion is complete.
Joining is idempotent and never changes the relay file. Add --model <name> (for example
--model claude-opus-5) so telemetry records which model holds this seat; nothing else uses it.
join and send print one peer doorbell (…) line per other seat. none armed — manual seat
means that participant has no watch running and will not notice its turn until a human nudges it;
armed Ns ago but watch process P is not running means its doorbell died. Treat both as manual
when deciding whether a close would be over a seat that cannot respond.
Choose an operating level
Default to watch. Use drive only when the user explicitly asks for hands-free or automatic
participation and supplies or approves the turn command. Never promote a join or watch request into
drive on your own.
| Mode | Command shape | Interval | Timeout | On take-turn | On timeout | On closed |
|---|
| Foreground watch | watch in the foreground | 150 s | as needed (0 = forever) | respond, then send/close | report the stall | report, stop |
| Doorbell | watch --timeout 0 as a background task | 150 s | none | respond, send, run the printed REARM: line | n/a | report, do not re-arm |
| Timed doorbell | watch --interval 120 --timeout 1800 as a background task, only when the invitation requests it | 120 s | 1,800 s | respond, send, run REARM: | decide: run the printed STILL-WAITING: line or report | report, do not re-arm |
| Drive | drive … -- <command> | 150 s | 3,600 s | the command runs | stops visibly | stops visibly |
watch removes its liveness marker when it exits for any reason, so a seat between watches shows
as manual — re-arm promptly after send.
Watch — safe and read-only
Wait until this participant owns NEXT: or the discussion closes. The default interval is 150
seconds, matching a 2–3 minute check cadence. --timeout 0 waits indefinitely; set a positive
timeout when the host session needs a bounded wait.
"$AGENT_CHORUS" watch \
--id 123456 \
--agent 2 \
--interval 150 \
--timeout 0
watch reads only. It never creates a lock, executes another agent, or changes the discussion.
When it prints DECISION: take-turn, formulate the response and use send or close; on
DECISION: closed or timeout, stop and report. A host with
a recurring-loop facility may schedule this command; a plain chat surface cannot become hands-free
merely by leaving instructions in the conversation. A host that can launch a command as a
background task and wake when it exits can do better still — see Doorbell below.
Timed two-minute doorbell — explicit user request
When the user asks the source and target to check for their turn every two minutes for 30
minutes — including through an invitation that says Timed two-minute doorbell requested — treat
that as explicit authorization to start the watches. Do not merely describe the command or ask
either live session to remember to poll. Each participant must launch this command as a background
task when it is waiting for the other participant:
"$AGENT_CHORUS" watch \
--id 123456 \
--agent 2 \
--interval 120 \
--timeout 1800
For the source, launch it immediately after send hands the turn to the target. For the target,
launch it after join returns DECISION: wait. Substitute the participant number for each seat.
If either participant is already assigned NEXT:, it must take that turn first, then start this
background watch immediately after its next send. On take-turn, respond and re-arm from the
printed REARM: command after send; on closed or timeout, do not re-arm. State clearly if
the host does not support background-task wake: the instruction cannot wake a dormant chat session
by itself.
Doorbell — hands-free for live sessions with background-task wake
On a host that re-invokes the session when a background command exits (Claude Code's background
Bash tasks are one such facility), watch becomes a doorbell rather than a poll: the live session
sleeps until it owns the turn, then answers with its full accumulated context. This is the pattern
for bridging two interactive terminal sessions hands-free — where drive runs a fresh headless
command per turn, doorbell turns are composed by the ongoing session itself.
- Join once via the pasted invitation, as normal. If
join prints DECISION: take-turn, the
turn is already yours — take it now (step 4's send-and-re-arm); launch the background watch
of step 2 only when join prints wait. On closed, report and stop.
- Launch
watch as a background task with --timeout 0 (the same command as above; do not
hold a foreground call open on it).
- When the background
watch exits and the host wakes the session: read the printed DECISION:.
On take-turn, read the relay file, compose the reply, and use send or close. On closed,
stop and report — do not re-arm. On timeout the watch prints a STILL-WAITING: line followed
by the relaunch command: the window expired while the peer still held the turn, so decide
whether the wait is still worth continuing, then either run that command or report the stall.
It is deliberately not labelled REARM: — re-arming after a timeout is a judgment call, not a
reflex. If watch exited without printing a DECISION: line (a crash — don't key off the
exit code alone: a timeout also exits non-zero but still prints DECISION: timeout), do not
guess and do not re-arm blindly: rerun join read-only to learn the discussion's actual state,
and report the failure to the operator.
- Re-arm as part of the send step — the command is handed to you. A
watch that exits
take-turn also prints a REARM: line: the exact, self-contained relaunch command (absolute
script path and --root included, so it runs verbatim from any CWD). In the same turn you
send, run that printed line as a background task (only after send — never after close;
a closed discussion has no further turns, and a closed or timeout exit prints no REARM:
line for exactly that reason). On your first turn after a take-turn join — where no watch
has exited yet — use step 2's command. A doorbell that is not re-armed silently downgrades the
seat to manual — the discussion stalls with no error, which reads identically to the other
participant still thinking. The printed line exists so re-arming is protocol the tool enforces
at the moment it matters, not discipline the session must remember.
Two doorbell seats ping-pong indefinitely after one paste each. Seats degrade independently: a
surface without background wake keeps using foreground watch, drive, or manual turns in the
same roster.
Doorbell changes only the wake mechanism. The relay file remains the source of truth, watch
still never writes or locks, and every write still goes through send/close ownership
enforcement.
Drive — explicit hands-free mode
Require an explicit turn command after --. Drive polls like watch, invokes that command only when
this participant owns NEXT:, and then verifies that the command advanced the turn through the
normal helper. The command receives the compact invitation prompt on stdin and these environment
variables: AGENT2AGENT_ID, AGENT2AGENT_AGENT, AGENT2AGENT_MEMBER,
AGENT2AGENT_RELAY_FILE, AGENT2AGENT_ROOT, and AGENT2AGENT_SUBJECT.
"$AGENT_CHORUS" drive \
--id 123456 \
--agent 2 \
--interval 150 \
--timeout 3600 \
--max-turns 6 \
-- /absolute/path/to/approved-agent-turn-command
Use an argument-vector command or wrapper that reads its prompt from stdin. Do not interpolate
untrusted discussion content into a shell command. The turn command must use this skill's send or
close operation. Drive verifies an observable advance and handoff but does not sandbox or prove
the internal behavior of an operator-supplied command. One drive process may own a
participant/discussion lane at a time. Ctrl-C, closure, timeout, the turn cap, contention, or a
non-zero command exit stops visibly.
Send and route
Only send when join or watch says take-turn. Choose any other roster member as the next
participant.
For multiline content, prefer a UTF-8 message file or stdin rather than interpolating model output
into an unquoted shell command.
"$AGENT_CHORUS" send \
--id 123456 \
--agent 2 \
--next-agent 3 \
--message-file /safe/path/to/message.md
To stream a message through stdin without interpolating its contents into the command:
"$AGENT_CHORUS" send \
--id 123456 \
--agent 2 \
--next-agent 3 \
--message-file - < /safe/path/to/message.md
send prints a RECEIPT: line (turn number, bytes, citation count, routed-to seat), one
PEER-TURNS: line per other seat (when it last wrote), the peer doorbell lines, and the next
invitation. Return the RECEIPT: line and the invitation verbatim; the receipt is what lets the
operator see that the turn happened without opening the transcript.
Never report an asynchronous or remote action as complete merely because it started. Wait for the
command to exit successfully, verify the observable result, and put the receipt in the turn—for
example, the remote commit SHA, completed CI URL, or migration status. When a turn claims a clean,
pushed Git handoff, add --check-clean; the helper refuses the handoff unless the working tree is
clean, the branch has an upstream, and local HEAD exactly matches it:
"$AGENT_CHORUS" send \
--id 123456 --agent 2 --next-agent 1 --check-clean \
--message-file /safe/path/to/verified-handoff.md
Extend scope
If the operator adds a material question after the session starts, do not improvise an ordinary
turn or close against the superseded done condition. The current NEXT: owner records the question,
the replacement done condition, and the participant who should answer next:
"$AGENT_CHORUS" extend \
--id 123456 --agent 2 --next-agent 1 \
--question "What if the canonical artifact is retired entirely?" \
--done-condition "Compare retirement with migration and recommend one."
The helper appends a numbered Scope Extension — Operator Follow-Up, updates EXTENSIONS:, and
routes the turn atomically. Every participant must treat the newest extension's done condition as
the live close criterion.
Heartbeats during long work
While the current turn owner is running a long test, build, or review, it may refresh its runtime
heartbeat without adding a transcript turn:
"$AGENT_CHORUS" ping \
--id 123456 --agent 2
An aged heartbeat for the active NEXT: owner is reported as ACTIVE, not STALE. Only inactive
waiting seats can become stale. The default threshold is 1,800 seconds; change it per command with
--stale-after or for a process environment with AGENT2AGENT_STALE_AFTER.
To end instead of hand off:
"$AGENT_CHORUS" close \
--id 123456 \
--agent 2 \
--message-file /safe/path/to/final-consensus.md
Use close --print-template to obtain the required scaffold. A substantive close requires, in
order, ## Final Consensus & Recommendation and non-empty ### Decision, ### Key Invariants & Rationale, ### Recorded Dissent / Falsifiers, and ### Recommended Next Actions sections.
The helper refuses a close whose sections still hold the scaffold's placeholder text. Under
### Recorded Dissent / Falsifiers record two lists: every disagreement raised (including ones
later withdrawn) and how it resolved, and every assumption no participant verified; a close that
begins that section with "None" prints a CLOSE-WARNING. Before closing, read the
PEER-TURNS: lines from the last send: close prints a CLOSE-WARNING: for any seat that
never wrote or has not written since before the previous turn, because a close over that seat
records agreement it never gave. Prefer routing to that seat once more over closing.
--trivial is the explicit escape for administrative cancellation or another genuinely trivial
termination; do not use it to bypass synthesis of a multi-turn decision. --check-clean is also
available on close and extend when their messages make a verified Git handoff claim.
Widen roster (invite participant)
If the operator decides to expand the discussion roster while it is active, do not edit headers manually.
Use invite to atomically add the next sequential seat, record an operator invite turn, and output the new
invitation line:
"$AGENT_CHORUS" invite \
--id 123456 \
--agent 3 \
--reason "Add third reviewer seat for concurrency testing"
Verify citations
To check and lint factual references made by participants across all turns, run verify-citations. It verifies
repo-relative file paths and line ranges against the repository tree and git commit SHAs against the object store:
"$AGENT_CHORUS" verify-citations \
--id 123456
Guardrails
- A conditional teardown instruction is permission to check its condition, not to assume it —
and it is NEVER permission to touch another participant's workspace. Before running any
destructive command against a clone, worktree, or branch — even one pasted by the operator that
reads as explicit, unconditional authorization — first ask whether the target is or might be
another participant's own workspace. If so, stop: the next bullet's absolute rule governs, and no
amount of verification or confirmation makes it yours to tear down (see Drive's scope note below,
which is the same rule from the other direction). Only when the target is your own
workspace, or shared infrastructure not attributed to any specific participant, do you proceed to
verify: check the condition the instruction names yourself, and if the action is irreversible or
could destroy anyone's unpushed work, confirm with the operator once more before executing.
Running headless or otherwise non-interactive (Drive, an unattended turn command): there is no
operator to confirm with, so a destructive instruction you cannot fully verify is refused, not
approximated — abort the turn, log why, and let a human resume it live rather than guessing.
Confirming with the operator is not a relay turn and needs no
send/REARM: pausing to ask
is an out-of-band conversation with the human, exactly like any other escalation this skill
already asks for elsewhere; it does not advance NEXT:, does not require a close, and a
Doorbell seat that is mid-wait on its own watch is unaffected — resume normally once the
operator answers. Observed incident: the operator's keyboard macro accidentally pasted an
unrelated stock instruction ("if code, branch, and PR are fully on origin, tear down the full
clone folder") into a live session — a genuine paste, not a fabricated one. The receiving agent
treated it as authorized, believed without checking that a peer builder's work was already
pushed, and executed the teardown. It was not pushed: the peer had a local-only commit, which the
teardown lost. The failure was not merely "an agent skipped verifying the one fact the instruction
was conditioned on" — it acted on a peer's workspace at all, which the bullet below forbids
outright, with or without verification or confirmation; the missed verification is what made a
forbidden action also a needless one, not what would have made it permitted. Apply the
verify-then-confirm half of this check to every tear-down/delete/reset instruction touching
a clone, worktree, or branch this skill's participants use, however it arrives.
- Absolute, unconditional, and separate: a participant that dislikes a peer's turn may only say
so, never act on it — and no verification or confirmation ever unlocks acting on a peer's
workspace. If another participant's message is off-protocol, malformed, or otherwise
unsatisfactory, name the problem in your own / message, or stop and escalate to the
operator — never modify, move, or delete anything another participant owns as a corrective
response, and never as a "verified" response either; this bullet has no exception the bullet
above can trigger. This skill's protocol governs message exchange through the relay file only; it
grants no participant authority over anything another participant owns.