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 +21 -0
- statebus-0.1.1/PKG-INFO +214 -0
- statebus-0.1.1/README.md +194 -0
- statebus-0.1.1/pyproject.toml +43 -0
- statebus-0.1.1/setup.cfg +4 -0
- statebus-0.1.1/src/statebus/__init__.py +44 -0
- statebus-0.1.1/src/statebus/bus.py +504 -0
- statebus-0.1.1/src/statebus/cli.py +305 -0
- statebus-0.1.1/src/statebus/errors.py +172 -0
- statebus-0.1.1/src/statebus/mcp_server.py +354 -0
- statebus-0.1.1/src/statebus/rest_server.py +312 -0
- statebus-0.1.1/src/statebus/store.py +610 -0
- statebus-0.1.1/src/statebus/validation.py +184 -0
- statebus-0.1.1/src/statebus.egg-info/PKG-INFO +214 -0
- statebus-0.1.1/src/statebus.egg-info/SOURCES.txt +24 -0
- statebus-0.1.1/src/statebus.egg-info/dependency_links.txt +1 -0
- statebus-0.1.1/src/statebus.egg-info/entry_points.txt +4 -0
- statebus-0.1.1/src/statebus.egg-info/requires.txt +1 -0
- statebus-0.1.1/src/statebus.egg-info/top_level.txt +1 -0
- statebus-0.1.1/tests/test_adapters.py +280 -0
- statebus-0.1.1/tests/test_bus.py +246 -0
- statebus-0.1.1/tests/test_conflicts.py +100 -0
- statebus-0.1.1/tests/test_final_audit.py +135 -0
- statebus-0.1.1/tests/test_schemas.py +117 -0
- statebus-0.1.1/tests/test_scrutiny.py +185 -0
- statebus-0.1.1/tests/test_store.py +68 -0
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.
|
statebus-0.1.1/PKG-INFO
ADDED
|
@@ -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.
|
statebus-0.1.1/README.md
ADDED
|
@@ -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"]
|
statebus-0.1.1/setup.cfg
ADDED
|
@@ -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
|
+
]
|