Skip to main content
A run answers three questions at three different times: what will happen? (before), what is happening? (during), what happened? (after). One command each, all offline-friendly — every frame below is real output from this workflow:
standup-digest.nika

Before · nika inspect

Static anatomy: the DAG’s wave groups (what runs in parallel), plus three planning lines — the parallelism width, the pinch points, and each task’s blast radius. Zero execution, zero tokens:
Read it before a long run the way you read a query plan before an expensive query.

During · the live render

In an interactive terminal, nika run animates a storyboard: tasks light up as their dependencies clear, in the wave order inspect predicted. Piped (CI logs, tee), the same run prints one plain, deterministic frame instead — ∥ marks tasks that ran in parallel:
Four render modes, one per audience:

After · the waterfall and the verdict card

An interactive run ends with two frames. The waterfall shows where the wall-clock went — overlapping bars are the parallelism you actually got. The verdict card compresses the run into one box: the wave shape (◆◆ ⇉ ◆ — two in parallel, then one), tasks, waves, retries, duration, cost (its ≥/unpriced grammar is the cost-honesty vocabulary), model, and the typed outputs it produced:
The same frames are recoverable from any recorded trace, hours or machines later. After a run, the journal is already on disk. With no path, these commands read the workspace’s latest journal:
trace show prints the final frames; trace replay plays the recorded events through the same renderer that drew the original run. Replay calls no model and runs no command — it is a projection of what already happened. Neither show nor replay proves the chain; nika trace verify does (Traces & replay).

Export to your tracing stack · OTLP

A recorded trace projects to OTLP/JSON lines, so any OpenTelemetry viewer becomes a Nika run viewer — no collector, no daemon, no vendor. Unlike show / replay / verify, nika trace export requires the journal path — there is no workspace-latest default. Fill <path> with a row from nika trace ls.
Each verb becomes a span (duration, tokens, the ADR-099 identity hashes). An infer: or agent: task also carries the model it ran on, in the standard OpenTelemetry GenAI semantic conventions: gen_ai.provider.name, gen_ai.request.model, gen_ai.response.model — the current names, so any GenAI-aware viewer (Grafana, Langfuse, Phoenix) reads which model produced which output with no translation. Drag the file into the Jaeger UI (≥ 1.60), or POST the lines to any OTLP/HTTP endpoint. Task outputs stay local by default; --include-content widens the exported file to carry them as span attributes when you explicitly want them. The export is a projection of the journal — it never re-runs anything.
Because the model, provider, tokens, cost, and status ride every run’s own trace, your runs are your own analytics store — query them with jq, feed them to any OTel-native eval tool, on files you own, with no vendor and no cloud. (A first-class --format dataset export for fine-tuning / eval sets is on the roadmap — it waits on a content-policy decision, since a training set needs the input prompts a trace does not capture by default.)

Terminals & CI

The live surfaces are TTY-only by design. Piped or NO_COLOR output is the sober register (plain per-line events — what CI logs want). Two render themes ship: unicode (default) and --ascii (every glyph has an ASCII twin — use it on terminals without good box-drawing fonts, including older Windows setups; Windows Terminal renders the default theme fine). --color never (or --plain), NO_COLOR, and CLICOLOR_FORCE follow the standard resolution order — one global colour chain, no per-verb twins. From an app, iterate run.events() then await run.result() — see SDK runtime events. traceVerify only reads evidence the run already left.