> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polpo.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Runtime Hooks

> Observe and control agent execution at stable lifecycle boundaries

Runtime Hooks are trusted project policy that runs around agent execution. The same hook pack
applies to direct chat, durable Run-backed chat, Channels, and agent nodes inside Project Loops.

Create `.polpo/hooks/quality.json`:

```json theme={null}
{
  "version": 1,
  "name": "quality",
  "hooks": [
    {
      "id": "protect-writes",
      "event": "tool:before",
      "matcher": { "tools": ["write", "edit", "*_delete"] },
      "onFailure": "block",
      "handler": { "type": "command", "command": "./scripts/check-write.mjs" }
    },
    {
      "id": "audit-results",
      "event": "tool:after",
      "mode": "observe",
      "handler": {
        "type": "http",
        "url": "https://policy.example.com/polpo-hooks",
        "connection": "connection-id"
      }
    }
  ]
}
```

Assign it in the agent's `agent.json`:

```json theme={null}
{
  "model": "anthropic/claude-sonnet-4.5",
  "hooks": ["quality"]
}
```

Validate and deploy it with:

```bash theme={null}
polpo hooks validate
polpo hooks list --local
polpo deploy
polpo hooks list
```

## Decisions and order

Controlling hooks run sequentially by scope, priority, and declaration order. Observe-only hooks
run concurrently and cannot change execution. A handler can return `allow`, `deny`, `ask`,
`defer`, `modify`, or `stop`.

`tool:before` may replace proposed input through `modify.data.input`. Polpo then applies the
ordinary tool policy, approval, and schema validation before execution. `tool:after` may replace
the normalized model-visible result through `modify.data.result`. Hooks never grant a capability
the agent did not already have.

Without `terminationGate`, `turn:stop` supports one bounded quality recovery step. If a hook returns feedback, Polpo makes
one additional tool-free model call, checks the result again, and ends. Completed tools are never
replayed.

## Handlers and security

Handlers can run a sandbox `command`, call a public `https` endpoint, invoke a Polpo `tool` or
`mcp_tool`, perform a bounded `prompt`, or invoke a bounded verifier `agent`. Use Connections for
HTTP authentication. Inline authorization headers, tokens, API keys, and secret environment
variables are rejected.

Events use a versioned, redacted envelope and a stable idempotency key. Payloads, timeouts, output,
recursion depth, prompt calls, and verifier turns are bounded. Provider-native tools execute at
the provider; a pre-side-effect `tool:before` decision therefore requires a locally executed Polpo
or Connection tool.

On Polpo Cloud, a command hook without an explicit `timeoutMs` gets 60 seconds so a cold managed
sandbox can start before the command runs. An explicit timeout still wins. The portable OSS
default remains five seconds, and `GET /v1/hooks/capabilities` reports the managed command default.

Set `POLPO_RUNTIME_HOOKS=off` as an emergency kill switch on a managed host.

## Termination gate: validate and repair in the same agent

This opt-in extends `turn:stop`; ordinary stop hooks keep their existing behavior.
Configure a deterministic validator and explicit cumulative limits:

```json theme={null}
{
  "version": 1,
  "name": "quality",
  "hooks": [{
    "id": "site-ready",
    "event": "turn:stop",
    "matcher": { "steps": ["implement"] },
    "terminationGate": { "maxCycles": 3, "maxDurationMs": 300000, "maxTokens": 60000 },
    "onFailure": "block",
    "handler": { "type": "tool", "tool": "site_validate", "timeoutMs": 60000 }
  }]
}
```

The validator returns one of these results:

```json theme={null}
{ "decision": { "action": "continue", "feedback": { "valid": false, "failures": [{ "phase": "build", "file": "src/app.ts", "diagnostic": "Missing import" }] } } }
```

```json theme={null}
{ "decision": { "action": "allow" } }
```

`continue` reopens the operational phase of the **same invocation**, with its authorized
tools, history, sandbox and working copy. Diagnostics are model-visible JSON; the candidate
is available at `hook.data.candidate`, alongside `terminationAttempt` and `correctionCycle`.
The rejected final response is buffered, then replaced by the validated result. Structured
output still uses the separate tool-free formatting phase; validation can return the agent
to work before a new final output is produced. Tool/progress events remain observable.
Remove the `steps` matcher to apply the pack to chat as well as Loop nodes. Validators use
the same authorized tool executor; include the validation tool in the applicable tool policy.

`maxCycles` is the number of allowed repairs (1–20); `maxDurationMs` is elapsed time from
invocation start (at most one hour); `maxTokens` bounds reported agent/compaction tokens
(at most 10 million). Agent maxTurns is an additional cumulative limit. Multiple matching
gates use the strictest limits. Budgets survive checkpoints and cannot be loosened by
resuming. Input tokens already processed by a provider cannot be preempted; the next output
is capped and over-budget usage stops execution. Nested tool/model costs are separate.
Validators themselves must be deterministic `command`, `http`, `tool` or `mcp_tool`
handlers, bounded by timeout. Prompt/verifier-agent hooks remain available outside a gate.

Empty output, missing decisions, invalid feedback, timeouts and checkpoint-write failures
**fail closed**, even if `onFailure` says continue. `stop` with non-empty string feedback
is a compatibility form of `continue` inside a gate. `continue` outside a configured gate
is rejected. Diagnostics remain subject to the hook contract's redaction and size limits.

Durable Run chat and sequential Project Loop nodes persist history, cumulative budgets,
step-invocation identity and pending/accepted validation together. A recorded validation
decision is replayed without rerunning its handler. An accepted checkpoint returns its
stored result without inference or rebilling previous usage. An interruption during a
model/tool operation is marked `termination_gate_effect_unconfirmed`: it requires
reconciliation and never blindly repeats the operation. Validators must honor their
idempotency key because an external effect can precede its ledger write.

Managed repair cycles retain the active sandbox lease for the current invocation. A checkpoint
persists conversation and validation state; it is not a snapshot of an arbitrary sandbox.
Recovery after loss of the worker or sandbox still requires the host to restore the working
copy and reconcile uncertain effects. This feature does not add automatic crash retries.

Direct HTTP chat supports continuation during the request, but has no crash-resume promise.
Parallel graph branches do not yet have individual durable checkpoints: a durable gate in
such a branch is rejected before inference. Client-tool handoff remains a handoff and does
not count as successful termination. Existing public Project Loop approval-resume rules
remain unchanged; this feature does not introduce a new retry endpoint.

Errors use `termination_gate_*` codes, including `cycles_exhausted`, `turns_exhausted`,
`time_exhausted`, `tokens_exhausted`, `usage_unknown`, `checkpoint_invalid`,
`checkpoint_too_large`, `effect_unconfirmed`, `parallel_unsupported` and `rejected`.
Traces distinguish validating, continuing and accepted, with stable hook execution IDs.

In Cloud, `runtime.termination-gate.mode` and `runtime.hooks.mode` must both enforce for
the target project/environment. Default gate rollout is off. A configured gate whose rollout
or handler is disabled blocks execution instead of silently removing validation.
Inspect `polpo hooks capabilities --json`: portable `terminationGate` states contract
support; `managed.terminationGate.enabled` states availability on the target.
Author packs under `.polpo/hooks`, run `polpo hooks validate`, then deploy normally.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.