Omni

Advanced

Hook System

Intercept and modify data at 7 points in the agent loop. Block tool calls, transform messages, and react to session events.

Overview

The hook system lets you intercept data at key points in the agent loop. Hooks can inspect, modify, or block data as it flows through the system. This enables use cases like content filtering, logging, rate limiting, and custom security policies.

There are two types of hooks:

Modifying Hooks

Run sequentially in priority order. Can transform data or block it entirely. If one hook blocks, the pipeline stops and downstream hooks don't run.

Notification Hooks

Run in parallel. Fire-and-forget for observability. Cannot modify data or block the pipeline. Used for logging, analytics, and external integrations.

Hook Points

Omni provides 7 hook points — 5 modifying and 2 notification.

Hook Point
Type
Description
MessageReceived
Modifying
Fired when a user message arrives. Can modify the message text or block it before it enters the conversation.
LlmInput
Modifying
Fired before the assembled prompt is sent to the LLM. Can modify messages, add context, or block the request.
LlmOutput
Modifying
Fired after the LLM responds. Can transform the response text or block it before tool processing begins.
BeforeToolCall
Modifying
Fired before a tool is executed. Can modify parameters or block specific tool calls.
AfterToolCall
Modifying
Fired after a tool returns. Can modify the tool's result before it's fed back to the LLM.
SessionStart
Notification
Fired when a new session begins. Use for initialization, logging, or external notifications.
SessionEnd
Notification
Fired when a session ends. Use for cleanup, analytics, or summary generation.

Modifying Hooks

Modifying hooks run sequentially in priority order (lowest number first). Each hook receives the output of the previous hook. A hook can return Continue (optionally with modified data) or Block to stop the pipeline.

execution order

Hook A (priority 10) → Continue(modified data)

Hook B (priority 20) → Continue(data)

Hook C (priority 30) → Block("reason")

Pipeline stops. Hook D (priority 40) never runs.

When a BeforeToolCall hook blocks, the agent loop receives a HookBlocked error and skips the tool execution. The LLM is informed that the tool call was blocked.

Notification Hooks

Notification hooks run in parallel using tokio::join!. They receive a read-only copy of the hook context. Errors in notification hooks are logged but don't affect the pipeline.

Common use cases: sending analytics events, writing to external log services, triggering webhooks, or updating a dashboard when sessions start or end.

Hook Context

Every hook receives a context object containing relevant data for the hook point.

Field
Type
Description
hook_point
HookPoint
Which hook point fired this context.
session_id
Option<String>
Current session ID, if available.
text
Option<String>
Message text (for MessageReceived, LlmInput, LlmOutput).
tool_call
Option<ToolCallInfo>
Tool name and arguments (for BeforeToolCall, AfterToolCall).
messages
Option<Vec<ChatMessage>>
Full conversation history (for LlmInput).
metadata
serde_json::Value
Arbitrary JSON metadata for custom data passing between hooks.

Hook Results

Modifying hooks return one of two results:

Continue(HookContext)

Let the pipeline proceed. Pass the context unchanged or with modifications. The next hook in the chain receives the returned context.

Block { reason: String }

Stop the pipeline. The reason string is logged and, for BeforeToolCall hooks, returned to the LLM as a HookBlocked error so it can adapt.

Registration

Hooks are registered with the HookRegistry which is shared across the agent loop via Arc<HookRegistry>.

Rust

let registry = HookRegistry::new();

 

// Register a modifying hook with priority 10

registry.register_modifying(

HookPoint::BeforeToolCall,

10, // priority

my_hook_handler,

);

 

// Register a notification hook

registry.register_notification(

HookPoint::SessionStart,

my_notification_handler,

);

Hook handlers implement the HookHandler trait, which has a single async method that receives a HookContext and returns a HookResult.

Examples

Block dangerous tool calls

A BeforeToolCall hook that prevents the agent from executing exec with destructive commands.

Rust

async fn handle(&self, ctx: HookContext) -> HookResult {

if let Some(tool_call) = &ctx.tool_call {

if tool_call.name == "exec" {

let cmd = tool_call.arguments

.get("command")

.and_then(|v| v.as_str())

.unwrap_or_default();

 

let blocked = ["rm -rf", "format", "del /f"];

if blocked.iter().any(|b| cmd.contains(b)) {

return HookResult::Block {

reason: "Destructive command blocked".into(),

};

}

}

}

HookResult::Continue(ctx)

}

Log all LLM requests

An LlmInput hook that logs the prompt being sent to the LLM for debugging.

Rust

async fn handle(&self, ctx: HookContext) -> HookResult {

if let Some(messages) = &ctx.messages {

tracing::debug!(

"LLM request: {} messages, session {{:?}}",

messages.len(),

ctx.session_id,

);

}

HookResult::Continue(ctx)

}

Filter profanity from messages

A MessageReceived hook that redacts profanity from user messages.

Rust

async fn handle(&self, mut ctx: HookContext) -> HookResult {

if let Some(ref mut text) = ctx.text {

*text = self.profanity_filter.censor(text);

}

HookResult::Continue(ctx)

}

Next Steps

Hook System — Agent Loop Interception | Omni AI Agent Builder