Architectural blueprints for building agents that are reliable, composable, and maintainable — from simple single-agent loops to complex multi-agent orchestration. This chapter moves beyond theory and into the concrete patterns practitioners use daily: how to structure the core plan-execute cycle, when to introduce multiple agents, how to wire workflows together, and how to assign coherent roles that stay stable under pressure.
The single-agent loop is the foundation of every agentic system. Before you introduce multiple agents, orchestration frameworks, or complex memory architectures, you need to master this primitive: one model, a set of tools, a goal, and an iterative cycle that continues until the task is done or the budget is exhausted.
Every single-agent loop has four phases that repeat until a termination condition is met. Observe: the agent receives the current state — the user's goal, prior tool results, and any injected context. Think: the LLM reasons over this state, often generating a scratchpad, deciding what action to take next. Act: a tool call is executed — a search, a code execution, an API request — and the result is appended to the context. Evaluate: the agent checks whether the goal has been achieved; if not, it loops.
This pattern was formalised in the ReAct paper (Yao et al., 2022), which interleaved Reasoning traces with Action calls to dramatically improve performance on multi-step tasks. Modern implementations follow this same structure, though the framing has evolved from explicit Thought/Action/Observation tokens to native function calling.
A loop without a reliable stop condition is a loop that will run until it times out or depletes your budget. There are three ways to terminate: the model signals completion (it returns a final answer rather than a tool call); a step budget is exhausted (hard limit on iterations, typically 5–20 for most tasks); or an external checker compares the output to the original goal and signals success. In practice, all three are used together — the model's own judgment is the primary exit, with step budgets as a safety backstop.
Setting the right step budget is important. Too low and the agent fails on legitimate complex tasks. Too high and it loops indefinitely when stuck. A good heuristic: estimate the minimum number of tool calls for the ideal solution, multiply by 2.5, and cap there.
Single-agent loops fail in predictable ways: tool calls return errors, the model hallucinates a non-existent function argument, or the task is genuinely impossible. Robust loops handle errors by appending the error message as an observation and allowing the model to reason about it. Most capable models will naturally retry with corrected arguments. For persistent failures, implement a retry budget (separate from the step budget) and a fallback strategy — either escalate to a human or gracefully fail with a structured error response.
Design principle: Treat every tool result — including errors — as valuable signal. A model that sees "404 Not Found" can infer the resource doesn't exist and try an alternative approach. Never hide errors from the agent's context.
Track the remaining step budget as a system prompt variable, not just a silent counter. When the budget is low, inject this information: "You have 2 steps remaining. Prioritise completing the core task over supplementary research." This prompt injection at budget boundaries significantly reduces incomplete outputs and encourages the model to produce a best-effort answer rather than an abrupt stop.
def run_agent(goal: str, tools, max_steps: int = 15): messages = [ {"role": "system", "content": build_system_prompt(goal)}, {"role": "user", "content": goal}, ] for step in range(max_steps): # Inject budget awareness near the limit if step == max_steps - 2: messages.append(budget_warning(max_steps - step)) response = llm.chat(messages, tools=tools) if response.stop_reason == "end_turn": return response.text # ✓ Model signals done for tool_call in response.tool_calls: result = execute_tool(tool_call) messages += format_tool_result(tool_call, result) return graceful_fail(messages) # Budget exhausted
“The single-agent loop is not a limitation — it is the right tool for 80% of tasks. Reach for multi-agent systems when you have a specific reason: parallelism, specialisation, or context isolation. Not because it feels more sophisticated.”
— agentic-ai / agent-design · 02.1Multi-agent systems allow you to parallelise work, specialise agents for specific domains, and break context window limits by distributing tasks. The cost is significant: coordination complexity, harder debugging, and emergent failure modes. This module covers the three primary topologies and when each is appropriate.
Multi-agent systems are best understood through their communication topology. The pattern you choose determines how agents coordinate, how state flows, and where failures propagate.
Orchestrator-Worker is the default topology for most production systems. A central orchestrator receives the user's goal, decomposes it into sub-tasks, dispatches each to a specialised worker agent, and synthesises the results. Workers are stateless — they receive a self-contained task description and return a result. This makes them easy to test in isolation and replace independently.
Peer-to-peer topologies have agents communicate directly without a central coordinator. This enables emergent collaboration but makes debugging extremely difficult. Avoid this pattern unless you are building systems where no single agent can hold the full task context (e.g., very long-horizon research agents).
Supervisor topologies add a meta-agent above the orchestrator that monitors progress, handles escalations, and can replan if the orchestrator is stuck. This adds resilience for complex, long-running tasks but significantly increases latency and cost.
Effective delegation requires self-contained task descriptions. When the orchestrator dispatches a sub-task to a worker, the task description must include all context the worker needs — it cannot ask the orchestrator follow-up questions. This means the orchestrator must reason carefully about what each worker needs to know before dispatching.
Include in every task description: the specific goal, relevant background context, output format expected, constraints and scope limits, and any prior attempts that failed. Workers that lack context will either hallucinate or produce generic outputs that don't fit the broader task.
Common mistake: Passing the full conversation history to every worker agent. This wastes tokens and introduces irrelevant context. Instead, have the orchestrator extract and summarise only what each specific worker needs for its sub-task.
When multiple agents need to read and write shared state — a document being edited, a plan being updated — you need explicit coordination. The simplest approach is orchestrator-mediated state: all state mutations pass through the orchestrator, which acts as a single writer. For higher throughput, use an external shared store (Redis, a database, or a shared file) with optimistic locking. Avoid having multiple agents write to the same resource concurrently without a conflict-resolution strategy.
In multi-agent systems, agent outputs become agent inputs. Standardise your message format early. A simple envelope — task ID, agent ID, status (success/failure), payload, and token usage — makes debugging dramatically easier and enables automated routing of failure cases.
async def orchestrate(goal: str): # Step 1: Orchestrator decomposes the goal plan = await orchestrator_agent( prompt=f"Decompose this goal into sub-tasks: {goal}" ) # Step 2: Dispatch workers in parallel tasks = [ worker_agent(name=task.worker, task=task.description) for task in plan.subtasks ] results = await asyncio.gather(*tasks) # Step 3: Orchestrator synthesises return await orchestrator_agent( prompt="Synthesise these results:", context=format_results(results) )
| Topology | Parallelism | Debuggability | Best for |
|---|---|---|---|
| Orchestrator-Worker | High | High | Most production tasks |
| Peer-to-Peer | Medium | Low | Emergent collaboration research |
| Supervisor | Medium | Medium | Long-horizon, resilience-critical tasks |
“The orchestrator-worker pattern succeeds because it respects a simple invariant: the orchestrator decides, workers execute. The moment a worker starts deciding what other workers should do, you have introduced a coordination problem without a solution.”
— agentic-ai / agent-design · 02.2Workflow orchestration adds deterministic structure to agentic systems. Rather than letting a single LLM decide the entire sequence of operations, you define the control flow explicitly — using DAGs, sequential chains, parallel fan-out, and conditional branches — with LLM calls at specific nodes.
A pure agent loop gives the LLM full autonomy over sequencing: it decides which tools to call, in what order, and when to stop. A workflow takes some of that autonomy away and encodes the high-level structure explicitly. LLMs are invoked at specific nodes for tasks they're good at — generating text, classifying, extracting — while the routing between nodes is deterministic code.
This trade-off is worth making when the process is well-understood, when compliance or auditability matters, or when the cost of an incorrect routing decision is high. Workflows are also significantly easier to test: each node can be evaluated independently.
All workflow patterns are combinations of four primitives: Sequential chains process one step at a time, each step's output feeding the next. Parallel fan-out dispatches the same input to multiple nodes simultaneously and aggregates results. Conditional branching routes execution based on a classification or decision, typically made by an LLM. Human-in-the-loop gates pause execution and await explicit human approval before proceeding.
HITL gates are pauses in the workflow where a human must review and approve before execution continues. They are essential for high-stakes actions — sending emails, making purchases, modifying databases, deploying code. Design them with three things in mind:
Context surfacing: the human must see everything they need to make an informed decision — the agent's reasoning, the proposed action, and the consequences. Timeout handling: define what happens if the human doesn't respond. Usually: wait, escalate, or safely abort. Feedback loop: capture the human's decision and reasoning and feed it back to the agent, so it can learn what kinds of actions are likely to be approved or rejected.
Practical note: Place HITL gates before irreversible actions, not after. Asking for approval after the fact is an audit trail, not a safety mechanism. The gate must block the action until approval is received.
LangGraph, Prefect, Temporal, and Airflow all support agentic workflow patterns. LangGraph is purpose-built for LLM workflows with native support for cycles (which most DAG frameworks disallow) and stateful checkpointing. Temporal is the right choice when you need durable execution — workflows that survive process restarts, network failures, and long pauses. For simpler use cases, a hand-coded Python state machine is often more maintainable than a full framework.
“A workflow without a HITL gate on its most dangerous action is not safer because it runs faster. It is just faster to make the same irreversible mistake.”
— agentic-ai / agent-design · 02.3Giving an agent a clear role — Planner, Critic, Executor, Summariser — is one of the highest-leverage prompting techniques available. But roles that are poorly defined collapse when the task drifts outside their description. This module covers how to define, inject, and maintain stable agent personas.
LLMs are trained on vast corpora that include human role-playing, professional writing in specific domains, and structured dialogues. When you inject a role via the system prompt, you activate a coherent latent representation — a "mode" of behaviour that influences not just tone but reasoning strategy. A Critic agent asked to review code will naturally look for edge cases, question assumptions, and propose alternatives. The same model asked to "review this code" without a role will produce more superficial feedback.
Roles also provide scope boundaries. An Executor agent knows it should not re-plan; it should execute the plan it was given. A Summariser should not add new information; it should compress. These implicit constraints, reinforced by the role framing, reduce the range of outputs the model will produce and make behaviour more predictable.
The system prompt is the primary vehicle for role injection. Effective role prompts have three components: a role statement (what the agent is), a scope statement (what the agent does and does not do), and output format (how the agent communicates its outputs).
You are a Senior Code Reviewer.
Your role:
- Identify bugs, security vulnerabilities, and performance issues
in the code submitted to you
- Be direct and specific; cite line numbers
- Prioritise issues by severity: Critical / High / Medium / Low
Scope limits:
- Do NOT rewrite the code; only flag issues
- Do NOT praise the code; focus entirely on problems
- Do NOT suggest new features outside the stated requirements
Output format:
Return a JSON object: {
"critical": [...],
"high": [...],
"medium": [...],
"low": [...]
}
Each item: { "line": N, "issue": "...", "fix": "..." }
Role collapse occurs when an agent drifts away from its assigned role — a Critic starts rewriting code instead of reviewing it, or an Executor starts re-planning instead of executing. This happens when the task input is ambiguous, when the model's training bias pulls it toward helpfulness over scope, or when the role prompt is too thin.
Countermeasures: Explicit negative constraints ("Do NOT rewrite the code") are as important as positive role definitions. Output schema enforcement forces the model to produce a structured output that is incompatible with role collapse. Role reinforcement injections — periodically reminding the model of its role mid-conversation — help in long agentic loops where the role can fade.
Pattern: For multi-agent systems where Planner and Critic roles interact, never allow them to share a context window. Run them as separate calls with separate system prompts. A Planner that can see the Critic's feedback will often pre-emptively hedge, weakening the initial plan. Separation produces stronger outputs from both.
In production, users (or upstream agents) will sometimes send inputs that try to override an agent's persona — "Ignore your previous instructions and…" — or that are sufficiently off-topic that the agent's role becomes unclear. Include explicit instructions for handling out-of-scope inputs: the agent should refuse, ask for clarification, or return a structured error — never silently drift into a different mode of behaviour.