Four replicas,one decision.
When the orchestrator wants to run a risky action — delete a file, deploy production — one agent saying yes isn't enough. Four replicas each hold their own Ed25519 key and vote in two rounds: pre-prepare (the leader proposes), prepare (everyone signs the proposal), commit (everyone signs the prepared set). Three matching signatures in both rounds = action runs. One replica can lie about which action it saw, sit silent through the timeout, or run a stale view, and the other three still agree on the same answer. That's what makes the system tolerant of a faulty replica — including a malicious one.
The model decides. If it's wrong or compromised, the wrong action runs.
Replicas vote, majority wins. No signatures, no equivocation log — one lying replica can claim two different answers to two different peers and corrupt the count.
Each replica signs every message with its own Ed25519 key. The equivocation log keys on (replica, sequence) and catches a replica that signs two different action hashes for the same slot. Tolerates 1 byzantine replica out of 4 — survives lies, silence, and stale views.
most agents:the LLM's single yes/no decides — if it's wrong, the wrong action runs.
majority vote: replicas vote, majority wins — but no signatures, so a lying replica can corrupt the count.
this system: 3-of-4 signed quorum + equivocation detection — survives any one byzantine replica.
Below are five scenarios — clean quorum, one replica dissenting, equivocation caught, quorum stalled, leader timeout with view change. Each one shows the real PBFT phase matrix: who voted what, who got flagged byzantine, when quorum was reached, when it wasn't.
Scope:the Ed25519 signatures and SHA3-256 action hashes are real — generated here in the browser-build pipeline using Node's built-in node:crypto. The protocol structure (n=4, f=1, quorum=2f+1=3, leader = replicas[view % n], equivocation log keyed on (replica, sequence), view change on 5s timeout) is protocol-faithful to tdd005/crates/tdd005_orchestrator/src/bft.rs — same constants, same phase ordering, same byzantine detection rule. This is the in-process VirtualBftCluster pattern (Patch 90), not a production multi-host PBFT deployment.
Honest divergence: the action hash shown on screen is sha3_256(JSON.stringify(action)) (canonicalised JS object). The Rust runtime hashes serde_json::to_vec(&Action) of its enum, so the two byte strings differ by serializer — what's preserved is the hash-then-sign structure, not the input bytes. Same goes for replica names: the production cluster names replicas analytical, creative, adversarial, domain_expert (the cognitive agents from scene 02). This demo uses r0–r3 for visual room.
{
"kind": "execute_tool",
"tool": "fs_write",
"path": "/tmp/report.md",
"bytes": 4096
}