Skip to main content
Nika is a small language ( verbs, namespaces, one expression surface), so good Nika is mostly about shape: where the determinism lives, where the model is allowed to think, and how data crosses task boundaries. These thirteen patterns are how the showcase workflows are written. Each links the canonical example that embodies it.
The spec’s normative cousin of this page is the one-obvious-way table (linter rules one-obvious-way/002-007 + /010 — /001 is retired, rule ids are never reused). This page is the engineering judgment around those rules.

1 · Deterministic core, model at the edges

jq decides; the model explains. Anything that can be computed (filtering, sums, diffs, ranking) happens in nika:jq or a data builtin, deterministically and for free. The model gets the jobs only a model can do: judgment, language, synthesis.
Anti-pattern · asking the model to « pick the urgent ones » from a list it already returned typed. You pay tokens for worse determinism. Taught by · Price watch (zero model calls) · Config drift sentinel · ETL quarantine.

2 · Parallelism is the default · edges are the only ordering

Tasks with no edge between them run together. Don’t serialize out of habit: a with: binding IS the data edge (data and dependency are one declaration) and an after: entry is the control edge — the engine computes the graph from those two doors and schedules the waves. The DAG has no invisible edges: a tasks.* reference outside the boundary is NIKA-VAR-021 (the fix is machine-applicable — hoist into with:). Anti-pattern · a linear chain of tasks that never read each other’s output. That is wall-clock spent on nothing. Taught by · Standup digest · Social repurpose (the diamond) · CEO Monday brief (3-branch gather).

3 · Type the boundaries

Every place data crosses from a model into the deterministic world gets a contract: schema: on infer/agent (the model must return that shape), nika:validate for second opinions, nika:assert as the hard gate. Enums kill « kinda-strong » ratings. Anti-pattern · prose in, prose out, regex in the middle. If a downstream task indexes into a field, the producer needs a schema. Taught by · Meeting actions · Contract guard (schema + validate + assert, belt-and-braces) · Support triage (enums).

4 · Fan out with a leash

for_each over a runtime collection is the power move. Bound it with max_parallel (providers rate-limit, GPUs thrash), make it resilient with fail_fast: false (collect errors instead of aborting the batch) and give each iteration its own retry and timeout. Anti-pattern · an unbounded fan-out against a rate-limited API, or leaving fail_fast on its default (true) for a batch where one bad item is normal. Taught by · Competitor radar · Localization factory · Resume screener.

5 · Plan, then execute

For open-ended work: a fast model writes a typed plan, an agent: executes it under budgets, a thinking model synthesizes. Three stages, three cost profiles, every intermediate auditable on disk. Anti-pattern · one giant agent loop with no plan, no budget and no typed output. Nothing about it can be audited or bounded. Taught by · Deep research brief · Corpus digest.

5b · Context folding · fresh windows over a folded corpus

The corpus-scale variant of « plan, then execute »: when the input is too big for one window, never let any window hold it. A fast pass plans a focus per unit from identifiers alone (paths, titles, ids · never contents), a for_each wave processes each unit in its own fresh window (one slice, one focus, zero history), jq merges and ranks the typed results deterministically, and the synthesis reads only that deck. The corpus lives in the dataflow; window size stops scaling with corpus size, and the deck in outputs: is an audit surface: everything the synthesis saw, nothing it didn’t. Two disciplines make the fold sovereign. The discovered collection is the spine · model assignments join it by key, so a hallucinated identifier drives nothing. And the per-unit schema carries no identifier · transpose re-attaches identity, so a model cannot misfile its own result. Anti-pattern · concatenating the corpus into one prompt and asking for a summary. Past a few documents you pay more for worse recall, and nothing in the middle was actually read. Taught by · Corpus digest · Localization factory (the mechanical fold · no plan stage).

6 · Three gates, three meanings

  • when: · the skip gate. Routing, not failure (skipped ≠ failed).
  • nika:assert · the fail-fast gate. The run is wrong, stop loudly.
  • nika:prompt · the human gate. Blocks until a person decides.
Pick the gate that matches the meaning. A workflow that sends drafts without a prompt gate, or claims success without an assert, is promising more than it checks. Anti-pattern · a tasks.* status test inside when: as a plain success gate. That form is illegal since W2 (NIKA-VAR-021): the gate lives on the edges — a value edge already admits on {success, skipped}, and after: { a: success } is the strict form. when: decides business conditions on local values, post-gate. Taught by · Invoice chaser (human gate) · Incident war room (assert refuses optimistic postmortems) · Release train (all three in one file).

7 · Sovereignty is a model: line

Sensitive data (contracts, CVs, medical, financial) runs on a local provider, ollama/… or lmstudio/…. Same file shape, zero cloud. The canonical providers make this a one-line decision, not an architecture meeting. Taught by · Contract guard · Resume screener.

8 · Agents get budgets, tools get grants

agent: is default-deny: no tools: means no tools at all. Grant the minimum (nika:read + nika:done makes a read-only reviewer), cap the loop (max_turns · max_tokens_total), and let nika:done end it cleanly. Anti-pattern · tools: ["nika:*", "mcp:*/*"] on an agent that only needed to read. Least privilege costs one line less. Taught by · PR review fan-out (the read-only swarm) · Code review (foundation).

9 · Evidence always lands · on_finally

Two tools, one rule. on_finally: is per-task cleanup: it fires when that task ran (success, failure, timeout, mid-flight cancel). A terminal after: {…: terminal} task is the always-pattern: it runs once its producers settle, whatever happened — terminal admits every terminal state (success · failure · skipped · cancelled). Pair it with a .status observation in with: when the body branches on the outcome. Cleanup belongs to the task; the record that must land at 3am belongs to a terminal task. Taught by · Incident war room · Release train (the departure record is an after: { live: terminal } task: it lands even when the train aborts).

10 · One data language · jq, once

output: bindings, nika:jq, the fan-in zip (transpose), the state diff: all of it is the same jq. Don’t invent per-task string parsing and don’t ask the model to reshape JSON. One transform language is already there. Taught by · Localization factory (the transpose zip) · ETL quarantine (group_by accounting).

11 · Workflows are callable · type the outputs:

A workflow with typed outputs: is a building block: another workflow (or a human, or CI) consumes a contract, not a log. Name what comes out, type it, describe it. Taught by · Deep research brief ({brief, sources}) · Schema retry (foundation · the typed-outputs shape).

12 · Mock-first · runnable with zero keys

model: mock/echo makes a workflow CI-runnable and demo-safe. Write it mock-first and swap the provider when it ships. Every example in this documentation that can run without keys does. Anti-pattern · a workflow you can’t validate without spending real tokens. The conformance gate runs every example on every push, and yours should pass the same way. Taught by · the whole examples pack, and the state-file pattern makes even stateful workflows replayable.

The shape of a well-written file

the shape · a skeleton, not a runnable file

Four recipes the patterns compose into

The 12 patterns are the values; these are the moves you reach for when a real integration pushes back. Each is canonical in the spec: linked, not improvised.

Poll until ready

retry: fires on errors, never on values, so make « not ready » an error inside the task. A jq-mode fetch whose program errors on the pending shape turns polling into typed, bounded retry:
Bounded, deterministic, zero LLM. (retry_when:, retrying on a value condition directly, is reserved for a future minor.)

Diamond join

Two exclusive when: branches, one consumer. A skipped branch’s output is defined null (never an error), so the join is one jq filter:

Fan-out that survives partial failure

A failed iteration contributes null at its index: positions stay aligned with the input. Recover per-iteration when a placeholder is acceptable, filter downstream. The same on_error: recover: mechanism also degrades a single task to a declared fallback — the run completes and the output says what it is:

Matrix expansion

A matrix is precomputed data, not control flow. Build the product with jq, fan out over it:
Include/exclude rules are jq filters on the product: no second syntax to learn.

See every pattern live

The examples gallery: real jobs, every construct taught, every file conformance-validated.