Nous.Hook behaviour (nous v0.13.3)

View Source

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

EventWhen FiredCan Block?
:session_startAgent run beginsNo
:pre_requestBefore LLM API callYes
:post_responseAfter LLM response receivedNo
:pre_tool_useBefore each tool executionYes
:post_tool_useAfter each tool executionNo (can modify result)
:session_endAfter run completesNo

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 shell command
%Nous.Hook{
  event: :pre_tool_use,
  matcher: ~r/^(write|delete)/,
  type: :command,
  handler: "python3 scripts/policy_check.py",
  timeout: 5_000
}

Summary

Callbacks

Handle a hook event with the given payload.

Functions

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

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

Create a new function hook.

Types

event()

@type event() ::
  :pre_tool_use
  | :post_tool_use
  | :pre_request
  | :post_response
  | :session_start
  | :session_end

hook_type()

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

matcher()

@type matcher() :: String.t() | Regex.t() | (map() -> boolean()) | nil

result()

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

t()

@type t() :: %Nous.Hook{
  event: event(),
  handler: (event(), map() -> result()) | module() | String.t(),
  matcher: matcher(),
  name: String.t() | nil,
  priority: integer(),
  timeout: non_neg_integer(),
  type: hook_type()
}

Callbacks

handle(event, payload)

@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.

Functions

blocking_event?(event)

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

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

matches?(hook, payload)

@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(event, opts \\ [])

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

Create a new function hook.