Skip to main content

Module hook

Module hook 

Source
Expand description

Hooks for observing and steering an agent run.

A hook is a single AgentHook::on_event method that the agent loop calls at every observable point of a run — before each model call, on each model response, around every tool call, on streamed deltas, and when the model emits an invalid tool call. Each call receives a HookContext (run-scoped identity + a shared scratchpad) and a StepEvent describing what is happening, and returns a Flow that lets the hook observe, patch the request, skip a tool, terminate the run early, or (for invalid tool calls) retry/repair/skip recovery.

Unlike the old multi-method hook trait, a hook implements one method and matches on the event it cares about — every other event falls through to the default Flow::Continue. Hooks compose in a HookStack that runs several hooks in registration order.

§Composition: mergeable patches vs. terminal control actions

How a HookStack combines several hooks’ Flow results depends on the event, and this is the central behavior to understand:

Blind merge. During accumulation/chaining a hook does not see earlier hooks’ contributions in its event payload for CompletionCall (it sees the agent baseline); for ToolCall/ToolResult it does see the running rewritten value. CompletionCall patches are declarative with documented conflict rules, so blind merge is sufficient and keeps StepEvent Copy.

Ordering guidance. Because Flow::Terminate short-circuits the stack, register observe-only hooks (telemetry) before steering hooks so a later terminate cannot hide the run from them. A HookStack pushed as a hook into another stack composes correctly: it returns its own net flow (a merged patch, a threaded rewrite, or a terminal action) which the outer stack folds in again — nesting never reintroduces short-circuiting on mergeable results.

§Why a returned Flow, not a next()-style middleware

A hook returns a typed Flow rather than receiving a next continuation it must invoke. A next()/middleware model — where each layer has to call next(ctx) to let the rest of the chain and the wrapped action run — carries a well-known footgun: forgetting the call silently disables every downstream hook and the action itself, with no error. The declarative returned-Flow model makes that impossible: proceeding is the explicit Flow::Continue, and any action an event cannot honor is fail-closed (it terminates the run) rather than silently skipped.

Hooks are a driver concern: they are async, side-effecting and generic over the model, so they live in the AgentRunner layer rather than inside the sans-IO, serializable AgentRun state machine.

§Migrating from PromptHook

The previous eight-method PromptHook<M> trait is replaced by the single AgentHook::on_event method. Each old method becomes one match arm on a StepEvent variant, and the value it used to return becomes the Flow you return from that arm (every event you don’t care about falls through to Flow::Continue). Every on_event now also receives a HookContext first argument. Attach one or more hooks with add_hook.

Old PromptHook methodStepEvent variantFlow to return
on_completion_callCompletionCall { prompt, history, turn }cont / patch_request / terminate
on_completion_responseCompletionResponse { prompt, response }cont / terminate
on_invalid_tool_callInvalidToolCall(ctx)fail (default) / retry / repair / skip / terminate
on_tool_callToolCall { tool_name, tool_call_id, internal_call_id, args }cont / rewrite_args / skip / terminate
on_tool_resultToolResult { tool_name, .., result, outcome, extensions }cont / rewrite_result / terminate
on_text_deltaTextDelta { delta, aggregated }cont / terminate
on_tool_call_deltaToolCallDelta { tool_call_id, internal_call_id, tool_name, delta }cont / terminate
on_stream_completion_response_finishStreamResponseFinish { prompt, response }cont / terminate
(new, both surfaces)ModelTurnFinished { turn, content, usage }cont / terminate

Behavioral notes:

§Steering on structured tool outcomes

StepEvent::ToolResult carries a structured ToolOutcome alongside the model-visible result, so a hook can branch on why a tool failed — a timeout vs. a 404 — without parsing strings. The motivating case: abort after repeated timeouts, but let a not-found flow back to the model as recoverable feedback.

use rig_core::agent::{AgentHook, Flow, HookContext, StepEvent};
use rig_core::completion::CompletionModel;
use rig_core::tool::ToolFailureKind;

#[derive(Clone, Default)]
struct TimeoutCount(usize);

struct OutcomePolicy;

impl<M: CompletionModel> AgentHook<M> for OutcomePolicy {
    async fn on_event(&self, ctx: &HookContext, event: StepEvent<'_, M>) -> Flow {
        if let StepEvent::ToolResult { outcome, .. } = event {
            // Repeated timeouts abort the run; a 404 does not.
            if outcome.is_error_kind(ToolFailureKind::Timeout) {
                let count = ctx.scratchpad().update(|c: &mut TimeoutCount| {
                    c.0 += 1;
                    c.0
                });
                if count >= 10 {
                    return Flow::terminate("aborting after repeated tool timeouts");
                }
            }
            // `NotFound` falls through to `Flow::cont`: the model sees the
            // error text and may try another path.
        }
        Flow::cont()
    }
}

Structs§

HookContext
Run-scoped context passed by shared reference to every AgentHook::on_event call.
HookStack
An ordered list of hooks run as one hook.
InvalidToolCallContext
Context passed to a hook on a StepEvent::InvalidToolCall event when the model emits a tool call that Rig would reject before normal tool-call handling or execution.
RequestPatch
A partial patch over the model request for a single turn, returned by a hook via Flow::PatchRequest on a StepEvent::CompletionCall event.
RunId
Opaque, process-scoped identifier for a single agent run.
Scratchpad
A run-scoped, shared scratchpad passed to every hook via HookContext.

Enums§

Flow
Control-flow result returned by AgentHook::on_event.
InvalidToolCallHookAction
Recovery action for an invalid tool call, used internally by AgentRun. Hooks express recovery via Flow; the AgentRunner translates a Flow returned for a StepEvent::InvalidToolCall into this type.
StepEvent
An observable point in an agent run, passed to AgentHook::on_event.
StepEventKind
The discriminant of a StepEvent.

Traits§

AgentHook
A per-run hook that observes and steers an agent run.