Skip to content

Save, replay, and fork

Use this page to save and load a run, keep compact history for a long-running simulation, replay a run exactly, or branch from a saved point to try different inputs.

A Canwu save holds more than world data. It also carries the commands, events, boundary records, random draws, plugin semantic environment, and state commitments that let a load verify the state. The plugin semantic environment is the name, version, and semantic hash of every plugin in the run; loading and replay need a plugin set that matches it.

Persistence paths: a live run can be saved as a snapshot or checkpoint journal and restored with the same plugins, replayed exactly from its replay journal, or forked into an alternative reality. View diagram source.
View diagram source
flowchart LR
  Run["Live run"] -->|"snapshot_json()"| Snap["Snapshot"]
  Run -->|"checkpoint_journal_json()"| CJ["Checkpoint + later journal"]
  Run -->|"replay_journal()"| Journal["ReplayJournal"]
  Run -->|"fork()"| Fork["Fork: new inputs, alternative reality"]
  Snap -->|"from_snapshot_json_with_plugins()"| Restored["Restored run"]
  CJ -->|"from_checkpoint_journal_json_with_plugins()"| Restored
  Journal -->|"replay_from_journal()"| Replayed["Replayed run"]
  Plugins["Matching plugin set"] -.-> Restored
  Plugins -.-> Replayed

A restored or replayed run has the same checkpoint_hash() as the original. A fork starts from the same state and then diverges.

Goal Save with Load with Your application’s job
Normal save or service restart snapshot_json() Canwu::from_snapshot_json_with_plugins() Store the whole snapshot; pass the same plugin set when loading
Compact storage for a long run checkpoint_journal_json() Canwu::from_checkpoint_journal_json_with_plugins() Store the checkpoint and the unbroken journal after it
Audit or exact reconstruction replay_journal() Canwu::replay_from_journal() or replay_from_journal_json() Store the whole journal and keep a matching plugin set
Send effects to outside systems outbox_entries() — Deliver at least once, deduplicate by delivery_id, and store acknowledgement state
Try different inputs from one point fork() — Treat the fork as a new alternative reality
use canwu_api::Canwu;
use canwu_reference_world::{ReferenceWorldPlugin, demo_scenario};
let (scenario, _) = demo_scenario()?;
let plugin = ReferenceWorldPlugin;
let canwu = Canwu::new_with_plugins(35, scenario, &[&plugin])?;
let json = canwu.snapshot_json()?;
let restored = Canwu::from_snapshot_json_with_plugins(&json, &[&plugin])?;
assert_eq!(restored.checkpoint_hash(), canwu.checkpoint_hash());

Loading validates the snapshot, including its format, engine version, and plugin descriptors, and fails if anything does not match. Once a save contains plugin domain records, pass the same plugins to load it.

The starter example runs this together with forking and exact replay:

Terminal window
cargo run -p canwu-reference-world --example starter

A checkpoint journal (CheckpointJournal) bundles a validated checkpoint with the unbroken evidence that follows it. It suits long runs because it starts from the checkpoint and carries only the evidence recorded after it.

Exact replay rebuilds the same run from the journal’s initial scenario, inputs, and recorded evidence:

let checkpoint_journal = canwu.checkpoint_journal_json()?;
let from_checkpoint =
Canwu::from_checkpoint_journal_json_with_plugins(&checkpoint_journal, &[&plugin])?;
assert_eq!(from_checkpoint.checkpoint_hash(), canwu.checkpoint_hash());
let journal = canwu.replay_journal();
let replayed = Canwu::replay_from_journal(&[&plugin], &journal)?;
assert_eq!(replayed.checkpoint_hash(), canwu.checkpoint_hash());

Rules for exact replay:

  • The scenario comes from the journal; replay_from_journal() takes only the plugins and the journal.
  • Run identity, engine version, plugin semantic environment, and recorded evidence must all match.
  • Replay reuses recorded outcomes. Decisions, random draws, and external answers are read from the journal, and side effects are not sent again.

fork() copies the run at its current point into an independent simulation. Use it to compare another command sequence, policy, or research assumption. The fork advances through the normal command and time APIs, and once its inputs differ it is a new alternative reality, separate from exact replay of the original.

Boundary emissions meant for outside systems appear in outbox_entries(). Each OutboxEntry has a stable delivery_id derived from the run, boundary, event, and emission index.

  1. Store each delivery_id with its delivery and acknowledgement state in your own storage.
  2. Deliver at least once, so a timeout or restart can retry safely.
  3. Deduplicate by delivery_id on the receiving side; a network call may happen more than once.
  4. Exact replay regenerates the same delivery_id values and leaves delivery to you.

In compact mode (CompactedCanwu), seal_evidence() returns the sealed EvidenceJournalSegment. Keep that segment with your delivery and acknowledgement state. Sealing or compacting evidence does not mark an effect as delivered or acknowledged.

Paged checkpoints, StatePageProvider, decision-history archives, and plugin archive participants matter only once snapshot size or hot-history growth becomes a real problem. At that point your application must:

  • store each state page under its content identifier and return the same bytes; a missing page must fail with an error, never read as empty;
  • keep the unbroken journal prefix and the archive-retention roots that each checkpoint needs;
  • restore the same archive contracts and semantic hashes for every plugin; and
  • check reachability from committed roots before deleting cold data.

The repository’s versioning and persistence contract lists format numbers, paged-storage contracts, and archive invariants.

  • Pin an exact release or Git revision for any application that keeps saves.
  • Treat a plugin semantic-hash change as a behavior change. A minor release (for example, 0.12 → 0.13) can change the semantic hashes of several extensions at once, and older saves that use those extensions then fail to load.
  • Canwu does not migrate old saves. To carry history across an engine upgrade, run your own export and import, or keep the engine that wrote the data.
  • New releases add enum variants and struct fields, so exhaustive match statements and struct literals may need updates.

For what each release changed, see the versioning and persistence contract.

  • Pin the engine version and plugin semantic environment.
  • Do one full save, process restart, and load, then compare checkpoint hashes.
  • Run one exact replay and compare the final checkpoint hash.
  • Simulate an outbox timeout and confirm that retrying by delivery_id does not repeat the outside effect.
  • Write down what an upgrade does with old saves: export and re-import them, keep the old engine to read them, or drop them.