Skip to demo
back to showScene · 04
A live walkthrough · TDD-005 BFT consensus

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.

Most agents
1 vote
the LLM's
Majority vote
N votes
unsigned quorum
This system
3 of 4
signed prepares + signed commits

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.

spec · TDD-005 bft.rsgenerated · 2026-05-16cluster · n=4 · f=1 · quorum=3
Clean quorum — all 4 replicas agree. The leader proposes; three replicas verify and
Cluster · n=4 · f=1 · quorum=3
view 0 · sequence 1
leader
r0
Scenarios
Proposed action
{
  "kind": "execute_tool",
  "tool": "fs_write",
  "path": "/tmp/report.md",
  "bytes": 4096
}
action_hash · 0f64eba132b54b4c787ffd01b4ec
Walkthrough · beat 1 of 14intro
PBFT phase × replica matrix
phase
r0
r1
r2
r3
Pre-prepareleader signs the proposal
Preparereplicas verify + broadcast
Commitcommit on prepare quorum
prepare votes
4/ 3
quorum reached
prepare votes quorum reached: 4 of 3
commit votes
4/ 3
quorum reached
commit votes quorum reached: 4 of 3
BFT round cost
~2.84ms
8 signed messages + state transitions · estimate from per-phase Rust costs, not a benchmark
single-trusted-leader
~50ms
one RPC to a service you have to trust
tolerates
f=1
byzantine fault out of n=4 replicas. Real cost 94% lower than a single-trusted-leader RPC, and you don't have to trust the leader.

The PBFT protocol on this page (PrePrepare → Prepare → Commit phases, leader rotation by view % n, equivocation log keyed by (replica_id, sequence) → set of action_hashes, view change on 5s timeout) is ported from tdd005/crates/tdd005_orchestrator/src/bft.rs. Constants verbatim: n=4, f=1, quorum=2f+1=3, view_timeout_ms=5000. Verifiable by grep.

The Rust implementation is the in-process VirtualBftCluster wired into 5 use cases via Patch 90 (multi-device tool quorum, cognitive hard gate, constitutional hot-swap, federated peer consensus, provenance fork resolution). Multi-host PBFT with full view-change recovery for production deployment is implemented but not battle-tested — see docs/tdd/TDD004_TDD005_GAPS.md. The build pipeline lives at demo/build-data-bft-consensus.mjs in Banterpacks.