Events
🦉 HooKit
Reference

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

EventWhen it occursHook OutcomeEvent Outcome behavior
tool_callBefore a model-requested tool executespass or blockRuns all matching Hooks; aggregates failures into one block; the tool does not run
tool_resultAfter tool execution, before Pi consumes its resultpass or patchRuns all matching Hooks; aggregates failures into one fixed error replacement patch
turn_endAfter one assistant turn and its tool resultspass or reportAggregates and presents failures
agent_endWhen an agent loop ends, before potential automatic continuationpass or reportAggregates; requests deduplicated corrective feedback
agent_settledAfter the run fully settles, with no automatic retry, compaction, or queued continuation pendingpass or reportAggregates and presents failures
session_before_switchBefore creating a new session or resuming anotherpass or cancelAggregates and cancels the switch on failure
session_before_forkBefore forking at a selected session entrypass or cancelAggregates and cancels the fork on failure
hook_resultAfter the Native Event Outcome freezes, for each originating Hook Resultpass or reportHandles 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:

  1. Traverse all matching native Hooks: match event and filter, run optional when, then effective shell, retaining immutable individual Hook Results. A filter miss or ordinary false Precondition creates no result.
  2. Aggregate the native Hook Outcomes and freeze the Native Event Outcome.
  3. 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_result Hooks and their owned Actions.
  4. 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 when means “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 shell canonicalizes to the exact command "true".
  • Exact "true" → pass, code 0; exact "false" → event-specific failure, code 1. 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 when and 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_end and agent_end evaluations skip Hooks before traversal. A cancellation after a real shell starts yields code null and 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/report Hook Result and may select its own Action.
  • That local result is never redispatched, bounding handling to one level.
  • hook_result handlers are awaited without the originating abort signal 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.