AGPL-3.0-or-later · forever.

the language reference

The contract an agent must satisfy before it acts.

The contract. Frozen forever.

A .nika.yaml is the plan, written down: one envelope, four verbs, a typed task shape, and a permits block that declares, and bounds, everything it's allowed to touch. You review it, the runtime enforces it, then it runs. This is the friendly map; the nika-spec repository is the canonical, normative source.

SCROLL·····READ TO ASSEMBLE

tl;dr · 5 pillars

The whole language, at a glance.

01 · the envelope
The format is frozen. One version marker, forever: files you write today won’t break.
nika: v1
02 · the verbs
Four moves cover everything. Think · run a command · use a tool · delegate.
infer · exec · invoke · agent
03 · the plan (dag)
Tasks, and the wires between them. Independent tasks run at the same time, automatically.
with · after
04 · variables
Answers thread by name. One task’s output becomes the next task’s input.
${{ }}
05 · errors
Failures come back typed. A stable code, plus whether retrying could help.
NIKA-*
S.0

The envelope

10 top-level keysthe keyword register

Every file opens with nika: v1, one version marker, pinned for the v1 lifetime. No v1.0, no v2 migration. Three keys are required; the rest are optional.

  • nikarequiredthe version marker · exactly v1, forever
  • workflowrequiredthe workflow id · kebab-case · unique in the file
  • tasksrequiredthe DAG · one or more nodes, each binding one verb
7 optional keys
  • descriptionoptionala human note · free text
  • modeloptionalthe default model · provider/name (e.g. ollama/llama3.2:3b)
  • varsoptionaltyped inputs · ${{ vars.X }} · with required / default
  • envoptionalnon-sensitive runtime config · ${{ env.X }}
  • secretsoptionalvault-backed references · never inline literals
  • permitsoptionalthe capability boundary · default-deny once present
  • outputsoptionalthe return value · ${{ tasks.X.output }} · symmetric to vars

A real file · standup-digest

standup-digest.nika.yaml
nika: v1workflow:  id: standup-digest  description: "Read yesterday's commits, write today's standup note"model: ollama/qwen3.5:4b   # local · zero key · swap for anthropic/claude-haiku-4-5 (fast one-liner job)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:      prompt: |        Date · ${{ with.today }}        Commits since yesterday ·        ${{ with.history }}        Write my standup note · 3 bullets · done / doing / blocked.        Plain words · no fluff.  save:    with:      digest: ${{ tasks.digest.output }}    invoke:      tool: "nika:write"      args:        path: "./standup-note.md"        content: "${{ with.digest }}"outputs:  note: ${{ tasks.digest.output }}
S.1

The four verbs

4 · locked foreverthe verb rooms

A verb is a distinct native execution model. A task binds exactly one. That is the whole operation space: fetch, recall, db and files are tools reached under invoke:, not verbs.

inferCall a model

Call a model. Any of the providers; structured output when you give it a schema.

yaml
research:  infer:    prompt: "Research ${{ vars.topic }}"
execRun a process

Run a real process. stdout becomes the output; a non-zero exit becomes an error.

yaml
build:  exec:    command: ["cargo", "build", "--release"]
invokeCall a tool

Call a tool · a nika: builtin or an mcp: server. Default-deny, args schema-checked.

yaml
read_config:  invoke:    tool: "nika:read"    args:      path: ./config.yaml
agentDrive a loop

Drive an autonomous tool-use loop, bounded by max_turns and a whitelist of tools.

yaml
research:  agent:    prompt: "Research ${{ vars.topic }}"    tools: ["nika:fetch"]
S.2

The task shape

1 identity · 1 verb · 2 doorsthe keyword register

A task is a DAG node. Its map key is its identity and exactly one verb binds;tasks.* crosses the boundary through two doors — with (data) and after (control); everything else is an optional structural control.

anatomy of a task node
summarize:the key IS the identityinfer:1 of 4 verbs prompt: "… ${{ with.notes }} …"with:the data edges notes: ${{ tasks.extract.output }}after: { lint: success }the control edgeswhen: ${{}}a local test

required coreoptional controls

  • ‹key›reqthe map key IS the identity · snake_case · CEL-safe · unique
  • ‹verb›reqexactly one of infer · exec · invoke · agent
8 optional controls
  • withoptthe data door · each ${{ tasks.X }} binding IS a typed edge
  • afteroptthe control door · {producer: success|failure|skipped|terminal}
  • whenopta local CEL yes/no (or true/false) · post-gate · false → skipped
  • for_eachoptmap the task over a collection
  • max_paralleloptcap concurrent for_each iterations · 1 = sequential
  • timeoutopta quoted Go-duration · e.g. "30s" "5m" "1h30m" · max 24h
  • retryoptattempts + backoff on a transient failure
  • on_erroroptfallback · recover from another task · or continue
S.3

The permits

default-deny once presentits register row

The capability boundary: the contract an agent must satisfy before it acts. Once permits: is present, every category is default-deny: which files it can read, which it can write (read XOR write), which hosts it can reach, which programs it can run, which tools it may call. The runtime enforces it: out of bounds is denied, not logged after the fact.

the plandefault-deny
  • fs.read / fs.write
  • net.http
  • exec
  • tools
fs.read / fs.write
which files it can read, which it can write: read XOR write, by glob.
net.http
which hosts it can reach. Omit the category and the plan cannot touch the network at all.
exec
which programs it can run: none, any (blocklist-gated), or a named allowlist.
tools
which nika:/mcp: tools it may call. Anything off the list is unreachable.

denied, before it runs

  • NIKA-SEC-004effect outside the declared permits: boundary (fs / net / exec / tool)
  • NIKA-SEC-002agent tool call outside the tools: whitelist
  • NIKA-SEC-001exec: blocklist hit

See it felt, not told: toggle a permit and watch the runtime obey.

S.4

The standard library

28 builtins · 5 familiesthe tool rooms

28 builtin tools, all reached with invoke: · nothing to install. Grouped by what they touch.

Files· 5

readwriteeditglobgrep

Data· 10

jqconvertvalidatejson_diffjson_merge_patchcomposehashuuiddatechart

Web· 1

fetch

Media· 3

image_generatetts_generateimage_fx

Flow· 9

assertdonewaitemitlognotifypromptinspectdecide
S.5

Providers

17 · 5 local · 11 cloud · 1 mockthe provider register

Pick per task or per file. Local-first: provider: ollama runs offline, no key. Cloud is open-weight-first (Mistral leads).

Local runtimes· 5 · offline

OllamaLM Studiollama.cppLocalAIvLLM

Cloud · open-weight first· 11

MistralAnthropicOpenAIGeminiDeepSeekxAIGroqOpenRouterHugging FaceNVIDIAmoonshot

Test· 1 · deterministic

mock
S.6

Extract modes

9 modes on fetchthe fetch room

How nika:fetch turns a page into typed output: from raw text to a parsed article, feed or jq projection.

articlefeedjqlinksmarkdownmetadataselectorsitemaptext
S.7

Error namespaces

21 namespaces · 82 codesthe error registry

Every failure carries a typed code (NIKA-‹NS›-NNN) across 21 namespaces, with a category and a transient flag. The full registry lives in errors/catalog.json.

  • NIKA-AGENTagent: the loop budget
  • NIKA-ASSERTsee the error catalog
  • NIKA-BUILTINbuiltin tool argument contracts
  • NIKA-CANCELtask or workflow cancellation
  • NIKA-COMPsee the error catalog
  • NIKA-DAGDAG topology · cycles · invalid deps
  • NIKA-EXECexec: the local process
  • NIKA-IMPLengine-internal errors
  • NIKA-INFERinfer: the model call
  • NIKA-INVOKEinvoke: the tool call
  • NIKA-LOCKsee the error catalog
  • NIKA-MCPMCP client + transport
  • NIKA-DECIDEsee the error catalog
  • NIKA-PARSEYAML parse + envelope validation
  • NIKA-POLICYsee the error catalog
  • NIKA-PORTsee the error catalog
  • NIKA-PROVIDERprovider adapter failures
  • NIKA-SECsecurity policy · SSRF · whitelist · permits
  • NIKA-TIMEOUTtask or step timeouts
  • NIKA-TYPEsee the error catalog
  • NIKA-VARvariable + expression resolution
S.8

License + invariants

locked, forever

The contract you can count on: the parts that never change.

  • S.8aReal semver toward 1.0

    The engine ships on real semver: currently v0.105.0, toward a 1.0 launch. The language envelope stays nika: v1, frozen forever.

  • S.8bFour verbs, locked

    infer · exec · invoke · agent: a closed set, locked forever. New capability arrives as a tool, never a fifth verb.

  • S.8cThe spec is Apache-2.0

    The language spec is permissive: adopt it, build a runtime against it, with a patent grant. The standard for the workflow file.

  • S.8dThe engine is AGPL-3.0-or-later

    Copyleft on the engine: a hosted fork shares its source. Anti-extraction by construction.

PROTOCOL

PROTOCOL
nika: v1
TYPE
workflow language
SPEC
Apache-2.0
ENGINE
AGPL-3.0 · forever
STATUS
FROZEN

where the language lives

7 surfaces · one map

The same contract, everywhere you meet it: the chapters are normative, the schema is what your editor validates, the engine enforces, the oracle teaches your agent, the registry re-proves what you share.

4 verbs · 28 builtins · 17 providers · 9 extract modes · 21 error namespaces · every count derives from the spec's canon.yaml, never hand-typed

nika

Intent as Code. The contract, frozen forever.