Skip to main content
Hooks let you observe and steer the agent at fixed lifecycle junctures — inject context, rewrite a prompt, rewrite or short-circuit a model call, gate or redact a tool call, block a turn, redact a subagent’s result, or force the agent to keep working. One value-based contract works for both in-process and out-of-process hooks. Two kinds of hook share that contract:
  • Command hooks — shell commands, configured in agents.json. The daemon spawns the command, writes the event as JSON to stdin, and reads a JSON HookOutput from stdout (exit 2 = deny). Language-agnostic.
  • Atom-pack hooks — in-process typed TypeScript handlers registered by an atom pack via the SDK (hook({ event, matcher, handler })). The daemon itself registers a few built-in atom hooks (e.g. the memory subsystem injects recalled context on BeforeTurn).

Naming

Flat <Before|After><Subject> PascalCase with a self-evident subject (Turn, Model, Tool, Compact, Subagent), plus SessionStart/SessionEnd lifecycle facts and ApprovalRequest for the human approval gate. AfterTool, AfterSubagent, and AfterTurn fire on BOTH success and failure — there is no separate failure event; the failure is carried in the input (ok / error) and the handler decides what to do. AfterModel is the exception: it fires only after a successful response; a failed model call ends the turn through AfterTurn with reason: 'error'. Source of truth in code: packages/protocol/src/hooks.ts (contract), apps/monad/src/hooks/runner.ts (dispatch), packages/environment/src/config/index.ts (hooks + policyHooks), packages/sdk-atom/src/hook.ts (SDK).

Events

Thirteen events, in firing order. “Can deny / mutate” is what a hook’s HookOutput may do at that juncture; fields not relevant to an event are ignored. ¹ SessionStart context is stashed and injected into that session’s first BeforeTurn.

Model events — scoping

BeforeModel / AfterModel fire around the agent’s reasoning calls only — the main turn loop and forked subagent loops. They do not fire for infra/utility model calls (summarization → use Before/AfterCompact; embeddings, vision, tool-search, memory have no model hook). The input’s caller distinguishes them:
A hook subscribes to BeforeSubagent to bracket a whole fork, or filters BeforeModel by caller.kind to scope to main vs subagent reasoning. They fire per model step (every request in a turn — including the intermediate responses that carry tool calls), not once per turn. That is the distinction from AfterTurn: AfterModel fires after every reasoning response; AfterTurn fires once, when the turn ends.

The contract


Configuration

Command hooks — agents.json

event → matcher[] → hooks[]. The matcher is a regex on the tool name and applies only to the tool-scoped events (BeforeTool, AfterTool, ApprovalRequest); other events always match.
A command hook receives HookInput JSON on stdin, runs with cwd = the sandbox root:
  • exit 2 → deny (stderr is the reason);
  • exit 0 + JSON stdout → parsed (schema-validated) as HookOutput; empty stdout → allow;
  • any other failure (non-zero exit, non-JSON, spawn error, timeout) → skipped, unless onError: "deny" (fails closed);
  • env is sanitized (MONAD_* and KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL stripped); default timeout 60 s.

Policy hooks — policyHooks

Same shape, but operator-managed and non-overridable: they run before user hooks and the settings API never writes them. Shell command hooks only.

Atom-pack hooks — SDK


Dispatch semantics

  1. Fast path — no matching hooks → returns immediately, spawns nothing.
  2. Order & dedup — atom hooks, then policy command hooks, then user command hooks; identical command specs run once per event.
  3. Serial (mutating) events chain — each hook sees the previous one’s rewrite, and the first deny short-circuits. Parallel events (PARALLEL_HOOK_EVENTS) fan out.
  4. Fail-closed — a hook’s own failure is skipped by default; onError: 'deny' turns it into a block.
  5. Audit seam — every executed hook is reported to deps.record (outcome + latency).
  6. Hot-reload — config/policy resolved per call.

Sequence: one turn


Examples

Deny a tool via the gate (auto-deny):
Redact every model response (atom pack):
Scope a model hook to the main turn only:

Notes & limits