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 { webSearch, webScraper, calculator } from '@agentforge/tools';
import { z } from 'zod';

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

// Create a ReAct agent for complex research
const researchAgent = createReActAgent({
  model,
  tools: [webSearch, webScraper, calculator],  // Real exports from @agentforge/tools
  maxIterations: 10
});

// Wrap ReAct agent as a tool
const complexResearchTool = toolBuilder()
  .name('complex-research')
  .description('Perform complex research using exploratory reasoning')
  .category(ToolCategory.WEB)  // Use WEB for search/research tools
  .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();

// Note: Define custom tools for summarization and reporting
// These are user-defined tools - replace with your own implementations
const customSummarizeTool = toolBuilder()
  .name('custom-summarize')
  .description('Summarize research findings')
  .category(ToolCategory.UTILITY)  // Use UTILITY for text processing tools
  .schema(z.object({ text: z.string().describe('Text to summarize') }))
  .implement(async ({ text }) => {
    // Your summarization logic here
    return `Summary of: ${text}`;
  })
  .build();

const customReportTool = toolBuilder()
  .name('custom-report')
  .description('Generate formatted report')
  .category(ToolCategory.UTILITY)  // Use UTILITY for text processing tools
  .schema(z.object({ content: z.string().describe('Content to format into a report') }))
  .implement(async ({ content }) => {
    // Your report generation logic here
    return `Report: ${content}`;
  })
  .build();

// Use in Plan-Execute agent
const agent = createPlanExecuteAgent({
  planner: {
    model,
    maxSteps: 5,
    systemPrompt: 'Create a structured research and analysis plan'
  },
  executor: {
    tools: [
      complexResearchTool,  // Uses ReAct internally
      customSummarizeTool,  // Custom tool
      customReportTool      // Custom tool
    ],
    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';
import { toolBuilder, ToolCategory } from '@agentforge/core';
import { webSearch } from '@agentforge/tools';
import { z } from 'zod';

// Note: Define custom tools for content creation workflow
// These are user-defined tools - replace with your own implementations
const customResearchTool = toolBuilder()
  .name('custom-research')
  .description('Research topic')
  .category(ToolCategory.WEB)  // Use WEB for search/research tools
  .schema(z.object({ topic: z.string().describe('Topic to research') }))
  .implement(async ({ topic }) => {
    // Your research logic here (could use webSearch internally)
    return `Research on: ${topic}`;
  })
  .build();

const customOutlineTool = toolBuilder()
  .name('custom-outline')
  .description('Create content outline')
  .category(ToolCategory.UTILITY)  // Use UTILITY for text processing tools
  .schema(z.object({ topic: z.string().describe('Topic to create outline for') }))
  .implement(async ({ topic }) => {
    // Your outline logic here
    return `Outline for: ${topic}`;
  })
  .build();

const customDraftTool = toolBuilder()
  .name('custom-draft')
  .description('Write content draft')
  .category(ToolCategory.UTILITY)  // Use UTILITY for text processing tools
  .schema(z.object({ outline: z.string().describe('Outline to base the draft on') }))
  .implement(async ({ outline }) => {
    // Your drafting logic here
    return `Draft based on: ${outline}`;
  })
  .build();

const customFormatTool = toolBuilder()
  .name('custom-format')
  .description('Format content')
  .category(ToolCategory.UTILITY)  // Use UTILITY for text processing tools
  .schema(z.object({ content: z.string().describe('Content to format') }))
  .implement(async ({ content }) => {
    // Your formatting logic here
    return `Formatted: ${content}`;
  })
  .build();

// Step 1: Execute content creation plan
const executionAgent = createPlanExecuteAgent({
  planner: {
    model,
    maxSteps: 4,
    systemPrompt: 'Plan content creation: research, outline, draft, format'
  },
  executor: {
    tools: [customResearchTool, customOutlineTool, customDraftTool, customFormatTool]  // Custom tools
  }
});

// Step 2: Refine with reflection
const reflectionAgent = createReflectionAgent({
  generator: { model },
  reflector: {
    model,
    systemPrompt: 'Critique content for clarity, accuracy, and engagement'
  },
  reviser: { model },
  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({
    input: 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, createReflectionAgent, createPlanExecuteAgent, createReActAgent } from '@agentforge/patterns';

// Create specialized worker agents
const codeReviewAgent = createReflectionAgent({
  generator: { model },
  reflector: {
    model,
    systemPrompt: 'Review code for bugs, style, and best practices'
  },
  reviser: {
    model,
    systemPrompt: 'Improve code based on review feedback'
  },
  maxIterations: 2
});

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

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

// Create multi-agent system with workers
const system = createMultiAgentSystem({
  supervisor: {
    model,
    strategy: 'skill-based'
  },
  workers: [
    {
      id: 'code-reviewer',
      capabilities: {
        skills: ['code-review', 'quality-check', 'reflection'],
        tools: [],
        available: true,
        currentWorkload: 0
      },
      agent: codeReviewAgent
    },
    {
      id: 'tester',
      capabilities: {
        skills: ['testing', 'qa', 'planning'],
        tools: ['custom-unit-test', 'custom-integration-test', 'custom-e2e-test'],  // Custom tools
        available: true,
        currentWorkload: 0
      },
      agent: testingAgent
    },
    {
      id: 'documenter',
      capabilities: {
        skills: ['documentation', 'examples', 'code-analysis'],
        tools: ['custom-code-analysis', 'custom-example-generator', 'custom-markdown'],  // Custom tools
        available: true,
        currentWorkload: 0
      },
      agent: documentationAgent
    }
  ],
  aggregator: { model }
});

// 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 { ChatOpenAI } from '@langchain/openai';

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

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

// Helper function to execute a step
async function executeTool(step: string): Promise<string> {
  // In a real implementation, this would call actual tools
  const response = await model.invoke(`Execute this step: ${step}`);
  return response.content as string;
}

// Create custom nodes
async function planningNode(state: CustomState): Promise<CustomState> {
  const response = await model.invoke(`Create a plan for: ${state.input}`);
  const plan = (response.content as string).split('\n').filter(line => line.trim());
  return {
    ...state,
    plan,
    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 response = await model.invoke(
    `Summarize results: ${state.results.join('\n')}`
  );
  return { ...state, finalOutput: response.content as string };
}

// 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 model to select relevant tools
  const selection = await model.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({ model, 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({
  model,
  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 model.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';

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

const agent = createPlanExecuteAgent({
  planner: {
    model,
    systemPrompt: 'Create a research plan'
  },
  executor: {
    tools: [cachedResearchTool, ...otherTools],
    model
  }
});

Best Practices ​

1. Start Simple, Then Compose ​

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

// Then enhance if needed
const enhancedAgent = createPlanExecuteAgent({
  planner: {
    model,
    systemPrompt: 'Create a plan to solve the task'
  },
  executor: {
    tools: [wrapAgentAsTool(simpleAgent), ...otherTools],
    model
  }
});

// ❌ Bad - over-engineering from the start
const overEngineered = createMultiAgentSystem({
  supervisor: {
    strategy: 'skill-based',
    model
  },
  workers: [
    {
      id: 'complex-worker',
      capabilities: {
        skills: ['everything'],
        tools: [],
        available: true,
        currentWorkload: 0
      },
      agent: createReflectionAgent({
        generator: { model },
        reflector: { model },
        reviser: { model }
        // Too complex and unnecessary!
      })
    }
  ],
  aggregator: { model }
});

2. Monitor and Debug ​

Add logging to understand workflow:

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

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

3. Handle Failures Gracefully ​

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

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';

// Test individual node components
const testNode = testing(myNode, {
  nodeName: 'my-node',
  mockResponse: { result: 'test' },
  trackInvocations: true
});

// Call the wrapped node directly
await testNode({ input: 'test' });
console.log(testNode.invocations); // Verify behavior

Complete Example: AI Research Assistant ​

Putting it all together:

typescript
import {
  createPlanExecuteAgent,
  createReActAgent,
  createReflectionAgent,
  createMultiAgentSystem
} from '@agentforge/patterns';
import { webSearch, webScraper, calculator, jsonParser } from '@agentforge/tools';

// 1. Create specialized agents
const webResearcher = createReActAgent({
  model,
  tools: [webSearch, webScraper],  // Real exports from @agentforge/tools
  maxIterations: 10
});

const dataAnalyzer = createPlanExecuteAgent({
  planner: { model, maxSteps: 5 },
  executor: { tools: [calculator, jsonParser] }  // Real exports from @agentforge/tools
});

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

// 2. Combine in multi-agent system
const researchSystem = createMultiAgentSystem({
  supervisor: {
    model,
    strategy: 'skill-based'
  },
  workers: [
    {
      id: 'researcher',
      capabilities: {
        skills: ['search', 'gather', 'web-research'],
        tools: [],
        available: true,
        currentWorkload: 0
      },
      agent: webResearcher
    },
    {
      id: 'analyzer',
      capabilities: {
        skills: ['analyze', 'visualize', 'data-processing'],
        tools: [],
        available: true,
        currentWorkload: 0
      },
      agent: dataAnalyzer
    },
    {
      id: 'writer',
      capabilities: {
        skills: ['write', 'report', 'documentation'],
        tools: [],
        available: true,
        currentWorkload: 0
      },
      agent: reportWriter
    }
  ],
  aggregator: { model }
});

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

console.log(result.response);

Next Steps ​

Released under the MIT License.