Episode 4 — Generative AI Engineering / 4.16 — Agent Design Patterns

4.16.a — Planner-Executor Pattern

In one sentence: The Planner-Executor pattern splits complex AI work into two agents — a Planner that decomposes a task into an ordered list of steps, and an Executor that carries out each step using tools and LLM calls — enabling AI systems to tackle multi-step problems that no single prompt can solve.

Navigation: ← 4.16 Overview · 4.16.b — Researcher-Writer →


1. What Is the Planner-Executor Pattern?

The Planner-Executor pattern is an agent design pattern where two distinct agents collaborate on a task:

  1. Planner Agent — receives a high-level task, reasons about it, and produces a structured plan (a list of steps, each with a description, required tool, and expected output).
  2. Executor Agent — takes each step from the plan and executes it — calling tools, making API requests, running code, or generating text.

This separation is powerful because it mirrors how humans solve complex problems: you think before you act. A single LLM call trying to both plan and execute a complex task often loses track of where it is. Splitting the responsibilities keeps each agent focused.

┌────────────────────────────────────────────────────────────────┐
│                  PLANNER-EXECUTOR PATTERN                       │
│                                                                │
│  User: "Analyze sales data and create a trend report"          │
│         │                                                      │
│         ▼                                                      │
│  ┌──────────────┐                                              │
│  │   PLANNER    │  System prompt: "You are a planning agent.   │
│  │   AGENT      │  Break this task into executable steps."     │
│  │              │                                              │
│  │  Receives:   │  Output:                                     │
│  │  - Task      │  ┌──────────────────────────────────────┐    │
│  │  - Available │  │ Plan:                                 │    │
│  │    tools     │  │ 1. load_csv("sales_2024.csv")        │    │
│  │  - Context   │  │ 2. clean_data(remove nulls, types)   │    │
│  │              │  │ 3. calculate_monthly_totals()         │    │
│  │              │  │ 4. detect_trends(window=3months)      │    │
│  │              │  │ 5. generate_chart(type="line")        │    │
│  │              │  │ 6. write_report(format="markdown")    │    │
│  │              │  └──────────────────────────────────────┘    │
│  └──────────────┘                                              │
│         │                                                      │
│         ▼  (plan passed to executor)                           │
│  ┌──────────────┐                                              │
│  │  EXECUTOR    │  System prompt: "You are an execution agent. │
│  │  AGENT       │  Execute each step and report results."      │
│  │              │                                              │
│  │  Step 1 ──► Call load_csv tool ──► data loaded              │
│  │  Step 2 ──► Call clean_data ──► 1,247 rows cleaned          │
│  │  Step 3 ──► Call calculate ──► monthly totals computed       │
│  │  Step 4 ──► Call detect_trends ──► 3 trends found           │
│  │  Step 5 ──► Call generate_chart ──► chart saved             │
│  │  Step 6 ──► Call write_report ──► report.md created         │
│  └──────────────┘                                              │
│         │                                                      │
│         ▼                                                      │
│  Final Result: Trend report with charts                        │
└────────────────────────────────────────────────────────────────┘

2. When to Use This Pattern

The Planner-Executor pattern shines when a task is too complex for a single prompt but has a clear decomposition into sequential or parallel steps.

Use This Pattern WhenDo NOT Use When
Task requires multiple tools in sequenceTask is a simple single-step operation
Task involves data pipelines (load, transform, analyze)You need real-time streaming responses
Task requires code generation + executionThe task is ambiguous and cannot be planned upfront
Steps have dependencies (step 3 needs output of step 2)Latency is critical (planning adds overhead)
You need auditability (log each step)The user expects a conversational back-and-forth
Task involves file operations across multiple files

Real-world examples

  • Data analysis: Load dataset, clean data, compute statistics, generate visualizations, write report
  • Code refactoring: Analyze codebase, identify patterns, create migration plan, refactor files, run tests
  • Research tasks: Identify questions, search multiple sources, cross-reference, synthesize findings
  • Deployment pipelines: Check prerequisites, build, test, deploy, verify, notify

3. The Planner Agent: Decomposing Tasks

The Planner Agent's sole job is to take a high-level task and produce a structured plan. It needs:

  1. A clear system prompt that instructs it to plan, not execute
  2. Knowledge of available tools so it creates actionable steps
  3. Output in a structured format (JSON) so the Executor can parse it

Planner system prompt

const PLANNER_SYSTEM_PROMPT = `You are a Planning Agent. Your job is to break down complex tasks into a step-by-step plan.

RULES:
1. Analyze the user's task carefully.
2. Break it into the SMALLEST meaningful steps.
3. Each step must use exactly ONE tool or action.
4. Steps should be ordered by dependency — if step 3 needs the output of step 2, step 2 must come first.
5. Include a description of what each step does and why.
6. NEVER execute steps yourself — only plan them.

AVAILABLE TOOLS:
- load_csv(filePath): Load a CSV file and return its contents as structured data
- clean_data(data, options): Remove nulls, fix types, handle duplicates
- calculate_stats(data, metrics): Compute statistical metrics (mean, median, sum, trends)
- detect_trends(data, config): Identify trends, anomalies, and patterns
- generate_chart(data, chartType): Create a visualization (line, bar, pie, scatter)
- write_report(sections, format): Compile sections into a formatted report
- search_web(query): Search the web for information
- read_file(path): Read a file from disk
- write_file(path, content): Write content to a file

OUTPUT FORMAT (strict JSON):
{
  "task_summary": "Brief summary of the overall task",
  "total_steps": <number>,
  "steps": [
    {
      "step_number": 1,
      "action": "tool_name",
      "parameters": { ... },
      "description": "What this step does",
      "depends_on": [],
      "expected_output": "What this step should produce"
    }
  ]
}`;

Planner output example

{
  "task_summary": "Analyze Q4 sales data and produce a trend report",
  "total_steps": 6,
  "steps": [
    {
      "step_number": 1,
      "action": "load_csv",
      "parameters": { "filePath": "sales_q4_2025.csv" },
      "description": "Load the raw sales CSV data into memory",
      "depends_on": [],
      "expected_output": "Structured data with columns: date, product, revenue, quantity"
    },
    {
      "step_number": 2,
      "action": "clean_data",
      "parameters": { "options": { "removeNulls": true, "fixTypes": true, "deduplicateBy": "transaction_id" } },
      "description": "Clean the data by removing null rows, fixing column types, and removing duplicates",
      "depends_on": [1],
      "expected_output": "Cleaned dataset with consistent types and no missing values"
    },
    {
      "step_number": 3,
      "action": "calculate_stats",
      "parameters": { "metrics": ["monthly_totals", "product_breakdown", "growth_rate"] },
      "description": "Calculate monthly revenue totals, per-product breakdown, and month-over-month growth",
      "depends_on": [2],
      "expected_output": "Statistical summary with monthly totals and growth percentages"
    },
    {
      "step_number": 4,
      "action": "detect_trends",
      "parameters": { "config": { "window": "monthly", "sensitivity": "medium" } },
      "description": "Identify upward/downward trends and any anomalies in the data",
      "depends_on": [3],
      "expected_output": "List of trends and anomalies with confidence scores"
    },
    {
      "step_number": 5,
      "action": "generate_chart",
      "parameters": { "chartType": "line", "title": "Q4 Revenue Trends" },
      "description": "Create a line chart showing monthly revenue trends",
      "depends_on": [3],
      "expected_output": "Chart saved as revenue_trends.png"
    },
    {
      "step_number": 6,
      "action": "write_report",
      "parameters": { "format": "markdown", "sections": ["summary", "stats", "trends", "charts", "recommendations"] },
      "description": "Compile all analysis into a formatted markdown report",
      "depends_on": [3, 4, 5],
      "expected_output": "Complete trend report saved as q4_report.md"
    }
  ]
}

Notice that steps 3, 4, and 5 all depend on step 2 (cleaned data), and step 6 depends on steps 3, 4, and 5. This dependency graph is critical — the Executor uses it to determine execution order.


4. The Executor Agent: Carrying Out the Plan

The Executor Agent receives the plan and executes each step in dependency order. It:

  1. Resolves dependencies — waits for prerequisite steps to complete
  2. Calls the appropriate tool for each step
  3. Captures output and passes it to dependent steps
  4. Reports status for each step (success, failure, skipped)

Executor system prompt

const EXECUTOR_SYSTEM_PROMPT = `You are an Execution Agent. You receive a plan and execute each step.

RULES:
1. Execute steps in the order provided, respecting dependencies.
2. For each step, call the specified tool with the given parameters.
3. Pass the output of earlier steps as input to later steps when they have dependencies.
4. If a step fails, report the failure with the error message.
5. After all steps, provide a summary of what was accomplished.
6. NEVER modify the plan — execute it as given.

When a step references output from a previous step, use the actual result from that step's execution.`;

5. Full Implementation: Data Analysis Pipeline

Here is a complete implementation of the Planner-Executor pattern for a data analysis pipeline.

import OpenAI from 'openai';

const openai = new OpenAI();

// ─────────────────────────────────────────────────────────
// TOOL DEFINITIONS (simulated for this example)
// ─────────────────────────────────────────────────────────

const tools = {
  load_csv: async ({ filePath }) => {
    console.log(`  [Tool] Loading CSV: ${filePath}`);
    // In production: fs.readFileSync + csv-parser
    return {
      rows: 1500,
      columns: ['date', 'product', 'revenue', 'quantity', 'region'],
      sample: [
        { date: '2025-10-01', product: 'Widget A', revenue: 1200, quantity: 50, region: 'North' },
        { date: '2025-10-01', product: 'Widget B', revenue: 800, quantity: 30, region: 'South' },
      ],
    };
  },

  clean_data: async ({ data, options }) => {
    console.log(`  [Tool] Cleaning data: ${JSON.stringify(options)}`);
    return {
      original_rows: data?.rows || 1500,
      cleaned_rows: 1423,
      removed: { nulls: 52, duplicates: 25 },
      columns_fixed: ['date (string->Date)', 'revenue (string->number)'],
    };
  },

  calculate_stats: async ({ data, metrics }) => {
    console.log(`  [Tool] Calculating stats: ${metrics.join(', ')}`);
    return {
      monthly_totals: {
        'October 2025': 145000,
        'November 2025': 162000,
        'December 2025': 198000,
      },
      product_breakdown: {
        'Widget A': 210000,
        'Widget B': 145000,
        'Widget C': 150000,
      },
      growth_rate: {
        'Oct-Nov': '+11.7%',
        'Nov-Dec': '+22.2%',
      },
    };
  },

  detect_trends: async ({ data, config }) => {
    console.log(`  [Tool] Detecting trends (window: ${config.window})`);
    return {
      trends: [
        { type: 'upward', metric: 'total_revenue', confidence: 0.94, description: 'Consistent month-over-month growth' },
        { type: 'spike', metric: 'widget_a_sales', confidence: 0.87, description: 'December spike likely holiday-driven' },
      ],
      anomalies: [
        { date: '2025-11-15', metric: 'widget_c_revenue', description: 'Unusual dip — possible stock outage' },
      ],
    };
  },

  generate_chart: async ({ data, chartType, title }) => {
    console.log(`  [Tool] Generating ${chartType} chart: "${title}"`);
    return { chartPath: 'output/revenue_trends.png', chartType, title };
  },

  write_report: async ({ sections, format, data }) => {
    console.log(`  [Tool] Writing ${format} report with sections: ${sections.join(', ')}`);
    return {
      filePath: 'output/q4_report.md',
      wordCount: 850,
      sections: sections.length,
    };
  },
};

// ─────────────────────────────────────────────────────────
// PLANNER AGENT
// ─────────────────────────────────────────────────────────

const PLANNER_SYSTEM_PROMPT = `You are a Planning Agent. Break the user's task into a step-by-step plan.

AVAILABLE TOOLS: load_csv, clean_data, calculate_stats, detect_trends, generate_chart, write_report

OUTPUT FORMAT (strict JSON):
{
  "task_summary": "...",
  "total_steps": <number>,
  "steps": [
    {
      "step_number": 1,
      "action": "tool_name",
      "parameters": { ... },
      "description": "...",
      "depends_on": [],
      "expected_output": "..."
    }
  ]
}

RULES:
- Each step uses exactly ONE tool.
- Order steps by dependency.
- Keep steps atomic and specific.
- Return ONLY valid JSON, no markdown fences.`;

async function runPlanner(task) {
  console.log('\n=== PLANNER AGENT ===');
  console.log(`Task: "${task}"\n`);

  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    temperature: 0,
    response_format: { type: 'json_object' },
    messages: [
      { role: 'system', content: PLANNER_SYSTEM_PROMPT },
      { role: 'user', content: task },
    ],
  });

  const plan = JSON.parse(response.choices[0].message.content);
  console.log(`Plan created: ${plan.total_steps} steps`);
  plan.steps.forEach((step) => {
    console.log(`  Step ${step.step_number}: [${step.action}] ${step.description}`);
  });

  return plan;
}

// ─────────────────────────────────────────────────────────
// EXECUTOR AGENT
// ─────────────────────────────────────────────────────────

async function runExecutor(plan) {
  console.log('\n=== EXECUTOR AGENT ===\n');

  const results = {};     // step_number -> result
  const statuses = {};    // step_number -> 'success' | 'failed' | 'skipped'

  for (const step of plan.steps) {
    console.log(`--- Step ${step.step_number}: ${step.action} ---`);
    console.log(`  Description: ${step.description}`);

    // Check dependencies
    const dependenciesMet = step.depends_on.every(
      (dep) => statuses[dep] === 'success'
    );

    if (!dependenciesMet) {
      console.log(`  SKIPPED: Dependencies not met (requires steps: ${step.depends_on.join(', ')})`);
      statuses[step.step_number] = 'skipped';
      continue;
    }

    // Resolve inputs from previous step results
    const toolFn = tools[step.action];
    if (!toolFn) {
      console.log(`  FAILED: Unknown tool "${step.action}"`);
      statuses[step.step_number] = 'failed';
      continue;
    }

    try {
      // Gather outputs from dependency steps
      const dependencyData = step.depends_on.reduce((acc, dep) => {
        acc[`step_${dep}_output`] = results[dep];
        return acc;
      }, {});

      // Merge step parameters with dependency outputs
      const params = {
        ...step.parameters,
        data: dependencyData,
      };

      const result = await toolFn(params);
      results[step.step_number] = result;
      statuses[step.step_number] = 'success';
      console.log(`  SUCCESS:`, JSON.stringify(result, null, 2).slice(0, 200));
    } catch (error) {
      console.log(`  FAILED: ${error.message}`);
      statuses[step.step_number] = 'failed';
      results[step.step_number] = { error: error.message };
    }

    console.log('');
  }

  return { results, statuses };
}

// ─────────────────────────────────────────────────────────
// ORCHESTRATOR (ties Planner + Executor together)
// ─────────────────────────────────────────────────────────

async function planAndExecute(task) {
  // Step 1: Plan
  const plan = await runPlanner(task);

  // Step 2: Execute
  const { results, statuses } = await runExecutor(plan);

  // Step 3: Summarize
  console.log('\n=== EXECUTION SUMMARY ===');
  const succeeded = Object.values(statuses).filter((s) => s === 'success').length;
  const failed = Object.values(statuses).filter((s) => s === 'failed').length;
  const skipped = Object.values(statuses).filter((s) => s === 'skipped').length;

  console.log(`Total steps: ${plan.total_steps}`);
  console.log(`Succeeded: ${succeeded}`);
  console.log(`Failed: ${failed}`);
  console.log(`Skipped: ${skipped}`);

  return { plan, results, statuses };
}

// Run it
planAndExecute('Analyze the Q4 2025 sales data from sales_q4_2025.csv and create a trend report with charts');

Expected output

=== PLANNER AGENT ===
Task: "Analyze the Q4 2025 sales data from sales_q4_2025.csv and create a trend report with charts"

Plan created: 6 steps
  Step 1: [load_csv] Load the Q4 sales CSV file
  Step 2: [clean_data] Clean and validate the dataset
  Step 3: [calculate_stats] Compute monthly totals and growth rates
  Step 4: [detect_trends] Identify trends and anomalies
  Step 5: [generate_chart] Create revenue trend line chart
  Step 6: [write_report] Compile findings into markdown report

=== EXECUTOR AGENT ===

--- Step 1: load_csv ---
  Description: Load the Q4 sales CSV file
  [Tool] Loading CSV: sales_q4_2025.csv
  SUCCESS: { "rows": 1500, "columns": ["date", "product", "revenue", "quantity", "region"] ... }

--- Step 2: clean_data ---
  Description: Clean and validate the dataset
  [Tool] Cleaning data: {"removeNulls":true,"fixTypes":true}
  SUCCESS: { "original_rows": 1500, "cleaned_rows": 1423, "removed": { "nulls": 52, "duplicates": 25 } }

...

=== EXECUTION SUMMARY ===
Total steps: 6
Succeeded: 6
Failed: 0
Skipped: 0

6. Handling Plan Failures and Re-planning

In production systems, steps fail. The Executor must handle failures gracefully, and in some cases the Planner must re-plan based on what went wrong.

Strategy 1: Skip dependent steps

If step 2 fails, skip all steps that depend on step 2. This is what the basic Executor above does.

Strategy 2: Retry with modified parameters

async function executeWithRetry(step, tools, maxRetries = 2) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const result = await tools[step.action](step.parameters);
      return { status: 'success', result, attempts: attempt };
    } catch (error) {
      console.log(`  Attempt ${attempt} failed: ${error.message}`);

      if (attempt === maxRetries) {
        return { status: 'failed', error: error.message, attempts: attempt };
      }

      // Wait before retry (exponential backoff)
      await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
    }
  }
}

Strategy 3: Re-planning on failure

When a step fails and cannot be retried, ask the Planner to create a new plan that works around the failure.

async function replanOnFailure(originalPlan, failedStep, error, completedResults) {
  console.log('\n=== RE-PLANNING ===');
  console.log(`Step ${failedStep.step_number} failed: ${error}`);

  const replanPrompt = `The original plan failed at step ${failedStep.step_number}.

ORIGINAL PLAN:
${JSON.stringify(originalPlan, null, 2)}

COMPLETED STEPS AND RESULTS:
${JSON.stringify(completedResults, null, 2)}

FAILED STEP:
${JSON.stringify(failedStep, null, 2)}

ERROR:
${error}

Create a NEW plan that:
1. Does NOT repeat already-completed steps.
2. Works around the failure (use alternative tools or approaches).
3. Still achieves the original goal: "${originalPlan.task_summary}"`;

  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    temperature: 0,
    response_format: { type: 'json_object' },
    messages: [
      { role: 'system', content: PLANNER_SYSTEM_PROMPT },
      { role: 'user', content: replanPrompt },
    ],
  });

  const newPlan = JSON.parse(response.choices[0].message.content);
  console.log(`New plan created: ${newPlan.total_steps} steps`);
  return newPlan;
}

Strategy 4: Full adaptive executor with re-planning loop

async function adaptiveExecutor(task, maxReplans = 3) {
  let plan = await runPlanner(task);
  let completedResults = {};
  let replanCount = 0;

  while (replanCount <= maxReplans) {
    const { results, statuses } = await runExecutor(plan);

    // Merge newly completed results
    Object.assign(completedResults, results);

    // Check if all steps succeeded
    const failedSteps = plan.steps.filter(
      (step) => statuses[step.step_number] === 'failed'
    );

    if (failedSteps.length === 0) {
      console.log('\nAll steps completed successfully!');
      return { success: true, results: completedResults };
    }

    // Re-plan around failures
    replanCount++;
    if (replanCount > maxReplans) {
      console.log(`\nMax re-plans (${maxReplans}) exceeded. Returning partial results.`);
      return { success: false, results: completedResults, failedSteps };
    }

    plan = await replanOnFailure(
      plan,
      failedSteps[0],
      results[failedSteps[0].step_number]?.error || 'Unknown error',
      completedResults
    );
  }
}

7. Advanced: Parallel Step Execution

When steps have no dependencies on each other, they can run in parallel. This significantly reduces total execution time.

async function runExecutorParallel(plan) {
  console.log('\n=== PARALLEL EXECUTOR ===\n');

  const results = {};
  const statuses = {};
  const completed = new Set();

  // Build a dependency graph
  const pendingSteps = [...plan.steps];

  while (pendingSteps.length > 0) {
    // Find all steps whose dependencies are met
    const readySteps = pendingSteps.filter((step) =>
      step.depends_on.every((dep) => completed.has(dep))
    );

    if (readySteps.length === 0) {
      console.log('DEADLOCK: No steps can proceed. Remaining steps have unmet dependencies.');
      break;
    }

    console.log(`Executing in parallel: steps ${readySteps.map((s) => s.step_number).join(', ')}`);

    // Execute all ready steps in parallel
    const parallelResults = await Promise.allSettled(
      readySteps.map(async (step) => {
        const toolFn = tools[step.action];
        const result = await toolFn(step.parameters);
        return { stepNumber: step.step_number, result };
      })
    );

    // Process results
    for (const outcome of parallelResults) {
      if (outcome.status === 'fulfilled') {
        const { stepNumber, result } = outcome.value;
        results[stepNumber] = result;
        statuses[stepNumber] = 'success';
        completed.add(stepNumber);
      } else {
        // Find the step that failed (by index matching)
        const failedStep = readySteps[parallelResults.indexOf(outcome)];
        statuses[failedStep.step_number] = 'failed';
        results[failedStep.step_number] = { error: outcome.reason?.message };
      }
    }

    // Remove executed steps from pending
    const executedNumbers = new Set(readySteps.map((s) => s.step_number));
    pendingSteps.splice(0, pendingSteps.length, ...pendingSteps.filter((s) => !executedNumbers.has(s.step_number)));
  }

  return { results, statuses };
}

With the plan from section 3, steps 4 and 5 (detect_trends and generate_chart) both depend on step 3 but NOT on each other. They execute in parallel:

Executing in parallel: steps 1
Executing in parallel: steps 2
Executing in parallel: steps 3
Executing in parallel: steps 4, 5    <-- parallel!
Executing in parallel: steps 6

8. Implementation with OpenAI Tool Calling

You can also implement the Planner-Executor pattern using the native tool calling API, where the Planner declares the tools and the model generates tool call requests:

const toolDefinitions = [
  {
    type: 'function',
    function: {
      name: 'create_plan',
      description: 'Create an execution plan for a complex task',
      parameters: {
        type: 'object',
        properties: {
          task_summary: { type: 'string', description: 'Brief summary of the task' },
          steps: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                step_number: { type: 'number' },
                action: { type: 'string' },
                parameters: { type: 'object' },
                description: { type: 'string' },
                depends_on: { type: 'array', items: { type: 'number' } },
              },
              required: ['step_number', 'action', 'description', 'depends_on'],
            },
          },
        },
        required: ['task_summary', 'steps'],
      },
    },
  },
];

async function planWithToolCalling(task) {
  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    temperature: 0,
    tools: toolDefinitions,
    tool_choice: { type: 'function', function: { name: 'create_plan' } },
    messages: [
      {
        role: 'system',
        content: 'You are a planning agent. When given a task, create a detailed execution plan by calling the create_plan tool.',
      },
      { role: 'user', content: task },
    ],
  });

  const toolCall = response.choices[0].message.tool_calls[0];
  const plan = JSON.parse(toolCall.function.arguments);
  return plan;
}

This approach leverages the model's built-in structured output capabilities and reduces the risk of malformed JSON.


9. Design Considerations

Planner prompt engineering

TechniqueWhy
List available tools explicitlyThe Planner can only plan with tools it knows about
Include tool parameter schemasSteps have correct parameter shapes
Specify dependency formatExecutor knows execution order
Add constraints ("max 10 steps")Prevents over-planning on simple tasks
Include examples of good plansFew-shot learning improves plan quality

Executor robustness

ConcernSolution
Tool throws an errorTry/catch per step, report failure, skip dependents
Tool returns unexpected shapeValidate output with Zod before passing to next step
Planner references nonexistent toolCheck tool exists before execution
Circular dependenciesDetect cycles in dependency graph before starting
Step takes too longAdd per-step timeout with Promise.race

Cost and latency

Planner call:  ~500-2000 tokens input, ~500-1500 tokens output
Executor:      One LLM call per step (if Executor uses LLM for reasoning)
               OR zero LLM calls (if Executor is pure tool dispatch)

Total cost = 1 Planner call + N tool executions + (optional) N Executor reasoning calls

For a 6-step plan:
  Pure dispatch: 1 LLM call total (Planner only)
  LLM-assisted:  7 LLM calls total (1 Planner + 6 Executor reasoning)

10. Key Takeaways

  1. Planner-Executor separates thinking from doing — the Planner decomposes the task, the Executor runs each step. This mirrors human problem-solving.
  2. Plans are structured data — JSON with steps, tool names, parameters, and dependencies. This makes plans parseable, loggable, and auditable.
  3. Dependency graphs enable parallelism — steps without mutual dependencies can run concurrently via Promise.allSettled.
  4. Re-planning handles failures gracefully — when a step fails, the Planner creates a new plan that works around the failure using partial results.
  5. Use tool calling for reliable plan structure — OpenAI's tool calling API produces well-formed plans without manual JSON parsing.
  6. Cost scales linearly with plan length — each step is one tool call. Keep plans as short as possible while covering the full task.

Explain-It Challenge

  1. A junior developer asks: "Why can't I just put the entire task in one big prompt?" Explain when single-prompt fails and Planner-Executor succeeds.
  2. You have a 6-step plan where steps 3, 4, 5 all depend on step 2, and step 6 depends on 3, 4, and 5. Draw the dependency graph and explain which steps can run in parallel.
  3. Step 4 of your plan fails. Explain the three strategies for handling this failure (skip dependents, retry, re-plan) and when you would pick each one.

Navigation: ← 4.16 Overview · 4.16.b — Researcher-Writer →