Tool dispatch vs cognitive delegation — `use <Tool>(k=v)` vs `apply: <Tool>`
AXON gives you two distinct ways to involve a tool in a flow. They
are not interchangeable, and they are not "the same thing, one
deterministic and one broken." They are two different epistemic
operations. Picking the wrong one is the single most common migration
error — so this page is the law.
The two operations
1. use <Tool>(k = v, …) — deterministic tool dispatch
A flow-level statement. The program asserts the call: the compiler
validates the named arguments against the tool's parameters: schema
(caller-blame, before any dispatch), the runtime assembles a typed JSON
body, and it executes directly against the tool-server — the LLM is
never involved. Deterministic, predictable, audited.
tool CrmRadar {
provider: http # ← see "the provider contract" below
parameters: { company: String, max_results: Int, active: Bool }
output_type: CrmReport
}
flow ScanCrm(company: String) -> CrmReport {
use CrmRadar(company = company, max_results = 5, active = true) # ← direct dispatch, no LLM
step Summarize { ask: "Summarise ${CrmRadar_result}" output: CrmReport }
return Summarize.output
}
Kwarg value forms (what v can be)
In use <Tool>(k = v), the value v is resolved at runtime by its kind:
- Literal — a quoted string (
"hello"), a number (10), a bool (true), a list. Coerced to the parameter's declared JSON type. A quoted string interpolates${param}/${StepName}inside it. - Reference — a bare identifier or a dotted step output, resolved
against the live bindings (like a
let):- a flow parameter —
company→ the request'scompanyvalue; - a prior step's output —
ExtractUrl.output(or the bare step nameExtractUrl) → that step's result. This is the extract → dispatch pattern for multi-argument tools: each argument comes from its own extraction step.
- a flow parameter —
flow Pulse(brief: String) -> Report {
step ExtractUrl { ask: "extract the URL from ${brief}" output: String }
step ExtractCompany { ask: "extract the company from ${brief}" output: String }
use GeneratePulse(
url = ExtractUrl.output, # reference → the step's output
company_name = ExtractCompany.output, # a distinct derived value per arg
source = brief # reference → a flow parameter
)
step Summarize { ask: "summarise ${GeneratePulse_result}" output: Report }
return Summarize.output
}
The type-checker validates a reference's source type against the
declared parameter type (caller-blame, compile time) — a url = ExtractCount.output
where the step outputs an Int and the parameter is a String is a
compile error, not a runtime mismatch.
2. apply: <Tool> — cognitive delegation
A step backend. The step runs as an LLM reasoning call with the tool made available to the model. The model decides, stochastically, whether (and how) to invoke it. This is genuine cognition — bounded rationality under uncertainty — not a deterministic call.
flow Investigate(brief: Brief) -> Finding {
step Research use Analyst {
given: brief
apply: WebScout # the model reasons and MAY call WebScout
ask: "Investigate the brief and synthesise a finding"
output: Finding
}
}
Why they are different (the four pillars)
use <Tool>(k=v) | apply: <Tool> | |
|---|---|---|
| Mathematics | a typed effect invocation — a morphism with a validated signature | a stochastic kernel (the LLM), its output bounded by the epistemic lattice ceiling |
| Logic | CT-2: a malformed call is a provable caller error at compile time | you cannot prove it calls — invocation is a decision, not a deduction |
| Philosophy | the program's assertion (apodictic about the call; the result decays per Theorem 5.1) | delegated judgement under uncertainty — the stochasticity is honest and surfaced (the epistemic envelope) |
| Computation | direct dispatch to the tool-server | the model as a runtime that may emit a tool-call |
In operating-system terms: use is a syscall (the program invokes the
service, deterministically); apply: is delegating cognition (you give
a reasoning process a capability and let it decide). Both are first-class.
AXON does not pretend the LLM is deterministic. It contains the model's stochasticity and surfaces it (the lattice, the epistemic envelope). You choose
usewhere you need determinism, andapply:where you genuinely want the model to reason with a tool available. Do not writeapply:expecting a deterministic call — that is asking the LLM to be deterministic, which contradicts whatapply:is.
The provider contract (required for real dispatch)
use <Tool>(k=v) dispatches directly only for tools whose provider:
the runtime tool-registry handles: http and mcp (plus the
built-in native/stub for testing). A tool declared with a
model-native slug — tavily, brave, openai, … — is not directly
dispatched: it falls through to the LLM's own tool-use surface, even with
use(k=v). So a deterministic call is two decisions:
- Form:
use <Tool>(k = v, …)(flow-level), notapply:. - Provider:
provider: http(ormcp) + a wired endpoint (an absoluteruntime:URL, or a relative slug resolved against the server/per-tenant tool base URL).
The compiler indicates the path (axon-W004)
Because the distinction is easy to miss, the type-checker is the honest
guide: when you write apply: <Tool> on a tool that declares a
parameters: schema, axon check emits axon-W004 — it names the
cognitive nature, and redirects you to the deterministic
use <Tool>(k = v, …) form (listing the schema's parameters). The
compiler never silently makes apply: deterministic; it tells you
which operation you wrote and which one achieves determinism.
Migrating a skill that should be deterministic
# ❌ Cognitive delegation — runs as an LLM step; the model decides
flow Scan(req: LeadRequest) -> CrmReport {
step Render { given: req apply: CrmRadar ask: "scan" output: CrmReport }
return Render.output
}
# ✅ Deterministic dispatch — direct, schema-validated, no LLM
flow Scan(company: String) -> CrmReport {
use CrmRadar(company = company, max_results = 5, active = true)
step Summarize { ask: "Summarise ${CrmRadar_result}" output: CrmReport }
return Summarize.output
}
See also
axon://primitives/tool— thetooldeclaration (parameters:,output_type:, providers, endpoint wiring).axon://primitives/step—apply:as a step backend.axon://logic/flow_composition—apply: <Flow>(composition), the other meaning ofapply:.