Planning Design Pattern

How agents break down complex tasks, create plans, and execute with structure

The Problem

Many tasks are too complex to complete in a single step. For example: “Plan a 7-day trip to Japan” requires researching destinations, finding flights, booking hotels, creating an itinerary… The agent needs to break down and plan.

Planning Pattern

1. Receive complex task
2. Decompose into subtasks
3. Identify dependency order
4. Execute each subtask
5. Evaluate results
6. Adjust plan if needed

Structured Output With Zod

Instead of letting the agent respond freely, we require a structured plan:

import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

const PlanSchema = z.object({
  goal: z.string().describe("Overall objective"),
  tasks: z.array(z.object({
    id: z.string(),
    title: z.string(),
    description: z.string(),
    dependencies: z.array(z.string()).describe("IDs of tasks that must complete first"),
    estimatedTime: z.string(),
    tools: z.array(z.string()).describe("Tools needed"),
    status: z.enum(["pending", "in_progress", "completed", "failed"])
  })),
  totalEstimate: z.string()
});

const { object: plan } = await generateObject({
  model: openai("gpt-4o"),
  schema: PlanSchema,
  prompt: "Plan a 7-day Japan trip for 2 people with a $3,000 budget"
});

console.log(plan);
// {
//   goal: "7-day Japan trip for 2 people",
//   tasks: [
//     { id: "1", title: "Find flights", dependencies: [], ... },
//     { id: "2", title: "Book hotels", dependencies: ["1"], ... },
//     { id: "3", title: "Create itinerary", dependencies: ["1", "2"], ... },
//   ]
// }

Plan & Execute Pattern

After creating the plan, the agent executes step by step:

import { generateText, tool } from "ai";

async function executePlan(plan: Plan) {
  const completed = new Set<string>();

  for (const task of topologicalSort(plan.tasks)) {
    const depsReady = task.dependencies.every(d => completed.has(d));
    if (!depsReady) continue;

    console.log(`\n📋 Executing: ${task.title}`);

    const result = await generateText({
      model: openai("gpt-4o-mini"),
      tools: {
        searchFlights: tool({ /* ... */ }),
        searchHotels: tool({ /* ... */ }),
        getWeather: tool({ /* ... */ }),
        calculateBudget: tool({ /* ... */ })
      },
      maxSteps: 5,
      system: `You are executing step "${task.title}" in the plan.
Description: ${task.description}
Focus only on this step, nothing else.`,
      prompt: `Execute: ${task.description}`
    });

    completed.add(task.id);
    console.log(`✅ Completed: ${task.title}`);
  }
}

function topologicalSort(tasks: Task[]): Task[] {
  const sorted: Task[] = [];
  const visited = new Set<string>();

  function visit(task: Task) {
    if (visited.has(task.id)) return;
    visited.add(task.id);

    for (const depId of task.dependencies) {
      const dep = tasks.find(t => t.id === depId);
      if (dep) visit(dep);
    }
    sorted.push(task);
  }

  tasks.forEach(visit);
  return sorted;
}

Re-Planning

Smart agents know how to adjust the plan when issues arise:

const replan = tool({
  description: "Adjust the plan when an issue or new information is discovered",
  parameters: z.object({
    reason: z.string().describe("Reason for adjustment"),
    failedTaskId: z.string().optional(),
    proposedChanges: z.array(z.object({
      action: z.enum(["add", "remove", "modify"]),
      taskId: z.string(),
      details: z.string()
    }))
  }),
  execute: async ({ reason, proposedChanges }) => {
    console.log(`🔄 Re-planning: ${reason}`);
    return { status: "plan_updated", changes: proposedChanges };
  }
});

Full Example: Blog Writer Agent

const blogPlanSchema = z.object({
  topic: z.string(),
  outline: z.array(z.object({
    section: z.string(),
    keyPoints: z.array(z.string()),
    requiredResearch: z.boolean()
  })),
  targetWordCount: z.number(),
  tone: z.enum(["professional", "casual", "technical"])
});

// Step 1: Create plan
const { object: blogPlan } = await generateObject({
  model: openai("gpt-4o"),
  schema: blogPlanSchema,
  prompt: "Write a blog post about AI Agents for new developers"
});

// Step 2: Execute each section
for (const section of blogPlan.outline) {
  const result = await generateText({
    model: openai("gpt-4o"),
    tools: section.requiredResearch ? { searchWeb } : {},
    maxSteps: 3,
    prompt: `Write section "${section.section}" with key points: ${section.keyPoints.join(", ")}`
  });
}

Summary

  • Planning pattern decomposes complex tasks into structured subtasks
  • Use Zod + generateObject for structured output
  • Topological sort to handle task dependencies
  • Re-planning when issues arise — agents adapt to reality