the deterministic authoring path
fanout
Agents do not invent structure — they instantiate it. Each skeleton below is a complete, valid workflow with # SLOT: markers at every decision point: route your intent, copy, fill the slots, nika check, done. Machines read the same pack at /templates/catalog.json; the originals live in the spec, conformance- gated on every push.
- 10skeletonsroute · copy · fill
- 94slot markersthe only blanks
- validevery fileconformance-gated
- sha256every copypinned, re-proven
take data, produce words, save them
- deterministic gather
- one model job
- explicit persist
watch X, act when Y
- jq extraction
- CEL skip-gate
- often zero model calls
do this for EVERY item
- runtime collection
- the full leash (max_parallel
- fail_fast
- retry)
fanout.nika.yaml# SPDX-License-Identifier: Apache-2.0# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json## TEMPLATE · fanout: discover a collection, process every item in# parallel (with a leash), merge the results.# The default shape for « N pages / N files / N records » jobs.## Instantiate · copy → fill `# SLOT:` → `nika check` → repair → re-check.# The leash (pattern 4 · ALWAYS set all three) ·# max_parallel cap concurrency (APIs rate-limit · GPUs thrash)# fail_fast: false + on_error recover: null: one bad item yields null# at its index (order preserved) · the batch lives · filter downstream# per-iteration retry/timeout · the flaky world is normalnika: v1workflow: fanout-template # SLOT: kebab-case workflow iddescription: "discover N items · process in parallel · merge" # SLOTmodel: ollama/qwen3.5:4b # SLOT: provider/model · local · zero keyvars: collection_source: "./items" # SLOT: where the collection comes fromtasks: - id: discover invoke: # SLOT: glob / fetch sitemap / exec + jq split tool: "nika:glob" args: { pattern: "${{ vars.collection_source }}/*.md" } - id: process depends_on: [discover] for_each: ${{ tasks.discover.output }} max_parallel: 4 # SLOT: the polite ceiling fail_fast: false timeout: "60s" # SLOT: per-iteration bound retry: max_attempts: 3 backoff_strategy: exponential jitter: true on_error: recover: null # a failed item yields null at its index · the batch lives infer: # SLOT: the per-item job (any verb) prompt: | Process this item · ${{ item }} - id: survivors depends_on: [process] invoke: # the null-aware fan-in · order preserved tool: "nika:jq" args: input: ${{ tasks.process.output }} expression: "[ .[] | select(. != null) ]" - id: merge depends_on: [survivors] infer: prompt: | # SLOT: the fan-in · the survivors array (failed items filtered) Merge these results into one report · ${{ tasks.survivors.output }}outputs: report: ${{ tasks.merge.output }}sha256 7a16dcdb72c0c3ce… · conformance-gated upstream · source
only what changed since last run · survive bad input
- state read→diff→write
- `on_error: recover:` quarantine
research / review / open-ended
- plan-then-execute
- default-deny tools
- budgets
- typed final message
anything irreversible (deploy · send · publish)
- parallel gates
- assert
- `nika:prompt` GO
- `on_finally` record
understand a site (domain · theme · assets) from a URL
- fetch `traverse:` crawl
- one typed infer
- explicit persist
- zero exec
generate image/audio assets from a brief
- `nika:image_generate`
- `nika:jq` manifest
- local/mock provider first
call a product API: upload a file, then create from it
- fetch `multipart:` upload
- masked secrets header
- mode/jq extraction
read a system's state (docker · kubectl · gh), explain it, keep the report
- argv-array exec (provable allowlist)
- parallel reads
- exec ledger
- one artifact
The protocol is mechanical: route with the phrases above, instantiate, fill the slots, then nika check teaches anything you missed before anything runs. Try one in the playground, or install and start from a real file. Read the spec →