Skip to main content

Observability

4 min read

Breakpoints & Checkpoints

Pause agent execution at key points for inspection, and save/restore full orchestrator state with checkpoints.


Breakpoints

Breakpoints pause execution at specific lifecycle points so you can inspect state, modify input, or skip steps entirely.

Breakpoint Types

Single-agent types:

TypeWhen it fires
pre_input_guardrailsBefore input guardrails run
pre_agent_runBefore the agent executes
pre_output_guardrailsBefore output guardrails run
post_runAfter the agent completes

Multi-agent types (additional):

TypeWhen it fires
pre_handoffBefore an agent-to-agent handoff
pre_pattern_stepBefore each step in a pattern execution

Configuration

import { createAgentOrchestrator } from '@directive-run/ai';
import type { BreakpointConfig } from '@directive-run/ai';

const orchestrator = createAgentOrchestrator({
  runner,
  debug: true,

  breakpoints: [
    { type: 'pre_agent_run', label: 'Before agent runs' },
    {
      type: 'pre_output_guardrails',
      when: (context) => context.input.includes('sensitive'),
      label: 'Sensitive content check',
    },
  ],

  onBreakpoint: (request) => {
    console.log(`Breakpoint hit: ${request.type} for ${request.agentId}`);
    console.log(`Input: ${request.input}`);
  },

  breakpointTimeoutMs: 300000,  // 5 minutes (default)
});

Resuming and Cancelling

// List pending breakpoints
const pending = orchestrator.getPendingBreakpoints();

for (const bp of pending) {
  console.log(`${bp.id}: ${bp.type}${bp.label}`);
}

// Resume with optional modifications
orchestrator.resumeBreakpoint(bp.id, {
  input: 'Modified input',  // Override the input
  skip: false,               // Set to true to skip the step entirely
});

// Cancel a breakpoint (aborts the operation)
orchestrator.cancelBreakpoint(bp.id, 'User cancelled');

BreakpointConfig

interface BreakpointConfig<T extends BreakpointType = BreakpointType> {
  type: T;
  when?: (context: BreakpointContext) => boolean;  // Conditional – fires only when true
  label?: string;                                   // Human-readable label
}

BreakpointContext

interface BreakpointContext {
  agentId: string;
  agentName: string;
  input: string;
  state: Record<string, unknown>;
  breakpointType: BreakpointType;
  patternId?: string;     // Set during pattern execution
  handoff?: HandoffRequest;  // Set during handoffs
}

BreakpointRequest

interface BreakpointRequest {
  id: string;
  type: BreakpointType;
  agentId: string;
  input: string;
  label?: string;
  requestedAt: number;
}

BreakpointModifications

interface BreakpointModifications {
  input?: string;   // Override the agent input
  skip?: boolean;   // Skip this step entirely
}

Multi-Agent Breakpoints

The multi-agent orchestrator supports two additional breakpoint types (pre_handoff and pre_pattern_step) and can filter by agent or pattern ID:

import { createMultiAgentOrchestrator } from '@directive-run/ai';

const orchestrator = createMultiAgentOrchestrator({
  runner,
  agents: { researcher: { agent: researcher }, writer: { agent: writer } },
  breakpoints: [
    { type: 'pre_handoff', agentIds: ['researcher'] },
    { type: 'pre_pattern_step', patternIds: ['pipeline'] },
  ],
  onBreakpoint: (req) => console.log('Breakpoint:', req.type, req.id),
});

Configuration Options

OptionTypeDefaultDescription
breakpointsBreakpointConfig[][]Breakpoint definitions
onBreakpoint(request) => voidCallback when breakpoint fires
breakpointTimeoutMsnumber300000Timeout before breakpoint auto-cancels (ms)

Breakpoint history is capped at 200 entries (MAX_BREAKPOINT_HISTORY).

Matching

import { matchBreakpoint } from '@directive-run/ai';

const match = matchBreakpoint(breakpoints, 'pre_agent_run', context);
if (match) {
  console.log(`Matched: ${match.label}`);
}

Checkpoints

Save and restore full orchestrator state – system facts, timeline events, memory, and orchestrator-specific local state.

Creating Checkpoints

const checkpoint = await orchestrator.checkpoint({ label: 'Before experiment' });

console.log(checkpoint.id);         // Unique ID
console.log(checkpoint.createdAt);  // Timestamp
console.log(checkpoint.label);      // 'Before experiment'

Restoring Checkpoints

orchestrator.restore(checkpoint, {
  restoreTimeline: true,  // Also restore timeline events (default: true)
});

Checkpoint Shape

interface Checkpoint {
  version: 1;
  id: string;
  createdAt: number;
  label?: string;
  systemExport: string;        // Serialized Directive System state
  timelineExport: string;      // Serialized timeline events
  localState: CheckpointLocalState;
  memoryExport: string | null; // Serialized memory state
  orchestratorType: 'single' | 'multi';
}

Multi-Agent Local State

For multi-agent orchestrators, localState includes:

interface MultiAgentCheckpointLocalState {
  type: 'multi';
  globalTokenCount: number;
  globalStatus: string;
  agentStates: Record<string, unknown>;
  handoffCounter: number;
  pendingHandoffs: HandoffRequest[];
  handoffResults: HandoffResult[];
  roundRobinCounters: Record<string, number>;
}

Checkpoint Store

Persist checkpoints with the CheckpointStore interface:

import { InMemoryCheckpointStore } from '@directive-run/ai';
import type { CheckpointStore } from '@directive-run/ai';

const store = new InMemoryCheckpointStore({ maxCheckpoints: 50 });

// Save
await store.save(checkpoint);

// Load
const loaded = await store.load(checkpoint.id);

// List all (returns { id, label, createdAt } summaries)
const all = await store.list();

// Delete
await store.delete(checkpoint.id);

// Clear all
await store.clear();

Custom Store

Implement the CheckpointStore interface for persistent backends:

const store: CheckpointStore = {
  save: async (checkpoint) => {
    await db.insert('checkpoints', checkpoint);

    return checkpoint.id;
  },
  load: async (id) => {
    return await db.findOne('checkpoints', { id });
  },
  list: async () => {
    return await db.find('checkpoints', {}, { select: ['id', 'label', 'createdAt'] });
  },
  delete: async (id) => {
    const deleted = await db.delete('checkpoints', { id });

    return deleted > 0;
  },
  clear: async () => {
    await db.deleteAll('checkpoints');
  },
};

Validation

import { validateCheckpoint, createCheckpointId } from '@directive-run/ai';

// Type guard with prototype pollution protection
if (validateCheckpoint(data)) {
  orchestrator.restore(data);
}

// Generate a unique checkpoint ID
const id = createCheckpointId();

Timeline Integration

Breakpoints emit breakpoint_hit and breakpoint_resumed events on the debug timeline:

const breakpointEvents = timeline.getEventsByType('breakpoint_hit');
const resumeEvents = timeline.getEventsByType('breakpoint_resumed');

Next Steps

Previous
Pattern Checkpoints

We care about your data. We'll never share your email.

Powered by Directive. This signup uses a Directive module with facts, derivations, constraints, and resolvers – zero useState, zero useEffect. Read how it works

Directive - Constraint-Driven State Management for TypeScript