| You want to... | Reach for |
|---|---|
| Turn a repeated AI job into a reviewable, diffable file | Nika |
| Know what a workflow will cost and touch before it runs | Nika (no other tool checks cost and secret flows statically) |
| Run AI work locally, offline, with no keys | Nika (mock and Ollama paths), or a local framework |
| Build a long-lived, distributed, durable-execution system | Temporal, DBOS, Restate |
| Write agent logic as code with a visual graph | LangGraph, Mastra |
| Orchestrate repos, runners and services in CI | GitHub Actions, Argo |
| An autonomous coding agent in your editor | Cursor, Claude Code, Continue.dev |
` | Teach a `NIKA-XXXX` error code — or narrate a workflow FILE (0.98+): the waves, cost before a token is spent, what it touches, the flight-recorder hand-off | | | | | | | |
| `nika doctor [--ping]` | Diagnose providers plus editor/agent readiness — findings grouped under `binary` · `config` · `lsp` · `mcp` · `provider` · `local` · `providers` · `image` · `tts`. `--ping` (0.94+) TCP-probes the local ports — loopback and configured URLs only, 300ms cap, nothing sent on the socket; the default run stays fully offline | | | | | | | |
| `nika init [dir]` | Scaffold schema wiring, `AGENTS.md`, and Cursor rules | | | | | | | |
| `nika wire ` | Explicitly wire MCP for `cursor`, `vscode`, `windsurf`, `claude`, `codex`, `zed`, `opencode`, `hermes`, or `all` — the [client table](/integrations/mcp-clients) carries every target and its config path | | | | | | | |
| `nika spec [--canon]` | Print the embedded spec or canon | | | | | | | |
| `nika spec --schema` | Print the embedded JSON Schema | | | | | | | |
| `nika catalog [--json]` | The embedded provider/model catalog (models · capabilities · env vars) — `--json` emits the versioned machine projection (`catalog_version: 1`) | | | | | | | |
| `nika catalog --tools [--json]` | The embedded builtin tool catalog (`nika:*` · model-facing schemas) — `--json` emits the versioned machine projection (`tools_version: 1`) | | | | | | | |
| `nika new [intent] [dest]` | The ONE creation door (0.107): plain words route across the whole catalog · an example slug lands verbatim with its ingredients · a skeleton name instantiates (`nika new '?'` lists the set) — the old `nika examples` tree and `nika new --from` died with 0.107 | | | | | | | |
| `nika completions ` | Generate shell completions ([setup](/getting-started/installation#shell-completions)) | | | | | | | |
| `nika sign ` | Sign a workflow file (author-binding) — mints `.minisig` · `--check` verifies the signature against the trusted key | | | | | | | |
| \`nika key mint | fingerprint | rotate\` | The run-signing key lifecycle — mint · TOFU fingerprint · rotate (old public keys stay verifiable) | | | | | |
| `nika trace evidence ` | Export the evidence pack for one run — journal + manifest + receipt + VERIFY.md, what an auditor reads without trusting us (moved under `trace` in 0.107 — one door on a run's dossier) | | | | | | | |
| `nika trace receipt explain ` | Read a run receipt — render the readable projection of an evidence pack's `receipt.json` (stable text · a READING, never a proof — `nika trace verify` owns the proof) | | | | | | | |
| \`nika trace verify | reproduce | replay | show | outputs | export | peek | flow\` | Prove, replay, summarize, inspect, or export a recorded run — `verify` walks the tamper-evidence hash chain and names the first broken link (several traces verify per-file, worst exit wins — a shell glob just works) · `peek` renders a task's full recorded value, and a FAILED task's recorded failure + its explain line · `reproduce` re-runs and classifies every divergence — `export` projects the journal to OTLP/JSON lines (Jaeger UI, or POST to any OTLP/HTTP endpoint; a local file, zero collector, zero vendor) |
| `nika dap` | Debug Adapter Protocol server over stdio — time-travel a recorded run in any DAP client (VS Code F5) | | | | | | | |
| `nika lsp` | Run the language server over stdio | | | | | | | |
| `nika mcp` | Run the read-only MCP oracle (the tool set is listed below) | | | | | | | |
## Global flags
| Flag | Command | Description | | |
| --------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----------------------- |
| `--json` | `check`, `run` | Machine-readable output | | |
| `--infer-permits` | `check` | Print the tightest inferred `permits:` boundary — the paste-ready repair for `NIKA-AUTH-006` (absent block = zero authority) | | |
| `--native-strict` | `check` | Fail (exit 2) while any `native-first` hint remains — an `exec:` a builtin or MCP tool probably covers (the agent/CI posture) | | |
| `--output json` | `run` | Print typed workflow outputs as one JSON object | | |
| `--var key=value` | `run` | Set a workflow `inputs:` entry (repeatable) — overrides a `default:`, satisfies `required: true`, parses as JSON when it parses, refuses unknown keys ([details](/concepts/workflows)) | | |
| `--model /` | `run` | Override the envelope `model:` — `--model mock/echo` previews any workflow offline (zero key, zero network) | | |
| `--resume ` | `run` | Resume from a prior run's NDJSON trace: matching completed tasks skip with a visible `task_cache_hit` ([details](/guides/resume)) | | |
| `--from ` | `run` | With `--resume`: force this task and its transitive downstream to re-run even on a match | | |
| `--answer =` | `run` | With `--resume`: answer a paused `nika:prompt` (repeatable) — a boolean for `confirm`, a string for `input`, one of the choices for `choice` | | |
| `--task ` | `run` | (0.94+) Run ONE task and its transitive upstream cone only — the whole file still audits, then the plan and cost re-derive for exactly what will run; workflow `outputs:` are skipped | | |
| `--no-trace-file` | `run` | (0.94+) Skip the run journal — every run otherwise writes `.nika/traces/-.ndjson` by default (`NIKA_NO_TRACE_FILE` opts out globally) | | |
| `--update` | `test` | (Re)write the golden from this run instead of comparing ([details](/guides/testing)) | | |
| `--dry-run` | `run` | Plan only; execute zero effects | | |
| `--no-progress` | `run` | Deterministic non-animated render | | |
| `--quiet` | `run` | Compact final verdict card | | |
| \`--format json | mermaid | dot\` | `graph` | Choose graph projection |
| `--force` | `init`, `new` | Overwrite existing generated files | | |
## Environment variables
The five local inference providers (Ollama · LM Studio · llama.cpp ·
LocalAI · vLLM) need **no variable at all** — keyless by construction,
loopback defaults built in. Everything below is opt-in:
| Var | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `NIKA_IMAGE_LOCAL_URL` | Base URL for the `local` image provider (any OpenAI-images-compatible server — LocalAI · Ollama · sd.cpp · SGLang · vLLM-Omni) |
| `NIKA_TTS_LOCAL_URL` | Base URL for the `local` speech provider (any OpenAI-speech-compatible server — LocalAI · Kokoro · Speaches) |
| `MISTRAL_API_KEY` | Mistral provider |
| `ANTHROPIC_API_KEY` | Anthropic provider |
| `OPENAI_API_KEY` | OpenAI provider |
| `GEMINI_API_KEY` | Google Gemini provider |
| `DEEPSEEK_API_KEY` | DeepSeek provider |
| `XAI_API_KEY` | xAI provider |
| `GROQ_API_KEY` | Groq provider |
| `OPENROUTER_API_KEY` | OpenRouter provider |
| `HF_TOKEN` | Hugging Face provider (`NIKA_HUGGINGFACE_API_KEY` wins when set) |
| `NVIDIA_API_KEY` | NVIDIA provider (`NIKA_NVIDIA_API_KEY` wins when set) |
| `ELEVENLABS_API_KEY` | ElevenLabs speech (`NIKA_ELEVENLABS_API_KEY` wins when set) |
The MCP server is intentionally read-only. It exposes 9 tools — `nika_check` ·
`nika_explain` · `nika_schema` · `nika_examples` · `nika_template` ·
`nika_canon` · `nika_catalog` · `nika_tools` · `nika_inspect`; effectful
execution stays behind `nika run`.
**MCP protocol** · latest `2026-07-28`; the server accepts 5 versions
(`2026-07-28` · `2025-11-25` · `2025-06-18` · `2025-03-26` · `2024-11-05`).
# Crate constellation
Source: https://docs.nika.sh/reference/constellation
Visual map of every Nika Diamond crate across six layers. Live admission status. The architecture grows additively across 1.x minors, toward a projected horizon.
Nika Diamond grows additively across 1.x minors, organized across six
layers. Today ** crates are admitted** and
are WIP
(), against a
projected horizon of ** crates** (hard cap
, per ADR-037). The horizon is a projection, never a gate:
crates are admitted when they pass the 12 gates, not to reach a number.
The remaining crates are future 1.x expansion, not required for the first-run
CLI/LSP/MCP path.
Live state: [`/reference/status`](/reference/status). Layer model:
[`/architecture/layers`](/architecture/layers). Admission process:
[`/architecture/admission`](/architecture/admission).
## Full dependency graph
```mermaid theme={"system"}
%%{init: {'theme':'dark','themeVariables':{'background':'transparent','mainBkg':'transparent'}}}%%
flowchart TB
classDef admitted fill:#22c55e,color:#000,stroke:#16a34a,font-weight:bold
classDef wip fill:#f59e0b,color:#000,stroke:#d97706,font-weight:bold
classDef planned fill:#1e293b,color:#94a3b8,stroke:#475569,stroke-dasharray:4
classDef binary fill:#a78bfa,color:#000,stroke:#7c3aed,font-weight:bold
subgraph L5["L5 · binary"]
NIKA["nika ✅"]:::admitted
end
subgraph L4["L4 · interfaces"]
CLI["nika-cli ✅"]:::admitted
SERVE["nika-serve 🧭"]:::planned
MCP["nika-mcp ✅"]:::admitted
LSP["nika-lsp ✅"]:::admitted
SDK["nika-sdk 🧭"]:::planned
CV["nika-catalog-verify ✅"]:::admitted
end
subgraph L3["L3 · runtime"]
RT["nika-runtime ✅"]:::admitted
SH["nika-shield 🧭"]:::planned
end
subgraph L2["L2 · verbs + services"]
VE["nika-verb-exec ✅"]:::admitted
VF["nika:fetch builtin ✅"]:::admitted
VI["nika-verb-invoke ✅"]:::admitted
VIF["nika-verb-infer ✅"]:::admitted
VA["nika-verb-agent ✅"]:::admitted
PCK["nika-pck 🧭"]:::planned
MEM["nika-connectome 🧭 v0.85+"]:::planned
OBS["nika-observability 🧭"]:::planned
BGH["nika-builtin-github 🧭"]:::planned
BCL["nika-builtin-cloud 🧭"]:::planned
BWS["nika-builtin-workspace 🧭"]:::planned
end
subgraph L1["L1 · effects"]
FS["nika-fs ✅"]:::admitted
HTTP["nika-http ✅"]:::admitted
PROC["nika-exec-runner ✅"]:::admitted
GIT["nika-git 🧭"]:::planned
KEYS["nika-keys-* 🧭"]:::planned
PROV["nika-providers ✅"]:::admitted
PREG["nika-pck-registry 🧭"]:::planned
PSTR["nika-pck-store 🧭"]:::planned
end
subgraph L05["L0.5 · kernel"]
K["nika-kernel ✅"]:::admitted
KM["nika-kernel-mock ✅"]:::admitted
end
subgraph L0["L0 · foundation"]
T["nika-types ✅"]:::admitted
E["nika-error ✅"]:::admitted
C["nika-catalog ✅"]:::admitted
CG["nika-catalog-codegen ✅"]:::admitted
S["nika-schema ✅"]:::admitted
EV["nika-event ✅"]:::admitted
BN["nika-binding 🧭"]:::planned
TR["nika-transform 🧭"]:::planned
PM["nika-pck-manifest 🧭"]:::planned
end
T --> E
T --> C
T --> K
E --> K
C --> K
C --> CV
S --> CV
K --> KM
K --> FS
K --> HTTP
K --> PROC
K --> GIT
K --> KEYS
K --> PROV
FS --> VE
HTTP --> VF
PROV --> VIF
VE --> RT
VF --> RT
VI --> RT
VIF --> RT
VA --> RT
RT --> CLI
RT --> SERVE
CLI --> NIKA
```
✅ admitted (12 gates green) · 🟡 WIP · 🧭 planned. Colors by state: green admitted, amber WIP, slate planned, purple binary root. Arrows read "depends on": all point downward.
## Layer breakdown
| Layer | Admitted | WIP | Planned | Notes |
| -------- | ----------------------------------------------------------------------------------------------------------------- | --- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| **L0** | nika-types, nika-error, nika-catalog, nika-catalog-codegen, nika-schema, nika-event | — | binding, transform, pck-manifest | Pure sync, zero I/O. |
| **L0.5** | nika-kernel (facade), nika-kernel-core, nika-kernel-ai, nika-kernel-runtime, nika-kernel-plugin, nika-kernel-mock | — | — | Trait definitions (4-way split + facade hub) + mock companion. Async OK |
| **L1** | nika-fs, nika-http, nika-exec-runner, nika-clock, nika-providers | — | git, keys-\*, pck-registry, pck-store, memory-satellites (v0.95) | Effect impls. Each declares capability axes |
| **L2** | nika-verb-infer, nika-verb-exec, nika-verb-invoke, nika-verb-agent, nika-builtin | — | pck, memory (v0.95), observability, builtin- | Verbs + domain services (`fetch` is the `nika:fetch` builtin, not a verb crate) |
| **L3** | nika-runtime | — | shield, wasm-host (v0.100), sandbox (v0.100) | Runtime + policy |
| **L4** | nika-catalog-verify, nika-cli, nika-mcp, nika-lsp | — | serve, sdk | Interfaces, libraries |
| **L5** | nika | — | — | The public binary surface |
## Memory subsystem (the Connectome): separate count
The memory subsystem is **11 crates** — 1 L2 orchestrator + 10 L1
satellites — and is **not counted** in the crate
horizon. It lands **whole before 1.0** (the
timeline's [connectome gate](https://nika.sh/timeline#gates) · a declared
pre-condition of the launch), via the `MemoryStore` + `EmbeddingProvider`
trait reservations already in `nika-kernel`.
`nika-connectome` ties the 10 satellites together behind the `MemoryStore`
trait. Exposes unified put / query / forget / recall API.
* `nika-hnsw`, approximate nearest neighbor (HNSW index)
* `nika-bm25`, sparse full-text ranking
* `nika-rrf`, reciprocal-rank fusion (hybrid search)
* `nika-rerank`, local reranking for precision
* `nika-fsrs`, Free Spaced Repetition Scheduler (memory decay)
* `nika-rdfs-reasoner`, RDFS inference for knowledge triples
* `nika-temporal`, time-aware recency + decay
* `nika-graph-algos`, graph traversal (shortest-path, community)
* `nika-autodesc-minimal`, provenance-attached auto-description
* `nika-autodesc-full`, schema-evolving auto-description
## Admission queue
Current admission order (per Q5 decision, rev.2):
Extract TOML → Rust codegen to its own L0 crate. Independent of other
crates; unblocks faster iteration on catalog data.
Complete parser + DAG + ariadne diagnostics. Unblocks verb crates
(they need to parse `.nika.yaml` step shapes).
Event envelope + 65 transforms. Can run in parallel.
Template engine + package manifest. Binding depends on transform.
One verb per round: infer → exec → invoke → agent (fetch is the
`nika:fetch` builtin under `invoke`, not a verb crate).
L1 effect crates (fs, http, process, keys, provider-\*) and L3 runtime.
L4 cli / serve / mcp / lsp / sdk and the L5 `nika` binary.
No hard timeline per admission round. Real semver toward 1.0: quality
over speed. Each admission = 1 atomic commit, on the tagged 0.9x train
(`1.0.0-rc.N` when the release-candidate window opens).
## See also
HEAD, tests, hygiene: the drift-proof snapshot.
Six layers, mechanical sort test, security axes.
How a crate earns a seat at the workspace.
Q1-Q13 decisions that shaped today's L0 layout.
# Design system (machine-readable)
Source: https://docs.nika.sh/reference/design-system
The nika icon ontology as data: every verb, builtin, feature and run-state with its canonical glyph, hue and motion — served as JSON, Turtle and SVG.
Every Nika **verb**, **builtin**, **feature** and **run-state** carries a
canonical icon, color role and motion pattern — declared once in an
ontology and served as data from [nika.sh/brand](https://nika.sh/brand).
If you're building a dashboard, an editor integration or an agent surface
on top of Nika, take the visual language from here instead of inventing
one: your run view and the official surfaces stay one system.
## The served surfaces
| Surface | URL | What it is |
| -------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Icon catalog | `https://nika.sh/brand/icons/-.svg` | One SVG per entity (e.g. [`verb-infer`](https://nika.sh/brand/icons/verb-infer.svg) · [`builtin-fetch`](https://nika.sh/brand/icons/builtin-fetch.svg) · [`state-recovered`](https://nika.sh/brand/icons/state-recovered.svg)) |
| JSON ontology | [`/brand/icons.json`](https://nika.sh/brand/icons.json) | Labels, semantics, hue tokens, effects and links for every entity |
| RDF projection | [`/brand/icons.ttl`](https://nika.sh/brand/icons.ttl) | The same graph in Turtle — SPARQL-ready, namespace [`nkd:`](https://nika.sh/ontology/design/) |
| Identity kit | [`/brand/`](https://nika.sh/brand) + [BRAND.md](https://github.com/supernovae-st/nika.sh/blob/main/BRAND.md) | The butterfly-supernova marks, lockups, tiles and usage rules |
Entity ids are `/` across seven namespaces: `verb` ·
`builtin` · `feature` · `state` · `ui` · `social` · `anim`. The builtin
entries track the -builtin stdlib — the ontology is
parity-gated against the spec canon, so a new builtin cannot ship without
its icon.
## The color law
The four verb hues mean **a run is alive** — they appear only on live-run
surfaces (an executing DAG, a replay). Static UI renders every icon in the
surrounding text ink. If you consume the catalog, keep that rule: it is
what makes color a signal instead of decoration.
| Verb | Hue |
| -------- | ---------------- |
| `infer` | blue `#5b8cff` |
| `exec` | orange `#ff7a3c` |
| `invoke` | cyan `#22d3ee` |
| `agent` | violet `#b07bff` |
## Motion
The `anim/*` entities define the dot-matrix motion register: each verb's
pattern is its execution model — `infer` samples (a sparkle emerges from
seeded noise), `exec` scans like stdout, `invoke` round-trips, `agent`
orbits its bounded loop. The pattern names and parameters ride
`icons.json`; the reference renderer lives in the
[site source](https://github.com/supernovae-st/nika.sh/tree/main/src/fx/dotmatrix).
## Querying the ontology
The Turtle projection answers SPARQL. For example, every builtin and its
glyph:
```sparql theme={"system"}
PREFIX nkd:
SELECT ?entity ?label ?glyph WHERE {
?entity a nkd:Builtin ;
rdfs:label ?label ;
nkd:glyph ?glyph .
}
```
Classes: `nkd:Verb` · `nkd:Builtin` · `nkd:Feature` · `nkd:RunState` ·
`nkd:UiGlyph` · `nkd:SocialMark` · `nkd:Animation`. Relations worth
knowing: `nkd:parent` (every builtin points at `verb/invoke` — everything
callable is a tool reached through `invoke:`), `nkd:sharesGlyphWith`
(deliberate glyph reuse, e.g. the `compose` builtin and the check feature
share the shield — same oracle), and `nkd:hueToken` (the CSS custom
property a hue rides on the official surfaces).
# Error codes
Source: https://docs.nika.sh/reference/error-codes
The typed error model: namespaces, categories, and the registered v0.1 codes. Generated from the spec registry.
Every error Nika emits is a **typed structure** with a stable code, a
category, and a `transient` flag the retry machinery reads. The format is
`NIKA--`. An optional sub-namespace self-documents builtin
errors (`NIKA-BUILTIN-WAIT-001`). The full grammar:
```
^NIKA-[A-Z]{2,9}(-[A-Z][A-Z0-9_]{1,15})?-[0-9]{3}$
```
**Canonical source:** [`spec/05-errors.md`](https://github.com/supernovae-st/nika-spec/blob/main/spec/05-errors.md)
owns the taxonomy: engines *derive* from it, never the reverse. The
machine-readable registry lives in the spec's
[`canon.yaml`](https://github.com/supernovae-st/nika-spec/blob/main/canon.yaml)
and is also served as JSON at
[`nika.sh/errors/catalog.json`](https://nika.sh/errors/catalog.json).
The tables below are generated from that registry.
## The error shape
```json theme={"system"}
{
"code": "NIKA-INFER-001",
"category": "provider_error",
"message": "Anthropic API returned 503 service unavailable",
"transient": true,
"details": { "provider": "anthropic", "status_code": 503 },
"task_id": "research",
"attempt": 2
}
```
`transient: true` means a retry might succeed. `retry:` only fires on
transient errors (unless `on_codes:` widens it). `on_error:` catches the
final error either way, and `on_error.on_codes` routes recovery by exact
code.
## Categories
| Category | Meaning |
| ------------------ | ----------------------------------------------------------------- |
| `parse_error` | Workflow YAML is malformed or invalid |
| `validation_error` | Workflow violates a spec rule (cycle · unknown field · …) |
| `variable_error` | Reference to undefined variable or invalid path |
| `provider_error` | LLM provider returned an error |
| `network_error` | Network failure (DNS · TCP · TLS · timeout) |
| `tool_error` | Builtin or MCP tool returned an error |
| `process_error` | exec: subprocess failure (non-zero exit · spawn) |
| `budget_error` | An agent: loop budget exhausted (max\_turns · max\_tokens\_total) |
| `security_error` | SSRF · blocklist · capability denied |
| `timeout_error` | Task or step exceeded its timeout |
| `cancelled` | Workflow or task cancelled |
| `internal_error` | Engine bug · unexpected state |
## The registered codes (v0.1 normative floor)
A conformant engine emits exactly these codes for these failures. Engines
may add codes within a namespace's `001-099` range. Never repurpose one.
| Code | Failure | Category | `transient` | |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------ | ----- |
| `NIKA-PARSE-001` | the YAML itself does not parse (syntax error) | `parse_error` | false | |
| `NIKA-PARSE-002` | missing envelope field (nika: / workflow: / non-empty tasks:) | `validation_error` | false | |
| `NIKA-PARSE-003` | nika: version marker is not exactly v1 | `parse_error` | false | |
| `NIKA-PARSE-004` | workflow: id violates ^\[a-z]\[a-z0-9-]\*\$ | `validation_error` | false | |
| `NIKA-PARSE-005` | unknown field — strict mode rejects anything outside the closed v1 set | `validation_error` | false | |
| `NIKA-PARSE-006` | task id violates ^\[a-z]\[a-z0-9\_]\*\$ (snake\_case · CEL-safe · no hyphens) | `validation_error` | false | |
| `NIKA-PARSE-007` | duplicate task id within the workflow | `validation_error` | false | |
| `NIKA-PARSE-008` | task declares no verb — exactly one of infer/exec/invoke/agent required | `validation_error` | false | |
| `NIKA-PARSE-009` | task declares multiple verbs — exactly one required | `validation_error` | false | |
| `NIKA-PARSE-010` | timeout: violates the quoted Go-duration contract (positive · max 24h · descending units) | `validation_error` | false | |
| `NIKA-PARSE-011` | retry: block violates the spec shape | `validation_error` | false | |
| `NIKA-PARSE-012` | on\_error: block violates the spec shape (fields mutually exclusive) | `validation_error` | false | |
| `NIKA-PARSE-013` | with:/output: binding uses a reserved name (output · status · error · started\_at · ended\_at · duration\_ms) | `validation_error` | false | |
| `NIKA-PARSE-014` | secrets: entry is not a store reference — inline literals forbidden | `validation_error` | false | |
| `NIKA-PARSE-017` | duplicate mapping key — no silent last-wins | `validation_error` | false | |
| `NIKA-PARSE-018` | missing required field in a verb body (infer.prompt · exec.command · invoke.tool) | `validation_error` | false | |
| `NIKA-PARSE-019` | generic structural validation — wrong YAML shape for a field | `validation_error` | false | |
| `NIKA-PARSE-020` | workflow: is a scalar — the envelope became an object (workflow: then id: \) | `validation_error` | false | |
| `NIKA-PARSE-021` | top-level description: — it moved into workflow\.description | `validation_error` | false | |
| `NIKA-PARSE-022` | tasks: is a sequence — it became a map keyed by task id (drop `- id:`, the key IS the identity) | `validation_error` | false | |
| `NIKA-PARSE-023` | a task carries an id: field — the map key is the identity, the field is gone | `validation_error` | false | |
| `NIKA-PARSE-024` | a task carries depends\_on: — dead since W2 (data → with: bindings · control → after: predicates · check --fix migrates) | `validation_error` | false | |
| `NIKA-PARSE-025` | decode: with capture: structured — that capture already IS an object · type it with returns: | `validation_error` | false | |
| `NIKA-PARSE-026` | a declared entropy x clock contradiction · entropy: ambient paired with clock: virtual — the ambient declaration contradicts the determinism demand (NEP-0010 · F-P3) | `validation_error` | false | |
| `NIKA-PARSE-027` | a declared entropy x clock contradiction · entropy: none | seeded paired with clock: system — deterministic journals cannot ride the wall clock (NEP-0010 · F-P3) | `validation_error` | false |
| `NIKA-PARSE-028` | entropy: none declares strict determinism while a structural randomness source is consumed (a live retry jitter · nika:uuid) — the strict declaration cannot hold (NEP-0010 · F-P3 · judged at check) | `validation_error` | false | |
| `NIKA-COMP-001` | invoke.workflow: target is not statically resolvable (templated · malformed · unpinned registry ref) — a call graph you cannot draw before the run cannot be bounded (spec 14) | `validation_error` | false | |
| `NIKA-COMP-002` | the child workflow's effect boundary exceeds the parent capped by the call-site declaration (Authority(child) subset of Authority(parent) intersect declared · spec 14 laws 3/4) | `security_error` | false | |
| `NIKA-COMP-003` | the static call graph is not acyclic (self-launch · cycle · spec 14 law 7 · NIKA-SEC-003 is the runtime depth backstop) | `validation_error` | false | |
| `NIKA-COMP-004` | the typed call does not compose — parent args do not fit child inputs, or child outputs do not fit parent returns (spec 14 law 2) | `validation_error` | false | |
| `NIKA-DAG-001` | cycle in the precedence graph G\_p = E\_d ∪ E\_c (incl. self-dependency · via with:/after:) | `validation_error` | false | |
| `NIKA-DAG-002` | with:/after: references an undeclared task | `validation_error` | false | |
| `NIKA-DAG-004` | on\_error.recover references a task downstream of the declaring task (await would deadlock) | `validation_error` | false | |
| `NIKA-DAG-005` | after: predicate outside the closed set (success · failure · skipped · terminal) | `validation_error` | false | |
| `NIKA-DAG-006` | statically dead task — an incoming edge's pass-set excludes every reachable producer state, or the when: gate is false under every reachable upstream combination (gate algebra v2) | `validation_error` | false | |
| `NIKA-DAG-007` | status compared against a literal outside the vocabulary (success · failure · skipped · cancelled) — == never matches, != always holds | `validation_error` | false | |
| `NIKA-TYPE-001` | unknown type name (in types: · returns: · an outputs: type) — did-you-mean when close | `validation_error` | false | |
| `NIKA-TYPE-002` | recursive type reference — the types: graph must be acyclic | `validation_error` | false | |
| `NIKA-TYPE-003` | returns: and schema: on the same task — one contract, one spelling | `validation_error` | false | |
| `NIKA-TYPE-004` | returns: type unreachable from the declared decode: (an object contract over decode: text · …) | `validation_error` | false | |
| `NIKA-TYPE-005` | a secret-carrying type in a lowered position (reserved with secret\ · W4) | `security_error` | false | |
| `NIKA-TYPE-006` | regex pattern outside the locked dialect (backreference · lookaround · named group · inline flags · lazy/possessive · word-boundary · unicode-class — spec 09 §the regex dialect) | `validation_error` | false | |
| `NIKA-TYPE-101` | run-time contract violation — the decoded value does not fit returns: (exec:/invoke: lane) | `validation_error` | false | |
| `NIKA-VAR-001` | unresolved reference (unknown namespace entry · undeclared inputs/config/const/secrets/with key) | `variable_error` | false | |
| `NIKA-VAR-002` | binding cardinality — a jq binding emitted zero or multiple values | `variable_error` | false | |
| `NIKA-VAR-003` | provably-invalid path into a declared schema (static walk) | `validation_error` | false | |
| `NIKA-VAR-004` | jq runtime error while evaluating a binding | `variable_error` | false | |
| `NIKA-VAR-005` | static expression violation — outside cel-subset/0.1 · chained relation · unknown function · non-boolean when: root · jq compile error | `validation_error` | false | |
| `NIKA-VAR-006` | expression type error at evaluation — cross-type compare · non-boolean when: value · for\_each over a non-array | `variable_error` | false | |
| `NIKA-VAR-007` | bytes value substituted into a string position | `variable_error` | false | |
| `NIKA-VAR-008` | unclosed \$\{\{ opener | `validation_error` | false | |
| `NIKA-VAR-020` | bare tasks.X is the envelope, not a value — the projection set (.output/.status/.error/.duration\_ms) is closed and required (04 §namespaces · 0.103 · #75 D2) | `validation_error` | false | |
| `NIKA-VAR-021` | a tasks.\* reference outside the boundary (with: · after: · on\_error.recover · on\_finally parent-only · workflow outputs) — hoist it into with: (check --fix applies it) | `validation_error` | false | |
| `NIKA-VAR-009` | typed outputs value did not match its declared type: at run end (the output half of the callable contract) | `validation_error` | false | |
| `NIKA-INFER-001` | provider call failed (HTTP error · provider refusal) | `provider_error` | engine-assessed | |
| `NIKA-INFER-002` | structured output failed schema validation (after any engine-internal retries) | `validation_error` | false | |
| `NIKA-INFER-003` | the provider reported no token usage for a priced model — the ledger cannot bill the call honestly (fail-closed · R3-F1) | `validation_error` | false | |
| `NIKA-EXEC-001` | non-zero exit code (default capture modes) | `process_error` | false | |
| `NIKA-EXEC-002` | spawn failure (command not found · permission) | `process_error` | false | |
| `NIKA-INVOKE-001` | unknown tool (unresolvable nika:/mcp: id) | `validation_error` | false | |
| `NIKA-INVOKE-002` | tool args failed the tool's schema | `validation_error` | false | |
| `NIKA-AGENT-001` | max\_turns exhausted before completion | `budget_error` | false | |
| `NIKA-AGENT-002` | max\_tokens\_total exhausted before completion | `budget_error` | false | |
| `NIKA-AGENT-003` | a skills: path does not resolve (file missing/unreadable at compose time) | `validation_error` | false | |
| `NIKA-AGENT-004` | a skills: file is not a valid Agent Skill (frontmatter shape · missing/empty name/description) | `validation_error` | false | |
| `NIKA-AGENT-005` | the provider reported no token usage for a priced model — every budget and ledger reads the turn as free, so the loop fails closed (R3-F1 · the usage-absence gate) | `budget_error` | false | |
| `NIKA-MCP-001` | MCP server not configured / not reachable at call time | `tool_error` | engine-assessed | |
| `NIKA-MCP-002` | MCP tool call failed (transport · tool-side error) | `tool_error` | engine-assessed | |
| `NIKA-DECIDE-001` | the decision bundle is malformed or violates its own laws (float weight · undeclared evidence key in rules · identity key feeding a technical dimension · missing contradictory fixture · monotonicity violated by the bundle's own fixtures) (spec 11) | `validation_error` | false | |
| `NIKA-DECIDE-002` | the evidence snapshot does not satisfy the bundle's evidence schema (type misfit · unauthorized source · integrity below the declared floor · undeclared key) (spec 11) | `validation_error` | false | |
| `NIKA-POLICY-001` | a hard policy: rule is violated (require.human\_gate\_before · forbid.exec\_after · allow\.providers · limits.max\_tasks) — the diagnostic names rule + task + witness (order rules: the path) · check-time, before any token (spec 10) | `security_error` | false | |
| `NIKA-PORT-001` | a gateway artifact (deployment bundle · capabilities report · lowering report · fidelity report · authority delta) is malformed or violates its laws (unknown promoted · permissive\_unsafe without refusal · disclosure subset-chain violated · child authority exceeding parent) (spec 12) | `validation_error` | false | |
| `NIKA-PORT-002` | policy lowering is permissive\_unsafe — the backend would allow what the policy forbids · refused with the divergence witness (spec 12) | `security_error` | false | |
| `NIKA-SEC-001` | exec: blocklist hit | `security_error` | false | |
| `NIKA-SEC-002` | agent tool call outside the tools: whitelist | `security_error` | false | |
| `NIKA-SEC-003` | run-recursion bound — nested-run depth exceeded OR self-launching workflow | `security_error` | false | |
| `NIKA-SEC-004` | effect outside the declared permits: capability boundary (fs/net/exec/tool) | `security_error` | false | |
| `NIKA-SEC-005` | SSRF block — a nika:fetch/nika:notify URL resolves to a loopback/private/link-local/metadata target (always-on engine floor · independent of permits:) | `security_error` | false | |
| `NIKA-SEC-006` | secret flow — a secrets.\ value reaches an unsanctioned sink (exec argument · invoke payload · infer/agent prompt) · the diagnostic carries the taint path + the egress clause that would sanction it (spec 10 · flow rules in 01 §egress) | `security_error` | false | |
| `NIKA-SEC-007` | secret egress — a tainted value reaches the workflow boundary (outputs:) · the diagnostic carries the taint path (spec 10 · the to: outputs sanction in 01 §egress) | `security_error` | false | |
| `NIKA-SEC-008` | data-as-code sink · a nika:fetch resolved URL path names a code-bearing class (serialized-executable · script/interpreter · executable binary/module · the closed NEP-0006 list) and the task declares no inert: door · the read hides an execution sink (F-O7 · NEP-0006) | `security_error` | false | |
| `NIKA-SEC-009` | lethal trifecta complete — the declared boundary grants private read (fs.read non-empty) + untrusted ingress (a nika:fetch builtin invoked · an mcp:\* tool invoked · an agent: whose whitelist admits ingress · an exec: task, permits.exec itself the ingress channel) + external egress (net.http non-empty · an escaping fs.write glob · exec enabled), the untrusted content REACHES an egress-capable task's effect surface (a realized flow), and no blocking invoke: nika:prompt (no default:) dominates it (NEP-0002 v2.0 · the Rule of Two as a static check) | `security_error` | false | |
| `NIKA-SEC-010` | the approval-capability law is violated · a rate-limited approval burst (the N+1th distinct mint of a run) · an approval whose resolved content hash differs from the shown hash (content\_mismatch) · a ticket replayed across runs or steps (scope\_mismatch) · or the static heterogeneous-batch refusal (one prompt unleashing two or more effect classes) — the 6th invariant (NEP-0013 · F-P4) | `security_error` | false | |
| `NIKA-SEC-011` | preview-commit divergence — the commit digest recomputed at the sink over the exact bytes about to fire differs from the preview digest computed at resolution (one bit of rendered argv · a permuted context field · a mutated tool argument) · the step refuses fail-closed and the receipt carries divergence:\{preview, commit} — judged = executed at the action scale (NEP-0015 · F-P6) | `security_error` | false | |
| `NIKA-SEC-012` | unordered shared writes — two tasks incomparable in the DAG closure whose literal nika:write/nika:edit paths collide with no ordering edge (after: · with:) to serialize them, or a for\_each fan writing one constant path · parallelism is safe exactly where the writes are provably disjoint (NEP-0014 law 1 · F-P15) | `security_error` | false | |
| `NIKA-SEC-013` | the endorsement mode law — a human gate (invoke: nika:prompt) declared under a policy: block that names no endorsement mode refuses fail-closed (endorsement.undeclared\_mode · F-F5 · zero implicit escape), and endorsement: solo carried by a workflow with more than one gate refuses as the declaration lying (endorsement.solo\_count · exactly one endorser) (NEP-0017 · F-P23) | `security_error` | false | |
| `NIKA-SEC-014` | the affirmative-consent law — a confirm-mode human gate (invoke: nika:prompt · mode absent or confirm) reaches an egress-capable task over a route no affirmative gate closes: a REFUSED confirm settles success with value false, so a bare after: \{ gate: success } edge, a when: that never reads the answer, and a when: provably true on the refusal all let the effect through · the gate is credited only when every route consumes the answer and proves false on it (the Kleene-falsifiable when: · when: false · a closer confirm gate owns its closure) · an undecidable gate (a nested binding · a non-fragment expression) defers to the advisory hint, never a refusal (NEP-0020 · P0-2 of the 2026-07-30 audit) | `security_error` | false | |
| `NIKA-TIMEOUT-001` | task (or for\_each iteration) exceeded timeout: | `timeout_error` | false | |
| `NIKA-CANCEL-001` | task cancelled (workflow failure gate · user cancellation) | `cancelled` | false | |
| `NIKA-ASSERT-001` | an assert: claims a level the evidence does not support (a StaticProof the IR cannot decide · a mis-leveled obligation · spec 15) | `validation_error` | false | |
| `NIKA-LOCK-001` | a dependency resolved that nika.lock does not pin, or a hand-edited lock digest does not match (pin-by-default · the lock's own hash catches the edit · spec 15) | `validation_error` | false | |
| `NIKA-BUILTIN-001` | builtin invoke violates its statically-checkable arg contract (e.g. nika:fetch without url: · nika:jq arg shape) | `validation_error` | false | |
| `NIKA-BUILTIN-DONE-001` | nika:done invoked outside an agent: loop | `validation_error` | false | |
| `NIKA-DRIFT-001` | declared-but-unused — a vars\:/env:/secrets: name or a permits: entry (exec program · tool glob · net host · fs path) that nothing in the body references (advisory check hint · never fails the audit — the reverse direction, used-but-undeclared, is the hard NIKA-VAR-001/NIKA-DAG-002/NIKA-SEC-004 surface) | `validation_error` | false | |
| `NIKA-AUTH-006` | no permits: block declared and the body has effects — absent = zero authority (F-O8 · NEP-0003) | `security_error` | false | |
| `NIKA-AUTH-007` | an interpolation reaches a permit bound (host · glob · program · env name) — a bound MUST be a literal, the boundary would be self-serve (F-O1 · NEP-0004 · env per NEP-0005) | `security_error` | false | |
| `NIKA-AUTH-008` | an untrusted value reaches a permitted verb's argument and its canonical resolved form escapes the step's permit — re-gate refused (F-O1 · NEP-0004) | `security_error` | false | |
| `NIKA-AUTH-009` | a permits env: entry names a dangerous-floor variable · the engine strips the name unconditionally, the grant can never take effect: an inert dead grant (F-O4 · NEP-0005) | `security_error` | false | |
| `NIKA-AUTH-010` | a permits net.http: entry carries the \*. subdomain wildcard · the grant delegates the boundary to the zone operator (every host under the suffix, present and future) — refused: name exact hosts, or the bare \* when allow-all is genuinely intended (F-P5 · NEP-0008) | `security_error` | false | |
| `NIKA-VALUES-001` | vars: is a dead envelope field (R3a · the E-split) | `validation_error` | false | |
| `NIKA-VALUES-002` | env: is a dead envelope field (R3a · the E-split) | `validation_error` | false | |
| `NIKA-VALUES-003` | a value-namespace read outside the four-authority family (R3a · LAW-SURFACE-0201) | `validation_error` | false | |
| `NIKA-DEFAULT-001` | a declared default (inputs · config) or typed const value does not conform to its declared type (R3b · LAW-TYPE-0211) | `validation_error` | false | |
### Retired codes (never reuse)
Two allocation holes in the table above are deliberate — a retired code is
never repurposed:
* **`NIKA-PARSE-016`** — the jq-binding-contains-template class folded into
`NIKA-VAR-005`.
* **`NIKA-DAG-003`** — « a `tasks.X` reference with no declared edge »
became **inexpressible** in W2 « the flow »: the `with:` binding IS the
edge (derived, never restated), and a reference outside the boundary is
`NIKA-VAR-021`.
## Namespaces
| Namespace | Scope | Range |
| --------------- | ----------------------------------------------- | ------- |
| `NIKA-AGENT` | agent: verb errors | 001-099 |
| `NIKA-ASSERT` | | 001-099 |
| `NIKA-AUTH` | | 001-099 |
| `NIKA-BUILTIN` | Builtin tool errors · per-builtin sub-namespace | 001-099 |
| `NIKA-CANCEL` | Task or workflow cancellation | 001-099 |
| `NIKA-COMP` | | 001-099 |
| `NIKA-DAG` | DAG topology · cycles · invalid deps | 001-099 |
| `NIKA-DEFAULT` | | 001-099 |
| `NIKA-EXEC` | exec: verb errors | 001-099 |
| `NIKA-IMPL` | Engine internal errors | 001-099 |
| `NIKA-INFER` | infer: verb errors | 001-099 |
| `NIKA-INVOKE` | invoke: verb errors | 001-099 |
| `NIKA-LOCK` | | 001-099 |
| `NIKA-MCP` | MCP client errors | 001-099 |
| `NIKA-DECIDE` | | 001-099 |
| `NIKA-DRIFT` | | 001-099 |
| `NIKA-PARSE` | YAML parse + envelope validation | 001-099 |
| `NIKA-POLICY` | | 001-099 |
| `NIKA-PORT` | | 001-099 |
| `NIKA-PROVIDER` | Provider adapter errors | 001-099 |
| `NIKA-SEC` | Security policy violations (SSRF · blocklist) | 001-099 |
| `NIKA-TIMEOUT` | Task or step timeouts | 001-099 |
| `NIKA-TYPE` | | 001-099 |
| `NIKA-VAR` | Variable resolution failures | 001-099 |
| `NIKA-VALUES` | | 001-099 |
Builtin errors use a per-builtin sub-namespace: each builtin owns its own
`001-099` (`NIKA-BUILTIN-FETCH-001` is the fetch tool's network/extraction
failure). Underscore-named builtins encode naturally:
`NIKA-BUILTIN-JSON_MERGE_PATCH-001`.
## Routing on codes
```yaml theme={"system"}
# Retry ONLY on the fetch network error · fixed 30s backoff
retry:
max_attempts: 5
backoff_strategy: fixed
backoff_ms: 30000
on_codes: [NIKA-BUILTIN-FETCH-001]
# Recover ONLY on timeout · any other code still fails
on_error:
on_codes: [NIKA-TIMEOUT-001]
recover: { stale: true, items: [] }
```
`on_error.skip` preserves the original error at `tasks.X.error`: a
downstream task can branch on `${{ tasks.X.error.code }}` while the status
reads `skipped`.
Retry policies, backoff strategies, recovery semantics, and the
gate-based failure propagation rules. See spec/05-errors.md.
# Machine surfaces
Source: https://docs.nika.sh/reference/machine-surfaces
The versioned JSON wires — every human surface has a machine twin, envelopes are versioned and additive-only, exit codes are a contract.
> The law: **every human-facing surface has a `--json` twin**, every
> machine payload carries a **version envelope**, and evolution is
> **additive-only** — a field is added, never renamed or removed within
> a version. Agents and tooling parse the JSON; the pretty output is
> free to improve without breaking anyone.
## The wires
| Surface | Envelope | What it carries |
| ----------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `nika check --json` | `report_version` | the full audit report: conformance findings (each with `code` · `severity` · `docs_url` · span), cost intervals per task, the derivation certificate, requirements (models · secrets · env), secret-flow findings, hints, pricing provenance (`pricing.snapshot`) |
| `nika doctor --json` | — (stable shape) | the environment diagnosis: per-finding status + the exact fix command, key PRESENCE (never values), local provider reachability |
| `nika catalog --json` | `catalog_version` | the provider/model vocabulary: models, capabilities, key env-var NAMES |
| `nika catalog --tools --json` | `tools_version` | the builtin tool catalog with model-facing JSON Schemas per tool |
| `nika inspect --format json` | `graph_format` (`2`) | the ONE graph projection: nodes (id · verb · model/tool · when · fan-out · permits · cost interval) + **typed edges** (`kind`: `value` · `terminal-observation` · `failure-observation` · `control` + `predicate` · `recovery` — `binding` on data edges) — mermaid and dot are derived from this, never parallel truths. Format 1 is dead: a reader refuses a format it does not speak; within format 2, evolution is additive-only |
| `nika run --json` | NDJSON stream | the live event stream, one JSON object per line — the same events the trace records |
| `nika trace export` | OTLP | any recorded journal projected to OpenTelemetry (Jaeger · Grafana · Langfuse) |
| `nika spec --canon` | `schema_version` | the embedded canon SSOT (counts and names — cite it, never a remembered number) |
| `nika spec --schema` | JSON Schema `$id` | the embedded workflow schema — the same one editors validate against |
`nika check -` reads the workflow from **stdin** — editors and agents
audit keystroke-fresh buffers without touching the disk.
## The hosted twins
The same law reaches the web: the machine surfaces an agent can fetch
without a binary, each probed and versioned like the CLI wires.
| URL | What it carries |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| [`nika.sh/spec/v1/workflow.schema.json`](https://nika.sh/spec/v1/workflow.schema.json) | the published workflow JSON Schema — the same one `nika spec --schema` embeds |
| [`nika.sh/errors/catalog.json`](https://nika.sh/errors/catalog.json) | the error catalog, projected from the spec's normative table |
| [`nika.sh/brand/icons.json`](https://nika.sh/brand/icons.json) | the [design ontology](/reference/design-system): every verb, builtin, feature and run-state with glyph, hue and motion |
| [`nika.sh/brand/icons.ttl`](https://nika.sh/brand/icons.ttl) | the same graph in Turtle — SPARQL-ready, namespace [`nkd:`](https://nika.sh/ontology/design/) |
| [`nika.sh/llms.txt`](https://nika.sh/llms.txt) | the agent-readable site summary (llmstxt.org shape) |
| [registry `index.json`](https://raw.githubusercontent.com/supernovae-st/nika-registry/main/index.json) | every shared artifact with its pin, digest, cert summary and advisory state — resolve names here before installing anything an LLM suggests |
## The exit-code contract
Scripts and agents branch on exit codes, so they are the engine CLI
exit-code contract (locked · additive-only), not an accident:
| Code | Meaning |
| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0` | success — clean check · completed run |
| `1` | the workflow ran and failed (a task settled failed) |
| `2` | findings / usage / refusal — check findings, bad flags, the `--max-cost-usd` floor refusal |
| `3` | environment — missing file, no golden to compare, binary-level trouble |
| `4` | paused — the run parked on a blocking `nika:prompt` (durable pause · ADR-099) · not a failure but non-zero on purpose · resume with `nika run --resume --answer =` |
Stream discipline: product output goes to **stdout**, only
environmental noise goes to stderr — and in `--json` modes, progress
moves to stderr so stdout stays parseable.
## The MCP oracle
`nika mcp` serves the same truths over the Model Context Protocol
(stdio, read-only): validate (`nika_check` — a dirty workflow returns
`isError: true`, mirroring the CLI's exit 2) and learn (`nika_schema` ·
`nika_examples` · `nika_template` · `nika_canon` · `nika_catalog` ·
`nika_tools` · `nika_explain`). Same owning builders as the CLI
payloads — the two lanes cannot drift apart by construction.
## The evolution rules
* **Versioned**: breaking a payload shape means bumping its envelope
field — consumers pin on it.
* **Additive-only within a version**: new fields appear, existing ones
keep their meaning.
* **Deterministic**: stable key ordering, no timestamps injected into
otherwise-stable payloads — machine output is cache- and
diff-friendly.
* **Presence, never values**: no secret VALUE crosses any wire, human
or machine. Requirements name the env var; doctor reports set/unset.
Next on this page once the next engine tag ships: `nika welcome --deep --json` (`context_version`) — the whole workspace truth in one call
(file map with per-file verdicts · run inventory with actual cost ·
environment snapshot · wiring state · caps always reported, relative
paths only). It is merged on `main` today and rides the next release.
# MCP catalog
Source: https://docs.nika.sh/reference/mcp-catalog
Every MCP server Nika knows about by id. Registry schema + stdio/HTTP packages, with the catalog count derived in-page.
Nika ships with **105 MCP server entries** in its catalog, aligned with
the official [MCP Registry schema](https://registry.modelcontextprotocol.io/)
(`v2025-12-11`). Each entry declares one or more local-install
`packages` (npm / pypi / oci / cargo / mcpb) and/or `remotes`
(streamable-http / sse endpoints). The workflow YAML `invoke:` verb
references servers by id: Nika handles the rest.
**Canonical source:** [`crates/nika-catalog/data/mcp-servers.toml`](https://github.com/supernovae-st/nika/blob/main/crates/nika-catalog/data/mcp-servers.toml).
Schema: `nika/mcp-servers@1.0`. Snapshot 2026-04-17: `grep -c '^\[\[servers\]\]'`
\= **105**. Build-time invariants (enforced in `build.rs`): unique ids,
packages+remotes non-empty, known category, pricing ∈ , alias non-collision.
## Overview
**105** entries
**17**: from `anthropic` to `vectordb`
`invoke:` verb with `server: `
## How to invoke one
```yaml workflow.nika.yaml theme={"system"}
nika: v1
workflow:
id: scrape-and-summarise
permits: # an MCP tool is granted like any other
exec: false
tools: ["mcp:firecrawl/scrape"] # the server's own reach is its side of the boundary
tasks:
crawl:
invoke:
tool: "mcp:firecrawl/scrape" # mcp:/ · server id from the catalog below
args:
url: https://example.com
summarise:
with:
page: ${{ tasks.crawl.output.content }}
infer:
model: anthropic/claude-sonnet-4-6 # /
prompt: "Summarise: ${{ with.page }}"
```
```bash Required env theme={"system"}
# Each server declares its env_var in mcp-servers.toml.
# Firecrawl needs FIRECRAWL_API_KEY — read mcp-servers.toml for the full
# list. Run `nika doctor` to inspect provider readiness.
export FIRECRAWL_API_KEY=fc-...
nika run workflow.nika.yaml
```
Verb `invoke:` is live in v. The Nika MCP catalog here is
about workflows calling external MCP tools; Nika's own agent-facing MCP
server remains read-only (9 tools — `nika_check` · `nika_explain` ·
the authoring surface).
## By category
Shipped by the Model Context Protocol project itself.
| Id | Title | Registry | Identifier |
| ------------ | ---------- | -------- | ----------------------------------------- |
| `filesystem` | Filesystem | npm | `@modelcontextprotocol/server-filesystem` |
| `memory` | Memory | npm | `@modelcontextprotocol/server-memory` |
| `pdf` | PDF | npm | `@modelcontextprotocol/server-pdf` |
One-liners:
* **filesystem**: read and write local files with path-based access control.
* **memory**: persistent key/value memory store.
* **pdf**: PDF text + structure extraction.
Build systems, browser automation, error trackers, CI / CD.
| Id | Title | Registry | Identifier |
| ----------------- | ------------------------------- | -------- | ----------------------------------- |
| `aws` | AWS | npm | `aws-mcp` |
| `bitbucket` | Bitbucket | npm | `@nexus2520/bitbucket-mcp-server` |
| `browserbase` | Browserbase | npm | `@browserbasehq/mcp` |
| `browserstack` | BrowserStack | npm | `@browserstack/mcp-server` |
| `chrome-devtools` | Chrome DevTools | npm | `chrome-devtools-mcp` |
| `circleci` | CircleCI | npm | `@circleci/mcp-server-circleci` |
| `cloudflare` | Cloudflare | npm | `@cloudflare/mcp-server-cloudflare` |
| `eslint` | ESLint | npm | `@eslint/mcp` |
| `figma` | Figma | npm | `figma-developer-mcp` |
| `launchdarkly` | LaunchDarkly | npm | `@launchdarkly/mcp-server` |
| `linear` | Linear | npm | `mcp-server-linear` |
| `nx` | Nx | npm | `nx-mcp` |
| `playwright` | Playwright (browser automation) | npm | `@playwright/mcp` |
| `postman` | Postman | npm | `@postman/postman-mcp-server` |
| `sentry` | Sentry | npm | `@sentry/mcp-server` |
| `vercel` | Vercel | npm | `vercel-mcp` |
| `xcode` | Xcode (build + simulator) | npm | `xcodebuildmcp` |
Calendars, project trackers, note-taking, knowledge bases.
| Id | Title | Registry | Identifier |
| ----------------- | ----------------------- | -------- | ------------------------------------- |
| `airtable` | Airtable | npm | `airtable-mcp-server` |
| `clickup` | ClickUp | npm | `@taazkareem/clickup-mcp-server` |
| `excel` | Excel | npm | `@negokaz/excel-mcp-server` |
| `google-calendar` | Google Calendar | npm | `@cocal/google-calendar-mcp` |
| `google-drive` | Google Drive | npm | `@modelcontextprotocol/server-gdrive` |
| `jira` | Jira | npm | `jira-mcp` |
| `monday` | Monday | npm | `@mondaydotcomorg/monday-api-mcp` |
| `n8n` | n8n | npm | `n8n-mcp` |
| `notion` | Notion | npm | `@notionhq/notion-mcp-server` |
| `obsidian` | Obsidian (vault access) | npm | `obsidian-mcp` |
| `slack` | Slack | npm | `@modelcontextprotocol/server-slack` |
| `zendesk` | Zendesk | npm | `zendesk-mcp` |
Embeddings, agents, sandboxes, documentation lookup.
| Id | Title | Registry | Identifier |
| --------------------- | ------------------- | -------- | -------------------------------------------------- |
| `21st` | 21st.dev Magic | npm | `@21st-dev/magic` |
| `ahrefs` | Ahrefs | npm | `@ahrefs/mcp` |
| `context7` | Context7 | npm | `@upstash/context7-mcp` |
| `dataforseo` | DataForSEO | npm | `dataforseo-mcp-server` |
| `e2b` | E2B | npm | `@e2b/mcp-server` |
| `langchain` | Langchain | npm | `langchain-mcp` |
| `sequential-thinking` | Sequential Thinking | npm | `@modelcontextprotocol/server-sequential-thinking` |
| `supadata` | Supadata | npm | `@supadata/mcp` |
Scrapers, search APIs, crawlers.
| Id | Title | Registry | Identifier |
| ------------ | --------------------------- | -------- | -------------------------- |
| `apify` | Apify | npm | `@apify/actors-mcp-server` |
| `exa` | Exa | npm | `exa-mcp-server` |
| `firecrawl` | Firecrawl (scrape + crawl) | npm | `firecrawl-mcp` |
| `perplexity` | Perplexity (search + sonar) | npm | `perplexity-mcp` |
| `searchapi` | Searchapi | npm | `searchapi-mcp` |
| `serper` | Serper | npm | `mcp-server-serper` |
| `tavily` | Tavily | npm | `tavily-mcp` |
Relational, document, key-value, graph stores.
| Id | Title | Registry | Identifier |
| ---------- | -------- | -------- | ------------------------- |
| `mongodb` | MongoDB | npm | `mongodb-mcp-server` |
| `mysql` | MySQL | npm | `mcp-server-mysql` |
| `neo4j` | Neo4j | npm | `@johnymontana/neo4j-mcp` |
| `redis` | Redis | npm | `redis-mcp` |
| `supabase` | Supabase | npm | `supabase-mcp` |
| `turso` | Turso | npm | `mcp-server-turso` |
| `upstash` | Upstash | npm | `@upstash/mcp-server` |
Dashboards, metrics, product analytics.
| Id | Title | Registry | Identifier |
| ------------ | --------------------------- | -------- | ------------------------------------- |
| `axiom` | Axiom | npm | `mcp-server-axiom` |
| `datadog` | Datadog | npm | `@winor30/mcp-server-datadog` |
| `dynatrace` | Dynatrace | npm | `@dynatrace-oss/dynatrace-mcp-server` |
| `grafana` | Grafana (Go binary via OCI) | oci | `ghcr.io/grafana/mcp-grafana` |
| `mixpanel` | Mixpanel | npm | `mixpanel-mcp-server` |
| `plausible` | Plausible | npm | `plausible-mcp` |
| `prometheus` | Prometheus | npm | `prometheus-mcp` |
Headless content platforms, blogs, marketing ops.
| Id | Title | Registry | Identifier |
| ------------ | ---------- | -------- | ------------------------ |
| `contentful` | Contentful | npm | `@contentful/mcp-server` |
| `ghost` | Ghost | npm | `ghost-mcp` |
| `hubspot` | HubSpot | npm | `@hubspot/mcp-server` |
| `sanity` | Sanity | npm | `@sanity/mcp-server` |
| `strapi` | Strapi | npm | `strapi-mcp` |
| `wordpress` | Wordpress | npm | `wordpress-mcp` |
Chat, customer messaging, transactional email, SMS.
| Id | Title | Registry | Identifier |
| ---------- | ----------------------- | -------- | -------------- |
| `discord` | Discord | npm | `discord-mcp` |
| `intercom` | Intercom (remote OAuth) | remote | `(HTTP)` |
| `resend` | Resend | npm | `resend-mcp` |
| `sendgrid` | SendGrid | npm | `sendgrid-mcp` |
| `telegram` | Telegram | npm | `telegram-mcp` |
| `twilio` | Twilio | npm | `twilio-mcp` |
Payment processors, marketplaces, market data.
| Id | Title | Registry | Identifier |
| ------------ | ---------- | -------- | ----------------- |
| `alpaca` | Alpaca | npm | `alpaca-mcp` |
| `paypal` | PayPal | npm | `@paypal/mcp` |
| `polygon` | Polygon | npm | `polygon-mcp` |
| `salesforce` | Salesforce | npm | `@salesforce/mcp` |
| `shopify` | Shopify | npm | `shopify-mcp` |
| `stripe` | Stripe | npm | `@stripe/mcp` |
Containers, clusters, IaC, PaaS.
| Id | Title | Registry | Identifier |
| ------------ | ------------------------ | -------- | ----------------------- |
| `docker` | Docker (daemon control) | npm | `docker-mcp` |
| `heroku` | Heroku | npm | `@heroku/mcp-server` |
| `kubernetes` | Kubernetes (cluster API) | npm | `mcp-server-kubernetes` |
| `pulumi` | Pulumi | npm | `@pulumi/mcp-server` |
| `railway` | Railway | npm | `@railway/mcp-server` |
| `terraform` | Terraform | npm | `terraform-mcp-server` |
Public posting + metadata lookup.
| Id | Title | Registry | Identifier |
| ---------- | -------- | -------- | -------------- |
| `linkedin` | LinkedIn | npm | `linkedin-mcp` |
| `reddit` | Reddit | npm | `reddit-mcp` |
| `tiktok` | TikTok | npm | `tiktok-mcp` |
| `twitter` | Twitter | npm | `twitter-mcp` |
| `youtube` | YouTube | npm | `youtube-mcp` |
Weather, recipes, news, music, general knowledge.
| Id | Title | Registry | Identifier |
| ------------- | ----------- | -------- | ----------------- |
| `news-api` | NewsAPI | npm | `newsapi-mcp` |
| `openweather` | OpenWeather | npm | `openweather-mcp` |
| `recipe` | Recipe | npm | `recipe-mcp` |
| `spotify` | Spotify | npm | `spotify-mcp` |
| `wolfram` | Wolfram | npm | `wolfram-mcp` |
Text-to-image, model inference for images.
| Id | Title | Registry | Identifier |
| ----------- | --------- | -------- | --------------- |
| `comfyui` | ComfyUI | npm | `comfyui-mcp` |
| `fal` | Fal | npm | `fal-mcp` |
| `replicate` | Replicate | npm | `replicate-mcp` |
Embeddings stores + ANN search.
| Id | Title | Registry | Identifier |
| ------------- | ----------------------- | -------- | ------------------------------ |
| `pinecone` | Pinecone | npm | `@pinecone-database/mcp` |
| `qdrant` | Qdrant (Python via uvx) | pypi | `mcp-server-qdrant` |
| `turbopuffer` | Turbopuffer | npm | `@turbopuffer/turbopuffer-mcp` |
Ad platforms + email marketing.
| Id | Title | Registry | Identifier |
| ------------ | ---------- | -------- | ---------------- |
| `google-ads` | Google Ads | npm | `google-ads-mcp` |
| `mailchimp` | Mailchimp | npm | `mailchimp-mcp` |
| `meta-ads` | Meta Ads | npm | `meta-ads-mcp` |
Geospatial + routing.
| Id | Title | Registry | Identifier |
| -------- | ------ | -------- | -------------------- |
| `mapbox` | Mapbox | npm | `@mapbox/mcp-server` |
## Distribution mix
Of the 105 entries:
* **102** ship as `npm` packages (stdio via `npx`).
* **1** ships as `pypi` (`qdrant`, runs via `uvx`).
* **1** ships as `oci` (`grafana`, Go binary in a container image).
* **1** is a pure `remote` endpoint (`intercom`, OAuth streamable-http).
The catalog's `registry_type` field is validated at build time: `runner`
is required for pypi packages and forbidden for every other registry
type.
## Authentication
Every server that needs credentials declares at least one
`[[servers.env_vars]]` entry with `is_secret = true` or `false`. The
env var names are visible in [`mcp-servers.toml`](https://github.com/supernovae-st/nika/blob/main/crates/nika-catalog/data/mcp-servers.toml).
Search for `^name = ` under your chosen server.
Nika never stores API tokens in workflow YAML. Secrets are read from
environment variables at run time. Use `nika doctor` to see which provider
variables are present or missing; it reports presence only and never prints
values.
## See also
How `invoke:` routes to MCP tools + built-ins at runtime.
The engine-native tools under `nika:*`.
LLM providers for the `infer:` and `agent:` verbs.
MCP errors: the spec's `NIKA-MCP` namespace (`NIKA-MCP-001` server unreachable · `NIKA-MCP-002` call failed); the engine's internal diagnostics registry stays engine-side.
# Nika as an MCP server
Source: https://docs.nika.sh/reference/mcp-server
The read-only oracle: 9 validate-and-learn tools over stdio. Agents inspect freely; execution stays on the CLI, where the budget flags and traces live.
`nika mcp` serves the **read-only oracle** over stdio — the in-binary MCP
server every wired client talks to. Its contract is one sentence:
> **Agents can inspect freely, execute deliberately.** The oracle validates
> and teaches; running a workflow stays on the CLI, where `--max-cost-usd`,
> the permits boundary and the trace file live.
The released binary serves stdio; a **Streamable HTTP** transport
(`nika mcp --transport http` — POST JSON-RPC, origin-gated against
DNS-rebinding, loopback bind by default on port 8123) has landed on `main`
and ships with the next release, for managed hosts that cannot spawn a
process. Widening `--bind` beyond loopback is on you: put TLS + auth in
front first. Either transport, there is no write tool and no way to make
the oracle execute anything. That is the design, not a gap: generation and
repair belong to [the authoring skill](/getting-started/agents), execution
belongs to a human-gated `nika run`.
## Start it
No binary yet? `brew install supernovae-st/tap/nika` — other paths (script,
Docker) in [installation](/getting-started/installation). Then every client
uses the same two fields:
```json theme={"system"}
{ "command": "nika", "args": ["mcp"] }
```
`nika wire ` writes this stanza into the right config file for
cursor · vscode · windsurf · claude · codex · opencode · hermes (and
`all`). For any other MCP-capable client, paste the stanza into its
server config — see [per-client wiring](/integrations/mcp-clients).
## The 9 tools
Projected from the live wire (`tools/list`), never hand-counted:
| tool | what it answers |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `nika_check` | Statically audit a workflow (schema · DAG · CEL · effects · permits · cost) **before** running it — findings, or a clean bill. |
| `nika_explain` | Teach one `NIKA-XXXX` error code: cause, category, and the fix form. |
| `nika_schema` | The embedded JSON Schema for `*.nika.yaml` — the structural contract an agent authors against. |
| `nika_examples` | Browse the embedded runnable examples; with a `slug`, that example's full source. |
| `nika_template` | The canonical workflow skeletons (chain · gate-and-act · fanout · …); with a `name`, that skeleton's source — copy it, never invent structure. |
| `nika_canon` | The spec canon SSOT (`canon.yaml`) — the locked counts and names. Cite it, never a remembered number. |
| `nika_catalog` | The embedded provider/model catalog — models, capabilities, context windows, API-key env vars. |
| `nika_tools` | The embedded builtin-tool catalog — every `nika:*` tool with its model-facing JSON Schema. |
| `nika_inspect` | The typed graph of a checked workflow (`graph_format: 2`) — nodes, edges with their kinds, byte-parity with `nika inspect --format json` and the LSP. |
The split is deliberate: **validate** (`nika_check` · `nika_explain`) and
**learn** (`nika_schema` · `nika_examples` · `nika_template` · `nika_canon` ·
`nika_catalog` · `nika_tools` · `nika_inspect`).
## Why read-only is the whole point
A `.nika.yaml` can declare `exec:` shell steps — that is the language working
as designed. An MCP tool that *ran* workflows would hand every wired agent a
shell with your credentials, one prompt-injection away. So the oracle refuses
the category:
* **No `nika_run`, no `nika_generate`, no write tools — ever.** Authoring is
the skill's job (the agent writes the file, you review it); running is the
CLI's job (`nika run`, with budget flags, under your eyes).
* **Secrets never transit the oracle.** The catalog names the env var a
provider needs; it never reads or returns values.
* What the trace's hash chain proves — and what it does not — is stated
plainly in [machine surfaces](/reference/machine-surfaces): tamper-evidence
for the recorded run, not a signature. Signed attestations are a separate,
future layer.
## Context budget
The oracle is lean by design — a handful of read-only tools with compact
outputs, sized so wiring it costs an agent almost nothing. Clients that
support per-server tool filtering can narrow further (e.g. expose only
`nika_check` + `nika_template` to a coding agent).
## Verify a wiring
Ask the connected agent to list its tools — the nine names above should
appear. From a shell, the JSON-RPC handshake works too:
```sh theme={"system"}
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
| nika mcp
```
And the last inch — a real `nika_check` call. `workflow` takes the file's
**source text** (the oracle has no filesystem access by design), so shell in
the file's contents:
```sh theme={"system"}
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
"{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"nika_check\",\"arguments\":{\"workflow\":$(python3 -c 'import json,sys;print(json.dumps(open(sys.argv[1]).read()))' flow.nika.yaml)}}}" \
| nika mcp
```
A clean workflow answers with the audit verdict in `content[0].text` —
same output as `nika check`, over the wire.
# Providers catalog
Source: https://docs.nika.sh/reference/providers-catalog
Every LLM provider Nika knows about: the canonical stdlib catalog — local-first — plus API dialects and environment variables.
Nika's stdlib v0.1 ships ** canonical providers**:
local / self-hosted runners,
cloud APIs, and deterministic mock. You select one with
a single `model: /` field. **Local first** · nothing leaves
your machine unless a cloud provider is explicitly selected.
**Canonical source:** [`stdlib/providers-v0.1.md`](https://github.com/supernovae-st/nika-spec/blob/main/stdlib/providers-v0.1.md)
in the spec (counts from `canon.yaml`). The engine additionally carries an
extended catalog of TOML records
([`crates/nika-catalog/data/llm-providers.toml`](https://github.com/supernovae-st/nika/blob/main/crates/nika-catalog/data/llm-providers.toml))
used for capability metadata. Anything outside the canonical catalog routes
through the `openai` + `base_url` escape hatch, not its own prefix.
## Overview
canonical: local · cloud · mock
**3**: openai-chat (the 5 local + most cloud) · anthropic · gemini
in `model-capabilities.toml`
## How to pick a provider
Local / self-hosted (Ollama / LM Studio / llama.cpp / LocalAI / vLLM): zero key, zero egress, sovereign by default. EU cloud (Mistral). US frontier (Anthropic / OpenAI / Google). Aggregator (OpenRouter). Each has different residency and compliance trade-offs.
All 5 local providers + most cloud providers speak `openai-chat`: same request shape, differing base URL. `anthropic` and `gemini` have native dialects.
Local providers need their server running (e.g. the Ollama daemon on `localhost:11434`) and no key. Cloud providers declare an `env_var` (e.g., `MISTRAL_API_KEY`). `nika doctor` checks provider readiness before run.
Every provider declares both. `default_model` is the capable tier; `cheap_model` is the cost-optimized tier. Capability rules in `model-capabilities.toml` determine which models support vision / tools / streaming / structured output.
## By category
Local/open-weight first (sovereignty default), then cloud starting with
Mistral (EU) — anthropic/openai are never presented first, per the
catalog's own presentation-order note in `canon.yaml`.
| ID | Name | Dialect | Env var | Example model |
| ---------- | ---------------------------------------------- | ------------- | -------- | ----------------------------- |
| `ollama` | Ollama daemon (`localhost:11434`) | `openai-chat` | `(none)` | `llama3.1` |
| `llamacpp` | llama.cpp `llama-server` (`localhost:8080/v1`) | `openai-chat` | `(none)` | `qwen2.5-7b-instruct` |
| `vllm` | vLLM OpenAI server (`localhost:8000/v1`) | `openai-chat` | `(none)` | `meta-llama-3.1-70b-instruct` |
| `lmstudio` | LM Studio (`localhost:1234/v1`) | `openai-chat` | `(none)` | `qwen2.5-14b-instruct` |
| `localai` | LocalAI (`localhost:8080/v1`) | `openai-chat` | `(none)` | `llama-3.1-8b-instruct` |
**The sovereign default.** No API key, no cloud egress:
`model: ollama/llama3.2:3b` runs offline / air-gapped. Any other
OpenAI-compatible local server (Jan · llamafile · KoboldCpp) routes
through the `openai` + `base_url` escape hatch.
| ID | Name | Dialect | Env var | Default model |
| ------------- | ----------------------------------------------------------------- | ------------- | -------------------- | ----------------------------------- |
| `mistral` | Mistral AI (EU · sovereign-leaning) | `openai-chat` | `MISTRAL_API_KEY` | `mistral-large-latest` |
| `huggingface` | Hugging Face Inference Providers (100+ open-weight · 18 backends) | `openai-chat` | `HF_TOKEN` | `Qwen/Qwen3.5-9B:cheapest` |
| `openai` | OpenAI | `openai-chat` | `OPENAI_API_KEY` | `gpt-4o` |
| `xai` | xAI Grok | `openai-chat` | `XAI_API_KEY` | `grok-3` |
| `anthropic` | Anthropic Claude | `anthropic` | `ANTHROPIC_API_KEY` | `claude-sonnet-4-6` |
| `gemini` | Google Gemini | `gemini` | `GEMINI_API_KEY` | `gemini-2.5-flash` |
| `deepseek` | DeepSeek | `openai-chat` | `DEEPSEEK_API_KEY` | `deepseek-chat` |
| `groq` | Groq (fastest open-weight) | `openai-chat` | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
| `openrouter` | OpenRouter gateway | `openai-chat` | `OPENROUTER_API_KEY` | `anthropic/claude-sonnet-4-6` |
| `nvidia` | NVIDIA API (Nemotron 3 · NIM self-hostable) | `openai-chat` | `NVIDIA_API_KEY` | `nvidia/nemotron-3-super-120b-a12b` |
| `moonshot` | Moonshot AI (Kimi K3 · frontier open-weight) | `openai-chat` | `MOONSHOT_API_KEY` | `kimi-k3` |
Keys live in `${{ secrets.* }}` / env vars, never inline in the
workflow. OpenRouter aggregates \~200 models under one key
(`openrouter//`); Hugging Face routes 100+ open models
across 18 backends under one `HF_TOKEN`. Use either to experiment
across vendors without per-vendor keys.
| ID | Name | Dialect | Env var | Default model |
| ------ | ------------- | ------- | -------- | ---------------------- |
| `mock` | Mock provider | custom | `(none)` | `echo` (deterministic) |
**Mock** ships with every install for deterministic tests:
`model: mock/echo` returns the prompt verbatim, zero network, zero
entropy. The canonical examples + CI rely on it.
## Full alphabetical list
`anthropic` · `deepseek` · `gemini` · `groq` · `huggingface` ·
`llamacpp` · `lmstudio` · `localai` · `mistral` · `mock` · `moonshot` ·
`nvidia` · `ollama` · `openai` · `openrouter` · `vllm` · `xai`
total: local · cloud ·
test · matches `canon.yaml` in the spec repo. The
engine's extended capability catalog ( TOML records)
is an implementation detail: extended IDs are reached via the
`openai` + `base_url` escape hatch, never their own prefix.
## Example: invoke a provider
```yaml workflow.nika.yaml theme={"system"}
nika: v1
workflow:
id: multi-provider
tasks:
primary:
on_error: { skip: true } # a dead provider skips (error preserved) · the gate below reads it
infer:
model: ollama/llama3.2:3b # local · zero key
prompt: "Explain linear algebra in one sentence."
fallback:
with:
problem: ${{ tasks.primary.error }} # failure-observation edge · a skip carries its preserved error
when: "${{ with.problem != null }}" # local read of the binding
infer:
model: mistral/mistral-large
prompt: "Explain linear algebra in one sentence."
```
```bash Required env theme={"system"}
ollama pull llama3.2:3b # local primary · no key
export MISTRAL_API_KEY=... # cloud fallback only
nika run workflow.nika.yaml
```
`infer:` is live in v. Run `nika doctor` first to see
which provider keys or local servers are available without printing secrets.
## See also
The rules that govern which models support which features.
How provider routing works at runtime.
`NIKA-1xx` provider errors.
Workflow envelope, `.nika.yaml` top-level keys.
# JSON Schema
Source: https://docs.nika.sh/reference/schema
The machine-authoritative schema for .nika.yaml.
The canonical schema lives at:
**[https://nika.sh/schema/workflow.json](https://nika.sh/schema/workflow.json)**
It's a standard [JSON Schema draft 2020-12](https://json-schema.org/)
document. Your editor's YAML LSP can consume it directly.
## Contract version
`nika: v1`, the first line of every workflow. A **single version marker**:
the key `nika` declares "this is a Nika workflow", the value `v1` pins the
language contract version. There is no separate `schema:` or `apiVersion:`
field (the engine's internal canonical URI stays `https://nika.sh/spec/v1`
for RDF / conformance tooling, but the author never types a URL).
## Point your editor
Add the LSP modeline at the top of any `.nika.yaml` so your editor's YAML
language server consumes the JSON Schema directly:
```yaml the modeline · paste at the top of any workflow theme={"system"}
# yaml-language-server: $schema=https://nika.sh/schema/workflow.json
nika: v1
workflow:
id: my-workflow-id
```
See [editor setup](/getting-started/editors) for per-IDE instructions.
## Local validation
```bash theme={"system"}
nika check workflow.nika.yaml # validates against the v1 contract
nika check workflow.nika.yaml --strict # + extra lints
```
## Versioning policy
* `v1` is the **only value for the entire lifetime** of the v1 contract.
* Minor additions (a new optional field, a new builtin) are **additive**
and never change the marker: no `@minor` bumps, no `nika migrate` for them.
* **There is no `nika: v2` — ever**: pre-1.0 grammar changes happen inside
`v1` (the pre-1.0 stability contract) · after engine 1.0.0 the grammar is
additive only.
* Anti-pattern: `nika: v1.0` · `nika: "1"` · `nika: 1.0`. The value is exactly `v1`.
# Live state
Source: https://docs.nika.sh/reference/status
Live state of the Nika Diamond rewrite, snapshotted by hand from the engine's `scripts/refresh-status.sh` block. Read the provenance note before quoting it.
Every number here imports `STATUS` from [`/snippets/_status-snapshot.mdx`](https://github.com/supernovae-st/nika-docs/blob/main/snippets/_status-snapshot.mdx).
**Mixed provenance, and worth knowing which is which:** `version` and
`lastUpdated` are auto-derived from the published release by
`scripts/mintlify-snapshot.sh`; the counts are a **hand-maintained periodic
snapshot** of the engine's canonical
[`scripts/refresh-status.sh`](https://github.com/supernovae-st/nika/blob/main/scripts/refresh-status.sh)
block. The engine's hygiene vector 23 guards that block *inside the engine
repo* — it cannot see this one. So a fresh `lastUpdated` is not evidence
that the counts are fresh.
**Counts as of engine `main` ** · branch `{STATUS.branch}` ·
**snapshot refreshed** .
## At a glance
crates past 12 gates
passing · clippy warnings
vectors gate every push
total · Accepted · Proposed
LLM providers in the catalog
rules in `model-capabilities.toml`
## Canonical block
The engine's `refresh-status.sh` emits this block inside the engine repo
(`ROADMAP.md`, `.claude/CLAUDE.md`). The rows below are a hand-copy of it,
taken at `{STATUS.head}`.
| field | value |
| --------------------------- | ------------------------ |
| branch | `{STATUS.branch}` |
| HEAD | `{STATUS.head}` |
| workspace version | v |
| crates (workspace) | |
| crates (admitted) | |
| crates (WIP) | · |
| crates (projected horizon) | · hard cap (ADR-037) |
| library tests | passed, 0 failed |
| clippy | warnings |
| ADRs | ( Accepted + Proposed) |
| hygiene vectors | |
| providers (catalog records) | |
| capability rules | |
A per-vector green/yellow/red breakdown is deliberately absent: it is a live
CI result, not a docs constant. Run `bash scripts/hygiene/check-all.sh --format=json` in the engine repo for it.
## How to refresh
```bash Engine repo · the counts theme={"system"}
git clone https://github.com/supernovae-st/nika.git
cd nika
bash scripts/refresh-status.sh # full refresh (runs cargo test + clippy)
bash scripts/refresh-status.sh --quick # structural fields only, skips cargo
# then hand-copy the regenerated block into this repo's snippets/_status-snapshot.mdx
```
```bash Docs repo · the release fields theme={"system"}
bash scripts/mintlify-snapshot.sh # version + lastUpdated ONLY
```
## Drift protection, stated honestly
Engine hygiene vector 23 (`check-status-claims-sync.sh`) parity-checks the
block against `ROADMAP.md` and `.claude/CLAUDE.md` **within the engine
repo**. It has no visibility into this repo, so nothing fails CI when the
counts here go stale. `scripts/mintlify-snapshot.sh` only rewrites
`version` and `lastUpdated` — it will happily bump the date on a page whose
counts are months old. Treat a recent `lastUpdated` as evidence about the
release, never about the crate, test, or ADR numbers.
## What's admitted today
Diamond crates are admitted, with WIP
. The
admitted set spans the foundation crates, kernel traits, local/effect crates,
providers, all four verbs, runtime, LSP, MCP, and CLI surfaces needed for the
first public release candidate.
## What's locked (decisions the project won't revisit)
* **Crate count is a projection, never a gate**: ADR-037 revised the old 40-42 target to a horizon (hard cap ); crates are admitted when they pass the 12 gates, reached additively across 1.x minors.
* **Real semver toward 1.0**: 0.90.0 is the first public release-candidate; 1.0 ships when release gates are green. Quality over speed.
* **Layer discipline**: L0 zero I/O, L0.5 traits only, strict downward deps, cargo-deny enforced. See [Layers](/architecture/layers).
* **12 gates per crate**: no exceptions. See [Admission](/architecture/admission).
* **Zero `.unwrap()` / `.expect(` in `src/`**: `?` propagation; `workspace.lints.clippy.unwrap_used = "deny"`.
* **Not an extraction**: every crate written fresh, legacy as reference only.
* **TDD with mutation ≥ 90%**: RED before GREEN; `cargo mutants -p ` gates admission.
* **`#[non_exhaustive]` on every public type**: see [Forward-compat invariants](/architecture/forward-compat-invariants).
## What's next
The remaining Diamond target crates are additive hardening work, not
prerequisites for the shipped 0.90 release-candidate surface. See the
roadmap for the exact current sequence.
The user-facing front door is already installable. The 1.0 work is about
proving the release checklist, docs, editor setup, and shadow-zone gates
stay true end-to-end.
**Separate count** from the Diamond crate horizon.
11 crates: 1 L2 orchestrator (`nika-connectome`) + 10 L1 satellites (hnsw,
bm25, rrf, rerank, fsrs, rdfs-reasoner, temporal, graph-algos,
autodesc-minimal, autodesc-full). Plugs into
`MemoryStore` / `EmbeddingProvider` trait reservations in `nika-kernel`.
No breaking changes. Design tracked via ADR-004 + ADRs 029-032.
## What's NOT in scope
Explicitly removed or deferred by POST\_AUDIT\_REVISIONS 2026-04-14:
| What | Why | Status |
| ---------------------------- | --------------------------------------- | -------------------- |
| `nika-sdk` | 0 consumers, speculative | Deleted |
| `nika-napi` / `nika-py` | W1 speculative, rebuild Act 3 if needed | Deferred |
| `nika-tui` | W1 speculative | Deferred to post-1.0 |
| `nika-macros` | No proc macros in L0 (Q1 locked) | Never a crate |
| `nika-lsp-core` | Merged into `nika-lsp` | Module, not crate |
| `nika-policy` / `nika-cache` | Modules in `nika-runtime` | Not crates |
| `nika-storage` | Module in `nika-daemon` | Not a crate |
## See also
Visual map of admitted / WIP / planned crates by layer.
The 12 gates every crate passes before joining.
NIKA-XXX code reference per crate range.
Every crate admission with verified numbers.
## The Nika ecosystem
# YAML syntax reference
Source: https://docs.nika.sh/reference/yaml-syntax
Full .nika.yaml schema. Every field, type, and validation rule.
The machine-readable schema is at
[https://nika.sh/schema/workflow.json](https://nika.sh/schema/workflow.json).
Point your editor at it for live validation. See [editor setup](/getting-started/editors).
## Top level
```yaml the envelope · a skeleton, not a runnable file theme={"system"}
nika: v1 # required · language + contract version (single marker)
workflow: # required · the identity object
id: # required · kebab-case · unique within file
description: # optional · human-readable
model: / # optional · default model for infer/agent tasks
# the four value authorities · one role each · no other value namespace exists
inputs: { ... } # optional · TYPED · the caller supplies at launch · ${{ inputs.* }}
config: { ... } # optional · TYPED · the deployment supplies · logged · ${{ config.* }}
const: { ... } # optional · fixed in the file · never caller-supplied · ${{ const.* }}
secrets: { ... } # optional · vault/env/file references · masked · ${{ secrets.* }}
permits: { ... } # optional KEY · but ABSENT = zero authority (see below)
policy: { ... } # optional · the run's declared limits
types: { ... } # optional · named PascalCase type declarations
run: { ... } # optional · the run's entropy + clock declaration
tasks: { ... } # required · non-empty MAP · the key IS the task id
outputs: { ... } # optional · the workflow's return value
assert: [ ... ] # optional · the author's obligations, checked
```
## The four value authorities
Every value a workflow declares lands under exactly one of four authorities.
The family is closed — a value-namespace read outside it is `NIKA-VALUES-003`.
| Authority | Who supplies it | Shape | Integrity |
| ---------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------- |
| `inputs:` | the **caller**, at launch (`--var k=v` · an MCP `nika.run_workflow` argument) | `{ type, required?, default?, description? }` · `type:` required | untrusted |
| `config:` | the **deployment** · may appear in logs and traces | `{ type, default?, description? }` · `type:` required · never `required:` | untrusted |
| `const:` | the **file** · immutable across the run, never caller-supplied | a bare literal, or `{ type, value }` | trusted |
| `secrets:` | a **governed store** · masked everywhere | `{ source: vault\|env\|file, key\|path, egress? }` | trusted |
```yaml theme={"system"}
inputs:
topic: { type: string, required: true, description: "Subject to research" }
bullets: { type: integer, default: 3 }
config:
log_level: { type: string, default: "info" }
const:
output_dir: "./output" # the file owns the destination
secrets:
api_key: { source: env, key: MISTRAL_API_KEY }
```
The integrity column is not decoration: under a `permits:` block an
**untrusted** value that reaches a verb argument is re-gated on its canonical
resolved form, and an escape is refused `NIKA-AUTH-008`. That is the practical
reason a destination the boundary pins belongs in `const:` — a caller cannot
move it out from under the permit.
`vars:` and `env:` are **dead envelope fields** (`NIKA-VALUES-001` /
`NIKA-VALUES-002`), and so are the `${{ vars.X }}` / `${{ env.X }}` reads.
There is no alias: each old use **classifies** into the authority its role
commands — a typed parameter is an `inputs:` declaration, a fixed value a
`const:` entry, non-sensitive runtime configuration a `config:` declaration, a
governed store reference a `secrets:` entry. `nika check --fix` migrates the
provable cases. (The `env:` **inside** an `exec:` block is a different field
and is alive: it is the child process's environment map.)
## `permits:` — the key is optional, the authority is not
An absent `permits:` block declares **zero** authority. Every effect the body
requires is refused at check with `NIKA-AUTH-006` — before a single token —
and any effect attempted at run time fails the task with `NIKA-SEC-004`. A
pure-compute workflow passes with an informational hint; `permits: {}` is the
authored spelling of "I touch nothing".
```bash theme={"system"}
nika check flow.nika.yaml --infer-permits # prints the tightest block, ready to paste
```
Once the block is present every category is **default-deny unless listed** —
including `tools:`, which must name every builtin the file invokes, even the
pure-compute ones.
## The run declaration
```yaml theme={"system"}
run:
entropy: ambient # none | ambient | { seeded: }
clock: system # system | virtual
```
Every source of randomness and time is declared, never ambient. Absent, the
run behaves as `entropy: ambient` + `clock: system`. The two dimensions
couple: only `ambient × system` and `none | seeded × virtual` are legal —
byte-identical journals need deterministic time as much as deterministic
randomness. A declared contradiction refuses at parse (`NIKA-PARSE-026` ·
`NIKA-PARSE-027`); `entropy: none` with a live randomness source consumed
refuses at check (`NIKA-PARSE-028`).
## Types and policy
Two optional envelope keys that most workflows never need — and that the
checker enforces the moment they appear.
`types:` declares named PascalCase types once; `returns:` (and `outputs:`)
then reference them by name instead of restating the shape. `policy:`
declares the limits the run must obey, checked **before** any token is spent.
```yaml typed-triage.nika.yaml theme={"system"}
nika: v1
workflow:
id: typed-triage
types:
Finding:
object:
severity: { enum: ["low", "medium", "high"] }
summary: string
line: { integer: { min: 1 } }
permits:
tools: ["nika:jq"]
policy:
limits:
max_tasks: 20
tasks:
classify:
invoke:
tool: "nika:jq"
args:
input: { severity: "high", summary: "unbounded retry", line: 42 }
expression: "."
returns: Finding
outputs:
severity: "${{ tasks.classify.output.severity }}"
```
The type grammar is closed: the primitives (`null` · `bool` · `integer` ·
`number` · `string` · `bytes` · `uri` · `path` · `duration` · `timestamp`), a
PascalCase reference to one of your own, or a constructor — `object:` ·
`array:` · `map:` · `union:` · `enum:` · plus the refinements `integer:`/
`number:` (`min`/`max`) and `string:` (`pattern`/`min_len`/`max_len`). An
unknown name is `NIKA-TYPE-001`; a recursive `types:` graph is
`NIKA-TYPE-002`.
The policy rule set is closed per minor. Rather than trust a list here, ask
the binary — a refused `policy:` block prints the whole current set:
```bash theme={"system"}
nika explain NIKA-POLICY-001
```
Today that set is six families — `require` · `forbid` · `allow` · `limits` ·
`prefer` · `optimize` — carrying `require.human_gate_before` ·
`forbid.exec_after` (both over `exec`/`write`/`net`/`tools`) ·
`allow.providers` · `limits.max_tasks` · `prefer.providers` · `optimize`
(`cost`/`latency`/`quality`). A violated hard rule is `NIKA-POLICY-001` at
check time, naming rule, task and witness.
## A task
```yaml theme={"system"}
: # the map KEY is the identity (snake_case · unique)
with: { ... } # optional · the DATA boundary · each tasks.* ref = one typed edge
after: { : } # optional · the CONTROL boundary · pred ∈ success|failure|skipped|terminal
when: # optional · LOCAL business condition · evaluated POST-gate
for_each: # optional fan-out (an array · local read)
max_parallel: # optional · cap concurrent iterations
fail_fast: # optional · default true · false = collect errors
timeout: "" # optional · quoted Go-duration · per-iteration on for_each
retry: { ... } # optional retry policy (max_attempts · backoff · on_codes)
on_error: { ... } # optional recovery · recover/skip/fail_workflow + on_codes
output: { : } # optional named jq bindings over the raw output
on_finally: [ ... ] # optional cleanup steps · run if the task STARTED
returns: # optional · the task's output contract (a type expression)
declassify: [ ... ] # optional · raise ONE binding untrusted → trusted
inert: "" # optional · this fetch is a code-bearing artifact, never loaded
# exactly one verb block:
infer: { ... }
exec: { ... }
invoke: { ... } # tool: XOR workflow:
agent: { ... }
```
The eighteen modifiers above are the complete task surface. `returns:`,
`declassify:` and `inert:` are the three that carry a contract of their own:
| Modifier | What it declares |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `returns:` | The task's output type, as a [type expression](#types-and-policy) — exclusive with a verb-level `schema:` (`NIKA-TYPE-003`). Under composition it is how the parent states the child's shape. |
| `declassify:` | The **only** door through the permit-parameterization taint. Each entry raises exactly one binding: `from:` (a dotted path) · `to: trusted` · a non-empty `because:`. It lifts the taint law only — the value is still matched against the declared boundary, so it is never a permit bypass. |
| `inert:` | The **only** door through the data-as-code sink. A `nika:fetch` whose resolved URL names a code-bearing class (`.pkl` · `.py` · `.so` · the closed NEP-0006 list) is refused with `NIKA-SEC-008`; a non-empty `inert:` string declares the artifact will never be loaded or run. It lifts that law only — never the `net.http` boundary, never the SSRF floor. |
```yaml inert-fetch.nika.yaml theme={"system"}
nika: v1
workflow:
id: inert-fetch
permits:
net: { http: ["example.com"] }
tools: ["nika:fetch"]
tasks:
audit_weights:
inert: "checksum audit only · the file is hashed, never deserialized"
invoke:
tool: "nika:fetch"
args: { url: "https://example.com/model.pkl" }
outputs:
status: "${{ tasks.audit_weights.output.status }}"
```
Drop the `inert:` line and that same file fails the check with
`NIKA-SEC-008`. Both doors are greppable by design: a reviewer can find every
place a law was waived, and each one carries its written justification into
the run receipt.
`tasks.*` crosses a task boundary through exactly **two doors** — `with:`
(data · observations) and `after:` (control) — and the engine computes the
graph FROM those doors. A `with:` binding that references `${{ tasks.X.* }}`
both names the data and declares a typed edge; an `after:` entry
(`{producer: predicate}`) orders on state and carries no data. `depends_on`
is dead (`NIKA-PARSE-024` · `nika check --fix` migrates it), and a `tasks.*`
reference outside the boundary — in a verb body, `when:`, or `for_each:` —
is `NIKA-VAR-021` with a machine-applicable fix (hoist into `with:`).
The `after:` predicate set is **closed**: `success` · `failure` · `skipped` ·
`terminal` (`terminal` admits any settled state, cancelled included). Anything
else is `NIKA-DAG-005`; the participial spellings `succeeded` and `failed` are
dead forms and refuse with their respelling, which `nika check --fix` applies.
The full per-field semantics live in the spec:
[03 · The flow](https://github.com/supernovae-st/nika-spec/blob/main/spec/03-dag.md).
## Timeouts
`timeout:` is a quoted **Go-duration string** (`"90s"`, `"7m"`, `"1h30m"`) and
bounds the **entire task**, wall-clock: retries and their backoff sleeps
included. Exceeding it fails the task with `NIKA-TIMEOUT-001` — catchable by
`on_error:`, never retryable (the timeout already covered the retries by
definition). On a `for_each` task the clock applies **per iteration**.
On an `infer:` / `agent:` task the declared `timeout:` also **governs the
provider HTTP deadline**: `timeout: "7m"` gives the provider round-trip those
seven minutes — no internal HTTP default undercuts the declared budget.
```yaml theme={"system"}
deep_review:
timeout: "7m" # the whole task AND the provider deadline
infer:
model: ollama/qwen3:8b # a thinking model legitimately needs minutes
prompt: "Review this diff…"
```
When **no** `timeout:` is declared, the provider deadline defaults per
provider **class**:
| Class | Providers | Default deadline |
| ----- | ------------------------------------------------------------------------------------------ | ---------------- |
| Local | `ollama` · `lmstudio` · `llamacpp` · `localai` · `vllm` | **≥ 300s** |
| Cloud | `mistral` · `anthropic` · `openai` · `openrouter` · `groq` · `deepseek` · `gemini` · `xai` | 30s |
A local model routinely needs minutes for one completion on consumer
hardware — a 30s-everywhere default would silently kill every serious
local-first workflow before the model finishes thinking. The class is keyed
on the **canonical provider id**: a `base_url` override never flips it.
Two honest bounds ride the transport (pinned by the engine's wire tests):
* **600s ceiling on a fully-silent connection.** A non-streaming completion
delivers zero bytes while the model computes, so the transport cannot tell
*thinking* from *dead*. A longer `timeout:` still bounds the task, but only
a connection that starts delivering can use it.
* **Streaming rides only an explicit budget.** A declared `timeout:` bounds a
streaming request; when none is declared, the idle-read guard reaps a
*stalled* stream instead of capping a healthy one.
Normative home: [stdlib · providers §Transport deadline](https://github.com/supernovae-st/nika-spec/blob/main/stdlib/providers-v0.1.md).
## Verbs
See individual pages:
* [`infer`](/concepts/verbs#infer): LLM call
* [`exec`](/concepts/verbs#exec): shell commands
* [`invoke`](/concepts/verbs#invoke): built-in or MCP tool (HTTP fetch is `invoke: nika:fetch`)
* [`agent`](/concepts/verbs#agent): tool-calling loop
## Full schema
The canonical JSON Schema is at
[https://nika.sh/schema/workflow.json](https://nika.sh/schema/workflow.json).
It's 100% machine-authoritative. This doc is a readable summary.
## Contract version policy
`nika: v1` is a **single version marker**: `v1` is the only value for the
entire lifetime of the v1 contract. Minor additions (a new optional field, a
new builtin) are **additive** and never change it (no `@minor` bumps, no
`nika migrate` for them). There is no `nika: v2` — ever:
pre-1.0 grammar changes happen inside `v1` (the pre-1.0 stability contract) ·
after engine 1.0.0 the grammar is additive only.