Hook Evaluation
The sequence from Event to Hook Result Event.
To reason about HooKit, follow what happens to one Native Event. HooKit derives an immutable Enabled Hook Set for the Event from the Catalog entry order and Preset reference order, snapshots bounded context once, then runs the matching native Hooks first, then processes reactions:
Native Event + one Enabled Hook Set
├─ each native Hook: Filter → when → shell → immutable Hook Result
├─ aggregate native Hook Outcomes → freeze Native Event Outcome
└─ each originating result, in order:
├─ select its owned Action
└─ evaluate Hook Result Event → reactive Hooks and Actions (bounded)
Ordered Effects → best-effort Pi delivery- Event. The Native Event arrives (
tool_call,tool_result,turn_end,agent_settled, and similar session changes). - Filter. The candidate is projected from bounded Event data. A miss runs nothing.
- Precondition (
when). Optional shell command checked inside the Hook Invocation. An ordinary false Precondition means “not applicable”: no Hook Result, no Action, and no report row. Inability to complete it (a timeout, cancellation, or spawn failure) fails closed with codenull. - Shell. The effective decision command runs and produces one immutable Hook Result carrying the Hook Reference, Invocation ID, Hook Outcome, and code.
- Aggregation. After all matching native shells finish, HooKit freezes the aggregate Native Event Outcome.
- Reactions, result-major. For each originating result, the owned Action
is selected against that owner’s outcome and code first. Then its
hook_resultEvent is evaluated so configured reactive Hooks can observe it and select their own Actions. Reactive results are never redispatched. - Delivery. Pi receives ordered Effects best-effort. Delivery cannot change the frozen Event Outcome.
Hook outcome vs event outcome
The Hook Outcome is one Hook’s individual decision — pass, or the
Event’s failure kind (block, patch, cancel, report). The Event
Outcome is the complete, event-specific decision aggregated from every Hook
Outcome for that Event, carrying only the needed response data (a combined
reason or replacement patch). Every Event has exactly one Event Outcome,
including a minimal pass.
These are deliberately distinct:
- Hooks run sequentially; tool and lifecycle Events run every matching Hook and aggregate all failures into one Event Outcome.
- The Native Event Outcome is frozen before any owned Action or reactive Hook runs, so reactions can observe but never alter it.
- An Action selects only its owner’s Hook Outcome and code—a passing Hook may request a pass Action even when a sibling blocks the Event, and delivery of an Effect can never weaken a block, patch, cancellation, or report.
Outcomes as effects
The Hook Evaluation Outcome is deeply immutable: a non-empty sequence of Event Outcomes (the Native Event first, then one Hook Result Event Outcome per originating result), ordered delivery-neutral Effects, and an optional Evaluation Report. The thin Pi adapter delivers Effects best-effort; Hook Evaluation and every delivery attempt complete before the observation closes, so Execution Duration covers the full callback-owned work. See Execution Reports for how this becomes a row in the transcript, and Events for the event-by-event outcome table.