> ## Documentation Index
> Fetch the complete documentation index at: https://docs.matrices.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

> Points, processors, decisions, and the default approval set.

Hooks live on a [profile](/model/profiles) in `configuration.hooks`, at most
16 of them, and run in list order.

## Points

| Point | Intercepts | `match.tools` | `where` paths |
| - | - | - | - |
| `before_turn_start` | The admitted user message, before the first sample. | — | `message` |
| `before_tool_call` | A requested tool name and input, before execution. | yes | `input.*` |
| `after_tool_result` | A tool result, before the model sees it. | yes | `input.*`, `output.*` |
| `after_model_response` | A sampled assistant response, before it is appended. | — | `text` |

`match.tools` is only valid on the two tool points. Patterns are exact names
(`payments.transfer`) or one trailing wildcard (`billing.*`). Omit `match`
entirely to run on every event at that point. `match.where` takes at most eight
conditions using the same operators as
[permissions](/reference/permissions#conditional-allows).

After-output hooks cannot undo external effects. Once a tool has run, the hook
only controls what gets persisted and returned.

## Processors

| Type | Behavior | `timeoutMs` | `asyncTimeoutMs` |
| - | - | - | - |
| `client` | Parks the turn until your client resolves the wait. | — | 24 h default |
| `url` | POSTs a `HookInput` to an HTTPS endpoint and reads a `HookResult`. | 30 s | 1 h default |
| `agent` | Asks another workspace agent for one `HookResult`. Requires `agentId`. | 5 min | 1 h default |

`asyncTimeoutMs` is capped at 24 hours. A `url` processor must be HTTPS (except
`http://localhost` for development), and an `agent` processor cannot itself
trigger another agent hook.

`onFailure` is `fail_closed` (the default: no answer means the work does not
happen) or `fail_open`.

## Decisions

A `HookResult` carries a `decision`, an optional `reason`, and an optional
rewrite (`updatedInput`, `updatedOutput`, or `updatedText`).

| Decision | Meaning |
| - | - |
| `allow` | Continue, and skip every later hook. |
| `deny` | Block the work and surface `reason`. |
| `ask` | Park on a wait for an external decision. |
| `defer` | Continue to the next matching hook. |

So hooks evaluate in profile order, and the first non-`defer` decision wins. A
`defer` may carry a rewrite; rewrites compose in order, and if every matching
hook defers, the turn continues with the composed value.

## HookInput

Delivered to `url` and `agent` processors:

| Field | Always | Notes |
| - | - | - |
| `hookId`, `deliveryId`, `point` | yes | `deliveryId` is stable across retries. |
| `workspaceId`, `agentId`, `threadId`, `turnId` | yes | Trusted context, never model output. |
| `identity` | no | The agent's identity. |
| `toolName`, `toolInput`, `toolCallId`, `toolsetId` | no | Tool points. |
| `toolOutput` | no | `after_tool_result` only. |
| `userMessage` | no | `before_turn_start` only. |
| `assistantMessage` | no | `after_model_response` only. |
| `callbackUrl`, `callbackToken`, `deadlineMs` | no | For answering asynchronously. |

## Default hooks

Create a profile without a `hooks` field and Matrices installs six removable
client-approval hooks. All six run at `before_tool_call` with a `client`
processor, wait up to 24 hours, and are `fail_closed`.

| Hook ID | Intercepts |
| - | - |
| `approve-agent-delete` | `agents.delete` |
| `approve-role-create` | `roles.create` |
| `approve-gmail-send` | `gmail.send_email` |
| `approve-calendar-write` | `calendar.create_event`, `calendar.update_event`, `calendar.delete_event`, `calendar.respond_to_event` |
| `approve-toolset-url` | `toolsets.create` and `toolsets.update`, when `input.url` exists |
| `approve-drive-write` | `drive.create_file`, `drive.copy_file` |

Three rules for the default set:

* Omitting `hooks` installs the defaults.
* `hooks: []` installs none. That is the explicit opt-out.
* Any non-empty list you send is authoritative. The defaults are not merged
  back in.

Their IDs are reserved, which is how a newly added default reaches existing
profiles without overwriting a hook you authored under the same ID.

## The audit trail

Every evaluated hook writes a [`hook_decision`](/reference/thread-events) event
carrying `hookId`, `index`, `point`, `processor`, `decision`, `latencyMs`, and,
when they apply, `reason`, `rewrote`, and `deliveryId`.

Parked work shows up as `pendingWaits` on the thread, and
`GET /v1/threads?state=waiting` lists every thread holding one. Resolve with
`POST /v1/threads/{threadId}/waits/{waitId}/resolve`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.