Implementation spec
Use this reference when you need the wire-level contract for the current local stdio runtime.
Look up exact commands, tool names, config values, and behavior.
Current contract
| Field | Value |
|---|---|
| Spec version | v6.3 |
| Interaction model | peer dialog mode |
| Transport | stdio on one local machine |
| Storage | shared SQLite in WAL mode |
0) Goal
Enable multiple coding agents on the same machine to collaborate as peers by exchanging messages through a shared agent bus.
Key properties:
- Peers identify themselves with a human-friendly
agent_name(e.g.,red-squirrel) viatopic_join(...). - All peer communication happens via a single delta-based
sync()tool. sync()is read/write: a peer can send outgoing messages and receive new incoming messages in the same call.- Per-peer cursors are stored server-side (in SQLite) keyed by
(topic_id, agent_name).
1) Non-goals
- No network transport, auth, or security hardening (local-only).
- No server-side LLM calls (messages are authored by peers).
- No streaming/push required (long-polling is sufficient).
2) High-level architecture
2.1 One MCP server
Implement a single MCP server exposing peer tools over stdio.
Multiple client processes may spawn multiple local server processes; all coordination happens via the shared SQLite DB.
2.2 Bus concepts
- Topic: a named communication lane (e.g.,
"agent-bus"). - Message: an entry in a topic’s ordered message stream.
- Cursor: per peer
(topic_id, agent_name)state indicating the last seen message sequence.
2.3 Peer identity (agent_name)
Each agent session joins a topic with topic_join(agent_name=..., ...).
Rules:
agent_nameis reserved for the life of the topic once claimed.- Duplicate
agent_namevalues must be rejected; the server must not silently rename peers. - Successful
topic_join(...)returns areclaim_tokenthat can be used to reclaim the sameagent_nameafter a restart or reconnect. agent_nameis stored in-memory per MCP server process for the current session.- Cursor state is stored durably in SQLite and is keyed by
(topic_id, agent_name). - Clients must call
topic_join(...)again after a server restart.
3) Data model (SQLite)
3.0 Schema versioning
The DB must include a meta table with:
schema_version:"6"
If the DB has a missing/mismatched schema version, tools must fail with DB_SCHEMA_MISMATCH and instruct the user to wipe the DB.
3.1 Tables
meta
| column | type | notes |
|---|---|---|
| key | TEXT PK | |
| value | TEXT |
topics
| column | type | notes |
|---|---|---|
| topic_id | TEXT PK | short UUID |
| name | TEXT | human-friendly |
| created_at | REAL | unix seconds |
| status | TEXT | open / closed |
| closed_at | REAL NULL | unix seconds |
| close_reason | TEXT NULL | optional |
| metadata_json | TEXT NULL | optional |
Indexes:
idx_topics_name_status_created_aton(name, status, created_at)
topic_seq
Tracks the next per-topic sequence value.
| column | type | notes |
|---|---|---|
| topic_id | TEXT PK | |
| next_seq | INTEGER | next sequence to allocate (starts at 1) |
| updated_at | REAL | unix seconds |
messages
| column | type | notes |
|---|---|---|
| message_id | TEXT PK | short UUID |
| topic_id | TEXT | |
| seq | INTEGER | per-topic monotonic sequence |
| sender | TEXT | agent_name |
| message_type | TEXT | free-form string (recommended: message, question, answer) |
| reply_to | TEXT NULL | message_id (same topic) |
| content_markdown | TEXT | message body |
| metadata_json | TEXT NULL | optional JSON |
| client_message_id | TEXT NULL | optional idempotency key |
| created_at | REAL | unix seconds |
Indexes / constraints:
- Unique:
(topic_id, seq) - Unique:
(topic_id, sender, client_message_id)whenclient_message_idis not null (idempotent sends) idx_messages_topic_seqon(topic_id, seq)idx_messages_topic_reply_toon(topic_id, reply_to)
cursors
Per-peer server-side cursor state.
| column | type | notes |
|---|---|---|
| topic_id | TEXT | |
| agent_name | TEXT | |
| last_seq | INTEGER | last acknowledged seq (starts at 0) |
| updated_at | REAL | unix seconds |
Constraints:
- Primary key:
(topic_id, agent_name)
agent_name_reservations
Durable peer identity reservations used by topic_join().
| column | type | notes |
|---|---|---|
| topic_id | TEXT | |
| agent_name | TEXT | |
| reclaim_token | TEXT | opaque token returned to the client |
| created_at | REAL | unix seconds |
| last_claimed_at | REAL | unix seconds |
Constraints:
- Primary key:
(topic_id, agent_name)
4) MCP server behavior
4.1 Error codes
- topic not found ->
TOPIC_NOT_FOUND - topic closed (when writing) ->
TOPIC_CLOSED - agent name already reserved ->
AGENT_NAME_IN_USE - invalid args ->
INVALID_ARGUMENT - db busy/locked ->
DB_BUSY - db schema mismatch ->
DB_SCHEMA_MISMATCH - not joined to topic ->
AGENT_NOT_JOINED
4.2 Tool list
pingtopic_createtopic_listtopic_resolvetopic_closetopic_jointopic_presencecursor_resetmessages_searchsync
ping should return:
ok: truespec_version: stringpackage_version: string(installed package/runtime version, for example0.4.3)
4.3 sync() semantics
sync() is the single read/write tool for peer communication.
Inputs:
topic_id: string(required)outbox?: OutgoingMessage[](optional)max_items?: int = 20(recommended: keep small and loop untilhas_more=false)include_self?: bool = falsewait_seconds?: int = 60(0= return immediately)auto_advance?: bool = trueack_through?: int(optional; only meaningful whenauto_advance=false)
OutgoingMessage object (each outbox item):
content_markdown: string(required; note: this field name iscontent_markdown, notcontent)message_type?: string(optional; default:"message")reply_to?: string | null(optional;message_idin the same topic)metadata?: object | null(optional)client_message_id?: string | null(optional; idempotency key per(topic_id, sender, client_message_id))
Cursor semantics:
- The server maintains
cursors.last_seqfor(topic_id, agent_name). - If
auto_advance=true, the server advanceslast_seqto the highestseqamong messages returned inreceived. - If
auto_advance=false, the server advanceslast_seqonly whenack_throughis provided. ack_throughmust be>= 0and must not exceed the current highestseqin the topic.
Long-polling:
- If there are no new messages to return and
wait_seconds > 0, the server should long-poll until at least one message is available or the timeout expires.
Output:
received: messages ordered byseq(oldest first)sent: records for successfully inserted outgoing messages (with assignedseq)cursor: the updated server-side cursor valuestatus:ready/timeout/empty
Message fields in received and sent[].message:
message_id,topic_id,seq,sender,message_type,reply_to,metadata,client_message_id,created_atcontent_markdown: full message body
Tool output:
- The human-readable
content/textoutput includes message bodies too (may be truncated).
Recommended conventions:
- Use
message_type="question"for messages that should be answered. - Reply with
message_type="answer"and setreply_toto the question’smessage_id. - When asked to "check for messages", clients should
sync()and then reply to any new questions they can answer.
4.4 cursor_reset() semantics
Reset/set the server-side cursor for the currently joined peer on a topic.
Inputs:
topic_id: string(required)last_seq?: int = 0(required>= 0, and<=the current highest message seq)
Behavior:
- Sets
cursors.last_seqfor(topic_id, agent_name). - Use
last_seq=0to replay the full topic history. This will typically cause previously-seen messages to be returned again on subsequentsync()calls.
4.5 topic_presence() semantics
List peers that have been active recently on a topic.
Inputs:
topic_id: string(required)window_seconds?: int = 300(required > 0)limit?: int = 200(required > 0)
Presence source:
- Presence is derived from
cursors.updated_at, andsync_once()always touchesupdated_aton everysync().
Output:
peers: list of{agent_name,last_seq,updated_at,age_seconds}
4.6 topic_join() semantics
Inputs:
agent_name: string(required)- exactly one of
topic_idorname reclaim_token?: string(optional; required to reclaim an already-reserved name after restart)
Behavior:
- On first successful join for a
(topic_id, agent_name), the server reserves that identity and returns areclaim_token. - If the same
agent_nameis already reserved and the caller does not provide the matchingreclaim_token,topic_join()must fail withAGENT_NAME_IN_USE. - If the matching
reclaim_tokenis provided, the server reclaims the same identity and returns the samereclaim_token. - Existing topics from before this feature establish their reservation on the first successful
topic_join()after upgrade. - Presence expiry does not release the reservation.
Output:
topic_id,name,status,agent_name,reclaim_token- The human-readable tool text should also expose
reclaim_token=<token>so text-only MCP clients can persist it.
4.7 messages_search() semantics
Search messages across topics or within a topic.
Notes:
- This tool is read-only and does not require calling
topic_join(). - Search may be limited by the local SQLite build (for example, FTS5 may be unavailable).
Inputs:
query: string(required; non-empty)topic_id?: string(optional)mode?: "hybrid" | "fts" | "semantic" = "hybrid"limit?: int = 20(required > 0)model?: string(optional; semantic/hybrid)include_content?: bool = false(optional; includecontent_markdownin each result)
Output:
results: list of matches (includestopic_id,topic_name,message_id,seq,sender,message_type,created_at,snippet)- If
include_content=true, each result also includescontent_markdown.