Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Agent hooks

Castellan supports two hook surfaces: governance lifecycle hooks (configured in castellan.toml) and multiplexer agent lifecycle reporting (socket API for editor agents).

Governance lifecycle hooks (castellan.toml)

Configure deterministic shell hooks in layered config (castellan.toml, ~/.castellan/config.toml, .castellan/local.toml):

[[hooks.hooks]]
event = "SessionStart"
command = "./scripts/hooks/session-start.sh"

[[hooks.hooks]]
event = "PreToolUse"
command = "./scripts/hooks/pre-tool.sh"

[[hooks.hooks]]
event = "PostToolUse"
command = "./scripts/hooks/post-tool.sh"

[[hooks.hooks]]
event = "SessionEnd"
command = "./scripts/hooks/session-end.sh"

Accepted event / kind values: SessionStart, SessionEnd, PreToolUse, PostToolUse (snake_case aliases also work).

Each command receives a single JSON payload line on stdin:

EventPayload fields
SessionStartgoal_id, plugin
SessionEndgoal_id, status
PreToolUsetool, argv, agent_id
PostToolUsetool, argv, success

Invocation paths

PathWhen hooks run
castellan runSessionStart/SessionEnd on episode boundaries; PreToolUse/PostToolUse on MCP tool calls and shell plugin execution via ToolGovernancePipeline
castellan run --rlmSame session hooks; mux delegate steps use nested sub_harness panes
Shell pluginPreToolUse/PostToolUse with tool: "shell" and argv in payload

Hook commands must exit 0; non-zero exits fail the surrounding operation.

Multiplexer agent lifecycle (socket API)

Agents (Claude Code, Codex, Cursor) can report lifecycle state to the multiplexer without relying on screen heuristics.

Socket API

{"id":"1","method":"pane.report_agent","params":{
  "pane_id": "w1:p1",
  "agent": "claude",
  "state": "working",
  "source": "hook"
}}

States: idle, working, blocked, done, unknown.

When source is hook, screen heuristics are ignored until the pane is reset.

Shell hook example

Install into your agent wrapper (~/.config/castellan/hooks/herdr-agent-state.sh):

#!/usr/bin/env bash
# Usage: herdr-agent-state.sh <pane_id> <agent> <state>
PANE_ID="${1:?pane_id}"
AGENT="${2:?agent}"
STATE="${3:?state}"
SOCKET="${CASTELLAN_SOCKET:-$HOME/.config/castellan/castellan.sock}"
printf '%s\n' "{\"id\":\"hook\",\"method\":\"pane.report_agent\",\"params\":{\"pane_id\":\"$PANE_ID\",\"agent\":\"$AGENT\",\"state\":\"$STATE\",\"source\":\"hook\"}}" \
  | nc -U "$SOCKET"

Claude / Codex / Cursor integration

AgentSuggested hook point
Claude CodeWrap claude in a shell function; call hook on tool-approval and turn-complete
CodexExport CODEX_HOOK_CMD pointing to the script above
CursorUse cursor-agent lifecycle env callbacks if available; else poll is fallback

CLI wait helper

castellan herd wait --pane w1:p1 --status idle --timeout-ms 60000

Equivalent to events.wait with pane_agent_status_changed.

Environment

VariablePurpose
CASTELLAN_SOCKETOverride default ~/.config/castellan/castellan.sock
CASTELLAN_ENV=1Set in spawned pane shells (detect castellan-managed sessions)