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.
Files changed (29) hide show
  1. agent_group_mcp-0.4.2/LICENSE +21 -0
  2. agent_group_mcp-0.4.2/PKG-INFO +642 -0
  3. agent_group_mcp-0.4.2/README.md +614 -0
  4. agent_group_mcp-0.4.2/pyproject.toml +48 -0
  5. agent_group_mcp-0.4.2/setup.cfg +4 -0
  6. agent_group_mcp-0.4.2/src/agent_bus/__init__.py +6 -0
  7. agent_group_mcp-0.4.2/src/agent_bus/audit.py +125 -0
  8. agent_group_mcp-0.4.2/src/agent_bus/chat_tui.py +384 -0
  9. agent_group_mcp-0.4.2/src/agent_bus/cli.py +612 -0
  10. agent_group_mcp-0.4.2/src/agent_bus/formatting.py +371 -0
  11. agent_group_mcp-0.4.2/src/agent_bus/hooks.py +107 -0
  12. agent_group_mcp-0.4.2/src/agent_bus/init_cmd.py +460 -0
  13. agent_group_mcp-0.4.2/src/agent_bus/paths.py +27 -0
  14. agent_group_mcp-0.4.2/src/agent_bus/server.py +138 -0
  15. agent_group_mcp-0.4.2/src/agent_bus/storage.py +577 -0
  16. agent_group_mcp-0.4.2/src/agent_bus/wake.py +171 -0
  17. agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/PKG-INFO +642 -0
  18. agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/SOURCES.txt +27 -0
  19. agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/dependency_links.txt +1 -0
  20. agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/entry_points.txt +2 -0
  21. agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/requires.txt +5 -0
  22. agent_group_mcp-0.4.2/src/agent_group_mcp.egg-info/top_level.txt +1 -0
  23. agent_group_mcp-0.4.2/tests/test_cli.py +251 -0
  24. agent_group_mcp-0.4.2/tests/test_formatting.py +212 -0
  25. agent_group_mcp-0.4.2/tests/test_hooks.py +97 -0
  26. agent_group_mcp-0.4.2/tests/test_init.py +286 -0
  27. agent_group_mcp-0.4.2/tests/test_storage.py +241 -0
  28. agent_group_mcp-0.4.2/tests/test_tools.py +97 -0
  29. 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: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
32
+ [![Python: 3.11+](https://img.shields.io/badge/Python-3.11+-blue.svg)](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.