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

# defineHook

> Define lifecycle hooks for agent execution

## Overview

`defineHook` creates a hook that runs at specific points during agent execution. Each hook has a unique `id` and must be explicitly referenced by a prompt or agent to execute.

```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);
  },
});
```

## Type Definition

```typescript theme={null}
function defineHook<K extends HookName>(
  options: HookDefinitionOptions<K>
): HookDefinitionResult<K>;

interface HookDefinitionOptions<K extends HookName> {
  hook: K;
  id: string;
  execute: HookSignatures[K];
}

interface HookDefinitionResult<K extends HookName> {
  hook: K;
  id: string;
  execute: HookSignatures[K];
}

type HookName =
  | 'after_thread_created'
  | 'after_subagent_created'
  | 'after_system_message'
  | 'filter_messages'
  | 'prefilter_llm_history'
  | 'before_create_message'
  | 'before_update_message'
  | 'after_create_message'
  | 'after_update_message'
  | 'before_store_tool_result'
  | 'after_tool_call_success'
  | 'after_tool_call_failure';
```

## Parameters

**hook** `HookName` *required*

The hook type. Determines when the hook runs and what parameters `execute` receives.

**id** `string` *required*

Unique identifier for this hook. Must be snake\_case (lowercase letters, numbers, underscores, starting with a letter). Referenced by prompts and agents in their `hooks` array.

**execute** `Function` *required*

Async function that executes when the hook is triggered. Parameters are automatically typed based on the `hook` type.

## Hook Scoping

Hooks must be referenced by a prompt or agent to execute:

```typescript theme={null}
// Prompt references hooks by ID
definePrompt({
  name: 'customer_support',
  hooks: ['log_message_creation', 'limit_to_20_messages'],
  // ...
});

// Agent can define default hooks (used when prompt has none)
defineAgent({
  name: 'support_agent',
  hooks: ['inject_context'],
  // ...
});
```

**Resolution priority:**

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

At-rest operations have no active prompt and use agent-level hooks.

## Hook Types

### Transformation Hooks

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

#### filter\_messages

Runs exactly once per model request on a stable snapshot of eligible persisted
history. Its result is request-local and does not replace
`state.execution.messageHistory`. Messages injected while it runs enter the
following snapshot.

```typescript theme={null}
defineHook({
  hook: 'filter_messages',
  id: 'keep_completed',
  execute: async (state, rows) => {
    return rows.filter(row => row.status === 'completed');
  },
});
```

**state** `ThreadState`

Current execution context

**rows** `Message[]`

Messages from storage

**Returns:** `Message[]` - Filtered/modified messages

#### prefilter\_llm\_history

Runs after provider-message transformation and before dispatch. Its changes are
request-local. Messages injected while it runs enter the following snapshot.

```typescript theme={null}
defineHook({
  hook: 'prefilter_llm_history',
  id: 'add_dynamic_context',
  execute: async (state, messages) => {
    return [
      { role: 'system', content: `Current time: ${new Date().toISOString()}` },
      ...messages,
    ];
  },
});
```

**state** `ThreadState`

Current execution context

**messages** `LLMMessage[]`

Messages about to be sent to LLM

**Returns:** `LLMMessage[]` - Modified messages

#### before\_create\_message

Runs before a message is inserted into the database.

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

**state** `ThreadState`

Current execution context

**message** `Record\<string, unknown\>`

Message about to be created

**Returns:** `Record<string, unknown>` - Modified message

#### before\_update\_message

Runs before a message is updated in the database.

```typescript theme={null}
defineHook({
  hook: 'before_update_message',
  id: 'add_updated_at',
  execute: async (state, messageId, updates) => {
    return {
      ...updates,
      updated_at: Date.now(),
    };
  },
});
```

**state** `ThreadState`

Current execution context

**messageId** `string`

ID of message being updated

**updates** `Record\<string, unknown\>`

Updates being applied

**Returns:** `Record<string, unknown>` - Modified updates

#### before\_store\_tool\_result

Runs before a tool result is stored in the database.

```typescript theme={null}
defineHook({
  hook: 'before_store_tool_result',
  id: 'sanitize_results',
  execute: async (state, toolCall, toolResult) => {
    return { ...toolResult, sanitized: true };
  },
});
```

**state** `ThreadState`

Current execution context

**toolCall** `Record\<string, unknown\>`

The tool call that was executed

**toolResult** `Record\<string, unknown\>`

The result to be stored

**Returns:** `Record<string, unknown>` - Modified tool result

#### after\_tool\_call\_success

Runs after a tool executes successfully. Can modify the result or convert to a different message type.

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

defineHook({
  hook: 'after_tool_call_success',
  id: 'convert_user_input',
  execute: async (state, call, result) => {
    if (call.function.name === 'get_user_input') {
      await injectMessage(state, {
        role: 'user',
        content: result.result || '',
      });
      return null; // Remove tool call from history
    }
    return result;
  },
});
```

**state** `ThreadState`

Current execution context

**call** `ToolCall`

The tool call that was executed

**result** `ToolResult`

Result from the tool

**Returns:** `ToolResult | null` - Modified result or null to remove

#### after\_tool\_call\_failure

Runs after a tool fails. Can modify the error or suppress the failure.

```typescript theme={null}
defineHook({
  hook: 'after_tool_call_failure',
  id: 'friendly_errors',
  execute: async (state, call, error) => {
    return {
      status: 'error',
      error: `The ${call.function.name} operation is temporarily unavailable.`,
    };
  },
});
```

**state** `ThreadState`

Current execution context

**call** `ToolCall`

The tool call that failed

**error** `ToolResult`

The error result

**Returns:** `ToolResult | null` - Modified error result or null to remove

### Event Hooks

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

#### after\_create\_message

Runs after a message is inserted. Use for logging, analytics, or webhooks.

```typescript theme={null}
defineHook({
  hook: 'after_create_message',
  id: 'log_messages',
  execute: async (state, message) => {
    await fetch('https://analytics.example.com/events', {
      method: 'POST',
      body: JSON.stringify({
        event: 'message_created',
        thread_id: state.threadId,
        role: message.role,
      }),
    });
  },
});
```

**state** `ThreadState`

Current execution context

**message** `Record\<string, unknown\>`

The created message

**Returns:** `void`

#### after\_update\_message

Runs after a message is updated. Use to track status changes.

```typescript theme={null}
defineHook({
  hook: 'after_update_message',
  id: 'track_updates',
  execute: async (state, message) => {
    console.log(`Message ${message.id} updated`);
  },
});
```

**state** `ThreadState`

Current execution context

**message** `Message`

The updated message

**Returns:** `void`

## Error Handling

All hooks are wrapped in error handling:

1. If a hook throws, the error is logged
2. Execution continues with original data
3. The framework doesn't crash

```typescript theme={null}
// This hook throws, but execution continues
defineHook({
  hook: 'filter_messages',
  id: 'buggy_filter',
  execute: async (state, rows) => {
    throw new Error('Something went wrong!');
  },
});

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

## File Location

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

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

**Requirements:**

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

## Quick Reference

| Hook | Type | When It Runs |
| - | - | - |
| `after_thread_created` | Event | After thread creation, before execution |
| `after_subagent_created` | Event | On parent after subagent thread creation |
| `after_system_message` | Transform | After system message render |
| `filter_messages` | Transform | Before message transformation |
| `prefilter_llm_history` | Transform | Before sending to LLM |
| `before_create_message` | Transform | Before INSERT |
| `before_update_message` | Transform | Before UPDATE |
| `before_store_tool_result` | Transform | Before storing tool result |
| `after_create_message` | Event | After INSERT |
| `after_update_message` | Event | After UPDATE |
| `after_tool_call_success` | Transform | After successful tool |
| `after_tool_call_failure` | Transform | After failed tool |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.