Local, human-in-the-loop message bus for coordinating parallel AI coding-agent sessions on one machine
Project description
downbeat
Local filesystem-backed message broker + TUI + CLI + skill for handing off work between parallel Claude Code sessions on the same machine.
Install
uv tool install downbeat # or: pipx install downbeat
downbeat init # one command installs the WHOLE runtime
downbeat init is the single source of truth for the entire relay runtime. It:
- bootstraps
~/.claude/relay/data dirs and migrates legacy messages, - installs the skill →
~/.claude/skills/downbeat/, - writes the
relay.pyshim →~/.claude/relay/relay.py, - installs the bundled hooks (
relay-inbox.py,relay-poll-offer.py) →~/.claude/hooks/(chmod +x), - installs the bundled slash commands (
relay-register/send/reply/peers/monitor.md) →~/.claude/commands/, - registers the relay hooks in
~/.claude/settings.json(idempotent, backed up, atomic).
It is safe to re-run: content-equal files are left as-is, already-registered hooks are
skipped, and a hook that differs from the bundled copy is kept (your local edit
wins) unless you pass --force. settings.json edits never clobber non-relay hooks
sharing the same event/matcher.
Use
downbeat register parent --role parent
downbeat register child --role child
downbeat send child "task" "do the thing"
downbeat inbox --peer child
downbeat reply <msg_id> "done"
downbeat tui # full management UI
kind is an open string: task (default for all normal messages) and backflow-ready
(structured RLM findings from a child — see the downbeat skill). Future kinds
(workflow-request, workflow-result) are planned for Phase 3.
Always-on inbox watch
Give a child session always-on inbox awareness after pairing:
downbeat watch # event-driven (fswatch/FSEvents); instant, ~0 idle cost
downbeat watch --peer child-1 # parent watching a child's inbox
downbeat watch --poll # force poll fallback (every --interval seconds)
downbeat watch --interval 30 # poll fallback interval (default: 90s)
downbeat watch --once # one-shot: print all current NEW, then exit
Run downbeat watch in the child terminal (or as a Monitor job) immediately
after downbeat register. The watcher notifies only — it never drains, acks,
or takes any action. The human (or the session's hook at the next prompt) drives action.
Stop with Ctrl+C.
watch is event-driven by default (uses watchdog FSEvents/inotify — fires instantly on
inbox changes, near-zero idle CPU). If watchdog is unavailable it falls back to polling
automatically and prints [watch] polling every Ns on startup so you always know which
backend is active. Use --poll to force the interval fallback regardless.
watch-vs-monitor cost table:
| Want | Use | Cost on idle channel |
|---|---|---|
| Cheap notify-to-wake, you act when woken | downbeat watch as a Monitor |
~0 (blocks on FS event; model turn only on real mail) |
| Session auto-acts role-aware on a cadence | /relay-monitor (/loop) |
a model turn every interval |
Background inbox polling
The first time you invoke a relay action in a Claude Code session, the skill
offers to start a 3-minute inbox poll via /loop. Accept to get notified of
incoming messages without having to manually check.
Continuous self-monitoring (/relay-monitor)
In a registered Claude Code session, run the /relay-monitor slash command to make that
session continuously pull its own inbox and act on new messages:
/relay-monitor # start monitoring, default 3-minute interval
/relay-monitor 5m # custom interval
/relay-monitor stop # stop
Behaviour is role-asymmetric:
- child session: auto-executes arriving tasks per its role briefing and replies with results (consent-at-startup autonomy).
- parent session: surfaces new messages concisely and asks the human how to handle each; never auto-executes.
Before starting the monitor, check your identity with:
downbeat whoami # prints: <name> <role>
downbeat whoami --json # prints: {"name": "...", "role": "..."}
watch vs /relay-monitor — key distinction:
downbeat watch |
/relay-monitor |
|
|---|---|---|
| Runs as | external process (pane / Monitor job) | in-session /loop |
| Backend | event-driven (FSEvents/inotify), poll fallback | timer-based loop |
| Does | prints new mail to a pane (human reads) | session pulls mail into its own context + acts per role |
| Acts? | never | child: yes (autonomous); parent: no (surfaces) |
| Idle cost | ~0 (event-driven; model turn only on real mail) | a model turn every interval |
| Use when | operator watching from outside | a session should self-drive on its inbox |
Both tools are complementary and can be run simultaneously.
TUI keybindings
| Key | Action |
|---|---|
| Tab/Shift+Tab | Cycle focus: Messages → Composer |
| s | Switch acting-as parent |
| a | Toggle archived history (chat view, 📥 inbox tab) |
| c | Clear inbox — archive this peer's backlog → processed/ (chat view, 📥 inbox tab) |
| Left/Right | Prev / next group member |
| Up/Down | Within focused region (messages, composer) |
| Enter | Send (in composer) / Open message detail (in message list) |
| Escape / q | Back (in message detail) |
| e | Edit (in message detail, only NEW) |
| r | Reply (in message detail) |
| d | Delete with confirm (in message detail) |
| Shift+B | Broadcast status (in message detail, when applicable) |
| y | Yank (copy) message body to clipboard (chat view and message detail) |
| c | Copy message id to clipboard (in message detail) |
| Up/k, Down/j | Scroll up / down in message detail |
| Ctrl+B / PgUp | Page up in message detail (Fn+↑ alias) |
| Ctrl+F / PgDn | Page down in message detail (Fn+↓ alias) |
| g / Home | Top of message detail (Fn+← alias) |
| G / End | Bottom of message detail (Fn+→ alias) |
| Ctrl+P | Peers screen (add / remove / gc) |
| f | Find message by id |
| ? / F1 | Help |
| Ctrl+R | Refresh |
| Ctrl+L / F6 | Toggle log viewer |
| q | Quit |
Uninstall
downbeat uninstall # removes skill + shim + hooks + commands + relay
# settings.json regs; leaves data + backups in ~/.claude/relay
Layout
- Source:
src/downbeat/{core,cli,tui,skill} - Bundled runtime assets:
src/downbeat/assets/{hooks/,commands/,hooks_manifest.json} - Tests:
tests/ - State:
~/.claude/relay/{sessions.json, inbox/, processed/, logs/, groups.json}
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file downbeat-0.1.1.tar.gz.
File metadata
- Download URL: downbeat-0.1.1.tar.gz
- Upload date:
- Size: 233.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e689d4fe66d2e8cb34cb651194174fc8f445177fae344e732498050c921313d
|
|
| MD5 |
6c4ed2b4fbb6a581fb5da41b8e3e0d6e
|
|
| BLAKE2b-256 |
07fc708afa111fdc0e583c1831a1c95c57e5be478e4894531ab096b96c1f8541
|
File details
Details for the file downbeat-0.1.1-py3-none-any.whl.
File metadata
- Download URL: downbeat-0.1.1-py3-none-any.whl
- Upload date:
- Size: 81.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
840851caa7a3b573052a4c98952aaee4b37314ce5399088e487bf0c5797f71bd
|
|
| MD5 |
78e5ec8862a293c467cb52e61a9691ea
|
|
| BLAKE2b-256 |
a1b35faa11b0a084210817b4e72cce7a5085aaacf15b3e19037f05614221add2
|