agent-group-mcp 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_group_mcp-0.4.2/LICENSE +21 -0
- agent_group_mcp-0.4.2/PKG-INFO +642 -0
- agent_group_mcp-0.4.2/README.md +614 -0
- agent_group_mcp-0.4.2/pyproject.toml +48 -0
- agent_group_mcp-0.4.2/setup.cfg +4 -0
- agent_group_mcp-0.4.2/src/agent_bus/__init__.py +6 -0
- agent_group_mcp-0.4.2/src/agent_bus/audit.py +125 -0
- agent_group_mcp-0.4.2/src/agent_bus/chat_tui.py +384 -0
- agent_group_mcp-0.4.2/src/agent_bus/cli.py +612 -0
- agent_group_mcp-0.4.2/src/agent_bus/formatting.py +371 -0
- agent_group_mcp-0.4.2/src/agent_bus/hooks.py +107 -0
- agent_group_mcp-0.4.2/src/agent_bus/init_cmd.py +460 -0
- agent_group_mcp-0.4.2/src/agent_bus/paths.py +27 -0
- agent_group_mcp-0.4.2/src/agent_bus/server.py +138 -0
- agent_group_mcp-0.4.2/src/agent_bus/storage.py +577 -0
- agent_group_mcp-0.4.2/src/agent_bus/wake.py +171 -0
- agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/PKG-INFO +642 -0
- agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/SOURCES.txt +27 -0
- agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/dependency_links.txt +1 -0
- agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/entry_points.txt +2 -0
- agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/requires.txt +5 -0
- agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/top_level.txt +1 -0
- agent_group_mcp-0.4.2/tests/test_cli.py +251 -0
- agent_group_mcp-0.4.2/tests/test_formatting.py +212 -0
- agent_group_mcp-0.4.2/tests/test_hooks.py +97 -0
- agent_group_mcp-0.4.2/tests/test_init.py +286 -0
- agent_group_mcp-0.4.2/tests/test_storage.py +241 -0
- agent_group_mcp-0.4.2/tests/test_tools.py +97 -0
- agent_group_mcp-0.4.2/tests/test_wake.py +225 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 agent-bus contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-group-mcp
|
|
3
|
+
Version: 0.4.2
|
|
4
|
+
Summary: Local MCP server + CLI letting multiple MCP clients (Claude Code, OpenCode, Gemini CLI, …) exchange messages over a shared SQLite bus with audit logging.
|
|
5
|
+
Author: Zoltan Nagy
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/zb-ss/agent-group-mcp
|
|
8
|
+
Project-URL: Issues, https://github.com/zb-ss/agent-group-mcp/issues
|
|
9
|
+
Project-URL: Source, https://github.com/zb-ss/agent-group-mcp
|
|
10
|
+
Keywords: mcp,claude-code,claude,agents,messaging,sqlite
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Topic :: Communications :: Chat
|
|
19
|
+
Classifier: Topic :: Software Development
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: mcp>=1.0
|
|
24
|
+
Requires-Dist: prompt_toolkit>=3.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# agent-bus
|
|
30
|
+
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
[](pyproject.toml)
|
|
33
|
+
|
|
34
|
+
Local MCP server + companion CLI that lets multiple Claude Code instances
|
|
35
|
+
(each running in a different repo on the same machine) **exchange messages
|
|
36
|
+
through a shared, persistent bus** — with full audit logging and Claude
|
|
37
|
+
Code hooks for instant reaction at every turn boundary.
|
|
38
|
+
|
|
39
|
+
- **Zero external services.** Local-only. Filesystem + SQLite (WAL mode).
|
|
40
|
+
- **One MCP server process per Claude session**, all sharing one DB.
|
|
41
|
+
- **Humans are first-class participants** via the `agent-bus` CLI
|
|
42
|
+
(`send`, `inbox`, `tail -f`, `chat` with a colored TUI).
|
|
43
|
+
- **Hook-driven reactivity** — peers see new messages at the start of
|
|
44
|
+
their next turn, and a Stop hook keeps an agent on the line until it
|
|
45
|
+
has handled its inbox.
|
|
46
|
+
- **Open-source friendly:** MIT, no telemetry, no network calls, all
|
|
47
|
+
state lives under `~/.claude-agent-bus/` (gitignored by default).
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
### Recommended: pipx (global)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pipx install agent-group-mcp
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This puts the `agent-bus` CLI on your `$PATH` (typically
|
|
60
|
+
`~/.local/bin/agent-bus`) in an isolated venv that pipx manages. Every
|
|
61
|
+
MCP client session, every repo, and every shell can call it without
|
|
62
|
+
sourcing anything. To upgrade later: `pipx upgrade agent-group-mcp`.
|
|
63
|
+
|
|
64
|
+
### Alternative: from source (development)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
git clone git@github.com:zb-ss/agent-group-mcp.git
|
|
68
|
+
cd agent-group-mcp
|
|
69
|
+
python -m venv .venv
|
|
70
|
+
source .venv/bin/activate
|
|
71
|
+
pip install -e ".[dev]"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Use this when you want to hack on the source — `pytest` runs from the
|
|
75
|
+
same venv. **Don't** wire this venv's paths into your `.mcp.json` /
|
|
76
|
+
hooks; use the pipx install for that. The two installs coexist fine.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Architecture
|
|
81
|
+
|
|
82
|
+
- **Language:** Python 3.11+
|
|
83
|
+
- **MCP transport:** stdio. Each `claude` spawns its own MCP subprocess.
|
|
84
|
+
No long-lived daemon, no port management.
|
|
85
|
+
- **SDK:** official `mcp` Python package (FastMCP).
|
|
86
|
+
- **Storage:** SQLite at `$AGENT_BUS_DB`
|
|
87
|
+
(default `~/.claude-agent-bus/bus.db`), WAL mode + `synchronous=NORMAL`.
|
|
88
|
+
- **Audit log:** append-only JSON-lines at `$AGENT_BUS_AUDIT_LOG`
|
|
89
|
+
(default `~/.claude-agent-bus/audit.log`).
|
|
90
|
+
|
|
91
|
+
### Identity from config (not from arguments)
|
|
92
|
+
|
|
93
|
+
Each MCP server reads `AGENT_BUS_NAME` and `AGENT_BUS_REPO` from env at
|
|
94
|
+
startup, upserts the `agents` row, and silently attaches its name to
|
|
95
|
+
every tool call. **`send_message` / `read_inbox` no longer take a `from`
|
|
96
|
+
or `name` argument** — the server already knows its identity.
|
|
97
|
+
|
|
98
|
+
The server fails hard if either env var is missing.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## MCP tools
|
|
103
|
+
|
|
104
|
+
All tools are auto-attributed to `AGENT_BUS_NAME`.
|
|
105
|
+
|
|
106
|
+
| Tool | Returns |
|
|
107
|
+
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
108
|
+
| `whoami()` | `{name, repo_path, registered_at, last_seen}`. Diagnostic; replaces the old `register_agent`. |
|
|
109
|
+
| `list_agents()` | `[{name, repo_path, last_seen, pending_count}, …]` |
|
|
110
|
+
| `send_message(to, body, thread_id=None)` | unicast → `{message_id, sent_at, thread_id, recipients}`; broadcast (`to="*"`) → `{message_ids, sent_at, thread_id, recipients}` |
|
|
111
|
+
| `read_inbox(mark_read=True, limit=50)` | Unread messages for self, oldest first: `[{message_id, from, to, body, sent_at, thread_id, read_at, delivered_at}]` |
|
|
112
|
+
| `read_thread(thread_id, limit=100)` | Full conversation across participants, ordered by `sent_at`. |
|
|
113
|
+
| `tail_audit(limit=50)` | Last N audit-log entries (any agent). |
|
|
114
|
+
|
|
115
|
+
> **Note on broadcast:** the schema keeps `message_id` as the row PK and
|
|
116
|
+
> tracks `read_at` per recipient, so a broadcast fans out into **N rows
|
|
117
|
+
> with N distinct `message_id`s** — one per peer. That's why the
|
|
118
|
+
> broadcast return shape uses `message_ids` (plural). The audit log
|
|
119
|
+
> shows one `send` row per recipient, which makes per-peer delivery
|
|
120
|
+
> easy to grep.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## SQLite schema
|
|
125
|
+
|
|
126
|
+
```sql
|
|
127
|
+
agents(name PK, repo_path, registered_at, last_seen)
|
|
128
|
+
messages(message_id PK, from_agent, to_agent, body, thread_id,
|
|
129
|
+
sent_at, read_at NULL, delivered_at NULL)
|
|
130
|
+
-- Indices on (to_agent, read_at) and (thread_id, sent_at).
|
|
131
|
+
-- Startup: PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`messages` is the durable history. **Rows are never deleted** — `read_at`
|
|
135
|
+
just flips when a peer pulls a message. The audit log is the recovery
|
|
136
|
+
source of truth.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Audit logging
|
|
141
|
+
|
|
142
|
+
Every `send_message`, `read_inbox`, hook delivery, and CLI `send` writes
|
|
143
|
+
one JSON-lines record to `audit.log` **before returning**. Row shape:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"ts": "<ISO8601 UTC>",
|
|
148
|
+
"op": "send" | "read" | "deliver",
|
|
149
|
+
"actor": "<agent or 'human'>",
|
|
150
|
+
"message_id": "...",
|
|
151
|
+
"from": "...",
|
|
152
|
+
"to": "...",
|
|
153
|
+
"thread_id": "...",
|
|
154
|
+
"body_preview": "<first 200 chars>",
|
|
155
|
+
"body_sha256": "<sha256 of full body>"
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The full body lives only in SQLite — the log keeps a preview + hash so
|
|
160
|
+
it stays grep-friendly while remaining tamper-evident. Hook deliveries
|
|
161
|
+
write a paired `read` + `deliver` row so the log shows the hook path.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Companion CLI
|
|
166
|
+
|
|
167
|
+
All subcommands hit the same SQLite store and audit log — no MCP round
|
|
168
|
+
trip — so a human can drive the bus from any shell even when no Claude
|
|
169
|
+
session is running.
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
agent-bus init [PATHS...] [--scan] [--apply] [--force] [--prefix STR] [--name NAME]
|
|
173
|
+
agent-bus send BODY [--to NAME] [--thread ID] [--name NAME] [--json]
|
|
174
|
+
agent-bus inbox [--name NAME] [--limit N] [--peek] [--json]
|
|
175
|
+
agent-bus tail [-f] [--limit N] [--full] [--json] # follow audit.log live
|
|
176
|
+
agent-bus chat [--name NAME] # colored TUI
|
|
177
|
+
agent-bus agents [--json]
|
|
178
|
+
agent-bus forget NAME # remove stale agent from roster
|
|
179
|
+
agent-bus wake-config {show,set,clear,test} [NAME] [COMMAND] # push-style alerts
|
|
180
|
+
agent-bus hook-stop # used by Stop hook
|
|
181
|
+
agent-bus hook-user-prompt # used by UserPromptSubmit hook
|
|
182
|
+
agent-bus serve # same as python -m agent_bus.server
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Identity defaults** for the CLI: `$AGENT_BUS_NAME` → `human`. That
|
|
186
|
+
identity is auto-registered in the `agents` table the first time it
|
|
187
|
+
sends, so peers can address you directly.
|
|
188
|
+
|
|
189
|
+
If you end up with stale identities on the roster (e.g. you sent once
|
|
190
|
+
as the default `human`, then started using `--name alex` and now both
|
|
191
|
+
show up in `/agents`), run `agent-bus forget human` to drop the stale
|
|
192
|
+
row. Message history is preserved — forgetting only removes the agent
|
|
193
|
+
from the broadcast fan-out and the roster.
|
|
194
|
+
|
|
195
|
+
### Chat TUI (`agent-bus chat`)
|
|
196
|
+
|
|
197
|
+
Built on [prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit)
|
|
198
|
+
so incoming messages never clobber the line you're typing on. Each agent
|
|
199
|
+
gets a stable color (sha1 of name → curated palette), timestamps are
|
|
200
|
+
shown in local `HH:MM:SS`, and thread IDs are truncated to 8 chars in the
|
|
201
|
+
display (full UUIDs still live in the DB and in `--json` output).
|
|
202
|
+
|
|
203
|
+
On connect you get:
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
agent-bus chat — connected as 'alex' (default → *). /help for commands.
|
|
207
|
+
agents on the bus (3):
|
|
208
|
+
api-service last seen 1m ago pending=0
|
|
209
|
+
web-app last seen 4s ago pending=2
|
|
210
|
+
alex last seen just now (you) pending=0
|
|
211
|
+
recent activity (last 10 sends):
|
|
212
|
+
14:30:51 web-app → api-service can you check the dashboard?
|
|
213
|
+
14:31:02 api-service → web-app on it [c7a3b2f0]
|
|
214
|
+
...
|
|
215
|
+
|
|
216
|
+
[alex → *] ▌
|
|
217
|
+
alex → * /help · /quit
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Commands inside the TUI:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
@<name> <body> direct message to one peer
|
|
224
|
+
/to <name|*|all> set default target
|
|
225
|
+
/agents list known agents + unread counts
|
|
226
|
+
/thread <id> reprint a thread (8-char prefix is enough)
|
|
227
|
+
/history [N] reprint last N 'send' rows
|
|
228
|
+
/clear clear screen
|
|
229
|
+
/help, /quit, /exit
|
|
230
|
+
plain text send to current default target (default '*')
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Tab-completes slash commands. Command history persists at
|
|
234
|
+
`~/.claude-agent-bus/chat_history`.
|
|
235
|
+
|
|
236
|
+
### Non-chat subcommands
|
|
237
|
+
|
|
238
|
+
`inbox`, `agents`, and `tail` use the same per-agent colors and column
|
|
239
|
+
alignment by default. Add `--json` to any of them for raw,
|
|
240
|
+
machine-readable output (shape stable across releases):
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
agent-bus inbox --json
|
|
244
|
+
agent-bus agents --json
|
|
245
|
+
agent-bus tail --json | jq '.[] | select(.op=="send")'
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
By default `tail -f` renders one-line summaries instead of raw JSON, so
|
|
249
|
+
it stays readable while still being scriptable via `--json`.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Bulk wiring with `agent-bus init`
|
|
254
|
+
|
|
255
|
+
If you have more than a handful of repos, hand-editing every
|
|
256
|
+
`.mcp.json` is a chore. `agent-bus init` does it for you:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# preview every git repo under these roots (default: dry-run)
|
|
260
|
+
agent-bus init --scan ~/websites ~/projects
|
|
261
|
+
|
|
262
|
+
# review the plan, then commit it
|
|
263
|
+
agent-bus init --scan ~/websites ~/projects --apply
|
|
264
|
+
|
|
265
|
+
# init just the current directory (writes immediately)
|
|
266
|
+
agent-bus init
|
|
267
|
+
|
|
268
|
+
# init one named repo
|
|
269
|
+
agent-bus init ~/projects/foo --name myname
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Each managed repo gets:
|
|
273
|
+
|
|
274
|
+
1. `.mcp.json` — only the `mcpServers.agent-bus` entry is added/refreshed;
|
|
275
|
+
other MCP servers in the file are preserved.
|
|
276
|
+
2. `.claude/settings.json` — `UserPromptSubmit` + `Stop` hooks are merged
|
|
277
|
+
in, and the six `mcp__agent-bus__*` permission entries are added to
|
|
278
|
+
`permissions.allow` (deduped if already present).
|
|
279
|
+
|
|
280
|
+
**Name derivation.** Default = slug of the repo's basename:
|
|
281
|
+
`~/websites/acme.dev` → `acme-dev`, `~/projects/my_thing` →
|
|
282
|
+
`my-thing`. Collisions across directories are resolved by prefixing the
|
|
283
|
+
parent dir (`projects-foo` vs `websites-foo`).
|
|
284
|
+
|
|
285
|
+
**Per-repo overrides.**
|
|
286
|
+
|
|
287
|
+
- Drop a `.agent-bus-name` file in any repo containing a single line
|
|
288
|
+
with the desired agent name. `agent-bus init` will use that name
|
|
289
|
+
instead of the slug. Useful for keeping legacy names during migration.
|
|
290
|
+
- Drop a `.agent-bus-ignore` file (empty) in any repo to opt it out of
|
|
291
|
+
bulk init entirely.
|
|
292
|
+
|
|
293
|
+
**Idempotency.** Re-running `init` is safe: it detects its own previous
|
|
294
|
+
output and refreshes it without duplicating hooks or allow-list
|
|
295
|
+
entries. Hand-written `agent-bus` entries are left alone unless you
|
|
296
|
+
pass `--force`.
|
|
297
|
+
|
|
298
|
+
**Useful flags.**
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
--scan treat paths as roots; walk for git repos
|
|
302
|
+
--apply actually write (required for --scan; single-repo is implicit)
|
|
303
|
+
--force overwrite hand-written agent-bus entries
|
|
304
|
+
--prefix STR prepend a slug to every derived name (e.g. `--prefix work-`)
|
|
305
|
+
--name NAME explicit override (single-repo init only)
|
|
306
|
+
--bin-path PATH override the agent-bus binary path written into the configs
|
|
307
|
+
--json emit the plan as JSON without applying
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Wiring it up manually (single repo, no bulk tool)
|
|
313
|
+
|
|
314
|
+
### 1. `.mcp.json` (one per repo)
|
|
315
|
+
|
|
316
|
+
Each repo gets its own MCP server with its own identity. Assuming the
|
|
317
|
+
pipx install above, `agent-bus serve` is on `$PATH`:
|
|
318
|
+
|
|
319
|
+
```jsonc
|
|
320
|
+
{
|
|
321
|
+
"mcpServers": {
|
|
322
|
+
"agent-bus": {
|
|
323
|
+
"command": "agent-bus",
|
|
324
|
+
"args": ["serve"],
|
|
325
|
+
"env": {
|
|
326
|
+
"AGENT_BUS_NAME": "alpha",
|
|
327
|
+
"AGENT_BUS_REPO": "/path/to/repo-a"
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
If Claude Code's subprocess environment doesn't inherit `~/.local/bin`,
|
|
335
|
+
use the absolute pipx path instead, e.g.
|
|
336
|
+
`"command": "/home/<you>/.local/bin/agent-bus"`.
|
|
337
|
+
|
|
338
|
+
In repo B, the same file uses `"AGENT_BUS_NAME": "beta"` and the
|
|
339
|
+
repo B path. Same `~/.claude-agent-bus/bus.db` is shared automatically.
|
|
340
|
+
|
|
341
|
+
### 2. `.claude/settings.json` (one per repo)
|
|
342
|
+
|
|
343
|
+
```jsonc
|
|
344
|
+
{
|
|
345
|
+
"hooks": {
|
|
346
|
+
"UserPromptSubmit": [
|
|
347
|
+
{
|
|
348
|
+
"matcher": "",
|
|
349
|
+
"hooks": [
|
|
350
|
+
{
|
|
351
|
+
"type": "command",
|
|
352
|
+
"command": "AGENT_BUS_NAME=alpha agent-bus hook-user-prompt"
|
|
353
|
+
}
|
|
354
|
+
]
|
|
355
|
+
}
|
|
356
|
+
],
|
|
357
|
+
"Stop": [
|
|
358
|
+
{
|
|
359
|
+
"matcher": "",
|
|
360
|
+
"hooks": [
|
|
361
|
+
{
|
|
362
|
+
"type": "command",
|
|
363
|
+
"command": "AGENT_BUS_NAME=alpha agent-bus hook-stop"
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
}
|
|
367
|
+
]
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
- `hook-user-prompt` reads the inbox, prints any pending messages, and
|
|
373
|
+
exits 0 — so they show up as extra context for Claude's next response.
|
|
374
|
+
- `hook-stop` reads the inbox; if empty it exits 0 (allow stop). If
|
|
375
|
+
there are pending messages, it emits
|
|
376
|
+
`{"decision":"block","reason":"…"}` so Claude keeps the turn open and
|
|
377
|
+
replies via `send_message`.
|
|
378
|
+
|
|
379
|
+
If you run the CLI from a different `$PATH`, replace `agent-bus` with the
|
|
380
|
+
absolute path to the script.
|
|
381
|
+
|
|
382
|
+
### 3. Pre-approve the MCP tools (optional but recommended)
|
|
383
|
+
|
|
384
|
+
By default Claude Code will prompt you the first time it calls each
|
|
385
|
+
`mcp__agent-bus__*` tool. To skip those prompts, drop this allowlist
|
|
386
|
+
into the same `.claude/settings.json`:
|
|
387
|
+
|
|
388
|
+
```jsonc
|
|
389
|
+
{
|
|
390
|
+
"permissions": {
|
|
391
|
+
"allow": [
|
|
392
|
+
"mcp__agent-bus__whoami",
|
|
393
|
+
"mcp__agent-bus__list_agents",
|
|
394
|
+
"mcp__agent-bus__send_message",
|
|
395
|
+
"mcp__agent-bus__read_inbox",
|
|
396
|
+
"mcp__agent-bus__read_thread",
|
|
397
|
+
"mcp__agent-bus__tail_audit"
|
|
398
|
+
]
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Worked example: three-way exchange
|
|
406
|
+
|
|
407
|
+
Open three terminals.
|
|
408
|
+
|
|
409
|
+
**Terminal A — Claude session in repo A** (`AGENT_BUS_NAME=alpha`):
|
|
410
|
+
|
|
411
|
+
```text
|
|
412
|
+
> Use the agent-bus MCP to greet beta and the human.
|
|
413
|
+
[claude calls send_message(to="*", body="hi everyone, alpha here")]
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Audit log gains:
|
|
417
|
+
|
|
418
|
+
```json
|
|
419
|
+
{"op":"send","actor":"alpha","from":"alpha","to":"beta","body_preview":"hi everyone, alpha here", ...}
|
|
420
|
+
{"op":"send","actor":"alpha","from":"alpha","to":"human","body_preview":"hi everyone, alpha here", ...}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**Terminal B — Claude session in repo B** (`AGENT_BUS_NAME=beta`):
|
|
424
|
+
|
|
425
|
+
The next user prompt fires `hook-user-prompt`, which prints:
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
[agent-bus] 1 new message(s) since last turn:
|
|
429
|
+
- from alpha at 2026-05-13T... (thread <id>): hi everyone, alpha here
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Claude replies with `send_message(to="alpha", body="hey alpha, beta here", thread_id="<same>")`. If Claude tries to stop without responding, the Stop hook blocks the turn with that exact reason payload, forcing it to handle the message first.
|
|
433
|
+
|
|
434
|
+
**Terminal C — human** running `agent-bus chat`:
|
|
435
|
+
|
|
436
|
+
```text
|
|
437
|
+
$ agent-bus chat
|
|
438
|
+
agent-bus chat: connected as 'human' (default target *).
|
|
439
|
+
[human → *] hello both
|
|
440
|
+
[chat] broadcast → alpha, beta (2 msg)
|
|
441
|
+
|
|
442
|
+
<alpha → human> [thread …] hi everyone, alpha here
|
|
443
|
+
[human → *] @alpha thanks for the ping!
|
|
444
|
+
[chat] sent → alpha (id 7c2a…)
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
All three participants see each other's messages and can chain replies
|
|
448
|
+
on the same `thread_id`.
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Recovery: reconstructing a thread from the audit log
|
|
453
|
+
|
|
454
|
+
`messages.body` is the only place full bodies live, but every send is
|
|
455
|
+
hashed and previewed in `audit.log`. To grep a thread:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
# every event in thread <id>, in order
|
|
459
|
+
grep '"thread_id":"<id>"' ~/.claude-agent-bus/audit.log
|
|
460
|
+
|
|
461
|
+
# every send by alpha
|
|
462
|
+
grep '"op":"send".*"actor":"alpha"' ~/.claude-agent-bus/audit.log
|
|
463
|
+
|
|
464
|
+
# verify a message body's integrity (preview + hash)
|
|
465
|
+
sqlite3 ~/.claude-agent-bus/bus.db \
|
|
466
|
+
"SELECT body FROM messages WHERE message_id='<id>'" \
|
|
467
|
+
| sha256sum
|
|
468
|
+
# compare against body_sha256 in the matching audit row
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
If the SQLite file is lost, the audit log gives you sender, recipient,
|
|
472
|
+
timing, thread, body preview (first 200 chars), and a hash. Full body
|
|
473
|
+
recovery requires SQLite + audit cross-reference.
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Running the tests
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
pip install -e ".[dev]"
|
|
481
|
+
pytest
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
The suite covers:
|
|
485
|
+
|
|
486
|
+
- whoami / env-derived identity
|
|
487
|
+
- send → read round trip
|
|
488
|
+
- broadcast hits all peers, not the sender
|
|
489
|
+
- audit log: one line per op, valid JSON, sha256 matches body, preview ≤ 200 chars
|
|
490
|
+
- thread retrieval ordering
|
|
491
|
+
- concurrent writers (2 subprocesses × 50 messages each, no loss, no dup `message_id`)
|
|
492
|
+
- `hook-stop` empty → exit 0, no stdout
|
|
493
|
+
- `hook-stop` with pending → exit 0, valid JSON with `decision: "block"` and bodies in `reason`
|
|
494
|
+
- `hook-user-prompt` with pending → bodies in stdout, inbox empty after
|
|
495
|
+
- `agent-bus tail -f` integration: send a message in a subprocess, assert it appears within 1s
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Contributing
|
|
500
|
+
|
|
501
|
+
PRs welcome. The codebase is small and intentionally stays that way.
|
|
502
|
+
|
|
503
|
+
- Install for development: `pip install -e ".[dev]"` (use a venv).
|
|
504
|
+
- Run the test suite: `pytest`.
|
|
505
|
+
- Style: no formatter pinned, but match what's already there. Type hints
|
|
506
|
+
are encouraged but not enforced.
|
|
507
|
+
- All `messages` data and the audit log live under `~/.claude-agent-bus/`
|
|
508
|
+
by default. Pytest fixtures redirect to a per-test `tmp_path` via the
|
|
509
|
+
`AGENT_BUS_DB` / `AGENT_BUS_AUDIT_LOG` env vars — please use them in
|
|
510
|
+
new tests so they never touch a developer's real bus.
|
|
511
|
+
- Never commit personal data, real agent names tied to private projects,
|
|
512
|
+
or absolute filesystem paths. The `.gitignore` already excludes
|
|
513
|
+
`*.db`, `audit.log`, `chat_history`, and `.claude-agent-bus/`.
|
|
514
|
+
|
|
515
|
+
## Waking idle agents
|
|
516
|
+
|
|
517
|
+
Agent CLIs (Claude Code, OpenCode, Gemini CLI) block on stdin when
|
|
518
|
+
idle, so a peer message landing in SQLite does **not** start a new
|
|
519
|
+
turn on its own. There is no MCP transport — stdio, SSE, or
|
|
520
|
+
Streamable HTTP — that fixes this, because the constraint is in the
|
|
521
|
+
client REPL, not the wire protocol.
|
|
522
|
+
|
|
523
|
+
agent-bus's answer is **wake-on-send**: a per-agent shell command runs
|
|
524
|
+
the instant a message arrives for that agent. The command is whatever
|
|
525
|
+
your environment makes feasible — drive emacs/vterm, drive a tmux
|
|
526
|
+
pane, fire a desktop notification, hit a webhook, ring a bell.
|
|
527
|
+
|
|
528
|
+
### How it works
|
|
529
|
+
|
|
530
|
+
1. You drop a `wake.json` next to `bus.db` (default:
|
|
531
|
+
`~/.claude-agent-bus/wake.json`) mapping `agent_name → shell command`.
|
|
532
|
+
2. On every `send_message` (MCP tool *or* CLI), agent-bus looks up the
|
|
533
|
+
recipient's entry and fires the command as a detached
|
|
534
|
+
fire-and-forget subprocess. Broadcast = one fire per recipient.
|
|
535
|
+
3. Every fire writes an `op="wake"` audit row alongside the `send`
|
|
536
|
+
row, with the launch status (`fired:OK`, `fired:ERR:…`, `disabled`,
|
|
537
|
+
`no-config`) so the bus log shows what happened.
|
|
538
|
+
4. The command receives routing info on **env vars**:
|
|
539
|
+
`AGENT_BUS_FROM`, `AGENT_BUS_TO`, `AGENT_BUS_THREAD_ID`,
|
|
540
|
+
`AGENT_BUS_MESSAGE_ID`, `AGENT_BUS_BODY_PREVIEW` (≤ 200 chars).
|
|
541
|
+
The full message body is piped to **stdin as JSON**.
|
|
542
|
+
5. **Always quote env-var expansions in your wake command** — bodies
|
|
543
|
+
are user-controlled. `notify-send "$AGENT_BUS_BODY_PREVIEW"` is
|
|
544
|
+
safe; `notify-send $AGENT_BUS_BODY_PREVIEW` is shell-injection-prone.
|
|
545
|
+
|
|
546
|
+
### Managing wake.json
|
|
547
|
+
|
|
548
|
+
Edit it directly, or use the CLI helpers:
|
|
549
|
+
|
|
550
|
+
```bash
|
|
551
|
+
agent-bus wake-config show
|
|
552
|
+
agent-bus wake-config set web-app \
|
|
553
|
+
'emacsclient -e "(with-current-buffer (get-buffer \"*vterm: web-app*\") (vterm-send-string \"check inbox\") (vterm-send-return))"'
|
|
554
|
+
agent-bus wake-config test web-app # fire a synthetic wake to verify
|
|
555
|
+
agent-bus wake-config clear web-app
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### Example wake commands
|
|
559
|
+
|
|
560
|
+
**emacs / vterm.** Requires `emacs --daemon` (or `M-x server-start`)
|
|
561
|
+
and a vterm buffer named per agent, e.g. `*vterm: web-app*`.
|
|
562
|
+
The command types into that buffer and submits, which fires
|
|
563
|
+
UserPromptSubmit in Claude Code (or the equivalent in OpenCode /
|
|
564
|
+
Gemini CLI), which lets the hook drain the inbox.
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
agent-bus wake-config set web-app \
|
|
568
|
+
'emacsclient -e "(with-current-buffer (get-buffer \"*vterm: web-app*\") (vterm-send-string \"check inbox\") (vterm-send-return))"'
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
**Plain terminals (desktop notification).** When no multiplexer is in
|
|
572
|
+
the picture and you're at the desk, notify yourself and switch tabs:
|
|
573
|
+
|
|
574
|
+
```bash
|
|
575
|
+
agent-bus wake-config set web-app \
|
|
576
|
+
'notify-send -a agent-bus "agent-bus → web-app" "$AGENT_BUS_FROM: $AGENT_BUS_BODY_PREVIEW"'
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
**tmux** (only if you do use it):
|
|
580
|
+
|
|
581
|
+
```bash
|
|
582
|
+
agent-bus wake-config set web-app \
|
|
583
|
+
'tmux send-keys -t main:agents.0 "check inbox" Enter'
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
**Disable for a specific agent** (e.g. the human):
|
|
587
|
+
|
|
588
|
+
```bash
|
|
589
|
+
agent-bus wake-config set alex false # or just omit the entry
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Failure modes
|
|
593
|
+
|
|
594
|
+
- Wake command crashes / exits nonzero: agent-bus doesn't notice
|
|
595
|
+
(we don't await the process). The `op="wake"` audit row says
|
|
596
|
+
`fired:OK` because the launch succeeded. Debug your wake command
|
|
597
|
+
separately by running it yourself.
|
|
598
|
+
- `wake.json` is missing or malformed: silently no-op for every
|
|
599
|
+
recipient. `agent-bus wake-config show` reports
|
|
600
|
+
`(no wake commands configured)`.
|
|
601
|
+
- `emacsclient` can't reach an emacs server: command exits nonzero
|
|
602
|
+
out-of-band. Run `emacsclient -e '(message "ping")'` once to verify
|
|
603
|
+
before wiring it.
|
|
604
|
+
|
|
605
|
+
## Privacy & security
|
|
606
|
+
|
|
607
|
+
- All state is local. No network calls leave your machine.
|
|
608
|
+
- The audit log records message **previews** (first 200 chars) and
|
|
609
|
+
**sha256 hashes** of bodies. Treat both the DB and the audit log as
|
|
610
|
+
containing message content, and back them up / protect them
|
|
611
|
+
accordingly.
|
|
612
|
+
- agent names + repo paths are recorded in the `agents` table and the
|
|
613
|
+
audit log. Don't put secrets in agent names. Don't broadcast
|
|
614
|
+
credentials over the bus.
|
|
615
|
+
|
|
616
|
+
## Out of scope (future work)
|
|
617
|
+
|
|
618
|
+
- **Online / cross-machine mode.** A Streamable HTTP MCP transport
|
|
619
|
+
(`mcp.run_streamable_http_async()` — the successor to SSE) would
|
|
620
|
+
let agents on different machines join one bus, expose a web/mobile
|
|
621
|
+
UI for the human, and accept webhooks from external integrations as
|
|
622
|
+
first-class senders. Not yet built; the SQLite + stdio design is
|
|
623
|
+
intentional for now to keep the install lightweight and the trust
|
|
624
|
+
story simple. `wake.json` is forward-compatible — wake commands
|
|
625
|
+
fire from wherever `send_message` runs, so an HTTP mode later
|
|
626
|
+
reuses the same config.
|
|
627
|
+
- Authentication (local-only, single-user assumed).
|
|
628
|
+
- Message expiry / log rotation.
|
|
629
|
+
- Synchronous request/reply within a single tool call (would require
|
|
630
|
+
the peer's session to be actively running).
|
|
631
|
+
- **Push wake without `wake.json`.** Anthropic ships
|
|
632
|
+
`notifications/claude/channel` for exactly this, but (a) it's
|
|
633
|
+
Claude-Code-only and we stay client-neutral (OpenCode and Gemini
|
|
634
|
+
CLI need to participate too), and (b) the upstream wake path has
|
|
635
|
+
known open bugs (`#44380` and dozens of duplicates). `wake.json`
|
|
636
|
+
is the portable answer until the spec + clients converge.
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## License
|
|
641
|
+
|
|
642
|
+
MIT.
|