AGPL-3.0-or-later · forever.

the skeleton register

chain

Your intent sounds like: take data, produce words, save them One of 10 skeletons the pack ships, green as-is under nika check. Fill the # SLOT: lines, repair from the fix lines, re-check. Machines read the catalog; the binary lists them with nika new '?'.

  • 11SLOT pointsthe only lines you edit
  • 3patterns lockedarrive correct, stay correct
  • 2tools grantedthe minimum for the job
  • 2ffe6e1esha-pinnedre-hashed on every test run

the plan

derived from the file, never drawn by hand
gatherinvoke
thinkinfer
persistinvoke

the file, whole

conformance-gated upstream on every spec push
chain.nika.yaml
# SPDX-License-Identifier: Apache-2.0# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json## TEMPLATE · chain · gather facts → one model step → persist.##   [gather]────▶[think]────▶[persist]#   nika:read     infer       nika:write## The everyday shape: pull in something real, run exactly ONE model job over# it, write the result somewhere you can find it again. Most useful workflows# are this and nothing more — reach for a bigger skeleton only when this one# genuinely cannot hold the job.## Demonstrates ·#   - the three-verb spine · invoke → infer → invoke#   - `with:` as the DAG edge · a task reads its input from a binding, and the#     binding IS what orders the graph (nobody writes the arrows)#   - `on_error: recover:` · the skeleton rehearses green in an empty#     directory, before it is pointed at real data## Needs · nothing. Scaffolds and runs green in an empty directory.## Run · nika run <file> --model mock/echo    # offline rehearsal · zero keysnika: v1workflow:  id: chain-template            # SLOT: kebab-case workflow id  # SLOT: one honest sentence — `nika new` matches intents against these words  description: "gather → think → persist"# SLOT: provider/model · local · zero key. Measured seat — `ollama/llama3.2:3b`# answers this prompt in ~50s. `--model mock/echo` needs no seat at all and is# the path this file guarantees.model: ollama/llama3.2:3bconst:  source: "./README.md"             # SLOT: your input · README.md exists in most repos  destination: "./output.md"        # SLOT: where the result landspermits:                            # the blast radius · default-deny once present  tools: ["nika:read", "nika:write"]  # Two literal paths, one each way. `check` can read these (they come from  # `const:`) and proves them against the boundary before anything runs — keep  # them in step with the two `const:` entries above.  fs:    read: ["./README.md"]    write: ["./output.md"]tasks:  gather:    invoke:                         # SLOT: the fact source · nika:read / nika:fetch / exec      tool: "nika:read"      args: { path: "${{ const.source }}" }    on_error:      # Offline rehearsal. A freshly scaffolded directory has no README, and a      # skeleton that dies on its first run teaches nothing — so a not-found      # recovers into a literal standing in for the real document. `on_codes:`      # keeps that narrow: ONLY not-found is forgiven, a permission error      # still fails loudly. Delete this block once the source really exists.      # In an EMPTY directory check prints one [inputs] hint that this read      # would fail — the run does not: this recover carries it (measured ·      # rc=0 · « 1 recovered »). The hint retires once your source exists.      on_codes: [NIKA-BUILTIN-READ-001]      recover: "REHEARSAL · no source document here yet."  think:    with:      gather: ${{ tasks.gather.output }}    infer:      max_tokens: 800               # SLOT: the spend ceiling · sized for the SEAT, not the answer      # SLOT: the one model job. Keep slot markers OUT of the block below —      # everything indented under `prompt: |` is prompt TEXT, so a stray      # `# SLOT:` line is sent to the model verbatim (visible in the mock      # echo). Comments belong out here, where YAML eats them.      prompt: |        Summarize the following in a short paragraph.        ${{ with.gather }}  persist:    with:      think: ${{ tasks.think.output }}    invoke:      tool: "nika:write"      args:        path: "${{ const.destination }}"   # SLOT: destination · same path as permits.fs.write        content: "${{ with.think }}"       # ALWAYS pass content · a write without it writes nothingoutputs:  result: ${{ tasks.think.output }}  # SLOT: the callable contract

sha256 2ffe6e1e85a5616b. The copy above re-hashes to its pin on every test run (a copy is re-provable, never trusted). Source: chain.nika.yaml in the spec pack · open it in the playground →

the patterns it locks

  • deterministic gather
  • one model job
  • explicit persist

Scaffold it locally: nika new chain my-flow.nika.yaml. Try the shape in the playground, or walk the showcase for the same patterns on real work. Read the spec →