Hooks
Intercept and customize tool execution, session lifecycle, permissions, file writes, compaction, and more with 18 hook events.
Hooks let you intercept key moments in the agent lifecycle — before and after tool calls, at session and turn boundaries, when permissions are requested or denied, when files are written, when models switch, around compaction, retry, memory extraction, and on errors. They can modify tool inputs/outputs, deny tool calls, or run side effects like logging and analytics.
Defining hooks
Pass hooks through Agent options:
import { Agent, type HookDefinition } from "noumen";
const hooks: HookDefinition[] = [
{
event: "SessionStart",
handler: async (input) => {
console.log(`Session ${input.sessionId} started (resume: ${input.isResume})`);
},
},
{
event: "PreToolUse",
matcher: "Bash",
handler: async (input) => {
console.log(`Running bash: ${input.toolInput.command}`);
return { decision: "allow" };
},
},
{
event: "FileWrite",
handler: async (input) => {
console.log(`${input.toolName} wrote ${input.filePath} (new: ${input.isNew})`);
},
},
];
const code = new Agent({
provider,
sandbox,
options: { hooks },
});Hook events
noumen provides 18 hook events across six categories.
Session lifecycle
| Event | Type | Description |
|---|---|---|
SessionStart | Notification | Fires at the start of thread.run(), after session restore. Includes prompt and isResume. |
SessionEnd | Notification | Fires when thread.run() completes. Includes reason: "complete", "abort", "maxTurns", or "error". |
TurnStart | Notification | Fires at the beginning of each provider call (before the LLM request). |
TurnEnd | Notification | Fires after the agent turn completes with a final text response. |
Error | Notification | Fires when an error occurs during the agent loop. |
Tool execution
| Event | Type | Description |
|---|---|---|
PreToolUse | Interceptor | Fires before a tool executes. Can allow, deny, or modify input. |
PostToolUse | Interceptor | Fires after a tool executes. Can modify output or stop the loop. |
PostToolUseFailure | Interceptor | Fires after a tool execution that returned an error. Can modify the error output. |
FileWrite | Notification | Fires after WriteFile or EditFile successfully writes to disk. Includes filePath and isNew. |
Permissions
| Event | Type | Description |
|---|---|---|
PermissionRequest | Notification | Fires before the user/handler is asked for permission. Includes toolName, input, and mode. |
PermissionDenied | Notification | Fires when a tool call is denied by rules, the user, or missing handler. Includes reason. |
Subagents
| Event | Type | Description |
|---|---|---|
SubagentStart | Notification | Fires when a subagent is spawned. |
SubagentStop | Notification | Fires when a subagent finishes. |
Compaction
| Event | Type | Description |
|---|---|---|
PreCompact | Notification | Fires before conversation compaction (proactive or reactive). |
PostCompact | Notification | Fires after compaction completes. |
System
| Event | Type | Description |
|---|---|---|
ModelSwitch | Notification | Fires when setModel() or setProvider() changes the active model. Includes previousModel and newModel. |
RetryAttempt | Notification | Fires before each retry attempt. Includes attempt, maxAttempts, error, and delay. |
MemoryUpdate | Notification | Fires after memory extraction creates, updates, or deletes entries. |
PreToolUse hooks
Pre-tool hooks run before tool execution. They receive the tool name, input, and session ID. Return a decision to control whether the tool runs:
{
event: "PreToolUse",
matcher: "WriteFile",
handler: async (input) => {
const path = input.toolInput.file_path as string;
if (path.includes("node_modules")) {
return { decision: "deny", message: "Cannot write to node_modules" };
}
return { decision: "allow" };
},
}PreToolUseHookOutput
| Field | Type | Description |
|---|---|---|
decision | "allow" | "deny" | "passthrough" | Whether to allow, deny, or defer to the next hook. |
updatedInput | Record<string, unknown> | Replace the tool input before execution. |
message | string | Message to include in the deny response. |
preventContinuation | boolean | Stop the agent loop after this tool call. |
PostToolUse hooks
Post-tool hooks run after execution. They can modify the output returned to the model:
{
event: "PostToolUse",
matcher: "ReadFile",
handler: async (input) => {
if (input.toolOutput.length > 50000) {
return { updatedOutput: input.toolOutput.slice(0, 50000) + "\n[truncated]" };
}
},
}PostToolUseFailure hooks
Failure hooks fire only when a tool returns an error (isError: true). Use them for error logging, alerting, or modifying the error message:
{
event: "PostToolUseFailure",
handler: async (input) => {
console.error(`Tool ${input.toolName} failed: ${input.errorMessage}`);
},
}Session lifecycle hooks
Track when sessions start and end:
{
event: "SessionStart",
handler: async (input) => {
await analytics.track("session_started", {
sessionId: input.sessionId,
isResume: input.isResume,
});
},
}{
event: "SessionEnd",
handler: async (input) => {
await analytics.track("session_ended", {
sessionId: input.sessionId,
reason: input.reason, // "complete" | "abort" | "maxTurns" | "error"
});
},
}Permission hooks
Observe permission decisions for audit trails and alerting:
{
event: "PermissionDenied",
handler: async (input) => {
await audit.log(`Permission denied for ${input.toolName}: ${input.reason}`);
},
}File write hooks
Get notified whenever the agent writes to the filesystem:
{
event: "FileWrite",
handler: async (input) => {
console.log(`[audit] ${input.toolName} wrote ${input.filePath} (new: ${input.isNew})`);
},
}Model switch hooks
Track model changes during a session:
{
event: "ModelSwitch",
handler: async (input) => {
console.log(`Model changed: ${input.previousModel} → ${input.newModel}`);
},
}Matcher patterns
The optional matcher field filters which tools a hook applies to. It supports simple glob patterns:
"Bash"— exact match"mcp__*"— prefix match (all MCP tools)"*File"— suffix match (ReadFile, WriteFile, EditFile)"*"— match all tools
Omitting matcher means the hook applies to all tools (for PreToolUse/PostToolUse/PostToolUseFailure) or all events of that type (for notifications).
Notification hooks
Notification hooks are fire-and-forget — they have no return value and errors are silently swallowed. Use them for logging, analytics, or side effects. All events except PreToolUse, PostToolUse, and PostToolUseFailure are notification hooks.
HookDefinition type
interface HookDefinition {
event: HookEvent;
matcher?: string;
handler: (input: HookInput) => Promise<HookOutput> | HookOutput;
}
type HookEvent =
| "PreToolUse" | "PostToolUse" | "PostToolUseFailure"
| "TurnStart" | "TurnEnd"
| "SessionStart" | "SessionEnd"
| "SubagentStart" | "SubagentStop"
| "PreCompact" | "PostCompact"
| "PermissionRequest" | "PermissionDenied"
| "FileWrite" | "ModelSwitch"
| "RetryAttempt" | "MemoryUpdate"
| "Error";