Sparks Bus
July 26, 2026 Β· View on GitHub
π¦ This project has moved to Disco-Bus
Sparks Bus is no longer developed. Its last release was v0.5.0 in May 2026. Everything it did, Disco-Bus does β standalone, actively maintained, and without requiring Mnemo Cortex to run.
β github.com/GuyMannDude/disco-bus Β· product page
Why the split happened: Sparks Bus used Mnemo as the message store, which meant you couldn't adopt the bus without adopting the memory system. Disco-Bus keeps the same doctrine β delivery confirmation, tracking IDs, a visible lifecycle, push-not-poll listeners β on a local SQLite store with no memory dependency at all. Bring your own agents; memory is optional. It's ~1000 lines and it's the bus we run our own fleet on.
Already running Sparks Bus? Nothing here stops working β this repository stays readable and the code is unchanged. There is no automated migration; Disco-Bus is a fresh install with its own schema. If you're starting today, start with Disco-Bus.
This repository is archived and read-only.
A multi-agent message bus with delivery confirmation. Discord is the doorbell, Mnemo is the mailbox, the tracking ID is the receipt.
Every message produces a visible lifecycle in your Discord:
π¬ DELIVERED β bus saved the payload, posted the receipt
β
PICKED UP β recipient agent read the message
π LOOP CLOSED β recipient replied; sender's task is done
β οΈ DELIVERY FAILED β wake-up failed; one alert, no retries
β οΈ STALE β DELIVERED but un-ACKd for too long
You stop asking agents "did you get that?" β you watch one channel and see every package move.
Two install modes
The watcher detects whether Mnemo Cortex is reachable at startup and picks a mode:
| Mode | Mnemo? | What carries the payload? | What you lose without Mnemo |
|---|---|---|---|
| Full | yes | Mnemo (recallable by tracking ID) | nothing β full doctrine |
| Standalone | no | Discord notification body | semantic recall and cross-agent memory |
In both modes the delivery β notify β pickup β reply lifecycle works identically. Standalone is the on-ramp; you can drop in Mnemo later without changing any agent code.
Architecture
ββββββββββββββββββββ
β Sparks Bus β
β (sqlite WAL) β
ββββββββββ¬ββββββββββ
β poll every 30s
βΌ
ββββββββββββββββββββ
β Bus Watcher β
β (this daemon) β
ββββββ¬ββββββββββ¬ββββ
β β
βββββββββββ βββββββββββ
βΌ βΌ
ββββββββββββββββββββββ ββββββββββββββββββββββ
β Mnemo Cortex β β Discord (Bot) β
β payload by β β #dispatch + ACKs β
β tracking_id β β #alerts β
β (FULL mode only) β β β
ββββββββββββββββββββββ ββββββββββ¬ββββββββββββ
β
ββββββββββββββΌβββββββββββββ
βΌ βΌ βΌ
[Agent A] [Agent B] [Agent C]
(claude) (discord) (http/queue)
Message lifecycle
1. CREATED bus_send inserts a row in bus.sqlite
2. SAVED (full mode) watcher POSTs payload to Mnemo by tracking_id
3. DELIVERED π¬ in #dispatch β bus knows about it, mailbox is stocked
4. NOTIFIED same step β DELIVERED *is* the notification
5. PICKED UP β
in #dispatch β recipient agent has read it
6. PROCESSING recipient does work
7. REPLIED recipient calls bus_reply (a new row, reply_to set)
8. CLOSED π in #dispatch on the reply, references original tracking ID
Failure paths produce a one-shot β οΈ in #alerts with the diagnostic, then quiet down β no retry storms.
Prerequisites
- Python 3.10+ (uses PEP 604 union syntax)
- SQLite (CLI for init only; the watcher uses the stdlib)
pip install -r requirements.txtβ onlyrequests- A Discord bot token with permission to post in your server's
#dispatchand#alertschannels - (Full mode only) Mnemo Cortex reachable over HTTP. Any service that exposes
GET /healthandPOST /writebackwith the same shape works as a substitute.
Step-by-step setup
-
Get the code. Either install mnemo-cortex (
pip install mnemo-cortex) and usesparks_bus/from the package, or just copy the directory anywhere on disk and run from there. The watcher has zero hard dependency on the rest of mnemo-cortex. -
Initialize the bus database. The watcher applies
schema.sqlautomatically on every startup, so you can skip this step β but if you want the DB ready before the first run:mkdir -p ~/.sparks sqlite3 ~/.sparks/bus.sqlite < schema.sql -
Drop in the Discord bot token.
echo "YOUR_BOT_TOKEN" > ~/.sparks/discord-token chmod 600 ~/.sparks/discord-token -
Map your channels. Copy
discord-channels.example.jsontodiscord-channels.jsonand fill in your guild's channel IDs (right-click channel β Copy Channel ID with developer mode on). -
Configure agents. Copy
config.example.jsontoconfig.jsonand edit theagentsblock. Each agent picks a wake method:claudeβ spawnclaude --printwith the message body as the prompthttpβ POST to an Agent Zeroβstyle API (seturl)discordβ post to a channel (setchannel); the agent must be listening therequeueβ pull mode; the watcher just notifies and waits for the agent's MCP/SDK to read the row
-
Start the watcher. For a one-off:
python3 sparks-bus-watcher.pyFor systemd (user-level):
cp systemd/sparks-bus-watcher.service ~/.config/systemd/user/ systemctl --user daemon-reload systemctl --user enable --now sparks-bus-watcher journalctl --user -u sparks-bus-watcher -f -
Verify mode. First two log lines tell you everything:
Mode: FULL (Mnemo + Discord) Mnemo: http://localhost:50001 reachable=Trueβ¦or, if Mnemo is unreachable:
Mode: STANDALONE (Discord only β payload in notifications) Mnemo: http://localhost:50001 reachable=False -
Send your first message. From any shell on the same host:
sqlite3 ~/.sparks/bus.sqlite \ "INSERT INTO messages (from_agent, to_agent, subject, body) VALUES ('You', 'CC', 'hello', '{\"text\":\"verifying the bus\"}');"Within one poll cycle you should see π¬ in
#dispatch. Once the agent reads the row, β follows. Reply withreply_toset and you'll get π.
Configuration reference
config.json keys (every key can also be set by the matching BUS_* env var β see .env.example):
| Key | Env | Default | Notes |
|---|---|---|---|
db_path | BUS_DB_PATH | ~/.sparks/bus.sqlite | sqlite file; created if missing |
poll_interval_seconds | BUS_POLL_INTERVAL_SECONDS | 30 | Lower = faster ACKs, more CPU |
stale_seconds | BUS_STALE_SECONDS | 3600 | DELIVERED-too-long threshold |
mnemo.url | BUS_MNEMO_URL | http://localhost:50001 | Probed at startup; unreachable = standalone mode |
mnemo.agent_id | BUS_MNEMO_AGENT_ID | bus | Tenant ID for Mnemo writeback |
mnemo.writeback_endpoint | β | /writeback | Override only if your backend uses a different path |
discord.token_file | BUS_DISCORD_TOKEN_FILE | ~/.sparks/discord-token | One line: the bot token |
discord.channels_file | BUS_DISCORD_CHANNELS_FILE | ./discord-channels.json | Slug β ID map |
discord.dispatch_channel | BUS_DISPATCH_CHANNEL | dispatch | Where π¬ / β / π land |
discord.alerts_channel | BUS_ALERTS_CHANNEL | alerts | Where β οΈ lands |
Schema changes are non-destructive β schema.sql uses CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS, so you can run the watcher against an existing bus DB.
CC / Claude Code session-start hook
Drop hooks/bus-pending.sh into a Claude Code SessionStart hook so unread messages surface at the top of every session:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|clear",
"hooks": [{ "type": "command", "command": "/path/to/sparks_bus/hooks/bus-pending.sh" }]
}
]
}
}
Override the agent ID with BUS_AGENT=YourName if the hook should report for a different name.
A2A compatibility
Sparks Bus speaks the Google A2A protocol's data shapes today; full transport is on the v2 roadmap.
| Sparks Bus | A2A | Notes |
|---|---|---|
tracking_id | task.id | Globally unique receipt |
subject | task.name | |
body | task.input | JSON or text |
| lifecycle (CREATEDβDELIVEREDβPICKED UPβREPLIED) | task.state (submittedβworkingβcompleted) | See A2A.md |
Mnemo session_id | task.artifact | Same value as tracking_id |
reply_to | task.metadata.reply_to |
Each agent has a card in agent-cards/ describing identity, capabilities, and delivery method. These follow the A2A Agent Card shape so external A2A clients can discover what each agent does.
What's not yet here: HTTPS/JSON-RPC transport endpoints, registration with external A2A directories, capability negotiation. Those land when Sparks Bus v2 ships an external transport. Until then, agents inside one Sparks deployment know each other through config.json and the bus DB.
See A2A.md for the full mapping reference.
Operations notes
- Failed deliveries are one-shot: β οΈ in
#alerts, thendelivery_failed_atis stamped and that row is excluded from retries, pickup ACKs, and stale alerts. Operator clears the column to retry:UPDATE messages SET delivery_failed_at=NULL WHERE id=?; - Backlog recovery is automatic. Stop the watcher, queue messages, restart β every backlogged row is processed on the first poll cycle.
- Schema changes historically: the bus DB is upgraded by
ALTER TABLE ADD COLUMNonly. New columns default toNULL, treated as "step not yet done" by the scans. To upgrade an existing deployment to a new column set, just restart the watcher with the newschema.sql. - Backfill before upgrading. If your bus has historical rows that should not trigger retroactive notifications, mark them as already-handled before the first run with the new code:
UPDATE messages SET notified_at = created_at, mnemo_saved_at = 'backfilled', pickup_notified_at = COALESCE(read_at, created_at), stale_notified_at = created_at WHERE notified_at IS NULL;
License
MIT (inherits from mnemo-cortex).