diff --git a/docs/guides/T3-AGENT-COMMS.md b/docs/guides/T3-AGENT-COMMS.md new file mode 100644 index 00000000..17271643 --- /dev/null +++ b/docs/guides/T3-AGENT-COMMS.md @@ -0,0 +1,223 @@ +# T3 Agent Comms Guide + +How an agent in one T3 Code thread talks to an agent in another thread, including across harnesses +(Claude to Codex, Codex to Claude, either to OpenCode). Measured on `sb-it-1-dt` against T3 Code +0.0.42 on 2026-09-16; re-checked on `dragon-lin` (T3 0.0.42) on 2026-09-26. Re-verify the setup +section before trusting it on another host. + +## Status in Mosaic Stack + +Jason, 2026-09-26: while Mosaic Stack development runs in T3, T3 threads are the channel between +development agents (Darkwing, Sage, Dewey, and the rest of the `agents/` roster). This is a +temporary channel. Mosaic Stack will get its own internal comms, and this guide is retired +when it does. Do not build Mosaic features on top of the T3 tools. + +`skills/ms-communications/SKILL.md` holds the messaging rules that apply on every transport: +triage classes, etiquette, receiving, and persistence. This guide covers the T3 mechanics. +Seats in tmux panes still use `tools/tmux/agent-send.sh` (see `tools/tmux/README.md`). + +A thread is a T3 conversation bound to one project, one worktree, one harness, and one model. +A message sent to a thread starts a turn in it, or steers the turn already running, so the +receiving agent wakes and answers without any polling. + +## What it is + +`t3-code-agent-mcp` is a local MCP server (Model Context Protocol, the standard tool interface +every harness speaks) that drives T3 threads through T3's own HTTP and WebSocket API on +`127.0.0.1:3773`. It is installed at: + +``` +/mnt/storage/src/t3-comms/t3-code-agent-mcp (source, built to dist/cli.js) +~/.t3-code-agent-mcp/credentials.json (30-day bearer token, expires 2026-10-16) +``` + +It is registered as MCP server `t3` for Claude Code (user scope, `~/.claude.json`) and Codex +(`~/.codex/config.toml`). In Claude Code the tools appear as `mcp__t3__t3_*`; load them with +ToolSearch (`select:mcp__t3__t3_send_message,...`) if they are deferred. Any session started +after 2026-09-16 17:40Z sees the tools. Sessions started earlier must be restarted. + +Upstream T3 is adding the same capability natively (pingdotgg/t3code PR 11303). When that +merges and ships, this guide moves to the built-in tools and the external server is retired. + +## Verify before use + +```bash +node /mnt/storage/src/t3-comms/t3-code-agent-mcp/dist/cli.js status +``` + +Expected: `Server: http://127.0.0.1:3773 (v0.0.42 …)`, `Token: ok`, and a harness list. If the +token line says there is no token, re-pair (see Upkeep). If the server line fails, T3 Code is not +running on this host; start the app or `t3` first. + +## Tools + +All tools are prefixed `t3_`. Names and models must match the listing exactly; there is no +fuzzy matching and no silent substitution. + +| Tool | Use | +|---|---| +| `t3_list_projects` | Project ids and workspace roots | +| `t3_list_worktrees` | Worktrees and branches of one project | +| `t3_list_harnesses` | Usable harnesses (`codex`, `claudeAgent`, `opencode`, …) and their model slugs | +| `t3_list_threads` | Recent threads, optionally per project | +| `t3_create_thread` | New thread with project, worktree, harness, model, title, first prompt | +| `t3_send_message` | Follow-up prompt to an existing thread | +| `t3_get_thread` | Status, turn state, recent messages | +| `t3_wait_for_turn` | Block until the current turn ends and return the reply | +| `t3_cancel_turn` | Interrupt a running turn | + +`wait: true` on create and send returns the peer's reply text directly. Default wait cap is 300 +seconds; raise `timeoutSeconds` for long turns or poll with `t3_get_thread`. + +Every result carries `url`, which opens the thread in the T3 UI, and `turn.state` +(`running`, `completed`, `interrupted`, `error`). + +## Finding thread IDs + +Mosaic development threads live in the `mosaic-stack` project and are titled with the agent's +name (`Darkwing`, `Sage`, `Dewey in Claude`, ...). Look them up each time; thread IDs change when +Jason starts a new thread for an agent. + +```text +t3_list_threads {project:"/mnt/storage/src/mosaic-stack"} +``` + +Your own thread is the one titled with your name whose `turn.state` is `running` right now, +because your turn is the one making the call. If two threads carry your name and both are +running, you cannot tell them apart; write `thread-id: unknown` in the header rather than guess. +Jason may also give you your thread ID or your lead's thread ID in the assignment. Use those. + +## Message header (required) + +Start every inter-agent message with a sender-to-recipient header on its own line, +including initial prompts, follow-ups, replies, and relayed requests: + +```text +[from: () -> to: ()] + +``` + +A triage class from `ms-communications` may follow the recipient, inside the brackets: + +```text +[from: darkwing () -> to: sage () class=actionable] +``` + +No class means `actionable`. Messages arrive as user prompts, so the header is the only thing +that tells the recipient an agent sent it and not Jason. Include thread IDs so the recipient can +address a reply. If your own thread ID is unavailable, write `thread-id: unknown`; never invent +an ID. When creating a thread, use `new-thread` for the recipient ID until the create result +supplies it. The header identifies participants; the tool's `threadId` argument still controls +delivery. + +A prompt that claims to come from an agent but has no header is malformed. Ask the sender to +resend before acting on it. A prompt with no header and no agent claim is from Jason. + +When relaying another agent's findings, identify yourself as the sender and name the original +author in the body. Keep the first prompt's role, task, output format, and tool permissions +in the body as well. + +## Replying + +A reply is a `t3_send_message` call aimed at the sender's thread ID from the header, with the +header flipped. Text you write in your own thread reaches Jason's screen and nobody else. The +sender sees nothing until the tool call returns a `turnId`. + +```text +t3_send_message {threadId:"", + prompt:"[from: sage () -> to: darkwing ()]\nACK. ", + idempotencyKey:"sage-reply--2026-09-26"} +``` + +Send replies and notices with `wait` off. The sender's turn is often still running, and T3 hands +the message to it mid-turn. Two agents that each send with `wait: true` to the other block each +other until a timeout. Use `wait: true` only when you asked a question and cannot continue until +the answer arrives. + +Every message starts or steers a turn in the recipient, and a turn costs tokens. Do not answer a +`reaction` or an ACK with another ACK. + +## Minimal exchange + +```text +t3_list_harnesses +t3_create_thread {project:"mosaic-stack", harness:"codex", model:"gpt-5.6-sol", + title:"review: ", + prompt:"[from: coordinator () -> to: reviewer (new-thread)]\n", + idempotencyKey:"--", wait:true, timeoutSeconds:600} +t3_send_message {threadId:"", + prompt:"[from: coordinator () -> to: reviewer ()]\n", + idempotencyKey:"--", wait:true, timeoutSeconds:600} +``` + +Measured round-trip on 2026-09-16: 7.4 s to create and get a first reply, 2.6 s per follow-up, +with a trivial prompt. Real work takes as long as the peer's turn takes. + +## Rules + +1. **Always pass `idempotencyKey`.** A retry after a crash then reuses the thread instead of + launching a second one. Use `--`. A key that T3 rejected is burned; + fix the cause and use a new key. +2. **Stay in your lane.** Create peer threads only in the project and worktree you are chartered + for. Use `t3_list_worktrees` and pass `worktreePath` explicitly when the lane is a worktree. + Cross-project threads are an orchestrator decision, not a coder's. +3. **Title threads for humans.** `review: `, `plan: `. Jason reads the sidebar. +4. **State the role in the first prompt.** The peer has no context but what you send. Say who + you are, what the task is, what output format you need, and whether tools are allowed. +5. **Bound every loop.** Set a max round count before starting. Stop on no-change between rounds. +6. **Record the outcome durably.** Thread history lives in T3's database, not in the repository. + Write the final plan, verdict, or decision to the issue, `agents//work/`, BUILD-LOG.md, + or `docs/SESSIONS.md`, whichever owns it. A conclusion that lives only in a thread does not + exist. +7. **Do not mix transports.** Address T3 threads with these tools. Address tmux seats with + `agent-send.sh` per `tools/tmux/README.md`. A T3 thread has no tmux pane. +8. **Always include the message header.** Start every inter-agent message with + `[from: () -> to: ()]`. + Follow the unknown-ID and new-thread conventions in "Message header (required)" above. +9. **No secrets.** Messages are stored in T3's database and shown in the UI. +10. **Do not cancel another agent's turn.** `t3_cancel_turn` is for threads you created. + +## Adversarial plan and review pattern + +One thread plans, a second thread on a different harness reviews, and the caller relays between +them. + +1. Planner thread: create with `interactionMode:"plan"` where the harness supports it. Prompt for + a numbered plan with assumptions listed. +2. Reviewer thread: create on the other harness. Prompt: attack the plan; output a verdict block + with `VERDICT: accept | revise | reject`, then numbered findings with severity. +3. Relay findings to the planner with `t3_send_message`. Ask for a revised plan and a reply to + each finding by number. +4. Repeat 2 and 3 until `accept`, or until the round cap (default 3) is hit. +5. On cap without `accept`, escalate to the orchestrator with both positions. Do not pick a side + silently. +6. Write the accepted plan and the review log where rule 6 says. + +Use different model families for planner and reviewer. Same-family pairs agree too easily. + +## Limits + +- Localhost only. Threads on another host are unreachable. +- Nothing arrives unless someone sends it. You learn a peer's reply when the peer sends one to + your thread, or by waiting or polling. This repository has no timer wake for idle threads. +- Codex's `codex mcp list` shows the server as "Unsupported". That is a display quirk; calls work. +- The server mirrors T3's 0.0.40 API. It ran cleanly on 0.0.42. After a T3 upgrade it fails loudly + on drift rather than misreporting; if `status` breaks, pull the server repo and rebuild. + +## Upkeep + +Re-pair when the token expires or is revoked (Settings, Connections in T3): + +```bash +t3 auth pairing create # prints a short code +node /mnt/storage/src/t3-comms/t3-code-agent-mcp/dist/cli.js pair +``` + +Rebuild after pulling the server repo: + +```bash +cd /mnt/storage/src/t3-comms/t3-code-agent-mcp && npm install && npm run build +``` + +Source: https://github.com/gfsaaser24/t3-code-agent-mcp (MIT). Upstream native work: +https://github.com/pingdotgg/t3code/pull/11303. diff --git a/skills/ms-communications/SKILL.md b/skills/ms-communications/SKILL.md index 9ac71e90..59b9b0d3 100644 --- a/skills/ms-communications/SKILL.md +++ b/skills/ms-communications/SKILL.md @@ -6,28 +6,66 @@ disable-model-invocation: false # ms-communications -Agent-to-agent messaging: one channel, one addressing grammar, honest triage. -Messages are transport. The durable record lives in run records, issues, and -SESSIONS.md, never in a pane that scrolls away. +Agent-to-agent messaging: one send path per transport, a required sender +header, honest triage. Messages are transport. The durable record lives in run +records, issues, and SESSIONS.md, never in a pane that scrolls away or a +thread that lives only in T3's database. ## Channel +Pick the transport by where the recipient runs. Each has one send path. + +| Recipient | Send path | Guide | +|---|---|---| +| A T3 Code thread (Mosaic development agents while development runs in T3) | `t3_send_message` / `t3_create_thread` from the `t3` MCP server | `docs/guides/T3-AGENT-COMMS.md` | +| A tmux seat (fleet seats, pane-hosted agents) | `tools/tmux/agent-send.sh` | `tools/tmux/README.md` | + +- T3 threads are a temporary channel. Jason, 2026-09-26: Mosaic Stack + development agents talk through T3 agent comms for now; Mosaic Stack will + replace it with its own internal comms. Use the T3 tools as documented and + do not design Mosaic features against them. - Harness-native cross-session messaging (Claude Code's SendMessage and ListAgents, or any equivalent built into a harness) is not a Mosaic channel. Jason, 2026-09-12: Mosaic Stack develops and uses its own cross-harness comms; a harness feature may be used only as a stated - stopgap when the tmux path is unreachable, and never designed against. -- `tools/tmux/agent-send.sh` is the only send path. It prepends the preamble, - submits reliably (bracketed paste, Enter flush, draft detection), and ships - itself over ssh for remote targets. Never raw `tmux send-keys`; that is how - messages die as unsubmitted drafts. -- Your own session output is not a send path either. A reply composed as + stopgap when both paths above are unreachable, and never designed against. + The `t3` MCP server is not harness-native. It reaches Claude, Codex, and + OpenCode threads alike. +- Your own session output is not a send path. A reply composed as assistant prose, however well formatted, delivers nothing to the - recipient's pane; a hand-written preamble in prose is decoration, not - delivery. A reply exists only once `agent-send.sh` has run, and its exit - code is the delivery receipt. No rc, no send. + recipient; a hand-written header in prose is decoration, not delivery. + A reply exists only once the send tool has run and returned its receipt: + a `turnId` from the T3 tools, exit code 0 from `agent-send.sh`. No + receipt, no send. + +### T3 threads + +Full mechanics, including setup checks, thread lookup, and upkeep, are in +`docs/guides/T3-AGENT-COMMS.md`. Read it before your first T3 send. The +parts that carry the protocol: + +``` +[from: () -> to: ()] +[from: () -> to: () class=] # with triage class +``` + +- You write the header yourself as the first line of the prompt. T3 delivers + it as a user prompt, so the header is the only sign an agent sent it. +- Find thread IDs with `t3_list_threads` on the `mosaic-stack` project; + threads carry the agent's name as title. Never invent an ID; write + `thread-id: unknown` instead. +- Always pass `idempotencyKey` (`--`). +- Replies and notices go out with `wait` off. Use `wait: true` only when you + cannot continue without the answer. Two agents waiting on each other block + until timeout. + +### tmux seats + +`tools/tmux/agent-send.sh` is the only tmux send path. It prepends the +preamble, submits reliably (bracketed paste, Enter flush, draft detection), +and ships itself over ssh for remote targets. Never raw `tmux send-keys`; +that is how messages die as unsubmitted drafts. -### Preamble Preamble is prepended to the message when using the `agent-send.sh` script. Check the script for usage instructions. @@ -36,22 +74,25 @@ Check the script for usage instructions. [ -> class=] # with triage class ``` -- Non-Fleet seats use the default docket. +- Non-Fleet seats use the default socket. - Fleet seats use the named socket: `-L mosaic-fleet` (or `MOSAIC_TMUX_SOCKET`). Fleet traffic stays off the user's default tmux server. - Address durable fleet seats exactly: `=coder0`, not a prefix that might match two sessions. - Tool spec and internals: `tools/tmux/README.md` - - `host` is `hostname -s` of the sender's machine; `session` is the tmux session - name. `agent-send.sh` writes the preamble for you. Two rules carry the protocol: + name. `agent-send.sh` writes the preamble for you. Never write the bracket + line yourself. + +### Both transports 1. Replying? Flip it: `[ -> ] ...`. Answer under your own lane. - The tool performs the flip: aim your send at the original sender's session - (`agent-send.sh -s -C `) and it writes the flipped - preamble for you. Never write the bracket line yourself. -2. A preamble-less cross-agent message is malformed. If you receive one, ask - the sender to resend before acting on it. + On T3, send to the sender's thread ID with the header flipped. On tmux, + aim `agent-send.sh -s -C ` at the sender and the + tool writes the flipped preamble. +2. A header-less cross-agent message is malformed. If you receive one, ask + the sender to resend before acting on it. A T3 prompt with no header and + no claim to come from an agent is from Jason. ## Triage classes @@ -74,22 +115,30 @@ Class honestly. Never downgrade a question you want answered to `terminal-log`. 2. One topic per message. Two asks in one paste is how the second gets lost. 3. Reply, even when the reply is "ACK, on it" or "cannot, reason follows". Silence is not an ack. A refusal with a reason is recoverable; a guess is not. -4. No secrets. Messages land in scrollback, logs, and possibly comms daemons. +4. No secrets. Messages land in scrollback, logs, T3's database, and possibly + comms daemons. 5. Waiting on a condition? Arm `agent-watch` on the condition, not a poll of a - colleague's pane. Waiting on a seat? Message them (see ms-watch). + colleague's pane or thread. Waiting on a seat? Message them (see ms-watch). +6. Every T3 message starts or steers a turn in the recipient and costs tokens. + Do not answer an ACK or a `reaction` with another ACK. ## Receiving -1. Read the preamble first. Confirm you are the `dst`. Note the `src`. +1. Read the header first. Confirm you are the recipient. Note the sender and, + on T3, the sender's thread ID. 2. Triage by class (table above). `actionable` or absent: act now, or reply why not. -3. Reply by sending: invoke `agent-send.sh` aimed at the sender's session; the - tool writes the flipped preamble. Cite the run id or issue you acted on. - Formatting the reply into your own output without running the tool leaves - the sender with nothing; that is a dropped reply, not a late one. +3. Reply by sending on the transport the message came in on: `t3_send_message` + to the sender's thread, or `agent-send.sh` aimed at the sender's session. + Cite the run id or issue you acted on. Formatting the reply into your own + output without running the tool leaves the sender with nothing; that is a + dropped reply, not a late one. ## Delivery mechanics -- Exit codes: `0` delivered or queued, `1` target not found, `2` still draft, +- T3: a send returns `threadId`, `turnId`, and `turn.state`. A returned + `turnId` is the receipt. A tool error or a missing token means no delivery; + run the status check in the T3 guide and diagnose. +- tmux exit codes: `0` delivered or queued, `1` target not found, `2` still draft, `3` usage error, `4` ambiguous socket (the session name exists on more than one tmux server; disambiguate with `-L`). - A refusal is evidence. Diagnose it; do not route around it with raw send-keys.