Documentation/Routine Execution
v2.0.0-beta.1Snapshot bebdcd1Markdown ↗

Routine Execution

Routine Execution

For new workflows that must verify business completion, prefer the additive verified routine runner. The legacy API below remains supported. Default and generated prompts use the current store_set / store_get tools. Nested map/fold/until instructions reach reused agents through the task prompt, while their system instructions and shared tool manager remain intact. Prompt overrides remain optional. See the offline store/map/fold example and release report.

Execute tasks in dependency order with shared working memory and optional LLM quality reflection. Host-verified business completion uses the separate facade above.

Overview

The Routine Runner (executeRoutine()) takes a RoutineDefinition and executes it end-to-end:

  1. Creates an Agent with working memory + in-context memory enabled
  2. Runs tasks in dependency order (respecting dependsOn)
  3. Runs LLM self-reflection only when both skipReflection: false and nonempty completion criteria are configured; otherwise quality auto-passes
  4. Clears conversation between tasks but preserves memory plugins — this is how tasks share data
  5. Retries failed tasks up to maxAttempts
  6. Returns a RoutineExecution with status, progress, and per-task results
text
Task A → validate → clear conversation → Task B → validate → clear → Task C → done
           ↓                                ↓
    memory persists ──────────────→ memory persists

Quick Start

typescript
import {
  Connector, ConnectorTools, Services, tools, Vendor, executeRoutine,
  createRoutineDefinition,
} from 'oneringai';

// 1. Setup connector
Connector.create({
  name: 'openai',
  vendor: Vendor.OpenAI,
  auth: { type: 'api_key', apiKey: process.env.OPENAI_API_KEY! },
});

Connector.create({
  name: 'serper-main', serviceType: Services.Serper,
  auth: { type: 'api_key', apiKey: process.env.SERPER_API_KEY! },
  baseURL: 'https://google.serper.dev',
});
const searchTools = ConnectorTools.for('serper-main');

// 2. Define a routine
const routine = createRoutineDefinition({
  name: 'Research Report',
  description: 'Research a topic and write a report',
  instructions: 'You are a research assistant. Be thorough and cite sources.',
  tasks: [
    {
      name: 'Research',
      description: 'Search for information about quantum computing advances in 2026',
      expectedOutput: 'A summary of key findings stored in memory',
      suggestedTools: ['serper-main_web_search', 'web_fetch'],
      validation: {
        skipReflection: false, // Reflection is opt-in; not a host completion proof.
        completionCriteria: [
          'At least 3 distinct sources were found',
          'Key findings were stored in memory',
        ],
      },
    },
    {
      name: 'Write Report',
      description: 'Write a structured report based on the research findings',
      expectedOutput: 'A markdown report with introduction, findings, and conclusion',
      dependsOn: ['Research'],
      validation: {
        skipReflection: false, // Reflection is opt-in; not a host completion proof.
        completionCriteria: [
          'Report has an introduction, findings section, and conclusion',
          'Report cites specific sources from the research',
        ],
      },
    },
  ],
});

// 3. Execute
const execution = await executeRoutine({
  definition: routine,
  connector: 'openai',
  model: 'gpt-4.1',
  tools: [...searchTools, tools.webFetch],
  onTaskComplete: (task) => console.log(`Done: ${task.name}`),
  onTaskFailed: (task) => console.error(`Failed: ${task.name}`),
});

console.log(execution.status);   // 'completed' | 'failed'
console.log(execution.progress); // 100

RoutineDefinition Structure

typescript
interface RoutineDefinition {
  name: string;                    // Routine name
  description: string;             // Human-readable description (required)
  instructions?: string;           // System prompt for the agent
  requiredTools?: string[];        // Tool names that must be available
  requiredPlugins?: string[];      // Plugin names that must be registered

  tasks: TaskInput[];              // Array of task definitions (TaskDefinition does not exist)

  concurrency?: {
    maxParallelTasks: number;      // Required if concurrency is set; runner executes tasks sequentially regardless
    strategy: 'fifo' | 'priority' | 'shortest-first';
    failureMode?: 'fail-fast' | 'continue' | 'fail-all';  // Default: 'fail-fast'
  };
}

Task Definition

Each task in a routine has:

typescript
interface TaskInput {
  name: string;                    // Task name (used as ID)
  description: string;             // What the agent should do
  expectedOutput?: string;         // What success looks like
  suggestedTools?: string[];       // Hint to the agent about useful tools
  dependsOn?: string[];            // Task names this depends on
  maxAttempts?: number;            // Max retry attempts (default: 3)

  validation?: {
    completionCriteria?: string[]; // Criteria for LLM self-reflection
    minCompletionScore?: number;   // Minimum score 0-100 (default: 80)
    skipReflection?: boolean;      // Skip validation, auto-pass
  };
}

Control Flow (Map / Fold / Until)

Tasks can use control flow to iterate over data. Three flow types are available:

  • map — Execute a sub-routine for each element in an array (from memory key)
  • fold — Accumulate a result across array elements (like Array.reduce)
  • until — Repeat a sub-routine until a condition is met

All three support an optional iterationTimeoutMs to prevent infinite hangs:

typescript
const mapTask = {
  name: 'Process Items',
  description: 'Process each item from the list',
  controlFlow: {
    type: 'map',
    source: '__items_list',
    resultKey: '__processed_items',
    maxIterations: 50,
    iterationTimeoutMs: 120000, // 2 min per iteration
    tasks: [
      { name: 'Process', description: 'Process the current item' },
    ],
  },
};

When iterationTimeoutMs is set, each sub-execution is wrapped with Promise.race. If an iteration exceeds the timeout (or its sub-execution otherwise fails), the entire map/fold/until task fails immediately — there is no per-iteration failure mode or skip-and-continue; the loop does not proceed to remaining iterations.

Error Classification

The routine runner classifies errors as transient or permanent:

  • Permanent errors (auth failures, context length exceeded, model not found, config errors) immediately fail the task without retrying
  • Transient errors (network issues, rate limits, unknown errors) are retried up to maxAttempts

This prevents wasting retries on errors that will never succeed.

ROUTINE_KEYS Constant

The ROUTINE_KEYS constant is exported from the core library and contains all well-known ICM/WM key names used internally by the routine framework:

typescript
import { ROUTINE_KEYS } from 'oneringai';

// ROUTINE_KEYS.PLAN            → '__routine_plan'
// ROUTINE_KEYS.DEPS            → '__routine_deps'
// ROUTINE_KEYS.DEP_RESULT_PREFIX → '__dep_result_'
// ROUTINE_KEYS.MAP_ITEM        → '__map_item'
// ROUTINE_KEYS.MAP_INDEX       → '__map_index'
// ROUTINE_KEYS.MAP_TOTAL       → '__map_total'
// ROUTINE_KEYS.FOLD_ACCUMULATOR → '__fold_accumulator'

Useful for custom integrations that need to read or set routine state programmatically.

Task Dependencies and Ordering

Tasks execute in dependency order. Use dependsOn to create a DAG:

typescript
const routine = createRoutineDefinition({
  name: 'Data Pipeline',
  tasks: [
    { name: 'Fetch Data', description: '...' },
    { name: 'Clean Data', description: '...', dependsOn: ['Fetch Data'] },
    { name: 'Analyze', description: '...', dependsOn: ['Clean Data'] },
    { name: 'Visualize', description: '...', dependsOn: ['Analyze'] },
  ],
});

Tasks with no dependencies run first. A task only becomes eligible when all its dependencies are completed.

Memory as the Inter-Task Bridge

Between tasks, conversation history is cleared but memory plugins persist. This is how tasks share information:

  • In-Context Memory (store_set("whiteboard", ...)): Small key results that subsequent tasks see immediately in context. No retrieval call needed.

    text
    Task 1 calls: store_set({ store: "whiteboard", key: "api_endpoints", description: "...", value: "Found 3 endpoints: /users, /orders, /products" })
    Task 2 sees this automatically in its context window
    
  • Working Memory (store_set("notes", ...) / store_get("notes", ...)): Larger data stored externally, retrieved on demand.

    text
    Task 1 calls: store_set({ store: "notes", key: "raw_data", description: "Full API response", value: "...", tier: "findings" })
    Task 2 calls: store_get({ store: "notes", key: "findings.raw_data" }) → gets the full response
    

The defaults use these unified tools. Read each notes source with its exact index key, including tier/collision suffixes; the index and keyless reads contain metadata only. Large routine dependencies and results spill to the findings tier, and internal control-flow resolution preserves exact-key precedence before checking that spill location. Routine cleanup removes its reserved tiered keys and preserves unrelated host notes. Map/fold iteration state requires the in-context memory plugin. Fold captures the last completed subtask output first; a whiteboard accumulator update is only a fallback when no task output exists.

Validation and Self-Reflection

After each task completes, the runner validates the output:

  1. LLM validation is opt-in: it only runs when the task sets skipReflection: false explicitly AND provides non-empty completionCriteria
  2. Otherwise (the default — skipReflection omitted/true, or no completionCriteria) the task auto-passes with completionScore: 100
  3. When enabled: calls agent.runDirect() with the task criteria and agent's response
  4. The validation LLM is asked to return { isComplete, completionScore, explanation }, but only completionScore/explanation are used — the LLM's own isComplete is ignored
  5. Task passes if completionScore >= minCompletionScore (default 80)
typescript
const validation = {
  validation: {
    skipReflection: false, // Reflection is opt-in; not a host completion proof.
    completionCriteria: [
      'All API endpoints were documented',
      'Error codes were listed for each endpoint',
      'Authentication requirements were specified',
    ],
    minCompletionScore: 85,  // Strict: require 85+ score
  },
};

Retry Logic

If validation fails and the task hasn't exhausted maxAttempts:

  • The conversation is NOT cleared (agent builds on previous attempt)
  • Task status returns to in_progress (increments attempt counter)
  • Agent retries with the same prompt

If maxAttempts is exceeded, the task is marked failed.

Failure Modes

typescript
const routine = createRoutineDefinition({
  name: 'Pipeline',
  concurrency: {
    maxParallelTasks: 1,
    strategy: 'fifo',
    failureMode: 'fail-fast',  // Default: stop on first failure
    // failureMode: 'continue', // Skip failed tasks, continue with independents
  },
  tasks: [],
});
Mode Behavior
fail-fast Stop entire routine on first task failure
continue Skip failed task, proceed to next independent task

Custom Prompts

Override any prompt builder to customize agent behavior:

typescript
const execution = await executeRoutine({
  definition: routine,
  connector: 'openai',
  model: 'gpt-4.1',
  prompts: {
    // Custom system prompt
    system: (definition) => `You are ${definition.name}. Follow these rules...`,

    // Custom task prompt
    task: (task) => `
      ## ${task.name}
      ${task.description}
      Remember to store results using store_set("whiteboard", key, { description, value }).
    `,

    // Custom validation prompt
    validation: (task, context) => `
      Did the agent complete "${task.name}"?
      Criteria: ${task.validation?.completionCriteria?.join(', ')}
      Response: ${context.responseText}
      Return JSON: { "isComplete": boolean, "completionScore": number, "explanation": string }
    `,
  },
});

Callbacks and Progress Tracking

typescript
const execution = await executeRoutine({
  definition: routine,
  connector: 'openai',
  model: 'gpt-4.1',

  onTaskComplete: (task, execution) => {
    console.log(`Task "${task.name}" completed`);
    console.log(`  Score: ${task.result?.validationScore}`);
    console.log(`  Progress: ${execution.progress}%`);
  },

  onTaskFailed: (task, execution) => {
    console.error(`Task "${task.name}" failed after ${task.attempts} attempts`);
    console.error(`  Error: ${task.result?.error}`);
  },
});

ExecuteRoutineOptions Reference

typescript
interface ExecuteRoutineOptions {
  /** Routine definition to execute */
  definition: RoutineDefinition;

  /** Pre-created Agent instance. When provided, connector/model/tools are ignored.
   *  The agent is NOT destroyed after execution — caller manages its lifecycle. */
  agent?: Agent;

  /** Connector name — required when `agent` is not provided */
  connector?: string;

  /** Model ID — required when `agent` is not provided */
  model?: string;

  /** Additional tools — only used when creating a new agent (no `agent` provided) */
  tools?: ToolFunction[];

  /** Input parameter values for parameterized routines */
  inputs?: Record<string, unknown>;

  /** Hooks — applied to agent for the duration of routine execution */
  hooks?: HookConfig;

  /** Called when a task starts executing */
  onTaskStarted?: (task: Task, execution: RoutineExecution) => void;

  /** Called when a task completes successfully */
  onTaskComplete?: (task: Task, execution: RoutineExecution) => void;

  /** Called when a task fails */
  onTaskFailed?: (task: Task, execution: RoutineExecution) => void;

  /** Called after each validation attempt (whether pass or fail) */
  onTaskValidation?: (task: Task, result: TaskValidationResult, execution: RoutineExecution) => void;

  /** Configurable prompts (all have sensible defaults) */
  prompts?: {
    system?: (definition: RoutineDefinition) => string;
    task?: (task: Task) => string;
    validation?: (task: Task, context: ValidationContext) => string;
  };
}

RoutineExecution Result

typescript
interface RoutineExecution {
  id: string;                      // Unique execution ID
  routineId: string;               // From definition
  status: 'pending' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled';
  plan: Plan;                      // Tasks with statuses and results
  progress: number;                // 0-100 percentage
  startedAt?: number;              // Timestamp
  completedAt?: number;            // Timestamp
  lastUpdatedAt: number;           // Timestamp
  error?: string;                  // Error message if failed
}

Each completed task has a result:

typescript
// Shape of Task.result (not a separately exported type)
interface TaskResult {
  success: boolean;
  output?: unknown;                // Agent's response text (or control-flow result)
  error?: string;                  // Error message if failed
  validationScore?: number;        // 0-100 from validation
  validationExplanation?: string;  // Why it passed/failed
}

Complete Example: Multi-Step Research Pipeline

typescript
import {
  Connector, Vendor, Agent,
  executeRoutine, createRoutineDefinition,
  ConnectorTools, Services, tools,
} from 'oneringai';

// Setup connectors
Connector.create({
  name: 'openai',
  vendor: Vendor.OpenAI,
  auth: { type: 'api_key', apiKey: process.env.OPENAI_API_KEY! },
});

Connector.create({
  name: 'serper',
  serviceType: Services.Serper,
  auth: { type: 'api_key', apiKey: process.env.SERPER_API_KEY! },
  baseURL: 'https://google.serper.dev',
});

// Define the routine
const routine = createRoutineDefinition({
  name: 'Competitive Analysis',
  description: 'Analyze competitors and produce a report',
  instructions: 'You are a business analyst. Be thorough and data-driven.',
  requiredTools: ['web_search'],
  tasks: [
    {
      name: 'Identify Competitors',
      description: 'Search for the top 5 competitors of Acme Corp in the cloud storage market',
      expectedOutput: 'List of competitors stored in context',
      suggestedTools: ['web_search'],
      validation: {
        skipReflection: false, // Reflection is opt-in; not a host completion proof.
        completionCriteria: [
          'At least 5 competitors were identified',
          'Competitor names were stored using store_set("whiteboard", ...)',
        ],
      },
    },
    {
      name: 'Analyze Features',
      description: 'For each competitor found, research their key features and pricing',
      dependsOn: ['Identify Competitors'],
      expectedOutput: 'Feature comparison data stored in memory',
      suggestedTools: ['web_search', 'web_fetch'],
      maxAttempts: 2,
      validation: {
        skipReflection: false, // Reflection is opt-in; not a host completion proof.
        completionCriteria: [
          'Features were researched for all identified competitors',
          'Pricing information was found for at least 3 competitors',
          'Data was stored in working memory',
        ],
        minCompletionScore: 70,  // More lenient — pricing may not always be public
      },
    },
    {
      name: 'Write Report',
      description: 'Compile findings into a structured competitive analysis report',
      dependsOn: ['Analyze Features'],
      expectedOutput: 'A markdown report with executive summary, feature comparison table, and recommendations',
      validation: {
        skipReflection: false, // Reflection is opt-in; not a host completion proof.
        completionCriteria: [
          'Report has an executive summary',
          'Report includes a feature comparison table',
          'Report provides actionable recommendations',
        ],
      },
    },
  ],
});

// Execute with search tools
const searchTools = ConnectorTools.for('serper');

const execution = await executeRoutine({
  definition: routine,
  connector: 'openai',
  model: 'gpt-4.1',
  tools: [...searchTools, tools.webFetch],
  onTaskComplete: (task, exec) => {
    console.log(`[${exec.progress}%] ${task.name} completed (score: ${task.result?.validationScore})`);
  },
  onTaskFailed: (task) => {
    console.error(`FAILED: ${task.name} — ${task.result?.error}`);
  },
});

if (execution.status === 'completed') {
  // Get the final report from the last task's output
  const reportTask = execution.plan.tasks.find(t => t.name === 'Write Report');
  console.log(reportTask?.result?.output);
} else {
  console.error(`Routine failed: ${execution.error}`);
}

Best Practices

  1. Use store_set("whiteboard", ...) for small shared state — task names, IDs, short summaries. These are always visible.
  2. Use store_set("notes", ...) for large data — full API responses, documents, raw data. Retrieved on demand via store_get("notes", key).
  3. Separate quality from completion — LLM criteria assess an answer; host verifiers must check actual artifacts and remote state.
  4. Set appropriate minCompletionScore — use 80+ for strict validation, 60-70 for lenient.
  5. Opt into reflection explicitly with skipReflection: false and criteria when needed; omission already auto-passes.
  6. Keep maxAttempts reasonable — 2-3 for most tasks. Each retry costs LLM calls.
  7. Use continue failure mode when tasks are independent and partial results are acceptable.

Routine Persistence

Save routine definitions to disk and load them later using FileRoutineDefinitionStorage (or implement IRoutineDefinitionStorage for custom backends like MongoDB).

typescript
import {
  createFileRoutineDefinitionStorage,
  createRoutineDefinition,
} from 'oneringai';

const storage = createFileRoutineDefinitionStorage();

// Save a routine
const routine = createRoutineDefinition({
  name: 'Daily Report',
  description: 'Generate a daily status report',
  tags: ['daily', 'reports'],
  author: 'alice',
  tasks: [
    { name: 'Gather Data', description: 'Collect metrics from all sources' },
    { name: 'Write Report', description: 'Summarize findings', dependsOn: ['Gather Data'] },
  ],
});

await storage.save(undefined, routine);  // undefined = default user

// Load by ID
const loaded = await storage.load(undefined, routine.id);

// List with filtering
const dailyRoutines = await storage.list(undefined, { tags: ['daily'] });
const searchResults = await storage.list(undefined, { search: 'report', limit: 10 });

// Delete
await storage.delete(undefined, routine.id);

Storage Path: ~/.oneringai/users/<userId>/routines/<id>.json (defaults to ~/.oneringai/users/default/routines/)

Multi-user isolation: Pass a userId to isolate routines per user. When userId is undefined, defaults to 'default'.

StorageRegistry integration:

typescript
import { StorageRegistry } from 'oneringai';

StorageRegistry.configure({
  routineDefinitions: (ctx) => new MongoRoutineStorage(ctx?.userId),
});

Execution Recording

Persist full routine execution history — every step, task snapshot, and progress update — without manually wiring hooks and callbacks.

Types:

Type Purpose
RoutineExecutionRecord Top-level persisted record (status, progress, tasks, steps, trigger info)
RoutineTaskSnapshot Per-task snapshot (status, attempts, result, controlFlowType)
RoutineExecutionStep Timestamped event (task.started, tool.call, llm.complete, etc.)
RoutineTaskResult Task outcome (success, output, error, validationScore)

Factory functions:

typescript
import {
  createRoutineExecutionRecord,
  createTaskSnapshots,
  createExecutionRecorder,
} from 'oneringai';

// 1. Create the initial record from a definition
const record = createRoutineExecutionRecord(definition, 'openai', 'gpt-4.1', {
  type: 'schedule',
  source: 'daily-cron',
});

// 2. Insert into your storage backend
const execId = await storage.insert(userId, record);

// 3. Create recorder — returns ready-to-use hooks + callbacks
const recorder = createExecutionRecorder({
  storage,
  executionId: execId,
  logPrefix: '[MyRoutine]',
  maxTruncateLength: 500,  // truncate tool args/results in steps
});

// 4. Wire into executeRoutine()
executeRoutine({
  definition, agent, inputs,
  hooks: recorder.hooks,
  onTaskStarted: recorder.onTaskStarted,
  onTaskComplete: recorder.onTaskComplete,
  onTaskFailed: recorder.onTaskFailed,
  onTaskValidation: recorder.onTaskValidation,
})
  .then(exec => recorder.finalize(exec))
  .catch(err => recorder.finalize(null, err));

What the recorder tracks:

Hook/Callback Step Type(s)
before:llm llm.start
after:llm llm.complete (with duration/tokens)
before:tool tool.start
after:tool tool.call (with args/result, truncated)
after:execution iteration.complete
pause:check heartbeat (lastActivityAt update)
onTaskStarted task.started, control_flow.started
onTaskComplete task.completed, control_flow.completed
onTaskFailed task.failed
onTaskValidation task.validation
finalize execution.error (on failure), final status update

Storage interface (IRoutineExecutionStorage):

typescript
interface IRoutineExecutionStorage {
  insert(userId: string | undefined, record: RoutineExecutionRecord): Promise<string>;
  update(id: string, updates: Partial<Pick<RoutineExecutionRecord, 'status' | 'progress' | 'error' | 'completedAt' | 'lastActivityAt'>>): Promise<void>;
  pushStep(id: string, step: RoutineExecutionStep): Promise<void>;
  updateTask(id: string, taskName: string, updates: Partial<RoutineTaskSnapshot>): Promise<void>;
  load(id: string): Promise<RoutineExecutionRecord | null>;
  list(userId: string | undefined, options?: { routineId?: string; status?: RoutineExecutionStatus; limit?: number; offset?: number }): Promise<RoutineExecutionRecord[]>;
  hasRunning(userId: string | undefined, routineId: string): Promise<boolean>;
}

Implement this interface for your own storage backend (MongoDB, PostgreSQL, etc.), or use the library's built-in FileRoutineExecutionStorage (createFileRoutineExecutionStorage()), which persists records to ~/.oneringai/users/<userId>/routine-executions/<executionId>.json.

StorageRegistry integration:

typescript
StorageRegistry.configure({
  routineExecutions: (ctx) => new MongoRoutineExecutionStorage(ctx?.userId),
});

Scheduling

Run routines on a timer using IScheduler. The built-in SimpleScheduler supports interval and one-time schedules:

typescript
import { SimpleScheduler } from 'oneringai';

const scheduler = new SimpleScheduler();

// Repeat every hour
scheduler.schedule('hourly-report', { intervalMs: 3600000 }, async () => {
  await executeRoutine({ definition: dailyRoutine, agent, inputs });
});

// Run once at a specific time
scheduler.schedule('end-of-day', { once: endOfDayTimestamp }, async () => {
  await executeRoutine({ definition: summaryRoutine, agent, inputs });
});

// Cancel a schedule
scheduler.cancel('hourly-report');

// Check if a schedule exists
scheduler.has('hourly-report'); // false

// Clean up all timers
scheduler.destroy();

ScheduleSpec options:

Field Type Description
intervalMs number Repeat every N milliseconds
once number Fire once at this Unix timestamp (ms)
cron string Cron expression (not supported by SimpleScheduler — use a cron library)
timezone string IANA timezone for cron expressions

SimpleScheduler is intentionally minimal. For cron support, implement IScheduler with a cron library (e.g., croner, node-cron):

typescript
import type { IScheduler, ScheduleHandle, ScheduleSpec } from 'oneringai';
import { Cron } from 'croner';

class CronScheduler implements IScheduler {
  private jobs = new Map<string, Cron>();
  private _isDestroyed = false;

  schedule(id: string, spec: ScheduleSpec, callback: () => void | Promise<void>): ScheduleHandle {
    if (spec.cron) {
      const job = new Cron(spec.cron, { timezone: spec.timezone }, callback);
      this.jobs.set(id, job);
      return { id, cancel: () => this.cancel(id) };
    }
    throw new Error('CronScheduler only handles cron specs');
  }
  cancel(id: string) { this.jobs.get(id)?.stop(); this.jobs.delete(id); }
  cancelAll() { for (const [id] of this.jobs) this.cancel(id); }
  has(id: string) { return this.jobs.has(id); }
  destroy() { this.cancelAll(); this._isDestroyed = true; }
  get isDestroyed() { return this._isDestroyed; }
}

Event Triggers

Trigger routine execution from external events (webhooks, message queues, custom signals) using EventEmitterTrigger:

typescript
import { EventEmitterTrigger } from 'oneringai';

const trigger = new EventEmitterTrigger();

// Register a handler
const unsubscribe = trigger.on('new-order', async (payload) => {
  const order = payload as { orderId: string; items: string[] };
  await executeRoutine({
    definition: orderProcessingRoutine,
    agent,
    inputs: { orderId: order.orderId, items: order.items },
  });
});

// Emit from your webhook handler, queue consumer, etc.
app.post('/webhooks/orders', (req, res) => {
  trigger.emit('new-order', req.body);
  res.sendStatus(200);
});

// Unsubscribe a specific handler
unsubscribe();

// Clean up all listeners
trigger.destroy();

Key design: EventEmitterTrigger is intentionally simple — no ITriggerSource interface. For complex trigger systems (AWS SQS, RabbitMQ, Kafka), just call trigger.emit() from your consumer callback.


Documentation snapshot · 2026-11-02Suggest a correction ↗

Search documentation

Type to search the v2 documentation.