conflux-state 0.8.0__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.
- conflux_state-0.8.0/LICENSE +21 -0
- conflux_state-0.8.0/PKG-INFO +635 -0
- conflux_state-0.8.0/README.md +591 -0
- conflux_state-0.8.0/conflux/__init__.py +87 -0
- conflux_state-0.8.0/conflux/actions.py +87 -0
- conflux_state-0.8.0/conflux/app.py +274 -0
- conflux_state-0.8.0/conflux/client.py +207 -0
- conflux_state-0.8.0/conflux/crdts.py +266 -0
- conflux_state-0.8.0/conflux/framework.py +106 -0
- conflux_state-0.8.0/conflux/integrations/__init__.py +38 -0
- conflux_state-0.8.0/conflux/integrations/autogen.py +319 -0
- conflux_state-0.8.0/conflux/integrations/base.py +148 -0
- conflux_state-0.8.0/conflux/integrations/crewai.py +567 -0
- conflux_state-0.8.0/conflux/integrations/langgraph.py +726 -0
- conflux_state-0.8.0/conflux/net.py +89 -0
- conflux_state-0.8.0/conflux/protocol.py +55 -0
- conflux_state-0.8.0/conflux/server.py +694 -0
- conflux_state-0.8.0/conflux/state.py +104 -0
- conflux_state-0.8.0/conflux/storage.py +192 -0
- conflux_state-0.8.0/conflux/validate.py +315 -0
- conflux_state-0.8.0/conflux/websocket.py +311 -0
- conflux_state-0.8.0/conflux_state.egg-info/PKG-INFO +635 -0
- conflux_state-0.8.0/conflux_state.egg-info/SOURCES.txt +45 -0
- conflux_state-0.8.0/conflux_state.egg-info/dependency_links.txt +1 -0
- conflux_state-0.8.0/conflux_state.egg-info/entry_points.txt +2 -0
- conflux_state-0.8.0/conflux_state.egg-info/requires.txt +23 -0
- conflux_state-0.8.0/conflux_state.egg-info/top_level.txt +1 -0
- conflux_state-0.8.0/pyproject.toml +68 -0
- conflux_state-0.8.0/setup.cfg +4 -0
- conflux_state-0.8.0/tests/test_app.py +160 -0
- conflux_state-0.8.0/tests/test_convergence.py +108 -0
- conflux_state-0.8.0/tests/test_crdts.py +105 -0
- conflux_state-0.8.0/tests/test_demos.py +23 -0
- conflux_state-0.8.0/tests/test_framework.py +39 -0
- conflux_state-0.8.0/tests/test_integrations.py +332 -0
- conflux_state-0.8.0/tests/test_namespace.py +83 -0
- conflux_state-0.8.0/tests/test_ops.py +69 -0
- conflux_state-0.8.0/tests/test_phase4.py +192 -0
- conflux_state-0.8.0/tests/test_probabilistic.py +47 -0
- conflux_state-0.8.0/tests/test_prod_hardening.py +272 -0
- conflux_state-0.8.0/tests/test_prometheus.py +76 -0
- conflux_state-0.8.0/tests/test_replay.py +44 -0
- conflux_state-0.8.0/tests/test_security_review.py +212 -0
- conflux_state-0.8.0/tests/test_server.py +131 -0
- conflux_state-0.8.0/tests/test_spec_conformance.py +220 -0
- conflux_state-0.8.0/tests/test_validate.py +252 -0
- conflux_state-0.8.0/tests/test_websocket.py +204 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lolllll212
|
|
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,635 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: conflux-state
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Deterministic, lock-free CRDT shared state protocol for concurrent AI agents, with drop-in adapters for LangGraph, CrewAI, and AutoGen.
|
|
5
|
+
Author: Conflux Maintainers
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/lolllll212/Conflux
|
|
8
|
+
Project-URL: Repository, https://github.com/lolllll212/Conflux
|
|
9
|
+
Project-URL: Documentation, https://github.com/lolllll212/Conflux#readme
|
|
10
|
+
Project-URL: Issues, https://github.com/lolllll212/Conflux/issues
|
|
11
|
+
Keywords: multi-agent,ai-agents,crdt,distributed-systems,shared-state,langgraph,crewai,autogen,lattice,state-management
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Provides-Extra: crypto
|
|
27
|
+
Requires-Dist: cryptography>=3.4; extra == "crypto"
|
|
28
|
+
Provides-Extra: langgraph
|
|
29
|
+
Requires-Dist: langgraph>=0.2.0; extra == "langgraph"
|
|
30
|
+
Provides-Extra: crewai
|
|
31
|
+
Requires-Dist: crewai>=0.80.0; extra == "crewai"
|
|
32
|
+
Provides-Extra: autogen
|
|
33
|
+
Requires-Dist: autogen-agentchat>=0.4.0; extra == "autogen"
|
|
34
|
+
Requires-Dist: autogen-core>=0.4.0; extra == "autogen"
|
|
35
|
+
Provides-Extra: integrations
|
|
36
|
+
Requires-Dist: langgraph>=0.2.0; extra == "integrations"
|
|
37
|
+
Requires-Dist: crewai>=0.80.0; extra == "integrations"
|
|
38
|
+
Requires-Dist: autogen-agentchat>=0.4.0; extra == "integrations"
|
|
39
|
+
Requires-Dist: autogen-core>=0.4.0; extra == "integrations"
|
|
40
|
+
Provides-Extra: test
|
|
41
|
+
Requires-Dist: pytest; extra == "test"
|
|
42
|
+
Requires-Dist: cryptography>=3.4; extra == "test"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# Conflux
|
|
46
|
+
|
|
47
|
+
[](https://pypi.org/project/conflux-state/)
|
|
48
|
+
[](https://www.python.org/)
|
|
49
|
+
[](LICENSE)
|
|
50
|
+
|
|
51
|
+
A deterministic state protocol for autonomous multi-agent systems. State is not a
|
|
52
|
+
set of rows — it is the join of an evolving stream of signed, replayable actions.
|
|
53
|
+
Agents write concurrently to the same shared state with no table locks, no
|
|
54
|
+
two-phase commit, and no central coordinator; every node that has seen the same
|
|
55
|
+
actions converges to the identical state by construction.
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
Install the zero-dependency core package from PyPI (imported in Python as `conflux`):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install conflux-state
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Optional extras for Ed25519 asymmetric signing and framework integrations:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install "conflux-state[crypto]" # Ed25519 signing via cryptography
|
|
69
|
+
pip install "conflux-state[langgraph]" # LangGraph CheckpointSaver + Store
|
|
70
|
+
pip install "conflux-state[crewai]" # CrewAI Memory StorageBackend + Flow + Tools
|
|
71
|
+
pip install "conflux-state[autogen]" # AutoGen Memory + FunctionTools
|
|
72
|
+
pip install "conflux-state[integrations]" # All framework integrations
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Core use case: shared agent state
|
|
76
|
+
|
|
77
|
+
The headline problem conflux solves is **shared agent state**: a few dozen
|
|
78
|
+
agents of one application reading and mutating the same counters, registers,
|
|
79
|
+
and sets — fleet progress, job counters, leader/slot election, capability
|
|
80
|
+
flags, hand-off pointers — with signed writes, durable replay, and crash
|
|
81
|
+
recovery, on a mesh of commodity TCP/WebSocket nodes. Two adjacent use cases
|
|
82
|
+
ride on the same fabric: **workflow state** (task queues and step status
|
|
83
|
+
through `TaskStore` and deterministic `Workflow`) and a **replicated memory /
|
|
84
|
+
knowledge layer** (an agent's shared facts as register/set/string slots).
|
|
85
|
+
All three are one storage geometry; the product pitches the agent-state head.
|
|
86
|
+
|
|
87
|
+
This is not a blockchain and not a distributed SQL system. It is a replicated
|
|
88
|
+
state fabric built on mathematically convergent data structures (CRDTs / lattice
|
|
89
|
+
theory) — a coordination layer for distributed AI workflows.
|
|
90
|
+
|
|
91
|
+
## Product definition and scope
|
|
92
|
+
|
|
93
|
+
Conflux is a **small-crew deterministic state fabric** for a few dozen agents
|
|
94
|
+
of a single application: signed, replayable, eventually-consistent shared
|
|
95
|
+
agent state with durable journaling and crash recovery. The protocol is
|
|
96
|
+
specified in [`docs/PROTOCOL.md`](docs/PROTOCOL.md); every claim in it maps to
|
|
97
|
+
a test (conformance index at the end of the spec). The signing, identity, and
|
|
98
|
+
storage subsystems are reviewed in [`docs/SECURITY.md`](docs/SECURITY.md),
|
|
99
|
+
which also states the trust boundary and the open findings. Conflux is
|
|
100
|
+
released under the [MIT License](LICENSE).
|
|
101
|
+
|
|
102
|
+
Scope — what it is:
|
|
103
|
+
|
|
104
|
+
- deterministic convergence: same action set ⇒ same `state_hash`, in any order;
|
|
105
|
+
- signed identity: HMAC (zero deps) or Ed25519, plus op/namespace/quota rules;
|
|
106
|
+
- durability: fsynced journal + cumulative snapshots that survive a hard crash;
|
|
107
|
+
- TCP and WebSocket transports, TLS on both, gossip between peer nodes.
|
|
108
|
+
|
|
109
|
+
Explicitly out of scope (backlog, not promised):
|
|
110
|
+
|
|
111
|
+
- linearizable reads, multi-region federation,
|
|
112
|
+
general-scale KV, blockchain-style global ordering.
|
|
113
|
+
|
|
114
|
+
## Problem
|
|
115
|
+
|
|
116
|
+
Classic distributed databases serialize writes: a crew of agents updating the
|
|
117
|
+
same operation counters, queue pointers, capability flags, or hand-off state
|
|
118
|
+
either blocks itself (latency) or deadlocks. Locks scale poorly once agents
|
|
119
|
+
outnumber transactions — and agent crews are exactly that regime: many writers,
|
|
120
|
+
one small shared state.
|
|
121
|
+
|
|
122
|
+
## Approach
|
|
123
|
+
|
|
124
|
+
Conflux treats shared state not as static rows but as the join of an evolving stream
|
|
125
|
+
of probabilistic actions. Every value living in the store is an element of a
|
|
126
|
+
join-semilattice, so any two replicas can merge their states with a single
|
|
127
|
+
commutative, associative, idempotent operation — the lattice join. Because a join is
|
|
128
|
+
order-independent, every replica that has seen the same set of actions converges to
|
|
129
|
+
the identical state, no matter the delivery order.
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
state = join(contribution(action_1), ..., contribution(action_n))
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Core concepts
|
|
136
|
+
|
|
137
|
+
### The lattice
|
|
138
|
+
|
|
139
|
+
`Lattice` is the abstract contract: every CRDT implements `merge(other)` (the join),
|
|
140
|
+
`read()`, and `canonical()` (a deterministic fingerprint used for hashing). The join
|
|
141
|
+
laws are verified directly in `tests/test_crdts.py`:
|
|
142
|
+
|
|
143
|
+
- idempotent: `a ⨆ a = a`
|
|
144
|
+
- commutative: `a ⨆ b = b ⨆ a`
|
|
145
|
+
- associative: `(a ⨆ b) ⨆ c = a ⨆ (b ⨆ c)`
|
|
146
|
+
|
|
147
|
+
### CRDT types
|
|
148
|
+
|
|
149
|
+
| Type | Field | Meaning |
|
|
150
|
+
| --------------- | ------------- | --------------------------------------------------- |
|
|
151
|
+
| `GCounter` | count per key | grow-only counter, merge = elementwise max |
|
|
152
|
+
| `PNCounter` | (p, n) slots | counters with increments and decrements |
|
|
153
|
+
| `LWWRegister` | value, stamp | last-writer-wins register, stamp = (tick, agent) |
|
|
154
|
+
| `GSet` | set | grows only, merge = union |
|
|
155
|
+
| `ORSet` | tags, values | add/remove with tombstones, merge = union |
|
|
156
|
+
| `ORMap` | key -> CRDT | grows-only keys over any of the above |
|
|
157
|
+
|
|
158
|
+
Register values are ordinary JSON-ish values; equal stamps (or equal set tags, an
|
|
159
|
+
invalid protocol usage) are resolved deterministically by canonical order so every
|
|
160
|
+
replica still picks the same winner.
|
|
161
|
+
|
|
162
|
+
### Actions and determinism
|
|
163
|
+
|
|
164
|
+
Agents do not mutate shared rows. They emit immutable `Action`s, each with a globally
|
|
165
|
+
unique `action_id`, an `agent`, a logical `tick`, an operation, and params:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
Action(action_id="act-agent-1-42", agent="agent-1", tick=42,
|
|
169
|
+
op="counter_inc", key="orders", params={"by": 5})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Probabilistic actions carry a `weight` param. Whether an action "fires" is decided by
|
|
173
|
+
`SHA-256(action_id, key)` projected onto a fixed-point 32-bit scale — a pure function
|
|
174
|
+
every replica can compute, so probabilistic inference produces identical state
|
|
175
|
+
everywhere without any coordination.
|
|
176
|
+
|
|
177
|
+
### Convergence protocol
|
|
178
|
+
|
|
179
|
+
- Each agent owns a `Replica` — its local join of the actions it has seen. Its
|
|
180
|
+
journal is append-only and de-duplicated by `action_id`.
|
|
181
|
+
- `reconcile(a, b)`: the two replicas exchange journals and absorb what they have not
|
|
182
|
+
seen. Absorption is exactly the lattice join.
|
|
183
|
+
- `converge_all(replicas)`: full-mesh anti-entropy sweep (for tests and small crews).
|
|
184
|
+
- `converge_by_gossip(replicas, rounds)`: randomized pairwise anti-entropy (for
|
|
185
|
+
large, partial meshes).
|
|
186
|
+
- `Cluster` / `DistributedApp` / `InMemoryTransport`: a thin network abstraction to
|
|
187
|
+
demonstrate the protocol over message passing.
|
|
188
|
+
|
|
189
|
+
Any two replicas that have both absorbed the union of available actions are
|
|
190
|
+
provably identical: identical action sets + identical join = identical state. A
|
|
191
|
+
consensus fingerprint, `state_hash(state)`, is a SHA-256 over the canonical
|
|
192
|
+
serialization.
|
|
193
|
+
|
|
194
|
+
## Quick start
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from conflux import Cluster, InMemoryTransport
|
|
198
|
+
|
|
199
|
+
cluster = Cluster({"agent-a", "agent-b", "agent-c"}, transport=InMemoryTransport())
|
|
200
|
+
cluster.node("agent-a").counter_inc("orders", 5)
|
|
201
|
+
cluster.node("agent-b").counter_inc("orders", 3)
|
|
202
|
+
cluster.node("agent-c").register_set_weighted("risk.appetite", "low", weight=0.6)
|
|
203
|
+
cluster.gossip_round()
|
|
204
|
+
|
|
205
|
+
assert cluster.read("agent-a", "orders") == 8
|
|
206
|
+
assert cluster.read("agent-b", "orders") == 8
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Deterministic replay from a journal:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from conflux import state_hash, replay
|
|
213
|
+
from conflux.protocol import journal_from_json, journal_to_json
|
|
214
|
+
|
|
215
|
+
blob = journal_to_json(cluster.node("agent-a"))
|
|
216
|
+
actions = journal_from_json(blob)
|
|
217
|
+
# fold in any order, every time, always the same state:
|
|
218
|
+
assert state_hash(replay(actions).state) == state_hash(cluster.node("agent-b").state)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Protocol hardening: versioning, signatures, authority
|
|
222
|
+
|
|
223
|
+
Actions travel as envelopes that any node can verify before they touch state.
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"action": { "action_id": "act-a-1", "agent": "a", "tick": 1, "op": "counter_inc",
|
|
228
|
+
"key": "orders", "params": {"by": 3}, "version": 1 },
|
|
229
|
+
"signed_by": "a",
|
|
230
|
+
"signature": "<signature over canonical action bytes>",
|
|
231
|
+
"alg": "hmac-sha256"
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
- **Versioning.** Every action carries `version`; the validator rejects unknown
|
|
236
|
+
versions, so protocol changes are explicit (current: `1`).
|
|
237
|
+
- **Signatures.** `sign_action(action, secret)` produces an HMAC-SHA256 over the
|
|
238
|
+
canonical (key-sorted) JSON of the action (zero dependencies). For asymmetric
|
|
239
|
+
identity, pass `alg="ed25519"` with a raw private key — `generate_ed25519_keypair()`
|
|
240
|
+
returns `(private, public)`, `verify_action(envelope, public, "ed25519")` checks it.
|
|
241
|
+
Ed25519 requires the optional `cryptography` package; HMAC needs nothing.
|
|
242
|
+
- **Identity + authority.** `AgentRegistry` maps agent ids to a secret (HMAC) *or* a
|
|
243
|
+
public key (Ed25519), plus allowed op sets and per-agent write quotas.
|
|
244
|
+
`validate_envelope` checks signer identity, signature, authorization, and quota
|
|
245
|
+
before a node ever merges, and refuses envelopes whose algorithm does not match
|
|
246
|
+
the agent's registered key.
|
|
247
|
+
- **Schema.** `validate_action` enforces op-specific payload rules (counter `by`,
|
|
248
|
+
JSON-safe register/set values, non-empty tags, `weight` in `[0, 1]`).
|
|
249
|
+
|
|
250
|
+
A server equipped with a registry refuses unverified, unauthorized, or
|
|
251
|
+
quota-exhausted submits (`{"type": "error", "reason": "..."}`); a server without
|
|
252
|
+
one still schema-validates but runs in trust-less dev mode. Note that the
|
|
253
|
+
registry guards the agent `submit` path only — the peer gossip path is trusted
|
|
254
|
+
at the network layer, and every node's listen port must therefore be treated
|
|
255
|
+
as a peer-trusted boundary. [`docs/SECURITY.md`](docs/SECURITY.md) walks
|
|
256
|
+
through what the signing, identity, and storage layers do and do not
|
|
257
|
+
guarantee, with each property mapped to the code and tests that enforce it.
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from conflux import AgentRegistry, Client, Server
|
|
261
|
+
|
|
262
|
+
registry = AgentRegistry()
|
|
263
|
+
registry.register("driver", "s3cr3t", ops={"counter_inc", "lww_set"}, quota=1000)
|
|
264
|
+
server = Server("n1", ("127.0.0.1", 7001), registry=registry)
|
|
265
|
+
server.start()
|
|
266
|
+
|
|
267
|
+
client = Client("127.0.0.1", 7001, agent_id="driver", secret="s3cr3t")
|
|
268
|
+
client.counter_inc("orders", 5) # signed, authorized, quota-counted
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Running the tests
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
python -m pytest
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
The suite covers join laws, threaded lock-free convergence, merge-order
|
|
278
|
+
determinism, probabilistic determinism, journal round-tripping, the
|
|
279
|
+
network-framework gossip path, the networked backend (client round-trip,
|
|
280
|
+
two-node gossip convergence, restart persistence), signed-envelope authority,
|
|
281
|
+
namespaces, the WebSocket transport (RFC6455 framing, WS node convergence,
|
|
282
|
+
discovery, batching, backoff), the application layer (events, watchers,
|
|
283
|
+
task state, deterministic workflows), and the operational surface (TLS framing
|
|
284
|
+
over tcp + ws, signed/verified journals, membership + partition heal, and
|
|
285
|
+
cumulative snapshot compaction).
|
|
286
|
+
|
|
287
|
+
## Application layer: workflows, tasks, events, watchers
|
|
288
|
+
|
|
289
|
+
Everything sits on the same replicated fabric, so application state converges
|
|
290
|
+
and replays exactly like raw CRDT state.
|
|
291
|
+
|
|
292
|
+
- **`EventBus`** — subscribe to the action stream by op, exact key, or key
|
|
293
|
+
prefix; handlers get `(action, origin)` where origin is `local` (this node's
|
|
294
|
+
own emit) or `absorb` (received from a peer). `Server.subscribe(...)` wires it
|
|
295
|
+
onto the live wire path.
|
|
296
|
+
- **`Watcher`** — value-level snapshots: `watch(key, handler)` fires when a
|
|
297
|
+
key's effective value changes, with the previous value.
|
|
298
|
+
- **`TaskStore`** — replicated task execution state under `tasks/<id>`
|
|
299
|
+
(created → running → step → done/failed), each transition a signed,
|
|
300
|
+
deterministic `lww_set`.
|
|
301
|
+
- **`Workflow` / `WorkflowRunner`** — ordered steps (`counter_inc`, `lww_set`,
|
|
302
|
+
`orset_add`, `orset_remove`, `noop`) against keys, with optional
|
|
303
|
+
`weight`-gated steps decided by the same deterministic hash every replica can
|
|
304
|
+
reproduce. Identical inputs always emit identical action logs.
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
from conflux import Server, Workflow, WorkflowRunner, WorkflowStep, TaskStore
|
|
308
|
+
|
|
309
|
+
server = Server("n1", ("127.0.0.1", 7001))
|
|
310
|
+
server.start()
|
|
311
|
+
server.subscribe(lambda action, origin: print("event:", origin, action.op),
|
|
312
|
+
op="counter_inc")
|
|
313
|
+
server.watch("wf/shipments", lambda value, key, old: print("watched:", value))
|
|
314
|
+
|
|
315
|
+
workflow = Workflow("fulfill", [
|
|
316
|
+
WorkflowStep("reserve", "counter_inc", key="wf/capacity", params={"by": 1}),
|
|
317
|
+
WorkflowStep("ship", "orset_add", key="wf/shipments", params={"value": "carrier-17"}),
|
|
318
|
+
])
|
|
319
|
+
task = WorkflowRunner(server.replica, workflow).run("fulfill-1", "fulfill",
|
|
320
|
+
inputs={"sku": "A-1"})
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Framework integrations: LangGraph, CrewAI, AutoGen
|
|
324
|
+
|
|
325
|
+
`conflux.integrations` ships drop-in adapters that let popular orchestration
|
|
326
|
+
frameworks use Conflux as a **lock-free, convergent shared state backend**.
|
|
327
|
+
Every adapter accepts any Conflux runtime object (`Replica`, `Cluster` node,
|
|
328
|
+
`Server`, or networked/signed `Client`), works out of the box with zero
|
|
329
|
+
third-party dependencies, and automatically inherits each framework's native
|
|
330
|
+
base classes when installed.
|
|
331
|
+
|
|
332
|
+
### LangGraph (`ConfluxCheckpointSaver` + `ConfluxStore`)
|
|
333
|
+
|
|
334
|
+
Persist, fork, and resume `StateGraph` checkpoints across separate Conflux
|
|
335
|
+
nodes, and share cross-thread items, CRDT counters, and sets without DB locks:
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
from langgraph.graph import StateGraph
|
|
339
|
+
from conflux import Client
|
|
340
|
+
from conflux.integrations.langgraph import ConfluxCheckpointSaver, ConfluxStore
|
|
341
|
+
|
|
342
|
+
client = Client("127.0.0.1", 7001, agent_id="planner", secret="s3cr3t")
|
|
343
|
+
checkpointer = ConfluxCheckpointSaver(client)
|
|
344
|
+
store = ConfluxStore(client)
|
|
345
|
+
|
|
346
|
+
graph = builder.compile(checkpointer=checkpointer, store=store)
|
|
347
|
+
graph.invoke({"topic": "CRDTs"}, config={"configurable": {"thread_id": "run-42"}})
|
|
348
|
+
|
|
349
|
+
# Concurrent CRDT counters & sets across parallel LangGraph workers:
|
|
350
|
+
store.counter_inc(("metrics", "research"), "queries_executed", by=1)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Runnable example: [`examples/langgraph_shared_state.py`](examples/langgraph_shared_state.py)
|
|
354
|
+
|
|
355
|
+
### CrewAI (`ConfluxCrewStorage`, `ConfluxFlowPersistence`, `create_crewai_tools`)
|
|
356
|
+
|
|
357
|
+
Plug Conflux into CrewAI's unified `Memory` (`StorageBackend` protocol),
|
|
358
|
+
checkpoint `@persist` Flows across nodes, and give agents direct CRDT tools:
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
from crewai import Agent, Crew
|
|
362
|
+
from crewai.memory import Memory
|
|
363
|
+
from conflux import Client
|
|
364
|
+
from conflux.integrations.crewai import (
|
|
365
|
+
ConfluxCrewStorage,
|
|
366
|
+
ConfluxFlowPersistence,
|
|
367
|
+
create_crewai_tools,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
client = Client("127.0.0.1", 7001, agent_id="researcher", secret="s3cr3t")
|
|
371
|
+
storage = ConfluxCrewStorage(client)
|
|
372
|
+
tools = create_crewai_tools(client, namespace="crew.")
|
|
373
|
+
|
|
374
|
+
researcher = Agent(role="Researcher", goal="...", backstory="...", tools=tools)
|
|
375
|
+
crew = Crew(agents=[researcher], tasks=[...], memory=Memory(storage=storage))
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Runnable example: [`examples/crewai_shared_memory.py`](examples/crewai_shared_memory.py)
|
|
379
|
+
|
|
380
|
+
### AutoGen (`ConfluxAutoGenMemory` + `create_autogen_tools`)
|
|
381
|
+
|
|
382
|
+
Equip AutoGen v0.4+/v0.7+ `AssistantAgent` teams with a replicated
|
|
383
|
+
`autogen_core.memory.Memory` store that injects converged facts + live CRDT
|
|
384
|
+
keys into `ChatCompletionContext`, alongside native `FunctionTool`s:
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
from autogen_agentchat.agents import AssistantAgent
|
|
388
|
+
from conflux import Client
|
|
389
|
+
from conflux.integrations.autogen import ConfluxAutoGenMemory, create_autogen_tools
|
|
390
|
+
|
|
391
|
+
client = Client("127.0.0.1", 7001, agent_id="coder", secret="s3cr3t")
|
|
392
|
+
shared_memory = ConfluxAutoGenMemory(client, state_watch_prefix="team.")
|
|
393
|
+
shared_tools = create_autogen_tools(client, namespace="team.")
|
|
394
|
+
|
|
395
|
+
agent = AssistantAgent(
|
|
396
|
+
name="coder",
|
|
397
|
+
model_client=model_client,
|
|
398
|
+
memory=[shared_memory],
|
|
399
|
+
tools=shared_tools,
|
|
400
|
+
)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Runnable example: [`examples/autogen_shared_state.py`](examples/autogen_shared_state.py)
|
|
404
|
+
|
|
405
|
+
## Publishing to PyPI (Trusted Publishing / OIDC)
|
|
406
|
+
|
|
407
|
+
Conflux is packaged as **`conflux-state`** on PyPI (imported as `conflux`) and
|
|
408
|
+
ships ready-to-use GitHub Actions workflows in
|
|
409
|
+
[`docs/github-workflows/publish.yml`](docs/github-workflows/publish.yml) and
|
|
410
|
+
[`docs/github-workflows/ci.yml`](docs/github-workflows/ci.yml) using PyPI
|
|
411
|
+
Trusted Publishing (OIDC — no long-lived API tokens required):
|
|
412
|
+
|
|
413
|
+
1. Move or copy the workflows into `.github/workflows/`:
|
|
414
|
+
```bash
|
|
415
|
+
mkdir -p .github/workflows && cp docs/github-workflows/*.yml .github/workflows/
|
|
416
|
+
```
|
|
417
|
+
2. On [pypi.org/manage/account/publishing/](https://pypi.org/manage/account/publishing/),
|
|
418
|
+
add a **Pending Publisher**:
|
|
419
|
+
- **PyPI Project Name**: `conflux-state`
|
|
420
|
+
- **Owner**: `lolllll212`
|
|
421
|
+
- **Repository name**: `Conflux`
|
|
422
|
+
- **Workflow name**: `publish.yml`
|
|
423
|
+
- **Environment name**: `pypi`
|
|
424
|
+
3. Tag and push a release (or publish a GitHub Release):
|
|
425
|
+
```bash
|
|
426
|
+
git tag v0.8.0 && git push origin v0.8.0
|
|
427
|
+
```
|
|
428
|
+
GitHub Actions runs the full test suite, verifies the wheel + sdist via
|
|
429
|
+
`twine check --strict`, and publishes `conflux-state` to PyPI automatically.
|
|
430
|
+
|
|
431
|
+
## Operations: metrics, admin stats, benchmarks
|
|
432
|
+
|
|
433
|
+
Every server exposes a live `metrics()` dict — absorbed/submitted actions,
|
|
434
|
+
journal length, links, outbound dials, discovered peers, known members, uptime,
|
|
435
|
+
current `state_hash` — and answers the wire query `{"type": "admin", "what": "stats"}`
|
|
436
|
+
with the same payload, so monitoring can poll nodes without a driver agent.
|
|
437
|
+
|
|
438
|
+
For Prometheus, the same data is rendered in text exposition format — no
|
|
439
|
+
dependencies — and served three ways: `Server.prometheus()`, the wire query
|
|
440
|
+
`{"type": "admin", "what": "prometheus"}` (returns the body over the framed
|
|
441
|
+
connection), and an embedded stdlib HTTP listener:
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
python -m conflux.server --id alpha --listen 127.0.0.1:7001 --metrics-http
|
|
445
|
+
# -> metrics http on http://127.0.0.1:<ephemeral>/metrics
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
```python
|
|
449
|
+
server = Server("alpha", ("127.0.0.1", 7001)).start()
|
|
450
|
+
server.start_metrics_http("0.0.0.0", 9101) # scrape http://<node>:9101/metrics
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Counters (`conflux_actions_absorbed_total`, `..._submitted_total`) and gauges
|
|
454
|
+
(links, outbound, peers, discovered, members, journal_actions, uptime,
|
|
455
|
+
state-hash info label) are labeled with `node_id`:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
python benchmarks/bench.py # replay, cluster, tcp + ws round-trips
|
|
459
|
+
python benchmarks/bench.py --quick # small sizes for CI
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Example (previous run, this machine): ~168k actions/s journal replay,
|
|
463
|
+
~38k actions/s full-mesh cluster convergence, ~0.13 ms single-agent
|
|
464
|
+
submit→ack round-trip over TCP and ~0.17 ms over WebSocket.
|
|
465
|
+
|
|
466
|
+
The real agent-state app, in one file: `examples/agent_crew_demo.py` — six
|
|
467
|
+
signed agents share state (`crew.work_done`, check-in set, leader slot) across
|
|
468
|
+
a 3-node mesh, then put the runtime through the failure model it was hardened
|
|
469
|
+
for: a hard crash, continued writes through the partition, restart from the
|
|
470
|
+
signed snapshot + journal tail, keyless reload refused, and an out-of-namespace
|
|
471
|
+
write refused with no state mutation. `examples/durable_crew_demo.py` shows the
|
|
472
|
+
same crash-recovery cycle on a single agent. Supporting demos:
|
|
473
|
+
`examples/workflow_demo.py` (tasks/events/watchers over a cluster) and
|
|
474
|
+
`examples/deploy_multi_node.py` (signed Ed25519 crews over TCP and WS meshes).
|
|
475
|
+
|
|
476
|
+
## Backend: networked, persistent nodes
|
|
477
|
+
|
|
478
|
+
The reference implementation ships with a multi-node backend so agents in
|
|
479
|
+
different processes (or different machines) can converge. Two transports are
|
|
480
|
+
built in: the default framed TCP and `--transport ws`, a dependency-free
|
|
481
|
+
RFC6455 WebSocket transport (`conflux.websocket`). Set `transport="ws"` on the
|
|
482
|
+
`Server`/`Client` to use it.
|
|
483
|
+
|
|
484
|
+
### Run a node
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
python -m conflux.server --id alpha --listen 127.0.0.1:7001 --data ./data/alpha
|
|
488
|
+
python -m conflux.server --id beta --listen 127.0.0.1:7002 --peers 127.0.0.1:7001 --data ./data/beta
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Use `--peers host:port,host:port` to list neighbors. Gossip links are
|
|
492
|
+
bidirectional once established, and each node replays its persisted journal on
|
|
493
|
+
startup.
|
|
494
|
+
|
|
495
|
+
### TLS framing (tls:// and wss://)
|
|
496
|
+
|
|
497
|
+
Pass a PEM certificate to encrypt every socket with the stdlib `ssl` module —
|
|
498
|
+
both the framed TCP transport and the RFC6455 WebSocket transport, for agent
|
|
499
|
+
clients and peer-to-peer gossip alike:
|
|
500
|
+
|
|
501
|
+
```bash
|
|
502
|
+
python -m conflux.server --id alpha --listen 127.0.0.1:7001 \
|
|
503
|
+
--cert ./cert.pem --key ./key.pem --data ./data/alpha
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
server = Server("alpha", ("127.0.0.1", 7001), tls=("cert.pem", "key.pem")).start()
|
|
508
|
+
client = Client("127.0.0.1", 7001, tls=("cert.pem", "key.pem", "cert.pem")) # ca = 3rd element
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Omitting the CA in the client's `tls=` tuple disables peer verification (self-signed
|
|
512
|
+
clusters); `tls=True` uses the system CAs, and `tls="ca.pem"` verifies against that CA.
|
|
513
|
+
|
|
514
|
+
### Membership and partitions
|
|
515
|
+
|
|
516
|
+
Peers announce their node id, address, peer list, and member view on
|
|
517
|
+
handshake; every server maintains a `members` view (see `metrics()["members"]`
|
|
518
|
+
and `members_view()`). When a neighbor dies, the receive loop drops the link
|
|
519
|
+
and the gossip loop re-dials it with exponential backoff. Links that go silent
|
|
520
|
+
without closing (a hung or partitioned peer) are liveness-checked and dropped
|
|
521
|
+
after `--stale-after` seconds (default 3× `gossip_interval`), then re-dialed —
|
|
522
|
+
so a healed partition re-converges automatically once the peer is reachable
|
|
523
|
+
again (see `tests/test_phase4.py::test_partition_reconnect`).
|
|
524
|
+
|
|
525
|
+
### Signed, compacted journals
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
python -m conflux.server --id alpha --listen 127.0.0.1:7001 \
|
|
529
|
+
--cert ./cert.pem --key ./key.pem --journal-key "$(openssl rand -hex 16)" --data ./data/alpha
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Every journal line carries `"sig"` — an HMAC-SHA256 over the canonical action
|
|
533
|
+
bytes — and every snapshot carries the same over its `{seq, root}` payload.
|
|
534
|
+
Replay verifies both; a tampered log or snapshot raises instead of silently
|
|
535
|
+
restoring a corrupt state. Snapshots are **cumulative**: each compaction folds
|
|
536
|
+
the current root into the previous generation via the lattice join and emits a
|
|
537
|
+
single merged summary, pruning older generations, so a crash mid-history never
|
|
538
|
+
loses committed state.
|
|
539
|
+
|
|
540
|
+
### Write and read from an agent
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
from conflux import Client
|
|
544
|
+
|
|
545
|
+
client = Client("127.0.0.1", 7001, agent_id="agent-42")
|
|
546
|
+
client.counter_inc("bookings.seats", 2)
|
|
547
|
+
client.register_set("flight.eta", "14:05")
|
|
548
|
+
client.set_add("tickets", "TK-100", tag="tk-100")
|
|
549
|
+
print(client.read("bookings.seats")) # 2
|
|
550
|
+
print(client.hash()) # 64-hex SHA-256 of canonical state
|
|
551
|
+
|
|
552
|
+
# Batch multiple mutations in a single atomic round-trip:
|
|
553
|
+
with client.batch() as b:
|
|
554
|
+
b.counter_inc("bookings.seats", 1)
|
|
555
|
+
b.register_set("flight.status", "boarding")
|
|
556
|
+
|
|
557
|
+
client.close()
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Durability
|
|
561
|
+
|
|
562
|
+
Each absorbed action is appended to `data/<node>/journal.jsonl` immediately.
|
|
563
|
+
When the journal passes the snapshot threshold, the current CRDT root is merged
|
|
564
|
+
(lattice join) into the previous generation's root and written atomically —
|
|
565
|
+
both as `snapshot-<seq>.json` (generation) and `snapshot.json` (latest alias) —
|
|
566
|
+
after which the log is truncated and older generations are pruned. A node never
|
|
567
|
+
replays a tail older than what its cumulative snapshot already captures, and a
|
|
568
|
+
crash mid-rotation degrades to the newest untampered generation. Optional
|
|
569
|
+
`--journal-key` HMAC-signs every line and snapshot, and replay refuses
|
|
570
|
+
tampered files. State values travel on the wire as JSON; register/set values
|
|
571
|
+
must be JSON-serializable.
|
|
572
|
+
|
|
573
|
+
## Guarantees and limits
|
|
574
|
+
|
|
575
|
+
Conflux promises, and the test suite verifies:
|
|
576
|
+
|
|
577
|
+
- **Eventually consistent convergence**: every node that has absorbed the union of
|
|
578
|
+
available actions shows identical state (same `state_hash`), regardless of
|
|
579
|
+
delivery order.
|
|
580
|
+
- **No global lock, no blocking writes**: agents write to their own replicas
|
|
581
|
+
concurrently; merging is a join, so writers never contend.
|
|
582
|
+
- **Deterministic merges**: identical action sets always yield identical state,
|
|
583
|
+
including tie-breaks (equal stamps/tags) resolved by canonical order.
|
|
584
|
+
- **Replayability**: any subset of the journal rebuilds exactly the same state.
|
|
585
|
+
- **Durable ack**: a submit is acknowledged only after the accepting node has
|
|
586
|
+
absorbed the actions and fsynced them to its journal; an acked action
|
|
587
|
+
survives that node's process crash (torn-tail recovery).
|
|
588
|
+
|
|
589
|
+
The exact limits — schema bounds, frame size, snapshot/gossip/stale defaults,
|
|
590
|
+
backoff schedule, quota rules, and measured throughput (≈24k actions/s local
|
|
591
|
+
replay, ≈2.8k actions/s across an 8-agent TCP cluster) — are pinned in
|
|
592
|
+
[`docs/PROTOCOL.md` §8](docs/PROTOCOL.md); re-run `benchmarks/bench.py` for
|
|
593
|
+
host-local figures.
|
|
594
|
+
|
|
595
|
+
Honest limits:
|
|
596
|
+
|
|
597
|
+
- **Stale reads**: a read observes the join of everything that node has gossiped
|
|
598
|
+
so far. Convergence is eventual, not linearizable; workflows that need
|
|
599
|
+
strictly ordered reads need an external sequencing channel.
|
|
600
|
+
- **One type per key**: merging a key's existing CRDT with a different CRDT type
|
|
601
|
+
raises `TypeError` — a deterministic schema contract, not an ambiguity.
|
|
602
|
+
- **Unique IDs are load-bearing**: `action_id` and set tags are event identities;
|
|
603
|
+
reuse silently merges (so agent ids must be process-unique and ticks monotonic).
|
|
604
|
+
- **Fixed-point weights**: probabilistic decisions use a fixed 32-bit scale over
|
|
605
|
+
the same serialized `weight`, so all replicas agree as long as `weight` is a
|
|
606
|
+
JSON number.
|
|
607
|
+
|
|
608
|
+
## Roadmap -> status
|
|
609
|
+
|
|
610
|
+
Phase 1 (protocol foundations), Phase 2 (cluster runtime), Phase 3 (application
|
|
611
|
+
layer), and the Phase 4 operational surface (metrics/admin probe, Prometheus
|
|
612
|
+
exporter, benchmarks, TLS, signed journals, membership/partition handling,
|
|
613
|
+
cumulative compaction) are complete.
|
|
614
|
+
|
|
615
|
+
The remaining ideas in each phase are **backlog, deliberately out of scope**
|
|
616
|
+
under the current product definition (§"Product definition and scope"): secure
|
|
617
|
+
framing beyond TLS, multi-cluster federation, chunked cross-peer snapshot
|
|
618
|
+
exchange, OpenTelemetry exporters, and workflow orchestration expansion. Each
|
|
619
|
+
is a candidate to revisit only after the core fabric — determinism, durability,
|
|
620
|
+
crash recovery — is proven by adopters.
|
|
621
|
+
|
|
622
|
+
## Security
|
|
623
|
+
|
|
624
|
+
The cryptographic signing, identity verification, and storage subsystems are
|
|
625
|
+
documented in [`docs/SECURITY.md`](docs/SECURITY.md): threat model and trust
|
|
626
|
+
boundary, the properties the implementation enforces (constant-time
|
|
627
|
+
verification, algorithm binding, canonical serialization, signer binding,
|
|
628
|
+
replay idempotence, keyless-reload refusal), the findings that remain open,
|
|
629
|
+
and a deployment checklist. To report a vulnerability, open a GitHub issue or
|
|
630
|
+
contact the maintainer privately as described there.
|
|
631
|
+
|
|
632
|
+
## License
|
|
633
|
+
|
|
634
|
+
Conflux is released under the [MIT License](LICENSE), as declared in
|
|
635
|
+
`pyproject.toml`.
|