# Lifecycle Hooks (`hooks.json`)

Lifecycle hooks allow you to execute external shell commands or scripts at
specific points during the Agent's execution loop. This is powerful for
enforcing safety guards, running linters, auto-formatting code, or capturing
custom diagnostics.

Hooks are configured in a single `hooks.json` file placed in your customization
root directory (e.g., `.agents/hooks.json`).

--------------------------------------------------------------------------------

## File Format

The `hooks.json` file is a JSON object where each top-level key is a **hook
name**, mapping to its event configuration.

```json
{
  "lint-checker": {
    "PostToolUse": [
      {
        "matcher": "run_command",
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/lint.sh",
            "timeout": 10
          }
        ]
      }
    ]
  },
  "safety-gate": {
    "enabled": false,
    "PreToolUse": [
      {
        "matcher": "run_command",
        "hooks": [
          {
            "command": "./scripts/safety-check.sh"
          }
        ]
      }
    ]
  },
  "reminder": {
    "PreInvocation": [
      {
        "type": "command",
        "command": "./scripts/reminder.sh"
      }
    ]
  }
}
```

*   **Merging**: Multiple named hooks (e.g., from different plugins or configs)
    for the same event type are merged and executed sequentially.
*   **Disabling**: Set `"enabled": false` at the hook level to temporarily
    disable all its handlers.

--------------------------------------------------------------------------------

## Hook Spec Fields

Each named hook supports:

*   **`enabled`** (bool, optional): Defaults to `true`. Set to `false` to
    disable.
*   **`PreToolUse`** (array, optional): Handlers running before a tool executes.
*   **`PostToolUse`** (array, optional): Handlers running after a tool
    completes.
*   **`PreInvocation`** (array, optional): Handlers running before the model is
    called.
*   **`PostInvocation`** (array, optional): Handlers running after tool calls
    finish.
*   **`Stop`** (array, optional): Handlers running when the execution loop
    terminates.

--------------------------------------------------------------------------------

## Supported Event Types

| Event            | When it fires     | Matcher target   | Structure         |
| :--------------- | :---------------- | :--------------- | :---------------- |
| `PreToolUse`     | Before a tool     | Tool name (e.g., | Grouped (uses     |
:                  : step executes.    : `run_command`).  : `matcher` &       :
:                  :                   :                  : `hooks` wrapper). :
| `PostToolUse`    | After a tool step | Tool name (e.g., | Grouped (uses     |
:                  : completes.        : `run_command`).  : `matcher` &       :
:                  :                   :                  : `hooks` wrapper). :
| `PreInvocation`  | Before the model  | N/A (ignored).   | Flat (list of     |
:                  : is called.        :                  : handler objects   :
:                  :                   :                  : directly).        :
| `PostInvocation` | After tool calls  | N/A (ignored).   | Flat (list of     |
:                  : finish.           :                  : handler objects   :
:                  :                   :                  : directly).        :
| `Stop`           | When the          | N/A (ignored).   | Flat (list of     |
:                  : execution loop    :                  : handler objects   :
:                  : terminates.       :                  : directly).        :

### The Matcher

For tool-specific events (`PreToolUse`, `PostToolUse`), you must wrap the
handlers in a group with a `matcher` regex:

*   `"matcher": "*"` or `""`: Matches all tools.
*   `"matcher": "run_command"`: Matches exactly `run_command`.
*   `"matcher": "run_command\|view_file"`: Matches either tool.
*   `"matcher": "browser_.*"`: Matches any tool starting with `browser_`.

Tool names are derived by lowercasing the step type and removing the
`CORTEX_STEP_TYPE_` prefix.

--------------------------------------------------------------------------------

## Hook Handler Fields

Each individual hook handler object supports:

*   **`type`** (string, optional): Defaults to `"command"`. Only `"command"`
    (shell execution) is currently supported.
*   **`command`** (string, required): The shell command to execute (run via `sh
    -c` on Unix, `cmd /c` on Windows). `~` is expanded to the home directory.
    The working directory is set to the directory containing `hooks.json`.
*   **`timeout`** (int, optional): Execution timeout in seconds. Defaults to
    `30`.

--------------------------------------------------------------------------------

## Input/Output Contract

Hook commands receive context as a JSON object on **stdin** and must output
their result as a JSON object on **stdout**.

> [!IMPORTANT] All JSON keys in the hook payloads use **camelCase** (protojson
> encoding), e.g., `conversationId` and `stepIdx`.

### Common Input Fields

Every hook payload sent to `stdin` includes these common system metadata fields:

```json
{
  "conversationId": "ec33ebf9-0cba-4100-8142-c61503f6c587",
  "workspacePaths": ["/path/to/workspace"],
  "transcriptPath": "/path/to/workspace/.gemini/antigravity/transcript.jsonl",
  "artifactDirectoryPath": "/path/to/workspace/.gemini/antigravity/artifacts",
  "modelName": "auto"
}

> [!NOTE]
> The `transcriptPath` and `artifactDirectoryPath` are product-specific. The example above uses `antigravity` (for Antigravity 2.0). Depending on the interface you are using, the directory name will differ:
*   **CLI**: `antigravity-cli/`
*   **Antigravity 2.0**: `antigravity/`
*   **IDE**: `antigravity-ide/`
```

--------------------------------------------------------------------------------

### 1. `PreToolUse` Contract

Use to gate, block, or audit tool executions.

*   **Input (stdin)**:

    ```json
    {
      "toolCall": {
        "name": "run_command",
        "args": {
          "CommandLine": "npm test"
        }
      },
      "stepIdx": 19,
      ... (common fields)
    }
    ```

*   **Output (stdout)**:

    ```json
    {
      "decision": "ask",
      "reason": "Requires confirmation for test execution.",
      "permissionOverrides": ["command(npm test)"]
    }
    ```

    *   **`decision`** (string, required):
        *   `"allow"`: Automatically allow the tool execution.
        *   `"deny"`: Hard block the execution immediately.
        *   `"ask"`: Prompt the user for permission (respects "Always Allow"
            cache).
        *   `"force_ask"`: Always prompt the user, ignoring cached permissions.
    *   **`reason`** (string, optional): Explanation shown to the user/agent.
    *   **`permissionOverrides`** (array of strings, optional): Temporary
        permission grants.

--------------------------------------------------------------------------------

### 2. `PostToolUse` Contract

Use for post-execution cleanup, auto-fixes, or analysis.

*   **Input (stdin)**:

    ```json
    {
      "stepIdx": 5,
      "error": "exit status 1", // Present if the tool failed
      ... (common fields)
    }
    ```

*   **Output (stdout)**: Expects an empty JSON object `{}`.

--------------------------------------------------------------------------------

### 3. `PreInvocation` Contract

Use to inject context or instructions before the model runs.

*   **Input (stdin)**:

    ```json
    {
      "invocationNum": 3,
      "initialNumSteps": 10,
      ... (common fields)
    }
    ```

*   **Output (stdout)**:

    ```json
    {
      "injectSteps": [
        {
          "ephemeralMessage": "Remember to check for lint errors before proposing changes."
        }
      ]
    }
    ```

    *   **`injectSteps`** (array of objects, optional): Steps to inject.
        Supported step types:
        *   `{"toolCall": {"name": "...", "args": {...}}}`
        *   `{"userMessage": "..."}`
        *   `{"ephemeralMessage": "..."}` (transient system message)

--------------------------------------------------------------------------------

### 4. `PostInvocation` Contract

Use to inspect model outputs and potentially force continuation.

*   **Input (stdin)**: Same as `PreInvocation` input.
*   **Output (stdout)**:

    ```json
    {
      "injectSteps": [],
      "terminationBehavior": "force_continue"
    }
    ```

    *   **`injectSteps`** (array, optional): Steps to inject.
    *   **`terminationBehavior`** (string, optional):
        *   `"force_continue"`: Forces the execution loop to continue.
        *   `"terminate"`: Forces the loop to stop.
        *   `""` (or omitted): Default behavior.

--------------------------------------------------------------------------------

### 5. `Stop` Contract

Use to prevent the agent from stopping if goals are not met.

*   **Input (stdin)**:

    ```json
    {
      "executionNum": 1,
      "terminationReason": "model_stop", // e.g., "model_stop", "max_steps_exceeded", "error"
      "error": "", // Present if stopped due to error
      "fullyIdle": true, // true if all background tasks are done
      ... (common fields)
    }
    ```

*   **Output (stdout)**:

    ```json
    {
      "decision": "continue",
      "reason": "The tests are still running in the background. Please wait."
    }
    ```

    *   **`decision`** (string, required): Set to `"continue"` to block the stop
        and re-enter the loop. Any other value allows the agent to stop.
    *   **`reason`** (string, optional): Injected as a system message if
        continuing.

--------------------------------------------------------------------------------

## Current Limitations

*   Only `type: "command"` is supported (no HTTP or prompt hooks yet).
*   Hooks run synchronously and block the agent loop (no async execution).
*   `overwrite` in `PreToolUse` is not yet implemented.
