Saltar al contenido principal

tool

Since v0.1.0 (initial language) · Top-level declaration

Grammar

tool <Name> {
provider: <slug> # optional — CLOSED catalog; omit = LLM-routed (T948)
parameters: { k: Type, ... } # optional (v2.8.0) — typed INPUT schema (the call contract)
output_type: <Type> # optional (v2.8.0) — declared OUTPUT type of the tool result
max_results: <integer> # optional — cap on returned items
filter: <expr> # optional — server-side filter expression
timeout: <duration> # optional — wall-clock budget (e.g. 10s, 500ms)
runtime: <ident> # optional — runtime hint / endpoint slug (native | sandboxed | <slug>)
sandbox: <true|false> # optional — force-execute inside a sandboxed worker
effects: <effect-row> # optional — declared effects (<network>, <io>, ...)
shield: <ShieldRef> # required (v4.3.0) when a parameter's type carries a compliance class (T1221)
target: <SocketRef> # optional (v2.39.0) — dispatch over this socket (Remote Hands)
risk: safe | destructive # optional (v2.39.0) — technician-command risk class
argv: [<token>, ...] # required with target:+bash (v2.39.0) — the argv template
}

A tool declares an external capability the cognition layer can call — a web search, a code interpreter, an HTTP fetch, a database query, a vector retriever. The declaration is purely descriptive: it binds a provider, declares its effects, and sets operational limits. The runtime resolves the provider at execute time against the backend's tool-binding registry.

A tool is referenced from a step by passing it in the persona or by composing it into the model's tool-use surface (the runtime forwards the declared effects: row into the audit trail).

Surface

tool is a top-level declaration. It is not nested inside a flow, a step, or a persona.

tool WebSearch {
provider: http
max_results: 5
timeout: 10s
effects: <network, io>
}

tool CodeInterpreter {
provider: native
runtime: sandboxed
sandbox: true
timeout: 30s
effects: <io, network, epistemic:speculate>
}

Fields

provider: (optional — omit for an LLM-routed tool)

A single identifier from a closed catalog (axon-T948, v2.69.0):

native · stub · stub_stream · http · mcp · scrape_http · scrape_dom · scrape_crawl · scrape_enrich · bash.

http (REST dispatch) and mcp (ℰMCP JSON-RPC transducer) make a real call to the tool-server (see Calling a tool below). native runs an in-process built-in; scrape_* are the v2.52.0 web-acquisition engines; bash is the v2.39.0 technician execve path; stub / stub_stream return synthetic output for tests.

Omit provider: for an LLM-routed tool — a tool that is the model (a Summarize, a Classify). That is validated by the absence of a provider.

⚠️ v2.69.0 — this page used to say the opposite

It read: "compile time validates that the slug parses as an identifier but does NOT validate it against the catalog", and listed a dozen slugs — tavily, exa, serper, bing, google_cse, python_repl, sql, vector_search, wikidata, brave, code_interpreternone of which the runtime ever dispatched. provider: was a free string, so the docs accumulated an imaginary catalog, exactly as they did for resource.kind.

And the closing line — "other slugs that the runtime registry does not handle locally fall through to the model's tool-use surface" — described the fabrication path as a feature: a typo'd provider silently handed the call to the model, which invented the output. On the streaming path an empty provider did the same, returning a canned [stub]. On the one primitive built so an action's result is born with an honest epistemic status, an unrecognised provider produced an invented one. v2.69.0 closes it: a non-empty provider outside the catalog is refused at compile.

parameters: (optional, v2.8.0)

The tool's typed input schema — the call contract. A brace-delimited list of name: Type pairs:

parameters: { company: String, max_results: Int, active: Bool }

The schema reuses the full type-expression grammar (generics like List<T>, ?-optionals); a parameter whose type ends in ? is optional, every other parameter is required. The schema is the signature the type-checker validates a use <Tool>(k = v, …) call against (v2.8.0) — an unknown argument name, a duplicate, a missing required parameter, or a literal type mismatch is a compile-time CALLER error (CT-2 blame), surfaced before any dispatch. A tool with no parameters: is schema-less: it accepts the legacy single-argument use <Tool> on <arg> form and its calls are not arg-validated (v2.8.0 D5 back-compat).

output_type: (optional, v2.8.0)

The tool's declared output type. After a real dispatch, the result is bound for downstream reference (the tool-step's typed output), so the declared type participates in the semantic type system rather than being an opaque blob.

output_type: CrmReport

max_results: (optional)

A non-negative integer literal. Caps the number of results the tool may return. The runtime trims the provider's response to this length before passing it to the model; the audit row records both the requested and the served counts.

filter: (optional)

A filter expression — an identifier optionally followed by a parenthesised argument list:

filter: lang(en)
filter: domain(example.com)
filter: published_after(2024-01-01)

The expression is forwarded to the provider verbatim; semantics are provider-defined. Compile time validates only the shape.

timeout: (optional)

A duration literal (100ms, 5s, 2m, …). Bounds the tool's wall-clock budget. The runtime cancels the call on expiry; the step that invoked the tool sees a structured tool_timeout diagnostic and may compose a fallback via its reason sub-construct.

runtime: (optional)

A single identifier hinting the execution surface. Canonical values:

ValueMeaning
nativeRun inline in the host process (e.g. an in-proc HTTP client).
sandboxedRun inside a sandboxed worker / container.
remoteDispatch to a registered remote service.

sandbox: (optional)

A boolean literal. When true, the runtime is required to execute the tool inside a sandboxed worker (network egress restricted, filesystem mounted read-only by default, no parent process inheritance). Independent of runtime: — a native runtime can still be sandboxed by the supervisor (v1.11.0).

effects: (optional)

The declared effect row for this tool. Lists the effects the model may produce by invoking it. Effect names are drawn from the closed catalog (axon-frontend::type_checker::VALID_EFFECTS):

EffectMeaning
ioGeneric I/O (filesystem reads/writes).
networkNetwork egress (HTTP, DNS, …).
storageBacked by an axonstore / data plane.
pureStrictly deterministic, no observable side effects.
randomConsumes randomness (must be reproducible per-trace via the runtime seed).
streamEmits a stream (paired with Stream<T> outputs, v1.24.0).
trustCarries a v1.4.0 trust proof obligation.
sensitiveTouches a sensitive data category (PII / PHI / financial).
legalCarries a v2.0.0 legal-basis tag (mandatory qualifier from the closed legal-basis catalog).
otsOne-shot transform (mandatory transform:<from>:<to> or backend:<native|ffmpeg> qualifier).

Each effect may carry a qualifier after : (dotted-slug grammar, v1.4.0):

effects: <network, io>
effects: <io, sensitive, legal:HIPAA.164_502>
effects: <stream, network, epistemic:speculate>
effects: <ots:transform:mulaw8:pcm16, io>

The epistemic: qualifier is special: it occupies its own field on the effect row and accepts the closed level catalog (believe, doubt, know, speculate) — see axon://compliance/epistemic_levels.

The type checker propagates the row through the flow's algebraic-effect signature (v1.17.0). At runtime, every invocation lands in the audit hash-chain with the row attached.

target: / risk: / argv: (optional, v2.39.0 — Remote Hands)

These three fields turn a tool into a technician command: a typed, template-locked operation an agent can run on a real end-user machine (a "PC technician" agent), dispatched over a declared socket a local agent dials into.

session TechConfirm {
server: [ send Command,
select { approved: [receive CommandResult, end],
denied: [receive DenyReason, end] } ]
client: [ receive Command,
branch { approved: [send CommandResult, end],
denied: [send DenyReason, end] } ]
}
socket TechConfirmWS { protocol: TechConfirm }

tool DeleteFile {
provider: bash
target: TechConfirmWS
risk: destructive
parameters: { path: String }
argv: ["rm", "${path}"]
output_type: CommandResult
}
  • target: names the socket this call dispatches over. Omit it and the tool behaves exactly as before (v2.39.0 is inert). With it, the tool is duality-checked against that socket's session (axon-T861).
  • risk: is the closed catalog safe | destructive (axon-T862). A destructive tool's bound session MUST contain a reachable branch{ approved / denied } — a human confirm/deny exit visible in the protocol's own shape — or it is a compile error (axon-T860).
  • argv: is the argv template: a list where each element is a literal ("rm") or a whole-element ${param} placeholder ("${path}"). This is the injection-safety keystone: a ${param} is substituted as exactly one argument, opaquely, and is never re-parsed by a shell — the same discipline retrieve.where: uses for SQL parameters (v2.33.0). A placeholder fused with other text ("${path}.bak") or unbound to a parameters: entry is a compile error (axon-T859); a target:-bound provider: bash tool with no argv: is axon-T858. The market's free command STRING is deliberately not offered — a string would let an argument break out into new shell syntax; the argv model makes that structurally impossible.

Unknown fields inside a target:-bound tool are a hard error (v2.39.0 the design decision) — a typo'd safety field can never silently disable a guard. (A legacy schema-less tool keeps its lenient skip.)

The enterprise data plane adds separation of duties (proposing and approving a destructive command are distinct capabilities — tech:dispatch vs tech:approve), a confirmation bound to the exact rendered command's hash (a swapped command after approval is refused), and fail-closed audit of every action. The local agent runs only argv-templates whose hash it was enrolled with, without a shell, least-privilege.

Calling a tool

There are two ways a declared tool is invoked.

1. Explicit dispatch (v2.8.0) — the typed, real-dispatch path

A flow-level use <Tool>(…) statement dispatches a real call to the tool-server and binds the typed result. Two surface forms:

# Structured, multi-field — the canonical form for typed args.
# Each named arg is validated against the tool's `parameters:`
# schema at compile time; the runtime assembles a typed JSON body
# ({"company":"Acme","max_results":5,"active":true}) and POSTs it.
use CrmRadar(company = "Acme", max_results = 5, active = true)

# Legacy single-argument (v2.7.0, D5 back-compat). `on
# "${param}"` interpolates a bound request/flow parameter; the
# body is wrapped as {"input": <arg>}.
use WebSearch on "${query}"

Both are flow-level: a use written inside a step { } body is a parse error (v2.7.0) — it would silently degrade to an unconstrained LLM step. The in-step equivalent is apply: <Tool> on a step (run the tool as that step's backend).

The dispatch is real on both transports: the synchronous endpoint path (execute_server_flow) and the SSE / streaming path (server_execute_streaming). For the http / mcp providers the runtime POSTs to the tool's resolved endpoint and binds the response under <ToolName>_result; a provider the registry does not handle locally falls through to the model (form 2).

2. Implicit tool-use surface

The backend's native tool-use surface (OpenAI tools, Anthropic tools, JSON-RPC tool calls) also makes declared tools available to the model while the surrounding step runs. The step author biases towards a tool via the prompt (e.g. "Search the web for recent rulings on …"), and the runtime exposes the declared tools as candidates. In strict-tool mode (run … effort: strict), the runtime restricts the model to ONLY the tools declared at module level; non-declared tool calls are rejected as protocol violations.

Wiring the endpoint (v2.8.0)

For the URL-dispatched providers (http / mcp), the call endpoint is config-driven, so the same source runs against any tool-server without edits:

  • A tool whose runtime: is an absolute http(s)://… URL is used verbatim (the program pinned it).
  • Otherwise the runtime: slug (or, when omitted, the tool name) is resolved against a base URL: {base}/{slug}. The base is the AXON_TOOL_BASE_URL env on the OSS server, or — on the enterprise multi-tenant server — the per-tenant tool.base_url config key (which overrides the env). Resolution is per-request, so concurrent tenants never share endpoints.

Regulated parameters

A tool call leaves this program, which makes it the widest exit AXON has. If any parameters: type — or any type nested inside one — carries a compliance: class, the tool must name a shield: whose own compliance: covers every one of those classes. axon-T1221 refuses the call otherwise, the same coverage rule an axonendpoint lives under (axon-T957).

axon-T1221 tool 'SendOut' carries regulated data (kappa = {HIPAA}) across the
process boundary but declares no `shield:`. …

Two things that look like coverage and are not:

  • requires: governs WHO may call the tool.
  • secret: governs WITH WHAT CREDENTIAL it calls.

Neither can act on a breach of a regulatory class, and coverage is defined as something can act on a breach. The shield is that something.

The rule does not depend on effects:. That row is optional, and a guarantee that switches off when a field is omitted is not a guarantee — a tool has a provider:, so it is an external call by construction.

What this primitive is NOT

  • Not the implementation of the capability. A tool is the declaration; the implementation lives in the host runtime's tool registry (Rust trait impl, Python plugin). The compiler never executes a tool.
  • Not a shell command. A tool with provider: bash is still subject to sandbox: and effects: controls; the compiler will reject a bash binding without an explicit effects: row in strict-policy modules.
  • Not a function in the host language. Calling a tool produces an audited side effect. As of v2.8.0, an explicitly-dispatched tool (use <Tool>(…)) DOES bind a typed result — declare output_type: to give it a type, and the runtime binds the response under <ToolName>_result for a subsequent step to consume. (The implicit model-tool-use surface, by contrast, still folds tool calls into the surrounding step's generation rather than producing a standalone value.)
  • Not nested inside a flow. Tools are declared at module scope and referenced implicitly by the runtime; there is no inline tool grammar.

See also

  • axon://primitives/flow — the orchestration primitive that exposes the declared tool set to its steps.
  • axon://primitives/shield — composes with tools to gate effects (e.g. PHI scrubbing on io:read).
  • axon://compliance/effect_catalog — the closed effect row vocabulary effects: draws from.
  • axon://logic/strict_tool_mode — when to use effort: strict to lock the tool surface.