TechByteByByte

Building a Multi-Agent System

Taking the recurring legal-contract pipeline from concept through pseudocode through working Python to real LangGraph mechanics — including the specific technical reason checkpointing turns a fragile loop into a production tool.

#AI Agents#Multi-Agent Systems#LangGraph#Implementation

The best way to understand coordination is to watch data move through a small working system. This module turns the course’s planner–executor–critic idea into code.

Input → planner tasks → executors results → critic verdicts → final report

What You Will Learn

  • How to represent tasks, messages, state, and results in code.
  • How the Python example executes line by line.
  • What production frameworks add beyond the small teaching implementation.

Twenty modules of principle deserve one module of construction. This module takes the Planner-Executor-Critic pipeline you’ve followed throughout this course and builds it, concretely — from architecture, through pseudocode, through working Python, to how a real framework implements the same thing.


The architecture, recalled precisely

You already know this shape from Module 7: the Planner decomposes a contract into checklist items and assigns each to an Executor; each Executor produces a comparison; the Critic reviews every comparison independently before the final report ships. This module’s job is making that concrete, not redesigning it.


Step one: pseudocode

function review_contract(contract):
    checklist = planner.decompose(contract)
    results = []

    for item in checklist:
        comparison = executor.review(item, contract)
        verdict = critic.review(comparison, item.policy)

        if verdict.rejected:
            comparison = executor.review(item, contract, feedback=verdict.reason)
            verdict = critic.review(comparison, item.policy)

        results.append(verdict.approved_comparison)

    return synthesize_report(results)

Notice this pseudocode already encodes real decisions from earlier modules: a bounded, single retry after rejection — not an unbounded loop, directly Module 16’s circular-delegation warning — and the Critic’s verdict, not the Executor’s own confidence, gates whether a result is accepted.

Why these two decisions specifically, and not others

It’s worth being explicit about why the pseudocode looks exactly this way, because every choice here traces back to a real lesson rather than an arbitrary default. The retry cap exists because Module 4’s Anthropic research found agents convergent enough that a second identical attempt often fails the same way a first one did — a bounded retry, not an unbounded one, reflects that a repeated failure is real signal, not bad luck worth trying again indefinitely.

The Critic-gates-acceptance decision exists because Module 2’s independence argument still holds here: letting the Executor decide its own output was good enough would mean the same reasoning process that produced a result also judges it, exactly the self-certification problem this course has warned against since its earliest modules.


Step two: working Python

The goal of this code is to make the handoffs visible. The Planner creates checklist items, the Executor produces one result per item, and the Critic either approves or rejects each result. We use small Python classes instead of a framework so you can trace exactly where data is created, passed, checked, and collected.

As you read, follow one checklist item through this path:

ChecklistItem → executor.review() → ReviewResult → critic.review() → final list
from dataclasses import dataclass
from enum import Enum

class Verdict(Enum):
    APPROVED = "approved"
    REJECTED = "rejected"

@dataclass
class ChecklistItem:
    policy_area: str
    contract_section_id: str

@dataclass
class ReviewResult:
    verdict: Verdict
    comparison: str
    reason: str = ""

def review_contract(contract, planner, executor, critic, max_retries=1):
    checklist = planner.decompose(contract)
    results = []

    for item in checklist:
        comparison = executor.review(item, contract)
        review = critic.review(comparison, item.policy_area)

        retries = 0
        while review.verdict == Verdict.REJECTED and retries < max_retries:
            comparison = executor.review(item, contract, feedback=review.reason)
            review = critic.review(comparison, item.policy_area)
            retries += 1

        if review.verdict == Verdict.REJECTED:
            comparison = f"ESCALATED: {review.reason}"

        results.append(comparison)

    return synthesize_report(results)

Every real lesson from this course is a real line here. max_retries=1 is Module 16’s mechanical guardrail against an unbounded loop — not a suggestion, an actual parameter enforced in code. The while loop’s exit condition is checked against the retry count and the verdict, so a persistently rejected item escalates rather than looping forever. And escalation produces a distinct, flagged result rather than silently substituting a lower-confidence answer — directly Module 16’s poor-termination-conditions lesson, avoided by making “done” mean something the code can actually check.


Step three: what a real framework adds

This Python is functional. It’s also missing something that matters the moment it runs in production: if this process crashes halfway through a twelve-item checklist, everything is lost — there’s no record of which items already completed, and a restart means starting over from item one.

This is precisely the problem real orchestration frameworks like LangGraph exist to solve, and it’s worth understanding the actual mechanism, not just that “it handles state.” LangGraph models a workflow as nodes (your agent functions) and edges (the routing between them), with every node reading from and writing to a shared state object — and checkpoints that state after every single node runs. (FreeCodeCamp, How to Build a Multi-Agent AI System with LangGraph, MCP, and A2A)

The concrete difference this makes, stated directly: “A naïve multi-agent loop written as a for loop loses everything the moment it crashes. LangGraph doesn’t. The checkpoint survives the crash, and graph.invoke() with the same session ID picks up exactly where it left off.” (FreeCodeCamp)

This is the, technical answer to “why use a framework instead of the Python above.” It’s not about writing less code for its own sake — it’s that the Python above has a real, structural reliability gap a hand-rolled loop can’t close without independently reimplementing exactly what a mature framework already solved.

It’s worth being honest about what this doesn’t solve too. Checkpointing recovers progress after a crash — it doesn’t validate that the progress recorded before the crash was actually correct. If the Planner’s decomposition was subtly wrong three steps before the crash, checkpointing faithfully preserves and resumes from that wrong state, exactly as reliably as it would preserve a correct one. Reliability infrastructure and correctness are separate concerns, and a framework solving the first doesn’t automatically solve the second — which is precisely why Module 19’s evaluation discipline and Module 16’s independent-validation lesson remain necessary even once production-grade orchestration infrastructure is in place.


The same architecture, as a real graph

builder = StateGraph(ContractReviewState)
builder.add_node("planner", planner_node)
builder.add_node("executor", executor_node)
builder.add_node("critic", critic_node)

builder.add_edge(START, "planner")
builder.add_edge("planner", "executor")
builder.add_edge("executor", "critic")
builder.add_conditional_edges(
    "critic",
    lambda state: "executor" if state.rejected and state.retries < 1 else "synthesize"
)
graph = builder.compile()

Each node is a Python function that reads the shared state and returns only the fields it actually changed — a node never needs to return the complete state, LangGraph handles merging the partial update back in. (Medium, LangGraph in Production)

The add_conditional_edges call is where the pseudocode’s retry logic actually lives — not as a while loop inside one function, but as a inspectable routing decision in the graph itself. This matters directly for Module 18’s observability: a retry that happens as an explicit graph edge is a traceable event with its own span. A retry buried inside a Python while loop is invisible to anything outside that function.


Composability, concretely: subgraphs

Module 8 and 15 both argued patterns compose — a hierarchical system with mesh coordination nested inside one stage, a pipeline launching a swarm for one parallel subtask. LangGraph has a real, direct mechanism for this: a subgraph is simply a graph used as a node in another graph. If the Executor’s own internal work — search, validation, summarization — becomes complex enough to warrant its own structure, it can become a subgraph the parent graph calls as a single node, without the parent needing to know those internal implementation details at all. (Build Fast with AI, How to Use LangGraph for Multi-Agent Systems)

This is precisely how Module 15’s composability claim becomes real, working code rather than a diagram — the Planner-Executor-Critic graph doesn’t change at all when the Executor’s own internals grow more sophisticated; only the Executor node’s own subgraph does.


What a complete real system actually wires together

It’s worth closing with a real, current, comprehensive reference, because it shows every module of this course converging into one system rather than staying separate concepts. A recent, complete open reference builds a system with four agents coordinated by LangGraph, two MCP servers giving those agents tool access, two A2A services enabling cross-framework delegation — including delegating to a CrewAI agent, a different framework entirelyLangfuse capturing every trace, and DeepEval running automated quality checks. (FreeCodeCamp)

Map this directly onto what you already know: LangGraph is this module’s orchestration mechanism. MCP is your previous course’s tool-access protocol. A2A is Module 3’s cross-agent communication standard — here shown crossing frameworks, LangGraph delegating to CrewAI, not just theoretically capable of it. Langfuse is Module 18’s tracing infrastructure. DeepEval is Module 19’s evaluation layer. Nothing in this reference system is a new concept — it’s every module of this course, wired together in one real, running implementation.

Why the cross-framework delegation specifically matters

It’s worth dwelling on the CrewAI delegation piece, because it’s easy to skim past as a minor implementation detail when it’s actually a direct, concrete answer to a question this course has raised more than once. Module 5 asked what happens when a pipeline needs to delegate to a external, differently-built agent — a vendor’s compliance checker, say. This reference system shows the real answer: A2A doesn’t require the delegating and delegated agents to share a framework at all.

A LangGraph-orchestrated Planner can hand a subtask to a CrewAI-built specialist through A2A’s standard protocol, and neither side needs to know or care what framework the other is built on.

This is worth connecting back to Module 17’s security material directly. The same interoperability that makes this composition possible is exactly where Module 17’s Agent Card verification gap becomes a practical risk — a LangGraph system delegating to an external CrewAI agent through A2A is precisely the cross-organization boundary where signed, certificate-backed identity stops being optional hardening and becomes the actual difference between a legitimate specialist and an impersonating one.


Applying Module 16’s failure catalog to this actual build

It’s worth running the Python and graph implementation above against a real failure scenario, not just describing the guardrails abstractly.

Recall Module 16’s centerpiece finding: a false claim injected at a hub position caused 100% system-wide failure across major frameworks. In this build, the Planner node is the hub — every checklist item’s scope originates there. If the Planner’s decompose call hallucinates a policy area that doesn’t actually apply to this contract, that error propagates to every downstream Executor call, exactly the cascade Module 16 measured.

The graph implementation’s conditional edge doesn’t prevent this on its own — a bad decomposition from the Planner is syntactically valid input to the Executor node, and nothing in the retry logic catches an error that originates before the Executor even starts.

This is precisely why Module 16’s independent-validation lesson matters concretely here: a fix isn’t a longer retry chain on the Executor-Critic loop, it’s an independent check on the Planner’s own output specifically, before any Executor node ever runs against a potentially flawed checklist — the same hub-position validation priority Module 16 and Module 20 both argued for, now visible as a gap in this actual implementation rather than an abstract principle.


Interview-relevant framing

Q: Why would you choose a framework like LangGraph over a hand-written orchestration loop?

Ans: The real, technical reason isn’t less code — it’s reliability under failure. A hand-written for loop coordinating several agents loses all progress the moment it crashes partway through. LangGraph checkpoints state after every node, so a crash and restart with the same session ID picks up exactly where execution left off, rather than starting over. That’s a production capability a naive loop doesn’t have unless you reimplement checkpointing yourself.

Q: How would you implement Module 16’s retry-cap guardrail in a graph-based framework specifically?

Ans: As a conditional edge, not a loop buried inside one function. The critic node’s output routes either back to the executor — if rejected and under the retry limit — or forward to synthesis, otherwise. Making that decision an explicit edge in the graph means it’s traceable as its own event, which matters directly for observability — a retry that’s a visible graph transition is debuggable in a way a retry hidden inside a while loop’s internal state isn’t.

A third question worth preparing for:

Q: What’s the actual technical purpose of a subgraph, beyond just ‘organizing code’?

Ans: It’s an encapsulation boundary that lets a specialist’s internal complexity grow without changing the parent graph’s structure at all. If the Executor’s own work — search, validation, summarization — grows into something with its own branching logic, that entire subprocess becomes one node from the parent Planner-Executor-Critic graph’s point of view. The parent never needs to know the Executor internally has three steps instead of one; it just sees a single node that takes an input and returns a result. That’s what makes Module 8’s hierarchical composability and Module 15’s pattern-combination claims real, working code rather than just an architecture diagram.

Connect the Teaching Build to a Real Framework

LangGraph’s official multi-agent documentation shows subagent, handoff, skills, and router patterns, and warns that one agent with the right tools and prompt can often achieve a similar result. Our Python example stays deliberately smaller so every state change is visible before a framework hides the plumbing. (LangGraph multi-agent documentation)

Common Misconception

Incorrect idea: Using a framework automatically supplies production reliability.

Why it is incorrect: A framework provides primitives. The application still needs schemas, persistence, permissions, idempotency, evaluation, monitoring, and recovery policies.

Key takeaways

  • The progression from concept to working system is and sequential: pseudocode encodes the real decisions (bounded retries, verdict-gated acceptance), Python makes them executable, and a framework adds what hand-written code structurally can’t easily provide on its own.
  • LangGraph’s checkpointing is the concrete, technical answer to “why use a framework” — state persisted after every node means a crash doesn’t lose all progress, unlike a naive hand-written loop.
  • Conditional edges are where retry and escalation logic actually belongs in a graph-based system — an explicit, traceable routing decision, not logic hidden inside one function’s control flow.
  • Subgraphs are the real, working mechanism behind Module 8 and 15’s composability claims — a specialist’s internal complexity can grow without the parent graph needing to know any of its implementation details.
  • A complete real system wires together nearly every concept this course has covered: LangGraph for orchestration, MCP for tool access, A2A for cross-framework delegation, tracing for observability, and automated evaluation — not as separate technologies, but as one integrated implementation.

Module 22 turns from building one system to seeing many real systems already built: real-world multi-agent systems — deep, sourced case studies across different industries, what worked, and what didn’t.

Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed