FIG S · the language reference

The contract an agent must satisfy before it acts.

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.

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 what they wait on. Independent tasks run at the same time, automatically.
depends_on
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-*
4
verbs
locked
23
builtins
4 families
14
providers
5 local
9
extract
on fetch
14
namespaces
50 codes
FIG S.0

The envelope

10 top-level keys

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
  • descriptionoptionala human note · free text
  • modeloptionalthe default model · provider/name (e.g. ollama/llama3.1)
  • 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: standup-digestdescription: "Read yesterday's commits, write today's standup note"model: mock/echo            # deterministic · swap for ollama/llama3.1 (local · zero key)tasks:  # No deps between these two → the engine runs them in parallel.  - id: today    invoke:      tool: "nika:date"      args: { op: now }  - id: history    exec:      command: "git log --since=yesterday --oneline --no-merges"  - id: digest    depends_on: [today, history]    infer:      prompt: |        Date · ${{ tasks.today.output }}        Commits since yesterday ·        ${{ tasks.history.output }}        Write my standup note · 3 bullets · done / doing / blocked.        Plain words · no fluff.  - id: save    depends_on: [digest]    invoke:      tool: "nika:write"      args:        path: "./standup-note.md"        content: "${{ tasks.digest.output }}"outputs:  note: ${{ tasks.digest.output }}
FIG S.1

The four verbs

4 · locked forever

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
- id: 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
- id: 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
- id: 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
- id: research  agent:    prompt: "Research ${{ vars.topic }}"    tools: ["nika:fetch"]
FIG S.2

The task shape

1 required field · 1 verb

A task is a DAG node. id is the only required field and exactly one verb binds; everything else is an optional structural control.

anatomy of a task node
id: summarizerequiredinfer:1 of 4 verbs prompt: "…"depends_on: [extract]the edgeswhen: ${{}}a CEL gate

required coreoptional controls

  • idreqsnake_case · CEL-safe · unique in the workflow
  • ‹verb›reqexactly one of infer · exec · invoke · agent
  • depends_onoptthe edges · ids this task waits on
  • whenopta CEL boolean gate (or true/false) — replaces the success gate
  • 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
FIG S.3

The permits

default-deny once present

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.

FIG S.4

The standard library

23 builtins · 4 families

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

Files· 5

readwriteeditglobgrep

Data· 9

jqconvertvalidatejson_diffjson_merge_patchcomposehashuuiddate

Web· 1

fetch

Flow· 8

assertdonewaitemitlognotifypromptinspect
FIG S.5

Providers

14 · 5 local · 8 cloud · 1 mock

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· 8

MistralAnthropicOpenAIGeminiDeepSeekxAIGroqOpenRouter

Test· 1 · deterministic

mock
FIG S.6

Extract modes

9 modes on fetch

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

articlefeedjqlinksmarkdownmetadataselectorsitemaptext
FIG S.7

Error namespaces

14 namespaces · 50 codes

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

  • NIKA-AGENTagent: the loop budget
  • NIKA-BUILTINbuiltin tool argument contracts
  • NIKA-CANCELtask or workflow cancellation
  • 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-MCPMCP client + transport
  • NIKA-PARSEYAML parse + envelope validation
  • NIKA-PROVIDERprovider adapter failures
  • NIKA-SECsecurity policy · SSRF · whitelist · permits
  • NIKA-TIMEOUTtask or step timeouts
  • NIKA-VARvariable + expression resolution
FIG 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.91.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.

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