AOS / Docs

Reproduction and branching

Crucible's central operating rule is to preserve the scenario, seed, and recorded schedule together. A seed can reproduce deterministic choices made from that seed, but a failure artifact is the stronger handoff: it also pins the resolved schedule and producer build identity.

#Verify repeated execution

verify runs independent reductions and compares their canonical logs and fingerprint streams:

./result/bin/crucible \
  --seed 0x2a \
  verify scenario.toml \
  --runs 5

Add --adversarial to exercise the hostile host-condition matrix and --bisect to identify the first divergent state if reductions disagree:

./result/bin/crucible \
  --seed 0x2a \
  verify scenario.toml \
  --runs 8 \
  --adversarial \
  --bisect

A divergence exits with status 1. When producer provenance is available, Crucible writes side artifacts for the disagreeing executions.

To compare two existing artifacts without running the scenario again:

./result/bin/crucible verify --compare run-a.crucible run-b.crucible

Artifact comparison uses the identities embedded in those artifacts. It does not select or execute the locally available backend. The two producer identities must match each other, so repeated comparisons of unchanged inputs produce stable evidence even when the comparison host does not have that producer backend installed.

#Failure artifacts

A non-passing run, search, or fuzz result normally writes a self-contained .crucible artifact below --artifact-dir; collect-mode exploration writes one artifact per retained finding, and verify writes paired side artifacts when both divergent reductions carry producer provenance. It records:

  • the resolved seed;
  • engine, protocol, QEMU, patch-series, and plugin identity;
  • canonical scenario material;
  • the recorded decision schedule;
  • canonical log and fingerprint evidence; and
  • embedded model-reproduction material where the producer supplied it.

Streamed event summaries retained in run evidence identify the event's original sequence, virtual-time and instruction-count coordinate, source, causal class, and a bounded set of diagnostic attributes. Fault summaries include their kind, tag, targets, and description; assertion summaries include their assertion id and state. Each production VM emits an initial Started lifecycle observation at the initial admitted scheduler boundary before the first assertion evaluation, so an invalid Always predicate can produce an immediate violation instead of remaining unknown. A terminal verdict at that boundary does not advance a guest. Guest byte payloads remain redacted and are represented only by their length. These coordinates distinguish repeated scheduler boundaries and make a fault or violated assertion findable without exposing console or channel data.

In table mode, the failure footer prints copy-pasteable replay and debug commands. JSON/JSONL records the artifact digest in the final outcome but does not add the host path to the canonical log; locate the matching repro-*.crucible file below --artifact-dir.

Search and fuzz also emit a signed .crucible-findings ledger that can be passed directly to triage. Use --findings-out <path> for a fixed ledger location; otherwise it is content-addressed below <artifact-dir>/findings.

#Replay

Replay validates the artifact schema and requires an exact producer/consumer build-identity match. Current production artifacts use the v3 schema; v2 artifacts are rejected instead of falling back to model-only replay. A v3 QEMU artifact contains the compact scenario, typed schedule, pure model proof, live replay recipe, canonical QEMU event bytes, and typed execution-fingerprint evidence. Run, verify, and fuzz artifacts retain the full sample stream. Search and fork artifacts retain a declared terminal snapshot containing exactly one sample for every VM node:

./result/bin/crucible replay .crucible/repro-failed-<digest>.crucible

Replay first executes the required pure reduce(ScenarioDef, Schedule) preflight. It then launches fresh guest VMs through the packaged QEMU/plugin backend, reapplies recorded branch, fault, and network inputs, executes the recorded non-interactive startup and initial controls, and requires the terminal tuple, event stream, and fingerprint stream to match the producer byte-for-byte. There is no production model-only success path. Virtual-time- and quantum-bounded runs advance through exact paused quantum boundaries, so frontend polling latency cannot change the recorded terminal quantum between production and replay.

Interactive failure-artifact capture is not supported yet. Crucible rejects it instead of recording command names without the exact decision/frontier timing needed to replay them.

Compare the artifact's canonical log with a retained log file:

./result/bin/crucible \
  replay failure.crucible \
  --check original.jsonl

The comparison is byte-for-byte. The file must use the canonical JSONL entry encoding written by --trace, not a table rendering. The live QEMU replay and its embedded event/fingerprint comparisons still run before this retained-log check.

Bisect two artifacts:

./result/bin/crucible \
  replay failing.crucible \
  --bisect passing.crucible

Both artifacts are independently replayed through fresh QEMU sessions before bisection compares their canonical evidence. A --check mismatch or bisection divergence exits with status 1.

With --to <savepoint>, Crucible completes the same live artifact replay, then proves that the requested savepoint is a typed schedule prefix and validates its materialization through the replay oracle. The savepoint handle or checkpoint object must remain available in the selected store. A v3 artifact's own terminal checkpoint hash is self-contained: Crucible reconstructs that target from the embedded scenario, schedule, and recorded frontier when the store does not contain a separate checkpoint object.

#Savepoints

save runs to a deterministic boundary, materializes a checkpoint, validates it against the replay oracle, and exports a .crucible-savepoint handle.

Save at virtual time:

./result/bin/crucible \
  save scenario.toml \
  --at virtual-time \
  --max-virtual-time 20s \
  --label before-election

Virtual-time saves pause after each scheduler quantum and export only at the exact requested coordinate. A backend that cannot advance virtual time or that steps past the coordinate fails the command instead of exporting an ambiguous handle. Zero-time boot quanta are allowed within a bounded progress window.

Other boundaries are:

--at quiescence
--at property --property <assertion-name>
--at marker --marker <guest-marker-name>

Quiescence, property, and marker saves continue across scheduler quanta until a one-shot suspending breakpoint observes the requested evidence. A property selector stops on the named assertion's violated phase. Marker and property selectors also stop at quiescence and fail without exporting a handle when the requested evidence never appeared. --max-virtual-time is accepted only with --at virtual-time; combining it with another boundary is a usage error. Live QEMU boundary observation can wait for the backend's production completion window and is not limited by the control stream's short acknowledgement poll.

New savepoint handles use schema crucible.savepoint-handle.v3. They include a selector line naming the property violation or guest marker (or none) and a boundary-proof line with the exact breakpoint or virtual-time coordinate. Breakpoint proofs use positional fields breakpoint, ID, suspend, frontier, and quantum, followed by a boundary-predicate line containing the predicate's content address and canonical payload. Coordinate proofs use coordinate, frontier, and quantum with boundary-predicate none. The canonical trace contains a matching save_boundary_proof entry; selector names there are percent-encoded so spaces and punctuation cannot resemble additional fields. This lets an agent audit which selector fired and where, instead of inferring it from generic set-breakpoint acknowledgements. Crucible still reads v2 handles created by older builds; those handles do not contain selector provenance.

If a selector does not fire before quiescence, Crucible creates no handle and returns exit 3. With --trace <path>, it still writes the commands and state updates observed before rejection, followed by save_boundary_failure and an error final_outcome. This is useful for confirming that the selector and guard were installed and driven; it is not a savepoint or reproduction artifact. planned_session_command and planned_api_call rows describe the declared workflow, while interactive_ack rows identify commands actually accepted. Marker failures label their boundary mode marker (quiescence-guarded).

Use --out <path> to choose the handle path. Otherwise it is written below --artifact-dir. Crucible does not export a handle if replay-oracle validation fails.

#Resume

Resume accepts a savepoint handle or a direct blake3:<checkpoint> reference:

./result/bin/crucible \
  resume .crucible/savepoint-before-election-<digest>.crucible-savepoint

Direct checkpoint hashes require the same --store used when the checkpoint was created. A savepoint handle carries scenario and schedule evidence, but its referenced store objects must still be resolvable when they were not embedded. If deterministic execution advanced without making a causal schedule decision, the saved configuration can still have genesis identity. Resume uses the fat checkpoint frontier as the runtime boundary and replays to that exact coordinate; it does not mistake the savepoint for the zero-time baked genesis.

resume supports the same --until, --max-virtual-time, --interactive, and --watch controls as run. In interactive mode, query prints both its acceptance line and interactive-query\tstate=<state>, including when resume is running through the local QEMU control plane or a daemon.

#Fork

Fork creates an independent child from a validated execution prefix:

./result/bin/crucible \
  --seed 0x2b \
  fork .crucible/savepoint-before-election-<digest>.crucible-savepoint \
  --label alternate-seed

Alternatively, pin scheduler-recorded live World-network choices with repeatable --override decision=value arguments:

./result/bin/crucible \
  fork savepoint.crucible-savepoint \
  --override 'live-world-network/link_endpoint_a_len%3D4%0Alink_endpoint_a%3Ddb-1%0Alink_endpoint_b_len%3D4%0Alink_endpoint_b%3Ddb-2/a-to-b/42/7=loss-fire' \
  --label alternate-delivery

An explicit fork seed and decision overrides are mutually exclusive. Copy the exact point and choice from exploration evidence; override points are not free-form labels. Crucible rejects unknown namespaces and choice names before launch, and a live run fails if the exact scheduler point is never reached. The fork checks scheduler-owned pending-choice state before session cleanup, so an unreachable point returns an artifact error instead of wedging shutdown. Successful fork output and canonical traces include every recorded point and choice. Point and choice components use RFC 3986 percent escapes, so the encoded point printed by Crucible can be copied directly even when its canonical link identity contains = or newline bytes. Without an explicit --seed, the fork inherits the savepoint's seed and does not generate a second run identity.

Non-interactive fork writes a child .crucible artifact below --artifact-dir. It is currently a local workflow; remote daemon fork is not implemented. An unchanged fork records an explicit resume recipe from the retained base. Reseeded and override forks record their branch coordinates, and replay forces only decisions owned by the post-branch suffix.

An interactive live-QEMU fork is a transient inspection session. A passing or explicitly stopped session can complete with its terminal checkpoint and replay-oracle evidence without a post-run artifact-capture error, but emits fork-artifact\tstatus=not-captured because the current artifact schema cannot bind interactive commands to exact decision/frontier coordinates. Failed, crashed, and timed-out sessions retain their normal nonzero outcome exits. Use a non-interactive fork when a replayable child artifact is required. The CLI does not claim that a partial interactive recipe is replayable.

Replaying any of these fork artifacts reconstructs the retained checkpoint and uses the same resume lifecycle as the original fork. The replay therefore checks the fork's exact control acknowledgements as well as its terminal configuration, event bytes, and terminal fingerprints. A fork artifact is not reinterpreted as a new run from genesis.

A successful live replay reports validation=passed separately from reproduced_status and reproduced_outcome. The command exits zero when the recorded failure or timeout was reproduced and validated; it does not recast that recorded outcome as a passing scenario.

For fork --until virtual-time, the target is measured from the savepoint's restored global scheduler frontier. Crucible continues across internal branch admission and per-node events until that cross-node frontier reaches the target; one node reaching the timestamp is not sufficient. If a backend cannot reach an exact requested boundary, the command reports the last state, frontier, quanta, and outcome in its error.

#Artifact portability

Reproduction deliberately fails on a build-identity mismatch. Move the matching Crucible package closure with an artifact, or rebuild the exact revision that produced it. A newer binary is not automatically a valid replay consumer even when its artifact schema is compatible. Initial production lifecycle observations are part of harness engine ABI v2; artifacts carrying the earlier engine ABI are rejected as identity mismatches rather than compared against the new event stream.