Recipes · agents

Planner / Executor

Patrón de dos etapas: un LLM planifica los pasos, otro los ejecuta. Reduce alucinaciones en tareas complejas al separar el razonamiento de la acción.

claude-opus-4-5agentmulti-stepplanningtypescriptActualizado 2026-04-23

Concepto

En lugar de pedir a un solo modelo que razone y actúe al mismo tiempo, separamos:

  1. Planner — toma el objetivo y produce un plan estructurado (JSON o lista numerada).
  2. Executor — toma cada paso del plan y lo ejecuta con herramientas.

Esto reduce las alucinaciones porque el modelo planificador no tiene acceso a herramientas y no puede "impacientar" ejecutando pasos precipitados.

System Prompt — Planner

You are a planning agent. Your job is to take a high-level goal and break it
down into a precise, ordered list of steps that a separate executor agent will
carry out.

## Output format
Always respond with valid JSON matching this schema:
{
  "goal": "<restate the goal clearly>",
  "steps": [
    {
      "id": 1,
      "action": "<imperative verb phrase>",
      "depends_on": [],
      "notes": "<optional context for the executor>"
    }
  ]
}

## Rules
- Each step must be atomic — one action, one outcome.
- Never include steps you are uncertain about. If the goal is ambiguous, ask
  one clarifying question before producing the plan.
- Keep steps under 10 unless truly necessary.
- Do not suggest tools by name — the executor decides how to implement each step.

System Prompt — Executor

You are an executor agent. You will be given one step from a plan and must
carry it out using the available tools.

## Input format
You will receive:
- The overall goal (for context)
- The current step: { id, action, notes }
- Results of previous steps (if any)

## Rules
- Execute exactly the step described — do not improvise or skip ahead.
- If a step is impossible to execute, report why clearly and stop.
- Return a structured result:
  { "step_id": N, "status": "success" | "failed", "output": "..." }

Implementación (TypeScript)

typescript
import Anthropic from "@anthropic-ai/sdk";
 
const client = new Anthropic();
 
interface Step {
  id: number;
  action: string;
  depends_on: number[];
  notes?: string;
}
 
interface Plan {
  goal: string;
  steps: Step[];
}
 
interface StepResult {
  step_id: number;
  status: "success" | "failed";
  output: string;
}
 
async function plan(goal: string): Promise<Plan> {
  const response = await client.messages.create({
    model: "claude-opus-4-5",
    max_tokens: 2048,
    system: PLANNER_SYSTEM_PROMPT,
    messages: [{ role: "user", content: goal }],
  });
 
  const text = response.content
    .filter((b): b is Anthropic.TextBlock => b.type === "text")
    .map((b) => b.text)
    .join("");
 
  return JSON.parse(text) as Plan;
}
 
async function executeStep(
  plan: Plan,
  step: Step,
  previousResults: StepResult[],
  tools: Anthropic.Tool[],
  executeTool: (name: string, input: unknown) => Promise<string>,
): Promise<StepResult> {
  const messages: Anthropic.MessageParam[] = [
    {
      role: "user",
      content: JSON.stringify({
        goal: plan.goal,
        current_step: step,
        previous_results: previousResults,
      }),
    },
  ];
 
  const response = await client.messages.create({
    model: "claude-opus-4-5",
    max_tokens: 4096,
    system: EXECUTOR_SYSTEM_PROMPT,
    tools,
    messages,
  });
 
  // Handle tool calls (simplified — use full agentic loop for complex steps)
  for (const block of response.content) {
    if (block.type === "tool_use") {
      await executeTool(block.name, block.input);
    }
  }
 
  const text = response.content
    .filter((b): b is Anthropic.TextBlock => b.type === "text")
    .map((b) => b.text)
    .join("");
 
  try {
    return JSON.parse(text) as StepResult;
  } catch {
    return { step_id: step.id, status: "success", output: text };
  }
}
 
// Orchestrator
async function runPlannerExecutor(
  goal: string,
  tools: Anthropic.Tool[],
  executeTool: (name: string, input: unknown) => Promise<string>,
) {
  const thePlan = await plan(goal);
  console.log("Plan:", JSON.stringify(thePlan, null, 2));
 
  const results: StepResult[] = [];
  for (const step of thePlan.steps) {
    const result = await executeStep(thePlan, step, results, tools, executeTool);
    results.push(result);
 
    if (result.status === "failed") {
      console.error(`Step ${step.id} failed:`, result.output);
      break;
    }
  }
 
  return results;
}

Ventajas vs. loop simple

AspectoAgentic Loop simplePlanner / Executor
CosteMenor (un modelo)Mayor (dos pasadas)
TransparenciaEl plan emerge implícitamenteEl plan es explícito y auditable
Corrección humanaDifícil de interrumpirSe puede revisar el plan antes de ejecutar
Tareas largasSe puede perder en el contextoCada paso tiene contexto fresco

Antes de ejecutar el plan, muéstralo al usuario para que lo apruebe. Esto evita ejecutar pasos costosos o destructivos basados en un malentendido del objetivo.