Ask a team where their prompts live. A chat window. A doc titled PROMPT_v2_FINAL_really. An f-string three layers deep in a glue script. Then ask the question that matters during an incident: which prompt produced this output?
The usual answer is a prompt-management platform — another dashboard, another account, another place your logic lives that is not your repo. Nika's answer is shorter: the prompt is a line in the workflow file.
nika: v1workflow: release-notes# local model · nothing leaves this machinemodel: ollama/llama3.2:3bpermits: fs: { read: [ ./commits.txt ], write: [ ./notes.md ] } tools: [ "nika:read", "nika:write" ]tasks: - { id: commits, invoke: { tool: "nika:read", args: { path: ./commits.txt } } } - id: draft depends_on: [ commits ] infer: prompt: "Write release notes from these commits: ${{ tasks.commits.output }}" max_tokens: 200 - id: save depends_on: [ draft ] invoke: { tool: "nika:write", args: { path: ./notes.md, content: "${{ tasks.draft.output }}" } }outputs: notes: "${{ tasks.draft.output }}"One reviewable artifact: the model (a local one here), the permits, the plan, and the prompt. Which means the prompt inherits every tool your code already has, starting with the one you trust most.
Git diffs it. We ran the file once, then tightened the prompt — the kind of change teams make every week and then lose track of forever:
$ git diffdiff --git a/release-notes.nika.yaml b/release-notes.nika.yamlindex 06134b3..253fa1e 100644--- a/release-notes.nika.yaml+++ b/release-notes.nika.yaml@@ -13,7 +13,7 @@ tasks: - id: draft depends_on: [ commits ] infer:- prompt: "Write release notes from these commits: ${{ tasks.commits.output }}"+ prompt: "Write release notes from these commits. Exactly three bullets, plain words, no hype: ${{ tasks.commits.output }}" max_tokens: 200 - id: saveA prompt change as a one-line diff. It gets a commit message. It goes through a pull request, a colleague reads it, and someone can git revert it at 2am without archaeology.
The diff shows up in the artifact. Same three commits in, first prompt out:
Release NotesWe are pleased to announce the availability of our latest release,featuring several exciting updates and improvements.**New Features*** Added support for `--json` output when running the CLI command with the `check` option. This allows users to easily view check results in a JSON format.* Expanded coverage of local models in our Quick Start documentation, providing more comprehensive guidance for getting started with [project name].It keeps going like that — ceremony, section headers, and a helpful placeholder for a project name we never gave it. After the one-line diff:
Here are the release notes based on the commits:Release Notes v0.1* Resolved an issue with parser where anchors were resolved after merge keys.* Added a new command-line option (--json) to output JSON data during check.* Updated documentation to include coverage of using local models in the quickstart guide.Still a 3B model — it kept its little preamble. But the three bullets are there, plain, and they will be there on every future run, because the constraint lives in the file, not in a chat's short-term memory.
Every run pins the prompt that produced it. The run closes with its trace and the journal records the workflow's content identity:
🦋 nika · release-notes · 3 tasks permits ✓ declared boundary · default-deny ✔ commits invoke · nika:read 0ms ✔ draft infer · ollama/llama3.2:3b 1.5s ✔ save invoke · nika:write 0ms ── 3/3 done · $0.00 · elapsed 1.5s ───────────────────────────── trace: .nika/traces/2026-07-08T09-52-43Z-22c6.ndjson · 11 events · chain 03ad7a3f2bf1c6a92481b8483b9942abThe old run stays exactly as it happened, replayable after the edit — nika trace replay re-renders the recorded events, it never re-executes. So "which prompt produced this output?" has a literal answer: the one recorded with the run.
And the engine names an edit. Hand nika trace reproduce the runs from before and after the diff:
$ nika trace reproduce .nika/traces/2026-07-08T09-52-15Z-e90b.ndjson .nika/traces/2026-07-08T09-52-43Z-22c6.ndjson reproduced commits AUTHORED draft — the task changed ENVIRONMENT save — inputs differDIVERGED — 1 AUTHORED · 1 ENVIRONMENT · 1 reproduced engine: 0.97.0/macos/aarch64 (both runs)AUTHORED — the task changed. Not "the model is being weird today": a classified, named fact that the divergence came from your edit — and you can watch it propagate, the downstream save diverging as ENVIRONMENT because its input changed. Prompt versioning is not just storage; it is being able to say, between any two runs, this changed because we changed it.
No platform. No prompt registry with its own login. A text file in your repo, the versioning tool you have used for fifteen years, and one binary as the witness.
Every transcript in this post was captured verbatim against the released nika 0.97.0 (brew install supernovae-st/tap/nika), on a local model, for $0.00.