Skip to main content

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.
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

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 =
  | '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:
// 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

Hook Types

Transformation Hooks

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

filter_messages

Runs before messages are transformed into chat completion format.
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 before messages are sent to the LLM.
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.
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.
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.
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.
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.
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.
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.
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
// 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

HookTypeWhen It Runs
filter_messagesTransformBefore message transformation
prefilter_llm_historyTransformBefore sending to LLM
before_create_messageTransformBefore INSERT
before_update_messageTransformBefore UPDATE
before_store_tool_resultTransformBefore storing tool result
after_create_messageEventAfter INSERT
after_update_messageEventAfter UPDATE
after_tool_call_successTransformAfter successful tool
after_tool_call_failureTransformAfter failed tool