State Fork (ι)
ι is the protocol for deliberate divergence. When θ consensus cannot agree on a single continuation, rather than halting the whole network, ι creates a sibling branch with a new identity. Siblings proceed in isolation until a future epoch reconciles them.
Phase 0 reality: forks are specified but not activated. The single-writer runtime cannot diverge. The schema supports ι so a later phase can turn it on without a migration.
Authoritative spec: ../../../spec/s07-fork-protocol.md.
Fork identity
A fork has a deterministic id derived from its lineage:
fork_id = SHA-256(parent_fork_id || divergence_event_id || rule_version_hash || reason)
The four inputs:
parent_fork_id— the fork this one split from. The root fork has a zero parent.divergence_event_id— the specific event at which θ could not agree.rule_version_hash— κ’s rule set at the moment of divergence.reason— one of the fiveForkReasonvalues.
Hashing over all four means two forks with the same parent and event but different rule sets are distinguishable, and a fork cannot be forged without knowing the exact divergence event.
Worked fork_id computation
Suppose the root fork is 0x00…00 (32 zero bytes), the divergence event id is evt:0x8a3c…, the active rule-version hash is rv:0xb1e2…, and the reason code is CONSENSUS_SPLIT. Canonical serialization concatenates ASCII-hex encodings with ||:
input = "0x0000…00" || "evt:0x8a3c…" || "rv:0xb1e2…" || "CONSENSUS_SPLIT"
digest = SHA-256(input)
= 0x4f92d3a7e1b0c82f… ← 64-hex-char output, truncated here for display
fork_id = "fork:0x4f92d3a7e1b0c82f…"
Every input is fixed-length (hashes are 32 bytes; reasons are enum strings from a closed set of 5). No length-prefix ambiguity is possible.
The five fork reasons
| Reason | Meaning |
|---|---|
CONSENSUS_SPLIT |
θ could not reach quorum; the vote genuinely split |
PARTITION_RECOVERY |
A network partition ended; both sides had diverged |
RULE_UPGRADE |
π governance approved a breaking rule change; legacy and upgraded chains run in parallel |
EMERGENCY |
A critical vulnerability required immediate divergence; governance seals after the fact |
EXPERIMENTAL |
An opt-in sandbox branch; governance bounded lifetime |
Every fork records its reason; a fork without a valid reason is rejected at creation.
Fork-trigger examples (one per reason)
| Reason | Example trigger |
|---|---|
CONSENSUS_SPLIT |
θ round 42 has votes {A→root_X, B→root_X, C→root_Y, D→root_Y}: 2 < quorum = 3 on both sides. ι forks X-branch and Y-branch. |
PARTITION_RECOVERY |
Network split at t=0; branch A and branch B both advance 100 rounds independently; at t=T, reunion happens — neither side can adopt the other without a fork. |
RULE_UPGRADE |
π enacts a breaking rule_version_hash change. Legacy chain continues under old rv; upgraded chain runs parallel under new rv until obsolete clients cut over. |
INVARIANT_VIOLATION |
An arbiter signs a block that violates AX-02 (derived reputation); detected after the fact. Fork isolates the contaminated branch. |
CONSTITUTIONAL_VIOLATION |
A rule change passed π but retroactive analysis shows it lowered a reputation decay below the AX-03 floor. Fork marks pre-violation history clean. |
VOLUNTARY_EXIT |
A constituent group exercises AX-06 right-to-exit; carries own state forward under a new fork. |
A fork without a valid reason is rejected at creation.
Fork status
A fork moves through four states:
ACTIVE— accepting new events, being voted on by its own arbiter set.RECONCILING— merge attempt underway; see merge protocol below.ABSORBED— merged back into a sibling; tasks complete, new events rejected.ABANDONED— frozen; no merge attempted. Retained for audit only.
Isolation modes
Forks differ in how much of the world they share with siblings:
- Full isolation — separate DB, separate arbiter set, separate skill registry. No cross-traffic until reconciliation.
- Read-sharing — shared read-only view of the pre-divergence state; writes are independent.
- Shared identity — agent Soul Vectors and reputation carry across the fork boundary but actions are recorded independently.
Full isolation is the safest and the slowest to reconcile. Shared identity is cheaper to run but harder to audit because a single agent acts in both branches.
Isolation mode × capability matrix
| Mode | Read parent state | Write to parent state |
|---|---|---|
| Full isolation | No | No |
| Read-sharing | Yes | No |
| Shared identity | Yes (identity only) | No (writes remain local to each branch) |
No mode permits writing to parent state — that would violate AX-01 (append-only history). All merges happen via the explicit merge protocol, never by one branch reaching into another’s storage.
Checkpointing
A fork checkpoints its state periodically so a reconciliation attempt has a well-defined common ancestor. A checkpoint is valid when 7 of 10 designated arbiters sign it. The 7-of-10 parameter is the Phase 0 spec default; π governance can revise it in later phases.
Checkpoint frequency is not fixed; it is triggered whenever the fork’s Merkle root accumulates a configured event count (default: 256 events) or a time bound (default: 15 minutes) is reached.
Merge protocol
Two forks reconcile by producing a union state:
- Common ancestor identified — the latest checkpoint signed by both branches’ arbiter sets.
- Divergence set computed — the events in each branch since the common ancestor.
- Conflict classification — events are (a) compatible (apply cleanly to a union), (b) conflicting (same resource, different outcome), or (c) mutually-exclusive-by-design (both cannot be true).
- Resolution — compatible events merge; conflicts require a π governance decision; mutually-exclusive events force one branch to become
ABSORBEDand the other to survive. - Merged root — η produces a new Merkle root over the reconciled state; θ reaches quorum on it; the merged fork replaces the two siblings.
A merge failure leaves both forks ACTIVE and emits a governance event requesting manual arbitration.
Merge pseudocode (5 steps)
fn merge(fork_source, fork_target):
# Step 1: common ancestor
ancestor = latest_state_root_shared_by(fork_source, fork_target)
if ancestor is None:
return MERGE_FAILED("no common ancestor")
# Step 2: event diff
events_src = events_since(fork_source, ancestor)
events_tgt = events_since(fork_target, ancestor)
to_apply = symmetric_difference(events_src, events_tgt)
# Step 3: conflict resolution
conflicts = detect_conflicts(to_apply) # same resource, divergent outcomes
for conflict in conflicts:
resolved = resolve(
conflict,
primary = timestamp_ordering, # default
secondary = reputation_weighted_vote, # fallback
tertiary = t0_manual # last resort
)
to_apply.replace(conflict, resolved)
# Step 4: compute merged fork id
reason = "MERGE"
new_fid = SHA-256(fork_source.id || fork_target.id || ancestor.root || rule_version_hash || reason)
# Step 5: 7-of-10 threshold-sign merge proof
proof = threshold_sign(arbiter_set, new_fid, quorum=7, total=10)
emit MERGE_PROOF(new_fid, proof)
transition both forks → ABSORBED; new fork becomes ACTIVE
Conflict scenario — two forks, same parent, divergent rules
Parent at rule version rv:A. Fork A applies π-approved delta min_stake: 500 → 600. Fork B independently applies min_stake: 500 → 700. Both advance 50 rounds.
Merge attempt:
- Common ancestor: last checkpoint under
rv:A. - Event diffs: A has 50 events applied under
rv:B1 (min_stake=600); B has 50 events applied underrv:B2 (min_stake=700). - Conflict: same parameter (
min_stake), different final value. Not a timestamp conflict — a policy conflict. - Resolution routes to π (policy conflicts cannot be resolved by timestamp ordering).
- π options: (a) supermajority vote picks one value — absorbed fork abandons its events; (b) both forks remain
ACTIVEpermanently (accepted divergence) — the union is rejected; (c) T0 declares the merge infeasible.
If (a), effects downstream of min_stake on the losing fork need replay under the winning value; in practice, many are invalidated — reputation gains from commitments that required the old threshold may be rolled forward but stake-related obligations must be re-evaluated.
Shield invariants
Three rules hold across every fork boundary, independent of reason or isolation mode:
- Identity keys persist. An agent’s Ed25519 keypair is valid in both branches. A fork does not revoke keys.
- Constitutional axioms persist. AX-01 through AX-07 (see
../constitution.md) apply in every branch. A fork cannot weaken a constitutional invariant. - Fork-id ancestry is append-only. The
parent_fork_idfield is immutable; no fork can be re-parented. The fork DAG is a tree, not a general graph.
Exit penalty formula
AX-06 guarantees a right to exit with a stake penalty capped at 10%. The exit penalty for an agent voluntarily leaving a fork:
penalty_bps = min(1000, scar_weight * 10 * log2(epochs_since_stake))
scar_weight— sum of λ scar weights on the agent’s record (typically 0–5 for clean participants, 5–20 for agents with multiple equivocation flags).epochs_since_stake— how long the stake has been active (older stake → larger log factor).- Hard cap at
1000 bps= 10% (AX-06). An agent with zero scars and any stake age pays approximately0— exit is free for unscarred participants.
Example: scar_weight = 2, epochs_since_stake = 8 → 2 * 10 * 3 = 60 bps = 0.6%.
What ι is not
- Not a rollback. ι creates siblings, not ancestors. A fork does not undo history; it branches from it.
- Not a test sandbox. A test run is an ephemeral process against a test DB (see
../../../2-plugin/modes.md); a fork is a real chain with real arbiters. - Not cheap. Every fork doubles the auditable surface until it reconciles. Forks are a last resort, not a feature.
Interaction with governance
π may bound a fork’s lifetime (common for EXPERIMENTAL and EMERGENCY forks), may veto an attempted merge, and may force a specific resolution in step 4 of the merge protocol. See ../enforcement/governance.md.
Phase 0 posture
No fork is ever created in Phase 0. The tool surface exposes no fork_* tool. The schema carries fork columns because the column shape is load-bearing for later phases; the fork_id field is always NULL on every row. First real ι activation target: R151+ (Phase 5) per ../../../5-time/roadmap.md.
See also
consensus.md— θ, whose split triggers ι; also provides threshold-signing used for 7-of-10 merge proofsproof-store.md— η, whose roots become per-forkrule-engine.md— κ, whose rule version hash is one of ι’s fork-id inputs../enforcement/governance.md— π, which resolves contested merges../../social/identity.md— ξ, whose keys persist across fork boundaries (shield invariant 1)../../../spec/s07-fork-protocol.md— authoritative spec../../../spec/s19-governance.md— governance pathway for contested merges