Middleware
Middleware wrap your agent's work. They can inspect a tool call before it runs,
change what the model sees after it returns, gate a call for approval, skip it
entirely, or answer a model call without asking the provider at all. Each one is a
small, independent piece of behaviour you turn on per agent, in agent.toml.
Every middleware on this page is off until you turn it on, and enabling one is a single line. A short always-on set rides underneath them; see what runs by default.
Choosing a middleware
| I want to... | Use |
|---|---|
| Stop paying for the same question twice | LLM response cache |
| Cut spend on short, easy requests | Model router |
| Try a new model on part of my traffic | Model router |
| Stop re-running the same lookup in one conversation | Tool result cache |
| Be asked about dangerous calls without being asked about every call | Risk-based approval |
| Stop a confused agent making sweeping changes | Blast radius limiter |
| Fit more of a long session in the model's context | Tool output summarizer |
| See what an agent would do before letting it | Dry run |
| Find which tool is slow or failing | Tool telemetry |
Every middleware
| Middleware | Slot | What it does |
|---|---|---|
| Tool telemetry | observe | Per-tool counts, errors, and latency |
| Tool output summarizer | context | Condenses big tool output for the model |
| Risk-based approval | policy | Approval only when arguments look dangerous |
| Blast radius limiter | policy | Caps writes per run |
| Dry run | policy | Suppresses writes so a run previews only |
| Model router | resilience | Cheaper model or less thinking for small requests; canary a new model |
| LLM response cache | resilience | Serves a repeated model call from a stored turn |
| Tool result cache | resilience | Reuses a repeated read's result |
A name AXL does not know, or settings that do not type-check, fails the boot
rather than being quietly ignored - including keys under a section you left
disabled, so a typo is caught immediately rather than waiting until you switch it
on. So does a section that sets keys without an enabled = true to switch on a
middleware that ships off, since none of them would apply. Past that, each
middleware rejects the settings that would leave it running on every call and
never acting: a cache bound of 0, a router with nothing to route to, an empty
dry-run notice. What no check can reach is a middleware you enabled and gave
nothing to do - risk-based approval with two empty lists boots and
gates nothing.
How the stack works
Every agent has a stack, ordered into bands called slots. Outermost first:
| Slot | What belongs there |
|---|---|
observe | Watches, never changes anything |
security | Scrubs or blocks content before anything inside sees it |
context | Shapes what the model sees |
policy | Decides whether a call may proceed |
resilience | Absorbs tool and provider failures, closest to the call |
A request travels outermost to innermost; a result travels back innermost to
outermost. The framework fixes the order for two cases: autonomy leads the
policy band, so a tool it forbids is refused before anything else can answer for
the call, and the model router runs before the LLM cache, so a cached answer is
keyed on the model that actually produced it.
What runs by default
tracing, loop_guard, and autonomy are on for every agent, plus audit when
you enable it. autonomy and audit cannot be turned off.
diagnostics follows the deployment rather than the agent: with
AXL_DIAGNOSTICS_ENABLED=true it joins every agent's stack. It is the only thing
that produces the runs, LLM calls, and tool calls the diagnostics boards read, so
under a per-agent opt-in, turning collection on would answer every board with an
empty list until each agent had been edited. An agent that should not be recorded
opts out with [middleware.diagnostics] enabled = false.
Everything in the table above ships off.
Seeing your stack
axl weave print --config axl-config/agents/<your-agent>/agent.tomlThis prints the resolved onion for that agent, outermost first, without starting
the server. It runs the checks the boot runs, so a name it rejects, a typo it
rejects, or a setting that would do nothing all fail here first. It reads
AXL_DIAGNOSTICS_ENABLED from your environment, so diagnostics shows up
exactly when a server started here would put it on the stack. Two checks need the
running platform and so happen only at startup: whether your provider actually
serves a model the model router routes to, and any middleware
your host registered in Rust - the CLI has no way to see one of those, so it
reports a config naming it as unknown.
Next
- Risk-based approval
- Blast radius limiter - a number you choose to cap how much damage one run can do.
- Create your first agent - where these blocks live in
agent.toml. - LLM failover keeps an agent answering through a
provider outage. It sits below the loop rather than in this stack, so it is
configured with an environment variable instead of
agent.toml. - Internals: Middleware covers the architecture, the hook set, and how to write your own.