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

# Hooks

> Intercept and modify agent execution at key lifecycle points

## What are Hooks?

Hooks are optional functions that run automatically at specific points during agent execution. They allow you to modify data, perform side effects, and integrate with external systems without modifying the framework code.

## How Hooks Work

Hooks are functions that receive execution context (ThreadState) and data, then either:

1. **Transform data** and return modified version (transformation hooks)
2. **Perform side effects** without returning anything (event hooks)

All hooks are wrapped in error handling - if a hook throws an error, it's logged but execution continues with original data.

## Defining Hooks

Hooks are defined using `defineHook` with a unique identifier:

```typescript theme={null}
import { defineHook } from '@standardagents/spec';

export default defineHook({
  hook: 'filter_messages',
  id: 'remove_failed_tools',
  execute: async (state, messages) => {
    return messages.filter(m => m.role !== 'tool' || m.tool_status !== 'error');
  },
});
```

Each hook requires three properties:

| Property  | Type       | Description                                    |
| --------- | ---------- | ---------------------------------------------- |
| `hook`    | `HookName` | The hook type (e.g., `'filter_messages'`)      |
| `id`      | `string`   | Unique identifier for this hook (snake\_case)  |
| `execute` | `Function` | The hook implementation, typed based on `hook` |

### Hook ID Requirements

* IDs **must** be unique across all hooks in the project
* IDs **must** be snake\_case (lowercase letters, numbers, underscores, starting with a letter)
* IDs **should** be descriptive of what the hook does

**Benefits of `defineHook`:**

* **Strict typing**: Parameters automatically typed based on hook type
* **IntelliSense support**: Full autocomplete in your editor
* **Type checking**: Catch errors at compile time

## Hook Scoping

Hooks must be explicitly declared on prompts or agents to execute. Undeclared hooks do not run.

### Prompt-Level Hooks

```typescript theme={null}
import { definePrompt } from '@standardagents/spec';

export default definePrompt({
  name: 'customer_support',
  // ... other config ...
  hooks: ['remove_failed_tools', 'log_tool_calls'],
});
```

Prompt hooks execute when that prompt is the active execution context.

### Agent-Level Hooks

```typescript theme={null}
import { defineAgent } from '@standardagents/spec';

export default defineAgent({
  name: 'support_agent',
  // ... other config ...
  hooks: ['inject_context', 'sanitize_output'],
});
```

Agent hooks are a fallback — they execute when the active prompt has no `hooks` defined.

### Resolution Priority

1. If the current prompt declares `hooks`, **only those hooks run**
2. If the prompt has no `hooks` but the agent does, **agent hooks run**
3. If neither declares hooks, **no hooks execute**

<Warning>
  This means hooks in `agents/hooks/` that aren't referenced by any prompt or agent will never run. Always add hook IDs to the relevant prompt or agent.
</Warning>

### Multiple Hooks of Same Type

You can define multiple hooks of the same hook type with different IDs:

```typescript theme={null}
// agents/hooks/limit_messages.ts
export default defineHook({
  hook: 'filter_messages',
  id: 'limit_to_20_messages',
  execute: async (state, messages) => messages.slice(-20),
});

// agents/hooks/remove_system.ts
export default defineHook({
  hook: 'filter_messages',
  id: 'remove_system_messages',
  execute: async (state, messages) => messages.filter(m => m.role !== 'system'),
});
```

When both are included in a prompt's `hooks` array, they execute in the order listed:

```typescript theme={null}
hooks: ['limit_to_20_messages', 'remove_system_messages']
```

## Hook Types

### Transformation Hooks

These hooks receive data, modify it, and return the modified version:

```typescript theme={null}
import { defineHook } from '@standardagents/spec';

export default defineHook({
  hook: 'filter_messages',
  id: 'keep_completed_only',
  execute: async (state, rows) => {
    return rows.filter(row => row.status === 'completed');
  },
});
```

**Available transformation hooks:**

* `after_system_message` - Modify the rendered system message for the current request
* `filter_messages` - Filter message rows before transformation
* `prefilter_llm_history` - Modify messages before sending to LLM
* `before_create_message` - Modify message before database insert
* `before_update_message` - Modify updates before applying
* `before_store_tool_result` - Modify tool result before storage
* `after_tool_call_success` - Modify or remove successful tool results
* `after_tool_call_failure` - Modify or remove failed tool results

### Event Hooks

These hooks run after an event occurs and don't return anything:

```typescript theme={null}
import { defineHook } from '@standardagents/spec';

export default defineHook({
  hook: 'after_create_message',
  id: 'log_message_creation',
  execute: async (state, message) => {
    console.log('Message created:', message.id);
  },
});
```

**Available event hooks:**

* `after_create_message` - After message inserted
* `after_update_message` - After message updated

## Common Use Cases

### Message Filtering

Filter out unwanted messages before they're sent to the LLM:

```typescript theme={null}
import { defineHook } from '@standardagents/spec';

export default defineHook({
  hook: 'filter_messages',
  id: 'remove_failed_tool_messages',
  execute: async (state, rows) => {
    return rows.filter(row => {
      if (row.role === 'tool' && row.tool_status === 'error') {
        return false;
      }
      return true;
    });
  },
});
```

### External Logging

Send events to external analytics services:

```typescript theme={null}
import { defineHook } from '@standardagents/spec';

export default defineHook({
  hook: 'after_create_message',
  id: 'send_analytics',
  execute: async (state, message) => {
    if (message.role === 'assistant') {
      await fetch('https://analytics.example.com/events', {
        method: 'POST',
        body: JSON.stringify({
          event: 'message_created',
          thread_id: state.threadId,
          message_id: message.id,
        }),
      });
    }
  },
});
```

### Tool Call Transformation

Convert tool calls to user messages:

```typescript theme={null}
import { defineHook } from '@standardagents/spec';
import { injectMessage } from '@standardagents/builder';

export default defineHook({
  hook: 'after_tool_call_success',
  id: 'convert_user_input_tool',
  execute: async (state, call, result) => {
    if (call.function.name === 'get_user_input') {
      await injectMessage(state, {
        role: 'user',
        content: result.result || '',
      });
      return null;
    }
    return result;
  },
});
```

### Message Enrichment

Add context to messages before storage:

```typescript theme={null}
import { defineHook } from '@standardagents/spec';

export default defineHook({
  hook: 'before_create_message',
  id: 'tag_assistant_messages',
  execute: async (state, message) => {
    if (message.role === 'assistant') {
      message.name = state.agentConfig.title;
    }
    return message;
  },
});
```

### Sanitize Tool Results

Clean sensitive data from tool results before storage:

```typescript theme={null}
import { defineHook } from '@standardagents/spec';

export default defineHook({
  hook: 'before_store_tool_result',
  id: 'sanitize_pii',
  execute: async (state, toolCall, toolResult) => {
    if (toolResult.result) {
      toolResult.result = toolResult.result.replace(/\b\d{3}-\d{2}-\d{4}\b/g, '***-**-****');
    }
    return toolResult;
  },
});
```

## Available Hooks Quick Reference

| Hook                       | Type      | When It Runs                            | Use Case                                   |
| -------------------------- | --------- | --------------------------------------- | ------------------------------------------ |
| `after_thread_created`     | Event     | After thread creation, before execution | Initialize values, files, env placeholders |
| `after_subagent_created`   | Event     | On parent after child thread creation   | Track child threads, copy metadata         |
| `after_system_message`     | Transform | After system message render             | Dynamic system instructions                |
| `filter_messages`          | Transform | Before SQL → Message conversion         | Filter/modify message rows                 |
| `prefilter_llm_history`    | Transform | Before sending to LLM                   | Limit context, add dynamic data            |
| `before_create_message`    | Transform | Before INSERT                           | Add metadata, modify content               |
| `before_update_message`    | Transform | Before UPDATE                           | Validate changes, add timestamps           |
| `before_store_tool_result` | Transform | Before storing tool result              | Sanitize results, add metadata             |
| `after_create_message`     | Event     | After INSERT                            | External logging, webhooks                 |
| `after_update_message`     | Event     | After UPDATE                            | Track status changes                       |
| `after_tool_call_success`  | Transform | After successful tool                   | Modify results, convert to messages        |
| `after_tool_call_failure`  | Transform | After failed tool                       | Enhance errors, suppress failures          |

## File Organization

Hooks are auto-discovered from the `agents/hooks/` directory:

```
agents/
└── hooks/
    ├── limit_messages.ts
    ├── remove_failed_tools.ts
    ├── log_analytics.ts
    └── sanitize_pii.ts
```

**Requirements:**

* File names can be anything (hook is identified by its `id`)
* Default export required
* Use `defineHook` for type safety
* Multiple hooks of the same type are supported (each with a unique `id`)

## Error Handling

All hooks are wrapped in error handling that:

1. **Catches exceptions** without breaking execution
2. **Logs errors** with `[Hooks] ✗` prefix
3. **Returns original data** as fallback

```typescript theme={null}
// Your hook throws an error
export default defineHook({
  hook: 'filter_messages',
  id: 'buggy_filter',
  execute: async (state, rows) => {
    throw new Error('Something went wrong!');
  },
});

// Framework behavior:
// 1. Catches error
// 2. Logs: [Hooks] ✗ Error running filter_messages hook: Something went wrong!
// 3. Returns original rows (unmodified)
// 4. Continues execution normally
```

<Tip>
  Always wrap risky operations (API calls, etc.) in try-catch blocks to handle errors gracefully.
</Tip>

## Best Practices

<AccordionGroup>
  <Accordion title="Keep Hooks Fast">
    Hooks run in the critical execution path. Keep them fast (\< 100ms ideal):

    ```typescript theme={null}
    // Good - quick operation
    export default defineHook({
      hook: 'filter_messages',
      id: 'quick_filter',
      execute: async (state, rows) => {
        return rows.filter(row => row.status === 'completed');
      },
    });

    // Avoid - slow operation
    export default defineHook({
      hook: 'filter_messages',
      id: 'slow_filter',
      execute: async (state, rows) => {
        for (const row of rows) {
          await slowExternalAPI(row);
        }
        return rows;
      },
    });
    ```
  </Accordion>

  <Accordion title="Handle Errors Gracefully">
    Wrap risky operations in try-catch:

    ```typescript theme={null}
    export default defineHook({
      hook: 'after_create_message',
      id: 'safe_analytics',
      execute: async (state, message) => {
        try {
          await fetch('https://api.example.com/log', {
            method: 'POST',
            body: JSON.stringify(message),
          });
        } catch (error) {
          console.error('[Hook] External API failed:', error);
        }
      },
    });
    ```
  </Accordion>

  <Accordion title="Validate Input Data">
    Check data structure before processing:

    ```typescript theme={null}
    export default defineHook({
      hook: 'before_create_message',
      id: 'trim_content',
      execute: async (state, message) => {
        if (!message || !message.content) {
          return message;
        }
        message.content = message.content.trim();
        return message;
      },
    });
    ```
  </Accordion>

  <Accordion title="Scope Hooks Intentionally">
    Only add hooks to prompts/agents that need them:

    ```typescript theme={null}
    // Prompt that needs message filtering
    definePrompt({
      name: 'support_prompt',
      hooks: ['limit_to_20_messages', 'remove_failed_tools'],
      // ...
    });

    // Prompt that doesn't need hooks — leave hooks undefined
    definePrompt({
      name: 'simple_responder',
      // No hooks property — no hooks run
      // ...
    });
    ```
  </Accordion>
</AccordionGroup>

## ThreadState Context

All hooks receive a `ThreadState` object containing execution context:

```typescript theme={null}
interface ThreadState {
  // Identity (readonly)
  readonly threadId: string;
  readonly agentId: string;
  readonly userId: string | null;
  readonly createdAt: number;
  readonly arguments: Record<string, unknown>;

  // Execution State (null at rest)
  execution: ExecutionState | null;

  // Context Storage (current execution only)
  context: Record<string, unknown>;

  // Methods
  getMessages(options?): Promise<MessagesResult>;
  injectMessage(input): Promise<Message>;
  queueTool(toolName, args): void;
  emit(event, data): void;
  // ... and more
}
```

Use ThreadState to:

* Check which agent is executing (`state.agentId`)
* Read invocation or subagent creation arguments (`state.arguments`)
* Access execution state (`state.execution?.stepCount`)
* Get/inject messages
* Emit events to frontend
* Access the file system

## Next Steps

<CardGroup cols={2}>
  <Card title="Hooks API Reference" icon="code" href="/api-reference/hooks">
    Complete hooks specification
  </Card>

  <Card title="ThreadState" icon="diagram-project" href="/core-concepts/flowstate">
    Learn about ThreadState interface
  </Card>

  <Card title="Tools" icon="wrench" href="/core-concepts/tools">
    Create custom tools
  </Card>

  <Card title="Examples" icon="sparkles" href="/examples">
    See hooks in action
  </Card>
</CardGroup>
