Chat sessions
- Great for exploring
- Gone when the tab closes
- Different answer every time
A file you keep
- Runs again tomorrow
- Same steps, same order
- Diff it like code
[ INTENT AS CODE ]
Nika turns repeatable AI work into files you can run, review, diff and share: audited before a token is spent, with a verifiable receipt after every run. An agent writes the plan; you review it; the runtime enforces it.
Repeatable AI work as a file: audited before a token is spent, a receipt after every run. The runtime enforces it.
v0.105.0macOS · LinuxAGPL-3.0
nika: v1workflow: id: daily-brief# local model · your notes never leavemodel: ollama/llama3.2:3b# the file IS the blast radiuspermits: fs: { read: [ ./notes/* ], write: [ ./brief.md ] } tools: [ "nika:read", "nika:write" ]tasks: notes: { invoke: { tool: "nika:read", args: { path: ./notes/today.md } } } inbox: { invoke: { tool: "nika:read", args: { path: ./notes/inbox.md } } } calendar: { invoke: { tool: "nika:read", args: { path: ./notes/calendar.md } } } triage: with: inbox: ${{ tasks.inbox.output }} infer: { prompt: "Flag what is urgent: ${{ with.inbox }}", max_tokens: 300 } agenda: with: calendar: ${{ tasks.calendar.output }} infer: { prompt: "Plan the day around: ${{ with.calendar }}", max_tokens: 300 } draft: with: notes: ${{ tasks.notes.output }} triage: ${{ tasks.triage.output }} agenda: ${{ tasks.agenda.output }} infer: prompt: "Write the morning brief. Notes: ${{ with.notes }} Urgent: ${{ with.triage }} Plan: ${{ with.agenda }}" max_tokens: 500 save: with: draft: ${{ tasks.draft.output }} invoke: tool: "nika:write" args: { path: ./brief.md, content: "${{ with.draft }}" }outputs: brief: "${{ tasks.draft.output }}"source01
nika: v1workflow: id: daily-brief# local model · your notes never leavemodel: ollama/llama3.2:3b# the file IS the blast radiuspermits: fs: { read: [ ./notes/* ], write: [ ./brief.md ] } tools: [ "nika:read", "nika:write" ]tasks: notes: { invoke: { tool: "nika:read", args: { path: ./notes/today.md } } } inbox: { invoke: { tool: "nika:read", args: { path: ./notes/inbox.md } } } calendar: { invoke: { tool: "nika:read", args: { path: ./notes/calendar.md } } } triage: with: inbox: ${{ tasks.inbox.output }} infer: { prompt: "Flag what is urgent: ${{ with.inbox }}", max_tokens: 300 } agenda: with: calendar: ${{ tasks.calendar.output }} infer: { prompt: "Plan the day around: ${{ with.calendar }}", max_tokens: 300 } draft: with: notes: ${{ tasks.notes.output }} triage: ${{ tasks.triage.output }} agenda: ${{ tasks.agenda.output }} infer: prompt: "Write the morning brief. Notes: ${{ with.notes }} Urgent: ${{ with.triage }} Plan: ${{ with.agenda }}" max_tokens: 500 save: with: draft: ${{ tasks.draft.output }} invoke: tool: "nika:write" args: { path: ./brief.md, content: "${{ with.draft }}" }outputs: brief: "${{ tasks.draft.output }}"[ 01 ]run together ×3
[ 02 ]run together ×2
[ 03 ]then
[ 04 ]then
recorded from a real nika run · replayed by your scroll · nothing staged
01.1
The same file, executed by the engine with a local model on a real machine. What you watch is the recorded event stream.
recorded from a real nika run · replayed at reading pace · nothing staged
01.2
The engine derives the order from the wiring: a with: binding is a data edge, an after: entry a control edge. Steps with no edge between them run together. Nothing runs that is not written in the file.
[ 01 ]run together ×3
[ 02 ]run together ×2
[ 03 ]then
[ 04 ]then
durations recorded from the real run · hover a step to trace its wires
[ THE BOUNDARY ]
02
The permits: block is part of the file you review. It is the whole list. Everything not on it is denied by default, before it runs. The full reference · families, secrets, the always-on floor · lives at /boundary.
permits: fs: { read: [ ./notes/* ], write: [ ./brief.md ] } tools: [ "nika:read", "nika:write" ]the same file as above · the permits block, verbatim
don't write it by hand:nika check --infer-permitsprints the tightest boundary the plan needs. Paste it in, loosen it yourself, or leave it strict.
fs:which files it may read and writetools:which tools it may callif a step reaches outside the list, say
write ~/.ssh/config NIKA-SEC-004effect outside the declared permits: capability boundary. Blocked before it runs, never logged after the fact. (An example: the recorded run above stayed in bounds, exit 0.)
03
nika: v1workflow: id: daily-brief# local model · your notes never leavemodel: ollama/llama3.2:3b# the file IS the blast radiuspermits: fs: { read: [ ./notes/* ], write: [ ./brief.md ] } tools: [ "nika:read", "nika:write" ]tasks: notes: { invoke: { tool: "nika:read", args: { path: ./notes/today.md } } } inbox: { invoke: { tool: "nika:read", args: { path: ./notes/inbox.md } } } calendar: { invoke: { tool: "nika:read", args: { path: ./notes/calendar.md } } } triage: with: inbox: ${{ tasks.inbox.output }} infer: { prompt: "Flag what is urgent: ${{ with.inbox }}", max_tokens: 300 } agenda: with: calendar: ${{ tasks.calendar.output }} infer: { prompt: "Plan the day around: ${{ with.calendar }}", max_tokens: 300 } draft: with: notes: ${{ tasks.notes.output }} triage: ${{ tasks.triage.output }} agenda: ${{ tasks.agenda.output }} infer: prompt: "Write the morning brief. Notes: ${{ with.notes }} Urgent: ${{ with.triage }} Plan: ${{ with.agenda }}" max_tokens: 500 save: with: draft: ${{ tasks.draft.output }} invoke: tool: "nika:write" args: { path: ./brief.md, content: "${{ with.draft }}" }outputs: brief: "${{ tasks.draft.output }}"the session ends · the file stays
Seeing the steps is not the same as enforcing them. Here is what each black box trades away.
Chat sessions
A file you keep
Glue scripts
Four verbs
Cloud automations
Your machine
04
One file ran once: audited before it started, storyboarded while it ran, replayable after it ended. Every frame below is real output of the real binary, captured on a terminal against committed fixtures. Offline, on the mock model, for zero dollars. How the proof works · graph, receipt, conformance · lives at /proof.
nika run · replayed at reading pace · nothing staged05
Four verbs are the only native execution models. Every action explicit and typed, never improvised from a hidden prompt. Everything callable is a tool under invoke, and tools are allow-listed. Everything about ordering is the plan: which tasks wait on which. No fifth verb, ever.
5.1infer
Think. Ask any model · local or cloud.
nika: v1workflow: id: thinkmodel: ollama/llama3.2:3btasks: summarize: infer: prompt: "Three risks in this release, ranked"5.2exec
Run. A shell command, captured and typed.
5.2.1 capture & exit codes5.2.2 retry · timeout5.2.3 permitted programs
nika: v1workflow: id: runtasks: build: exec: command: ["cargo", "build", "--release"]5.3invoke
Use a tool. Fetch a page, write a file, call GitHub. Every tool explicit.
5.3.1 builtins5.3.2 extract modes5.3.3 MCP servers
nika: v1workflow: id: use-a-tooltasks: page: invoke: tool: "nika:fetch" args: { url: "https://nika.sh" }5.4agent
Delegate. An autonomous loop, on a leash you can read.
5.4.1 tool allow-list5.4.2 max turns5.4.3 the human gate
nika: v1workflow: id: delegatemodel: ollama/llama3.2:3btasks: audit: agent: prompt: "Find every dead link in ./docs" tools: [ "nika:read", "nika:fetch" ]infer ◇ · exec ▷ · invoke ◆ · agent ✦ · the whole operation space, declared in one file
06
The language stays four verbs. The standard library does the rest: 28 builtins, 17 model providers, and any of your agent tools (MCP servers) your editor already uses. All reached the same way: invoke:, and none of it runs unless the file's permits: allows it.
fetchinvoke:.28 tools · 4 familiesFiles· 5
Data· 9
Web· 1
Flow· 13
provider: ollama runs offline.17 totalLocal runtimes· 5 · no cloud needed
Cloud · open-weight first· 11
Test· 1
fetch turns a page into typed output.9 modesmcp:.native · unboundedevery count derives from the spec's canon.yaml · never hand-typed
07
Frameworks and assistants help an agent decide and act. Nika is orthogonal: it makes what the agent does reviewable and enforceable the layer between the model wants to act and the system lets it act.
Run a Nika plan from any of them. It's a portable file, not a platform.
08
Anything you'd ask an AI to do more than once belongs in a file. Every workflow below is real, projected from nika-spec, audited before it runs: plan, cost, secrets. Pick your métier, open a card, read the exact YAML that runs it.
Ship faster. Let the boring parts run themselves.
nika: v1workflow: id: standup-digest description: "Read yesterday's commits, write today's standup note"model: ollama/qwen3.5:4b # local · zero key · swap for anthropic/claude-haiku-4-5 (fast one-liner job)tasks: # No deps between these two → the engine runs them in parallel. today: invoke: tool: "nika:date" args: { op: now } history: exec: command: ["git", "log", "--since=yesterday", "--oneline", "--no-merges"] digest: with: today: ${{ tasks.today.output }} history: ${{ tasks.history.output }} infer: prompt: | Date · ${{ with.today }} Commits since yesterday · ${{ with.history }} Write my standup note · 3 bullets · done / doing / blocked. Plain words · no fluff. save: with: digest: ${{ tasks.digest.output }} invoke: tool: "nika:write" args: path: "./standup-note.md" content: "${{ with.digest }}"outputs: note: ${{ tasks.digest.output }}Every morning: the note is already written. You glance, you tweak one word, you go.
26 workflows · four tiers · nika-spec/examples/showcase the plan, the typed answers and the tools, all checked before it runs
09
.nika.yaml in VS Code or Cursor: prompts sit on the cards, a run lights the graph wave by wave, and every canvas edit lands back in the file. This miniature works: press ▶ mock.release-notes.nika.yaml · canvashover a card; its lineage stays lit
$0.00 ceiling · local model · audited before it runs
release-notes.nika.yaml
nika: v1
workflow:
id: release-notes
model: ollama/qwen3.5:4b
tasks:
fetch_commits:
invoke:
tool: "nika:fetch"
args:
url: "https://api.github.com/repos/acme/app/commits?since=v1.4.0"
write_notes:
with:
commits: ${{ tasks.fetch_commits.output }}
infer:
max_tokens: 800
prompt: "Write the release notes from ${{ with.commits }}: grouped, human, no hype."
hero_image:
after:
fetch_commits: succeeded
invoke:
tool: "nika:image_generate"
args:
provider: local
prompt: "A minimal banner for the v1.5 release"
aspect_ratio: "16:9"
output_dir: "media/"
publish:
after:
write_notes: succeeded
hero_image: succeeded
exec:
command: ["gh", "release", "create", "v1.5.0", "--notes-file", "notes.md"]
nika check and the schema: codes, fixes and positions are the binary's, not the extension's. Even this demo file passes the real nika check, exit 0.10
A workflow language earns trust by moving, and by not breaking what you wrote. Here is the recent ship log: the spec opened, the verbs locked: and the engine shipped, one brew install away.
The refonte reaches users: the file becomes a map. workflow: is an object (id + description), tasks: is a map keyed by id, and precedence reads the way you think: the binding IS the edge (with: declares the data edge) while control-only ordering is after: {producer: succeeded} against a closed predicate set. The old forms die loudly (a scalar workflow: is NIKA-PARSE-020, depends_on: is NIKA-PARSE-024) and nika check --fix machine-migrates the provable cases. The proof layer opens: semantic hash, nika.lock, receipt_format: 1, workflow composition (invoke: workflow:), and nika:decide, the deterministic decision kernel. One security seat ships in the tag: the lethal-trifecta judge (NIKA-SEC-009) refuses private-read plus untrusted-ingress plus external-egress with no blocking human gate on the path. The LSP learns the map end to end: server-side rename, per-block keysets, semantic_document_format: 1. Every surface you copy from re-baked the same day; what you copy runs on YOUR installed binary.
Moonshot joins the canonical catalog: model: moonshot/kimi-k3 runs first-class (1M context, MOONSHOT_API_KEY), and the pricing snapshot makes --max-cost-usd a hard ceiling on kimi spend (the promotion run metered real cents against a printed worst case). K3 is a thinking model, and the honesty ships with it: reasoning spends output tokens, so budget max_tokens generously; starved, the answer comes back empty and the engine says why. The release branch cut from v0.103.0 on purpose: main is mid-refonte and users never ship a transitional grammar. Also in the wave: the lsp, graph and mcp fixtures that shipped red at the previous tag now speak the argv form, banked as a CI-coverage finding.
The 0.104 release flipped the language: the envelope became a scalar (workflow: my-job), tasks became a sequence (- id: …), and every edge is declared (depends_on — an undeclared tasks.X read now dies at check as NIKA-DAG-003; after: died with it). The public spec still teaches the old form, so the site now runs on TWO spec surfaces and says so out loud: /spec/v1 stays the ratified pin, /spec/shipped vendors what the released binary itself embeds (nika spec --schema and --canon, refreshed deliberately, never probed at build), and /sources renders the grammar gap as a third clock line beside builtins and providers. Everything a visitor can copy speaks the shipped grammar and is PROVEN against the released binary: every hero flagship and its served /library file, the /learn walk and its assembled weekly radar, both install hellos with transcripts re-captured verbatim, the verb chapters, the 404, the /convert proof, and every showcase room and template seed, which stay byte-pure ratified projections and convert at the door through one mechanical, idempotent, line-mapped pass — the plan choreography, the hero highlights and the inspector line anchors all re-aim through the same map, so nothing drifts from the text it animates. The playground lint learned the new world (scalar envelope, sequence tasks, duplicate ids, declared-edge DAG-003, after: refused with its fix line), its break-lesson inverted to teach the flip, and a shipped-ahead door renders text-only until the spec lands the room. Alongside the flip: the engine badge reads v0.104.0 everywhere, the tools register carries decide as ratified-not-shipped with its spec contract on the page, the types register lands its primitives read from the schema itself (the last soon chip dies), the provider catalog re-vendors at 0.104, and the hub headers stop sliding under the fixed nav.
The pre-1.0 window pays for itself: three breaking cuts in one wave, spec and engine merged in the same window. command: is argv-only (execve, no shell: an interpolated value can never break out of its argument) and the string form moves to shell:, the explicit door; semantics ride the field, never the YAML type. A bare ${{ tasks.X }} is now an error (NIKA-VAR-020): name the projection (.output, .status, .error, .duration_ms); the envelope is not a value. And the gate algebra becomes a normative spec table: the when-status pattern is a propagation choice, not redundancy. The whole corpus migrated in lockstep: docs, site, editor, kit and registry re-taught the same day.
11
No logos to borrow, no quotes to dress up. Just what the engine guarantees: review before it acts · enforced permissions · replayable trace · portable off any platform · versioned like code: verifiable in the open spec and the one binary you install.
A README is documentation. Nika is an executable contract.
every number derives from the spec's canon.yaml · verifiable, never hand-typed
12
One binary, one file, one command. Install it, write a plan, run it, and watch it print the plan, check the permits, and stay within bounds.
One Rust binary. Homebrew on macOS, or the install script anywhere.
A plan is plain YAML: the steps, the verbs, the wiring. This is the same file the page just ran, opened at its first step.
nika: v1workflow: id: daily-brief…permits: fs: { read: [ ./notes/* ], write: [ ./brief.md ] } tools: [ "nika:read", "nika:write" ]… notes: { invoke: { tool: "nika:read", args: { path: ./notes/today.md } } }real lines, sliced from the file above · read-only
Point the binary at the file. It prints the plan, checks the permits, and runs within bounds. Every step traced and replayable.
runs everywhere · same file, same result
One Rust binary. The command is the button: click it, paste it, run.
The nika-lang extension: the file as a content-first canvas. Run it live, replay it, audit it before a token is spent, with engine-true diagnostics and completions, in VS Code, Cursor and Windsurf.
nika init writes AGENTS.md · nika wire claude|cursor adds the read-only oracle (nika_check · nika_explain).
WORKS WITH YOUR STACK
Claude Code · Codex · Cursor · VS Code · Hermes · OpenCode · Zed · GitHub Actions · MCP
nika init teaches whatever agent you run. the full integrations map
13
The real objections, and honest answers. No overclaiming, no dunking on the tools you already use.
Seeing the steps is not the same as enforcing the procedure. A README is documentation; a Nika file is an executable contract: versionable, replayable, permission-bound, auditable, and portable off any platform. You do not just read what it intends to do; the runtime holds it to exactly that.
The syntax is plain YAML, but the point is not the syntax. The point is the reviewable, enforced plan. The 4 verbs (infer · exec · invoke · agent) are native execution models, each one explicit and typed, with a permits: block the runtime enforces before anything runs. The file is the control surface, not the formatting.
They live at a different layer. Frameworks help you orchestrate and assistants help an agent act; MCP exposes tools to call. Nika is the contract-and-control layer underneath: it makes the plan reviewable and enforceable, and it runs their tools through invoke, allow-listed and traced. It complements them rather than replacing them.
Local-first, any model: fully local, or any API. Run a local model and nothing leaves at all. Every plan declares its network egress in its permits: block, and it is default-deny: omit the hosts and the workflow physically cannot reach the network. The file states exactly what can leave, and the runtime enforces it. 5 of the 17 providers are local.
Honest answer: Nika is early. Real semver toward a 1.0 launch, currently at v0.105.0, shipping in the open. It is one Rust binary you can install and run today. The engine is AGPL-3.0-or-later; the spec is Apache-2.0. We would rather you trust the spec and the binary than a maturity claim we have not earned yet.
the noise becomes the file.
14
Install the binary, write the plan as a file, review what it's allowed to touch, run it. Same file, same result, enforced on your machine. Tomorrow, and the day the vendor is gone.
Do you repeat an AI task every week, in ChatGPT, Claude, Cursor, Codex, or scripts? Send it. We convert the best ones into runnable .nika.yaml examples, credited to you.