statebus 0.1.1__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.
statebus-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Calas Automations Inc.
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,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: statebus
3
+ Version: 0.1.1
4
+ Summary: Typed, versioned state for multi-agent workflows: schema-validated slots, handoff tracing, replay.
5
+ Author: Calas Automations Inc.
6
+ License: MIT
7
+ Project-URL: Homepage, https://calasautomations.com
8
+ Project-URL: Repository, https://github.com/calasdan-sketch/statebus
9
+ Keywords: agents,multi-agent,state,workflow,mcp,handoff
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: jsonschema>=4.18
19
+ Dynamic: license-file
20
+
21
+ # statebus
22
+
23
+ **Typed, versioned state for multi-agent workflows.**
24
+
25
+ Every multi-agent team rebuilds the same three things: shared state, schema
26
+ validation between steps, and handoff tracing. `statebus` is that layer,
27
+ framework-agnostic: agents read/write named **slots**, the bus validates every
28
+ write against a versioned JSON Schema, keeps full version history, and traces
29
+ every handoff.
30
+
31
+ > "Agent B got v3 of `customer_record` (schema v2.1) from Agent A at 14:03."
32
+
33
+ Replay any handoff. Library-first, with MCP tools and a REST adapter for
34
+ non-Python agents.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install statebus # PyPI: statebus (free, unreleased — v0.1.0)
40
+ ```
41
+
42
+ npm (for the adapters' JS clients): `@decnutz/statebus` — the unscoped
43
+ `statebus` name is taken on npm by an unrelated package.
44
+
45
+ Requires Python 3.10+ and `jsonschema`.
46
+
47
+ ## Quick start
48
+
49
+ ```python
50
+ from statebus import StateBus, ConflictError
51
+
52
+ bus = StateBus("team.db") # durable SQLite; StateBus() is in-memory
53
+
54
+ bus.register_slot(
55
+ "customer_record",
56
+ schema={
57
+ "type": "object",
58
+ "properties": {
59
+ "name": {"type": "string"},
60
+ "email": {"type": "string"},
61
+ "tier": {"type": "string", "enum": ["free", "pro"]},
62
+ },
63
+ "required": ["name", "email"],
64
+ },
65
+ schema_version="2.1",
66
+ )
67
+
68
+ # Agent A writes; the value is validated, versioned, traced.
69
+ receipt = bus.write("customer_record", {"name": "Ada", "email": "a@x.com", "tier": "pro"},
70
+ agent="agent_a")
71
+ print(receipt.version) # 1
72
+
73
+ # Agent B reads — plain JSON, no wrapper.
74
+ customer = bus.read("customer_record")
75
+
76
+ # What did Agent B actually see? Replay the exact handoff.
77
+ old = bus.replay("customer_record", version=1)
78
+
79
+ # Who wrote what, when:
80
+ for e in bus.trace(slot="customer_record"):
81
+ print(e.seq, e.ts, e.kind, e.agent, e.version)
82
+ ```
83
+
84
+ ### Safe concurrent writes
85
+
86
+ Default is last-write-wins with **full history preserved** (nothing is ever
87
+ lost — both versions stay replayable). For contested slots, use optimistic
88
+ concurrency:
89
+
90
+ ```python
91
+ try:
92
+ bus.write("customer_record", new_value, agent="agent_b", expected_version=3)
93
+ except ConflictError as e:
94
+ # e.current_version / e.current_agent tell you who moved it — re-read and retry.
95
+ ...
96
+ ```
97
+
98
+ ### Schema evolution without silent breakage
99
+
100
+ ```python
101
+ # Compatible change (new optional field): registered, becomes active.
102
+ bus.register_schema_version("customer_record", new_schema, "2.2")
103
+
104
+ # Breaking change (new required field): rejected unless you mean it.
105
+ bus.register_schema_version("customer_record", breaking_schema, "3.0",
106
+ allow_breaking=True) # explicit, and traced
107
+ ```
108
+
109
+ Every write stamps the schema version it was validated against, so readers
110
+ always know what shape they're holding.
111
+
112
+ ## CLI
113
+
114
+ ```bash
115
+ statebus --db team.db register customer_record --file schema.json
116
+ statebus --db team.db write customer_record --json '{"name":"Ada","email":"a@x.com"}'
117
+ statebus --db team.db read customer_record
118
+ statebus --db team.db history customer_record --limit 5
119
+ statebus --db team.db replay customer_record 2
120
+ statebus --db team.db trace --slot customer_record --limit 20
121
+ statebus --db team.db watch # follow the trace live
122
+ statebus --db team.db prune # enforce max_versions retention
123
+ statebus --db team.db prune-trace # bound trace storage
124
+ ```
125
+
126
+ ## MCP adapter (for Claude Desktop, agents, etc.)
127
+
128
+ ```bash
129
+ statebus-mcp --db team.db
130
+ ```
131
+
132
+ Speaks MCP over stdio (newline-delimited JSON-RPC): `initialize` →
133
+ `tools/list` → `tools/call`. Nine tools: `statebus_register`,
134
+ `statebus_write`, `statebus_read`, `statebus_inspect`, `statebus_history`,
135
+ `statebus_replay`, `statebus_slots`, `statebus_trace`,
136
+ `statebus_register_schema_version`. Validation and conflict errors are
137
+ returned as tool-level `isError` results with structured detail — the agent
138
+ can repair its own output.
139
+
140
+ ## REST adapter
141
+
142
+ ```bash
143
+ statebus-serve --db team.db --port 8765 --token SECRET
144
+ ```
145
+
146
+ JSON over HTTP for non-Python agents: `GET/POST /slots`,
147
+ `POST /slots/{name}/write`, `GET /slots/{name}` (envelope),
148
+ `GET /slots/{name}/value` (plain value), `GET /slots/{name}/history`,
149
+ `GET /slots/{name}/versions/{v}`, `GET /trace`, … Status codes are
150
+ meaningful: `422` validation (with structured issues), `409` conflict or
151
+ duplicate, `404` unknown slot/version, `413` oversized, `400` bad argument.
152
+
153
+ ## Design decisions
154
+
155
+ - **JSON Schema only** (Draft 2020-12) — agents speak JSON, not Python classes;
156
+ the schema registry stays portable across languages.
157
+ - **Append-only versions** — history is never mutated; `archive`, never delete.
158
+ - **No auto-merge** — the bus never invents data. If two agents disagree, a
159
+ reducer agent merges explicitly, in the trace.
160
+ - **Explicit schemas only** — no inference from first write (that's the "small
161
+ formatting differences" bug factory).
162
+ - **Genesis pinning** — pruning keeps version 1 plus the newest N.
163
+ - **Reads traced only on demand** (`trace_reads`) — polling agents would
164
+ otherwise flood the trace.
165
+
166
+ ## Limitations (honest)
167
+
168
+ 1. **Single-host.** Durability is SQLite+WAL: crash-safe and serialized across
169
+ processes on one machine. Multi-host deployments should run one bus behind
170
+ `statebus-serve` — there is no consensus, replication, or HA.
171
+ 2. **Compat checker is heuristic, not a proof.** It catches the common
172
+ breakages (added `required` fields, narrowed types/enums, tightened bounds,
173
+ removed properties under closed schemas). Exotic constructs
174
+ (`allOf`/`oneOf`/`not`, conditionals, patternProperties) are skipped
175
+ conservatively — review those schema changes by hand.
176
+ 3. **Hand-rolled MCP server.** No `mcp` SDK dependency keeps installs light,
177
+ but the stdio implementation covers the protocol core
178
+ (`initialize`/`tools/list`/`tools/call`/`ping`) and could drift from future
179
+ MCP spec revisions.
180
+ 4. **No pub/sub.** Agents poll `trace(since=last_seq)` or use `statebus watch`.
181
+ Webhooks are a future addition.
182
+ 5. **No encryption at rest.** The SQLite file holds plaintext values; rely on
183
+ filesystem permissions. Values are capped (default 1 MiB per write) and
184
+ `NaN`/`Infinity` are rejected to keep the JSON valid for every language.
185
+ 6. **Trace retention is manual.** `prune()` bounds versions; call
186
+ `prune_trace()` (or schedule it) to bound the trace table on long-running
187
+ buses.
188
+ 7. **In-process trust boundary.** A caller with a reference to the `StateBus`
189
+ object can reach `bus._store`; the guarantee "every write is validated"
190
+ holds for all public APIs (library, CLI, MCP, REST), not against deliberate
191
+ in-process sabotage.
192
+
193
+ ## API reference (essentials)
194
+
195
+ | Call | Returns |
196
+ |---|---|
197
+ | `register_slot(name, schema, schema_version="1.0", max_versions=100)` | `None` |
198
+ | `register_schema_version(slot, schema, version, allow_breaking=False)` | list of breakages |
199
+ | `write(slot, value, agent=…, expected_version=…, schema_version=…)` | `WriteReceipt(version, schema_version, agent, ts)` |
200
+ | `read(slot, agent=…)` | plain JSON value |
201
+ | `inspect(slot)` | `SlotView` envelope (version, writer, schema, ts, value) |
202
+ | `history(slot, limit=50)` | newest-first `[SlotView]` |
203
+ | `replay(slot, version)` | exact value at that version |
204
+ | `trace(slot=…, agent=…, since=…, kinds=…, limit=100)` | newest-first `[TraceEvent]` |
205
+ | `archive_slot(slot)` / `prune(slot?)` / `prune_trace(keep_last)` / `export_slot(slot)` | |
206
+
207
+ Errors: `ValidationError` (structured `.issues` → `[{path, message, validator,
208
+ expected}]`), `ConflictError`, `BreakingSchemaChangeError`,
209
+ `InvalidSchemaError`, `SlotNotFoundError`, `SlotArchivedError`,
210
+ `ValueTooLargeError`, `NotJsonSerializableError`.
211
+
212
+ ## License
213
+
214
+ MIT — Calas Automations Inc.
@@ -0,0 +1,194 @@
1
+ # statebus
2
+
3
+ **Typed, versioned state for multi-agent workflows.**
4
+
5
+ Every multi-agent team rebuilds the same three things: shared state, schema
6
+ validation between steps, and handoff tracing. `statebus` is that layer,
7
+ framework-agnostic: agents read/write named **slots**, the bus validates every
8
+ write against a versioned JSON Schema, keeps full version history, and traces
9
+ every handoff.
10
+
11
+ > "Agent B got v3 of `customer_record` (schema v2.1) from Agent A at 14:03."
12
+
13
+ Replay any handoff. Library-first, with MCP tools and a REST adapter for
14
+ non-Python agents.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install statebus # PyPI: statebus (free, unreleased — v0.1.0)
20
+ ```
21
+
22
+ npm (for the adapters' JS clients): `@decnutz/statebus` — the unscoped
23
+ `statebus` name is taken on npm by an unrelated package.
24
+
25
+ Requires Python 3.10+ and `jsonschema`.
26
+
27
+ ## Quick start
28
+
29
+ ```python
30
+ from statebus import StateBus, ConflictError
31
+
32
+ bus = StateBus("team.db") # durable SQLite; StateBus() is in-memory
33
+
34
+ bus.register_slot(
35
+ "customer_record",
36
+ schema={
37
+ "type": "object",
38
+ "properties": {
39
+ "name": {"type": "string"},
40
+ "email": {"type": "string"},
41
+ "tier": {"type": "string", "enum": ["free", "pro"]},
42
+ },
43
+ "required": ["name", "email"],
44
+ },
45
+ schema_version="2.1",
46
+ )
47
+
48
+ # Agent A writes; the value is validated, versioned, traced.
49
+ receipt = bus.write("customer_record", {"name": "Ada", "email": "a@x.com", "tier": "pro"},
50
+ agent="agent_a")
51
+ print(receipt.version) # 1
52
+
53
+ # Agent B reads — plain JSON, no wrapper.
54
+ customer = bus.read("customer_record")
55
+
56
+ # What did Agent B actually see? Replay the exact handoff.
57
+ old = bus.replay("customer_record", version=1)
58
+
59
+ # Who wrote what, when:
60
+ for e in bus.trace(slot="customer_record"):
61
+ print(e.seq, e.ts, e.kind, e.agent, e.version)
62
+ ```
63
+
64
+ ### Safe concurrent writes
65
+
66
+ Default is last-write-wins with **full history preserved** (nothing is ever
67
+ lost — both versions stay replayable). For contested slots, use optimistic
68
+ concurrency:
69
+
70
+ ```python
71
+ try:
72
+ bus.write("customer_record", new_value, agent="agent_b", expected_version=3)
73
+ except ConflictError as e:
74
+ # e.current_version / e.current_agent tell you who moved it — re-read and retry.
75
+ ...
76
+ ```
77
+
78
+ ### Schema evolution without silent breakage
79
+
80
+ ```python
81
+ # Compatible change (new optional field): registered, becomes active.
82
+ bus.register_schema_version("customer_record", new_schema, "2.2")
83
+
84
+ # Breaking change (new required field): rejected unless you mean it.
85
+ bus.register_schema_version("customer_record", breaking_schema, "3.0",
86
+ allow_breaking=True) # explicit, and traced
87
+ ```
88
+
89
+ Every write stamps the schema version it was validated against, so readers
90
+ always know what shape they're holding.
91
+
92
+ ## CLI
93
+
94
+ ```bash
95
+ statebus --db team.db register customer_record --file schema.json
96
+ statebus --db team.db write customer_record --json '{"name":"Ada","email":"a@x.com"}'
97
+ statebus --db team.db read customer_record
98
+ statebus --db team.db history customer_record --limit 5
99
+ statebus --db team.db replay customer_record 2
100
+ statebus --db team.db trace --slot customer_record --limit 20
101
+ statebus --db team.db watch # follow the trace live
102
+ statebus --db team.db prune # enforce max_versions retention
103
+ statebus --db team.db prune-trace # bound trace storage
104
+ ```
105
+
106
+ ## MCP adapter (for Claude Desktop, agents, etc.)
107
+
108
+ ```bash
109
+ statebus-mcp --db team.db
110
+ ```
111
+
112
+ Speaks MCP over stdio (newline-delimited JSON-RPC): `initialize` →
113
+ `tools/list` → `tools/call`. Nine tools: `statebus_register`,
114
+ `statebus_write`, `statebus_read`, `statebus_inspect`, `statebus_history`,
115
+ `statebus_replay`, `statebus_slots`, `statebus_trace`,
116
+ `statebus_register_schema_version`. Validation and conflict errors are
117
+ returned as tool-level `isError` results with structured detail — the agent
118
+ can repair its own output.
119
+
120
+ ## REST adapter
121
+
122
+ ```bash
123
+ statebus-serve --db team.db --port 8765 --token SECRET
124
+ ```
125
+
126
+ JSON over HTTP for non-Python agents: `GET/POST /slots`,
127
+ `POST /slots/{name}/write`, `GET /slots/{name}` (envelope),
128
+ `GET /slots/{name}/value` (plain value), `GET /slots/{name}/history`,
129
+ `GET /slots/{name}/versions/{v}`, `GET /trace`, … Status codes are
130
+ meaningful: `422` validation (with structured issues), `409` conflict or
131
+ duplicate, `404` unknown slot/version, `413` oversized, `400` bad argument.
132
+
133
+ ## Design decisions
134
+
135
+ - **JSON Schema only** (Draft 2020-12) — agents speak JSON, not Python classes;
136
+ the schema registry stays portable across languages.
137
+ - **Append-only versions** — history is never mutated; `archive`, never delete.
138
+ - **No auto-merge** — the bus never invents data. If two agents disagree, a
139
+ reducer agent merges explicitly, in the trace.
140
+ - **Explicit schemas only** — no inference from first write (that's the "small
141
+ formatting differences" bug factory).
142
+ - **Genesis pinning** — pruning keeps version 1 plus the newest N.
143
+ - **Reads traced only on demand** (`trace_reads`) — polling agents would
144
+ otherwise flood the trace.
145
+
146
+ ## Limitations (honest)
147
+
148
+ 1. **Single-host.** Durability is SQLite+WAL: crash-safe and serialized across
149
+ processes on one machine. Multi-host deployments should run one bus behind
150
+ `statebus-serve` — there is no consensus, replication, or HA.
151
+ 2. **Compat checker is heuristic, not a proof.** It catches the common
152
+ breakages (added `required` fields, narrowed types/enums, tightened bounds,
153
+ removed properties under closed schemas). Exotic constructs
154
+ (`allOf`/`oneOf`/`not`, conditionals, patternProperties) are skipped
155
+ conservatively — review those schema changes by hand.
156
+ 3. **Hand-rolled MCP server.** No `mcp` SDK dependency keeps installs light,
157
+ but the stdio implementation covers the protocol core
158
+ (`initialize`/`tools/list`/`tools/call`/`ping`) and could drift from future
159
+ MCP spec revisions.
160
+ 4. **No pub/sub.** Agents poll `trace(since=last_seq)` or use `statebus watch`.
161
+ Webhooks are a future addition.
162
+ 5. **No encryption at rest.** The SQLite file holds plaintext values; rely on
163
+ filesystem permissions. Values are capped (default 1 MiB per write) and
164
+ `NaN`/`Infinity` are rejected to keep the JSON valid for every language.
165
+ 6. **Trace retention is manual.** `prune()` bounds versions; call
166
+ `prune_trace()` (or schedule it) to bound the trace table on long-running
167
+ buses.
168
+ 7. **In-process trust boundary.** A caller with a reference to the `StateBus`
169
+ object can reach `bus._store`; the guarantee "every write is validated"
170
+ holds for all public APIs (library, CLI, MCP, REST), not against deliberate
171
+ in-process sabotage.
172
+
173
+ ## API reference (essentials)
174
+
175
+ | Call | Returns |
176
+ |---|---|
177
+ | `register_slot(name, schema, schema_version="1.0", max_versions=100)` | `None` |
178
+ | `register_schema_version(slot, schema, version, allow_breaking=False)` | list of breakages |
179
+ | `write(slot, value, agent=…, expected_version=…, schema_version=…)` | `WriteReceipt(version, schema_version, agent, ts)` |
180
+ | `read(slot, agent=…)` | plain JSON value |
181
+ | `inspect(slot)` | `SlotView` envelope (version, writer, schema, ts, value) |
182
+ | `history(slot, limit=50)` | newest-first `[SlotView]` |
183
+ | `replay(slot, version)` | exact value at that version |
184
+ | `trace(slot=…, agent=…, since=…, kinds=…, limit=100)` | newest-first `[TraceEvent]` |
185
+ | `archive_slot(slot)` / `prune(slot?)` / `prune_trace(keep_last)` / `export_slot(slot)` | |
186
+
187
+ Errors: `ValidationError` (structured `.issues` → `[{path, message, validator,
188
+ expected}]`), `ConflictError`, `BreakingSchemaChangeError`,
189
+ `InvalidSchemaError`, `SlotNotFoundError`, `SlotArchivedError`,
190
+ `ValueTooLargeError`, `NotJsonSerializableError`.
191
+
192
+ ## License
193
+
194
+ MIT — Calas Automations Inc.
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "statebus"
7
+ version = "0.1.1"
8
+ description = "Typed, versioned state for multi-agent workflows: schema-validated slots, handoff tracing, replay."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Calas Automations Inc." }]
13
+ keywords = ["agents", "multi-agent", "state", "workflow", "mcp", "handoff"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Libraries :: Python Modules",
20
+ ]
21
+ dependencies = ["jsonschema>=4.18"]
22
+
23
+ [project.urls]
24
+ Homepage = "https://calasautomations.com"
25
+ Repository = "https://github.com/calasdan-sketch/statebus"
26
+
27
+ [project.scripts]
28
+ statebus = "statebus.cli:main"
29
+ statebus-mcp = "statebus.mcp_server:main"
30
+ statebus-serve = "statebus.rest_server:main"
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
34
+
35
+ [tool.ruff]
36
+ target-version = "py310"
37
+ line-length = 100
38
+
39
+ [tool.ruff.lint]
40
+ select = ["E", "F", "W", "I", "UP", "B"]
41
+
42
+ [tool.pytest.ini_options]
43
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,44 @@
1
+ """statebus — typed, versioned state for multi-agent workflows."""
2
+
3
+ from .bus import SlotView, StateBus, TraceEvent, WriteReceipt
4
+ from .errors import (
5
+ BreakingSchemaChangeError,
6
+ ConflictError,
7
+ InvalidArgumentError,
8
+ InvalidSchemaError,
9
+ NotJsonSerializableError,
10
+ SchemaVersionNotFoundError,
11
+ SlotArchivedError,
12
+ SlotExistsError,
13
+ SlotNameError,
14
+ SlotNotFoundError,
15
+ SlotVersionNotFoundError,
16
+ StateBusError,
17
+ ValidationError,
18
+ ValidationIssue,
19
+ ValueTooLargeError,
20
+ )
21
+
22
+ __version__ = "0.1.1"
23
+
24
+ __all__ = [
25
+ "StateBus",
26
+ "WriteReceipt",
27
+ "SlotView",
28
+ "TraceEvent",
29
+ "StateBusError",
30
+ "SlotNotFoundError",
31
+ "SlotExistsError",
32
+ "SlotArchivedError",
33
+ "SlotNameError",
34
+ "SchemaVersionNotFoundError",
35
+ "SlotVersionNotFoundError",
36
+ "NotJsonSerializableError",
37
+ "ValueTooLargeError",
38
+ "ValidationError",
39
+ "ValidationIssue",
40
+ "ConflictError",
41
+ "BreakingSchemaChangeError",
42
+ "InvalidArgumentError",
43
+ "InvalidSchemaError",
44
+ ]