Engineering the Agentic Stack · Parte 1

Bucles de razonamiento de los AI agents: ReAct, ReWOO y Plan-and-Execute

Traducción automática Este artículo se tradujo automáticamente a partir de la versión original en inglés.

Un agent reasoning loop es el flujo de control que decide cuándo un modelo planifica, hace un tool call, lee el resultado y se detiene. Para un ingeniero que construye un agent, esa elección también es un presupuesto: determina cuántas veces se ejecuta el modelo, cuánto historial lleva cada llamada y si un resultado inesperado puede cambiar la siguiente acción.

En este artículo comparo ReAct, ReWOO y Plan-and-Execute mediante un Market Analyst Agent construido con LangGraph. Te llevarás una regla de enrutamiento y patrones de implementación que podrás adaptar, no solo tres nombres que añadir a un diagrama.

El bucle es la capa más interna de la serie. La memoria, las herramientas, la seguridad, el runtime y las comprobaciones de aceptación lo rodean; no lo sustituyen.

Para una comparación breve de frameworks, consulta Best AI Agent Frameworks in 2026.

El reasoning loop decide qué hacer a continuación. No almacena el estado, ejecuta herramientas ni autoriza efectos secundarios.

El harness es el programa de control entre el modelo y la máquina. Ensambla prompts a partir del estado almacenado (Parte 2), define las acciones que el modelo puede nombrar (Parte 3), autoriza las llamadas (Parte 4) y comprueba las evidencias antes de declarar terminada una tarea (Parte 6). Son problemas de ingeniería distintos, pero un turno pasa por los cuatro.

El runtime (Parte 5) proporciona el registro de sesión, el sandbox, el almacén de checkpoints y las trazas que sobreviven a un único proceso worker.

Dónde encaja cada parte de la serie Engineering the Agentic StackDónde encaja cada parte de la serie Engineering the Agentic Stack

Cada artículo se sostiene por sí mismo. Juntos avanzan desde el bucle hacia las capas exteriores.


Empieza por el límite de fallo

Un buen prompt no resuelve la cuestión del flujo de control. Los patrones se diferencian por cuánto trabajo queda fijado antes del primer tool call. Eso determina el número de llamadas al modelo, cuándo se hace visible un plan defectuoso y si un tool result inesperado puede redirigir la ejecución.

Tres patrones de razonamiento para AI agents

Comparación de ReAct, ReWOO y Plan-and-Execute según el momento en que las evidencias pueden cambiar el planComparación de ReAct, ReWOO y Plan-and-Execute según el momento en que las evidencias pueden cambiar el plan

ReAct: decide después de cada observación

ReAct (Yao et al., 2022), abreviatura de Reason + Act, mantiene la siguiente decisión cerca de la observación más reciente:

  1. Thought: el agent genera un «thought» para descomponer el objetivo y planificar el siguiente paso.
  2. Action: basándose en el thought, hace un tool call.
  3. Observation: el agent lee el resultado, lo que actualiza su comprensión para el siguiente thought.

ReAct lee cada tool result antes de elegir la siguiente acciónReAct lee cada tool result antes de elegir la siguiente acción

Esto proporciona a ReAct varias propiedades útiles:

  • En el ejemplo manual del artículo sobre HotpotQA con PaLM-540B, las observaciones de Wikipedia produjeron menos datos alucinados que el prompting con chain-of-thought.
  • El agent puede cambiar de estrategia sobre la marcha basándose en lo que acaba de observar.
  • El historial de tool calls y observaciones ofrece una traza de ejecución concreta.

El mismo bucle también tiene costes:

  • En una implementación ingenua con historial completo, el historial se reprocesa en cada paso, por lo que la latencia y el coste crecen con la longitud del bucle. La summarization o la truncation pueden limitar ese crecimiento, a costa de descartar contexto.
  • Es ineficiente cuando los tool calls podrían haberse planificado de antemano, que es precisamente el nicho que cubre ReWOO.
  • Sin una condición de parada o un límite de pasos, el bucle puede ejecutarse indefinidamente.

Úsalo para tareas exploratorias, debugging y trabajos en los que no puedas predecir la siguiente acción.

ReWOO: compila primero el grafo de herramientas

ReWOO (Reasoning WithOut Observation) separa la planificación de la ejecución. El planner escribe la secuencia completa de herramientas en un único paso, usando placeholders para valores que solo existen después de la ejecución.

  1. Plan: una llamada al LLM escribe el plan completo de tool calls, usando placeholders de variables (#E1, #E2) para outputs que aún no existen.
  2. Worker: un ejecutor que no es un LLM ejecuta las herramientas planificadas y rellena los placeholders. El worker del artículo sigue el plan; la implementación posterior añade batches paralelos conscientes de las dependencias para los pasos listos.
  3. Solver: una llamada final al LLM toma las observaciones recopiladas y redacta la respuesta.

ReWOO planifica el grafo de dependencias antes de ejecutar las herramientasReWOO planifica el grafo de dependencias antes de ejecutar las herramientas

Esta separación ofrece:

  • Menos llamadas repetidas al modelo que ReAct cuando el plan inicial sigue siendo válido.
  • Menos historial de prompt repetido que en un bucle intercalado con historial completo. La latencia de las herramientas sigue dependiendo de cómo el worker programe las llamadas.
  • El planner puede recibir fine-tuning por separado, sin un entorno real en ejecución.

También crea un límite rígido. En la prueba de estrés de HotpotQA del artículo, todas las herramientas devolvieron No evidence found; ReWOO perdió menos precisión que ReAct porque las observaciones fallidas no hicieron que su planner entrara en otro bucle. Se trata de robustez relativa, no de una política de recuperación durante la ejecución. Una implementación aún debe decidir si un error de herramienta se convierte en evidencia para el solver, activa un retry o aborta la ejecución. ReWOO encaja en workflows predecibles; por sí solo no replanifica alrededor de un grafo inicial defectuoso.

Úsalo para snapshots rápidos, comprobaciones de estado y dashboards cuyo comportamiento de las herramientas sea predecible.

Plan-and-Execute: descompón y reacciona localmente

Plan-and-Solve prompting describe un método de prompting que primero crea un plan y después resuelve las subtareas. Un patrón relacionado de tool orchestration suele denominarse Plan-and-Execute. La guía de Plan-and-Execute de LangChain documenta este patrón:

  1. Fase de planificación: el agent genera primero un plan que divide la tarea en subtareas más pequeñas.
  2. Fase de ejecución: el agent lleva a cabo esas subtareas una a una. Cuando intervienen herramientas, cada subtarea suele ejecutarse como su propio pequeño bucle ReAct, de modo que el executor puede seguir reaccionando a lo que devuelve una herramienta aunque el plan general sea fijo.

El artículo original se centraba en el prompting zero-shot. En una implementación que usa herramientas, el patrón de orquestación puede ejecutar secuencialmente los pasos planificados y utilizar modelos distintos para la planificación y la ejecución. Separar los modelos es una decisión de implementación, no un resultado demostrado por el artículo de Plan-and-Solve.

Plan-and-Execute mantiene un plan general mientras cada paso usa feedbackPlan-and-Execute mantiene un plan general mientras cada paso usa feedback

El patrón resulta útil porque proporciona:

  • Razonamiento jerárquico que refleja cómo un experto humano descompone un proyecto.
  • Un edge de replanning explícito puede pausar la ejecución y reevaluar la situación después de un resultado inesperado.
  • Especialización de modelos. El planner puede ser caro y el executor, barato.
  • Con un checkpointer configurado, cada paso completado puede convertirse en un punto de reanudación.

Sus costes son:

  • Más round trips al modelo que ReWOO cuando cada paso contiene su propio bucle ReAct.
  • Más estado que gestionar.
  • Es excesivo para consultas de un solo paso.

Úsalo para análisis complejos e investigación que necesiten una síntesis final.

Elige según dónde pueda fallar el plan

CaracterísticaReAct (2022)Plan-and-Execute (2023)ReWOO (2023)
Filosofía centralImprovisador: decide el siguiente movimiento a partir del último resultado, una llamada cada vez.Arquitecto: construye un blueprint completo, lo ejecuta y después lo revisa.Optimizador: compila un grafo de dependencias y agrupa las llamadas que están listas.
WorkflowBucle iterativo: Thought → Action → Observation.Dos fases: Fase 1 (Planning), Fase 2 (Execution).Desacoplado: el Planner escribe un grafo de tool calls; el Worker los ejecuta; el Solver compone la respuesta.
AdaptabilidadMáxima: puede cambiar de dirección después de cada tool call.Media: normalmente solo replanifica después de completar un conjunto de pasos.Mínima: el script del planner se ejecuta hasta el final; nada replanifica durante la ejecución.
EficienciaUn bucle ingenuo con historial completo repite más tokens de entrada; la gestión del contexto puede limitar ese crecimiento.El replanning es ocasional en lugar de ejecutarse en cada paso, y el bucle ReAct de cada paso puede empezar con un contexto breve en lugar del historial completo de la ejecución.Menos llamadas al modelo; el worker de este artículo también agrupa las herramientas cuyas dependencias están listas.
Mejor paraExploración abierta o tareas cuyos resultados son impredecibles.Tareas de horizonte largo que requieren mantener un objetivo estable (por ejemplo, escribir un artículo).Workflows estructurados y repetibles (por ejemplo, consultar el tiempo en 5 ciudades).

La tabla sirve para orientar el enrutamiento, no es un benchmark. Usa ReAct cuando una observación pueda cambiar la siguiente acción. Usa Plan-and-Execute cuando la tarea se descomponga limpiamente, pero cada paso siga necesitando feedback. Usa ReWOO cuando todas las dependencias entre herramientas se conozcan antes de la ejecución. En la implementación didáctica siguiente, ese grafo de dependencias permite ejecutar batches en paralelo y al worker detectar un grafo que no puede avanzar. Su helper execute_tool está diseñado deliberadamente para fallar rápido; el código de producción necesita una política explícita de retry, fallback o tratamiento del error como evidencia. Mide los tres patrones con tu modelo, la latencia de las herramientas, tu conjunto de tareas y tu política de reintentos antes de optimizar el número de llamadas.

Un ejemplo completo: el Market Analyst Agent

El Market Analyst Agent hace concreta la diferencia. Un mismo codebase utiliza los tres patrones para market research, y un router elige entre una ruta de deep research y otra de flash briefing. Los fragmentos siguientes son variantes didácticas abreviadas del commit b4e769a: el companion fijado captura una excepción de herramienta y pasa un string de error a su solver, mientras que el worker mostrado aquí deja que esa excepción aborte y lanza un error cuando su grafo de dependencias no puede avanzar. La ruta y la forma del estado son las mismas; aquí la política de errores se hace explícita deliberadamente.

Utiliza LangGraph para la orquestación. Un node es una función de Python que devuelve campos que actualizar en el estado compartido. Un edge declara el siguiente node y puede llamar a una función de enrutamiento. LangGraph fusiona las actualizaciones y puede crear un checkpoint después de cada node, lo que permite reanudar una ejecución. Los tres patrones comparten un único objeto de estado, por lo que el enrutamiento no requiere tres esquemas separados:

El Market Analyst Agent dirige cada petición a uno de dos reasoning loops con un estado compartidoEl Market Analyst Agent dirige cada petición a uno de dos reasoning loops con un estado compartido

El diagrama aísla el enrutamiento y la creación del borrador. Omite el evaluador compartido y la publish gate que se muestran más adelante para que los dos reasoning loops sigan siendo legibles.

Definición del estado

El esquema de estado contiene los campos que necesitan ambos modos:

class PlanStep(BaseModel):
    """A single step in the research plan."""
    step_number: int
    description: str
    tool_hint: str | None = None
    completed: bool = False
    result: str | None = None

class UserProfile(BaseModel):
    """Structured user context loaded from long-term memory."""
    risk_tolerance: str | None = None
    investment_horizon: str | None = None

class AgentState(BaseModel):
    """Main state for the Market Analyst Agent graph."""

    # Identity and profile context for memory-backed personalization
    user_id: str
    user_profile: UserProfile = Field(default_factory=UserProfile)

    # Message history with LangGraph's add_messages reducer
    messages: Annotated[list, add_messages] = Field(default_factory=list)

    # Execution mode (set by router)
    execution_mode: ExecutionMode | None = None

    # Plan-and-Execute state
    plan: list[PlanStep] = Field(default_factory=list)
    current_step_index: int = 0

    # ReWOO state
    rewoo_plan: list[ReWOOPlanStep] = Field(default_factory=list)

    # Research results
    research_data: ResearchData | None = None

    # Final report. Both paths write this field, then the graph pauses before
    # publishing (see interrupt_before below), so a human signs off on a draft
    # a fresh-context evaluator has already voted on.
    draft_report: DraftReport | None = None

Patrón 1: implementación de Plan-and-Execute

Plan-and-Execute encaja con la síntesis en varios pasos. Un planner escribe los pasos de alto nivel y, después, un bucle ReAct ejecuta cada paso y reacciona a los tool results.

El código siguiente fija claude-sonnet-4-5-20250929, que es contra lo que ejecuté estos ejemplos cuando se publicó este artículo, en enero de 2026. Desde entonces, las generaciones de modelos han cambiado. Sustitúyelo por lo que esté disponible actualmente y vuelve a comprobar el comportamiento del enrutamiento con tus propias tareas: el patrón es lo importante, no el ID del modelo.

La implementación mantiene visibles cuatro límites:

  1. Una única fase de planificación inicial. Una sola llamada al LLM produce el plan completo como una lista de descripciones de pasos.
  2. Output guiado por esquema, que valida el plan antes de la ejecución.
  3. Todavía no se ejecutan herramientas. El planner solo decide qué hacer, no cómo hacerlo.
  4. Pasos legibles para humanos. Cada paso es texto que un executor interpretará.
# System prompt guides the LLM to think like a research analyst
# creating a strategic plan, not immediate tool calls
PLANNER_SYSTEM_PROMPT = """You are a senior investment research analyst.
Break down stock analysis requests into 4-6 research steps covering:
1. Current price and basic metrics
2. Recent news and announcements
3. Competitor analysis (if relevant)
4. Financial health assessment
5. Risk factors
6. Investment thesis synthesis

Output as JSON with step_number, description, and tool_hint."""

# Schema-Guided Reasoning: Enforce structure with Pydantic
class PlanOutput(BaseModel):
    """Structured output for the planner."""

    steps: list[PlanStep] = Field(description="Research steps to execute")
    ticker: str = Field(description="The stock ticker being analyzed")

def planner_node(state: AgentState) -> dict:
    """Generate a research plan from the user's request.

    This is Phase 1 of Plan-and-Execute: creating the high-level strategy.
    """

    # Use a powerful model for strategic planning
    llm = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)

    # Ask for a typed plan and validate it before execution.
    # The API can still fail, so production code also handles that exception.
    structured_llm = llm.with_structured_output(PlanOutput)

    # Pull the request out of the message history
    human = [m for m in state.messages if isinstance(m, HumanMessage)]
    last_user_message = human[-1].content if human else "Analyze the market"

    # Context from long-term memory personalizes the plan
    profile_context = f"""
User Profile:
- Risk Tolerance: {state.user_profile.risk_tolerance}
- Investment Horizon: {state.user_profile.investment_horizon}
"""

    # Single LLM call creates the complete plan
    result: PlanOutput = structured_llm.invoke([
        SystemMessage(content=PLANNER_SYSTEM_PROMPT + profile_context),
        HumanMessage(content=f"Create a research plan for: {last_user_message}"),
    ])

    # State update: Store the plan and initialize tracking
    return {
        "plan": result.steps,           # The sequential steps to execute
        "current_step_index": 0,        # Start at step 0
        "research_data": ResearchData(ticker=result.ticker),  # Initialize data container
    }

La línea llm.with_structured_output(PlanOutput) implementa Schema-Guided Reasoning (SGR), que expliqué en un artículo anterior. El esquema permite a la aplicación rechazar un plan mal formado antes de que LangGraph utilice los campos validados para dirigir los edges condicionales.

Patrón 2: ejecución con ReAct

Una vez creado el plan, el executor ejecuta cada paso como su propio bucle ReAct. Esta es la Fase 2: cada paso es lo bastante pequeño como para que el ciclo Thought-Action-Observation se mantenga centrado, y el agent puede reaccionar a cualquier resultado que devuelva la herramienta.

Así encaja la parte de ReAct:

  1. Ejecución iterativa. Un paso cada vez, con feedback de las observaciones.
  2. El bucle Thought-Action-Observation se ejecuta dentro de create_react_agent.
  3. Los resultados de los pasos anteriores se introducen como contexto para el razonamiento actual.
  4. El agent elige las herramientas basándose en la descripción del paso.
  5. Puede cambiar de enfoque durante el paso según lo que devuelva una herramienta.
# The five market-data tools the ReAct agent chooses from here. The repo's
# TOOLS list carries four more — a skill loader, two CLI wrappers, and a
# restricted in-process Python evaluator — covering three of the five tool
# modalities Part 3 compares. MCP is the fourth, and it lives in a sidecar
# rather than in this list.
TOOLS = [
    get_stock_snapshot,
    get_price_history,
    search_news,
    search_competitors,
    get_financials,
]

def executor_node(state: AgentState) -> dict:
    """Execute the current step using a ReAct agent.

    This is Phase 2 of Plan-and-Execute: adaptive execution of each planned step.
    Each step runs as a mini ReAct loop until completion.
    """

    # Get the current step from the plan
    current_step = state.plan[state.current_step_index]

    # Build context from what we've learned so far
    # This matters: each step builds on previous observations
    previous_context = ""
    for step in state.plan[:state.current_step_index]:
        if step.result:
            previous_context += f"\nStep {step.step_number}: {step.result}\n"

    # Create a ReAct agent for this step
    # This companion example pins LangGraph's deprecated create_react_agent API.
    # Current LangChain guidance recommends create_agent instead:
    # https://reference.langchain.com/python/langgraph.prebuilt/chat_agent_executor/create_react_agent
    # The factory name does not change the Thought-Action-Observation loop:
    # 1. Agent generates a "thought" about what tool to call
    # 2. Agent calls the tool ("action")
    # 3. Tool returns result ("observation")
    # 4. Agent decides: call another tool or finish
    react_agent = create_react_agent(
        model=ChatAnthropic(model="claude-sonnet-4-5-20250929"),
        tools=TOOLS,
    )

    # Invoke the ReAct loop for this single step
    # The agent will loop internally until it completes the step
    result = react_agent.invoke({
        "messages": [
            SystemMessage(content=EXECUTOR_SYSTEM_PROMPT),
            HumanMessage(content=f"""Execute Step {current_step.step_number}:
{current_step.description}

Ticker: {state.research_data.ticker}
Previous findings: {previous_context}"""),
        ]
    })

    # Extract the final answer from the ReAct agent's message history
    # The last message contains the synthesis after all tool calls
    updated_plan = list(state.plan)
    updated_plan[state.current_step_index] = PlanStep(
        step_number=current_step.step_number,
        description=current_step.description,
        completed=True,
        result=result["messages"][-1].content,  # Final synthesized answer
    )

    # State update: Mark step complete and advance to next
    return {
        "plan": updated_plan,
        "current_step_index": state.current_step_index + 1,
    }

Patrón 3: ReWOO para snapshots rápidos

Para un briefing rápido, ReWOO elimina las llamadas al modelo de la fase de ejecución. Las herramientas independientes se ejecutan en paralelo; las herramientas dependientes esperan a que se cumplan sus prerrequisitos. El planner emite el grafo de herramientas por adelantado y el worker lo ejecuta sin preguntar al modelo qué debe hacer a continuación.

Su estructura es la siguiente:

  1. Tres fases (Planner → Worker → Solver), sin bucles.
  2. Los tool calls hacen referencia a los placeholders #E1, #E2 para resultados que aún no existen.
  3. No hay ningún LLM durante la ejecución. El worker simplemente ejecuta las herramientas.
  4. Las herramientas independientes se ejecutan en paralelo.
  5. Una única llamada de síntesis al final, sobre todos los datos a la vez.

Fase 1: planner de ReWOO (crea por adelantado el grafo de ejecución completo)

class ReWOOPlanStep(BaseModel):
    """A step in the ReWOO plan with variable placeholders.

    Key difference from Plan-and-Execute's PlanStep:
    - Contains actual tool_name and tool_args (not just description)
    - Uses variable references (#E1) for dependencies
    """
    step_id: str  # e.g., "#E1" - becomes a variable
    description: str
    tool_name: str     # Exact tool to call
    tool_args: dict    # May contain variable refs like {"price": "#E1"}
    depends_on: list[str] = []  # For dependency ordering
    result: str | None = None

class ReWOOPlanOutput(BaseModel):
    """Structured output for ReWOO planner."""
    steps: list[ReWOOPlanStep] = Field(description="Planned tool calls with variables")

def rewoo_planner_node(state: AgentState) -> dict:
    """Generate a complete plan of tool calls upfront.

    This is the key difference from Plan-and-Execute: instead of creating
    human-readable step descriptions, we create EXACT tool calls that
    the worker will execute blindly.
    """

    llm = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)

    # Schema-Guided Reasoning ensures valid tool call specifications
    structured_llm = llm.with_structured_output(ReWOOPlanOutput)

    ticker = state.research_data.ticker if state.research_data else "UNKNOWN"
    human = [m for m in state.messages if isinstance(m, HumanMessage)]
    query = human[-1].content if human else f"Analyze {ticker}"

    # Single LLM call to plan ALL tool executions
    result: ReWOOPlanOutput = structured_llm.invoke([
        SystemMessage(content=REWOO_PLANNER_PROMPT),
        HumanMessage(content=f"""Create a ReWOO plan for: {query}

Ticker: {ticker}

Output tool calls with:
- step_id: Variable name (#E1, #E2, etc.)
- description: What this accomplishes
- tool_name: Exact tool from the list
- tool_args: Dictionary of arguments
- depends_on: List of step_ids this depends on"""),
    ])

    # State update: Store the complete execution plan
    # Worker will execute this without any LLM involvement
    return {"rewoo_plan": result.steps}

Fase 2: worker de ReWOO (ejecuta las herramientas sin razonamiento del LLM)

def rewoo_worker_node(state: AgentState) -> dict:
    """Execute dependency-ready tools in parallel batches (no LLM calls).

    Independent tools share a batch. Dependent tools wait until their
    prerequisites complete. The worker follows the dependency graph and
    does not add LLM calls.
    """

    results = {}        # Results keyed by step_id (e.g., "#E1": "$150.23")
    updated_steps = []  # Plan steps with their result field filled in
    pending = {step.step_id: step for step in state.rewoo_plan}

    # Keep scheduling dependency-ready batches until the graph is complete.
    # This handles chains even when the planner does not list them topologically.
    with ThreadPoolExecutor(max_workers=5) as executor:
        while pending:
            ready = [
                step for step in pending.values()
                if all(dep in results for dep in step.depends_on)
            ]
            if not ready:
                unresolved = ", ".join(pending)
                raise ValueError(f"Unresolvable ReWOO dependencies: {unresolved}")

            futures = {
                executor.submit(execute_tool, step, results): step
                for step in ready
            }
            for future in as_completed(futures):
                step = futures[future]
                results[step.step_id] = future.result()
                updated_steps.append(step.model_copy(update={"result": results[step.step_id]}))
                del pending[step.step_id]

    # State update: restore the planner's order (sorting on step_id would put
    # "#E10" before "#E2") and hand the filled-in plan to the Solver
    plan_order = {s.step_id: i for i, s in enumerate(state.rewoo_plan)}
    return {"rewoo_plan": sorted(updated_steps, key=lambda s: plan_order[s.step_id])}

Fase 3: solver de ReWOO (sintetiza todos los resultados en una única llamada al LLM)

def rewoo_solver_node(state: AgentState) -> dict:
    """Synthesize all tool results into a flash briefing.

    This is the second efficiency gain: Instead of interleaving
    LLM calls with tool execution (like ReAct), we make ONE
    final synthesis call with all gathered data.
    """

    # Build context from ALL tool results at once
    tool_results = []
    for step in state.rewoo_plan:
        if step.result:
            tool_results.append(f"### {step.description}\n{step.result}")

    context = "\n\n".join(tool_results)

    # Single LLM call to synthesize everything
    llm = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)
    structured_llm = llm.with_structured_output(FlashBriefingOutput)
    result = structured_llm.invoke([
        SystemMessage(content=REWOO_SOLVER_PROMPT),
        HumanMessage(content=f"Create a flash briefing from this data:\n\n{context}"),
    ])

    # FlashBriefingOutput and DraftReport carry the same fields; the state
    # schema expects DraftReport, so convert before returning.
    return {"draft_report": DraftReport(**result.model_dump())}

Las tres fases tienen un contrato sencillo: el planner crea llamadas ejecutables, el worker rellena sus placeholders y el solver recibe los resultados completados. Si el grafo no se puede resolver, se lanza un error antes de ejecutar el solver; después, una política de retry de LangGraph o un edge de fallo puede decidir si se debe volver a planificar. Este contrato solo es eficiente cuando las suposiciones del planner sobreviven al contacto con las herramientas.

Dónde llama cada patrón al modelo

PatrónLlamadas al LLM durante la ejecuciónActualizaciones del estadoPatrón de código clave
Plan-and-Execute1 para planificar + un bucle ReAct por paso (varias llamadas cada uno) + 1 para el informeFinalización secuencial de pasosplanner_node() → loop: executor_node()reporter_node()
ReAct (dentro de cada paso)Varias por paso (ciclos de thought-action)Historial acumulado de mensajesEl companion fijado usa el obsoleto create_react_agent()
ReWOO1 para planificar + 0 durante la ejecución + 1 para la síntesisBatches de herramientas conscientes de las dependenciasrewoo_planner_node()rewoo_worker_node()rewoo_solver_node()

La diferencia importante está en el output del planner. Determina cuánta discreción conserva el executor:

  1. Plan-and-Execute crea descripciones de pasos legibles para humanos:

    # Planner output (list of PlanStep objects)
    plan = [
        PlanStep(
            step_number=1,
            description="Get current price and key financial metrics",
            tool_hint="get_stock_snapshot"
        ),
        PlanStep(
            step_number=2,
            description="Search for recent news and earnings",
            tool_hint="search_news"
        ),
        # ... more steps
    ]

    El executor lee cada descripción y decide qué herramientas utilizar. Es flexible, pero cada paso tiene su propio bucle ReAct, por lo que un paso cuesta varias llamadas al modelo, no una.

  2. ReAct no tiene un plan inicial. Utiliza razonamiento iterativo:

    # No planning phase - ReAct works step-by-step with accumulated messages
    messages = [
        HumanMessage(content="Execute Step 1: Get current price"),
        AIMessage(content="I'll call get_stock_snapshot"),
        ToolMessage(tool_call_id="1", content="$132.45"),
        AIMessage(content="Now I need metrics..."),
        # ... agent continues until step complete
    ]

    En esta implementación con historial completo, cada llamada al modelo vuelve a leer un historial que crece durante toda la tarea. Un bucle de producción puede resumirlo o truncarlo. Plan-and-Execute también ejecuta bucles ReAct, pero cada uno empieza con la descripción de su paso y un resumen de los hallazgos anteriores, no con todo el historial de tool calls.

  3. ReWOO crea especificaciones de tool calls explícitas y ejecutables:

    # Planner output (list of ReWOOPlanStep objects)
    rewoo_plan = [
        ReWOOPlanStep(
            step_id="#E1",
            tool_name="get_stock_snapshot",
            tool_args={"ticker": "NVDA"}
        ),
        ReWOOPlanStep(
            step_id="#E2",
            tool_name="search_news",
            tool_args={"query": "NVDA earnings", "limit": 5}
        ),
        # ... all tool calls planned upfront
    ]

    El worker trabaja a ciegas, sin intervención del LLM. Todas las llamadas al modelo se concentran en el planner y el solver, lo que hace predecible su número.

Flujo de memoria y estado:

  • Plan-and-Execute: el estado pasa por plancurrent_step_indexresearch_data.
  • ReAct: el estado se acumula en el array messages (el historial completo de la conversación).
  • ReWOO: el estado pasa por rewoo_plan, cuyos campos result rellena el worker.

Conecta ambas rutas en un único grafo

El grafo tiene dos rutas expuestas al usuario sobre un único AgentState: la deep research utiliza Plan-and-Execute con un bucle ReAct dentro de cada paso, mientras que el flash briefing utiliza ReWOO. Aquí ReAct es una primitiva de ejecución, no una tercera ruta.

Esta implementación no tiene replanner: ejecuta el plan inicial hasta el final. Añadir replanning requeriría un edge desde executor de vuelta a planner y una regla que determinase cuándo un resultado inesperado justifica otra llamada al modelo.

LangGraph mantiene declarativa la conexión:

def create_graph(checkpointer=None):
    builder = StateGraph(AgentState)

    # Add nodes
    builder.add_node("router", router_node)
    builder.add_node("planner", planner_node)
    builder.add_node("executor", executor_node)
    builder.add_node("reporter", reporter_node)
    builder.add_node("rewoo_planner", rewoo_planner_node)
    builder.add_node("rewoo_worker", rewoo_worker_node)
    builder.add_node("rewoo_solver", rewoo_solver_node)
    builder.add_node("evaluator", evaluator_node)
    builder.add_node("publish", publish_node)

    # Define edges
    builder.add_edge(START, "router")
    builder.add_conditional_edges("router", route_after_router, {
        "planner": "planner",
        "rewoo_planner": "rewoo_planner",
    })

    # Deep Research path
    builder.add_edge("planner", "executor")
    builder.add_conditional_edges("executor", route_after_executor, {
        "executor": "executor",  # Loop back for more steps
        "reporter": "reporter",  # Done with plan
    })
    builder.add_edge("reporter", "evaluator")

    # Flash Briefing path (ReWOO)
    builder.add_edge("rewoo_planner", "rewoo_worker")
    builder.add_edge("rewoo_worker", "rewoo_solver")
    builder.add_edge("rewoo_solver", "evaluator")

    # Both paths converge on the same acceptance check
    builder.add_edge("evaluator", "publish")
    builder.add_edge("publish", END)

    return builder.compile(
        checkpointer=checkpointer,
        # Human-in-the-loop pause: the reporter (or the ReWOO solver) writes a
        # draft, a fresh-context evaluator — a second model session with no
        # history of the run — votes on it, and the graph stops before publish.
        # The human approves a report that already carries an evaluator's
        # verdict rather than adjudicating raw research. The verdict is
        # advisory here — the graph routes to the interrupt either way.
        # Part 6 takes up how an acceptance check decides that a run is done.
        interrupt_before=["publish"],
    )

Selección automática del patrón mediante un router

El router asigna la forma de la petición a una ruta. Schema-Guided Reasoning restringe el output del clasificador:

class ExecutionMode(str, Enum):
    """Execution mode for the agent."""

    DEEP_RESEARCH = "deep_research"  # Plan-and-Execute + ReAct (thorough)
    FLASH_BRIEFING = "flash_briefing"  # ReWOO (fast, token-efficient)

class RouterOutput(BaseModel):
    """Structured output for the router."""

    mode: ExecutionMode  # DEEP_RESEARCH or FLASH_BRIEFING
    ticker: str
    reasoning: str

ROUTER_SYSTEM_PROMPT = """Classify the user's request:

1. **deep_research**: Complex analysis requiring synthesis
   - Examples: "Analyze strategic risks", "investment thesis"

2. **flash_briefing**: Quick snapshots, simple data retrieval
   - Examples: "quick snapshot", "current price"

Default to deep_research if unclear."""

llm = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)
structured_llm = llm.with_structured_output(RouterOutput)

Con este router, «current price» se dirige a ReWOO y «investment thesis» a Plan-and-Execute. Por defecto, las peticiones ambiguas van a deep research. Antes de poner el router delante de los usuarios, reproduce un conjunto de tareas en ambas rutas y compara las llamadas al modelo, el tiempo de pared, los fallos y el comportamiento de recuperación.

La implementación completa del companion, incluido el router y el estado compartido, está en el commit fijado del Market Analyst Agent.

La siguiente capa es la memoria

La Parte 2, AI Agent Memory Architecture, separa los checkpoints reanudables del conocimiento entre sesiones y los documentos del proyecto. Sin esa capa de estado, el router y el executor anteriores solo funcionan mientras un único proceso y una única ventana de contexto permanezcan activos.

Referencias