Skip to content

Plan-Execute Pattern

The Plan-Execute pattern separates planning from execution. The agent first creates a comprehensive plan, then executes each step systematically. This pattern is ideal for complex, multi-step tasks that benefit from upfront planning.

Overview

Plan-Execute agents work in two distinct phases:

  1. Planning Phase - The LLM creates a detailed, step-by-step plan
  2. Execution Phase - Each step is executed in order using available tools
  3. Re-planning (optional) - Adjust the plan based on execution results

This pattern is inspired by classical AI planning systems and the Plan-and-Solve paper.

When to Use Plan-Execute

Good for:

  • Complex multi-step tasks
  • Tasks requiring sequential execution
  • When you need predictable execution order
  • Tasks that benefit from upfront planning
  • Long-running workflows

Not ideal for:

  • Simple, single-step tasks (use ReAct instead)
  • Highly exploratory tasks (use ReAct instead)
  • When flexibility is more important than structure (use ReAct instead)
  • Real-time interactive applications (use ReAct instead)

Pattern Comparison

Not sure which pattern to use? See the Agent Patterns Overview for a detailed comparison of all patterns.

Basic Usage

typescript
import { createPlanExecuteAgent } from '@agentforge/patterns';
import { ChatOpenAI } from '@langchain/openai';
import { webScraper, calculator, fileWriter } from '@agentforge/tools';

const agent = createPlanExecuteAgent({
  planner: {
    model: new ChatOpenAI({ model: 'gpt-4' }),
    maxSteps: 5
  },
  executor: {
    tools: [webScraper, calculator, fileWriter],
    parallel: false
  },
  maxIterations: 20
});

const result = await agent.invoke({
  input: 'Research the top 5 programming languages in 2026, compare their popularity, and create a summary report.'
});

console.log('Plan:', result.plan);
console.log('Result:', result.response);

Configuration Options

Core Options

typescript
interface PlanExecuteConfig {
  // Required
  planner: {
    model: BaseChatModel;         // Language model for planning
    systemPrompt?: string;        // Custom planning prompt
    maxSteps?: number;            // Max steps in a plan (default: 7)
    includeToolDescriptions?: boolean;  // Include tool info in planning
  };

  executor: {
    tools: Tool[];                // Available tools for execution
    model?: BaseChatModel;        // Optional model for sub-tasks
    parallel?: boolean;           // Enable parallel execution
    stepTimeout?: number;         // Timeout per step (ms)
  };

  // Optional
  replanner?: {
    model: BaseChatModel;         // Model for replanning decisions
    replanThreshold?: number;     // Confidence threshold (0-1)
    systemPrompt?: string;        // Custom replanning prompt
  };

  maxIterations?: number;         // Max planning iterations (default: 5)
  returnIntermediateSteps?: boolean;  // [Not yet implemented] Include execution steps
  verbose?: boolean;              // [Not yet implemented] Enable verbose logging
  checkpointer?: BaseCheckpointSaver;  // State persistence
}

Advanced Configuration

typescript
const agent = createPlanExecuteAgent({
  planner: {
    model: new ChatOpenAI({ model: 'gpt-4' }),
    maxSteps: 5,
    // Custom planning prompt
    systemPrompt: `Create a detailed, step-by-step plan.

Each step should:
- Be specific and actionable
- Specify which tool to use
- Include expected outcomes
- Consider dependencies`
  },
  executor: {
    tools: [webScraper, calculator, fileWriter],
    parallel: false
  },
  replanner: {
    model: new ChatOpenAI({ model: 'gpt-4' }),
    replanThreshold: 0.7
  },
  maxIterations: 30,
  // verbose: true  // [Not yet implemented] Enable verbose logging for detailed execution trace
});

How It Works

1. Planning Phase

The agent creates a structured plan:

typescript
// Example plan generated by the agent
{
  steps: [
    {
      id: "step-1",
      description: "Search for '2026 programming language popularity'",
      tool: "web-search",
      args: { query: "2026 programming language popularity" }
    },
    {
      id: "step-2",
      description: "Extract statistics for top 5 languages",
      tool: "calculator",
      args: { operation: "analyze" },
      dependencies: ["step-1"]
    },
    {
      id: "step-3",
      description: "Write summary report to file",
      tool: "file-write",
      args: { path: "report.md" },
      dependencies: ["step-2"]
    }
  ]
}

2. Execution Phase

Each step is executed sequentially:

typescript
for (const step of plan.steps) {
  const result = await executeTool(step.tool, step.args);

  // Check if re-planning is needed based on state
  if (state.status === 'replanning' || state.iteration > 0) {
    plan = await replan(plan, state.pastSteps);
  }
}

3. Re-planning

If a step fails or produces unexpected results:

typescript
const agent = createPlanExecuteAgent({
  planner: {
    model,
    maxSteps: 10
  },
  executor: {
    tools,
    parallel: false
  },
  replanner: {
    model,
    replanThreshold: 0.7,
    systemPrompt: `Re-evaluate the plan based on execution results.
    If errors occurred, adjust the plan to work around them.
    If confidence is low, create alternative approaches.`
  }
});

Customization

Custom Planning Prompt

typescript
const agent = createPlanExecuteAgent({
  planner: {
    model,
    maxSteps: 10,
    systemPrompt: `You are an expert planner.

Create a comprehensive plan with these requirements:
1. Break down the task into 5-10 clear steps
2. Identify dependencies between steps
3. Specify tools and parameters for each step
4. Include validation checkpoints
5. Plan for error handling

Format your plan as a numbered list with tool specifications.`
  },
  executor: {
    tools,
    parallel: false
  }
});

Custom Execution Strategy

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

const agent = createPlanExecuteAgent({
  planner: {
    model,
    maxSteps: 10
  },
  executor: {
    tools,
    parallel: true,  // Enable parallel execution of independent steps
    stepTimeout: 30000
  }
});

Streaming

Monitor planning and execution in real-time:

typescript
const stream = await agent.stream({
  input: 'Complex research task'
});

for await (const chunk of stream) {
  if (chunk.status === 'planning') {
    console.log('Planning in progress...');
  }
  if (chunk.status === 'executing' && chunk.currentStepIndex !== undefined) {
    const currentStep = chunk.plan?.steps[chunk.currentStepIndex];
    console.log('Executing step:', currentStep?.id);
    console.log('Description:', currentStep?.description);
  }
  if (chunk.pastSteps && chunk.pastSteps.length > 0) {
    const lastStep = chunk.pastSteps[chunk.pastSteps.length - 1];
    console.log('Completed:', lastStep.step.description);
    console.log('Result:', lastStep.result);
  }
}

Best Practices

1. Provide Clear Task Descriptions

The quality of the plan depends on task clarity:

typescript
// ❌ Vague
const result = await agent.invoke({
  input: 'Do some research'
});

// ✅ Clear and specific
const result = await agent.invoke({
  input: `Research the environmental impact of electric vehicles:
    1. Find recent studies (2024-2026)
    2. Compare with traditional vehicles
    3. Include manufacturing and lifecycle data
    4. Create a summary with citations`
});

2. Set Appropriate Step Limits

typescript
const agent = createPlanExecuteAgent({
  planner: {
    model,
    maxSteps: 10
  },
  executor: {
    tools,
    parallel: false
  },
  maxIterations: 25  // Prevent runaway execution
});

3. Enable Re-planning for Robustness

typescript
const agent = createPlanExecuteAgent({
  planner: {
    model,
    maxSteps: 10
  },
  executor: {
    tools,
    parallel: false
  },
  replanner: {
    model,
    replanThreshold: 0.7  // Re-plan if confidence < 70%
  }
});

4. Use Checkpointer for Long Tasks

typescript
import { MemorySaver } from '@langchain/langgraph';

const checkpointer = new MemorySaver();

const agent = createPlanExecuteAgent({
  planner: {
    model,
    maxSteps: 5
  },
  executor: {
    tools,
    parallel: false
  },
  checkpointer  // Enable state persistence for resuming
});

// Use with thread_id to resume conversations
const result = await agent.invoke(
  { input: 'Complex task' },
  { configurable: { thread_id: 'task-123' } }
);

Common Patterns

Research & Report Generation

typescript
import { webScraper, htmlParser, fileWriter } from '@agentforge/tools';

const researchAgent = createPlanExecuteAgent({
  planner: {
    model: new ChatOpenAI({ model: 'gpt-4' }),
    maxSteps: 10,
    systemPrompt: `Create a research plan:

1. Identify key topics to research
2. Search multiple sources for each topic
3. Synthesize findings
4. Generate structured report
5. Save to file with citations`
  },
  executor: {
    tools: [webScraper, htmlParser, fileWriter],
    parallel: false
  },
  maxIterations: 30
});

Data Pipeline

typescript
import { fileReader, jsonParser, csvParser } from '@agentforge/tools';

const pipelineAgent = createPlanExecuteAgent({
  planner: {
    model: new ChatOpenAI({ model: 'gpt-4' }),
    maxSteps: 10,
    systemPrompt: `Create a data processing plan:

1. Read source data
2. Validate and clean data
3. Transform to target schema
4. Write to database
5. Verify data integrity`
  },
  executor: {
    tools: [fileReader, jsonParser, csvParser],
    parallel: false
  }
});

Multi-Source Analysis

typescript
import { httpGet, calculator, jsonParser } from '@agentforge/tools';

// Note: webScraper and chartGenerate are user-defined tools for this example
const analysisAgent = createPlanExecuteAgent({
  planner: {
    model: new ChatOpenAI({ model: 'gpt-4' }),
    maxSteps: 10,
    systemPrompt: `Create an analysis plan:

1. Gather data from all sources
2. Normalize and merge datasets
3. Perform statistical analysis
4. Generate visualizations
5. Create summary report`
  },
  executor: {
    tools: [httpGet, calculator, jsonParser],
    parallel: false
  },
  replanner: {
    model: new ChatOpenAI({ model: 'gpt-4' }),
    replanThreshold: 0.7
  }
});

Debugging

Inspect the Plan

typescript
const result = await agent.invoke(input);

console.log('Generated Plan:');
result.plan.steps.forEach((step, i) => {
  console.log(`${i + 1}. ${step.description}`);
  console.log(`   Tool: ${step.tool || 'none'}`);
  console.log(`   Args: ${JSON.stringify(step.args || {})}`);
  if (step.dependencies) {
    console.log(`   Dependencies: ${step.dependencies.join(', ')}`);
  }
});

Track Execution Progress

typescript
const result = await agent.invoke(input, {
  callbacks: [{
    handleToolStart: (tool, input) => {
      console.log(`Starting: ${tool.name}`, input);
    },
    handleToolEnd: (output) => {
      console.log('Completed:', output);
    },
    handleToolError: (error) => {
      console.error('Error:', error);
    }
  }]
});

Visualize Plan Execution

typescript
const result = await agent.invoke(input);

// Inspect execution steps
console.log('Execution Steps:');
result.pastSteps.forEach((step, i) => {
  console.log(`${i + 1}. ${step.step.description}`);
  console.log(`   Status: ${step.status}`);
  console.log(`   Result: ${step.result}`);
});

Performance Optimization

1. Parallel Execution

Execute independent steps in parallel:

typescript
const agent = createPlanExecuteAgent({
  planner: {
    model,
    maxSteps: 10
  },
  executor: {
    tools,
    parallel: true,  // Execute independent steps concurrently
    stepTimeout: 30000
  }
});

2. Plan Caching

Cache plans for similar tasks using middleware:

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

const cache = createSharedCache({ maxSize: 100 });

// Apply caching to the planner node
// Note: This requires accessing the graph nodes directly
// For simpler caching, consider using the production preset
const agent = createPlanExecuteAgent({
  planner: {
    model,
    maxSteps: 5
  },
  executor: {
    tools,
    parallel: false
  }
});

3. Incremental Execution

Resume from checkpoints:

typescript
import { MemorySaver } from '@langchain/langgraph';

const checkpointer = new MemorySaver();

const agent = createPlanExecuteAgent({
  planner: {
    model,
    maxSteps: 10
  },
  executor: {
    tools,
    parallel: false
  },
  checkpointer
});

// Resume interrupted execution
const result = await agent.invoke(input, {
  configurable: { thread_id: 'task-123' }
});

Comparison with ReAct

FeaturePlan-ExecuteReAct
PlanningUpfront, structuredDynamic, iterative
ExecutionSequentialFlexible
Best forComplex, multi-stepGeneral purpose
PredictabilityHighMedium
AdaptabilityMedium (with re-planning)High
Token usageHigher (planning overhead)Lower

Next Steps

Further Reading

Released under the MIT License.