Hooks API Reference
Complete type definitions for the hooks system in @helix-agents/core.
Installation
Hooks are included in the core package:
npm install @helix-agents/coreTypes
HookContext
Context passed to all hooks with execution info and capabilities.
interface HookContext<TState = unknown, TOutput = unknown> {
// Execution Info (read-only)
readonly runId: string;
readonly agentType: string;
readonly streamId: string;
readonly stepCount: number;
readonly parentAgentId?: string;
readonly abortSignal: AbortSignal;
// Session Context (read-only)
readonly sessionId?: string;
readonly userId?: string;
readonly tags?: string[];
readonly metadata?: Record<string, string>;
// State Methods
getState: () => TState;
updateState: (updater: (draft: Draft<TState>) => void) => void;
// Stream Methods
emit: (chunk: StreamChunk) => Promise<void>;
emitCustom: (eventName: string, data: unknown) => Promise<void>;
}AgentHooks
Interface defining all available hooks. All hooks are optional and can be sync or async.
interface AgentHooks<TState = unknown, TOutput = unknown> {
// Agent Lifecycle
onAgentStart?: (
payload: AgentStartPayload<TState, TOutput>,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
onAgentComplete?: (
payload: AgentCompletePayload<TState, TOutput>,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
onAgentFail?: (
payload: AgentFailPayload<TState>,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
// LLM Calls
beforeLLMCall?: (
payload: BeforeLLMCallPayload,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
afterLLMCall?: (
payload: AfterLLMCallPayload<TOutput>,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
// Tool Execution
beforeTool?: (
payload: BeforeToolPayload,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
afterTool?: (
payload: AfterToolPayload,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
// Sub-Agent Execution
beforeSubAgent?: (
payload: BeforeSubAgentPayload,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
afterSubAgent?: (
payload: AfterSubAgentPayload,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
// State & Messages
onStateChange?: (
payload: StateChangePayload<TState>,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
onMessage?: (
payload: MessagePayload,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
// Streaming (NOT YET IMPLEMENTED - reserved for future use)
onTextDelta?: (
delta: string,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
onThinking?: (
content: string,
isComplete: boolean,
context: HookContext<TState, TOutput>
) => void | Promise<void>;
}HookManager
Interface for managing hook registration and invocation.
interface HookManager<TState = unknown, TOutput = unknown> {
/** Register a hook handler */
register(hooks: AgentHooks<TState, TOutput>): void;
/** Unregister a specific hook handler */
unregister(hooks: AgentHooks<TState, TOutput>): void;
/** Invoke a hook with payload and context */
invoke<K extends keyof AgentHooks<TState, TOutput>>(
hookName: K,
...args: Parameters<NonNullable<AgentHooks<TState, TOutput>[K]>>
): Promise<void>;
/** Create a child manager that inherits parent hooks */
createChild(): HookManager<TState, TOutput>;
/** Check if any hooks are registered */
hasHooks(): boolean;
}Payload Types
AgentStartPayload
Data passed to onAgentStart.
interface AgentStartPayload<TState = unknown, TOutput = unknown> {
agent: AgentConfig<ZodType<TState>, ZodType<TOutput>>;
input: string;
initialState: TState;
}AgentCompletePayload
Data passed to onAgentComplete.
interface AgentCompletePayload<TState = unknown, TOutput = unknown> {
output: TOutput;
finalState: TState;
stepCount: number;
durationMs: number;
}AgentFailPayload
Data passed to onAgentFail.
interface AgentFailPayload<TState = unknown> {
error: Error;
finalState: TState;
stepCount: number;
durationMs: number;
recoverable: boolean;
}BeforeLLMCallPayload
Data passed to beforeLLMCall.
interface BeforeLLMCallPayload {
messages: Message[];
tools: Tool[];
config: AgentConfig<any, any>;
model?: string;
modelParameters?: ModelParameters;
}
interface ModelParameters {
temperature?: number;
maxOutputTokens?: number;
topP?: number;
topK?: number;
[key: string]: unknown;
}AfterLLMCallPayload
Data passed to afterLLMCall.
interface AfterLLMCallPayload<TOutput = unknown> {
result: StepResult<TOutput>;
plan: StepProcessingPlan<TOutput>;
durationMs: number;
model?: string;
usage?: TokenUsage;
}
interface TokenUsage {
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
reasoningTokens?: number;
cachedTokens?: number;
}BeforeToolPayload
Data passed to beforeTool.
interface BeforeToolPayload {
toolCall: ParsedToolCall;
tool: Tool;
}
interface ParsedToolCall {
id: string;
name: string;
arguments: Record<string, unknown>;
}AfterToolPayload
Data passed to afterTool.
interface AfterToolPayload {
toolCall: ParsedToolCall;
tool: Tool;
result: unknown;
success: boolean;
error?: Error;
durationMs: number;
}BeforeSubAgentPayload
Data passed to beforeSubAgent.
interface BeforeSubAgentPayload {
call: ParsedSubAgentCall;
agentConfig: AgentConfig<any, any>;
parentRunId: string;
}
interface ParsedSubAgentCall {
id: string;
agentType: string;
input: Record<string, unknown>;
}AfterSubAgentPayload
Data passed to afterSubAgent.
interface AfterSubAgentPayload {
call: ParsedSubAgentCall;
agentConfig: AgentConfig<any, any>;
result: unknown;
success: boolean;
error?: Error;
durationMs: number;
subAgentRunId: string;
}StateChangePayload
Data passed to onStateChange.
interface StateChangePayload<TState = unknown> {
previousState: TState;
newState: TState;
source: 'tool' | 'hook' | 'system';
toolName?: string;
}MessagePayload
Data passed to onMessage.
interface MessagePayload {
message: Message;
messageIndex: number;
}Factory Functions
createHookManager
Create a hook manager with optional initial hooks.
function createHookManager<TState = unknown, TOutput = unknown>(
initialHooks?: AgentHooks<TState, TOutput>
): HookManager<TState, TOutput>;Example:
import { createHookManager } from '@helix-agents/core';
const manager = createHookManager({
onAgentStart: (payload, ctx) => console.log('Starting:', ctx.runId),
afterTool: (payload, ctx) => console.log('Tool done:', payload.tool.name),
});composeHookManagers
Compose multiple hook sets into a single manager. Hooks run in order provided.
function composeHookManagers<TState = unknown, TOutput = unknown>(
...hooksList: (AgentHooks<TState, TOutput> | undefined)[]
): HookManager<TState, TOutput>;Example:
import { composeHookManagers } from '@helix-agents/core';
const manager = composeHookManagers(
createLoggingHooks(logger),
createMetricsHooks(metrics),
createAuditHooks(audit),
);mergeHooks
Merge agent-level and execution-level hooks. Agent hooks run first.
function mergeHooks<TState = unknown, TOutput = unknown>(
agentHooks?: AgentHooks<TState, TOutput>,
executionHooks?: AgentHooks<TState, TOutput>,
existingManager?: HookManager<TState, TOutput>
): HookManager<TState, TOutput>;Example:
import { mergeHooks } from '@helix-agents/core';
const manager = mergeHooks(
agent.hooks, // Run first
executionHooks, // Run second
);createHookContext
Create a HookContext from options. Used internally by runtimes.
function createHookContext<TState = unknown, TOutput = unknown>(
options: CreateHookContextOptions<TState, TOutput>
): HookContext<TState, TOutput>;
interface CreateHookContextOptions<TState = unknown, TOutput = unknown> {
runId: string;
agentType: string;
streamId: string;
stepCount: number;
parentAgentId?: string;
abortSignal: AbortSignal;
getState: () => TState;
updateState: (updater: (draft: Draft<TState>) => void) => void;
emit: (chunk: StreamChunk) => Promise<void>;
sessionId?: string;
userId?: string;
tags?: string[];
metadata?: Record<string, string>;
}Classes
DefaultHookManager
Primary hook manager implementation with parent-child support.
class DefaultHookManager<TState = unknown, TOutput = unknown>
implements HookManager<TState, TOutput>
{
constructor(parent?: HookManager<TState, TOutput>);
register(hooks: AgentHooks<TState, TOutput>): void;
unregister(hooks: AgentHooks<TState, TOutput>): void;
invoke<K extends keyof AgentHooks<TState, TOutput>>(
hookName: K,
...args: Parameters<NonNullable<AgentHooks<TState, TOutput>[K]>>
): Promise<void>;
createChild(): HookManager<TState, TOutput>;
hasHooks(): boolean;
}Example:
import { DefaultHookManager } from '@helix-agents/core';
const parent = new DefaultHookManager();
parent.register({ onAgentStart: () => console.log('Parent') });
const child = parent.createChild();
child.register({ onAgentStart: () => console.log('Child') });
// Invoking on child runs both: "Parent" then "Child"
await child.invoke('onAgentStart', payload, context);NoopHookManager
No-op implementation for when hooks are disabled. All methods are no-ops.
class NoopHookManager<TState = unknown, TOutput = unknown>
implements HookManager<TState, TOutput>
{
register(): void;
unregister(): void;
invoke(): Promise<void>;
createChild(): HookManager<TState, TOutput>;
hasHooks(): boolean; // Always returns false
}noopHookManager
Singleton no-op hook manager instance.
const noopHookManager: HookManager<any, any>;Example:
import { noopHookManager } from '@helix-agents/core';
// Use when no hooks are configured
const manager = agent.hooks ? createHookManager(agent.hooks) : noopHookManager;Usage with defineAgent
Register hooks at agent definition time:
import { defineAgent } from '@helix-agents/core';
import type { AgentHooks } from '@helix-agents/core';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
const StateSchema = z.object({ counter: z.number().default(0) });
const OutputSchema = z.object({ result: z.string() });
type State = z.infer<typeof StateSchema>;
type Output = z.infer<typeof OutputSchema>;
const hooks: AgentHooks<State, Output> = {
onAgentStart: (payload, ctx) => {
console.log(`Starting with state: ${payload.initialState.counter}`);
},
onAgentComplete: (payload, ctx) => {
console.log(`Output: ${payload.output.result}`);
},
};
const agent = defineAgent({
name: 'my-agent',
systemPrompt: 'You are helpful.',
stateSchema: StateSchema,
outputSchema: OutputSchema,
llmConfig: { model: openai('gpt-4o') },
hooks,
});Usage with execute
Pass hooks at execution time (where executor is a JSAgentExecutor or other runtime executor):
// Option 1: Pass individual hooks
const handle = await executor.execute(agent, 'Hello', {
hooks: {
afterTool: (payload, ctx) => {
metrics.recordToolCall(payload.tool.name, payload.durationMs);
},
},
sessionId: 'session-123',
userId: 'user-456',
});
// Option 2: Pass a pre-built hook manager
const handle = await executor.execute(agent, 'Hello', {
hookManager: myHookManager,
sessionId: 'session-123',
userId: 'user-456',
});Note: Typically use either hooks or hookManager, not both. If both are provided, hooks run in this order: (1) hooks already registered on hookManager, (2) agent-level hooks from defineAgent({ hooks }), (3) execution-time hooks passed here.
Exports
All hooks types and utilities are exported from @helix-agents/core:
import {
// Types
type Draft, // Re-exported from 'immer' - needed for updateState
type HookContext,
type AgentHooks,
type HookManager,
type CreateHookContextOptions,
type ModelParameters,
// Payload Types
type AgentStartPayload,
type AgentCompletePayload,
type AgentFailPayload,
type BeforeLLMCallPayload,
type AfterLLMCallPayload,
type BeforeToolPayload,
type AfterToolPayload,
type BeforeSubAgentPayload,
type AfterSubAgentPayload,
type StateChangePayload,
type MessagePayload,
// Classes
DefaultHookManager,
NoopHookManager,
// Factory Functions
createHookManager,
composeHookManagers,
mergeHooks,
createHookContext,
// Singleton
noopHookManager,
} from '@helix-agents/core';See Also
- Hooks Guide - User-facing documentation
- Hooks Internals - Architecture deep dive
- Tools Guide - Tool definition and context
- Streaming Guide - Stream chunk types