# `Nous.Hook`
[🔗](https://github.com/nyo16/nous/blob/v0.17.1/lib/nous/hook.ex#L1)

Lifecycle interceptors for agent tool execution and request/response flow.

Hooks provide granular control over agent behavior at specific lifecycle
events. They can block actions, modify inputs/outputs, and execute external
commands for policy enforcement.

## Hook Events

| Event | When Fired | Can Block? |
|-------|-----------|-----------|
| `:session_start` | Agent run begins | No |
| `:pre_request` | Before LLM API call | Yes |
| `:post_response` | After LLM response received | No |
| `:pre_tool_use` | Before each tool execution | Yes |
| `:post_tool_use` | After each tool execution | No (can modify result) |
| `:pre_node` | Before each workflow node (`Nous.Workflow.Engine`) | Yes (`:deny` / `{:pause, reason}`) |
| `:post_node` | After each workflow node | No (can modify workflow state) |
| `:session_end` | After run completes | No |

## Hook Types

- `:function` — Inline function `fn event, payload -> result end`
- `:module` — Module implementing `Nous.Hook` behaviour
- `:command` — Shell command executed via `NetRunner.run/2`

## Matchers

Matchers filter hooks to specific tools (for `:pre_tool_use` / `:post_tool_use`):

- `nil` — matches all tool calls
- `"tool_name"` — exact name match
- `~r/pattern/` — regex match on tool name
- `fn payload -> boolean end` — arbitrary predicate

## Examples

    # Block dangerous tool calls
    %Nous.Hook{
      event: :pre_tool_use,
      matcher: "delete_file",
      type: :function,
      handler: fn _event, %{arguments: %{"path" => path}} ->
        if String.starts_with?(path, "/etc"), do: :deny, else: :allow
      end
    }

    # External policy check via an external command. `:command` handlers are an
    # argv list, never a shell string — there is no shell, so no word splitting
    # and no expansion. `Nous.Hook.Runner` rejects a raw string with
    # `{:error, :invalid_command_handler}`.
    %Nous.Hook{
      event: :pre_tool_use,
      matcher: ~r/^(write|delete)/,
      type: :command,
      handler: ["python3", "scripts/policy_check.py"],
      timeout: 5_000
    }

# `event`

```elixir
@type event() ::
  :pre_tool_use
  | :post_tool_use
  | :pre_request
  | :post_response
  | :workflow_start
  | :workflow_end
  | :pre_node
  | :post_node
  | :session_start
  | :session_end
```

# `hook_type`

```elixir
@type hook_type() :: :function | :module | :command
```

# `matcher`

```elixir
@type matcher() :: String.t() | Regex.t() | (map() -&gt; boolean()) | nil
```

# `result`

```elixir
@type result() ::
  :allow | :deny | {:deny, String.t()} | {:modify, map()} | {:error, term()}
```

# `t`

```elixir
@type t() :: %Nous.Hook{
  event: event(),
  fail_closed: boolean(),
  handler: (event(), map() -&gt; result()) | module() | String.t(),
  matcher: matcher(),
  name: String.t() | nil,
  priority: integer(),
  timeout: non_neg_integer(),
  type: hook_type()
}
```

# `handle`

```elixir
@callback handle(event(), payload :: map()) :: result()
```

Handle a hook event with the given payload.

Return `:allow` to proceed, `:deny` or `{:deny, reason}` to block,
or `{:modify, changes}` to modify the payload.

# `blocking_event?`

```elixir
@spec blocking_event?(event()) :: boolean()
```

Returns whether an event type supports blocking (returning `:deny`).

# `matches?`

```elixir
@spec matches?(t(), map()) :: boolean()
```

Check if a hook's matcher matches the given payload.

For `:pre_tool_use` and `:post_tool_use` events, matches against the tool name.
For other events, `nil` matchers always match.

# `new`

```elixir
@spec new(
  event(),
  keyword()
) :: t()
```

Create a new function hook.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
