statebus
Typed, versioned state for multi-agent workflows.
Every multi-agent team rebuilds the same three things: shared state, schema
validation between steps, and handoff tracing. statebus is that layer,
framework-agnostic: agents read/write named slots, the bus validates every
write against a versioned JSON Schema, keeps full version history, and traces
every handoff.
"Agent B got v3 of
customer_record(schema v2.1) from Agent A at 14:03."
Replay any handoff. Library-first, with MCP tools and a REST adapter for non-Python agents.
Install
pip install statebus # PyPI: statebus (free, unreleased — v0.1.0)
npm (for the adapters' JS clients): @decnutz/statebus — the unscoped
statebus name is taken on npm by an unrelated package.
Requires Python 3.10+ and jsonschema.
Quick start
from statebus import StateBus, ConflictError
bus = StateBus("team.db") # durable SQLite; StateBus() is in-memory
bus.register_slot(
"customer_record",
schema={
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"tier": {"type": "string", "enum": ["free", "pro"]},
},
"required": ["name", "email"],
},
schema_version="2.1",
)
# Agent A writes; the value is validated, versioned, traced.
receipt = bus.write("customer_record", {"name": "Ada", "email": "a@x.com", "tier": "pro"},
agent="agent_a")
print(receipt.version) # 1
# Agent B reads — plain JSON, no wrapper.
customer = bus.read("customer_record")
# What did Agent B actually see? Replay the exact handoff.
old = bus.replay("customer_record", version=1)
# Who wrote what, when:
for e in bus.trace(slot="customer_record"):
print(e.seq, e.ts, e.kind, e.agent, e.version)
Safe concurrent writes
Default is last-write-wins with full history preserved (nothing is ever lost — both versions stay replayable). For contested slots, use optimistic concurrency:
try:
bus.write("customer_record", new_value, agent="agent_b", expected_version=3)
except ConflictError as e:
# e.current_version / e.current_agent tell you who moved it — re-read and retry.
...
Schema evolution without silent breakage
# Compatible change (new optional field): registered, becomes active.
bus.register_schema_version("customer_record", new_schema, "2.2")
# Breaking change (new required field): rejected unless you mean it.
bus.register_schema_version("customer_record", breaking_schema, "3.0",
allow_breaking=True) # explicit, and traced
Every write stamps the schema version it was validated against, so readers always know what shape they're holding.
CLI
statebus --db team.db register customer_record --file schema.json
statebus --db team.db write customer_record --json '{"name":"Ada","email":"a@x.com"}'
statebus --db team.db read customer_record
statebus --db team.db history customer_record --limit 5
statebus --db team.db replay customer_record 2
statebus --db team.db trace --slot customer_record --limit 20
statebus --db team.db watch # follow the trace live
statebus --db team.db prune # enforce max_versions retention
statebus --db team.db prune-trace # bound trace storage
MCP adapter (for Claude Desktop, agents, etc.)
statebus-mcp --db team.db
Speaks MCP over stdio (newline-delimited JSON-RPC): initialize →
tools/list → tools/call. Nine tools: statebus_register,
statebus_write, statebus_read, statebus_inspect, statebus_history,
statebus_replay, statebus_slots, statebus_trace,
statebus_register_schema_version. Validation and conflict errors are
returned as tool-level isError results with structured detail — the agent
can repair its own output.
REST adapter
statebus-serve --db team.db --port 8765 --token SECRET
JSON over HTTP for non-Python agents: GET/POST /slots,
POST /slots/{name}/write, GET /slots/{name} (envelope),
GET /slots/{name}/value (plain value), GET /slots/{name}/history,
GET /slots/{name}/versions/{v}, GET /trace, … Status codes are
meaningful: 422 validation (with structured issues), 409 conflict or
duplicate, 404 unknown slot/version, 413 oversized, 400 bad argument.
Design decisions
- JSON Schema only (Draft 2020-12) — agents speak JSON, not Python classes; the schema registry stays portable across languages.
- Append-only versions — history is never mutated;
archive, never delete. - No auto-merge — the bus never invents data. If two agents disagree, a reducer agent merges explicitly, in the trace.
- Explicit schemas only — no inference from first write (that's the "small formatting differences" bug factory).
- Genesis pinning — pruning keeps version 1 plus the newest N.
- Reads traced only on demand (
trace_reads) — polling agents would otherwise flood the trace.
Limitations (honest)
- Single-host. Durability is SQLite+WAL: crash-safe and serialized across
processes on one machine. Multi-host deployments should run one bus behind
statebus-serve— there is no consensus, replication, or HA. - Compat checker is heuristic, not a proof. It catches the common
breakages (added
requiredfields, narrowed types/enums, tightened bounds, removed properties under closed schemas). Exotic constructs (allOf/oneOf/not, conditionals, patternProperties) are skipped conservatively — review those schema changes by hand. - Hand-rolled MCP server. No
mcpSDK dependency keeps installs light, but the stdio implementation covers the protocol core (initialize/tools/list/tools/call/ping) and could drift from future MCP spec revisions. - No pub/sub. Agents poll
trace(since=last_seq)or usestatebus watch. Webhooks are a future addition. - No encryption at rest. The SQLite file holds plaintext values; rely on
filesystem permissions. Values are capped (default 1 MiB per write) and
NaN/Infinityare rejected to keep the JSON valid for every language. - Trace retention is manual.
prune()bounds versions; callprune_trace()(or schedule it) to bound the trace table on long-running buses. - In-process trust boundary. A caller with a reference to the
StateBusobject can reachbus._store; the guarantee "every write is validated" holds for all public APIs (library, CLI, MCP, REST), not against deliberate in-process sabotage.
API reference (essentials)
| Call | Returns |
|---|---|
register_slot(name, schema, schema_version="1.0", max_versions=100) |
None |
register_schema_version(slot, schema, version, allow_breaking=False) |
list of breakages |
write(slot, value, agent=…, expected_version=…, schema_version=…) |
WriteReceipt(version, schema_version, agent, ts) |
read(slot, agent=…) |
plain JSON value |
inspect(slot) |
SlotView envelope (version, writer, schema, ts, value) |
history(slot, limit=50) |
newest-first [SlotView] |
replay(slot, version) |
exact value at that version |
trace(slot=…, agent=…, since=…, kinds=…, limit=100) |
newest-first [TraceEvent] |
archive_slot(slot) / prune(slot?) / prune_trace(keep_last) / export_slot(slot) |
Errors: ValidationError (structured .issues → [{path, message, validator, expected}]), ConflictError, BreakingSchemaChangeError,
InvalidSchemaError, SlotNotFoundError, SlotArchivedError,
ValueTooLargeError, NotJsonSerializableError.
License
MIT — Calas Automations Inc.
Metadata
Release files for statebus 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| statebus-0.1.1.tar.gz | 37.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| statebus-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 66.8 kB
Release files / statebus-0.1.1.tar.gz
| Download URL | statebus-0.1.1.tar.gz |
|---|---|
| Size | 37.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4d9fce176b58b6a28c985fde39e1229cbfedf8009958f3805af48f4e13fb912e
|
|
BLAKE2b-256 checksum How to use checksums |
8e29e8c4090f932eaea87bfa3e3d3d36ba185db0f72f460769d15c062c55fa28
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|
Release files / statebus-0.1.1-py3-none-any.whl
| Download URL | statebus-0.1.1-py3-none-any.whl |
|---|---|
| Size | 29.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
28ccda8608f89908953ac643488ef90dc4bdf9758514119e944e96e5398af1ff
|
|
BLAKE2b-256 checksum How to use checksums |
74f3170d226d02c18947836da1301d5ab9d7d0b36af9848111602c3ae0edc059
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|
Security for statebus 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.