Agent Architecture: ReAct, Plan-and-Execute & Multi-Agent

Introduction

The previous post covered Function Calling — letting the LLM call a tool. But in production, 90% of complex tasks can’t be solved in one tool call:

  • “Book me a flight, budget under ¥2000, afternoon departure” → search flights → compare → pick best → fill payment → issue ticket, 5+ steps
  • “Analyze last quarter’s sales and create a PPT for the boss” → query DB → aggregate → generate charts → write outline → layout, multi-step + multi-tool

These tasks need an Agent — an LLM system that can autonomously decide which tools to call, in what order, for how many iterations.

This post covers three mainstream agent architectures:

  1. ReAct — the simplest thought / action / observation loop
  2. Plan-and-Execute — plan first, then execute; great for long-chained tasks
  3. Multi-Agent — decompose into sub-tasks, multiple agents collaborate

1. ReAct: The Thought / Action / Observation Loop

ReAct (Yao et al. 2022, ICLR 2023) core idea: let the LLM interleave reasoning and actions:

Thought 1: I need to search for flight prices
Action 1:  search_flight({from: "PEK", to: "SHA", date: "2026-07-15"})
Observation 1: Lowest price ¥1200 (China Eastern MU5101)

Thought 2: Within budget, let me check the flight time
Action 2:  get_flight_details({flight: "MU5101"})
Observation 2: Departs 14:30, arrives 17:00

Thought 3: Good time slot, ready to book
Action 3:  book_flight({flight: "MU5101", passenger: "..."})
Observation 3: Order confirmed: MU5101, ¥1200

Thought 4: Task complete
Final Answer: Successfully booked 7/15 PEK→SHA MU5101, ¥1200
Figure 1: ReAct loop

Code Implementation

Vercel AI SDK + custom ReAct:

import { generateText, tool, isStepCount } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await generateText({
  model: openai('gpt-4o'),
  system: `You are a travel booking agent. For each turn:
1. State your Thought (what to do next)
2. Take an Action (call a tool)
3. Receive Observation (tool result)
4. Repeat until done

End with a clear final answer when the task is complete.`,
  tools: {
    searchFlight: tool({ /* ... */ }),
    getFlightDetails: tool({ /* ... */ }),
    bookFlight: tool({ /* ... */ }),
  },
  stopWhen: isStepCount(10),  // max 10 steps
  prompt: userQuery,
});

ReAct is simple and powerful, but inefficient for long-chained tasks — the LLM re-reads all history every step and can be derailed by early errors.

2. Plan-and-Execute: Plan First, Then Execute

For tasks with 5+ steps and dependencies, plan-then-execute is more stable than ReAct:

Figure 2: Plan-and-Execute architecture

Implementation Example

import { generateObject, generateText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

// 1. Planner generates the plan upfront
const plan = await generateObject({
  model: openai('gpt-4o'),
  schema: z.object({
    steps: z.array(z.object({
      id: z.number(),
      action: z.string(),
      toolName: z.string(),
      dependsOn: z.array(z.number()).optional(),
    })),
  }),
  prompt: `Decompose the user task into executable steps.

User task: ${userQuery}`,
});

// 2. Execute plan step-by-step (with dependency checks)
const results = new Map();
for (const step of plan.object.steps) {
  if (step.dependsOn) {
    for (const dep of step.dependsOn) {
      await results.get(dep);  // actually awaits a promise
    }
  }
  
  const stepResult = await executeStep(step, results);
  results.set(step.id, stepResult);
}

// 3. Replan fallback (when any step fails)
// 4. Final aggregation
const summary = await generateText({
  model: openai('gpt-4o'),
  prompt: `Based on these results answer the user: ${JSON.stringify(results)}`,
});

When to use:

  • Long-chained (5+ step) tasks
  • Steps have data dependencies (“query X, then use X’s result for Y”)
  • Users want to see the plan (transparency)

Drawback: a single plan can be inaccurate, so you need a replan fallback.

3. Multi-Agent: Decompose + Collaborate

When a task has multiple independent sub-domains, splitting into multiple agents beats one super-agent:

                    ┌── Researcher Agent ──┐
                    │   flights + hotels     │
User Query ── Router ─┼── Budget Agent ──────┼── Aggregator ── Final
                    │   total cost control    │
                    └── Planner Agent ─────┘
                        sequencing

Classic Implementation: Supervisor Pattern

// Each agent is an independent system prompt + tool set
const agents = {
  researcher: new Agent({
    name: 'Researcher',
    systemPrompt: 'You are the researcher. Find flights and hotels...',
    tools: [searchFlightTool, searchHotelTool],
  }),
  budget: new Agent({
    name: 'BudgetController',
    systemPrompt: 'You are the budget controller. Keep total spend < ¥2000...',
    tools: [sumCostsTool, checkBudgetTool],
  }),
  planner: new Agent({
    name: 'Planner',
    systemPrompt: 'You are the planner. Decompose the task into steps...',
    tools: [],  // pure reasoning agent
  }),
};

// Supervisor decides the next agent to invoke
async function supervisor(userQuery: string) {
  const state = { messages: [], currentAgent: 'planner' };
  
  while (!state.done) {
    const agent = agents[state.currentAgent];
    const result = await agent.run(state);
    state.messages.push(...result.messages);
    
    const next = await generateText({
      model: openai('gpt-4o-mini'),
      system: `Decide which agent to invoke next: researcher / budget / planner / done`,
      prompt: JSON.stringify(state),
    });
    
    state.currentAgent = parseNextAgent(next.text);
    if (state.currentAgent === 'done') state.done = true;
  }
  
  return state.messages;
}

3 Main Multi-Agent Patterns

  1. Supervisor (centralized): best for heterogeneous tasks
  2. Swarm: agents hand off to each other directly (LangGraph Swarm), best for dynamic handoffs
  3. Hierarchical: agents nest agents (a supervisor managing 3 sub-agents, each managing 3 workers), best for large-scale tasks

Multi-Agent Pitfalls

  • Token blowup: multiple agents accumulate context, may exceed model limits
  • Coordination deadlocks: A waits for B, B waits for A
  • Blurred responsibility: vague boundaries cause duplicate work

Heuristic: start with ReAct / Plan-and-Execute. Only introduce Multi-Agent when you actually need it.

4. LangGraph: State-Machine Agent Orchestration

LangGraph is LangChain’s lower-level orchestration framework, abstracting agents as state graphs:

from langgraph.graph import StateGraph, START, END
from typing import TypedDict

class State(TypedDict):
    messages: list
    next_step: str

def planner(state: State):
    # Decide the next step
    return {"next_step": "researcher"}

def researcher(state: State):
    # Query flights
    return {"messages": state["messages"] + [...]}

def should_continue(state: State) -> str:
    if state["next_step"] == "done":
        return END
    return state["next_step"]

graph = (
    StateGraph(State)
    .add_node("planner", planner)
    .add_node("researcher", researcher)
    .add_edge(START, "planner")
    .add_conditional_edges("planner", should_continue)
    .compile()
)

LangGraph’s core value:

  • State persistence: agents can pause / resume (for human-in-the-loop)
  • Debuggability: every state transition is traceable
  • Production-ready: built-in checkpointing, error recovery, parallel execution

5. Selection Decision Tree

Figure 3: Agent architecture selection

Pitfalls for Senior Architects

  1. Don’t start with Multi-Agent. 80% of tasks are served well by a single agent + good ReAct. Start simple.
  2. Always set stopWhen. Without a step limit, agents loop infinitely and burn tokens.
  3. Each agent’s system prompt must define clear responsibility boundaries. “You do A, not B, else error” beats generic “You are a helpful assistant” 10x.
  4. Multi-Agent needs a shared state layer. Without unified state store, agents can’t coordinate (classic anti-pattern).
  5. Tool failure rate > 0 means you need retry + fallback. Real APIs aren’t 100% reliable; agents without degradation are time bombs.

Summary

Five takeaways:

  • ReAct: simplest thought / action / observation loop. Best for 1-5 step tasks. Default for 80% of scenarios.
  • Plan-and-Execute: plan first, then execute. Best for 5+ step tasks with dependencies. Needs replan fallback.
  • Multi-Agent: decompose + coordinate. Best for heterogeneous / large-scale tasks. Architecturally complex — use sparingly.
  • LangGraph: state-machine orchestration. Production-grade agent essential. Supports persistence, human-in-the-loop, debuggability.
  • Always set stopWhen + retry + fallback. Agents are LLMs + tool calls. Any unreliable link causes the agent to fail.

Next up: Memory System Design for LLM Applications — entering the engineering trio.

References

🔗 Original Link Share to reach more people

// Related Posts

Comments