Skip to Content
Hooks

Hooks

Hooks let you run your own shell commands when something interesting happens inside AgentLoop — a task moves to review, an agent finishes work, the orchestrator starts up, and so on. They’re the primary extension point for wiring AgentLoop into the rest of your toolchain (Slack notifications, deploys, custom logging, external trackers) without writing a plugin.

Hooks are an alpha feature. The event list is stable, but new events may be added in upcoming releases.

Why use hooks

A hook is just a shell command that AgentLoop runs when an event fires. The command receives a JSON payload describing the event on stdin, so you can pipe it into jq, a script, or a small program of your choice.

Common reasons to add a hook:

  • Post a Slack or Discord message when a task gets blocked
  • Kick off a deploy when a task is marked done
  • Mirror task activity into an external tracker
  • Run a security scan whenever code lands in the review column
  • Capture custom metrics about agent runtimes and outcomes

If you need richer behavior than a shell command can provide — for example, dynamically registering new agents — write a plugin instead.

Where hooks are configured

Hooks live under the hooks: section of your AgentLoop config file. Both user-level and project-level configs are supported, and definitions are additive: if you have hooks at both levels, all of them run.

LocationScope
~/.config/agentloop/config.yamlHooks that should fire on every project on this machine
./.agentloop/config.yamlHooks specific to one project (commit these so the team shares them)

Project-level hooks run before user-level hooks for the same event.

Treat project-level hook scripts the same way you treat any executable in your repo. AgentLoop will run them on your machine when events fire — review hooks before pulling changes from collaborators.

Available events

EventFires when
task:createdA new task is added to the board
task:updatedA task’s metadata changes
task:statusChangedA task moves between columns (todo, in-progress, review, done, blocked)
task:assignedA task is assigned to an agent
task:completedA task is marked done
task:deletedA task is removed
agent:beforeInvokeAn agent is about to start work on a task
agent:afterInvokeAn agent finishes a turn (success or failure)
agent:errorAn agent errors out
orchestrator:startedThe orchestrator (the loop that schedules agents) starts
orchestrator:stoppedThe orchestrator stops
session:completedAn interactive chat session finishes

Configuring a hook

A hook definition has two required fields — event and command — plus a handful of optional knobs for filtering, timeouts, and execution behavior.

# ./.agentloop/config.yaml hooks: settings: enabled: true default_timeout_ms: 30000 log_output: true fail_silently: true definitions: - event: "task:statusChanged" command: "./scripts/notify-slack.sh" description: "Ping the team when something gets blocked" matcher: to_status: ["blocked"]

Definition fields

FieldRequiredDescription
eventyesOne of the events listed above
commandyesShell command to run (executed via sh -c)
descriptionnoHuman-readable label, shown in logs
matchernoFilter that limits when this hook fires (see below)
timeout_msnoPer-hook timeout. Falls back to settings.default_timeout_ms
blockingnoIf true, AgentLoop waits for the script to finish. Defaults to fire-and-forget
prioritynoHigher numbers run first when multiple hooks match the same event
environmentnoExtra environment variables passed to the script

Settings

SettingDefaultDescription
enabledtrueMaster kill switch for all hooks
default_timeout_ms30000Default per-hook timeout
log_outputtrueCapture stdout/stderr from hooks in the AgentLoop logs
fail_silentlytrueIf false, blocking hooks that exit non-zero surface the error to the caller

Filtering with matcher

Most hooks only care about a slice of events. The matcher block narrows down when a hook fires — all conditions are combined with AND.

Matcher fieldApplies toDescription
from_statustask:statusChangedOnly fire when moving from one of these columns
to_statustask:statusChangedOnly fire when moving into one of these columns
agent_typetask and agent eventsLimit to specific agents (e.g. engineer, qa-tester)
task_prioritytask eventsLimit to certain priorities (low, medium, high, critical)
task_tagstask eventsFire when the task carries any of these tags
successagent:afterInvoke, agent:errorFilter by success/failure

What a hook receives

When a hook fires, AgentLoop spawns a shell, sets a couple of environment variables, and writes a JSON payload to the script’s stdin.

Environment

VariableDescription
AGENTLOOP_HOOK_EVENTThe event name (e.g. task:statusChanged)
AGENTLOOP_PROJECT_PATHAbsolute path of the project the event came from

The script also runs with cwd set to the project root, so relative paths in your command (./scripts/foo.sh, ~/bin/foo.sh) resolve sensibly.

JSON payload on stdin

The exact shape varies by event, but every payload includes event, timestamp, timestamp_iso, and project_path. Task events include the full task object; status changes include previousStatus and newStatus; agent events include agentType, executionId, and friends.

A task:statusChanged payload, for example, looks roughly like:

{ "event": "task:statusChanged", "timestamp": 1746320000000, "timestamp_iso": "2026-05-04T00:00:00.000Z", "project_path": "/Users/you/projects/my-app", "task": { "id": 42, "title": "Implement authentication", "status": "review" }, "previousStatus": "in-progress", "newStatus": "review" }

A simple script can pull fields out of the payload with jq:

#!/usr/bin/env bash # scripts/notify-slack.sh payload=$(cat) title=$(echo "$payload" | jq -r '.task.title') to=$(echo "$payload" | jq -r '.newStatus') curl -X POST "$SLACK_WEBHOOK" \ -H 'Content-Type: application/json' \ --data "{\"text\":\"Task moved to *$to*: $title\"}"

Examples

Notify Slack when a task gets blocked

hooks: definitions: - event: "task:statusChanged" command: "./scripts/notify-slack.sh" description: "Slack alert on blocked tasks" matcher: to_status: ["blocked"]

Trigger a deploy when work ships

hooks: definitions: - event: "task:statusChanged" command: "./scripts/deploy.sh" description: "Kick off staging deploy" blocking: true timeout_ms: 120000 matcher: from_status: ["review"] to_status: ["done"] task_tags: ["deploy"]

Audit log of every agent run

hooks: definitions: - event: "agent:afterInvoke" command: "jq -c . >> ~/.agentloop-audit.jsonl" description: "Append every agent run to a JSONL audit log"

Limits and gotchas

  • Hooks run with your full shell environment. They can do anything your user account can do. Treat them like any other script in your repo.
  • Non-blocking by default. Hooks fire-and-forget unless you set blocking: true. Use blocking only when downstream agents need to wait on the result.
  • Timeouts are enforced. Hooks that exceed timeout_ms (default 30 seconds) are killed.
  • Output is captured for logging. Stdout and stderr are written to AgentLoop’s logs when log_output is on. Don’t echo secrets.
  • CI environment variables are stripped. AgentLoop removes CI, CONTINUOUS_INTEGRATION, BUILD_NUMBER, and GITHUB_ACTIONS from the hook environment so tools don’t accidentally enter non-interactive CI mode.
  • Restart after editing. Hook configuration is loaded on startup. Restart the daemon (or quit and reopen the desktop app) after changing your config.
Last updated on
AgentLoop — Multi-agent loops you can see and control