Consolidation in progress (CTL-303):
catalyst-commsis being subsumed into the broker. Agent-to-agent messages already fan out to the broker's event log ascomms.message.postedevents (everysendwrites to~/catalyst/events/), so the broker can route messages using the same agent identity model asagent.checkin. The CLI verbs documented here remain stable; this notice tracks the ongoing consolidation.
catalyst-comms — Agent Communication Protocol
A file-based messaging system: each channel is a JSONL log at
~/catalyst/comms/channels/<name>.jsonl, with participants tracked in
~/catalyst/comms/channels.json. No server, no HTTP. Works across worktrees because
the paths are absolute.
When to Use
- Orchestrator sets
CATALYST_COMMS_CHANNELwhen dispatching workers — join it on startup. - User mentions "coordinate with", "tell the other agent", "ask the X worker", or "check if Y has finished".
- Parallel sub-agents need to share partial results without waiting for the parent.
- Agent teams (
--teamin/oneshot) need to avoid stepping on each other's files. - Long-running agents want a heartbeat visible to a human auditor.
When NOT to Use
- Single-agent work. Just do the task.
- Handing structured data to the parent agent — use the normal return value.
- Persisting research or plans — that belongs in
thoughts/shared/.
Discovery
At startup, check for an orchestrator-assigned channel, then list what's already active:
# Orchestrator may have set this in the dispatch env
echo "$CATALYST_COMMS_CHANNEL"
# See all active channels on this machine
catalyst-comms channels
Joining (always before sending)
catalyst-comms join <channel> --as <your-name> \
--capabilities "what you own (file paths, domains)" \
--parent "name of the agent that spawned you" \
--orch "<orchestrator-id>" \
--ttl 600
--ttl is how long (seconds) before a human treats your last activity as stale.
Long-running agents should re-join periodically (same --as) to bump lastSeen.
If the channel doesn't exist yet, join creates it. Participants are upserted by
--as, so re-joins are idempotent.
Message Types
All send calls carry a --type (default info):
| Type | When to use | Expected response |
|---|---|---|
proposal | "I plan to do X" | ack or counter |
question | "Does my filter conflict with yours?" | answer |
answer | Reply to a question (always set --re <msg-id>) | — |
ack | Reply to a proposal saying "go ahead" (set --re <msg-id>) | — |
info | FYI, no response needed | — |
attention | "I'm blocked, a human or coordinator must intervene" | — |
done | "My portion is complete" (automatically sent by done cmd) | — |
Example:
catalyst-comms send pr-114 "I'm rebasing first, please hold migrations" \
--as backend-worker --type proposal
# Later, reply:
MSG_ID=$(catalyst-comms send pr-114 "ack, holding" \
--as frontend-worker --type ack --re msg-abc123)
Posting Discipline
The message-type table above defines what each type means. This section defines when
a worker should choose each type. Workers that emit attention as a heartbeat make the
orchestrator's NEEDS ATTENTION banner useless and foreclose any real-time interrupt
pattern (e.g., the Claude Code Monitor tool). Follow these rules.
1. Message-Type Semantics (when to choose which)
info— the default. Cheap, append-only, never interrupts anyone. Use for phase transitions, PR-opened, "still working", and any FYI a human auditor or the orchestrator might read but is not required to act on.attention— reserved for orchestrator action. The orchestrator promotes everyattentionto a state-level NEEDS ATTENTION item. If you would not interrupt a human for it, do not post it. Default toinfoand ask: "is the orchestrator blocked from making forward progress unless it sees this now?" If no, it isinfo.done— sent only via thedonesubcommand at terminal success. One per worker per session. Never usesend --type donemanually; let the subcommand do it so quorum is auto-checked.proposal/question/answer/ack— peer-to-peer coordination only. Use when you need a sibling worker to confirm before you proceed (e.g., overlapping file scope). The recipient is expected to reply within minutes; if no reply, treat asackand proceed.
2. Volume Budgets
Per worker per session:
| Type | Budget |
|---|---|
info | At phase boundaries + PR-opened only. ~5–7 in the normal path. |
attention | 0–2 per worker. More than 2 means you are using it as info. |
done | Exactly 1, on terminal success. |
proposal / question / answer / ack | As needed for active coordination. |
info posts in the middle of a phase ("running tests…", "still here…") are noise. Phase
transitions are the heartbeat — skip per-step status updates.
attention above 2 is a signal that either (a) the worker is mis-categorising routine
events, or (b) something is genuinely wrong and the worker should stop and write a
clear final attention instead of spamming partial status.
3. Mandatory Escalation (when you MUST post attention)
These are not discretionary. The worker MUST post exactly one attention message —
clear, single-shot, with a body the orchestrator can act on — when any of these occur:
- Scope conflict — your dispatch brief tells you to touch files another worker also owns, or your work has a hard dependency on a sibling worker's output that has not arrived. Body: name the conflicting file/sibling.
- Missing access — required CLI / credential / API not available, and you cannot proceed without it. Body: name the missing thing.
- Ambiguous spec — the ticket / dispatch brief contradicts itself or omits a fact you must have to make a correct choice. Body: state the ambiguity and the two candidate interpretations.
- Repeated test/CI failures — same failure mode 3+ times after distinct fix attempts. Body: failure signature + what you tried.
- Stalled merge — you wrote
status="stalled"for any reason (merge conflict you cannot resolve, required reviewer you cannot satisfy, branch protection rule you cannot meet). Body: which blocker, which PR.
Do NOT wait for human input before escalating. Post the attention, then either
continue working on what you can still do, or exit if the blocker is total.
4. Severity Framing (blocking vs nonblocking)
Catalyst uses a binary severity system mapped onto the existing types:
- blocking →
attention(orchestrator must act before forward progress is possible) - nonblocking →
info(informational; orchestrator may act eventually)
When in doubt, prefix the body to make severity unambiguous to a human reading the channel:
# blocking — pairs with --type attention
catalyst-comms send "$CH" "[blocking] missing GH_TOKEN, cannot create PR" \
--as worker-3 --type attention
# nonblocking — pairs with --type info
catalyst-comms send "$CH" "[nonblocking] codex flagged 1 minor style issue, fixing inline" \
--as worker-3 --type info
Workers MAY adopt P1/P2/P3 in the body ([P1], [P2], [P3]) for finer grain — but
only the binary distinction is enforced by the orchestrator. P1/P2/P3 is a body
convention, not a s