Skip to Content

ReWOO

from loomflow.architecture import ReWOO, ReWOOPlan, ReWOOStep, ReWOOStepResult

Reasoning WithOut Observation. Xu et al. 2023 , ReWOO: Decoupling Reasoning from Observations for Efficient Augmented Language Models. The cost-saving sibling of PlanAndExecute. Each plan step is a real tool call with {{En}} placeholder substitution, and independent steps run in parallel.

For the conceptual page see ReWOO.


Class signature

class ReWOO: name: str = "rewoo" def __init__( self, *, max_steps: int = 8, planner_prompt: str | None = None, solver_prompt: str | None = None, parallel_levels: bool = True, ) -> None: ...

Constructor parameters

max_steps

Typeint
Default8

Hard cap on plan length. Truncates to max_steps if the planner emits more. Must be >= 1.

planner_prompt

Typestr | None
DefaultNone (uses built-in default)

Override the planner’s system prompt. The default teaches the model to emit a JSON list of steps, each with id, tool, args, and optional depends_on references via {{En}} placeholders.

solver_prompt

Typestr | None
DefaultNone (uses built-in default)

Override the final solver/synthesizer prompt. The default combines the original prompt + the tool results into the final answer.

parallel_levels

Typebool
DefaultTrue

When True (default), independent steps in the same dependency level run concurrently via anyio.create_task_group. Set to False for deterministic test ordering or when your tool provider has tight rate limits and parallel calls trigger throttling.


Methods

declared_workers

Returns {}. Single-agent.

run

Three-phase loop:

  1. Planner. ONE LLM call. Output is a JSON list parsed into a ReWOOPlan. Each step has id, tool, args, depends_on. Args may reference prior steps via {{En}} placeholders. Emits rewoo.planner_started / rewoo.plan_created.
  2. Topological executor. Computes dependency levels; each level runs in parallel (when parallel_levels=True). Cyclic plans abort with rewoo.cycle_detected and session.output = "Planner produced a plan with cyclic dependencies; cannot execute.".
  3. Solver. ONE LLM call combining the original prompt + the tool results into the final answer.

ReWOOStep

class ReWOOStep(BaseModel): id: str # "E1", "E2", ... tool: str # tool name to call args: dict[str, Any] # args; may contain {{En}} placeholders depends_on: list[str] = [] # ids this step depends on

One step in the plan. The framework does textual substitution of {{En}} placeholders with the prior step’s output before running the tool. For complex data flows (extract a URL out of a JSON blob), have the model emit a more structured plan upfront, or fall back to ReAct.

ReWOOPlan

class ReWOOPlan(BaseModel): steps: list[ReWOOStep]

The planner’s output. Stored on session.metadata["plan"].

ReWOOStepResult

class ReWOOStepResult(BaseModel): step: ReWOOStep output: str error: str | None = None

The result of one tool call. error is non-None when the tool raised; downstream steps that reference the failing step’s output see the error string substituted in.


Cost model

Total: 2 LLM calls + N tool calls. ReAct on the same task needs roughly N+1 LLM calls (one per turn). For tool-heavy workloads where the planner can predict the call sequence upfront, ReWOO is 30–50% cheaper.


When to fall back to ReAct

  • The plan depends on tool output you can’t predict. If a search result reshapes the next call, ReAct’s “think between each tool call” loop is needed.
  • Failure recovery matters. ReWOO commits to the plan; a failed tool call doesn’t trigger replanning.
  • Tools share state in surprising ways. Parallel-by-default execution can break tools that aren’t idempotent across concurrent calls. Set parallel_levels=False or use ReAct.

Example

from loomflow import Agent, tool from loomflow.architecture import ReWOO @tool async def search(query: str) -> str: """Web search.""" ... @tool async def fetch(url: str) -> str: """Fetch a URL.""" ... agent = Agent( "Research the topic and produce a summary with citations.", model="claude-opus-4-7", architecture=ReWOO(), tools=[search, fetch], ) result = await agent.run("Recent advances in agent harnesses.")

Or by string:

agent = Agent("...", model="...", architecture="rewoo", tools=[search, fetch])

Source

loomflow/architecture/rewoo.py

Placeholder substitution is textual. {{E1}} is replaced with E1’s tool result string before the dependent tool runs. Use a structured plan (or ReAct) for complex data flows.

Last updated on