infer, exec, invoke, agent) and
supplies its required fields. The set is closed: the parser’s verb
dispatcher is exhaustive at compile time, so adding a fifth verb is a
schema-version bump, not a patch release.
All four verbs execute end-to-end today: every snippet on this page is
runnable with
nika run at v. Verb grammar is frozen by
the workflow language envelope: these four, forever.Why four?
A verb is a distinct native execution model the engine itself implements. Every workflow decomposes into one of these moves:
Anything else we considered (“transform”, “map”, “branch”, “retry”)
was a special case of one of these four, or workflow-level machinery
(dependencies,
for_each, when) that lives on the task envelope,
not as a new verb.
The vNext families stay on the same tool — uploads are multipart:, forms are
form:, and a bounded site crawl is traverse: (crawl is an intention, fetch
is the primitive):
invoke; everything about ordering is a DAG construct on the task envelope.
Where didfetchgo? Fetching a URL is calling a tool, not a distinct execution model, so it is thenika:fetchbuiltin, reached throughinvoke:(the extract modes become itsmodeargument). Same reason a DB query (invoke: mcp:postgres/query) or a file write (invoke: nika:write) is not its own verb.
exec: run a local command
Spawns a subprocess, captures stdout/stderr, returns exit code + output.
Required: exactly one of command (array · argv through execve, no
shell) | shell (string · the explicit shell door: pipes · redirects ·
globs). A string command: is rejected at parse.
Optional: cwd (path), env (map · OS env for this subprocess), stdin (string), capture (stdout default · stderr · combined · structured). Task-level timeout: applies (not a verb field).
The portable argv example prints a literal message with the system printf.
Save the canonical file
as run.nika, then execute it. It needs no Cargo project or Rust toolchain:
command: ["printf", "%s\n", "Hello from a confined program"]
and declares permits.exec: ["printf"]. The complete file lives in the
canonical argv example.
To adapt it to a build, declare the workspace and toolchain access that build
actually requires, then check and run the resulting workflow.
For the shell door, structured exit status, conditional branches and cleanup,
save the complete pipeline
as exec-pipeline.nika and run nika run exec-pipeline.nika.
That lesson uses explicit sample commands:
its test exits 1, the simulated deploy skips, and cleanup runs. Change the
sample’s exit 1 to exit 0 to exercise the other branch. It performs no real
build or deployment.
Invariants (from nika-kernel::process::ShellExecutor):
Command::newMUST setkill_on_drop(true): no leaked child processes (INV-011).- Concurrent pipe reading uses
tokio::try_join!(INV-012). - Non-zero exit is a task failure (
NIKA-EXEC-001) in the default capture modes, exceptcapture: structured, where the exit code is data (output.exit_code· the task succeeds and the workflow branches on it). Untrusted values ride argv elements (each substituted independently, no shell to break out of) orstdin:— never ashell:string.
ShellExecutor trait (L0.5 kernel contract).
HTTP fetch: invoke: nika:fetch (a builtin, not a verb)
Fetching a URL is calling a tool, so it is the nika:fetch builtin
reached through invoke:, not its own verb. Issues an HTTP request
(GET/POST/etc.), returns bytes or parsed/extracted data; the old extract
modes become the builtin’s mode argument.
secrets: reference — never an inputs: default, never a
literal. And a secret reaching any sink is a blocking leak by default
(NIKA-SEC-006): the egress: entry is the author sanctioning one
specific destination, statically checkable, and it never widens
permits.net.http — it narrows inside it.
Pick the mode for the content — the default is markdown, built
for pages: fetching a JSON API without a mode markdown-escapes the
body (\[, assets\_url) and every downstream jq/fromjson fails
on byte 0. APIs take mode: raw (the exact bytes) or mode: jq + a
jq: expression — the shape step rides the fetch itself, one task
instead of two.
Invariants (from nika-kernel::http::HttpClient):
- Output is tainted by default (external input is untrusted, T3:A).
- Transient statuses (429, 5xx) are retried per
retry:on the task. - Authentication headers never appear in events: they pass through
SecretResolver+AuditSink::SecretRedacted.
nika:fetch’s
full argument schema (extract modes, session, cache).
invoke: call a builtin or MCP tool
Calls a builtin (nika: namespace) or a tool/resource exposed by a
connected Model Context Protocol server. MCP server aliases are
declared in the workflow’s mcp: block.
Required: tool (string · nika:<path> for a builtin OR mcp:<server>/<tool> for an MCP tool).
Optional: args (object · tool-specific schema). Task-level timeout: applies (not a verb field).
chroma is configured in the engine’s MCP server registry
(engine config · out of scope of the workflow file). Builtins use the
nika: namespace, e.g. tool: "nika:read".
Routing goes through ToolExecutor + ToolExecute::execute(ToolCall)
at L0.5, the same trait the agent verb uses to dispatch in-loop
tool calls (crates/nika-kernel/src/runtime/tool_executor.rs).
infer: LLM inference
Single-shot call to a language model. One InferRequest shape covers
all providers in the catalog.
Required: prompt (string).
Optional: system (string), model (<provider>/<name> · overrides the
workflow default), temperature (0.0–2.0), max_tokens (u32), schema
(JSON Schema · structured output), thinking ({ enabled, budget_tokens }),
vision (image inputs).
Model identifiers are catalog-validated at parse time. See the
Providers catalog for live IDs. We
never hard-code them in concept docs because model names drift faster
than the docs refresh cycle.
nika-kernel::provider::Provider):
- Unified
InferRequest(INV-017): one DTO · per-provider wire dialects below (3 implemented today: anthropic · gemini · openai-compat, the dialect that carries most of the 16-provider catalog · plus mock). - Capability rules ( rules)
run before dispatch. Sending
toolsto a model whose capability rule saystool_calling = falsefails with a typed validation error before the network round-trip. - Every provider emits the same
InferEventstream:Delta,ToolUseStart,ToolUseDelta,Thinking,Usage,Done.
Provider trait · ADR-035 (telemetry seams).
agent: multi-turn loop with tools
A model-driven loop: the agent calls tools, observes results, iterates
until a completion signal fires or a budget exhausts.
Required: prompt (string · initial user message).
Optional: system (string), model (<provider>/<name>), tools
(whitelist · glob patterns · default-deny), max_turns (u32 · default 10),
max_tokens_total (u32 · cumulative token budget), temperature (0.0–2.0),
schema (JSON Schema · validates the final message).
nika-kernel::runtime::agent):
- The loop is budget-bounded. The LANGUAGE contract (
max_turns·max_tokens_total· spec 02-verbs) is what workflows declare; the engine’sAgentLoopConfigcarriesmax_turnstoday plus its planning/reflection/compression knobs.max_tokens_totalwires in with the runtime milestone. A ceiling hit is a typed failure (NIKA-AGENT-001/NIKA-AGENT-002·budget_error) with the last assistant message preserved inerror.details.partial_output. Recover it explicitly viaon_error:if a partial is acceptable. Natural completion (ornika:done, optionally withresult:) is success. - A failing tool call feeds its typed error back to the model (the
loop continues against its budgets), EXCEPT
security_error, which fails the task immediately. - Every tool call is taint-checked: untrusted data cannot reach dangerous verbs without an explicit sanitizer.
- The iteration trace (
ToolCallRecord+CheckpointMessage) is emitted as events for audit and, optionally, checkpoint resume.
AgentLoopConfig · ADR-016 (cancellation model).
Verb selection is exhaustive
Thenika-schema parser enforces “exactly one verb key per task” at
compile time: the Verb enum in
crates/nika-schema/src/parser/tasks.rs has four variants, and the
action builder has exactly four match arms. Adding a fifth verb
requires:
- Extending
RawAction+ theVerbenum. - Wiring a new action builder arm.
- Bumping
SchemaVersion(currentlyv1). - Running all 12 admission gates on the consuming L2 verb crate.
Implementation status. Current verb executors ship as design-only
kernel traits; the four L2 verb crates (
nika-verb-infer,
nika-verb-exec, nika-verb-invoke, nika-verb-agent) are all
admitted and execute end-to-end (see
Constellation). fetch is not a verb
crate: it is the nika:fetch builtin reached through invoke. The verb
grammar is locked and shipped (nika-schema).Read next
Workflows
How verbs compose into a DAG.
Bindings
How values flow between tasks.
Providers
The -provider catalog behind
infer and agent.Events
Every verb emits typed events.