Events
Supported Events, Hook Outcomes, and Event Outcome behavior.
A Hook subscribes to exactly one Event named by its event field. Events
are either Native Events originating from Pi, or the Hook Result Event
(hook_result) projected from a Hook Result. Each completed Hook Invocation
produces one Hook Outcome; Filter misses and ordinary false Preconditions
produce none. Each evaluated Event has one aggregated, event-specific Event
Outcome.
Supported events
| Event | When it occurs | Hook Outcome | Event Outcome behavior |
|---|---|---|---|
tool_call | Before a model-requested tool executes | pass or block | Runs all matching Hooks; aggregates failures into one block; the tool does not run |
tool_result | After tool execution, before Pi consumes its result | pass or patch | Runs all matching Hooks; aggregates failures into one fixed error replacement patch |
turn_end | After one assistant turn and its tool results | pass or report | Aggregates and presents failures |
agent_end | When an agent loop ends, before potential automatic continuation | pass or report | Aggregates; requests deduplicated corrective feedback |
agent_settled | After the run fully settles, with no automatic retry, compaction, or queued continuation pending | pass or report | Aggregates and presents failures |
session_before_switch | Before creating a new session or resuming another | pass or cancel | Aggregates and cancels the switch on failure |
session_before_fork | Before forking at a selected session entry | pass or cancel | Aggregates and cancels the fork on failure |
hook_result | After the Native Event Outcome freezes, for each originating Hook Result | pass or report | Handles one originating result; cannot alter it |
Every Event Outcome identifies its Event. Native Event Outcomes carry only the
required reason or replacement patch for their event-specific failure kind.
Hook Result Event Outcomes additionally carry the originating Hook Reference
and Invocation ID and permit only pass or report. session_shutdown is
not a supported Hook Event because Pi provides no cancellation result for it.
Tool-result suppression
A failing tool_result Hook suppresses the original content. HooKit supplies
one text result beginning [BLOCKED by HooKit], with its combined reason and
The original tool result was suppressed., marks the result isError: true,
and retains the toolâs details. This is a fixed error replacement, not a
custom replacement supplied by shell stdout. An owned message Action adds
context separately; it cannot restore the suppressed output.
Agent-end corrective feedback
Ordinary agent_end failures request a corrective turn with the combined
feedback. HooKit remembers that failure fingerprint for the session: an
identical repeated failure reports automatic retry stopped instead of
requesting another corrective turn. A changed failure can request another
turn; a passing evaluation clears the fingerprint. Infrastructure-level
Evaluation errors are presented without an automatic corrective turn.
This deduplication applies to HooKitâs ordinary corrective feedback, not to user-authored continuation Actions. Those have no general loop guard; see continuation caution.
Evaluation order
Matching Hooks run sequentially in the deterministic order of the immutable Enabled Hook Set, derived once per Event from the Catalog entry order and Preset reference order:
- Traverse all matching native Hooks: match
eventandfilter, run optionalwhen, then effectiveshell, retaining immutable individual Hook Results. A filter miss or ordinary false Precondition creates no result. - Aggregate the native Hook Outcomes and freeze the Native Event Outcome.
- Traverse originating Hook Results in order. For each result, first select
its owned Action against that ownerâs Hook Outcome and code, then evaluate
its Hook Result Event through the matching
hook_resultHooks and their owned Actions. - Deliver the ordered Effects best-effort through Pi.
No owned Action or reactive Hook runs between native shells. All matching native Hooks run even after a sibling fails; failures aggregate into one Event Outcome. Actions and reactive Hooks cannot change that frozen decision.
Hook outcome vs event outcome
The Hook Outcome is one Hookâs decision: pass, or the Eventâs failure
kind (block, patch, cancel, report). The Event Outcome is the
complete aggregated decision for the Event. They are deliberately distinct: an
owned Action selects only its ownerâs Hook Outcome and code, so a passing Hook
may request a pass Action even when a sibling blocks the Event.
Preconditions
The optional when field is a shell precondition that runs first. Distinguish
the two failure ways:
- Ordinary non-zero
whenmeans ânot applicableâ: the Hook is skipped, producing no Hook Result, no Action, and no Evaluation Report row. - Infrastructure failureâa timeout, cancellation, or spawn failureâfails
closed with code
null, producing the event-specific failure outcome so an owned failure Action can select it.
A passing when and the main shell share one Invocation ID and are counted
as one Hook row.
Shell semantics
- Omitted
shellcanonicalizes to the exact command"true". - Exact
"true"â pass, code0; exact"false"â event-specific failure, code1. These are optimized without spawning a subprocess, yet behave and render as ordinary commands with no trimming or parsing:" true","true ","true && echo ok", and every other string still run through the real/bin/sh, so pipes, redirects,&&, and||work normally. - A
whenand its main shell share one Invocation ID; owned Actions share that same source-qualified ref and Invocation ID. - Shortcuts honor an already-aborted signal. Already-aborted
turn_endandagent_endevaluations skip Hooks before traversal. A cancellation after a real shell starts yields codenulland the event-specific failure outcome. - A 5-second default timeout bounds commands and preconditions.
Delivery cannot weaken an Outcome
Selected Actions are returned as delivery-neutral Effects in deterministic result-major order and delivered by the Pi adapter best-effort. A delivery failure does not change the Event Outcome: a block, patch, cancellation, or report stands regardless of whether its Effect was delivered. See action.
Hook Result Event
The Hook Result Event (hook_result) lets one Hook observe anotherâs
immutable result. For each originating Hook Result, its owned Action is
considered first, followed by configured hook_result Hooks in Enabled
Hook Set order. The projected Event carries only the bounded identity and
decision the originating Hook produced:
{ event: "hook_result", hookRef, invocationId, outcome, code }Every originating result projects exactly one Hook Result Eventâincluding
when no reactive Hook matchesâand its explicit Event Outcome is then pass.
Handling is bounded to one level:
- A matching reactive Hook gets its own local
pass/reportHook Result and may select its own Action. - That local result is never redispatched, bounding handling to one level.
hook_resulthandlers are awaited without the originatingabortsignal and can never alter the frozen Native Event Outcomeâthe originating Hook Result is already immutable.- Origin identity remains available separately for accounting and shell environment projection, so you can audit a block without changing it.
The hook_result Filter is limited to event, source-qualified hookRef,
invocationId, outcome, and code. Identity fields use the
regular-expression/any-of string rules; outcome and code use the exact
shared selector semantics of owned Actions. The filter
page lists all candidate fields and matching rules.
Example: audit every block
{
"local": {
"audit-blocks": {
"description": "Emit an event whenever a Hook block occurs",
"event": "hook_result",
"filter": {
"hookRef": "^local/no-env-access$",
"outcome": "block",
"code": [1, null]
},
"shell": "./scripts/audit-hook-result.sh",
"action": {
"type": "emit-custom-event",
"outcome": "pass",
"name": "example:hook-audited"
}
}
}
}Each time local/no-env-access produces a block with code 1 or null,
audit-blocks runs its shell and, on a pass, emits the custom Event. It can
never reverse the block.