<!-- Generated from release sources; edit site/content/docs, not this file. -->

# 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](configuration/action.md#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](configuration/action.md).

## 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:

```text
{ 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](configuration/filter.md)
page lists all candidate fields and matching rules.

### Example: audit every block

<!-- docs-example:valid -->

```json
{
  "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.
