Hooks (cersei-hooks)
Middleware system for intercepting agent lifecycle events.
cersei-hooks
Hooks intercept agent lifecycle events — pre/post tool use, model turns, and custom events.
Hook Trait
#[async_trait]
pub trait Hook: Send + Sync {
fn name(&self) -> &str;
fn events(&self) -> &[HookEvent];
async fn on_event(&self, ctx: &HookContext) -> HookAction;
}HookEvent
pub enum HookEvent {
PreToolUse,
PostToolUse,
PreModelTurn,
PostModelTurn,
}HookAction
pub enum HookAction {
Continue,
Block(String),
ModifyInput(Value),
}Examples
Cost Guard
pub struct CostGuard { pub max_usd: f64 }
#[async_trait]
impl Hook for CostGuard {
fn name(&self) -> &str { "cost-guard" }
fn events(&self) -> &[HookEvent] { &[HookEvent::PostToolUse] }
async fn on_event(&self, ctx: &HookContext) -> HookAction {
if ctx.cumulative_cost_usd() > self.max_usd {
HookAction::Block(format!("Cost limit ${:.2} exceeded", self.max_usd))
} else {
HookAction::Continue
}
}
}Audit Logger
pub struct AuditLogger;
#[async_trait]
impl Hook for AuditLogger {
fn name(&self) -> &str { "audit" }
fn events(&self) -> &[HookEvent] { &[HookEvent::PreToolUse, HookEvent::PostToolUse] }
async fn on_event(&self, ctx: &HookContext) -> HookAction {
eprintln!("[audit] {} tool={}", ctx.event, ctx.tool_name.as_deref().unwrap_or("?"));
HookAction::Continue
}
}Tool Blocker
pub struct BlockDangerous;
#[async_trait]
impl Hook for BlockDangerous {
fn name(&self) -> &str { "block-dangerous" }
fn events(&self) -> &[HookEvent] { &[HookEvent::PreToolUse] }
async fn on_event(&self, ctx: &HookContext) -> HookAction {
if ctx.tool_name.as_deref() == Some("Bash") {
if let Some(cmd) = ctx.tool_input.get("command").and_then(|v| v.as_str()) {
if cmd.contains("rm -rf") {
return HookAction::Block("Destructive command blocked".into());
}
}
}
HookAction::Continue
}
}Shell Hooks
Execute shell commands at hook points:
use cersei_hooks::ShellHook;
let hook = ShellHook::new("notify", HookEvent::PostToolUse, "echo 'Tool used'");Registration
Agent::builder()
.hook(CostGuard { max_usd: 5.0 })
.hook(AuditLogger)
.hook(BlockDangerous)
.build()?;Hooks fire in registration order. A Block action stops the pipeline.