Engineering the Agentic Stack · Parte 1

Reasoning loops de agentes de AI: ReAct, ReWOO, 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 reasoning loop de agente es el flujo de control que decide cuándo un modelo elige una acción, recibe un tool result y vuelve a elegir, o se detiene. Este artículo está dirigido a ingenieros que deciden cómo debe gestionar un agente un trabajo cuyo siguiente paso puede depender de lo que encuentren sus herramientas. Aprenderás cuándo usar ReAct, ReWOO o Plan-and-Execute, y cómo se reflejan esas decisiones en una implementación con LangGraph.

Una petición del precio actual de una acción tiene pasos de consulta conocidos. Investigar por qué han cambiado sus perspectivas puede exigir que la siguiente búsqueda dependa del primer resultado. El modelo necesita un grado de libertad distinto en esas dos tareas, y la segunda suele requerir más llamadas al modelo.

Esta es la Parte 1 de Engineering the Agentic Stack. El loop es su capa más interna; las partes siguientes tratan la memoria, las herramientas, la seguridad, el runtime y las comprobaciones previas a dar una tarea por terminada. El ejemplo práctico es un Market Analyst Agent construido con LangGraph.

Para una comparación breve de los frameworks que implementan estos loops, consulta Best AI Agent Frameworks in 2026.

Las decisiones que debe tomar un modelo

Enumera las decisiones que quedan una vez definidos la petición y sus herramientas. Para consultar un precio, la aplicación puede llamar a la herramienta de precios y pasar su resultado a un modelo para que lo explique. Si el usuario pide un briefing con precio, datos financieros y noticias recientes, un planificador puede nombrar de antemano esas llamadas independientes. Si una noticia sobre la reformulación de unos resultados exige una nueva consulta, una lista fija ya no basta: el modelo debe ver el resultado antes de elegir esa consulta.

Tres preguntas determinan el loop:

  1. ¿Puede el código nombrar ahora las llamadas? Si puede, usa un workflow fijo. Un modelo puede seguir resumiendo los resultados, pero no necesita decidir el orden de las consultas.
  2. Si un modelo debe elegir las llamadas, ¿puede nombrarlas todas antes de ver resultados? ReWOO puede ejecutar ese plan sin volver al modelo entre llamadas. Su worker puede ejecutar a la vez las llamadas independientes.
  3. ¿Puede un resultado cambiar la siguiente llamada o un objetivo posterior? ReAct da al modelo otra decisión después de cada observación. Plan-and-Execute añade una lista previa de objetivos alrededor de esos loops locales; cambiar objetivos posteriores requiere un paso de replanificación aparte.

La elección también decide dónde aparece un fallo. Un plan ReWOO incorrecto puede recorrer varias llamadas antes de que el solver vea un problema. Un loop ReAct puede recuperarse de un resultado inesperado, pero también puede desviarse o repetir trabajo. Un plan previo facilita el seguimiento de una tarea larga, pero un executor local no puede corregir en silencio un plan general equivocado. Cada patrón sigue necesitando una regla de parada, permisos para las herramientas y una forma de informar de las llamadas fallidas; el loop por sí mismo no ofrece ninguna de esas garantías.

Qué hace cada patrón con un resultado

Tres líneas de tiempo con las mismas tres tool calls: en ReAct cada resultado vuelve al modelo antes de la siguiente llamada; en ReWOO el planner nombra todas las llamadas y los resultados solo llegan a un modelo en el solver; en Plan-and-Execute un resultado solo puede cambiar las llamadas dentro de su objetivoTres líneas de tiempo con las mismas tres tool calls: en ReAct cada resultado vuelve al modelo antes de la siguiente llamada; en ReWOO el planner nombra todas las llamadas y los resultados solo llegan a un modelo en el solver; en Plan-and-Execute un resultado solo puede cambiar las llamadas dentro de su objetivo

Los tres patrones llaman a las mismas herramientas. Se diferencian en cuándo un tool result puede cambiar el plan: después de cada llamada, solo después de la ejecución o dentro de cada paso planificado.

ReAct: decidir después de cada observación

ReAct (Yao et al., 2022), abreviatura de Reason + Act, repite una decisión: el modelo elige un tool call, la aplicación lo ejecuta y el resultado vuelve al modelo. Ante una petición de investigar el descenso del margen de una empresa, un estado financiero podría apuntar a un aumento de costes; la siguiente llamada podría entonces buscar evidencia sobre esos costes. El modelo no tiene que elegir esa segunda llamada antes de ver el estado financiero.

El paper original lo escribe como Thought → Action → Observation. Las APIs nativas de herramientas exponen la llamada propuesta y el tool result, pero no tienen por qué exponer el razonamiento interno del modelo. Pensar entre tool calls es una capacidad aparte del modelo y de la API.

Una ejecución de ReAct con la pregunta sobre la caída del margen: get_financials devuelve costes al alza, ese resultado elige search_news para costes al alza, y el historial que lee el modelo crece con cada llamadaUna ejecución de ReAct con la pregunta sobre la caída del margen: get_financials devuelve costes al alza, ese resultado elige search_news para costes al alza, y el historial que lee el modelo crece con cada llamada

Ese feedback es la ventaja de ReAct: una observación sorprendente puede cambiar la siguiente acción. El historial de tool calls también muestra qué evidencia obtuvo el sistema. En la muestra manual de HotpotQA con PaLM-540B del paper, las observaciones de Wikipedia produjeron menos hechos alucinados que el prompting chain-of-thought. Ese resultado se refiere a su tarea y a su modelo, no a la precisión de cualquier loop que use herramientas.

El coste son las decisiones repetidas. Una implementación con historial completo envía en cada turno una conversación cada vez más larga. El prompt caching puede reducir el procesamiento repetido de la entrada y su coste, pero no elimina las observaciones antiguas de la ventana de contexto. Resumir o truncar sí elimina información. Limita los pasos y el tiempo transcurrido, y comprueba si el agente se recupera de un mal resultado o simplemente sigue buscando.

Usa ReAct para investigación exploratoria, depuración y tareas en las que no puedes predecir la siguiente acción.

ReWOO: compilar primero el grafo de herramientas

ReWOO (Reasoning WithOut Observation) separa la llamada de planificación de un modelo de su respuesta final. El planificador nombra todos los tool calls antes de que llegue ningún resultado. Puede referirse a un resultado futuro como #E1 y después poner #E1 en los argumentos de una llamada posterior. Un worker sin LLM resuelve esos marcadores y ejecuta las llamadas; un modelo solver recibe los resultados reunidos y escribe la respuesta.

Para un snapshot de mercado, el planificador puede pedir a la vez precio, datos financieros y noticias recientes. El worker de este artículo ejecuta en paralelo las llamadas cuyas dependencias están listas. Ese batching es una decisión de implementación; el rasgo definitorio del paper es que el worker sigue un plan sin preguntar al modelo qué hacer a continuación.

Un briefing rápido de ReWOO para NVDA: el planner escribe tres tool calls con sus argumentos, el worker las ejecuta en un único lote paralelo sin modelo, ningún resultado vuelve al plan y el solver escribe draft_reportUn briefing rápido de ReWOO para NVDA: el planner escribe tres tool calls con sus argumentos, el worker las ejecuta en un único lote paralelo sin modelo, ningún resultado vuelve al plan y el solver escribe draft_report

Hay menos llamadas al modelo durante la ejecución, siempre que el plan inicial siga siendo útil. La prueba de estrés del paper en HotpotQA sustituyó cada tool result por No evidence found; ReWOO perdió menos precisión que ReAct en esa prueba. Aun así, no recuperó la evidencia que faltaba. Un worker debe decidir si un error va al solver, se reintenta o detiene la ejecución. El solver no puede corregir una dependencia errónea o una herramienta que falta cuando las llamadas ya se han ejecutado.

Como el planificador nunca lee un tool result, puede ajustarse con fine-tuning o probarse por separado, sin un entorno de herramientas real. Usa ReWOO para snapshots rápidos, comprobaciones de estado y dashboards cuyas herramientas se comportan de forma predecible.

Plan-and-Execute: descomponer y reaccionar localmente

Plan-and-Solve prompting crea primero un plan y después resuelve sus subtareas. El patrón de orquestación de herramientas relacionado, Plan-and-Execute, permite que un planificador enumere objetivos de alto nivel y que un executor los atienda uno a uno. El Market Analyst Agent pide pasos de investigación como comprobar la salud financiera y examinar los riesgos. Dentro de cada paso, un loop ReAct puede elegir herramientas después de ver resultados. La salida del planificador es una lista de objetivos, a diferencia de los tool calls propuestos de ReWOO.

El paper de Plan-and-Solve estudió el prompting zero-shot, no este grafo con herramientas. Elegir modelos distintos para planificar y ejecutar es posible en una implementación; no es un resultado demostrado por ese paper.

Plan-and-Execute durante el objetivo 3: los objetivos 1 y 2 tienen resultados guardados, el executor ejecuta un ReAct loop para el objetivo 3, y un replanificador opcional, en línea discontinua, podría reescribir los objetivos 4 y 5Plan-and-Execute durante el objetivo 3: los objetivos 1 y 2 tienen resultados guardados, el executor ejecuta un ReAct loop para el objetivo 3, y un replanificador opcional, en línea discontinua, podría reescribir los objetivos 4 y 5

La figura incluye un replanificador opcional. El grafo didáctico de abajo no tiene rama de replanificación: los resultados pueden cambiar el trabajo dentro de un paso, pero la lista de pasos posteriores permanece fija. Añade replanificación solo cuando el resultado de un paso deba cambiar esa lista.

En una implementación, el planificador puede usar un modelo más potente y el executor uno más barato, y con un checkpointer cada paso completado se convierte en un punto desde el que reanudar. El patrón también necesita más llamadas al modelo y más estado que una consulta simple, así que úsalo solo cuando el plan explícito ayude al sistema a completar una tarea más grande, como un informe de investigación que termina en una síntesis.

Elige un loop para la tarea y luego prueba su fallo

Señal de la tareaFlujo de control inicialEjemploQué probar
Los pasos de consulta ya se conocenWorkflow fijoPrecio actual de un ticker¿El resultado necesita interpretación, o puede devolverlo el código directamente?
Un modelo puede nombrar cada llamada y dependencia antes de la ejecuciónReWOOPrecio, datos financieros y noticias de un ticker; el tiempo en cinco ciudades¿Qué pasa cuando falla una llamada planificada o el plan nombra una dependencia no válida?
La siguiente llamada depende del último resultadoReActAveriguar por qué cayó el margen de una empresa¿Puede recuperarse de un resultado engañoso y detenerse dentro de un presupuesto limitado?
Una tarea larga tiene objetivos con nombre, y cada uno necesita decisiones localesPlan-and-ExecuteUna tesis de inversión con riesgos y competidores¿Puede el executor completar un paso, y qué pasa cuando un resultado invalida objetivos posteriores?

Es una regla de partida, no un ranking de rendimiento. En particular, el grafo didáctico de Plan-and-Execute no puede revisar objetivos posteriores. El worker de ReWOO ejecuta en paralelo las llamadas con dependencias listas, pero no puede crear una llamada nueva después de ver un resultado. Compara las rutas con las mismas tareas, el mismo comportamiento de las herramientas y el mismo presupuesto antes de elegir por número de llamadas al modelo.

Los patrones describen decisiones que están fuera del modelo. Un modelo que razona entre tool calls sigue necesitando un programa que ejecute esas llamadas, detenga el loop y autorice los efectos. Al comparar loops, cuenta los reasoning tokens facturados además de las llamadas; un modelo puede razonar sin mostrar ese texto.

Para una implementación nueva con LangChain, create_agent proporciona un loop de herramientas guiado por observaciones. El grafo de referencia de abajo usa la API anterior create_react_agent de LangGraph, así que sus fragmentos explican el flujo de control en lugar de servir como guía de configuración para un proyecto nuevo.

Si el trabajo necesita contextos aislados para subagentes, un sistema de archivos y gestión automática del contexto, Deep Agents los empaqueta alrededor del mismo loop de herramientas. Es una elección de harness, no un cuarto patrón de razonamiento. Compara un solo agente con la delegación en tareas que se dividen en trabajo independiente, e incluye en el resultado el coste de coordinación y el contexto perdido.

Un ejemplo práctico: el Market Analyst Agent

El Market Analyst Agent envía una petición de flash briefing por ReWOO: planifica llamadas de datos de mercado, las ejecuta y después sintetiza los resultados. Una petición de investigación más profunda pasa por Plan-and-Execute, con un loop ReAct dentro de cada paso planificado. En esa ruta, ReAct es una primitiva de ejecución, no una tercera ruta para el usuario. Las rutas se diferencian en lo que el planificador pasa al executor: tool calls propuestos exactos en ReWOO, u objetivos que aún requieren elegir herramientas en Plan-and-Execute.

Los fragmentos son variantes didácticas abreviadas del commit b4e769a, no scripts independientes.

Usa LangGraph para la orquestación. Un nodo es una función de Python que devuelve los campos que hay que actualizar en el estado compartido; una arista elige el siguiente nodo. Ambas rutas comparten un esquema de estado, mientras que su progreso vive en campos distintos:

Grafo del Market Analyst Agent: el router escribe execution_mode, cada nodo de las dos rutas escribe sus propios campos de estado, y tanto el reporter como el rewoo_solver escriben draft_reportGrafo del Market Analyst Agent: el router escribe execution_mode, cada nodo de las dos rutas escribe sus propios campos de estado, y tanto el reporter como el rewoo_solver escriben draft_report

El diagrama aísla el enrutamiento y la creación del borrador. Omite el evaluador compartido y la aprobación humana antes de publicar, que sí incluye el cableado del grafo de más abajo.

Definición del estado

El esquema de estado guarda el progreso específico de cada ruta en plan o rewoo_plan y coloca el borrador final de cualquiera de las rutas en draft_report. Los campos de memoria y aprobación aparecen porque ambas rutas comparten el mismo estado de aplicación; no deciden qué loop se ejecuta.

from typing import Literal

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
    report_approved: bool = False
    evaluator_verdict: Literal["pass", "fail", "needs_human"] | None = None
    evaluator_reasons: list[str] = Field(default_factory=list)

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

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

Los fragmentos de referencia mantienen claude-sonnet-4-5-20250929 y la API anterior create_react_agent para seguir siendo comparables con el commit enlazado. Para código nuevo, usa la interfaz create_agent actual. Migrar este grafo también exige probar sus esquemas de planificación, los mensajes internos, el enrutamiento y el comportamiento al reanudar; cambiar solo el nombre del modelo no es esa migración.

El planificador hace una llamada al modelo y devuelve descripciones de pasos. No llama a ninguna herramienta de datos de mercado. El structured output comprueba la forma de la respuesta; sigue haciendo falta una comprobación aparte de que el plan no esté vacío y sea utilizable.

# 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
    }

Esa línea llm.with_structured_output(PlanOutput) es Schema-Guided Reasoning (SGR), tratado en un post anterior. El esquema comprueba los tipos de los campos, pero no si los pasos son utilizables. Antes de ejecutar, exige una lista no vacía y acotada, con números de paso únicos y descripciones claras.

Patrón 2: ejecución con ReAct

Una vez que existe el plan, executor_node pasa un objetivo y un resumen de los resultados anteriores a un loop ReAct interno. El loop interno puede elegir otra herramienta después de ver una observación. Cuando termina, el grafo externo registra el resultado final del paso y avanza; no conserva la transcripción interna en plan.

# Market-data tools available to the inner loop in this excerpt. The repo's
# TOOLS list also has a skill loader, two CLI wrappers, and a restricted Python
# evaluator; Part 3 compares those interfaces, and MCP runs as a sidecar.
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.
    """

    # Guard against an empty or exhausted plan before indexing it
    if state.current_step_index >= len(state.plan):
        raise ValueError("No plan step to execute; check the planner output")

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

    # Build context from what we've learned so far
    # Each step sees a digest of earlier results
    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. The companion pins the older
    # create_react_agent API; new code uses create_agent.
    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=str(result["messages"][-1].text),  # 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, el planificador devuelve tool calls propuestos en lugar de objetivos de investigación. El worker ejecuta sin modelo las llamadas con dependencias listas; el solver hace una llamada al modelo sobre sus resultados. Estos fragmentos muestran cada traspaso.

Fase 1: planificador ReWOO (escribe por adelantado un plan tipado de llamadas propuestas)

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     # Requested tool name; validate it against a registry in production
    tool_args: dict    # Proposed arguments; may contain refs like {"price": "#E1"}
    depends_on: list[str] = []  # Declared ordering; cross-check it against #E placeholders
    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, it creates typed proposed tool calls.
    Production code validates them before execution; this teaching worker does not.
    """

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

    # Schema-Guided Reasoning validates the planner response shape.
    # It does not validate a tool name, its arguments, or its dependencies.
    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"""),
    ])

    # Store the typed proposed-call plan.
    # This teaching worker sends ready steps directly to execute_tool.
    return {"rewoo_plan": result.steps}

Fase 2: worker ReWOO (ejecuta 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 ReWOO (sintetiza todos los resultados en una sola 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())}

El planificador escribe llamadas propuestas, el worker rellena sus marcadores y el solver recibe los resultados. El esquema comprueba su forma, no si el plan puede ejecutarse. El diccionario pending de arriba descarta en silencio los IDs duplicados. Si #E2 usa #E1 en sus argumentos pero omite #E1 en depends_on, ambas llamadas pueden entrar en el mismo lote antes de que exista el primer resultado.

Antes del despacho, rechaza los planes vacíos o demasiado grandes, los IDs duplicados, las dependencias que faltan y los ciclos. Valida los nombres de las herramientas y sus argumentos contra un registro. Inspecciona los marcadores dentro de valores anidados y exige que coincidan con depends_on. Este worker didáctico omite esas comprobaciones. Envía las llamadas listas a execute_tool del código de referencia, cuyas excepciones capturadas se convierten en strings de error. El solver debe tratar un string así como evidencia que falta, no como una consulta correcta. Un grafo que no se puede resolver se detiene antes del solver. La recuperación necesita un resultado de fallo, una arista condicional de replanificación y un límite de intentos.

A qué se compromete el planificador

Ruta en este grafoAntes de la primera llamada de datos de mercadoDespués de un tool resultLlamadas al modelo en esta implementaciónNodos
Deep research: Plan-and-Execute con pasos ReActEl planificador escribe descripciones de objetivosEl loop ReAct interno puede elegir otra llamada dentro de su objetivo actual; los objetivos posteriores siguen fijosUn plan, un loop de modelo y herramientas por paso, un informeplanner_node → executor_node por paso → reporter_node
Flash briefing: ReWOOEl planificador escribe nombres de herramientas, argumentos y dependenciasEl worker sigue el plan; el solver ve los resultados después de la ejecuciónUn plan, ninguna llamada al modelo en el worker, una síntesisrewoo_planner_node → rewoo_worker_node → rewoo_solver_node

Un PlanStep que dice “Investigate recent earnings” deja la elección de herramienta al agente interno. Un ReWOOPlanStep que nombra search_news y aporta su consulta se compromete con esa llamada antes de que llegue ningún resultado. Esa diferencia, y no el nombre del patrón, decide qué observaciones inesperadas pueden cambiar la ejecución. Una junto a otra, las dos salidas del planificador tienen este aspecto:

# Plan-and-Execute: goals; the inner ReAct loop picks the tools.
plan = [
    PlanStep(step_number=1, description="Get current price and key metrics",
             tool_hint="get_stock_snapshot"),
    PlanStep(step_number=2, description="Search for recent news and earnings",
             tool_hint="search_news"),
]

# ReWOO: exact calls, fixed before any result arrives.
rewoo_plan = [
    ReWOOPlanStep(step_id="#E1", description="Read the price and valuation snapshot",
                  tool_name="get_stock_snapshot", tool_args={"ticker": "NVDA"}),
    ReWOOPlanStep(step_id="#E2", description="Find recent NVDA earnings news",
                  tool_name="search_news",
                  tool_args={"query": "NVDA earnings", "max_results": 5}),
]

Un tool_hint es un consejo que el executor puede ignorar. Un tool_name con tool_args es la llamada que hará el worker.

Un loop ReAct independiente puede llevar todos los mensajes anteriores a cada nueva llamada al modelo. Este grafo inicia cada loop interno con su paso actual y un resumen de los pasos completados. El estado externo guarda los resultados de los pasos, no la transcripción interna de tool calls. ReWOO, en cambio, rellena los pasos planificados con resultados y entrega la lista completa a su solver. Son decisiones de gestión del contexto de este código de referencia, no requisitos de los papers originales.

Conectar ambas rutas en un grafo

El grafo tiene dos rutas para el usuario sobre un único AgentState: deep research usa Plan-and-Execute con un loop ReAct dentro de cada paso, mientras que flash briefing usa ReWOO.

Esta implementación no tiene replanificador: ejecuta el plan inicial hasta el final. Añadir replanificación exigiría una arista de executor de vuelta a planner y una regla sobre cuándo un resultado sorprendente justifica otra llamada al modelo.

LangGraph mantiene el cableado declarativo:

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 run the same evaluator before the human approval step.
    builder.add_edge("evaluator", "publish")
    builder.add_edge("publish", END)

    return builder.compile(
        checkpointer=checkpointer,
        # Human-in-the-loop pause: the reporter (or ReWOO solver) writes a
        # draft. A separate model session reads that draft and records its
        # assessment. The graph then stops before publishing, whichever
        # assessment it produced, so a human can review both the draft and
        # assessment. Part 6 explains how the program decides a run is complete.
        interrupt_before=["publish"],
    )

Comprueba si la llamada al modelo del router ayuda

El router clasifica los snapshots rápidos, incluido “current price”, como ReWOO, y las peticiones complejas, como una tesis de inversión, como Plan-and-Execute. Por defecto elige deep research cuando la clasificación no está clara. Schema-Guided Reasoning restringe la salida del clasificador a uno de los dos modos:

class ExecutionMode(str, Enum):
    DEEP_RESEARCH = "deep_research"  # Plan-and-Execute with ReAct steps
    FLASH_BRIEFING = "flash_briefing"  # ReWOO

class RouterOutput(BaseModel):
    mode: ExecutionMode
    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)

Así enruta las peticiones esta aplicación; no es una prueba de que toda consulta de precio necesite un planificador. Para una petición que solo pide el precio, una llamada directa a la herramienta evitaría las llamadas del router, del planificador ReWOO y del solver. Esas llamadas solo se justifican en peticiones que necesitan que un modelo seleccione o explique datos.

Un router también puede enviar una petición al loop equivocado. Compara ambas rutas y un workflow fijo con las mismas tareas, herramientas y presupuesto. Registra la finalización de la tarea, las aceptaciones indebidas, los tokens con y sin caché, el tiempo real y la recuperación tras errores de herramientas u observaciones engañosas. Repite las ejecuciones cuando la elección del modelo sea estocástica y cuenta cada intento iniciado. Un número menor de llamadas solo ayuda si la ruta sigue completando la tarea dentro de sus restricciones. El artículo de ingeniería de Anthropic también recomienda empezar con workflows sencillos y combinables.

La implementación de referencia completa, incluidos el router y el estado compartido, está en el commit b4e769a del Market Analyst Agent.

Prueba la API actual de loop único

El grafo de arriba usa una factory de agentes más antigua de LangGraph. Si quieres examinar primero la API create_agent actual, este pequeño fixture evita los servicios de datos de mercado. Muestra una llamada de modelo y herramienta y su resultado, no una razón para usar un agente en una consulta conocida. En una aplicación que solo responde a esta pregunta, llama directamente a la función de consulta.

Define ANTHROPIC_API_KEY e instala langchain y langchain-anthropic antes de ejecutar el snippet. La invocación hace una petición de pago al modelo.

from langchain.agents import create_agent
from langchain_anthropic import ChatAnthropic

def lookup_fixture_company(ticker: str) -> str:
    """Look up a company name in a local fixture, not live market data."""
    return {"NVDA": "NVIDIA", "AMD": "AMD"}.get(
        ticker.strip().upper(), "No company in the fixture"
    )

agent = create_agent(
    model=ChatAnthropic(model="claude-sonnet-5", max_tokens=4096),
    tools=[lookup_fixture_company],
    system_prompt="Use the fixture for company names. Do not invent market data.",
)
result = agent.invoke({
    "messages": [{"role": "user", "content": "Which company is NVDA in the fixture?"}]
})
print(result["messages"][-1].text)

Si el modelo propone lookup_fixture_company, la biblioteca invoca esa función de Python y le devuelve su resultado. El snippet no garantiza que el modelo use la herramienta ni prueba una comparación de rutas, una política de fallos o la precisión con datos reales.

La siguiente capa es la memoria

El loop decide qué llamada viene después; el resto del harness tiene que aportar contexto, ejecutar y autorizar llamadas, conservar el estado y comprobar que la tarea se ha completado. La Parte 2, arquitectura de memoria de agentes de AI separa los checkpoints reanudables del conocimiento entre sesiones y de los documentos del proyecto. Sin checkpoints duraderos, un reinicio del worker pierde el estado en curso de este grafo. La Parte 3 examina las interfaces de herramientas, la Parte 4 trata la autorización, la Parte 5 trata la ejecución y la recuperación, y la Parte 6 examina la evidencia de que una tarea está terminada.

Un turno de un agente con cada parte de la serie en el punto donde actúa: las herramientas y el contexto alimentan el modelo, el modelo propone una llamada, el permiso la autoriza, el runtime la ejecuta y la guarda, y la comprobación de finalización la acepta o continúaUn turno de un agente con cada parte de la serie en el punto donde actúa: las herramientas y el contexto alimentan el modelo, el modelo propone una llamada, el permiso la autoriza, el runtime la ejecuta y la guarda, y la comprobación de finalización la acepta o continúa

La figura sitúa cada parte en el punto de un turno donde actúa.

Referencias