step
Since v0.1.0 (initial language) · Used inside a declaration
Grammar
step <Name> [use <Persona>] {
given: <Expression> # required — typed input expression
ask: "<prompt>" # required for cognitive steps — natural-language instruction
output: <Type> # required for cognitive steps — output type
confidence_floor: <0.0..1.0> # optional — minimum confidence to accept
requires_context: <tokens> # optional (v2.22.0) — min context window; resolver picks the smallest model that fits
now: "<IANA-tz>" # optional (v2.46.0) — declared cognitive time: inject the run's captured instant, rendered in THIS zone
navigate: <Graph.Path> # optional — route through a knowledge graph
apply: <FlowName> # optional — invoke a declared flow
# Reasoning sub-constructs (each its own primitive):
use <Persona> # per-step persona override
probe <ProbeName> # diagnostic probe
reason <Strategy> # explicit reasoning step (CoT / debate / ...)
weave <Spec> # multi-thread reasoning braid
stream <Spec> # streaming output spec
}
A step is the atom of cognition in AXON. It declares one
typed operation: an input (given), an instruction (ask), and a
typed output. The compiler enforces that every flow body is a
sequence of steps (and control-flow constructs), and that every
data dependency between steps is resolved lexically.
Every other cognitive surface in AXON — reason, weave,
probe, stream — appears as a sub-construct inside a step's
body, never as a flow-level node of its own.
Surface
step is nested — it lives only inside a flow body. It is
not a top-level declaration.
flow GreetUser(name: String) -> Greeting {
step ComposeGreeting use FriendlyAssistant {
given: name
ask: "Write a warm, locale-aware greeting"
output: Greeting
confidence_floor: 0.7
}
}
Header
step <Name>
<Name>— aPascalCaseidentifier, unique within the enclosing flow. The step's outputs are referenced from subsequent steps as<Name>.output.
step <Name> use <Persona> (optional)
A per-step persona override. When present, this step runs
under the named persona instead of the flow-level binding
(run … as <Persona>). The override is lexical: it does not
propagate to sub-flows invoked via apply inside this step.
Body
given: (required for cognitive steps)
The typed input expression. Accepts:
- A flow parameter name (
name,doc). - A previous step's output (
Extract.output). - A bound
letvariable. - A field projection (
User.email). - A literal (numeric / string / list).
The type checker resolves the expression against the lexical scope and records the inferred type — this becomes the input type the runtime passes to the backend.
ask: (required for cognitive steps)
A string literal containing the natural-language instruction the model receives. This is the only unstructured field on a step; it is the bridge between the typed surface and the generative model.
ask: "Extract all parties, obligations, dates, and penalties"
Prompt-engineering best practice (axon://logic/prompt_design):
keep ask: imperative and outcome-oriented; leave structural
specifications to the output: type and to bound anchors.
output: (required for cognitive steps)
The declared output type. Accepts the full type-expression shape (v1.23.0):
| Form | Example |
|---|---|
| Bare type | EntityMap |
| Generic | List<Risk>, Optional<Citation> |
| Stream (v1.24.0) | Stream<Token> |
| Nested generic | FlowEnvelope<List<TenantRecord>> |
| Optional | Greeting?, Stream<Token>? |
Streaming output (Stream<T>) participates in the algebraic
stream-effect runtime — the step's emissions are materialised as
SSE / WebSocket frames on the declared transport.
confidence_floor: (optional)
A numeric literal in [0.0, 1.0]. Overrides the persona's
confidence_threshold for this step alone. Useful when one step
in a flow is markedly higher-stakes than the others (e.g. a
medical-diagnosis step inside an otherwise informational flow).
requires_context: (optional, v2.22.0)
A positive integer — the minimum context-window size (in tokens) this step needs. The v2.22.0 capability-aware resolver picks the smallest model whose context window is ≥ this value, and fails closed:
step Big {
given: history
ask: "summarize ${history}"
requires_context: 16000
output: String
}
The point is to declare a capability requirement, never a vendor SKU —
the resolver, not the author, maps the requirement to a concrete model.
A step without requires_context: lowers to None (back-compat: the
flow-level model binding applies). Validation is axon-T809: the
value must be > 0 and not exceed the largest context window in the
capability catalog (a requirement no model can satisfy is a compile
error, not a silent fallback).
now: (optional, v2.46.0)
An IANA timezone string literal ("America/Bogota", "UTC") —
the step's declared cognitive time. When present, the runtime
injects the run's single captured instant — rendered in THIS zone —
into the step's system prompt, e.g.:
Current datetime: 2026-07-07T14:33:05-05:00 (America/Bogota; tzdb 2025b; captured at run start).
This is time_is_an_explicit_input (v2.27.0) applied to cognition: the
source DECLARES that the step carries time and WHOSE time it reasons
in; the runtime supplies the instant; the envelope records
(captured_utc, tzdb_version, zones) so the exact prompt is
replayable. There is deliberately no ambient clock — a step
without now: (and no frame-level context.now:) gets no temporal
line, byte-identical to pre-v2.46.0.
step Triage {
given: request
now: "America/Bogota"
ask: "Propose three visit slots this week."
output: VisitPlan
}
Three laws:
- One instant per run — every
now:-bearing step in a run renders the SAME captured instant (steps never disagree about "now"); a daemon tick is a new run with a fresh capture. - Step overrides frame — a step's
now:wins over the boundcontextdeclaration'snow:for that step alone. - Fail-closed — the zone is format-checked at compile time
(
axon-T892:"Area/Location"or"UTC"); a shape-valid zone unknown to the tz database fails the step loudly at runtime (and is caught earlier by theTemporalContextSoundnessproof at verify/deploy). Never a silent omission.
navigate: (optional)
A dotted identifier referencing a path in a declared
knowledge graph or store. When present, the step's given:
expression is routed through the graph before reaching the
model.
step LookupPatient {
given: patient_id
navigate: ClinicalGraph.PatientRecord
ask: "Summarise the patient's relevant history"
output: PatientSummary
}
apply: (optional)
A declared-flow name (flow composition) or a declared
tool (run the tool as this step's backend). When present, the
step invokes the named flow/tool with the given: expression
as its argument, rather than calling the cognitive backend.
step EnrichWithLegalContext {
given: extracted_entities
apply: AnalyzeLegalContext # compose a sub-flow
output: EnrichedEntityMap
}
For flow composition, the applied flow's parameters and return
type are expected to align with given: and output: (the
intended contract).
Enforcement note (v2.8.0). The type-checked, structured way to invoke a tool with named, schema-validated arguments is the flow-level
use <Tool>(k = v, …)form — the type-checker validates the call against the tool's declaredparameters:schema at compile time (CT-2 caller blame, before any dispatch). Seeaxon://primitives/tool. The step-levelapply: <Tool> given: <struct>splat (auto-mapping a struct's fields onto the tool schema) is a planned refinement and is not yet compile-validated; the compiler emitsaxon-W004redirectingapply: <Tool>to the typeduse <Tool>(k = v, …)flow-level form (v2.9.0) — prefer that for typed tool calls.
Sub-constructs (one per step)
Inside a step body, these reasoning surfaces appear as named sub-constructs:
| Sub-construct | Purpose | Doc |
|---|---|---|
use <Persona> | Per-step persona override (header form) | axon://primitives/persona |
probe <Probe> | Diagnostic / probing prompt | axon://primitives/probe |
reason <Strategy> | Explicit reasoning strategy (CoT, debate, …) | axon://primitives/reason |
weave <Spec> | Multi-thread reasoning braid | axon://primitives/weave |
stream <Spec> | Streaming output spec | axon://primitives/stream |
Each sub-construct has its own primitive entry; the step's body parser dispatches structurally to them.
What this primitive is NOT
- Not a top-level declaration. A
steponly makes sense inside aflow. The parser rejects top-levelstepnodes. - Not a function call. A step does not "return" in the
imperative sense; it produces a typed output value the
subsequent steps consume via
<Name>.output. - Not implicitly ordered by data. The flow body's textual order is the canonical evaluation order; the runtime may reorder data-independent steps, but the audit trail always reflects the lexical sequence.
- Not free-form.
stephas a closed grammar (the field set listed above); unknown fields cause a parse error.
See also
axon://primitives/flow— the enclosing primitive that sequencessteps.axon://primitives/persona— whatuse <Persona>binds.axon://primitives/reason— the explicit-reasoning sub-construct.axon://primitives/anchor— typed grounding constraints that shield bind to a step.axon://logic/flow_composition— when to useapply:to invoke a sub-flow vs. inlining a step.