# Workflows LangGraph-based workflows for multi-step agent orchestration. ## What are Workflows? Workflows are state machines that orchestrate complex multi-step tasks with: - **State Management**: Typed state with annotations - **Conditional Routing**: Different paths based on state - **Validation Loops**: Retry with fixes - **Human-in-the-Loop**: Approval gates and interrupts - **Error Recovery**: Graceful handling of failures Built on [LangGraph.js](https://langchain-ai.github.io/langgraphjs/). ## Directory Structure ``` workflows/ ├── base-workflow.ts # Base class and utilities ├── {workflow-name}/ │ ├── config.yaml # Workflow configuration │ ├── state.ts # State schema (Annotations) │ ├── nodes.ts # Node implementations │ └── graph.ts # StateGraph definition └── README.md # This file ``` ## Workflow Components ### State (state.ts) Defines what data flows through the workflow: ```typescript import { Annotation } from '@langchain/langgraph'; import { BaseWorkflowState } from '../base-workflow.js'; export const MyWorkflowState = Annotation.Root({ ...BaseWorkflowState.spec, // Inherit base fields // Your custom fields input: Annotation(), result: Annotation({ default: () => null }), errorCount: Annotation({ default: () => 0 }), }); export type MyWorkflowStateType = typeof MyWorkflowState.State; ``` ### Nodes (nodes.ts) Functions that transform state: ```typescript export function createMyNode(deps: Dependencies) { return async (state: MyWorkflowStateType): Promise> => { // Do work const result = await doSomething(state.input); // Return partial state update return { result }; }; } ``` ### Graph (graph.ts) Connects nodes with edges: ```typescript import { StateGraph } from '@langchain/langgraph'; import { BaseWorkflow } from '../base-workflow.js'; export class MyWorkflow extends BaseWorkflow { buildGraph(): StateGraph { const graph = new StateGraph(MyWorkflowState); // Add nodes graph .addNode('step1', createStep1Node()) .addNode('step2', createStep2Node()); // Add edges graph .addEdge('__start__', 'step1') .addEdge('step1', 'step2') .addEdge('step2', '__end__'); return graph; } } ``` ### Config (config.yaml) Workflow settings: ```yaml name: my-workflow description: What it does timeout: 300000 # 5 minutes maxRetries: 3 requiresApproval: true approvalNodes: - human_approval # Custom settings myCustomSetting: value ``` ## Common Patterns ### 1. Validation Loop (Retry with Fixes) ```typescript graph .addNode('validate', validateNode) .addNode('fix', fixNode) .addConditionalEdges('validate', (state) => { if (state.isValid) return 'next_step'; if (state.retryCount >= 3) return '__end__'; // Give up return 'fix'; // Try to fix }) .addEdge('fix', 'validate'); // Loop back ``` ### 2. Human-in-the-Loop (Approval) ```typescript const approvalNode = async (state) => { // Send approval request to user's channel await sendToChannel(state.userContext.activeChannel, { type: 'approval_request', data: { action: 'execute_trade', details: state.tradeDetails, } }); // Mark as waiting for approval return { approvalRequested: true, userApproved: false }; }; graph.addConditionalEdges('approval', (state) => { return state.userApproved ? 'execute' : '__end__'; }); // To resume after user input: // const updated = await workflow.execute({ ...state, userApproved: true }); ``` ### 3. Parallel Execution ```typescript import { Branch } from '@langchain/langgraph'; graph .addNode('parallel_start', startNode) .addNode('task_a', taskANode) .addNode('task_b', taskBNode) .addNode('merge', mergeNode); // Branch to parallel tasks graph.addEdge('parallel_start', Branch.parallel(['task_a', 'task_b'])); // Merge results graph .addEdge('task_a', 'merge') .addEdge('task_b', 'merge'); ``` ### 4. Error Recovery ```typescript const resilientNode = async (state) => { try { const result = await riskyOperation(); return { result, error: null }; } catch (error) { logger.error({ error }, 'Operation failed'); return { error: error.message, fallbackUsed: true, result: await fallbackOperation() }; } }; ``` ### 5. Conditional Routing ```typescript graph.addConditionalEdges('decision', (state) => { if (state.score > 0.8) return 'high_confidence'; if (state.score > 0.5) return 'medium_confidence'; return 'low_confidence'; }); graph .addNode('high_confidence', autoApproveNode) .addNode('medium_confidence', humanReviewNode) .addNode('low_confidence', rejectNode); ``` ## Available Workflows ### strategy-validation Validates trading strategies with multiple steps and a validation loop. **Flow:** 1. Code Review (using CodeReviewerSubagent) 2. If issues → Fix Code → loop back 3. Backtest (via MCP) 4. If failed → Fix Code → loop back 5. Risk Assessment 6. Human Approval 7. Final Recommendation **Features:** - Max 3 retry attempts - Multi-file memory from subagent - Risk-based auto-approval - Comprehensive state tracking ### trading-request Human-in-the-loop workflow for trade execution. **Flow:** 1. Analyze market conditions 2. Calculate risk and position size 3. Request human approval (PAUSE) 4. If approved → Execute trade 5. Generate summary **Features:** - Interrupt at approval node - Channel-aware approval UI - Risk validation - Execution confirmation ## Creating a New Workflow ### 1. Create Directory ```bash mkdir -p workflows/my-workflow ``` ### 2. Define State ```typescript // state.ts import { Annotation } from '@langchain/langgraph'; import { BaseWorkflowState } from '../base-workflow.js'; export const MyWorkflowState = Annotation.Root({ ...BaseWorkflowState.spec, // Add your fields input: Annotation(), step1Result: Annotation({ default: () => null }), step2Result: Annotation({ default: () => null }), }); export type MyWorkflowStateType = typeof MyWorkflowState.State; ``` ### 3. Create Nodes ```typescript // nodes.ts import { MyWorkflowStateType } from './state.js'; export function createStep1Node(deps: any) { return async (state: MyWorkflowStateType) => { const result = await doStep1(state.input); return { step1Result: result }; }; } export function createStep2Node(deps: any) { return async (state: MyWorkflowStateType) => { const result = await doStep2(state.step1Result); return { step2Result: result, output: result }; }; } ``` ### 4. Build Graph ```typescript // graph.ts import { StateGraph } from '@langchain/langgraph'; import { BaseWorkflow, WorkflowConfig } from '../base-workflow.js'; import { MyWorkflowState, MyWorkflowStateType } from './state.js'; import { createStep1Node, createStep2Node } from './nodes.js'; export class MyWorkflow extends BaseWorkflow { constructor(config: WorkflowConfig, private deps: any, logger: Logger) { super(config, logger); } buildGraph(): StateGraph { const graph = new StateGraph(MyWorkflowState); const step1 = createStep1Node(this.deps); const step2 = createStep2Node(this.deps); graph .addNode('step1', step1) .addNode('step2', step2) .addEdge('__start__', 'step1') .addEdge('step1', 'step2') .addEdge('step2', '__end__'); return graph; } } ``` ### 5. Create Config ```yaml # config.yaml name: my-workflow description: My workflow description timeout: 60000 maxRetries: 3 requiresApproval: false model: claude-sonnet-4-6 ``` ### 6. Add Factory Function ```typescript // graph.ts (continued) export async function createMyWorkflow( deps: any, logger: Logger, configPath: string ): Promise { const config = await loadYAML(configPath); const workflow = new MyWorkflow(config, deps, logger); workflow.compile(); return workflow; } ``` ## Usage ### Execute Workflow ```typescript import { createMyWorkflow } from './harness/workflows'; const workflow = await createMyWorkflow(deps, logger, configPath); const result = await workflow.execute({ userContext, input: 'my input' }); console.log(result.output); ``` ### Stream Workflow ```typescript for await (const state of workflow.stream({ userContext, input })) { console.log('Current state:', state); } ``` ### With Interrupts (Human-in-the-Loop) ```typescript // Initial execution (pauses at interrupt) const pausedState = await workflow.execute(initialState); // User provides input const userInput = await getUserApproval(); // Resume from paused state const finalState = await workflow.execute({ ...pausedState, userApproved: userInput.approved }); ``` ## Best Practices ### State Design - **Immutable Updates**: Return partial state, don't mutate - **Type Safety**: Use TypeScript annotations - **Defaults**: Provide sensible defaults - **Nullable Fields**: Use `| null` with `default: () => null` ### Node Implementation - **Pure Functions**: Avoid side effects in state logic - **Error Handling**: Catch errors, return error state - **Logging**: Log entry/exit of nodes - **Partial Updates**: Only return fields that changed ### Graph Design - **Single Responsibility**: Each node does one thing - **Clear Flow**: Easy to visualize the graph - **Error Paths**: Handle failures gracefully - **Idempotency**: Safe to retry nodes ### Configuration - **Timeouts**: Set reasonable limits - **Retries**: Don't retry forever - **Approvals**: Mark approval nodes explicitly - **Documentation**: Explain complex config values ## Debugging ### View Graph ```typescript // Get graph structure const compiled = workflow.compile(); console.log(compiled.getGraph()); ``` ### Log State ```typescript const debugNode = async (state) => { logger.debug({ state }, 'Current state'); return {}; // No changes }; graph.addNode('debug', debugNode); ``` ### Test Nodes in Isolation ```typescript const step1 = createStep1Node(deps); const result = await step1({ input: 'test', /* ... */ }); expect(result.step1Result).toBe('expected'); ``` ## References - [LangGraph.js Docs](https://langchain-ai.github.io/langgraphjs/) - [LangChain.js Docs](https://js.langchain.com/) - [Example: strategy-validation](./strategy-validation/graph.ts) - [Example: trading-request](./trading-request/graph.ts)