npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@wundr.io/langgraph-orchestrator

v1.0.6

Published

LangGraph-style cyclic, state-driven workflow orchestration for AI agents

Readme

@wundr.io/langgraph-orchestrator

LangGraph-style cyclic, state-driven workflow orchestration for AI agents. This package provides a powerful framework for building complex AI workflows with conditional branching, state management, checkpointing, and human-in-the-loop capabilities.

Installation

npm install @wundr.io/langgraph-orchestrator
# or
pnpm add @wundr.io/langgraph-orchestrator
# or
yarn add @wundr.io/langgraph-orchestrator

Package Overview

The @wundr.io/langgraph-orchestrator package enables you to build sophisticated AI agent workflows using a state graph paradigm. Key features include:

  • State Graph Architecture: Define workflows as directed graphs with nodes and edges
  • Cyclic Workflows: Support for loops and iterative processing patterns
  • Decision Nodes: Conditional branching based on state values
  • Checkpointing: Save and restore workflow state for fault tolerance
  • Human-in-the-Loop: Pause workflows for human input or approval
  • Type Safety: Full TypeScript support with Zod schema validation

Quick Start

import {
  StateGraph,
  createLLMNode,
  createToolNode,
  createDecisionNode,
  MemoryCheckpointer
} from '@wundr.io/langgraph-orchestrator';

// Create a workflow graph
const graph = new StateGraph('my-workflow')
  .addNode('agent', createLLMNode({
    id: 'agent',
    name: 'Agent',
    config: { model: 'claude-3-sonnet-20240229' }
  }))
  .addNode('tools', createToolNode({
    id: 'tools',
    name: 'Tools'
  }))
  .addEdge('agent', 'tools')
  .addConditionalEdge('tools', 'agent', {
    type: 'exists',
    field: 'data.pendingToolCalls'
  })
  .setEntryPoint('agent')
  .setCheckpointer(new MemoryCheckpointer());

// Execute the workflow
const result = await graph.execute({
  initialState: {
    data: { task: 'Research AI developments' }
  }
});

Core Concepts

StateGraph

The StateGraph class is the foundation for defining workflows. It manages nodes, edges, and execution flow.

const graph = new StateGraph('workflow-name', {
  maxIterations: 100,      // Cycle protection
  timeout: 300000,         // 5 minute timeout
  checkpointEnabled: true, // Enable state persistence
  logLevel: 'info'
});

AgentState

All workflows operate on an AgentState object that maintains:

  • id: Unique state identifier
  • messages: Conversation history
  • data: Arbitrary key-value store for workflow data
  • currentStep: Current node in execution
  • history: State history for debugging
  • metadata: Execution tracking metadata

Nodes

Nodes are the processing units in your workflow. Each node receives state, performs operations, and returns updated state with optional next node specification.

Edges

Edges define transitions between nodes:

  • Direct edges: Always follow to the target node
  • Conditional edges: Follow based on state conditions
  • Loop edges: Enable cyclic workflows
  • Parallel edges: Branch to multiple nodes

Decision Nodes

Decision nodes enable conditional branching in your workflows based on state values. The package provides five decision node factory functions, each suited for different routing patterns.

Condition Types

All decision nodes use condition types to evaluate state values:

| Type | Description | Example | |------|-------------|---------| | equals | Exact match comparison | { type: 'equals', field: 'data.status', value: 'complete' } | | not_equals | Inverse of equals | { type: 'not_equals', field: 'data.error', value: null } | | contains | Array includes or string contains | { type: 'contains', field: 'data.tags', value: 'urgent' } | | greater_than | Numeric comparison (>) | { type: 'greater_than', field: 'data.score', value: 0.8 } | | less_than | Numeric comparison (<) | { type: 'less_than', field: 'data.retries', value: 3 } | | exists | Value is not null/undefined | { type: 'exists', field: 'data.result' } | | not_exists | Value is null/undefined | { type: 'not_exists', field: 'error' } | | custom | Custom evaluation function | { type: 'custom', evaluate: async (state) => state.data.x > 10 } |

Field Path Notation

Conditions use dot notation to access nested state values:

// Access state.data.user.role
{ type: 'equals', field: 'data.user.role', value: 'admin' }

// Access state.messages
{ type: 'exists', field: 'messages' }

// Access state.metadata.stepCount
{ type: 'greater_than', field: 'metadata.stepCount', value: 5 }

createDecisionNode

The most flexible decision node, allowing multiple branches with priority-based evaluation.

API:

function createDecisionNode<TState extends AgentState = AgentState>(options: {
  id: string;                              // Unique node identifier
  name: string;                            // Human-readable name
  config: DecisionNodeConfig;              // Decision configuration
  nodeConfig?: NodeConfig;                 // Optional node settings
}): NodeDefinition<TState>

DecisionNodeConfig:

interface DecisionNodeConfig {
  branches: DecisionBranch[];              // Decision branches
  defaultBranch?: string;                  // Fallback target node
  throwOnNoMatch?: boolean;                // Error if no match (default: false)
  decide?: (state: AgentState) => string | Promise<string>;  // Custom function
}

interface DecisionBranch {
  name: string;                            // Branch identifier
  target: string;                          // Target node name
  condition: EdgeCondition;                // Branch condition
  priority?: number;                       // Evaluation order (higher first)
}

Example:

import { createDecisionNode } from '@wundr.io/langgraph-orchestrator';

const router = createDecisionNode({
  id: 'router',
  name: 'Task Router',
  config: {
    branches: [
      {
        name: 'search',
        target: 'search-node',
        condition: { type: 'equals', field: 'data.action', value: 'search' },
        priority: 2
      },
      {
        name: 'answer',
        target: 'answer-node',
        condition: { type: 'equals', field: 'data.action', value: 'answer' },
        priority: 1
      }
    ],
    defaultBranch: 'fallback-node'
  }
});

graph.addNode('router', router);

With Custom Decision Function:

const customRouter = createDecisionNode({
  id: 'smart-router',
  name: 'Smart Router',
  config: {
    branches: [], // Not used when decide function is provided
    decide: async (state) => {
      const score = state.data.confidence as number;
      if (score > 0.9) return 'high-confidence-handler';
      if (score > 0.5) return 'medium-confidence-handler';
      return 'low-confidence-handler';
    }
  }
});

createSwitchNode

A switch-case style decision node for routing based on a single field value. Ideal for enumerated routing scenarios.

API:

function createSwitchNode<TState extends AgentState = AgentState>(options: {
  id: string;                              // Unique node identifier
  name: string;                            // Human-readable name
  field: string;                           // State field to switch on
  cases: Record<string, string>;           // Value-to-target mapping
  default?: string;                        // Default target if no case matches
  nodeConfig?: NodeConfig;                 // Optional node settings
}): NodeDefinition<TState>

Example:

import { createSwitchNode } from '@wundr.io/langgraph-orchestrator';

const typeSwitch = createSwitchNode({
  id: 'type-switch',
  name: 'Message Type Switch',
  field: 'data.messageType',
  cases: {
    'question': 'question-handler',
    'command': 'command-handler',
    'feedback': 'feedback-handler'
  },
  default: 'unknown-handler'
});

graph.addNode('type-switch', typeSwitch);

Practical Use Case - Support Ticket Routing:

const ticketRouter = createSwitchNode({
  id: 'ticket-router',
  name: 'Support Ticket Router',
  field: 'data.department',
  cases: {
    'billing': 'billing-team',
    'technical': 'tech-support',
    'sales': 'sales-team',
    'legal': 'legal-team'
  },
  default: 'general-support'
});

createThresholdNode

Routes based on numeric thresholds, perfect for confidence scores, priority levels, or any numeric classification.

API:

function createThresholdNode<TState extends AgentState = AgentState>(options: {
  id: string;                              // Unique node identifier
  name: string;                            // Human-readable name
  field: string;                           // Numeric field to evaluate
  thresholds: Array<{                      // Threshold definitions
    value: number;                         // Threshold value
    target: string;                        // Target node
  }>;
  default?: string;                        // Target for values below all thresholds
  nodeConfig?: NodeConfig;                 // Optional node settings
}): NodeDefinition<TState>

Threshold Evaluation:

Thresholds are automatically sorted in descending order. A value matches a threshold if it is greater than or equal to the threshold value but less than the next higher threshold.

Example:

import { createThresholdNode } from '@wundr.io/langgraph-orchestrator';

const confidenceRouter = createThresholdNode({
  id: 'confidence-router',
  name: 'Confidence Router',
  field: 'data.confidence',
  thresholds: [
    { value: 0.9, target: 'high-confidence' },    // >= 0.9
    { value: 0.7, target: 'medium-confidence' },  // >= 0.7 and < 0.9
    { value: 0.5, target: 'low-confidence' }      // >= 0.5 and < 0.7
  ],
  default: 'very-low-confidence'                  // < 0.5
});

graph.addNode('confidence-router', confidenceRouter);

Practical Use Case - Priority Queue:

const priorityRouter = createThresholdNode({
  id: 'priority-router',
  name: 'Task Priority Router',
  field: 'data.priority',
  thresholds: [
    { value: 90, target: 'critical-queue' },
    { value: 70, target: 'high-priority-queue' },
    { value: 40, target: 'normal-queue' },
    { value: 10, target: 'low-priority-queue' }
  ],
  default: 'backlog-queue'
});

createIfElseNode

A simple binary decision node for true/false branching based on a single condition.

API:

function createIfElseNode<TState extends AgentState = AgentState>(options: {
  id: string;                              // Unique node identifier
  name: string;                            // Human-readable name
  condition: EdgeCondition;                // Condition to evaluate
  ifTrue: string;                          // Target node if condition is true
  ifFalse: string;                         // Target node if condition is false
  nodeConfig?: NodeConfig;                 // Optional node settings
}): NodeDefinition<TState>

Example:

import { createIfElseNode } from '@wundr.io/langgraph-orchestrator';

const errorCheck = createIfElseNode({
  id: 'has-error',
  name: 'Error Check',
  condition: {
    type: 'exists',
    field: 'error'
  },
  ifTrue: 'error-handler',
  ifFalse: 'success-handler'
});

graph.addNode('has-error', errorCheck);

Practical Use Cases:

// Authentication check
const authCheck = createIfElseNode({
  id: 'auth-check',
  name: 'Authentication Check',
  condition: { type: 'exists', field: 'data.userId' },
  ifTrue: 'authenticated-flow',
  ifFalse: 'login-required'
});

// Retry decision
const retryCheck = createIfElseNode({
  id: 'retry-check',
  name: 'Retry Decision',
  condition: { type: 'less_than', field: 'data.retryCount', value: 3 },
  ifTrue: 'retry-operation',
  ifFalse: 'max-retries-reached'
});

// Feature flag
const featureFlag = createIfElseNode({
  id: 'feature-flag',
  name: 'New Feature Check',
  condition: { type: 'equals', field: 'data.features.newUI', value: true },
  ifTrue: 'new-ui-flow',
  ifFalse: 'legacy-ui-flow'
});

createMultiConditionNode

Routes based on multiple conditions combined with AND/OR logic. Ideal for complex business rules.

API:

function createMultiConditionNode<TState extends AgentState = AgentState>(options: {
  id: string;                              // Unique node identifier
  name: string;                            // Human-readable name
  branches: Array<{
    name: string;                          // Branch identifier
    target: string;                        // Target node
    conditions: EdgeCondition[];           // Array of conditions
    logic: 'AND' | 'OR';                   // How to combine conditions
    priority?: number;                     // Evaluation order
  }>;
  default?: string;                        // Default target if no branch matches
  nodeConfig?: NodeConfig;                 // Optional node settings
}): NodeDefinition<TState>

Logic Evaluation:

  • AND: All conditions must be true for the branch to match
  • OR: At least one condition must be true for the branch to match

Example:

import { createMultiConditionNode } from '@wundr.io/langgraph-orchestrator';

const complexRouter = createMultiConditionNode({
  id: 'complex-router',
  name: 'Complex Router',
  branches: [
    {
      name: 'premium-user',
      target: 'premium-flow',
      conditions: [
        { type: 'equals', field: 'data.userType', value: 'premium' },
        { type: 'greater_than', field: 'data.credits', value: 0 }
      ],
      logic: 'AND',  // Must be premium AND have credits
      priority: 2
    },
    {
      name: 'needs-upgrade',
      target: 'upgrade-flow',
      conditions: [
        { type: 'equals', field: 'data.userType', value: 'free' },
        { type: 'less_than', field: 'data.credits', value: 1 }
      ],
      logic: 'OR',   // Either free user OR out of credits
      priority: 1
    }
  ],
  default: 'standard-flow'
});

graph.addNode('complex-router', complexRouter);

Practical Use Case - Access Control:

const accessControl = createMultiConditionNode({
  id: 'access-control',
  name: 'Access Control',
  branches: [
    {
      name: 'admin-access',
      target: 'admin-dashboard',
      conditions: [
        { type: 'equals', field: 'data.role', value: 'admin' },
        { type: 'equals', field: 'data.verified', value: true }
      ],
      logic: 'AND',
      priority: 3
    },
    {
      name: 'power-user',
      target: 'advanced-features',
      conditions: [
        { type: 'equals', field: 'data.role', value: 'power-user' },
        { type: 'greater_than', field: 'data.accountAge', value: 30 }
      ],
      logic: 'AND',
      priority: 2
    },
    {
      name: 'suspended-or-unverified',
      target: 'restricted-access',
      conditions: [
        { type: 'equals', field: 'data.suspended', value: true },
        { type: 'equals', field: 'data.verified', value: false }
      ],
      logic: 'OR',
      priority: 1
    }
  ],
  default: 'standard-user-flow'
});

State Management

Creating Initial State

const result = await graph.execute({
  initialState: {
    data: {
      task: 'Process user request',
      userId: '12345',
      config: { maxRetries: 3 }
    }
  }
});

State Updates in Nodes

Nodes return updated state in their NodeResult:

const processNode: NodeDefinition = {
  id: 'process',
  name: 'Process Node',
  type: 'transform',
  config: {},
  execute: async (state, context) => {
    // Immutably update state
    const newState = {
      ...state,
      data: {
        ...state.data,
        processed: true,
        result: 'Some computation result'
      }
    };

    return {
      state: newState,
      next: 'next-node'  // Optional: specify next node
    };
  }
};

State History

The graph automatically tracks state changes in the history array:

// Access execution history
result.state.history.forEach(entry => {
  console.log(`Step: ${entry.step}`);
  console.log(`Changes: ${JSON.stringify(entry.changes)}`);
});

Node Execution Flow

Node Result

Every node returns a NodeResult:

interface NodeResult<TState> {
  state: TState;                // Updated state
  next?: string | string[];     // Next node(s) - optional
  terminate?: boolean;          // Stop execution
  metadata?: {
    duration: number;           // Execution time
    tokensUsed?: number;        // LLM tokens consumed
    toolCalls?: ToolCall[];     // Tools executed
    retryCount?: number;        // Retry attempts
  };
}

Execution Handlers

Monitor workflow execution with event handlers:

const result = await graph.execute({
  handlers: {
    onStart: (state) => console.log('Started:', state.id),
    onNodeEnter: (nodeName, state) => console.log(`Entering: ${nodeName}`),
    onNodeExit: (nodeName, result) => console.log(`Exiting: ${nodeName}`),
    onCheckpoint: (checkpoint) => console.log('Checkpoint:', checkpoint.id),
    onError: (error) => console.error('Error:', error.message),
    onComplete: (state) => console.log('Complete:', state.id)
  }
});

Event Emitter

The StateGraph class extends EventEmitter for reactive patterns:

graph.on('execution:start', (state) => {
  console.log('Workflow started');
});

graph.on('node:enter', (nodeName, state) => {
  metrics.trackNodeEntry(nodeName);
});

graph.on('state:updated', (state) => {
  saveToDatabase(state);
});

Checkpointing

Memory Checkpointer

For development and testing:

import { MemoryCheckpointer } from '@wundr.io/langgraph-orchestrator';

const checkpointer = new MemoryCheckpointer();
graph.setCheckpointer(checkpointer);

File Checkpointer

For persistent storage:

import { FileCheckpointer } from '@wundr.io/langgraph-orchestrator';

const checkpointer = new FileCheckpointer({
  directory: './checkpoints',
  format: 'json'
});
graph.setCheckpointer(checkpointer);

Resuming from Checkpoint

// Resume execution from a specific checkpoint
const result = await graph.execute({
  resumeFrom: 'checkpoint-id-here'
});

Complete Example: AI Research Assistant

import {
  StateGraph,
  createLLMNode,
  createToolNode,
  createDecisionNode,
  createIfElseNode,
  createThresholdNode,
  MemoryCheckpointer
} from '@wundr.io/langgraph-orchestrator';

// Define custom state
interface ResearchState extends AgentState {
  data: {
    query: string;
    searchResults?: string[];
    analysis?: string;
    confidence?: number;
    needsMoreInfo?: boolean;
  };
}

// Create the research workflow
const researchGraph = new StateGraph<ResearchState>('research-assistant', {
  maxIterations: 20
});

// Add nodes
researchGraph
  // Initial planning node
  .addNode('planner', createLLMNode({
    id: 'planner',
    name: 'Research Planner',
    config: { systemPrompt: 'Plan the research strategy for the given query.' }
  }))

  // Search execution
  .addNode('searcher', createToolNode({
    id: 'searcher',
    name: 'Web Searcher',
    config: { tools: [webSearchTool] }
  }))

  // Analyze search results
  .addNode('analyzer', createLLMNode({
    id: 'analyzer',
    name: 'Result Analyzer',
    config: { systemPrompt: 'Analyze the search results and assess confidence.' }
  }))

  // Decision: enough information?
  .addNode('confidence-check', createThresholdNode({
    id: 'confidence-check',
    name: 'Confidence Check',
    field: 'data.confidence',
    thresholds: [
      { value: 0.8, target: 'synthesizer' },
      { value: 0.5, target: 'refiner' }
    ],
    default: 'searcher'  // Low confidence, search more
  }))

  // Refine search query
  .addNode('refiner', createLLMNode({
    id: 'refiner',
    name: 'Query Refiner',
    config: { systemPrompt: 'Refine the search query based on gaps identified.' }
  }))

  // Synthesize final answer
  .addNode('synthesizer', createLLMNode({
    id: 'synthesizer',
    name: 'Answer Synthesizer',
    config: { systemPrompt: 'Synthesize a comprehensive answer from all research.' }
  }))

  // Define edges
  .addEdge('planner', 'searcher')
  .addEdge('searcher', 'analyzer')
  .addEdge('analyzer', 'confidence-check')
  .addEdge('refiner', 'searcher')

  // Set entry point and checkpointer
  .setEntryPoint('planner')
  .setCheckpointer(new MemoryCheckpointer());

// Execute research
const result = await researchGraph.execute({
  initialState: {
    data: {
      query: 'What are the latest advances in quantum computing?'
    }
  }
});

console.log('Research complete:', result.state.data.analysis);
console.log('Confidence:', result.state.data.confidence);
console.log('Path taken:', result.path.join(' -> '));

API Reference

Exports

Core Classes

  • StateGraph - Main graph class for workflow definition and execution
  • StateGraphEvents - Event types emitted by StateGraph

Node Factories

  • createLLMNode - LLM-based processing node
  • createToolNode - Tool execution node
  • createDecisionNode - Multi-branch decision node
  • createSwitchNode - Switch-case style routing
  • createThresholdNode - Numeric threshold routing
  • createIfElseNode - Binary decision node
  • createMultiConditionNode - Complex multi-condition routing
  • createHumanNode - Human-in-the-loop node

Edge Builders

  • conditionalEdge - Conditional edge builder
  • loopEdge - Loop edge builder
  • createRouter - Dynamic routing helper

Checkpointing

  • MemoryCheckpointer - In-memory checkpoint storage
  • FileCheckpointer - File-based checkpoint storage
  • TimeTravelDebugger - Debug tool for state history

Prebuilt Graphs

  • createAgentWorkflow - Simple agent with tools
  • createChatWorkflow - Conversational workflow
  • createDecisionTree - Multi-level decision tree
  • createPlanExecuteRefineGraph - Plan-execute-refine pattern

Types

All TypeScript types are exported for use in your projects:

import type {
  AgentState,
  NodeDefinition,
  NodeResult,
  EdgeCondition,
  ConditionType,
  ExecutionResult,
  ExecutionOptions
} from '@wundr.io/langgraph-orchestrator';

License

MIT

Links