SQLite-backed multi-project messaging relay for cooperating agent/orchestrator sessions
Project description
Sandesh
संदेश (Sanskrit/Hindi: message · dispatch) — a tiny, standalone, multi-project messaging system for cooperating agent/orchestrator sessions. A SQLite-backed maildir + a mailbox watcher, pure Python stdlib (no third-party deps).
Latest release (see all releases) — the global store (one DB for all projects, cross-project messaging behind an admin grant, archive→tombstone lifecycle), inbox filters + FTS5 search (CLI/MCP/Pi), 12 MCP tools, and the Pi extension at full parity.
Built for the "Model-B" parallel-orchestration pattern (a Mainline coordinator + worker Track sessions that can't talk to each other directly), but project-agnostic.
📖 Must-read: User Guide — how to run cooperating sessions (MCP & Pi).
Why
Sessions can't message each other directly, and re-invoking a sleeping agent is only possible via its host's background-task mechanism. Sandesh provides the relay: a durable, queryable mailbox each session reads from, plus a blocking notify watcher a session runs in the background so it wakes when mail addressed to it arrives.
Model
| table | holds |
|---|---|
address |
the addressbook — durable identities, '<Orchestrator> - <Project>' |
message |
the envelope — subject (required), kind, in_reply_to, body_path |
message_recipient |
per-message addressees — role (to/cc) + per-recipient read_at |
notifier |
per-session watcher liveness — pid, token, heartbeat, tombstone |
Semantics: To wakes / Cc silent · all-tracks broadcast (minus sender) ·
per-recipient read (read = being acted on; reply = done) · subject-only ⟷
file-body (full absolute paths) · keep history · reply threading ·
crash-safe liveness (dead-pid / stale-heartbeat reap) · cooperative tombstone eviction ·
validated address format.
Layout
Sandesh is a standard Python package (sandesh/) installed via pyproject.toml; the two
console scripts (sandesh, sandesh-mcp) land on $PATH. Runtime data lives under
the XDG data dir — one global DB for all projects, plus a body folder per project:
$XDG_DATA_HOME/sandesh/ (default ~/.local/share/sandesh/…)
├── sandesh.db (the ONE global DB, WAL — all projects)
└── projects/<project_id>/
└── messages/msg-<id>.md
Install
Distribution name: sandesh-relay (the import package + the sandesh / sandesh-mcp
commands keep the name sandesh; sandesh was taken on PyPI). The bare install is the
stdlib-only CLI + notify; the [mcp] extra adds the MCP server.
Sandesh is CLI + a chosen agent surface (MCP for Claude Code, or the Pi extension). The
full per-route × per-surface walkthrough — install → sandesh init (provision) → manage
(auto-migrate on update, admin) → uninstall, plus the uninstall matrix — lives in the
install guide:
➡ Install & uninstall guide → docs/INSTALL.md
In short: pick a route (uv / pipx / pip / install.sh), install the sandesh-relay
distribution with the extras for your surface ([mcp,migrate] for Claude, [migrate] for Pi),
then provision with sandesh init (--check to verify). PyPI publishing is automated via OIDC
trusted publishing — see RELEASING.md.
Use
sandesh setup --project Atlas
sandesh --project Atlas register --address "Mainline - Atlas" --kind mainline
sandesh --project Atlas register --address "Track 2 - Atlas" --kind track
# send (subject-only ⇢ no file; --body/--body-file ⇢ md body)
sandesh --project Atlas send --from "Track 2 - Atlas" --to "Mainline - Atlas" \
--subject "CR-308 started — chain unaffected"
# the watcher (run in the background; exits 0 with ids when 'to' mail lands)
sandesh --project Atlas notify --to "Mainline - Atlas"
# read (consolidates unread to+cc, marks read)
sandesh --project Atlas fetch --to "Mainline - Atlas"
# reply (threads under the parent; the reply IS the completion signal)
sandesh --project Atlas reply --to-msg 1 --from "Mainline - Atlas" --body "ack"
sandesh --project Atlas addressbook
$SANDESH_PROJECT and $SANDESH_ADDRESS default --project and the caller's own
address. $SANDESH_POLL_SECONDS sets the watcher cadence (default 10, floor 3).
Cross-project messaging
All projects share the one global DB, but sending across projects is admin-gated:
the sender's project needs a one-time grant — sandesh grant --cross-project --project <id> --by <admin> (revoke with sandesh revoke --cross-project …; project-wide, inherited by
every participant of the granted project). Without it, the send fails with
cross-project sending not approved for project '<id>' — ask the Sandesh admin. The admin
is assigned only at install via $SANDESH_ADMIN (install.sh) — there is no CLI or MCP
surface to set it. all-tracks broadcasts never cross projects, grant or not.
sandesh projects shows each project's state and grant (PROJECT STATE CROSS-PROJECT).
Project lifecycle
A project moves active → archived → tombstoned — strictly two-step, with two-tier
authorization:
# read-only freeze — reversible, deletes NOTHING (project's own Mainline only)
sandesh archive --project Atlas --by "Mainline - Atlas"
sandesh unarchive --project Atlas --by "Mainline - Atlas"
# permanent retirement — ARCHIVED projects only (the install-assigned super-admin only)
sandesh tombstone --project Atlas --by <admin> # prompts y/N; --yes to skip
archiveevicts the project's livenotifywatchers (cooperatively;--forcereaps stragglers) and freezes it: sends from/to it and new registrations are refused, but every message, body, and thread stays fully readable.unarchivereverses it.tombstoneis destructive and irreversible: it purges the project's internal messages (sender and all recipients inside the project) and deletes itsprojects/<id>/body folder. What survives: cross-project envelopes (rows stay for audit + thread anchoring — their bodies are lost), and the tracker row itself as a permanenttombstonedmarker (the project id is retired;setuprefuses to reuse it). Afterwardsinbox/fetchhide the tombstoned project's traffic, andthreadmarks holes withincomplete chain — message(s) removed (project tombstoned).- Who may do what:
archive/unarchivetake the project's own Mainline as--by;tombstonetakes only the super-admin assigned at install via$SANDESH_ADMIN. Without--yes,tombstoneasks for interactive confirmation (and refuses when stdin is not a terminal). --dry-run(all three verbs) reports what would happen — watchers to evict, the would-be state, and fortombstonethe purge counts (internal messages,body files,cross-project messageswhose bodies would be lost) — and writes nothing. Guards still apply: a dry-run on the wrong state or with the wrong--byerrors exactly like the real command.
MCP server
The verbs are also exposed as an MCP server (stdio) so an agent can call them as tools
instead of shelling out. mcp is the only third-party dependency — it ships behind the optional
[mcp] extra and is imported only by sandesh.mcp_server, so the CLI above stays pure-stdlib
either way. Installing the [mcp] extra (see the install guide) provides the
sandesh-mcp command; a base
install without the extra still ships sandesh-mcp but it prints a one-line "install the [mcp]
extra" hint and exits non-zero rather than crashing.
Register with Claude Code (stdio; bake a default project via env so tools can omit
project_id):
claude mcp add sandesh --scope user --env SANDESH_PROJECT=<id> -- sandesh-mcp
That writes an mcpServers entry (~/.claude.json for user scope, or a committed
.mcp.json for --scope project). Manage with claude mcp list / claude mcp get sandesh
/ claude mcp remove sandesh; the in-session /mcp panel shows status + tools.
Twelve tools, each taking project_id (falls back to $SANDESH_PROJECT): sandesh_setup,
sandesh_register, sandesh_unregister, sandesh_addressbook, sandesh_send,
sandesh_reply, sandesh_inbox, sandesh_fetch, sandesh_thread, sandesh_archive,
sandesh_unarchive, sandesh_search. The server also returns
usage instructions on initialize and serves the full scenarios doc as the sandesh://usage
resource. (Lifecycle: read = being acted on, reply = done — there is no separate status tool.)
Manual smoke — the MCP Inspector (browser UI):
mcp dev -m sandesh.mcp_server # needs the [mcp] extra; explore tools by hand
The wake path stays the notify watcher — MCP exposes the verbs, not the wake (an MCP
server can't re-invoke a sleeping agent). No notify/watch tool is exposed over MCP.
Discover via the MCP Registry
Sandesh is listed on the official MCP Registry as io.github.anthill-tec/sandesh (see
server.json), so MCP-aware clients/aggregators (Claude Code, Cursor, Glama,
PulseMCP, mcp.so, …) can discover it. The listing points at the PyPI distribution sandesh-relay
and the stdio launch uvx --from 'sandesh-relay[mcp]' sandesh-mcp. The listing is the server
(the verbs) only — the notify wake is a separate background process, not a tool. Publishing the
listing is a maintainer step (after the PyPI release) — see RELEASING.md.
Test
python3 -m unittest -v # from the repo root (stdlib-only: CLI + library)
.venv/bin/python -m unittest -v # includes the MCP server + E2E tests (needs the venv)
Roadmap
Shipped: the MCP server (stdio, [mcp] extra), packaging + OIDC PyPI publish, the
official MCP Registry listing (io.github.anthill-tec/sandesh), and the Pi
extension with native wake (integrations/pi/, npm
@anthill-tec/sandesh-pi). Registry publishes (PyPI / MCP-registry / npm) are
maintainer actions — see RELEASING.md.
License
Copyright © 2026 anthill-tec. Licensed under the GNU General Public License v3.0 — see
LICENSE. You may use, study, modify, and redistribute Sandesh under the GPLv3;
distributed derivatives must remain GPL-licensed with source available.
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 sandesh_relay-0.3.2.tar.gz.
File metadata
- Download URL: sandesh_relay-0.3.2.tar.gz
- Upload date:
- Size: 655.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9c463f2a66fa438165135813603155f9732fccffc76b2ae9100cce4140dc9c5
|
|
| MD5 |
3360c2ec4910320d9e3663c4037efa3f
|
|
| BLAKE2b-256 |
7a95f239da07f7e37266dd34e5dd402dc695e734c9bcfb3ef0855a41326b801b
|
Provenance
The following attestation bundles were made for sandesh_relay-0.3.2.tar.gz:
Publisher:
publish-pypi.yml on anthill-tec/sandesh
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sandesh_relay-0.3.2.tar.gz -
Subject digest:
e9c463f2a66fa438165135813603155f9732fccffc76b2ae9100cce4140dc9c5 - Sigstore transparency entry: 1828148293
- Sigstore integration time:
-
Permalink:
anthill-tec/sandesh@446e95fc9e69a6ec69915b89cb451a3770e58a39 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/anthill-tec
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@446e95fc9e69a6ec69915b89cb451a3770e58a39 -
Trigger Event:
release
-
Statement type:
File details
Details for the file sandesh_relay-0.3.2-py3-none-any.whl.
File metadata
- Download URL: sandesh_relay-0.3.2-py3-none-any.whl
- Upload date:
- Size: 80.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a4fa9e668e7f33c270a1945a3ac2ae892bb5d18851269548065a0562de45abd
|
|
| MD5 |
6dd00fb9609f94364d20390f48f82481
|
|
| BLAKE2b-256 |
bfb7aed7d042654279a5cb4a2cd944abe28a2e2a163b092fd70af5aaa5e230bf
|
Provenance
The following attestation bundles were made for sandesh_relay-0.3.2-py3-none-any.whl:
Publisher:
publish-pypi.yml on anthill-tec/sandesh
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sandesh_relay-0.3.2-py3-none-any.whl -
Subject digest:
9a4fa9e668e7f33c270a1945a3ac2ae892bb5d18851269548065a0562de45abd - Sigstore transparency entry: 1828148339
- Sigstore integration time:
-
Permalink:
anthill-tec/sandesh@446e95fc9e69a6ec69915b89cb451a3770e58a39 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/anthill-tec
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@446e95fc9e69a6ec69915b89cb451a3770e58a39 -
Trigger Event:
release
-
Statement type: