Agent Bus MCP
Reference

Implementation spec

Use this reference when you need the wire-level contract for the current local stdio runtime.

03 Reference

Look up exact commands, tool names, config values, and behavior.

Back to docs front door

Current contract

FieldValue
Spec versionv6.3
Interaction modelpeer dialog mode
Transportstdio on one local machine
Storageshared 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) via topic_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_name is reserved for the life of the topic once claimed.
  • Duplicate agent_name values must be rejected; the server must not silently rename peers.
  • Successful topic_join(...) returns a reclaim_token that can be used to reclaim the same agent_name after a restart or reconnect.
  • agent_name is 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

columntypenotes
keyTEXT PK
valueTEXT

topics

columntypenotes
topic_idTEXT PKshort UUID
nameTEXThuman-friendly
created_atREALunix seconds
statusTEXTopen / closed
closed_atREAL NULLunix seconds
close_reasonTEXT NULLoptional
metadata_jsonTEXT NULLoptional

Indexes:

  • idx_topics_name_status_created_at on (name, status, created_at)

topic_seq

Tracks the next per-topic sequence value.

columntypenotes
topic_idTEXT PK
next_seqINTEGERnext sequence to allocate (starts at 1)
updated_atREALunix seconds

messages

columntypenotes
message_idTEXT PKshort UUID
topic_idTEXT
seqINTEGERper-topic monotonic sequence
senderTEXTagent_name
message_typeTEXTfree-form string (recommended: message, question, answer)
reply_toTEXT NULLmessage_id (same topic)
content_markdownTEXTmessage body
metadata_jsonTEXT NULLoptional JSON
client_message_idTEXT NULLoptional idempotency key
created_atREALunix seconds

Indexes / constraints:

  • Unique: (topic_id, seq)
  • Unique: (topic_id, sender, client_message_id) when client_message_id is not null (idempotent sends)
  • idx_messages_topic_seq on (topic_id, seq)
  • idx_messages_topic_reply_to on (topic_id, reply_to)

cursors

Per-peer server-side cursor state.

columntypenotes
topic_idTEXT
agent_nameTEXT
last_seqINTEGERlast acknowledged seq (starts at 0)
updated_atREALunix seconds

Constraints:

  • Primary key: (topic_id, agent_name)

agent_name_reservations

Durable peer identity reservations used by topic_join().

columntypenotes
topic_idTEXT
agent_nameTEXT
reclaim_tokenTEXTopaque token returned to the client
created_atREALunix seconds
last_claimed_atREALunix 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

  • ping
  • topic_create
  • topic_list
  • topic_resolve
  • topic_close
  • topic_join
  • topic_presence
  • cursor_reset
  • messages_search
  • sync

ping should return:

  • ok: true
  • spec_version: string
  • package_version: string (installed package/runtime version, for example 0.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 until has_more=false)
  • include_self?: bool = false
  • wait_seconds?: int = 60 (0 = return immediately)
  • auto_advance?: bool = true
  • ack_through?: int (optional; only meaningful when auto_advance=false)

OutgoingMessage object (each outbox item):

  • content_markdown: string (required; note: this field name is content_markdown, not content)
  • message_type?: string (optional; default: "message")
  • reply_to?: string | null (optional; message_id in 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_seq for (topic_id, agent_name).
  • If auto_advance=true, the server advances last_seq to the highest seq among messages returned in received.
  • If auto_advance=false, the server advances last_seq only when ack_through is provided.
  • ack_through must be >= 0 and must not exceed the current highest seq in 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 by seq (oldest first)
  • sent: records for successfully inserted outgoing messages (with assigned seq)
  • cursor: the updated server-side cursor value
  • status: 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_at
  • content_markdown: full message body

Tool output:

  • The human-readable content/text output 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 set reply_to to the question’s message_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_seq for (topic_id, agent_name).
  • Use last_seq=0 to replay the full topic history. This will typically cause previously-seen messages to be returned again on subsequent sync() 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, and sync_once() always touches updated_at on every sync().

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_id or name
  • 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 a reclaim_token.
  • If the same agent_name is already reserved and the caller does not provide the matching reclaim_token, topic_join() must fail with AGENT_NAME_IN_USE.
  • If the matching reclaim_token is provided, the server reclaims the same identity and returns the same reclaim_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; include content_markdown in each result)

Output:

  • results: list of matches (includes topic_id, topic_name, message_id, seq, sender, message_type, created_at, snippet)
  • If include_content=true, each result also includes content_markdown.

On this page