The protocol
1
Route: one intent, one template
The templates
are complete, valid skeletons (conformance-gated on every
push). Match the outer shape of the job:Use the template catalog to choose a starting shape.
For finite batches, validation, comparison, privacy boundaries and recovery,
the bounded examples pair each skeleton with
a filled workflow and observable success and refusal behavior.
nika compile --list names your installed templates. Preview with
nika compile <skeleton> --json. A destination writes only a Ready
candidate; chain stays incomplete until its questions are answered
with --answer KEY=JSON_LITERAL. Unknown words do not select a
substitute workflow.2
Adapt the example to the job
Copy the template and replace every
<SLOT: …> value. Review the
comments marking choices, including the model, paths and limits. Keep
the source, validation, transformation and effect boundaries explicit.
If the job needs a construct the template lacks, the
coverage matrix
names the canonical example that exercises it: read it, don’t
guess.3
Check: never ship unchecked
nika check workflow.nika --native-strict. From an app, the same
audit is await nika.check("workflow.nika") on
@supernovae-st/nika@0.120.2. Editors get the same truth live via the
JSON Schema.4
Repair: the error names its fix
Fix exactly what the code says, nothing else, then re-check:
The hard rules
Use these checks when reviewing an authored workflow. The validator enforces them; knowing them just saves round-trips:- One verb per task ·
infer,exec,invokeoragent. - snake_case task ids (CEL-safe) · kebab-case
nika:name. tasks.*crosses a boundary throughwith:orafter:only · the binding IS the edge, the body reads${{ with.<name> }}— the DAG has no invisible edges (NIKA-VAR-021otherwise, anddepends_on:is dead:NIKA-PARSE-024).when:is a${{ }}CEL boolean or the literaltrue/false·${{ inputs.count }}is not a condition;${{ inputs.count > 0 }}is; a bare string without${{ }}is rejected. It reads local namespaces only (the value authoritiesinputs·const·secrets, pluswith·item/index), post-gate; the always-pattern isafter: { x: terminal }, never awhen:trick.- The CEL function set is closed ·
size()· thehas()presence macro · thecontains/startsWith/endsWithstring tests · the?:conditional (cel-subset/0.1). Everything else — arithmetic, regex, transforms — lives in jq, not in${{ }}. nika:writeneedscontent:· a write without it writes nothing.nika:doneonly insideagent.tools· it is the loop sentinel, meaningless elsewhere.- Fan-outs get the leash ·
max_parallel+fail_fast: false+ per-iterationretry/timeout. - Native-first ·
invoke: nika:*→mcp:<server>/<tool>→exec:last. Anexec:a builtin covers earns anative-firsthint;nika check --native-strictfails on any that remain, and a survivingexec:gets an exec-ledger row (task · command · why · unlock) in the workflow header. - Every value lands under one of three authorities ·
inputs:(the caller supplies it · a deployment knob is an input with adefault:) ·const:(the file owns it) ·secrets:(a governed store).vars:,env:andconfig:are dead envelope fields (NIKA-VALUES-001/NIKA-VALUES-002/NIKA-PARSE-005) — classify by the role the value plays, never rename in bulk. - A body with effects carries
permits:· an absent block is zero authority, not a free pass: the check refuses withNIKA-AUTH-006before a token is spent, and the run refuses withNIKA-SEC-004. Write the block withnika check --infer-permits, which prints the tightest one and is paste-ready. - Extract facts, then the law · the model emits closed numeric
facts (
type: integer+ a numericenum). Scoring, routing, publish/abstain isnika:jqornika:decide. A second infer to “pick the level” is the expensive mistake. The shape is13-extract-then-law. Prove the law on const fixtures (unproven-law). The named bundle is14-decide-publish. Check--native-strict, probe builtins onmock/echo(media:17-tts-self·provider: mock), freeze the schema type, pin the glob (exclude: "**/README.md"), then wire a paid model. nika:inspectis live ·view: cost|records|dag_info|threads. The runtime seeds the DAG at run start. Shape:16-inspect-self.nika:composeis loop-only · grant it onagent.toolsafternika:done. The model drafts YAML, gets the fullnika checkJSON, iterates untilvalid. A standaloneinvoke:isNIKA-BUILTIN-COMPOSE-001. Checking never executes. Shape:15-compose-self-check. Parent→child calls stay10-compose-pipeline.- After valid, ask if there is a better one-way · a green check
is legal, not best.
nika check --jsonreportspaid_ready,compiled(the law is proven) andnext(the first repair).nika explain <file>prints a before a paid model panel for the paid-run family. Hintinfer-as-lawfires when a prompt asks the model to assign a belt. Read a matching example when needed, decompose (for_each/ a child workflow), and verify with jq, not a second infer. Handoff only when findings are gone ANDpaid_readyis true (or an honest-red note remains, CONVENTIONS §10). The MCPnika_checkoracle failsinfer-as-lawanddigit-string-enumby default.
After valid: the judgment layer
Validity is the floor. The twelve patterns are the ceiling: deterministic core, typed boundaries, the right gate (when = skip · assert = fail · prompt = human), sovereignty for
sensitive data, budgets on agents, unwind evidence. Every
pattern links the canonical example that embodies it.
The templates
The skeletons, every decision point a slot, conformance-gated.
The twelve patterns
The judgment layer: why each locked choice is locked.
Test the boundary as well as the happy path
Before connecting a paid model or a real destination, exercise the filled workflow with--model mock/echo. Parse its declared outputs and compare
values, not just exit codes. For a guarded task, test an empty collection,
the exact size limit, one item over it, a wrong type and missing authority.
Inspect the trace to confirm a refusal happens before downstream model calls
or writes. See bounded authoring examples for
copyable guards and the limits each one can actually enforce.
A green mock run validates the wiring. Model quality, remote API behavior and
the consequences of an effect need their own evaluation before the workflow
is used for that purpose.