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

# action

Owned Action variants, selectors, and delivery.

A Hook may **own** one declarative Pi **Action**. Every Action requires
`outcome`; optional `code` narrows it further. The owned Action is selected
against its owner's immutable **Hook Outcome and code**, never the aggregate
Event Outcome. Selection waits until all matching native shells finish and
the aggregate Native Event Outcome freezes; then each originating result's
Action is considered before its Hook Result Event is evaluated.

## Common fields and selectors

| Field     | Type                                         | Requirement | Default | Applies to   | Constraints                                                                                                   | Behavior                                    | Example              |
| --------- | -------------------------------------------- | ----------- | ------- | ------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -------------------- |
| `type`    | string enum                                  | required    | —       | every Action | `interrupt`, `shutdown`, `compact`, `message`, or `emit-custom-event`                                         | Selects exactly one payload shape.          | `"message"`          |
| `outcome` | outcome or non-empty outcome array           | required    | —       | every Action | Values must be possible for the owning Hook's Event: `pass` plus its event-specific failure kind.             | Matches the owner's Hook Outcome.           | `["pass", "report"]` |
| `code`    | number, `null`, or non-empty array of either | optional    | —       | every Action | When present, it must have a possible pairing with `outcome`: pass uses `0`; failure uses non-zero or `null`. | Narrows selection to the owner's exit code. | `[1, null]`          |

Selector fields are **ANDed**; array members are **any-of**; matching is
strict. Validation rejects impossible Event outcomes and selectors with no
possible outcome/code pairing. `outcome` and `code` are removed when the
selected Action becomes an Action Request Effect.

## Variant payload fields

| Field          | Type           | Requirement | Default  | Applies to            | Constraints                                                                     | Behavior                                                                               | Example                         |
| -------------- | -------------- | ----------- | -------- | --------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------- |
| `interrupt`    | boolean        | optional    | `false`  | `shutdown`            | —                                                                               | Requests an interrupt before graceful shutdown.                                        | `true`                          |
| `instructions` | string         | optional    | —        | `compact`             | Static text.                                                                    | Passes instructions to Pi compaction.                                                  | `"Preserve policy decisions."`  |
| `message`      | string         | required    | —        | `message`             | Must contain at least one non-whitespace character.                             | Supplies the original, untrimmed message text Pi should deliver.                       | `"A policy blocked this call."` |
| `sendAs`       | string enum    | optional    | `custom` | `message`             | `custom` or `user`                                                              | Chooses custom-context or genuine user-message delivery. Omission behaves as `custom`. | `"user"`                        |
| `delivery`     | string enum    | required    | —        | `message`             | Custom: `steer`, `followUp`, or `nextTurn`. User: `steer` or `followUp`.        | Chooses Pi's message queue position.                                                   | `"followUp"`                    |
| `triggerTurn`  | boolean        | optional    | `false`  | custom `message` only | Forbidden with `sendAs: "user"`; `true` is invalid with `delivery: "nextTurn"`. | Controls whether custom delivery starts an idle agent.                                 | `true`                          |
| `name`         | string         | required    | —        | `emit-custom-event`   | Must contain at least one non-whitespace character.                             | Names the custom Pi Event.                                                             | `"audit:blocked"`               |
| `data`         | any JSON value | optional    | —        | `emit-custom-event`   | Static JSON data.                                                               | Supplies the custom Event payload.                                                     | `{ "source": "HooKit" }`        |

Every variant rejects fields outside its own shape. Action strings and `data`
are static: HooKit does not interpolate environment variables, shell output,
or Event fields. Pi may process genuine user input as described below.

## Supported variants

| Action              | Required fields                          | Behavior                                                | Small valid Action                                                                         |
| ------------------- | ---------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `interrupt`         | `type`, `outcome`                        | Requests Pi's supported interrupt operation.            | `{ "type": "interrupt", "outcome": "block" }`                                              |
| `shutdown`          | `type`, `outcome`                        | Optionally interrupts, then requests graceful shutdown. | `{ "type": "shutdown", "outcome": "report", "interrupt": true }`                           |
| `compact`           | `type`, `outcome`                        | Requests Pi compaction with optional instructions.      | `{ "type": "compact", "outcome": "pass", "instructions": "Preserve policy decisions." }`   |
| `message`           | `type`, `outcome`, `message`, `delivery` | Requests one static custom or user message.             | `{ "type": "message", "outcome": "block", "message": "Blocked.", "delivery": "followUp" }` |
| `emit-custom-event` | `type`, `outcome`, `name`                | Requests one named custom Event with optional data.     | `{ "type": "emit-custom-event", "outcome": "pass", "name": "audit:complete" }`             |

## Message delivery

| `sendAs`            | Pi delivery                                                 | Supported `delivery`            | Continuation                                                                           |
| ------------------- | ----------------------------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------- |
| omitted or `custom` | A HooKit custom message that participates in model context. | `steer`, `followUp`, `nextTurn` | `triggerTurn` controls only whether an idle agent starts.                              |
| `user`              | A genuine Pi user message retained as user-role history.    | `steer`, `followUp`             | Always starts an idle agent or continues active execution. `triggerTurn` is forbidden. |

For an active agent, custom `steer` and `followUp` delivery requests active
continuation regardless of the configured `triggerTurn`. This keeps the
selected queue position meaningful: `steer` reaches the model after the
current assistant tool batch and before the next model call; `followUp` waits
until the existing tool chain settles. This is batch timing, not a promise to
interrupt or follow one parallel tool completion immediately.

For an idle agent, custom delivery honors `triggerTurn`, which defaults to
`false`. Custom `nextTurn` is always deferred until a later external user
prompt and never requests immediate work.

A user message passes through Pi's normal programmatic input path with input
expansion enabled. Other extensions receive the `input` Event with extension
provenance and may transform or handle it. If it continues, Pi can dispatch an
extension command, expand a `/skill:name` command, or expand a prompt template.
HooKit does not maintain an allowlist, preflight command availability, or
reimplement discovery. Unknown commands and missing Skills remain literal user
text according to Pi behavior.

A recognized extension command is dispatched before Pi applies the streaming
queue selection, so it may execute immediately even when the Action configures
`delivery: "followUp"`. The selected delivery governs input that becomes a
queued message. Interactive-only built-in commands, such as commands Pi handles only
in the TUI, are not exposed by `sendUserMessage`; HooKit does not make them
executable. They remain subject to Pi's literal-input behavior.

## A custom block message

This Hook blocks a `.env` write and asks Pi to deliver custom follow-up context:

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

```json
{
  "local": {
    "protect-env": {
      "description": "Block writes to dotenv files",
      "event": "tool_call",
      "filter": { "toolName": "^write$", "path": "(^|/)\\.env$" },
      "shell": "false",
      "action": {
        "type": "message",
        "outcome": "block",
        "code": 1,
        "message": "A dotenv write was blocked by HooKit.",
        "delivery": "followUp",
        "sendAs": "custom"
      }
    }
  }
}
```

The `outcome: "block"` and `code: 1` select the owner's result. The static
message is additive to the authoritative block reason; it does not replace it.

## A user message with Pi input processing

This Hook sends user-directed work after a passing tool result. Pi receives the
leading Skill command unchanged and performs any supported expansion:

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

```json
{
  "local": {
    "review-after-tool": {
      "description": "Ask the review Skill to inspect a completed tool result",
      "event": "tool_result",
      "action": {
        "type": "message",
        "outcome": "pass",
        "message": "/skill:review focus on safety",
        "delivery": "steer",
        "sendAs": "user"
      }
    }
  }
}
```

HooKit adds no attribution prefix, so leading command syntax remains intact.
The resulting user message is retained as genuine user-role session history.
The nearby Execution Report preserves bounded HooKit provenance by recording a
`message` Action row without storing the message body, delivery mode, or
expanded content.

## Invalid message combinations

A user message cannot use `nextTurn` or configure `triggerTurn`:

<!-- docs-example:invalid -->

```json
{
  "local": {
    "invalid-user-message": {
      "description": "Contradictory user continuation settings",
      "event": "agent_settled",
      "action": {
        "type": "message",
        "outcome": "pass",
        "message": "Continue",
        "delivery": "nextTurn",
        "sendAs": "user",
        "triggerTurn": false
      }
    }
  }
}
```

A custom `nextTurn` message cannot request an immediate turn:

<!-- docs-example:invalid -->

```json
{
  "local": {
    "invalid-custom-message": {
      "description": "Contradictory custom continuation settings",
      "event": "agent_settled",
      "action": {
        "type": "message",
        "outcome": "pass",
        "message": "Continue later",
        "delivery": "nextTurn",
        "sendAs": "custom",
        "triggerTurn": true
      }
    }
  }
}
```

## Selection is separated from delivery

HooKit freezes the Native Event Outcome before Actions are considered, and
Actions are returned as immutable delivery-neutral **Effects** in
deterministic, result-major order. The Pi adapter delivers them best-effort:
**a delivery failure cannot weaken an Event Outcome**. A block, patch,
cancellation, or report stands even if an Effect fails to deliver. Multiple
message Actions remain separate ordered Effects; Pi owns any downstream
steering or follow-up batching.

## Continuation caution

Message, compact, interrupt, and shutdown Actions can cause later Pi Events.
Generated user work can call more tools, producing independent Hook
Evaluations that may select the same Action again. HooKit adds no loop guard,
cooldown, or message deduplication; use narrow Filters, Preconditions, shells,
or external state to make continuation self-limiting.

In particular, a genuine user message always starts or continues execution. An
unconditional `sendAs: "user"` Action on `agent_settled` creates an endless
continuation loop unless a Precondition, shell, Filter, or external state
eventually prevents selection. A `pass`-only Hook that omits `shell` is still
an optimized canonical `"true"` Hook and emits an ordinary `hook_result`.
