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:
| Event | Payload fields |
|---|---|
SessionStart | goal_id, plugin |
SessionEnd | goal_id, status |
PreToolUse | tool, argv, agent_id |
PostToolUse | tool, argv, success |
Invocation paths
| Path | When hooks run |
|---|---|
castellan run | SessionStart/SessionEnd on episode boundaries; PreToolUse/PostToolUse on MCP tool calls and shell plugin execution via ToolGovernancePipeline |
castellan run --rlm | Same session hooks; mux delegate steps use nested sub_harness panes |
| Shell plugin | PreToolUse/PostToolUse with tool: "shell" and argv in payload |
Hook commands must exit 0; non-zero exits fail the surrounding operation.
Prove-before-execute ([governance.plan_verify])
Optional Guardians-shaped gate (Meijer / metareflection pattern — not Universalis-the-language).
When enabled, high-risk tools (shell / exec by default) require a verified workflow_plan
certificate in the tool args envelope before side effects. Missing or failing verification ⇒ deny
(fail closed). Composes under PermissionPolicy: Deny still short-circuits; plan verify does not
replace Allow / Deny / Prompt.
[governance.plan_verify]
enabled = true
# high_risk_tools = ["shell", "exec"]
# allowlisted_tools = ["shell", "exec", ...]
Supply the plan on shell/MCP args as workflow_plan (or tool_workflow): ordered steps with
symbolic refs (no live payloads). Research: docs/superpowers/research/2026-07-13-universalis-agent-proofs.md.
Demo: docs/demo/plan-prove-before-execute.sh.
This is tool-plan safety on the PreToolUse path — distinct from evolve drift / Φ / immune gates
on topology genomes. When plan verify runs on the MCP tool hot path, outcomes emit
PlanVerifyPassed / PlanVerifyDenied on the live EventSink (CLI --json, mux bridge, ACP)
so observers see prove-before-execute without reading audit JSONL alone.
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 — historical script name):
#!/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
| Agent | Suggested hook point |
|---|---|
| Claude Code | Wrap claude in a shell function; call hook on tool-approval and turn-complete |
| Codex | Export CODEX_HOOK_CMD pointing to the script above |
| Cursor | Use 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
| Variable | Purpose |
|---|---|
CASTELLAN_SOCKET | Override default ~/.config/castellan/castellan.sock |
CASTELLAN_ENV=1 | Set in spawned pane shells (detect castellan-managed sessions) |