docs(comms): T3 threads as the temporary development channel beside tmux

Jason, 2026-09-26: while Mosaic Stack development runs in T3, development
agents message each other through T3 threads with the t3 MCP tools, until
Mosaic Stack has its own internal comms. ms-communications now picks the
transport by where the recipient runs, carries the T3 header and reply
rules, and counts a returned turnId as the delivery receipt.
docs/guides/T3-AGENT-COMMS.md is adapted to this repository: status,
thread lookup by agent-name title, replies with wait off, and record
locations; references to guides absent here are replaced.

Suites green before commit: config 24, task 90, foundation 43,
conductor 17, release 14, auth 15, discord 63. unslop-check clean on
both files. Reviewed by Sage (T3 thread 1ef1e4f8).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-26 14:33:24 -05:00
co-authored by Claude Opus 5.5
parent 43d7574d6a
commit 21e3e908b6
2 changed files with 301 additions and 29 deletions
+223
View File
@@ -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: <sender-role> (<sender-thread-id>) -> to: <recipient-role> (<recipient-thread-id>)]
<message body>
```
A triage class from `ms-communications` may follow the recipient, inside the brackets:
```text
[from: darkwing (<id>) -> to: sage (<id>) 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:"<sender-thread-id>",
prompt:"[from: sage (<my-id>) -> to: darkwing (<sender-thread-id>)]\nACK. <answer>",
idempotencyKey:"sage-reply-<topic>-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: <topic>",
prompt:"[from: coordinator (<source-id>) -> to: reviewer (new-thread)]\n<task>",
idempotencyKey:"<lane>-<task>-<date>", wait:true, timeoutSeconds:600}
t3_send_message {threadId:"<id>",
prompt:"[from: coordinator (<source-id>) -> to: reviewer (<id>)]\n<next round>",
idempotencyKey:"<lane>-<next-round>-<date>", 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 `<lane>-<purpose>-<date>`. 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: <topic>`, `plan: <topic>`. 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/<name>/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: <sender-role> (<sender-thread-id>) -> to: <recipient-role> (<recipient-thread-id>)]`.
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 <code>
```
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.