Skip to content

Hooks API Reference

Complete type definitions for the hooks system in @helix-agents/core.

Installation

Hooks are included in the core package:

bash
npm install @helix-agents/core

Types

HookContext

Context passed to all hooks with execution info and capabilities.

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

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

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

typescript
interface AgentStartPayload<TState = unknown, TOutput = unknown> {
  agent: AgentConfig<ZodType<TState>, ZodType<TOutput>>;
  input: string;
  initialState: TState;
}

AgentCompletePayload

Data passed to onAgentComplete.

typescript
interface AgentCompletePayload<TState = unknown, TOutput = unknown> {
  output: TOutput;
  finalState: TState;
  stepCount: number;
  durationMs: number;
}

AgentFailPayload

Data passed to onAgentFail.

typescript
interface AgentFailPayload<TState = unknown> {
  error: Error;
  finalState: TState;
  stepCount: number;
  durationMs: number;
  recoverable: boolean;
}

BeforeLLMCallPayload

Data passed to beforeLLMCall.

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

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

typescript
interface BeforeToolPayload {
  toolCall: ParsedToolCall;
  tool: Tool;
}

interface ParsedToolCall {
  id: string;
  name: string;
  arguments: Record<string, unknown>;
}

AfterToolPayload

Data passed to afterTool.

typescript
interface AfterToolPayload {
  toolCall: ParsedToolCall;
  tool: Tool;
  result: unknown;
  success: boolean;
  error?: Error;
  durationMs: number;
}

BeforeSubAgentPayload

Data passed to beforeSubAgent.

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

typescript
interface AfterSubAgentPayload {
  call: ParsedSubAgentCall;
  agentConfig: AgentConfig<any, any>;
  result: unknown;
  success: boolean;
  error?: Error;
  durationMs: number;
  subAgentRunId: string;
}

StateChangePayload

Data passed to onStateChange.

typescript
interface StateChangePayload<TState = unknown> {
  previousState: TState;
  newState: TState;
  source: 'tool' | 'hook' | 'system';
  toolName?: string;
}

MessagePayload

Data passed to onMessage.

typescript
interface MessagePayload {
  message: Message;
  messageIndex: number;
}

Factory Functions

createHookManager

Create a hook manager with optional initial hooks.

typescript
function createHookManager<TState = unknown, TOutput = unknown>(
  initialHooks?: AgentHooks<TState, TOutput>
): HookManager<TState, TOutput>;

Example:

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

typescript
function composeHookManagers<TState = unknown, TOutput = unknown>(
  ...hooksList: (AgentHooks<TState, TOutput> | undefined)[]
): HookManager<TState, TOutput>;

Example:

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

typescript
function mergeHooks<TState = unknown, TOutput = unknown>(
  agentHooks?: AgentHooks<TState, TOutput>,
  executionHooks?: AgentHooks<TState, TOutput>,
  existingManager?: HookManager<TState, TOutput>
): HookManager<TState, TOutput>;

Example:

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

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

typescript
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:

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

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

typescript
const noopHookManager: HookManager<any, any>;

Example:

typescript
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:

typescript
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):

typescript
// 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:

typescript
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

Released under the MIT License.