mirror of
https://github.com/saymrwulf/crisis.git
synced 2026-05-14 20:37:54 +00:00
2 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| a1064660d5 |
Decentralize crisis_agents: agents own graphs, detect locally, vote by quorum
The previous design routed every Crisis message through a `Mothership` that
also held every agent's LamportGraph, ran the byzantine scan from a
privileged vantage, and built proofs from its own view. That made the
mothership a chokepoint — exactly what a BFT layer is supposed to remove.
This commit redistributes responsibility along the lines you'd expect from
a real open protocol:
Each `CrisisAgent` now owns:
- its own `LamportGraph` (the agent's view of the network)
- `emit_claim(claim) → Message`: wraps a Claim into a fully-valid Crisis
Message built from the agent's OWN graph state, with chain link + cross
references + mined PoW nonce
- `receive(message)`: extends my graph if integrity holds; idempotent
- `gossip_to(peer) → int`: shares everything I have with peer until
quiescence (Algorithm 4 in the paper, in-process flavor)
- `detect_mutations() → list[LocalAlarm]`: scans MY graph for same-id
spacelike vertex pairs via the existing
`LamportGraph.find_mutations`, filtered by application-layer
`statement_id` so cross-detector AlarmClaims canonicalize
The `Mothership` shrinks to coordinator-only:
- bootstrap (register honest agents; trigger boundary open with a joiner)
- clock (call each agent's `next_turn()` per turn)
- first-hop routing (sender's emission → declared target subset)
- all-pairs gossip rounds between turns
- emit_alarms_from_detectors(): poll each agent for its LocalAlarms,
wrap any returned alarms into AlarmClaim payloads, broadcast them as
Crisis Messages over the gossip layer
Gone (regression-tested in `test_no_chokepoint.py`):
- `Mothership._graphs`, `Mothership.all_graphs()`, `Mothership.graph_of()`
- `alarm.scan_for_mutations(mothership)`
- any path where the mothership reads an agent's internal state
New voting layer (`crisis_agents/vote.py`):
- `AlarmClaim`: a Crisis-payload dataclass discriminated by `kind="alarm"`.
Wraps the accused process_id, statement_id, witness_digests, and
detection turn. Round-trips through JSON same as Claim.
- `quorum_for(n) = ceil(2n/3)`: classic BFT threshold.
- `tally_alarms(graph, threshold)`: groups AlarmClaim vertices by
(accused, statement_id, witness_pair), counts unique signer
process_ids, ratifies groups meeting the threshold. Deterministic
ordering so two equal graphs produce equal `RatifiedAlarm` lists.
- `RatifiedAlarm`: the network-level consensus on byzantine behavior.
Multi-signer proofs (`crisis_agents/proof.py`):
- schema_version bumped 1 → 2.
- ProofDocument now embeds every signer's process_id_hex and the
quorum threshold that was met. Self-consistency check enforces
distinct signers, witness pairs, and signer count ≥ threshold.
Byzantine scenario rewrite:
- `MockByzantineAgent` now takes an `intro_claim` for its first turn (a
benign broadcast). The intro is technically necessary: the agent's two
contradictory variants both chain to the intro vertex, so they can
propagate through gossip — without it, the second variant would fail
the chain constraint in any graph already holding the first.
- `fact_check` scenario: closed phase still has 3 honest agents emitting
6 claims each into the closed log; Crisis phase grew to 2 turns (intro
+ equivocation) so the byzantine can establish its same-id anchor
before equivocating.
End-to-end CLI output reframed around six phases:
1. closed team (no Crisis)
2. boundary opens
3. emission + gossip
4. decentralized detection (each agent reports its own findings)
5. alarms emitted + gossiped + ratified by quorum
6. proof emission
Tests (51 fresh + 5 carried over for boundary):
- `test_mothership.py`: per-agent graph ownership, broadcast vs.
targeted delivery semantics, gossip propagation, regression guards
against the removed centralization attributes.
- `test_alarm.py`: every honest agent independently detects the same
mutation; the byzantine doesn't detect itself; witness pairs are
canonical across detectors.
- `test_vote.py`: AlarmClaim round-trip, quorum formulas, tally
determinism, mothership convenience method matches direct tallying.
- `test_proof.py`: build_proof from RatifiedAlarm; multi-signer JSON
round-trip; tampered-witness/below-quorum/duplicate-signer rejection.
- `test_no_chokepoint.py` (the centerpiece): after the full lifecycle,
every honest agent's ratified-alarm set is byte-identical. A single
byzantine accuser alone cannot ratify. Forbidden attributes don't
exist on Mothership.
Full suite: 163 tests, all green in 0.80s.
CrisisViz: untouched by this refactor. The `crisis_data.json` pipeline
the visualizer consumes is produced by the orthogonal
`crisis.demo.Simulation`, which this commit doesn't touch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
|||
| b8684297fa |
Add crisis_agents — Crisis as a coordination layer for AI agent teams
A new sibling Python package, `crisis_agents`, that lifts the Crisis
protocol from "consensus between machines" to "consensus between AI
agents". Threat model: a team of sub-agents normally talks freely
with its orchestrator (the "mothership"); when the team's boundary
opens and an external agent of unknown trust joins, the mothership
activates the Crisis layer so byzantine equivocation is detectable.
Two-phase orchestration model:
Phase 1 — closed team, no Crisis: agents emit claims directly, the
mothership collects them flat.
Phase 2 — boundary opens: every subsequent claim is wrapped into a
Crisis Message with the agent's stable process_id and a PoW nonce,
delivered into per-agent LamportGraphs, and after each turn the
mothership scans for mutations via LamportGraph.find_mutations.
Phase 3 — proof: when an alarm fires, the mothership emits a
replayable JSON proof-of-malfeasance document with the contradictory
witnesses, their delivery sets, and DAG cross-references showing
which honest agents saw what.
Modules:
- claim.py Claim dataclass + JSON round-trip
- boundary.py membership tracker + open() trigger
- agent.py CrisisAgent abstract + MockAgent + MockByzantineAgent
(the latter equivocates by emitting two variants to
disjoint peer subsets at the same logical turn)
- mothership.py orchestrator driving both phases, building Crisis
Messages from Claims, per-agent LamportGraphs, log
- alarm.py scan_for_mutations: same-agent same-turn distinct
digests with non-identical delivery sets, verified
spacelike via LamportGraph.are_spacelike on the
honest-agent graphs
- proof.py build_proof + ProofDocument + JSON serializer +
verify_proof_self_consistent
- cli.py `crisis-agents demo` + `crisis-agents verify`
- scenarios/ fact_check: reference doc + 6 statements + scripted
honest/byzantine agents producing a deterministic
equivocation on statement s03
Tests: 50 new tests across test_claim, test_boundary, test_mothership,
test_alarm, test_proof, test_demo_fact_check. End-to-end test runs the
fact_check scenario, asserts exactly one alarm raised, proof is built,
re-serialized JSON passes self-consistency. Full suite (existing
crisis + new crisis_agents) green in 0.77s — 145 tests.
Out of scope (deliberately): visualization (separate CrisisViz upgrade
later), real TCP gossip (agents talk via in-process function calls in
the mothership), false-claim detection without equivocation (an
agent that consistently lies but never equivocates is out-voted, not
"caught"; catching it would require a ground-truth oracle).
Reuse from existing crisis package: Message, Vertex, LamportGraph,
LamportGraph.find_mutations, ProofOfWorkWeight, digest. The new code
is a thin adapter layer; the protocol substrate did the heavy lifting.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|