Aleph
Automation

Hooks

React to agent lifecycle events with shell commands, prompts, and agent invocations

Hooks are callback functions that execute in response to agent lifecycle events — before a tool runs, when a session starts, after a command executes, and more. They let you inject custom logic into the agent pipeline without modifying core code: audit logging, security gates, notification dispatching, analytics, and dynamic prompt injection.

How Hooks Work

Aleph's hook system sits between the agent loop and tool execution, forming an event-driven middleware pipeline. When a lifecycle event fires, the HookExecutor finds all registered hooks for that event, checks their matcher patterns, and executes their actions according to their kind (interceptor, observer, or resolver).

User Input ──▶ Agent Loop ──▶ [PreToolUse hooks] ──▶ Tool Execution

                                          [PostToolUse hooks] ◀──┘

                                          Response ──▶ [SessionEnd hooks]

Hook Events

Every hook is bound to a specific event. The single source of truth for the exhaustive list is the HookEvent enum in src/extension/types/hooks.rs. Serialization uses snake_case (before_tool_call); PascalCase aliases are kept for backward compatibility with legacy hooks.json (PreToolUseBeforeToolCall). The hooks.events RPC and the hooks_manage events command both project HookEvent::ALL and do not maintain their own hand-written lists.

Tool Lifecycle

EventWhen It FiresCan Block?
BeforeToolCallBefore a tool is executedYes
AfterToolCallAfter a tool completes successfullyYes (can rewrite the output)
AfterToolCallFailureAfter a tool failsNo
ToolResultPersistWhen a tool result is being persistedNo

Session and Agent

EventWhen It FiresCan Block?
BeforeAgentStartBefore the agent begins processingYes
AgentEndWhen the agent completesYes (can rewrite the final output)
SessionStartWhen a new session beginsYes
SessionEndWhen a session endsNo
BeforeCompactionBefore session compactionYes
AfterCompactionAfter session compactionNo
UserPromptSubmitAfter the user submits a prompt, before the first think callYes
StopWhen the model produces a turn with no tool callsYes (interceptor may veto)

Subagent

EventWhen It FiresCan Block?
SubagentStartWhen a sub-agent is spawnedNo (observer-only)
SubagentStopWhen a sub-agent completesNo

Message Channels

EventWhen It FiresCan Block?
MessageReceivedWhen a channel message is receivedYes
MessageSendingBefore a message is sent to a channelYes
MessageSentAfter a message has been sentNo

Provider and Gateway

EventWhen It FiresCan Block?
PreApiRequestBefore an LLM provider request is issuedNo
PostApiRequestAfter an LLM provider response is receivedNo
GatewayStartWhen the gateway startsNo
GatewayStopWhen the gateway stopsNo

Notification and Permission

EventWhen It FiresCan Block?
NotificationWhen a notification is sentNo
PermissionRequestWhen a tool requests elevated permissionYes

Hook Kinds

Not all hooks behave the same way. Aleph classifies hooks into three kinds, each with different execution semantics:

Interceptor

Pipeline execution. Interceptors run sequentially by priority and can modify the request context or block execution entirely. They short-circuit on the first block.

Use interceptors for:

  • Security gates (block dangerous tool calls)
  • Input validation (reject malformed arguments)
  • Request modification (rewrite tool parameters)
# Block all shell commands that contain 'rm -rf'
[[hooks]]
event = "before_tool_call"   # PascalCase aliases PreToolUse / BeforeToolCall also accepted
kind = "interceptor"
priority = "system"
matcher = "shell:exec"
actions = [{ type = "command", command = "echo $ARGUMENTS | grep -q 'rm -rf' && echo 'block: Dangerous command blocked' || echo 'pass'" }]

When an interceptor command outputs a line starting with block:, execution is halted and the rest of the line becomes the block reason shown to the user.

Observer

Fire-and-forget. Observers run in parallel, receive read-only context, and cannot block or modify anything. Errors are logged but never propagated.

Use observers for:

  • Audit logging
  • Analytics and metrics
  • External notifications
  • Telemetry
[[hooks]]
event = "PostToolUse"
kind = "observer"
actions = [{ type = "command", command = "echo \"$(date): Tool $TOOL_NAME completed\" >> /var/log/aleph/tool_audit.log" }]

Resolver

First-win competition. Resolvers run sequentially by priority and stop as soon as one returns a value. Used for dynamic resolution where multiple sources might provide an answer.

Use resolvers for:

  • Dynamic configuration lookup
  • Credential resolution
  • Route selection

Hook Priority

Hooks execute in priority order within their kind. Lower numeric values run first:

PriorityValueUse Case
system-1000Security, audit — always runs first
high-100Important business logic
normal0Default priority
low100Non-critical extensions

For interceptors, priority determines who gets to block first. For resolvers, it determines who gets to resolve first. For observers, priority has no practical effect since they run in parallel.

Hook Actions

Each hook can execute one or more actions when triggered. Three action types are available:

Command

Execute a shell command. The command runs in a subprocess with environment variables populated from the hook context:

actions = [{ type = "command", command = "${PLUGIN_ROOT}/scripts/validate.sh" }]

Available environment variables:

VariableDescription
$TOOL_NAMEName of the tool being invoked
$ARGUMENTSTool arguments as JSON string
$TOOL_INPUTTool input content
$FILEFile path (if applicable)
$SESSION_IDCurrent session ID
$PLUGIN_ROOTRoot directory of the plugin

Both $VAR and ${VAR} syntax are supported. Commands have a default timeout of 300 seconds.

Prompt

Inject a prompt string into the conversation context. The prompt is passed to the LLM for evaluation, allowing hooks to influence the agent's reasoning:

actions = [{ type = "prompt", prompt = "Before executing ${TOOL_NAME}, verify the arguments are safe: ${ARGUMENTS}" }]

Agent

Invoke a named agent to handle the event. The agent runs as a sub-agent with its own session:

actions = [{ type = "agent", agent = "review-agent" }]

Matcher Patterns

Tool-related hooks can use matcher to restrict which tools they apply to. The matcher is a regex pattern tested against the tool name:

# Match only the Write tool
matcher = "Write"

# Match Write or Edit
matcher = "Write|Edit"

# Match any shell tool
matcher = "shell:.*"

# Match all tools (default when no matcher is specified)
# matcher not set — hook fires for every tool

If no matcher is set, the hook fires for all tool invocations of that event type.

Registering Hooks

In Plugin Configuration

Plugins define hooks in their aleph-plugin.json manifest:

{
  "name": "security-gate",
  "hooks": [
    {
      "event": "PreToolUse",
      "kind": "interceptor",
      "priority": "system",
      "matcher": "shell:exec|Write|Edit",
      "actions": [
        {
          "type": "command",
          "command": "${PLUGIN_ROOT}/check-safety.sh"
        }
      ]
    },
    {
      "event": "PostToolUse",
      "kind": "observer",
      "actions": [
        {
          "type": "command",
          "command": "${PLUGIN_ROOT}/log-action.sh"
        }
      ]
    }
  ]
}

In TOML Configuration

Hooks can also be defined directly in config.toml:

[[hooks]]
event = "SessionStart"
kind = "observer"
plugin_name = "session-logger"
actions = [
  { type = "command", command = "echo 'Session started: $SESSION_ID' >> ~/aleph-sessions.log" }
]

[[hooks]]
event = "PreToolUse"
kind = "interceptor"
priority = "high"
matcher = "shell:exec"
plugin_name = "command-guard"
actions = [
  { type = "command", command = "~/scripts/check-command.sh" }
]

Hook Execution Context

Every hook receives a HookContext with information about the triggering event:

FieldTypeDescription
session_idstringThe active session ID
tool_namestring?Tool name (for tool events)
argumentsstring?Tool arguments as JSON
tool_inputstring?Tool input content
file_pathstring?Relevant file path
working_dirstring?Working directory for commands
envmapAdditional environment variables

For command actions, all context fields are injected as environment variables. For prompt actions, they are available as ${VAR} template substitutions.

Interceptor Results

Interceptor hooks return structured results that control the pipeline:

OutcomeWhat Happens
PassExecution continues to the next interceptor or the tool itself
BlockExecution halts. The block reason is reported to the user.
Block (silent)Execution halts without showing a message to the user
ModifiedExecution continues with a modified context (rewritten arguments, etc.)

When an interceptor hook action fails (command exits non-zero, timeout, etc.), the default behavior is to block execution for safety. A broken security hook should fail closed, not open.

Practical Examples

Audit Trail

Log every tool invocation to a file for compliance:

{
  "event": "PostToolUse",
  "kind": "observer",
  "actions": [{
    "type": "command",
    "command": "jq -n --arg tool \"$TOOL_NAME\" --arg args \"$ARGUMENTS\" --arg session \"$SESSION_ID\" --arg ts \"$(date -u +%FT%TZ)\" '{timestamp: $ts, tool: $tool, arguments: $args, session: $session}' >> /var/log/aleph/audit.jsonl"
  }]
}

Slack Notification on Session End

Notify a Slack channel when a long-running session finishes:

{
  "event": "SessionEnd",
  "kind": "observer",
  "actions": [{
    "type": "command",
    "command": "curl -s -X POST \"$SLACK_WEBHOOK\" -H 'Content-Type: application/json' -d '{\"text\": \"Aleph session '$SESSION_ID' completed.\"}'"
  }]
}

File Write Protection

Prevent the agent from writing to sensitive directories:

{
  "event": "PreToolUse",
  "kind": "interceptor",
  "priority": "system",
  "matcher": "Write|Edit",
  "actions": [{
    "type": "command",
    "command": "echo $ARGUMENTS | jq -r '.file_path // .path // \"\"' | grep -qE '^/(etc|usr|System)' && echo 'block: Writing to system directories is not allowed' || echo 'pass'"
  }]
}

Dynamic Prompt Injection

Add context-aware instructions before tool execution:

{
  "event": "PreToolUse",
  "kind": "observer",
  "matcher": "shell:exec",
  "actions": [{
    "type": "prompt",
    "prompt": "Safety reminder: You are about to execute a shell command. Verify that the command does not modify or delete files outside the project directory."
  }]
}

See Also

  • Events — Subscribe to system events programmatically
  • Cron Jobs — Schedule recurring tasks
  • Extensions — Plugin system that registers hooks
  • Security — Security model and permission system

26.7.x Addendum

Cron / Heartbeat Realized (stubs → real)

26.7.x: the historical stub RPCs (cron.*_stub / heartbeat.*_stub) have been realized.

hooks.* Full RPC

  • hooks.list / hooks.registry / hooks.add / hooks.remove / hooks.reload / hooks.events

26.7.x Hooks hardening:

  • context_injection_limit_bytes — bound injected context (avoid unbounded growth)
  • require_consent — consent gate
  • Runtime inventory — list registered hooks

Guardian Judge

26.7.21+: the LLM judge for heartbeat probes — payload masks secrets.

Heartbeat Refuses Dangerous Tools

26.7.21+: heartbeat probes refuse to use dangerous tools or confirmation-gated tools.

Day-Boundary Log Rotation

26.7.24+: the daemon supports day-boundary log rotation; ALEPH-BOOT marker.

Full RPC

See Gateway Methods Reference for cron / heartbeat / hooks.

On this page