flow
Since v0.1.0 (initial language) · Top-level declaration
Grammar
flow <Name>(<param>: <Type>, <param>: <Type>, ...) -> <ReturnType> {
<FlowStep>
<FlowStep>
...
}
# FlowStep ::= step | if | for | let | return | weave | use | remember
# | recall | hibernate | associate | aggregate | explore
# | ingest | navigate | drill | corroborate | listen
# | retrieve | persist | mutate | run | reason | ...
A flow is the unit of orchestration in AXON. It declares a
typed function whose body is an ordered sequence of cognitive
operations — steps, control flow (if, for), bindings (let),
memory operations (remember, recall), graph operations
(navigate, drill), data-plane operations (retrieve,
persist, mutate), and others.
A flow is what you run. The run statement binds a flow to a
persona, a context, and any anchors that must hold across its
execution.
Surface
flow is a top-level declaration. It is not nested inside
another flow, a daemon, or any other primitive. (Flows compose by
calling one another via apply from within a step, not by
nesting.)
flow AnalyzeContract(doc: Document) -> ContractAnalysis {
step Extract {
given: doc
ask: "Extract parties, obligations, dates, penalties"
output: EntityMap
}
step Assess {
given: Extract.output
ask: "Identify ambiguous or risky clauses"
output: RiskAnalysis
}
return Assess.output
}
Header
flow <Name>(<params>) -> <ReturnType>
<Name>— aPascalCaseidentifier, unique among flows within the same module. The compiler builds a per-module flow symbol table at parse time; duplicates are rejected.(<params>)— zero or more comma-separatedname: Typeparameters. Types must resolve to a declaredtype, a built-in (String,Number,Bool), a generic application (List<T>,Stream<T>,Optional<T>?), or a recursive generic (FlowEnvelope<List<TenantRecord>>, since v2.0.0).-> <ReturnType>— optional. When present, the flow's last evaluated step (or explicitreturn) must produce a value assignable to<ReturnType>. When omitted, the flow is treated as effect-only (no return value).
flow GreetUser(name: String, locale: String) -> Greeting { … }
flow EmitMetrics() { … } # no return
flow Chat(prompt: String) -> Stream<Token> { … } # streaming return
Body
The body is a { }-braced ordered sequence of flow steps.
Each step is one of (see axon://primitives/<step-kind> for the
detail of each):
| Kind | Purpose |
|---|---|
step | A cognitive operation (the most common) |
if, for, let, return, break, continue | Control + binding |
reason, probe, weave, stream | Reasoning sub-constructs |
remember, recall, hibernate, associate, aggregate | Memory ops |
explore, ingest, navigate, drill, corroborate | Graph + retrieval |
listen, retrieve, persist, mutate | Data plane |
apply | Invoke another flow (composition) |
The compiler enforces three structural rules across the body:
- Definite assignment — each step that references
<OtherStep>.outputmust lexically follow that step. - Flow-locality — references are resolved against the
enclosing flow's lexical scope;
apply-invoked sub-flows do not see the caller's step outputs. - Return-type compatibility — when
-> <T>is declared, everyreturnstatement (or the last expression-step) must produce a<T>.
Conditions: the pure expression engine (v2.26.0)
An if condition is a pure, total, statically-typed expression
(axon://logic/total_expressions). The closed operator catalog is:
arithmetic + - * / %, comparison == != < <= > >=, boolean
and / or / not, the collection/string builtins .length /
.count / .is_empty / .is_null / .contains(x) / .starts_with(s) /
.ends_with(s), and field/index access .field / [i]. Precedence is
or < and < comparison < + - < * / % < unary < atom; parentheses
group.
So an elementary check is native — no use Tool, no LLM step:
flow Throttle(recent: List<Call>, limit: Int) -> String {
if recent.length >= limit {
return "throttled"
}
return "ok"
}
The compiler type-checks the condition (axon-T810/T811/T812 for a
type-incoherent operator, axon-T813/T814 for a builtin misuse) and
const-folds it: a condition that is a constant (if 2 + 2 == 4)
statically decides its branch and raises axon-W008 (the other branch is
dead code). A reference of unknown static type is permissive. Evaluation is
deterministic and side-effect-free — overflow and division-by-zero fail
closed (the branch is not taken). A simple if x == "v" (a reference
compared to a literal) keeps its pre-v2.26.0 form unchanged.
Streaming returns (v1.24.0)
A flow whose return type is Stream<T> participates in the
algebraic stream effect runtime (v1.24.0). Each step that
emits chunks does so via the <stream: ...> effect row; the
runtime materialises the emissions as Server-Sent Events on the
declared transport (axonendpoint route, socket, etc.).
flow Chat(prompt: String) -> Stream<Token> {
step Generate {
given: prompt
ask: "Reply, one token at a time"
output: Stream<Token>
}
}
The compile-time gate (flow_has_stream_output) checks that the
final step's output type begins with Stream< and ends with >;
adopters who only need batch returns can ignore the entire
stream-effect surface.
Running a flow
A declared flow does nothing until a run statement binds it:
run AnalyzeContract(myContract)
as LegalExpert # persona binding (required)
within LegalReview # context binding (optional)
constrained_by [NoHallucination, NoPHI] # anchor list (optional)
on_failure: retry(backoff: exponential) # failure policy (optional)
output_to: "report.json" # destination (optional)
effort: high # compute hint (optional)
See axon://primitives/run for the full surface.
What this primitive is NOT
- Not a function in the imperative sense. A flow is a typed orchestration of cognitive operations. The runtime is free to reorder data-independent steps and to fuse step bodies for backend efficiency, as long as observed effects respect the declared ordering and the IR audit trail.
- Not nested inside another flow. Composition is via
apply(which calls another declared flow) — there is no anonymous or inline flow grammar. - Not a daemon. A flow runs once per
run; adaemonruns continuously and reacts to events. A daemon's bodies are expressed as flow-step bodies, but the lifecycle is different. - Not an
axonendpoint. Anaxonendpointexposes a flow over HTTP; the flow is the cognition, the endpoint is the wire.
See also
axon://primitives/step— the most common flow-step kind.axon://primitives/run— the binding statement that executes a flow with a persona + context + anchors.axon://primitives/axonendpoint— exposes a flow as an HTTP route.axon://primitives/socket— exposes a flow as a session-typed WebSocket.axon://logic/flow_composition— when to nest sub-flows viaapplyvs. inlinesteps.axon://grammar/top_level— full top-level vs. nested table.