Skip to content

Advanced Patterns Tutorial

Learn how to combine agent patterns and create custom workflows for complex tasks.

Overview

While individual patterns are powerful, combining them unlocks even more capabilities:

  • Pattern Composition - Use multiple patterns together
  • Custom Workflows - Build tailored agent workflows
  • Hybrid Approaches - Mix pattern strengths
  • Advanced Techniques - Optimize for complex scenarios

Prerequisites

Before starting this tutorial, you should be familiar with:

Pattern Combination Strategies

Strategy 1: Plan-Execute + ReAct

Use Plan-Execute for overall structure, ReAct for complex steps.

When to use:

  • Tasks need structured planning
  • Some steps require exploratory reasoning
  • Mix of predictable and unpredictable sub-tasks

Example: Research and Analysis

typescript
import { createPlanExecuteAgent, createReActAgent } from '@agentforge/patterns';
import { ChatOpenAI } from '@langchain/openai';
import { toolBuilder, ToolCategory } from '@agentforge/core';
import { z } from 'zod';

const llm = new ChatOpenAI({ model: 'gpt-4' });

// Create a ReAct agent for complex research
const researchAgent = createReActAgent({
  llm,
  tools: [webSearchTool, scrapeTool, analyzeTool],
  maxIterations: 10
});

// Wrap ReAct agent as a tool
const complexResearchTool = toolBuilder()
  .name('complex-research')
  .description('Perform complex research using exploratory reasoning')
  .category(ToolCategory.SEARCH)
  .schema(z.object({
    topic: z.string().describe('Research topic'),
    depth: z.enum(['shallow', 'deep']).describe('Research depth')
  }))
  .implement(async ({ topic, depth }) => {
    const result = await researchAgent.invoke({
      messages: [{ role: 'user', content: `Research: ${topic} (${depth})` }]
    });
    return result.response;
  })
  .build();

// Use in Plan-Execute agent
const agent = createPlanExecuteAgent({
  planner: {
    llm,
    maxSteps: 5,
    systemPrompt: 'Create a structured research and analysis plan'
  },
  executor: {
    tools: [
      complexResearchTool,  // Uses ReAct internally
      summarizeTool,
      reportTool
    ],
    parallel: false
  }
});

// Execute
const result = await agent.invoke({
  input: 'Research AI trends and create a comprehensive report'
});

Strategy 2: Reflection + Plan-Execute

Use Plan-Execute for execution, Reflection for quality improvement.

When to use:

  • Quality is critical
  • Output needs iterative refinement
  • Multiple execution attempts acceptable

Example: Content Creation Pipeline

typescript
import { createPlanExecuteAgent, createReflectionAgent } from '@agentforge/patterns';

// Step 1: Execute content creation plan
const executionAgent = createPlanExecuteAgent({
  planner: {
    llm,
    maxSteps: 4,
    systemPrompt: 'Plan content creation: research, outline, draft, format'
  },
  executor: {
    tools: [researchTool, outlineTool, draftTool, formatTool]
  }
});

// Step 2: Refine with reflection
const reflectionAgent = createReflectionAgent({
  generator: { llm },
  reflector: {
    llm,
    systemPrompt: 'Critique content for clarity, accuracy, and engagement'
  },
  reviser: { llm },
  maxIterations: 3
});

// Combined workflow
async function createQualityContent(topic: string) {
  // Execute plan
  const draft = await executionAgent.invoke({
    input: `Create content about: ${topic}`
  });
  
  // Refine with reflection
  const refined = await reflectionAgent.invoke({
    messages: [{ role: 'user', content: draft.response }]
  });
  
  return refined.response;
}

const content = await createQualityContent('AI in Healthcare');

Strategy 3: Multi-Agent + Specialized Patterns

Use Multi-Agent for coordination, specialized patterns for workers.

When to use:

  • Multiple specialized capabilities needed
  • Tasks can be parallelized
  • Different approaches for different sub-tasks

Example: Software Development Team

typescript
import { createMultiAgentSystem, registerWorkers } from '@agentforge/patterns';

// Create specialized worker agents
const codeReviewAgent = createReflectionAgent({
  generator: { llm },
  reflector: {
    llm,
    systemPrompt: 'Review code for bugs, style, and best practices'
  },
  maxIterations: 2
});

const testingAgent = createPlanExecuteAgent({
  planner: {
    llm,
    systemPrompt: 'Plan comprehensive testing strategy'
  },
  executor: {
    tools: [unitTestTool, integrationTestTool, e2eTestTool]
  }
});

const documentationAgent = createReActAgent({
  llm,
  tools: [codeAnalysisTool, exampleGeneratorTool, markdownTool]
});

// Create multi-agent system
const system = createMultiAgentSystem({
  supervisor: {
    llm,
    routingStrategy: 'skill-based'
  },
  workers: [],
  aggregator: { llm }
});

// Register workers
registerWorkers(system, [
  {
    name: 'code-reviewer',
    capabilities: ['code-review', 'quality-check'],
    agent: codeReviewAgent
  },
  {
    name: 'tester',
    capabilities: ['testing', 'qa'],
    agent: testingAgent
  },
  {
    name: 'documenter',
    capabilities: ['documentation', 'examples'],
    agent: documentationAgent
  }
]);

// Use the system
const result = await system.invoke({
  input: 'Review, test, and document the authentication module'
});

Custom Workflows

Building Custom LangGraph Workflows

Create completely custom workflows by composing nodes manually:

typescript
import { StateGraph, END } from '@langchain/langgraph';
import { createReasoningNode, createActionNode } from '@agentforge/patterns';

// Define custom state
interface CustomState {
  input: string;
  plan?: string[];
  currentStep?: number;
  results: string[];
  finalOutput?: string;
}

// Create custom nodes
async function planningNode(state: CustomState): Promise<CustomState> {
  const plan = await llm.invoke(`Create a plan for: ${state.input}`);
  return {
    ...state,
    plan: plan.split('\n'),
    currentStep: 0
  };
}

async function executionNode(state: CustomState): Promise<CustomState> {
  const step = state.plan![state.currentStep!];
  const result = await executeTool(step);

  return {
    ...state,
    results: [...state.results, result],
    currentStep: state.currentStep! + 1
  };
}

async function aggregationNode(state: CustomState): Promise<CustomState> {
  const finalOutput = await llm.invoke(
    `Summarize results: ${state.results.join('\n')}`
  );
  return { ...state, finalOutput };
}

// Build workflow
const workflow = new StateGraph<CustomState>({
  channels: {
    input: { value: (x, y) => y ?? x },
    plan: { value: (x, y) => y ?? x },
    currentStep: { value: (x, y) => y ?? x },
    results: { value: (x, y) => y ?? x },
    finalOutput: { value: (x, y) => y ?? x }
  }
});

workflow
  .addNode('planning', planningNode)
  .addNode('execution', executionNode)
  .addNode('aggregation', aggregationNode);

workflow.addEdge('__start__', 'planning');

workflow.addConditionalEdges(
  'planning',
  (state) => state.plan && state.plan.length > 0 ? 'execution' : END
);

workflow.addConditionalEdges(
  'execution',
  (state) => {
    if (state.currentStep! < state.plan!.length) {
      return 'execution'; // Continue executing
    }
    return 'aggregation'; // Done executing
  }
);

workflow.addEdge('aggregation', END);

const agent = workflow.compile();

Adding Validation and Error Handling

Enhance custom workflows with validation:

typescript
async function validationNode(state: CustomState): Promise<CustomState> {
  const isValid = await validateResults(state.results);

  if (!isValid) {
    return {
      ...state,
      currentStep: 0, // Restart
      results: []
    };
  }

  return state;
}

// Add to workflow
workflow.addNode('validation', validationNode);

workflow.addConditionalEdges(
  'execution',
  (state) => {
    if (state.currentStep! < state.plan!.length) {
      return 'execution';
    }
    return 'validation'; // Validate before aggregation
  }
);

workflow.addConditionalEdges(
  'validation',
  (state) => state.results.length > 0 ? 'aggregation' : 'planning'
);

Advanced Techniques

Technique 1: Dynamic Tool Selection

Dynamically select tools based on context:

typescript
import { ToolRegistry } from '@agentforge/core';

const registry = new ToolRegistry();
registry.registerMany([...allTools]);

async function selectTools(task: string): Promise<Tool[]> {
  // Use LLM to select relevant tools
  const selection = await llm.invoke(
    `Which tools are needed for: ${task}?\nAvailable: ${registry.getAll().map(t => t.metadata.name).join(', ')}`
  );

  const toolNames = parseToolNames(selection);
  return toolNames.map(name => registry.get(name));
}

// Use in agent
const tools = await selectTools('Research and analyze data');
const agent = createReActAgent({ llm, tools });

Technique 2: Adaptive Iteration Limits

Adjust iteration limits based on task complexity:

typescript
function estimateComplexity(task: string): number {
  const keywords = ['complex', 'comprehensive', 'detailed', 'thorough'];
  const matches = keywords.filter(k => task.toLowerCase().includes(k));
  return Math.min(5 + matches.length * 2, 15);
}

const maxIterations = estimateComplexity(userInput);

const agent = createReActAgent({
  llm,
  tools,
  maxIterations
});

Technique 3: Hierarchical Planning

Break complex tasks into hierarchical plans:

typescript
async function hierarchicalPlanning(task: string, depth: number = 0): Promise<Plan> {
  if (depth > 2) return { steps: [task] }; // Max depth

  const plan = await llm.invoke(`Break down: ${task}`);
  const steps = parsePlan(plan);

  const subPlans = await Promise.all(
    steps.map(step =>
      isComplex(step) ? hierarchicalPlanning(step, depth + 1) : { steps: [step] }
    )
  );

  return { steps, subPlans };
}

Technique 4: Result Caching and Reuse

Cache intermediate results for efficiency:

typescript
import { withCache } from '@agentforge/core/middleware';

const cachedResearchTool = withCache(researchTool, {
  ttl: 3600000, // 1 hour
  keyGenerator: (input) => `research:${input.topic}`
});

const agent = createPlanExecuteAgent({
  executor: {
    tools: [cachedResearchTool, ...otherTools]
  }
});

Best Practices

1. Start Simple, Then Compose

typescript
// ✅ Good - start with simple pattern
const simpleAgent = createReActAgent({ llm, tools });

// Then enhance if needed
const enhancedAgent = createPlanExecuteAgent({
  executor: {
    tools: [wrapAgentAsTool(simpleAgent), ...otherTools]
  }
});

// ❌ Bad - over-engineering from the start
const overEngineered = createMultiAgentSystem({
  workers: [
    createReflectionAgent({
      generator: createPlanExecuteAgent({...}),
      // Too complex!
    })
  ]
});

2. Monitor and Debug

Add logging to understand workflow:

typescript
import { withLogging } from '@agentforge/core/middleware';

const loggedNode = withLogging(myNode, {
  name: 'custom-node',
  level: 'debug',
  logInput: true,
  logOutput: true
});

3. Handle Failures Gracefully

typescript
import { withRetry, withErrorHandler } from '@agentforge/core/middleware';

const resilientNode = compose(
  (n) => withRetry(n, { maxAttempts: 3 }),
  (n) => withErrorHandler(n, {
    onError: (error, state) => {
      console.error('Node failed:', error);
      return { ...state, error: error.message };
    }
  })
)(myNode);

4. Test Each Component

Test patterns individually before combining:

typescript
import { testing } from '@agentforge/core/middleware';

// Test individual components
const testAgent = testing(myAgent, {
  mockResponse: { result: 'test' },
  trackInvocations: true
});

await testAgent.invoke({ input: 'test' });
console.log(testAgent.invocations); // Verify behavior

Complete Example: AI Research Assistant

Putting it all together:

typescript
import {
  createPlanExecuteAgent,
  createReActAgent,
  createReflectionAgent,
  createMultiAgentSystem
} from '@agentforge/patterns';

// 1. Create specialized agents
const webResearcher = createReActAgent({
  llm,
  tools: [webSearchTool, scrapeTool],
  maxIterations: 10
});

const dataAnalyzer = createPlanExecuteAgent({
  planner: { llm, maxSteps: 5 },
  executor: { tools: [analyzeTool, visualizeTool] }
});

const reportWriter = createReflectionAgent({
  generator: { llm },
  reflector: { llm },
  maxIterations: 3
});

// 2. Combine in multi-agent system
const researchSystem = createMultiAgentSystem({
  supervisor: {
    llm,
    routingStrategy: 'skill-based'
  },
  workers: [
    { name: 'researcher', capabilities: ['search', 'gather'], agent: webResearcher },
    { name: 'analyzer', capabilities: ['analyze', 'visualize'], agent: dataAnalyzer },
    { name: 'writer', capabilities: ['write', 'report'], agent: reportWriter }
  ],
  aggregator: { llm }
});

// 3. Use the system
const result = await researchSystem.invoke({
  input: 'Research AI trends, analyze data, and create a comprehensive report'
});

console.log(result.finalOutput);

Next Steps

Released under the MIT License.