Skip to main content
A long run dies at task 7 of 9 β€” laptop lid, ctrl-C, a provider hiccup. With most tools that is 7 tasks of tokens re-spent. With Nika the run’s own trace is the checkpoint: --resume re-executes the workflow, skipping every task whose recorded work is still valid (ADR-099). No daemon, no run store, no new artifact β€” the reader of a file you already have. That sentence is the whole architecture, so it deserves saying plainly: durable-execution systems anchor recovery in standing infrastructure β€” a database, an embedded log server, or a managed cloud β€” and pay for it with author-facing rules (deterministic code, side effects quarantined into steps, versioning APIs, replay-test suites). Nika anchors recovery in the hash-chained trace every run already writes. No server, no database β€” the trace is the checkpoint: readable with cat, diffable with git, owned by you. Two hashes decide skip-or-rerun; recorded work replays from the trace; everything else honestly re-executes. No determinism is ever demanded of your workflow.

Record, then resume

release-notes.nika.yaml
Any --json run is a recording (Traces & replay):
Resume from it:
Every skip is visible β€” a cache hit line in the render, a task_cache_hit event in the new trace, and the summary counts skipped vs live. A resumed run never pretends work happened silently.
A trace recorded by an engine version without resume keys is not an error: --resume prints a notice and runs everything live.

The skip rule Β· two hashes, both must match

A task skips iff the trace holds its completed record and
  1. the task definition hashes the same (the verb body, with:, extract:, retry: / on_error:, when:, for_each: β€” as now written), and
  2. the resolved inputs hash the same (what its ${{ }} references actually resolved to β€” upstream outputs, inputs, const, secrets).
Edit a prompt β†’ that task re-runs. Pass a different --var β†’ the tasks that consume it re-run, and the mismatch cascades exactly as far as the data flows β€” untouched sibling branches still skip:
An infer: / agent: task that matches replays its recorded output β€” that is the point: crash-resume without re-spending tokens. There are no determinism rules, no replay constraints, no workflow versioning: durability is the engine’s problem, never yours. A task that does not match simply re-runs live, side effects included.
The honest limit β€” at-least-once, not exactly-once. Completed work never re-fires, but a crash in the middle of a side effect can double-fire it on re-run β€” the same caveat every database-backed durable-execution system documents for its own steps. Idempotency keys are a deliberate deferral: make external effects idempotent via their own arguments, or fence the node with --from discipline.

--from Β· force a re-run the hashes cannot see

Some changes are invisible to hashing: a rotated secret, external state, an infer: output you want re-rolled. --from <task_id> forces that task and its transitive downstream to re-run even on a match β€” upstream tasks still cache-hit:
An unknown task id is refused before the run, like an unknown --var key.
Secrets participate by name, never by value β€” a trace carries no secret-derived material, so a rotated secret does not invalidate the cache. That is the one sharp edge: after a rotation, force the affected node with --from.

The durable human gate Β· pause and answer

A nika:prompt task waits on a human. At a terminal, the gate asks you directly (confirm [y/N] Β· choice by number or value Β· input verbatim) and the run continues through the same verified resume path. Anywhere no human can answer (a pipe Β· CI Β· an agent Β· --json) with no usable default:, the run does not hang and does not fail β€” it pauses durably: the trace records a workflow_paused event with the prompt payload, the process exits with code 4, and the frame prints the exact resume line (your --var/--model included). Pre-answer in one pass with --answer <task>=<value> at launch.
gated-ship.nika.yaml
The paused trace carries everything needed to pick the run back up β€” hours later, on the same machine, by a different process:
the workflow_paused event (one line of the trace Β· reformatted)
Resume with the answer bound to the task id via --answer (repeatable):
The value follows the prompt’s mode:. confirm wants a boolean (--answer approve=true / =false β€” a refusal is a value, and the when: gate downstream decides what happens with it). input takes a string, choice one of the declared choices. Like --var, the value parses as JSON when it parses. Resumed without an answer, a --json run pauses again β€” idempotent, exit 4 every time, so a poller can retry harmlessly. Resumed interactively (a human at the terminal), the prompt simply asks.

Exit codes

Further reading

The design has a lineage, and naming it beats pretending novelty:
  • Build Systems Γ  la Carte β€” Mokhov, Mitchell & Peyton Jones, ICFP 2018 (doi:10.1145/3236774). The formal frame for hash-keyed skip-or-rerun: --resume is a build system’s verifying trace applied to a workflow DAG.
  • Durable Functions: Semantics for Stateful Serverless β€” Burckhardt et al., OOPSLA 2021 (doi:10.1145/3485510). The semantics of replay-based durable execution β€” and of the determinism obligation that comes with it, which content-addressed resume structurally avoids.
  • Nextflow β€” Di Tommaso et al., Nature Biotechnology 2017 (doi:10.1038/nbt.3820). Scientific computing proved content-addressed -resume a decade ago; its ecosystem calls it the single most-loved feature.