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 nika: v1 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:| Verb | One-line intent |
|---|---|
infer | Ask an LLM. |
exec | Run a local command. |
invoke | Call a builtin tool OR an MCP tool. |
agent | Delegate to an agent loop (LLM + tools + iteration budget). |
for_each, when) that lives on the task envelope,
not as a new verb.
The set is closed at four. Everything callable is a tool under 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: command (string).
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).
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). Sanitize untrusted interpolation through theshell_quotefilter before the command reaches L1.
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.
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 openai-compatible dialect carries most of the 14-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.