ExAgent. Capability behaviour
(ExAgent v2.2.2)
View Source
Composable middleware that observes/transforms an agent run at well-defined
points. This is ExAgent's "capabilities" spine, modelled the Elixir way: a
behaviour whose callbacks default to no-ops via use ExAgent.Capability, so
a capability overrides only the hooks it cares about.
A capability is a module or a configured struct. That value is passed as the first argument to its callbacks, keeping configuration explicit without global state. Hooks compose in list order.
Hooks (all optional)
before_model_request(cap, state)— return (possibly modified)state. Setstate.request_messagesto alter what's sent to the model without touching the canonical history (e.g. keep a sliding window, redact PII). Selectstate.model,state.settingsandstate.params.function_toolshere. Selected tools are prepared and become this request's executable set; the wider agent inventory does not authorize omitted tools.after_model_request(cap, state)— observe/modify state after the model responded (state already includes the new response + merged usage).before_tool_execute(cap, ctx, tool_call)— observe or transform arguments, revalidated before authority. ID, name, kind and metadata are immutable for every Model; context includes call identity/retry fields.after_tool_execute(cap, ctx, tool_call, result)— observe a tool result.
Model after-hooks transform the effective response before tool admission;
their output still passes validation, limits and ancestor authority.
Before-tool exceptions report :not_executed; after-tool failures retain
confirmed effects. Hooks are trusted host code, not a sandbox. Run identity,
scope handles, prepared validators and accounting fields are internal and must
not be replaced.
In Model hooks, tool_retries, output_retries_used, run_step, tool_calls,
max_steps and agent.output_retries are observable, runtime-owned values.
Writes to these six fields are ignored: each callback's result restores the
confirmed values before the next capability and before admission/persistence/IO.
Configure limits before starting the run; callbacks cannot replenish budgets.
Other valid agent/model/settings/tool selections and response/projection
transformations remain supported, including selected tools' max_retries.
This applies to actual runs, not generic maps passed to <code>ExAgent.Capabilities</code>.
It is a behavioral change for the pending major; invalid callback results are
not repaired into valid runs, and historical persisted counters are not migrated.
Example
defmodule MyApp.RedactPII do
use ExAgent.Capability
@impl true
def before_model_request(_cap, state) do
redacted = redact(state.messages)
%{state | request_messages: redacted}
end
end
ExAgent.new(model: "test", capabilities: [MyApp.RedactPII])
Summary
Types
@type state() :: map()