ExAthena.Hooks (ExAthena v0.4.8)

Copy Markdown View Source

Lifecycle hooks the loop fires at key transitions.

Shape mirrors Claude Code's SDK so existing hook code ports cleanly:

hooks = %{
  PreToolUse: [%{matcher: "Write|Edit", hooks: [&deny_protected_paths/2]}],
  PostToolUse: [%{matcher: "Bash", hooks: [&capture_test_output/2]}],
  Stop: [&log_stop/2]
}

Each hook callback receives (input_map, tool_use_id) and returns one of:

  • :ok — continue with no side effects.
  • {:deny, reason} — only meaningful from PreToolUse / PermissionRequest; deny the tool call.
  • {:halt, reason} — stop the loop immediately.
  • {:inject, message_or_messages} — append the given message (or list of messages) to the conversation. Useful for experimental.chat.system.transform-style context injection.
  • {:transform, new_prompt} — only valid from UserPromptSubmit; rewrites the incoming user prompt before it enters the loop.

Hooks are matched by :matcher (regex run against tool_name); a nil matcher or a missing :matcher key fires for every tool. Lifecycle-only events are passed as plain function lists, not wrapped in matcher maps.

Catalog of supported events

  • Session: :SessionStart, :SessionEnd
  • Per-turn: :UserPromptSubmit, :ChatParams, :Stop, :StopFailure
  • Per-tool: :PreToolUse, :PostToolUse, :PostToolUseFailure, :PermissionRequest, :PermissionDenied
  • Subagent: :SubagentStart, :SubagentStop
  • Compaction: :PreCompact, :PreCompactStage, :PostCompact
  • Notification: :Notification

Summary

Functions

Catalog of every hook event ex_athena fires today. Useful for hosts that want to enumerate or validate user-supplied hook tables.

Fire lifecycle hooks that aren't scoped to a tool (Stop, SessionStart, etc.). Backward-compatible: returns :ok | {:halt, reason} like before. Use run_lifecycle_with_outputs/3 for events that may inject messages or transform prompts.

Like run_lifecycle/3 but returns a structured outputs map so callers can act on {:inject, msg} and {:transform, prompt} returns. Used by the kernel to thread UserPromptSubmit transforms and :inject-driven message injection across hook events.

Fire PostToolUse hooks matching tool_name.

Fire PreToolUse hooks matching tool_name.

Types

hook_fun()

@type hook_fun() :: (map(), String.t() -> term())

lifecycle_outputs()

@type lifecycle_outputs() :: %{
  halt: nil | {:halt, term()},
  injects: [ExAthena.Messages.Message.t()],
  transform: nil | String.t()
}

matcher()

@type matcher() :: String.t() | Regex.t() | nil

matcher_group()

@type matcher_group() :: %{matcher: matcher(), hooks: [hook_fun()]}

t()

@type t() :: %{optional(atom()) => [matcher_group()] | [hook_fun()]}

Functions

events()

@spec events() :: [atom()]

Catalog of every hook event ex_athena fires today. Useful for hosts that want to enumerate or validate user-supplied hook tables.

run_lifecycle(hooks, event, payload)

@spec run_lifecycle(t(), atom(), map()) :: :ok | {:halt, term()}

Fire lifecycle hooks that aren't scoped to a tool (Stop, SessionStart, etc.). Backward-compatible: returns :ok | {:halt, reason} like before. Use run_lifecycle_with_outputs/3 for events that may inject messages or transform prompts.

run_lifecycle_with_outputs(hooks, event, payload)

@spec run_lifecycle_with_outputs(t(), atom(), map()) :: lifecycle_outputs()

Like run_lifecycle/3 but returns a structured outputs map so callers can act on {:inject, msg} and {:transform, prompt} returns. Used by the kernel to thread UserPromptSubmit transforms and :inject-driven message injection across hook events.

Returns %{halt:, injects:, transform:}. halt short-circuits the remaining callbacks (denies / halts always win); injects accumulates in order; transform is last-write-wins.

run_post_tool_use(hooks, tool_name, result, tool_use_id)

@spec run_post_tool_use(t(), String.t(), map(), String.t() | nil) ::
  :ok | {:halt, term()}

Fire PostToolUse hooks matching tool_name.

run_pre_tool_use(hooks, tool_name, input, tool_use_id)

@spec run_pre_tool_use(t(), String.t(), map(), String.t() | nil) ::
  :ok | {:deny, term()} | {:halt, term()}

Fire PreToolUse hooks matching tool_name.