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 ]
The checked workflow language for AI: audited before a token is spent, every fix named (the typo, the unbounded cost, the permit wider than the code), with a verifiable receipt after every run. The runtime enforces the file as a contract: permits default-deny, the trace hash-chained, cost capped.
The checked workflow language for AI: audited before a token is spent, a receipt after every run. The file is a contract the runtime enforces.
v0.111.0macOS · LinuxAGPL-3.0
nika: 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 }}"sourceaudited · 7 tasks · 4 waves · permits declarednika 0.111.0
nika check: audited · 7 tasks · 4 waves · permits declarednika 0.111.0
[ THE READING ]
01
An agent’s draft, two keystrokes early. Step through what the checker saw before a token was spent, then the repair: applied by the engine itself, not by hand.
two keystrokes · 3 findings · zero tokens spent
error:CONFORMNIKA-DAG-002
unknown dependency: task judge depends on dif, which does not exist — did you mean diff?
begin snippet for pr-review.broken.nika.yaml at line 19, column 16
changes: ${{ tasks.dif.output }}
helpnika explain NIKA-DAG-002
the first keystroke: a reference to a task that does not exist, and the checker names what you meant
01
nika: 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 }}"audited · 7 tasks · 4 waves · permits declarednika 0.111.0
[ 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.)
the written law · numbered, public, reviewable
the full set lives in the spec’s governance register · proposals, numbered and versioned, before they bind the engine
03
nika: 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 }}"audited · 7 tasks · 4 waves · permits declarednika 0.111.0
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 staged
05
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.
# the infer verb alone · one bounded model callnika: thinkmodel: ollama/llama3.2:3bpermits: {}tasks: summarize: infer: prompt: "Three risks in this release, ranked" max_tokens: 256outputs: summary: ${{ tasks.summarize.output }}5.2exec
Run. A shell command, captured and typed.
5.2.1 capture & exit codes5.2.2 retry · timeout5.2.3 permitted programs
# the exec verb alone · one program, argv form, allowlistednika: runpermits: exec: ["cargo"]tasks: 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
# the invoke verb alone · one tool call under the two-part grantnika: use-a-toolpermits: tools: ["nika:fetch"] net: { http: ["nika.sh"] }tasks: 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
# the agent verb alone · goal + tool whitelist, loop boundednika: delegatemodel: ollama/llama3.2:3bpermits: tools: ["nika:read", "nika:fetch"] net: { http: ["nika.sh"] } fs: read: ["./docs/*"]tasks: audit: agent: prompt: "Find every dead link in ./docs" tools: [ "nika:read", "nika:fetch" ] max_tokens_total: 8192outputs: report: ${{ tasks.audit.output }}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
Every seat nika can hold ships inside the binary: models, prices, measured energy, MCP servers. This site vendors those tables from the engine at v0.111.0 and gives every member a room you can open · each number below derives from the vendored bytes, none is typed.
08
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.
09
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: standup-digestmodel: ollama/qwen3.5:4b # local · zero key · swap for groq/llama-3.3-70b (a fast one-liner job)permits: exec: ["git"] # the ONE program this workflow may run tools: ["nika:date", "nika:write"] fs: { write: ["out/standup-note.md"] }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: max_tokens: 400 # a standup note is short · the ceiling says so prompt: | Date · ${{ with.today }} Commits since yesterday · ${{ with.history }} Write my standup note · 3 bullets · done / doing / blocked. Plain words · no fluff. If there are no commits, say so in one line. save: with: digest: ${{ tasks.digest.output }} invoke: tool: "nika:write" args: path: out/standup-note.md create_dirs: true content: "${{ with.digest }}"outputs: note: ${{ tasks.digest.output }}audited · 4 tasks · 3 waves · permits declarednika 0.111.0
Every morning: the note is already written. You glance, you tweak one word, you go.
27 workflows · four tiers · nika-spec/examples 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: 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: success
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: success
hero_image: success
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.11
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 checker now distinguishes a legal file from one ready to spend: paid_ready goes quiet only when no paid-run hint remains, compiled means the law was proven, and next names the first repair. nika:inspect is live from the first task and follows the run after every wave, so a workflow can read its DAG, records, spend and threads. nika:compose stays inside an agent loop after nika:done, where the model can draft, read the whole check verdict and revise without making check execute the draft. The arm lock now survives the shot, and the paid extract wave repairs structured hashing, string schemas, scalar anyOf coercion and resumed for_each item fields.
The envelope is nine keys: nika: is the mark and the name, never a version marker. Values come from three authorities (inputs · const · secrets). The first two 0.109 tags died at the release gates before any asset landed; this is the one a visitor can install. Linux builders now install bubblewrap so a permits: file is judged confined, the same way macOS already was.
The access layer arrives, and the check stops trusting what it cannot read. From this tag, model: picks the intelligence and access picks the path: the admission-time resolver is a pure function with a strict sovereign order (local · mock · harness · oauth · api), enumeration order can never change the outcome, and every dropped candidate carries its witness: the dimension, the layer, the teaching line. --access pins the path at the launch gates, before a token is spent: unsatisfied refuses, never substitutes. The ACP harness class lands with its mock agent in a quarantined workspace, proving the wire with no vendor in the loop. And the security fix that taught the train its lesson: a shell-string exec with no permits passed check green. The verifiable argv door refused while the unverifiable shell door stood open, the exact inversion of a security gate. The exec capability is now required whatever the spelling, and the runtime deferral owns dynamic values, never the door itself.
An adversary was set on 0.107.1’s six boundaries with one instruction, break it. Three of six claims fell, and hunting what the first repairs still let through found four more · every fix proven on the binary before the code was touched, each carrying the mutation that makes it fail. Two doors were open: a permit could name a system root by shouting it (/ROOT/x* passed where /root/./x* refused: macOS folds case, so the comparison does now, and the Linux side can only ever refuse a path that does not exist), and a secret redacted in the trace was printed on stdout (outputs: rides RunOutcome past the event lane · the map is scrubbed at any depth now). Three guards judged less than they claimed: the dangerous-environment floor is proven entry by entry (forty names, both halves pinned), a tainted nika:notify target is judged whatever channel carries it, and the run guard speaks only about runs (an unreadable payload no longer blocks ls). And one law had two implementations: the MCP spawn now composes its child environment through the same function as the exec runner · the copy was equivalent, and equivalence was exactly what nothing guaranteed.
12
No logos to borrow, no quotes to dress up. Just what the engine guarantees: review before it acts · enforced permissions · hash-chained trace · priced before it spends · crash-resume without a server · portable off any platform: 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 · every repo file declares its provenance in estate.yaml · authored or generated, hashed
13
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.
Installed? See a workflow work before anything else · offline, zero keys, nothing written:
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: daily-brief# local model · your notes never leavemodel: ollama/llama3.2:3b…permits: fs: { read: [ ./notes/* ], write: [ ./brief.md ] } tools: [ "nika:read", "nika:write" ]… notes: { invoke: { tool: "nika:read", args: { path: ./notes/today.md } } }audited · 7 tasks · 4 waves · permits declarednika 0.111.0
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
14
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.111.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.