pub struct IntentAgent {
pub fencing_epoch: u64,
pub runs: BTreeMap<String, FoldedRecord>,
pub committed_runs: BTreeMap<String, FoldedRecord>,
}Expand description
One agent’s leased execution-intent ledger — the FoldTier::Leased
tier’s per-agent folded state (B5).
This slice delivers deterministic ledger convergence plus a durable idempotency oracle — it does NOT by itself make execution exactly-once. The exactly-once execution gate is B6’s dispatch fence (a linearizable “am I still epoch N?” check plus the durable non-fenced idempotency read before the external side effect); the fold decides who wins the ledger, not whether the effect happens.
Two distinct views live here, and confusing them causes double-execution:
committed_runsis the idempotency oracle — a fence-INDEPENDENT, keep-all maprun_id → committed record. Once a run commits, it stays here forever (a commit is a fact; no epoch bump erases it), soSyncState::committed_runis the correct “did this run already execute?” lookup. It is carried in the checkpoint and never trimmed by retention/compaction (seecrate::compact).runsis the “who holds now” view — per-agent epoch fencing applies to pending intents (a stale zombie holder’s pending is fenced), while committed/failed records are terminal-immune (never reverted to pending, never cleared by a fence raise). Read viaSyncState::intent. Do NOT useruns/intent()as the idempotency oracle — a pending fenced by a later epoch is absent here yet the run may have committed; askcommitted_runs/SyncState::committed_run.
Fencing is per AGENT, not per run (the proposal’s spec, deliberately):
a zombie’s unique post-failover pending — one the new holder never
re-ran — is fenced too (per-run fencing would let it through). fencing_epoch
is the agent’s max-seen lease epoch. committed_runs is what makes that
safe for idempotency: even after prior-epoch pendings drop from runs, the
committed fact survives keep-all.
Fields§
§fencing_epoch: u64The agent’s fencing epoch — the max lease epoch any of its intents carried. Pending intents below it are fenced (terminals are immune).
runs: BTreeMap<String, FoldedRecord>run_id key (id:<run_id>) → the “who holds now” winner (terminal-immune;
pendings fenced to fencing_epoch). NOT the idempotency oracle.
committed_runs: BTreeMap<String, FoldedRecord>run_id key → the committed record. Fence-independent, keep-all — the
durable idempotency oracle that survives epoch bumps AND compaction.
Grow-only (highest (epoch, hlc, op_id) committed record wins on a
collision); never cleared by fencing. #[serde(default)] so a state
serialized before this field parses.