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. Aleph supports the following hook events:

Tool Lifecycle

EventWhen It FiresCan Block?
PreToolUseBefore a tool is executedYes
PostToolUseAfter a tool completes successfullyNo
PostToolUseFailureAfter a tool failsNo

Session Lifecycle

EventWhen It FiresCan Block?
SessionStartWhen a new session beginsNo
SessionEndWhen a session endsNo
PreCompactBefore session compactionYes

User Interaction

EventWhen It FiresCan Block?
UserPromptSubmitWhen the user submits a promptYes
PermissionRequestWhen a tool requests elevated permissionYes

Agent Lifecycle

EventWhen It FiresCan Block?
SubagentStartWhen a sub-agent is launchedNo
SubagentStopWhen a sub-agent finishesNo
StopWhen processing stopsNo
NotificationWhen a notification is sentNo
SetupDuring initial setupNo

Chat and Command (Plugin Events)

EventWhen It FiresCan Block?
ChatMessageWhen a chat message is receivedYes
ChatParamsWhen chat parameters are configuredYes
ChatResponseWhen a chat response is generatedNo
CommandExecuteBeforeBefore a command executesYes
CommandExecuteAfterAfter a command executesNo

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 = "PreToolUse"
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 aleph.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

On this page