Skip to main content
Glama
README.md
# agentbus

A message bus and live dashboard for MCP-capable agents that run in separate
consoles: OpenCode, Kimi Code, Claude Code, Gemini CLI, Cursor, Codex, VS Code,
or anything else that speaks MCP. One daemon, any number of agents, a browser
page that shows who said what to whom as it happens, and a tone on every
message.

## Why a bus, and why `recv` blocks

An MCP server cannot push to an idle agent; the model only acts when it calls a
tool. agentbus works around that with a blocking `recv` tool: the agent parks
inside the call and the daemon releases it the moment a message for it lands.
That is what makes delivery feel instant instead of polled.

## Install

```
pip install -e .
```

Python 3.11 or newer. Pulls in the official `mcp` SDK (2.x), Starlette,
uvicorn and httpx.

## Run

```
agentbus serve --open
```

| What | Where |
|---|---|
| Dashboard | http://127.0.0.1:8765/ |
| MCP endpoint (streamable HTTP) | http://127.0.0.1:8765/mcp |
| Message log | `~/.agentbus/bus.db` (use `--db :memory:` for a throwaway bus) |

## Connect a console

Every console gets a name. The name is bound to the connection through an
`X-Agent` header, so the model can never send as the wrong agent. Print the
exact snippet for your client:

```
agentbus config --agent alice --client opencode
agentbus config --agent bob   --client kimi
```

Supported `--client` values: `opencode`, `kimi`, `claude`, `gemini`, `cursor`,
`codex`, `vscode`, `json` (generic `mcpServers` entry), and `stdio` for clients
that can only launch local MCP processes. The stdio entry runs
`agentbus stdio --agent NAME`, a tiny MCP server that forwards every call to
the daemon. A client that cannot set headers at all can put the name in the
URL instead: `http://127.0.0.1:8765/mcp?agent=alice`.

Then tell each agent how to behave, for example:

> You are alice on the bus. After every step call `recv`. When messages
> arrive, act on them, reply with `send`, then call `recv` again.

The daemon also sends those instructions to every client that shows MCP
server instructions to its model.

## What the agents see

| Tool | Arguments | Returns |
|---|---|---|
| `send` | `target`, `text` | `sent #N to target`, or `error: ...` |
| `recv` | `wait_seconds` (default 30, max 600), `since` (optional) | `{"messages": [{seq, ts, sender, target, text}], "next_since": N}` |
| `peers` | none | `{"you": name, "paused": bool, "agents": [{agent, state, since}]}` |
| `history` | `limit` | `{"messages": [...]}` |

`target` is an agent name or `*` for everyone. A bare `recv` returns only
messages the agent has not seen yet; `since=N` replays everything after
sequence N. Every problem comes back as a string starting with `error:` so
the model can read it and change course.

Clients enforce their own tool-call timeouts. The config snippets set a long
one where the client has a setting for it (OpenCode `timeout`, Kimi
`toolTimeoutMs`, Gemini `timeout`, Codex `tool_timeout_sec`, Claude Code
`MCP_TOOL_TIMEOUT`). If your client cuts `recv` off early, ask the agent to
pass a smaller `wait_seconds` and simply call again.

### Notes from testing with real clients

- **OpenCode** (1.18) connects with the JSON snippet and its `opencode mcp
  list` shows the bus as connected. Without the `timeout` setting it aborted
  a 120-second `recv` with "MCP error -32001: Request timed out"; the model
  then retried with 25 seconds and everything worked. With `timeout` set as
  in the snippet, long waits are fine.
- **Kimi Code** (0.40) only loads project-level `.kimi-code/mcp.json` after
  you trust the folder in the interactive prompt, so headless `kimi -p` runs
  in a fresh folder see no bus tools. Put the snippet in the user-level
  `~/.kimi-code/mcp.json` instead, or open the folder once interactively and
  accept the trust prompt. (In the test run Kimi noticed the missing tools,
  read the config, and drove the MCP endpoint by hand with curl; the message
  still arrived, which says something nice about the protocol.)

## The dashboard

![agentbus dashboard: Kimi Code and OpenCode exchanging messages, plus an operator broadcast](docs/dashboard.png)

- One column per agent, with a presence dot: solid means active, breathing
  means parked in `recv`, grey means idle.
- Each message is an arrow from the sender's column to the target's, with the
  text underneath. Broadcasts are dashed across every column.
- A tick appears under a message when its target picks it up.
- A short tone plays per message, pitched by sender. Browsers block audio
  until you click the page once. The tab title shows an unread count while
  the tab is hidden.
- The compose box at the bottom sends as `operator` to one agent or everyone.
- The pause button makes every `send` fail with an error the agents can
  read, which is the quickest way to stop two agents that are ping-ponging.

## From a shell or any HTTP client

```
agentbus send --agent alice --to bob "build is green"
agentbus send --agent alice --to bob --file report.txt   # posts a summary line (paste-aware)
agentbus recv --agent bob --wait 60
agentbus recv --agent bob --tail 10                       # last 10, no cursor needed
agentbus tail --agent bob                                 # live-follow the bus (Ctrl-C to stop)
agentbus history --limit 50 --grep v73                    # filter the log
agentbus peers
```

| Route | Purpose |
|---|---|
| `POST /api/send` `{sender, target, text}` | send (sender also accepted from `X-Agent`) |
| `GET /api/recv?wait=30&since=N` | long-poll; identity from `X-Agent` or `?agent=` |
| `GET /api/peers` | presence (state, last activity, read cursor per agent) |
| `GET /api/history?limit=50&since=N` | recent messages, or everything after a sequence cursor |
| `GET /api/profile` / `PUT /api/profile` `{blob}` | durable per-agent state — compaction handoff |
| `GET /api/profiles` | everyone's profiles |
| `POST /api/paste` `{text}` | long content out of band; returns `{id, chars}` |
| `GET /api/paste/{id}` | fetch a paste |
| `POST /api/pause` `{paused: true}` | pause or resume |
| `GET /events` | server-sent events feed used by the dashboard |
| `GET /health` | liveness |

Presence is touched by any authenticated request (`X-Agent`), so polling counts as
being alive — no heartbeat messages needed. Profiles survive restarts (SQLite);
agents that compact their context re-read their profile plus `history?since=` and
are back in one call.

```
curl -X POST http://127.0.0.1:8765/api/send \
  -H "content-type: application/json" \
  -d '{"sender":"ci","target":"*","text":"deploy finished"}'
```

## Safety valves

- Rate limit: 60 messages per minute per agent (the operator is exempt). Over
  the limit, `send` returns `error: rate limit ...`.
- Pause: see above.
- The daemon binds to 127.0.0.1. `--host 0.0.0.0` lets agents on other
  machines join; there is no authentication, so only do that on a network you
  trust.

## Development

```
pip install -e ".[dev]"
python -m pytest -q
```

Design notes live in `docs/superpowers/specs/`, the implementation plan in
`docs/superpowers/plans/`.

Maintenance

ActivityMaintained
ResponsivenessNo issues