> ## Documentation Index
> Fetch the complete documentation index at: https://beige.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

> Intercept messages, tool calls, and lifecycle events

Plugins can register hooks to intercept and modify messages, tool calls, and lifecycle events.

## Available Hooks

| Hook              | When                                      | Can modify/block?            | Use cases                                     |
| ----------------- | ----------------------------------------- | ---------------------------- | --------------------------------------------- |
| `prePrompt`       | Before user message → LLM                 | Transform message, block it  | Content filtering, prompt injection detection |
| `postResponse`    | After LLM response, before delivery       | Transform response, block it | Content filtering, post-processing            |
| `preToolExec`     | Before a tool executes                    | Allow/deny                   | Policy enforcement, audit                     |
| `postToolExec`    | After a tool executes                     | Transform result             | Audit, caching                                |
| `sessionCreated`  | New session created                       | Observe only                 | Analytics, welcome messages                   |
| `sessionDisposed` | Session cleaned up                        | Observe only                 | Cleanup                                       |
| `modelSwitched`   | Model changed (fallback or user override) | Observe only                 | Notifications, analytics                      |
| `gatewayStarted`  | All plugins loaded + started              | Observe only                 | Health checks                                 |
| `gatewayShutdown` | Before gateway shuts down                 | Observe only                 | Cleanup                                       |

## Execution Order

Hooks execute **sequentially in plugin config order**. If you have:

```json5 theme={null}
plugins: {
  logger: { ... },      // registered first
  filter: { ... },      // registered second
}
```

Then `logger`'s hooks run before `filter`'s hooks.

## Hook Flow

### Channel plugins (Telegram, etc.)

For channels that run in-process with the gateway, hooks execute as part of the `AgentManager.prompt()` / `promptStreaming()` call:

```
User message arrives
  │
  ▼
prePrompt hooks (can transform/block)
  │
  ▼
LLM call with tool loop
  │ (for each tool call)
  ├── preToolExec hooks (can deny)
  ├── tool execution
  └── postToolExec hooks (can transform result)
  │
  ▼
postResponse hooks (can transform/block)
  │
  ▼
Response delivered to channel
```

### TUI (separate process)

For the TUI, LLM calls are proxied through the gateway's `/api/chat/stream` endpoint. Hooks execute **server-side in the gateway**, not in the TUI process:

```
User types message in TUI
  │
  ▼
TUI sends POST /api/chat/stream to gateway
  │
  ▼
Gateway: prePrompt hooks (can transform/block)
  │ (if blocked → sends "blocked" event → TUI aborts)
  ▼
Gateway: streams LLM call back to TUI (with fallback model logic)
  │
  ▼
Gateway: postResponse hooks (fire-and-forget, response already streamed)
  │
  ▼
TUI renders streamed response
```

> **Note:** preToolExec/postToolExec hooks run on tool calls made through the `AgentManager` path (channel plugins). For TUI tool calls (which go through `/api/agents/:name/exec`), tools execute directly in the sandbox — preToolExec/postToolExec hooks apply to plugin tool calls routed through the Unix socket, not to core tool execution.

## Registering Hooks

```typescript theme={null}
export function createPlugin(config, ctx) {
  return {
    register(reg) {
      // Content filter: block messages containing forbidden patterns
      reg.hook("prePrompt", async (event) => {
        for (const pattern of config.blockedPatterns) {
          if (event.message.includes(pattern)) {
            return { message: "", block: true, reason: `Blocked: contains "${pattern}"` };
          }
        }
        return { message: event.message };
      });

      // Audit: log every tool call
      reg.hook("preToolExec", async (event) => {
        console.log(`[AUDIT] ${event.agentName} calling ${event.toolName}`);
        return { allow: true };
      });

      // Post-processing: strip sensitive data from responses
      reg.hook("postResponse", async (event) => {
        const cleaned = event.response.replace(/\b\d{3}-\d{2}-\d{4}\b/g, "***-**-****");
        return { response: cleaned };
      });
    },
  };
}
```

## Hook Event Types

### prePrompt

```typescript theme={null}
interface PrePromptEvent {
  message: string;       // The user's message
  sessionKey: string;    // Session identifier
  agentName: string;     // Target agent
  channel: string;       // Which channel sent this
}

// Return: { message, block?, reason? }
```

### postResponse

```typescript theme={null}
interface PostResponseEvent {
  response: string;      // The LLM's response
  sessionKey: string;
  agentName: string;
  channel: string;
}

// Return: { response, block? }
```

### preToolExec / postToolExec

```typescript theme={null}
interface PreToolExecEvent {
  toolName: string;
  args: string[];
  sessionKey: string;
  agentName: string;
}

// preToolExec return: { allow, reason? }
// postToolExec return: { result? }
```

### Lifecycle hooks

```typescript theme={null}
interface SessionLifecycleEvent {
  sessionKey: string;
  agentName: string;
  channel: string;
}

// sessionCreated / sessionDisposed: return void

interface GatewayLifecycleEvent {}

// gatewayStarted / gatewayShutdown: return void
```

### modelSwitched

Fired when a session uses a different model than the primary — either due to automatic fallback (rate limit, error, timeout) or an explicit user override (e.g. `/model` command).

```typescript theme={null}
interface ModelSwitchedEvent {
  sessionKey: string;
  agentName: string;
  channel: string;
  previousModel: { provider: string; modelId: string };
  newModel: { provider: string; modelId: string };
  reason: "fallback_rate_limit" | "fallback_error" | "fallback_timeout" | "user_override";
}

// return void
```

Example: send a Telegram notification when a fallback occurs:

```typescript theme={null}
reg.hook("modelSwitched", async (event) => {
  if (event.reason === "user_override") return; // user chose this, no alert needed
  const from = `${event.previousModel.provider}/${event.previousModel.modelId}`;
  const to = `${event.newModel.provider}/${event.newModel.modelId}`;
  ctx.log.warn(`Model switched from ${from} to ${to} (${event.reason})`);
});
```
