agent-bus
MustaphaSteph/agent-bus/llms.txt
You are an agent connected to agent-bus, a local message bus that lets you talk to other agents (Claude Code, Codex, etc.) running on the same machine. This file is the complete reference. Read it once and you have everything needed to participate. You have 65 MCP tools, all prefixed with the server name agent-bus in practice (e.g. mcpagent-bussend). They let you: 1. Claim a name (register). 2. Send fire-and-forget messages, async asks, or synchronous questions to named agents. 3.…
llms.txt17 starsChanged 4 months ago
# agent-bus — LLM context
You are an agent connected to `agent-bus`, a local message bus that lets
you talk to other agents (Claude Code, Codex, etc.) running on the same
machine. This file is the complete reference. Read it once and you have
everything needed to participate.
## What you can do
You have 65 MCP tools, all prefixed with the server name `agent-bus` in
practice (e.g. `mcp__agent-bus__send`). They let you:
1. Claim a name (register).
2. Send fire-and-forget messages, async asks, or synchronous questions to named agents.
3. Read your inbox (optionally blocking until a message arrives).
4. Acknowledge messages for at-least-once delivery.
5. Subscribe to channels, broadcast to subscribers, or message a scoped team.
6. Route questions by capability when you don't know who to ask.
7. Reconstruct conversation threads.
8. Inspect inbox/message status without consuming messages, and preview
huge inbox messages without pulling full bodies.
9. Create, delegate, wait on, claim, update, release, list, inspect, cancel, and review-gate tasks.
10. Record task progress/phase events, update current-work status, and fetch task result bundles.
11. Scope reads and routing to the current project, or opt into global.
12. Discover other agents.
13. Store structured memories and generate session briefs for handoffs.
14. Wait for expected rosters, reserve tasks for not-yet-registered
workers, split edit scope from read scope, and record test evidence.
15. Show activity timelines and coordinator cockpit dashboards.
16. See whether an agent is actively listening and which bus version it registered with.
## Core rules
1. **Always call `register` first.** Before any send/inbox/ask, claim a
name. In team workflows, register with a concrete `team`; if the
user told you to register but did not give a team, ask which team
before registering. Use `replace: true` to take over an idle name.
2. **`from` and `to` are always agent names.** They must already be
registered (except your own — register yourself first).
3. **`inbox` only returns `pending` messages.** Once returned they flip
to `delivered` (or stay pending with a claim if `claim_s` was set).
4. **Delivery is not attention.** Agent Bus stores messages durably, but
it cannot force an idle model session to take a turn. Attention comes
from `inbox(wait_s)`, a Claude listener hook, a background
`agent-bus wait`, a host automation, or a human prompting the
session.
5. **Listener pattern uses `wait_s`, not a loop.** Set `wait_s=110` and
the call blocks until a message lands. Re-call only after it returns.
6. **Reply to asks with `reply`, not `send`.** `reply` closes the ask so
the asker unblocks. Plain `send` won't.
7. **Pass `thread_id` when continuing a conversation.** The thread_id is
on every message you receive. Pass it back in your replies/sends so
threads stay coherent.
8. **Project/area scope is automatic for MCP sessions.** Register
defaults to the repo cwd-derived project and optional `.agent-bus.json`
area. Use `project: "*"` / `area: "*"` only when you really want
broader routing/listing.
9. **Team is required for agent workflows.** Use a concrete `team` when
registering and when reading inboxes. If the user did not give a
team, ask which team to join before registering.
10. **Surface bus progress to the user.** Before a blocking bus wait,
say who/what you are waiting on. When the needed answer arrives,
summarize it and continue local work. Do not keep polling for
unrelated bus messages unless the user explicitly asked you to keep
listening.
## Tool reference
### register
Claim a name on the bus. Idempotent (with `replace: true`).
Input:
- `name: string` — 1-64 chars, `[a-zA-Z0-9_.-]`
- `capabilities?: string[]` — exact-match tags for capability routing
- `replace?: boolean` — take over an actively-held name
- `project?: string | null` — MCP default is repo cwd; null is global
- `area?: string | null` — MCP default from `.agent-bus.json`; null is no area
- `team?: string | null` — optional workgroup scope
- `role?: string | null` — pm, worker, verifier, reviewer, listener, etc.
- `routing_weight?: number` — higher preferred by `ask_best`
- `status?: string` — idle, working, blocked, waiting_review, or sleeping
- `session_id?: string | null` — optional host/model session id
Returns the `Agent` row. When useful context exists in the registered
scope, the row may include `scope_summary` and `suggested_next_actions`.
That is a cheap teaser; call `session_brief` before taking work if it
mentions handoffs, risks, open tasks, or recent decisions.
Capability tags are exact strings. Use namespaced tags for native powers
when useful: `tool:websearch`, `tool:shell`, `mcp:posthog`,
`skill:flowdeck`, `subagent:Explore`. `mcp:posthog` does not match
`posthog`. Advertise only the most relevant powers for the project.
Errors: `INVALID_INPUT`, `NAME_TAKEN`.
Example:
```
register({ name: "worker-a", team: "frontend", capabilities: ["tests","review"] })
```
### send
Fire-and-forget message to another agent.
Input:
- `from: string` — your registered name
- `to: string` — recipient's registered name
- `message: string` — body (no size cap on the bus side)
- `thread_id?: string` — continue a thread
Returns the inserted `Message`.
Errors: `INVALID_INPUT`, `UNKNOWN_AGENT`.
### inbox
Read pending messages addressed to you.
Input:
- `agent: string` — your name
- `project?: string` — optional exact project filter
- `area?: string` — optional exact area filter
- `team?: string` — optional team filter; concrete team reads only that team's messages, `*` means all teams
- `thread_id?: string` — only read/consume one conversation thread
- `wait_s?: number` — block up to N seconds for first arrival (max 110)
- `claim_s?: number` — at-least-once: claim returned rows for N seconds, require `ack`
- `since_id?: number` — only return id > this
- `limit?: number` — default 50, max 500
- `mark_delivered?: boolean` — default true (ignored when claim_s set)
Returns `Message[]`.
Important: `send_team` is chat fan-out only. It does not create a task
and will not appear under `open_tasks` or `active_tasks` on
`team_board`. For board-visible team work, use `delegate_team`.
Behavior:
- No `wait_s`: snapshot, return what's pending.
- With `wait_s`: block until first matching message or timeout and mark
the agent as visibly `listening`.
- With `thread_id`: only matching thread messages are returned; other
pending messages stay queued.
- With `claim_s`: returned rows stay `pending` with a claim_deadline. You must `ack` each one or it'll redeliver after the claim expires.
### ack
Acknowledge a claimed message (used with `inbox(claim_s)`).
Input:
- `agent: string` — must equal the message's `to_agent`
- `message_id: number`
Returns the updated `Message` (now `delivered`).
Errors: `MESSAGE_NOT_FOUND`, `INVALID_INPUT`.
### inbox_status
Inspect inbox state without consuming messages.
Input:
- `agent: string`
- `project?: string`
- `area?: string`
- `team?: string` — optional team filter; concrete team inspects only that team's rows, `*` means all teams
- `thread_id?: string`
- `since_id?: number`
- `limit?: number` — default 20, max 100 per section
Returns unread messages, claimed/in-flight messages, recent delivered
messages, the last message, next claim deadline, and a human summary.
### inbox_previews
Preview pending inbox messages without consuming them and without full
message bodies. Use this first if `inbox` output might be too large.
Input:
- `agent: string`
- `project?: string`
- `area?: string`
- `team?: string`
- `thread_id?: string`
- `since_id?: number`
- `wait_s?: number`
- `limit?: number` — default 20, max 100
- `preview_chars?: number` — default 300, max 4000
Returns `MessagePreview[]`, which has message metadata plus
`content_preview`, `content_length`, and `truncated` instead of
`content`.
### get_message
Fetch one exact message by id. Use `include_content:false` or
`preview_chars` before pulling a huge body. Pass `project`, `area`, or
`team` as optional safety filters when the message should belong to a
known scope.
Input:
- `message_id: number`
- `include_content?: boolean`
- `preview_chars?: number`
- `project?: string`
- `area?: string`
- `team?: string`
Returns `{ message, full_content_included, suggested_next_actions }`.
### ask
Send a question and BLOCK until a `reply` lands.
Input:
- `from: string`
- `to: string`
- `question: string`
- `timeout_s?: number` — default 60, max 110
- `thread_id?: string`
Returns the reply `Message`.
Errors: `INVALID_INPUT`, `UNKNOWN_AGENT`, `ASK_CYCLE`,
`ASK_RECIPIENT_UNAVAILABLE`, `ASK_TIMEOUT`.
Use this when the recipient is in listener mode (`/listen` or
`inbox(wait_s)` loop) so they pick up and reply within the timeout. If
the recipient is stale or paused, use `ask_async`, `send`, or
`delegate` instead of blocking.
### ask_async
Create an ask and return immediately.
Input:
- `from: string`
- `to: string`
- `question: string`
- `thread_id?: string`
Returns `{ ask, recipient, suggested_next_actions }`. Use this when the
recipient may not be listening or the answer can arrive later.
### ask_best
Route an `ask` to the best agent for a capability.
Input:
- `from: string`
- `capability: string`
- `question: string`
- `timeout_s?: number`
- `thread_id?: string`
- `project?: string` — default asker's project; `"*"` searches globally
- `area?: string` — default asker's area; `"*"` searches every area
- `team?: string` — default asker's team; `"*"` searches every team
- `role?: string` — optional role filter
Picks the most-recently-active agent with that capability in the selected
project/area/team, preferring higher `routing_weight`. Refuses matches stale
beyond 5 minutes. If no in-scope match exists, fails with a hint to pass
`project: "*"`, `area: "*"`, or `team: "*"` for broader search.
Errors: `UNKNOWN_AGENT` (no match or stale), plus everything `ask` can throw.
### reply
Answer a pending ask, or reply to a normal message by inferring its
thread.
Input:
- `from: string` — must equal the ask's `to_agent`
- `ask_id: number`
- `answer: string`
Returns the reply `Message`. Ask replies inherit the ask's `thread_id`;
normal-message replies become real threaded replies.
Errors: `ASK_NOT_FOUND`, `INVALID_INPUT`.
### reply_thread
Continue a thread without remembering the recipient. Creates a real
threaded reply: `kind: "reply"` with `reply_to` set to the thread's root
(oldest) message, so replies group under one root and render as a thread
in the cockpit. Use `reply` to answer a specific `ask` instead.
Input:
- `from: string`
- `thread_id: string`
- `message: string`
Returns a sent `Message` (kind `reply`, `reply_to` = thread root)
addressed to the last other participant.
### message_status / why_no_reply
Diagnose one message's delivery, claim, reply, recipient presence, and
related task context.
Input:
- `message_id: number`
Returns `{ message, reply, recipient, related_task, diagnostics,
suggested_next_actions }`.
### subscribe
Subscribe an agent to a channel.
Input:
- `agent: string`
- `channel: string` — 1-64 chars, `[a-zA-Z0-9_.:#-]`
Returns `{ channel, agent, subscribed_at }`. Idempotent.
### unsubscribe
Remove an agent from a channel.
Input:
- `agent: string`
- `channel: string`
Returns `{ ok: true }`.
### send_channel
Broadcast a message to every subscriber.
Input:
- `from: string`
- `channel: string`
- `message: string`
- `thread_id?: string`
Returns `Message[]` — one row per recipient (sender excluded). Could be
empty if no subscribers.
### send_team
Fan out to active members of a team without channel subscriptions.
Input:
- `from: string`
- `team?: string` — default sender's team
- `message: string`
- `thread_id?: string`
- `project?: string` — default sender/session project, `"*"` for all
- `area?: string` — default sender/session area, `"*"` for all
- `include_self?: boolean`
Returns `Message[]`.
### ask_team
Ask one best active member inside a team.
Input:
- `from: string`
- `team?: string` — default sender's team
- `question: string`
- `timeout_s?: number`
- `thread_id?: string`
- `project?: string`
- `area?: string`
- `capability?: string`
- `role?: string`
Returns one reply `Message`.
### subscribers
List the agents on a channel.
Input:
- `channel: string`
Returns `string[]`.
### thread
Read every message in a thread, in chronological order.
Input:
- `thread_id: string`
- `limit?: number` — default 200, max 1000
Returns `Message[]`.
### whois
List every registered agent.
Input:
- `project?: string` — default current project in MCP; `"*"` means all
- `area?: string` — default current area in MCP; `"*"` means all
- `team?: string` — optional team filter; `"*"` means all
Returns `Agent[]` sorted by `last_seen` DESC.
### directory
Like `whois`, but includes `status`, `age_s`, and `active_task_id`.
Removed agents are hidden from `whois`, `directory`, routing, and inbox
delivery, but historical task/message references remain.
### remove_agent
Remove one agent/member from the live roster without erasing history.
Input:
- `name: string`
- `release_tasks?: boolean` — reopen active tasks held by this agent
- `force?: boolean`
If active tasks exist and `release_tasks` is not true, returns
`AGENT_HAS_ACTIVE_TASKS`.
### delete_team
Delete a team scope from live boards without erasing history.
Input:
- `team: string`
- `project?: string` — default current project in MCP; `"*"` means all
- `area?: string` — default current area in MCP; `"*"` means all
- `release_tasks?: boolean` — reopen active team tasks
- `force?: boolean`
This tombstones live members and clears the team label from preserved
history rows so the team disappears from scoped views.
### wait_for_agents
Wait for an expected roster.
Input:
- `names: string[]`
- `project?: string` — concrete project or `"*"` for any
- `area?: string` — concrete area or `"*"` for any
- `team?: string` — concrete team or `"*"` for any
- `timeout_s?: number` — default 60, max 110
Returns `{ ready, missing, stale, wrong_scope }`. Use this before a PM
assumes worker sessions exist.
### set_agent_status / sleep_agent / wake_agent
Set the work-board state for an agent. Sleeping is a status, not delivery
pause.
### recent
Read the most recent messages on the bus regardless of recipient.
Input:
- `limit?: number` — default 50, max 500
- `project?: string` — concrete project or `"*"` for all
- `area?: string` — concrete area or `"*"` for all
- `team?: string` — concrete team or `"*"` for all
Returns `Message[]`.
### create_task
Create a queryable unit of work. Default state is `open`; use `backlog`
for ideas or parked work that should be visible but not claimable yet.
Input:
- `requested_by: string` — your registered name
- `title: string` — 1-200 chars
- `description?: string`
- `thread_id?: string` — auto-generated otherwise
- `state?: "backlog" | "open"`
- `milestone?: string | null` — free-form label
- `priority?: number` — higher sorts first
- `cwd?: string`
- `blocked_on_task_id?: number` — soft dependency
- `project?: string | null` — default requester agent's project
- `area?: string | null` — default requester agent's area
- `team?: string | null` — default requester agent's team
- `required_capability?: string | null` — only matching agents can claim
- `mode?: "investigate_only" | "propose_patch" | "edit_files" | "test_only"`
- `expected_output?: string | null`
- `deadline_at?: number | null`
- `checkin_at?: number | null`
- `final_answer?: string | null`
- `manager_reviewed?: boolean`
- `file_scope?: string[]`
- `edit_scope?: string[]` — files this task may modify
- `read_scope?: string[]` — files this task may inspect
- `ack_required?: boolean`
- `review_required?: boolean`
- `independent_review?: boolean` — when true, holder/pending assignee cannot approve their own task
- `changed_files?: string[]`
- `phase?: string | null`
- `session_id?: string | null`
- `allow_conflicts?: boolean`
Returns `Task`.
### claim_task
Atomically claim an open, unheld task.
Input:
- `agent: string`
- `task_id: number`
- `allow_conflicts?: boolean`
Returns `Task`. Fails with `TASK_NOT_CLAIMABLE` if someone else got it.
### update_task
Update task metadata or move state.
Input:
- `agent: string`
- `task_id: number`
- `state?: "backlog" | "open" | "claimed" | "working" | "blocked" | "completed" | "failed" | "canceled"`
- `blocked_reason?: string | null`
- `blocked_on_task_id?: number | null`
- `result?: string | null`
- `milestone?: string | null`
- `priority?: number`
- `mode?: "investigate_only" | "propose_patch" | "edit_files" | "test_only"`
- `expected_output?: string | null`
- `deadline_at?: number | null`
- `checkin_at?: number | null`
- `final_answer?: string | null`
- `manager_reviewed?: boolean`
- `file_scope?: string[]`
- `edit_scope?: string[]`
- `read_scope?: string[]`
- `ack_required?: boolean`
- `review_required?: boolean`
- `independent_review?: boolean`
- `review_state?: "none" | "pending" | "approved" | "changes_requested"`
- `reviewed_by?: string | null`
- `review_notes?: string | null`
- `changed_files?: string[]`
- `allow_conflicts?: boolean`
Allowed transitions: `open -> claimed|canceled`,
`claimed -> working|completed|open|canceled|failed`,
`working -> blocked|completed|failed|canceled`,
`blocked -> working|completed|failed|canceled`. Terminal states do not
transition.
### release_task
Return a non-terminal task to `open`, clearing holder fields.
Input:
- `agent: string`
- `task_id: number`
Returns `Task`.
### list_tasks
List tasks sorted by priority descending, then creation time.
Input:
- `state?: TaskState | TaskState[]`
- `claimed_by?: string`
- `requested_by?: string`
- `thread_id?: string`
- `include_terminal?: boolean` — default false
- `limit?: number` — default 100, max 500
- `project?: string` — concrete project or `"*"` for all
- `area?: string` — concrete area or `"*"` for all
- `team?: string` — concrete team or `"*"` for all
- `required_capability?: string`
- `mode?: "investigate_only" | "propose_patch" | "edit_files" | "test_only"`
- `milestone?: string`
- `manager_reviewed?: boolean`
Returns `Task[]`. Terminal tasks are hidden by default, but backlog tasks
are included so a manager can see parked ideas and ready work together.
Active tasks may include `stale: true` when the holder has not heartbeated
within `AGENT_BUS_TASK_STALE_MS`.
### assign_task
Input:
- `task_id: number`
- `to_agent: string`
- `allow_conflicts?: boolean`
- `allow_pending_agent?: boolean`
Assigns an open task to an agent. With `allow_pending_agent`, the task
can be reserved for a worker that has not registered yet.
### delegate
Create a task, assign it, notify the assignee, require acknowledgement
by default, and record a delegation event. Prefer this for long-running
work instead of `ask`.
Input:
- `from: string`
- `to_agent: string`
- `title: string`
- `description?: string`
- `mode?: "investigate_only" | "propose_patch" | "edit_files" | "test_only"`
- `expected_output?: string | null`
- `priority?: number`
- `cwd?: string`
- `thread_id?: string`
- `project?: string | null`
- `area?: string | null`
- `team?: string | null`
- `required_capability?: string | null`
- `deadline_at?: number | null`
- `checkin_at?: number | null`
- `file_scope?: string[]`
- `edit_scope?: string[]`
- `read_scope?: string[]`
- `ack_required?: boolean`
- `review_required?: boolean`
- `allow_pending_agent?: boolean`
- `allow_conflicts?: boolean`
Returns `{ task, event, assigned, pending, suggested_next_actions }`.
### claim_best_task
Input:
- `agent: string`
- `project?: string`
- `area?: string`
- `team?: string`
Claims the best open task in scope that matches the agent's capabilities.
### acknowledge_task / submit_review / handoff_task
Use acknowledgement to remove uncertainty after assignment. Use
`submit_review` for verifier approval; review-required tasks cannot be
completed until approved. Use `handoff_task` when a session stops
mid-task; it records a pinned handoff memory and can reassign the work.
`acknowledge_task` input:
- `agent: string`
- `task_id: number`
- `response: "claimed" | "declined" | "blocked"`
- `note?: string`
`submit_review` input:
- `reviewer: string`
- `task_id: number`
- `approved: boolean`
- `notes?: string`
`handoff_task` input:
- `from_agent: string`
- `task_id: number`
- `to_agent?: string`
- `reason: string`
- `memory?: string`
### check_scope_conflicts / project_board / activity / cockpit / now
Use `check_scope_conflicts` before assigning overlapping `edit_files` or
`propose_patch` work. Use `project_board` for manager status: agents,
active tasks, blocked tasks, waiting review, stale tasks, scope conflicts,
pinned risks, pinned handoffs, and suggested next actions.
`check_scope_conflicts` input:
- `file_scope?: string[]`
- `edit_scope?: string[]`
- `project?: string`
- `area?: string`
- `team?: string`
- `exclude_task_id?: number`
`project_board` input:
- `project?: string`
- `area?: string`
- `team?: string`
`team_board` input:
- `team: string`
- `project?: string`
- `area?: string`
`activity` input:
- `project?: string`
- `area?: string`
- `team?: string`
- `since?: number` — ms epoch lower bound
- `limit?: number`
`cockpit` input:
- `project?: string`
- `area?: string`
- `team?: string`
- `agent?: string`
- `limit?: number`
`now` input:
- `agent: string`
- `task_id?: number`
- `phase?: string | null`
- `note?: string | null`
- `status?: "idle" | "working" | "blocked" | "waiting_review" | "sleeping"`
Conflict checks compare active edit/propose tasks by `edit_scope`; broad
verifier `read_scope` does not create edit-conflict noise.
Use `activity` to answer "what happened recently?", `cockpit` to answer
"what needs manager attention?", and `now` to update your own visible
current work with one tool call.
### record_decision / list_decisions
Store and read durable project decisions.
### remember / list_memories / session_brief
Store durable structured memories and generate startup or handoff briefs.
Use `remember` for summaries, handoffs, risks, todos, facts, blockers, or
custom kinds. Pin handoff memories when the next agent should see them
first. Use `session_brief` when a new session needs current agents,
open/blocked/stale tasks, recent decisions, pinned memories, recent
memories, recent messages, and suggested next actions. Pinned handoffs
and risks never age out; recent decisions, unpinned memories, and
messages use the brief recency window.
For team work, use memory deliberately:
- `kind="decision"` for settled architecture/product choices.
- `kind="risk", pinned=true` for active risks the board should keep visible.
- `kind="summary"` for durable done-work context that future sessions need.
- `kind="todo"` for next actions that are not yet formal tasks.
- `kind="handoff", pinned=true` before a session exits or transfers work.
- Call `session_brief` before taking over an existing project/team.
Loop memory pattern: use event triggers. A settled debate or PM choice
gets `record_decision`. A reusable task gotcha gets
`remember(kind="lesson", task_id=...)`. A long-lived risk gets
`remember(kind="risk", pinned=true)`. A stop/switch/handoff gets
`remember(kind="handoff", pinned=true)` with a `Next:` line. Test and
review evidence should stay in task/test/review records, not duplicated
into memory.
### record_test_result / list_test_results
Record and list explicit build/lint/test evidence for `final_report`.
`record_test_result` input:
- `by_agent: string`
- `task_id?: number | null`
- `command: string`
- `status: "passed" | "failed" | "skipped"`
- `output_summary?: string | null`
- `git_ref?: string | null` — caller-supplied ref/commit/branch tested
- `cwd?: string | null` — directory where evidence was produced
- `project?: string | null`
- `area?: string | null`
- `team?: string | null`
`list_test_results` input:
- `task_id?: number`
- `by_agent?: string`
- `status?: "passed" | "failed" | "skipped"`
- `project?: string`
- `area?: string`
- `team?: string`
- `limit?: number`
### record_task_event / list_task_events / task_result / cancel_task
Record durable task progress and retrieve the full evidence bundle.
`record_task_event` input:
- `by_agent: string`
- `task_id: number`
- `event_type?: "note" | "phase" | "progress" | "log" | "result" | "cancel"`
- `message: string`
- `phase?: string | null` — also updates `task.phase` when present
- `metadata?: object`
Use `task_result({ task_id })` before verifier review or handoff. It
returns the task, task events, test results, memories, and thread
messages. Use `cancel_task({ agent, task_id, reason })` for superseded
or intentionally stopped work so the board does not show abandoned
active tasks.
Use `wait_for_task({ task_id, wait_s })` when you need to block for task
activity. It returns the same evidence bundle plus `timed_out`, holder,
latest event/message/test result, and suggested next actions.
After `ask`, `ask_best`, `ask_team`, `wait_for_task`, or intentional
`inbox(wait_s)`, give the user a visible status update. Once you have
the information needed for the current step, stop waiting on the bus and
continue the local task in your own session.
### final_report
Generate implemented / not implemented / risks / tests / manual checks,
warnings, and safe-to-commit/push/deploy flags from tasks.
### review_gate
Generate a deterministic ready/block decision from `project_board` and
`final_report`. `ok=false` includes blockers such as active tasks,
blocked tasks, pending reviews, edit-scope conflicts, or unsafe final
report flags.
Verifier gate pattern: for implementation work, "done" means the
implementation is finished, test evidence is recorded with
`record_test_result`, any required reviewer has approved with
`submit_review(approved=true)`, and `review_gate` / `final_report`
reports safe. Do not treat a chat reply or task status alone as final
completion.
Set `independent_review=true` with `review_required=true` when there is
another agent available to review implementation work. This rejects
self-review by the current holder or pending assignee with
`REVIEW_SELF_FORBIDDEN`; requester/PM review is still allowed. Do not
set it for solo-agent work unless the user accepts that another reviewer
is needed.
### get_task
Fetch one task.
Input:
- `task_id: number`
Returns `Task`.
## Data shapes
### Agent
```ts
{
name: string
capabilities: string[]
registered_at: number // ms epoch
last_seen: number // ms epoch
paused: boolean
project: string | null
area: string | null
role: string | null
routing_weight: number
status: "idle" | "working" | "blocked" | "waiting_review" | "sleeping"
}
```
### Message
```ts
{
id: number
from_agent: string
to_agent: string
kind: "msg" | "ask" | "reply"
content: string
reply_to: number | null // ask id, set on replies
status: "pending" | "delivered" | "answered"
created_at: number
delivered_at: number | null
replied_at: number | null
thread_id: string // conversation grouping
claim_deadline: number | null // at-least-once claim expiry
claimed_by: string | null
channel: string | null // set on channel fan-outs
project: string | null
area: string | null
team: string | null
priority: "low" | "normal" | "high" | "urgent"
}
```
### Task
```ts
{
id: number
title: string
description: string | null
thread_id: string
requested_by: string
claimed_by: string | null
state: "backlog" | "open" | "claimed" | "working" | "blocked" |
"completed" | "failed" | "canceled"
milestone: string | null
priority: number
cwd: string | null
blocked_reason: string | null
blocked_on_task_id: number | null
result: string | null
created_at: number
updated_at: number
claimed_at: number | null
finished_at: number | null
project: string | null
area: string | null
required_capability: string | null
mode: "investigate_only" | "propose_patch" | "edit_files" | "test_only"
expected_output: string | null
deadline_at: number | null
checkin_at: number | null
final_answer: string | null
manager_reviewed: boolean
file_scope: string[]
edit_scope: string[]
read_scope: string[]
pending_assignee: string | null
stale?: boolean
}
```
## Error codes
| Code | Meaning | Common cause |
|---|---|---|
| `INVALID_INPUT` | Argument validation failed | Bad name format, missing field |
| `UNKNOWN_AGENT` | Agent not registered | Typo, or recipient never registered |
| `NAME_TAKEN` | Name actively held | Add `replace: true` to register |
| `ASK_TIMEOUT` | No reply within timeout | Recipient idle or slow |
| `ASK_CYCLE` | Would create mutual deadlock with an active opposite ask | Inspect the ask id from the error with `message_status`, answer it, or use `ask_async`/`send`; stale opposite asks no longer block |
| `ASK_RECIPIENT_UNAVAILABLE` | Blocking ask recipient is stale or paused | Use `ask_async`, `send`, `delegate`, or wake/start them |
| `ASK_NOT_FOUND` | Referenced ask id doesn't exist | Wrong id, or ask was deleted |
| `TEAM_NOT_FOUND` | Team cleanup target not found | Wrong scope or already deleted |
| `AGENT_HAS_ACTIVE_TASKS` | Member has active tasks | Reassign/release tasks, or pass `release_tasks: true` intentionally |
| `TEAM_HAS_ACTIVE_TASKS` | Team has active tasks | Review board, or pass `release_tasks: true` intentionally |
| `TASK_NOT_FOUND` | Referenced task id doesn't exist | Wrong id or missing dependency |
| `TASK_INVALID_TRANSITION` | Bad task state transition | Terminal or skipped state |
| `TASK_NOT_CLAIMABLE` | Task cannot be claimed | Already claimed or not open |
| `TASK_FORBIDDEN` | Task mutation denied | Not requester or holder |
| `REVIEW_SELF_FORBIDDEN` | Independent review rejects self-review | Have another agent review, or disable independent_review for solo work |
| `INTERNAL` | Bug / unexpected | File a report |
## Patterns
### Listener mode
You sit waiting for messages.
```
1. register({ name: "me", replace: true })
2. inbox({ agent: "me", team: "frontend", wait_s: 110 }) ← blocks on this team only
3. For each returned message:
- reply({ from: "me", ask_id: m.id, answer: ... })
// answers asks; creates a threaded reply for normal messages
4. Go to step 2 immediately. Do NOT narrate empty timeouts.
```
### Synchronous question
```
ask({ from: "me", to: "specialist", question: "...", timeout_s: 60 })
```
Returns the reply. If you don't know who the specialist is:
```
ask_best({ from: "me", capability: "react", question: "..." })
```
If the recipient may not be listening:
```
ask_async({ from: "me", to: "specialist", question: "..." })
```
### Reliable processing (at-least-once)
When the message triggers something expensive or irreversible:
```
const msgs = inbox({ agent: "me", wait_s: 110, claim_s: 600 })
for (const m of msgs) {
try {
doExpensiveWork(m)
ack({ agent: "me", message_id: m.id })
} catch {
// skip ack — message will redeliver after 600s
}
}
```
### Broadcast to a team
```
subscribe({ agent: "me", channel: "alerts" })
send_channel({ from: "ci", channel: "alerts", message: "deploy failed" })
```
### Scoped team coordination
```
register({ name: "pm", project: "movie-app", area: "*", team: "ios-ui" })
send_team({ from: "pm", team: "ios-ui", message: "sync on navigation" })
ask_team({ from: "pm", team: "ios-ui", capability: "design", question: "which detail layout should we implement first?" })
delegate_team({ from: "pm", team: "ios-ui", capability: "design", title: "Compare detail screen options", mode: "investigate_only" })
team_board({ team: "ios-ui", project: "movie-app" })
```
Use `send_team` for announcements and lightweight discussion. If you
want each worker's assignment to show on the team board, use
`delegate_team`; messages alone do not become board tasks.
For a human terminal view of one team's discussion, use
`agent-bus team-chat --team <team>` or
`agent-bus team-chat --team <team> --watch`. For noisy chats, use
`agent-bus team-chat --team <team> --since-id <id>`,
`agent-bus team-chat --team <team> --thread <thread_id>`, or
`agent-bus team-chat --team <team> --threads`. With MCP only, call
`recent(team=<team>)` and render the matching scoped messages.
For a terminal or human that wants a notification without consuming the
message:
```
agent-bus wait --agent worker-a --team frontend --notify
agent-bus wait --agent worker-a --team frontend --thread t_abc123
```
### Continue a thread
When you receive a message, take its `thread_id` and pass it back:
```
const incoming = inbox({ agent: "me" })
const m = incoming[0]
send({ from: "me", to: m.from_agent, message: "...", thread_id: m.thread_id })
```
### Delegate and track a task
Use this when work may take longer than one `ask` timeout or needs visible
state. If the user expects work to show on `project_board`,
`team_board`, `kanban`, or `done`, it must be represented as a task;
plain `send` / `send_team` messages are not enough.
Kanban uses stable task states plus phases. Use `backlog` for parked
ideas, `open` for claimable work, then
`claimed/working/blocked/completed/failed/canceled`; use phases like
`planning`, `editing`, `testing`, `review`, and `done` to drive human
workflow lanes.
```
const task = create_task({
requested_by: "me",
title: "Verify current diff",
description: "Run tests and report findings first.",
priority: 10,
cwd: "/repo",
})
send({
from: "me",
to: "verifier",
message: `Please claim task #${task.id}.`,
thread_id: task.thread_id,
})
```
The worker claims, records progress with `record_task_event`, moves to
`working`, and finishes with `completed`,
`failed`, or `blocked`. Use `list_tasks` to see what is open, active, or
stale.
### Multi-project / multi-area default
If you are running inside a repo, your MCP server usually registers you
with that repo-derived project automatically. If `.agent-bus.json`
defines path areas, sessions under matching folders also register with
that area. If you register with `team`, reads, tasks, team boards, and
capability routing can also stay inside that workgroup. Use
`project: "*"`, `area: "*"`, or `team: "*"` only for intentional
broader views or capability routing.
## Limits and caps
- `wait_s`, `timeout_s`: max 110 seconds (Claude Code tool timeout).
- Message body: no cap on the bus; recipients may see truncation around
1 MB depending on the MCP client.
- Agent names: 1-64 chars, `[a-zA-Z0-9_.-]`.
- Project, area, and team names: 1-64 chars, `[a-zA-Z0-9_.-]`; `"*"`
means all values for that filter dimension.
- Channel names: 1-64 chars, `[a-zA-Z0-9_.:#-]`.
- `ack` claim window: passed by caller in `claim_s` (no fixed maximum at
the MCP layer, but the Zod schema caps at 3600 s = 1 hour).
## What you should NOT do
- Don't loop on `inbox` with `wait_s=1` — you'll burn token budget on
Claude reasoning. Use the max `wait_s=110`.
- Don't narrate empty timeouts. When inbox returns an empty array,
silently re-call.
- Don't keep calling inbox/status/diagnostic tools after the reply you
needed has arrived. Tell the user what arrived and continue locally.
- Don't `send` a reply to an ask — use `reply`, otherwise the asker
doesn't unblock.
- Don't make up `from` names. Use the name you `register`ed.
- Don't poll `whois` repeatedly to check for new agents — the bus has no
"agent joined" notification; just use `ask_best` or address by known
name.
## Quick start (paste into yourself)
```
1. Call register with my chosen name and capabilities.
2. Call whois to see what other agents exist.
3. To send a message: call send.
4. To wait for messages: call inbox with wait_s=110, handle each
returned message, then call inbox again.
5. To ask a question and wait for an answer: call ask.
```
That's the entire surface.
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

