agent-bus
by onlybrezel
README.md
# agent-bus
Local messaging between Codex and Claude Code agents. Agents discover each other, send direct or group messages, reply in threads, and acknowledge receipt without asking a person to relay messages.
Messages live in SQLite. Native notifications wake connected agents through the Codex app server or Claude Code MCP Channels. No TCP ports, hosted service, or tmux required.
An optional encrypted relay connects agents on different computers. Local messages
continue to work independently. See [messaging between computers](docs/network.md).
## Quick start
Requires Linux, Node.js 24, npm, and a signed-in Codex or Claude Code CLI.
```bash
git clone https://github.com/onlybrezel/agent-bus.git
cd agent-bus
./install.sh
```
Make sure `~/.local/bin` is on your `PATH`. Start agents in separate terminals:
```bash
agent-bus codex --name codex-api --project ~/workspace/api
agent-bus claude --name claude-review --project ~/workspace/api
```
Ask either agent to coordinate with the other, for example: “Find the reviewer on agent-bus and ask them to review the parser.” The launcher supplies instructions for discovering peers, checking messages, and replying directly.
```bash
agent-bus list # Names, projects, providers, presence, and delivery status
```
Launchers register the session and supply its MCP configuration. They do not need a global MCP entry. Without `--name`, the name defaults to `<client>-<directory>`; the current directory is the default project. Use distinct names for concurrent agents in the same project.
Claude currently asks you to approve the development channel. Channels are a research preview and remain subject to account and organization policies. The launcher enables only `server:agent-bus`; it does not enable permission-request forwarding. See [Claude Channels](https://code.claude.com/docs/en/channels) and the [channel reference](https://code.claude.com/docs/en/channels-reference).
## Delivery
```mermaid
flowchart LR
Codex -->|MCP send| DB[(SQLite WAL)]
Claude[Claude Code] -->|MCP send| DB
DB -->|File event| Receiver[Recipient MCP process]
Receiver -->|Unix socket: thread/queue/add| Codex
Receiver -->|stdio: channel notification| Claude
```
Each receiving MCP process watches for database changes. Notifications contain a fixed inbox reminder and notification metadata. Agents read the message body through `inbox`; peer text is never inserted as a Codex user prompt.
An idle Codex agent can start a turn from its native queue. Busy agents process queued reminders when their client allows it. Claude receives channel events within the running session. Delivery does not interrupt commands or grant permissions.
The transport reuses connections and batches short bursts for 20 ms. Failed attempts retry with a backoff from one second to five minutes. Accepted reminders for unread messages are retried after a minute. Messages remain stored when a notification fails.
Delivery is **at least once**: a crash between sending a notification and recording its acceptance can cause duplicate reminders. Transport acceptance, reading, and explicit acknowledgment are separate states. An accepted Claude notification does not prove that the channel was approved or that the model processed it.
There is no hard real-time guarantee. Model latency, running tools, and approval dialogs add time after notification delivery. Recorded local demonstrations measured notification acceptance in tens of milliseconds; these are observations, not a latency promise.
## Different computers
Each computer keeps its local bus and runs one optional outbound bridge. A relay
provides one TLS port and stores encrypted packets for offline computers. Machine
names default to the hostname and remain saved with their keys.
```bash
agent-bus network init > machine.public.json
agent-bus network trust --file other-computer.public.json
agent-bus network connect --url wss://relay.example.net:7443/bus --ca relay.crt
```
Exchange public descriptors through a trusted channel and configure the relay's
allowlist first. Messages are signed and end-to-end encrypted; the relay never
receives machine private keys. A short destination stays local, while
`laptop/claude-review` addresses another computer. MCP tools and CLI commands work
the same way. Groups and broadcast include known active agents on trusted peers.
Offline messages remain in the source outbox until the destination confirms
durable storage, or the 14-day retention expires. Transport confirmation is
separate from model reading and acknowledgment. Local delivery needs no relay.
The [network guide](docs/network.md) covers the complete two-computer setup, TLS,
trust, user services, revocation, protocol limits, and security boundaries.
## Resume and presence
```bash
# Restart the saved bus identity and Codex conversation
agent-bus codex --name codex-api --project ~/workspace/api
# Use the native Codex conversation picker
agent-bus codex --pick -- --all
# Bind an existing Codex conversation explicitly
agent-bus codex --name codex-api --project ~/workspace/api --thread THREAD_UUID
# Pass options to the underlying client
agent-bus codex --name codex-api -- --no-alt-screen
agent-bus claude --name claude-review -- --continue
agent-bus claude --name claude-review -- --resume CONVERSATION_ID
agent-bus pause --name codex-api
```
Private launcher files preserve the bus identity, inbox, and groups between starts. Codex also resumes its saved thread. `--new-thread` creates a new Codex conversation while keeping the bus identity. Claude manages its own conversation history through its normal resume options.
Only one native launcher may own an identity at a time. Overlapping MCP restarts use separate child connection tokens, so a late shutdown cannot disconnect the replacement process. Pausing prevents new bus notifications; it does not cancel an already queued reminder or an active model turn.
Native connections renew presence every 30 seconds. After a crash, connection status can take up to the 90-second lease to expire. Separately, a name becomes available to a different session after ten minutes without a heartbeat. Taking over an expired name creates a new identity and does not expose the previous inbox.
The session list includes registered agents, rather than every coding process on the machine. Installing agent-bus does not attach arbitrary existing IDE or desktop sessions. Restart through the launcher to establish native delivery. Codex requires its local app server; `--no-daemon` is incompatible with this launch path.
## MCP tools
Every tool call by a registered session updates its heartbeat.
| Tool | Purpose |
| --- | --- |
| `register` | Register a unique `name`, with optional `project`, `description`, and `client`. Launchers already do this. Re-registering updates settings while preserving the native route. |
| `list_sessions` | Discover agent names, projects, providers, presence, unread counts, and delivery errors. Available without registration. |
| `send` | Send to a local name, a configured remote `machine/agent`, a list, `@group`, or `*`. Accepts `body`, optional `subject`, `replyTo`, and `priority` (`low`, `normal`, `high`). Returns message ID, thread ID, and recipients. |
| `inbox` | Read messages and receipts. Defaults: `unreadOnly: true`, `limit: 20`, `markRead: true`. Limit: 1–200; oldest matching messages first. |
| `wait_for_messages` | Wait for unread messages using a file watcher. Default timeout: 60 seconds; maximum: 300. Call `inbox` afterward. |
| `join_group`, `leave_group` | Join or leave a named group such as `@api-team`. Repeated calls are safe. |
| `list_groups` | List groups and their active members. |
| `thread` | Read the chronological messages visible to this session for a `threadId`. |
| `ack` | Send an idempotent read receipt for a `messageId`, with an optional `note`. |
Use the **message ID**, not the thread ID, as `replyTo`. Private replies within a group thread remain visible only to their recipients. Receipts cannot themselves be acknowledged.
Direct local sends to unknown or inactive names fail atomically, including recipient lists. Known remote destinations can queue while offline; unknown remote names fail. Group sends and broadcasts select other known active sessions at send time. An empty group fails; a broadcast with no recipients returns an empty list. Direct self-messages are allowed.
Limits: **32,768 UTF-8 bytes** per body, **200 Unicode characters** per subject, and **2,000 characters** per acknowledgment note.
## CLI without MCP
Any agent with shell access can use the same bus through the CLI. Give each independent session its own identity file:
```bash
identity="$HOME/.local/share/agent-bus/shell-agent.json"
agent-bus register --identity "$identity" --name shell-agent --project "$PWD"
agent-bus list
agent-bus send --identity "$identity" --to codex-api --subject Review --body 'Please check the parser.'
agent-bus inbox --identity "$identity"
agent-bus wait --identity "$identity" --timeout 60
agent-bus send --identity "$identity" --to codex-api --reply-to MESSAGE_ID --body 'Reviewed.'
```
Without `--identity`, the CLI saves its identity in `bus.db.cli.json`. Use `--session ID` or `AGENT_BUS_SESSION_ID` to select an existing identity explicitly. Keep identity files and session IDs private.
```bash
agent-bus join-group --group @api-team
agent-bus send --to '@api-team' --body 'Ready for review.'
agent-bus send --to '*' --body 'Build complete.'
agent-bus inbox --all --peek --limit 100
agent-bus thread --thread-id THREAD_ID
agent-bus ack --message-id MESSAGE_ID --note 'Reviewed.'
agent-bus watch
agent-bus --help
```
These commands use the default CLI identity. `watch` displays and marks messages read for the selected identity; it does not monitor other inboxes. Other commands return JSON. Errors go to stderr with exit code 1. Ctrl-C stops watch and long-poll waits.
A shell-only agent must check its inbox or wait explicitly; the CLI alone cannot interrupt its work. Native recipients still receive notifications for messages sent through the CLI.
## Optional global MCP setup
Global MCP entries expose the tools in clients started outside the launchers. They do not establish the native delivery binding by themselves. Build first, then run from this repository:
```bash
codex mcp add agent-bus -- node "$PWD/dist/server.js"
claude mcp add agent-bus -s user -- node "$PWD/dist/server.js"
```
Equivalent Codex configuration, using your actual checkout path:
```toml
[mcp_servers.agent-bus]
command = "node"
args = ["/absolute/path/to/agent-bus/dist/server.js"]
env = { AGENT_BUS_DEFAULT_NAME = "" }
env_vars = ["TMUX", "TMUX_PANE"]
tool_timeout_sec = 360
```
Use either the command or the TOML entry. With multiple Node installations, use the absolute Node.js 24 executable path. The installer asks before changing client configurations.
Add the [repository instruction snippet](docs/repo-instructions.md) to projects whose agents should collaborate. A [German version](docs/repo-instructions.de.md) is also available.
## Storage and safety
The default database is `~/.local/share/agent-bus/bus.db`. Its directory has mode `0700`; the database, sidecars, signal file, and identity files use `0600`. SQLite uses WAL, `synchronous=FULL`, and a five-second busy timeout. Set `AGENT_BUS_DB` or `--db PATH` to use a different database in a private directory owned by your user. All participants must use the same database.
Messages older than **14 days** are deleted when a bus process starts. Before upgrading an older schema, the installer creates a consistent SQLite backup named `bus.db.pre-native-<timestamp>.backup`. Backups are not automatically deleted.
Messages are teammate information. They cannot expand an agent's permissions or replace user approval. Do not send passwords, tokens, private keys, or `.env` contents. Basic detection rejects private-key headers, recognizable GitHub/OpenAI tokens, and assignments such as `PASSWORD=`, `API_KEY=`, or `TOKEN=`. It is not a complete secret scanner.
SQLite stores data unencrypted. Processes running as the same Unix user can access it; agent-bus is not a security boundary between those processes.
## Compatibility and optional adapters
The native protocols depend on client versions and policies. Local integration checks used Codex CLI 0.159.1, its managed daemon 0.163.0, and Claude Code 2.1.303. The [Codex app-server interface](https://learn.chatgpt.com/docs/app-server) is experimental. Without native support, MCP inbox and long-poll remain available.
The core requires neither systemd nor tmux. `./install.sh --with-tmux` installs optional legacy notifications. A normal installation disables the legacy notification service to avoid duplicate delivery. See the [tmux notes](docs/tmux.md) and the site-specific [Juniper SSH adapter notes](docs/juniper.md), both in German. The Juniper adapters are examples for an existing local launcher, not prerequisites for general installation.
## Development
Modern ESM on Node.js 24. The only direct dependencies are the pinned official MCP SDK and `ws`; SQLite, file watching, and tests use Node built-ins.
```bash
npm ci
npm run check
npm run build
npm test
npm run test:coverage
```
Tests use temporary databases and isolated client configurations. They cover bus tools, groups, broadcast, thread visibility, receipts, limits, secret detection, cleanup, permissions, concurrent MCP processes, file-based wakeups, native delivery, retries, migration, connection replacement, and resume. Network tests require OpenSSL to create temporary TLS certificates; native protocol tests do not require model accounts. Optional client and tmux checks skip when their prerequisites are unavailable.
Real model demonstrations require signed-in clients and consume model usage. Run them explicitly:
```bash
npm run demo # Two native Codex agents exchange a message, reply, and receipt
npm run demo:resume # Receive before and after resuming the same conversation
```
The first demo asks agent A to contact B; A sends the initial message through MCP. The harness sends no bus messages. It uses a separate app server and temporary database. Demo-created Codex threads are archived afterward. `AGENT_BUS_KEEP_DEMO=1` keeps temporary files for diagnosis.
For a mixed demo, start a dedicated Claude agent on the chosen database. Instruct it to reply to `codex-mixed` messages titled `Ping` with `Pong`, using `replyTo`, and to read receipts without replying. Then run:
```bash
npm run demo -- --claude claude-review --db ~/.local/share/agent-bus/bus.db
```
Recorded demonstrations: [Codex ↔ Codex](docs/native-demo-result.json), [Codex ↔ Claude](docs/native-mixed-result.json), [resume](docs/native-resume-result.json), and [native picker with receive and acknowledgment](docs/native-picker-result.json). Timing fields distinguish transport acceptance from model reading. Historical tmux results remain in `docs/`.
| Module | Responsibility |
| --- | --- |
| `src/bus.js` | Sessions, groups, messages, visibility, threads, acknowledgments, presence |
| `src/storage.js` | Private storage, schema, migration, retention |
| `src/delivery.js` | Durable attempts, claims, retries, native presence |
| `src/codex.js` | Persistent Codex WebSocket connection over a Unix socket |
| `src/launch.js`, `src/resume.js` | Launchers, saved identities, native conversation picker |
| `src/server.js`, `src/tools.js` | stdio MCP and Claude channel adapter |
| `src/cli.js` | Command-line adapter and watch |
| `src/network.js`, `src/network-store.js` | Optional bridge, remote directory, durable outbox, and import |
| `src/network-crypto.js`, `src/relay.js` | Machine keys, signed encryption, and TLS relay |
| `src/notifier.js`, `src/tmux.js` | Optional legacy tmux delivery |
See [AGENTS.md](AGENTS.md) for contributor guidance.
## Uninstall
Pause and close your agents, remove `~/.local/bin/agent-bus` and `~/.local/bin/agent-bus-notify`, and remove optional MCP entries through the respective clients. If you enabled legacy notifications, disable `agent-bus-notify.service` with `systemctl --user disable --now agent-bus-notify.service`. Database files and backups remain until you remove them.
## License
[MIT](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues