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
[ NIKA · CHECKED WORKFLOWS FOR AI ]
No key. No account. No server to keep running. Install the binary, then run the command it prints as next. On v0.114.0 that is nika try 01-hello. It finishes in a second, and says out loud that it was a rehearsal.
No key. No account. No server. Install, then nika try 01-hello.
✔ greet infer · mock/echo 0ms rehearsal · a mock model echoed the prompt — not a real answer
v0.114.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:assert", "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: "Urgent item, 10 words max: ${{ with.inbox }}", max_tokens: 2048 } agenda: with: calendar: ${{ tasks.calendar.output }} infer: { prompt: "Three bullets, 8 words max: ${{ with.calendar }}", max_tokens: 2048 } draft: with: notes: ${{ tasks.notes.output }} triage: ${{ tasks.triage.output }} agenda: ${{ tasks.agenda.output }} infer: prompt: "Three bullets, 8 words max. No preface. Notes: ${{ with.notes }} Urgent: ${{ with.triage }} Plan: ${{ with.agenda }}" max_tokens: 2048 verify: with: draft: ${{ tasks.draft.output }} invoke: tool: "nika:assert" args: condition: "${{ size(with.draft) > 0 }}" message: "Empty brief. Increase max_tokens or change model." save: after: { verify: success } with: draft: ${{ tasks.draft.output }} invoke: tool: "nika:write" args: { path: ./brief.md, content: "${{ with.draft }}" }outputs: brief: "${{ tasks.draft.output }}"sourceaudited · 8 tasks · 5 waves · permits declared · 2 hintsnika 0.114.0
Nika does not hide an agent behind chat. The source, permissions, live stages and final receipt stay visible in one operational frame.
Declared capabilities bound the possible run.
Every step remains inspectable in order.
Evidence closes around what actually happened.
checkednika: release-review
permits:
fs.write:
- out/receipt.md
tools:
- nika:read
- nika:write
tasks:
review:
agent: bounded3 / 4inferclassify intent1.82sinvokeread repository184msagentreview boundaryactiveexecwrite receiptqueued[ 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? · → nika explain NIKA-DAG-002
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:assert", "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: "Urgent item, 10 words max: ${{ with.inbox }}", max_tokens: 2048 } agenda: with: calendar: ${{ tasks.calendar.output }} infer: { prompt: "Three bullets, 8 words max: ${{ with.calendar }}", max_tokens: 2048 } draft: with: notes: ${{ tasks.notes.output }} triage: ${{ tasks.triage.output }} agenda: ${{ tasks.agenda.output }} infer: prompt: "Three bullets, 8 words max. No preface. Notes: ${{ with.notes }} Urgent: ${{ with.triage }} Plan: ${{ with.agenda }}" max_tokens: 2048 verify: with: draft: ${{ tasks.draft.output }} invoke: tool: "nika:assert" args: condition: "${{ size(with.draft) > 0 }}" message: "Empty brief. Increase max_tokens or change model." save: after: { verify: success } with: draft: ${{ tasks.draft.output }} invoke: tool: "nika:write" args: { path: ./brief.md, content: "${{ with.draft }}" }outputs: brief: "${{ tasks.draft.output }}"audited · 8 tasks · 5 waves · permits declared · 2 hintsnika 0.114.0
[ 01 ]run together ×3
[ 02 ]run together ×2
[ 03 ]then
[ 04 ]then
[ 05 ]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
[ 05 ]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:assert", "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:assert", "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: "Urgent item, 10 words max: ${{ with.inbox }}", max_tokens: 2048 } agenda: with: calendar: ${{ tasks.calendar.output }} infer: { prompt: "Three bullets, 8 words max: ${{ with.calendar }}", max_tokens: 2048 } draft: with: notes: ${{ tasks.notes.output }} triage: ${{ tasks.triage.output }} agenda: ${{ tasks.agenda.output }} infer: prompt: "Three bullets, 8 words max. No preface. Notes: ${{ with.notes }} Urgent: ${{ with.triage }} Plan: ${{ with.agenda }}" max_tokens: 2048 verify: with: draft: ${{ tasks.draft.output }} invoke: tool: "nika:assert" args: condition: "${{ size(with.draft) > 0 }}" message: "Empty brief. Increase max_tokens or change model." save: after: { verify: success } with: draft: ${{ tasks.draft.output }} invoke: tool: "nika:write" args: { path: ./brief.md, content: "${{ with.draft }}" }outputs: brief: "${{ tasks.draft.output }}"audited · 8 tasks · 5 waves · permits declared · 2 hintsnika 0.114.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 first run needs none of this. After that, the language stays four verbs. The standard library does the rest: 28 builtins, the seats nika catalog lists, and any agent tools 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.114.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.114.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.
Default nika serve stays the resident firer. --bind with --workflows and --token-file opens authenticated loopback HTTP: jobs, SSE and OpenAPI. Cancel, artifacts and POST /v1/run stay 404. A project file names itself (nika: my-project); nika: v1 on nika.yaml is the retired schema tag.
The six origin records now read as one continuous arc, from the first objection to the name. The series page replaces its generic card grid with a dated ledger that shows reading order, source receipts and the evidence boundary before a reader opens a chapter.
Four retrospective field notes fill the public story from November through February without pretending they were published then. Story dates and publication dates are now separate across the journal, feeds and structured metadata, and six records form one origin series from the first note to the name.
The SDK now connects nika.yaml project setup, local runs, runtime proof and the resident firer, separate from the future HTTP service.
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
No key, no account, and nothing left running in the background. Install the binary, run the command it prints as next: nika try 01-hello. Then keep a file. It prints the plan, checks the permits, and stays 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:assert", "nika:write" ]… notes: { invoke: { tool: "nika:read", args: { path: ./notes/today.md } } }audited · 8 tasks · 5 waves · permits declared · 2 hintsnika 0.114.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.114.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.