Dinesh/ Blog
machine learning

Multi-Agent Orchestration — LangGraph, CrewAI Patterns

Multi-Agent Orchestration — LangGraph, CrewAI Patterns: mechanisms, worked examples, assumptions and source-backed corrections with explicit experiment boundaries.

18 min readChecking device speech…
Lesson preparation & details

Level: advanced

Language model engineering · Lesson 16 of 26 ↗
Loading this browser’s progress…

By the end, you should be able to

  • Explain hierarchical (manager + workers), peer-to-peer (round-robin), and graph (LangGraph-style) orchestration patterns.
  • Design roles + handoffs for a real task and know when to add more agents vs make one better.
  • Build a small multi-agent system using explicit state-graph contracts.
  • Diagnose 'infinite handoff', 'agents forget context', and 'work is duplicated' bugs.
  • Compare LangGraph, CrewAI, AutoGen at the mental-model level and pick one for a new project.

Bring with you

Sources checked: . A source check does not change the publication or update date.

Editorial review: · What review means

In this article · 10 sections

Why this session exists

A researcher, writer and reviewer resemble a small magazine team: each produces a different artifact, and somebody must decide what happens next. The analogy motivates separation of concerns, not a guarantee that adding agents raises quality. There is no eight-step wall for one agent. Compare a single agent with phased prompts, a deterministic pipeline and a multi-worker design on the same tasks before choosing.

The objectives are to explain supervisor, peer and graph patterns; design typed handoffs and ownership; build a bounded research/write/review state machine; diagnose duplicate work, stale reviews and cycles; and understand LangGraph, CrewAI and AutoGen without treating any as a universal winner. Session 120 supplies the individual controller and authorization boundary.

Three patterns and their actual tradeoffs

Supervisor/worker: the supervisor delegates and gathers artifacts. Narrow worker contexts, role-specific tools and independent parallel research can help. The supervisor is a routing and synthesis bottleneck, not an oracle. Peer or round-robin: workers exchange messages according to a policy; it may support brainstorming, but needs explicit termination and artifact ownership. Neither a peer topology nor a supervisor alone proves termination. Graph: nodes and edges describe state transitions, including conditional branches and cycles. Graphs can encode both other topologies; these are overlapping design patterns, not disjoint product categories.

Three patterns and their actual tradeoffs
Figure text equivalent — labels and relationships
  • Node “Supervisor or deterministic router” (square).
  • Node “Research with source evidence” (square).
  • Node “Write versioned draft” (square).
  • Node “Review exact draft and source evidence” (square).
  • Node “Approved for this version?” (diamond).
  • Node “Complete” (square).
  • Node “Revision and global budgets remain?” (diamond).
  • Node “Partial or failed with artifacts” (square).
  • “Supervisor or deterministic router” leads to “Research with source evidence”. Line: normal; ends: arrow point.
  • “Research with source evidence” leads to “Write versioned draft”. Line: normal; ends: arrow point.
  • “Write versioned draft” leads to “Review exact draft and source evidence”. Line: normal; ends: arrow point.
  • “Review exact draft and source evidence” leads to “Approved for this version?”. Line: normal; ends: arrow point.
  • “Approved for this version?” leads to “Complete”: yes. Line: normal; ends: arrow point.
  • “Approved for this version?” leads to “Revision and global budgets remain?”: no. Line: normal; ends: arrow point.
  • “Revision and global budgets remain?” leads to “Write versioned draft”: yes. Line: normal; ends: arrow point.
  • “Revision and global budgets remain?” leads to “Partial or failed with artifacts”: no. Line: normal; ends: arrow point.

Use parallel workers when subtasks are genuinely independent, contexts are too large to combine economically, or tool permissions need separation. Different skill labels alone do not establish benefit. A single phased controller preserves shared context and one trace; a pipeline gives explicit paths and bounded call counts. Neither has “zero communication cost” or exact predictable token usage when generated lengths vary.

For n workers with directed all-to-all communication there can be n(n−1) channels. A supervisor arrangement has roughly 2n worker/supervisor channels. Actual tokens depend on payload sizes, turns, caching and retries, not simply N× or necessarily superlinear growth. Parallel wall time is bounded below by the critical path plus scheduling/merge work, not the sum of independent task times.

State, handoffs and distributed failure

Treat a handoff as a schema with task ID, actor/permission context, input artifact versions, expected output, source evidence, status and errors. Keep the original request immutable. Separate research_notes, draft, review, next_role, revision counters and global budget. A list of messages is useful for traces; it is not the only source of truth. Restrict each worker to its own fields and validate returned patches.

Every boundary can lose implicit context, but it need not be prose-only or inherently untyped. Structured messages reduce ambiguity without eliminating semantic misunderstanding. Parallel writers require a reducer, conflict resolution or disjoint ownership; “last writer wins” can destroy evidence. Checkpoints preserve resumable state, not automatic exactly-once external effects. Use idempotency keys and reconcile side effects after retries. Record artifact hashes so that a reviewer cannot approve an earlier draft by accident.

A new draft invalidates its prior review. “APPROVED” embedded in arbitrary prose is not a reliable status parser. Unknown/empty routes should fail explicitly, not silently map to a successful finish. Global steps and per-revision caps are separate: a global cap prevents runaway execution, while a local cap preserves budget for diagnosis and other work. Budget exhaustion is partial/failure, never a reason to force a reviewer to approve.

Why a reviewer may help, and why it may not

Generation and verification are different tasks. A fresh review context can reduce pressure to continue an earlier answer and focus on a rubric. This is a hypothesis about conditioning, not a proof that identical weights lose their blind spots. The analogy to checking a proof does not make arbitrary language verification an easy NP problem. A reviewer wired to source passages, tests or a compiler has a stronger signal than unsupported self-critique; a different model or temperature does not guarantee independence or truth.

The retained “polite loop” scenario is a writer and reviewer repeatedly disagreeing without progress. Stop with evidence when budgets run out; do not lower the bar merely to manufacture completion. The original claims that every serious codebase converged on LangGraph or that a particular framework “won” are unsupported market claims, not teaching facts.

Hands-on: version-bound research, write and review

This executable CPU state machine models the same roles and conditional edges as the original LangGraph exercise. It uses an explicit local source and deterministic workers. It is not LLM research, multi-agent intelligence or a framework integration benchmark. Its purpose is to test handoff integrity, review invalidation, bounded revision and replay state.

from dataclasses import dataclass, field
from copy import deepcopy
import hashlib
 
SOURCE = "HNSW uses a hierarchy of proximity graphs."
 
def digest(text):
    return hashlib.sha256(text.encode()).hexdigest()
 
@dataclass
class State:
    request: str
    notes: str = ""
    draft: str = ""
    version: int = 0
    review: dict = field(default_factory=dict)
    revisions: int = 0
    steps: int = 0
    status: str = "running"
    trace: list = field(default_factory=list)
 
def route(s):
    if not s.notes:
        return "researcher"
    if not s.draft:
        return "writer"
    current = s.review.get("version") == s.version and s.review.get("sha256") == digest(s.draft)
    if not current:
        return "reviewer"
    if s.review.get("decision") == "approved":
        return "finish"
    if s.review.get("decision") == "revise":
        return "writer"
    raise ValueError("invalid review decision")
 
def run(s, always_reject=False, max_steps=10, max_revisions=2):
    s = deepcopy(s)
    while s.status == "running":
        node = route(s)
        if node == "finish":
            s.status = "complete"
            break
        if s.steps >= max_steps or (node == "writer" and s.revisions >= max_revisions):
            s.status = "partial"
            break
        before = digest(s.draft)
        s.steps += 1
        if node == "researcher":
            s.notes = SOURCE  # evidence fixture, not a model-generated citation
        elif node == "writer":
            s.draft = s.notes
            s.version += 1
            s.revisions += 1
            s.review = {}  # invalidate the old review
        elif node == "reviewer":
            ok = s.draft == SOURCE and not always_reject
            s.review = {"version": s.version, "sha256": digest(s.draft),
                        "decision": "approved" if ok else "revise",
                        "reason": "exact fixture entailment" if ok else "fixture rejection"}
        else:
            raise ValueError("unknown route")
        s.trace.append({"node": node, "step": s.steps, "before": before, "after": digest(s.draft)})
    return s
 
initial = State("Summarize the supplied HNSW source")
final = run(initial)
assert final.status == "complete" and final.steps == 3
assert initial.steps == 0  # caller/checkpoint snapshot is not mutated
assert run(initial, always_reject=True).status == "partial"
assert run(initial, max_steps=1).status == "partial"
stale = deepcopy(final)
stale.status = "running"
stale.draft = "HNSW always returns exact neighbors."
assert route(stale) == "reviewer"  # old approval is hash-bound
assert run(stale, max_revisions=1).status == "partial"
assert [t["node"] for t in final.trace] == ["researcher", "writer", "reviewer"]
assert len(final.trace) == final.steps
assert run(State(initial.request, notes=SOURCE)).draft == final.draft
print("handoff/version/budget contracts pass; no model or framework run")

Mapping the state machine to LangGraph

LangGraph 0.6.7 StateGraph source defines nodes as State -> Partial<State> and optional per-key reducers. A Python TypedDict describes keys for tooling; it is not runtime validation of arbitrary LLM output. Register researcher, writer and reviewer nodes with add_node, connect START to a router/first node, and use add_conditional_edges for validated route choices and END for termination. Call compile(checkpointer=...) with a suitable saver only when persistence is intended; compile() alone does not create durable storage. Separate immutable run context from writable state.

Preserve the original shared-message option with an add_messages reducer when conversation history is needed; raw list + list has different message-ID/update semantics. Test concurrent updates explicitly. Set and test the framework recursion limit alongside application steps: supervisor and worker transitions can consume multiple graph steps, so MAX_STEPS is not automatically the framework limit.

The original provider recipe used ChatOpenAI.invoke for every role, but its “researcher” had no search/fetch tool, so requested sources could be invented. A real researcher must receive curated evidence or an authorized retrieval capability. Writer and reviewer must receive the original user constraints as well as full relevant artifacts, not only the first 200 characters. Constrain route outputs; clear the old review after writing; distinguish complete, partial and failed; propagate cancellation/time/cost budgets. This is a source-supported LangGraph integration procedure, not an executed LangGraph/ChatOpenAI application. No package install, hosted calls or descendants were used.

Framework choices, memory and worked evaluation

LangGraph emphasizes state transitions/reducers and optional persistence. CrewAI's process documentation describes sequential and hierarchical organization around roles/tasks. AutoGen's 2023 paper describes customizable conversable agents and flexible conversation patterns; it is not evidence that every later release defaults to round-robin or is only for research. APIs evolve: select a pinned version after testing state, routing, error and checkpoint behavior, not from a historical product stereotype. The 2024 Anthropic article recommends simple sufficient systems; “workflow > agent > multi-agent” is a teaching shorthand, not its formal ranking of all designs. Swarm and Agents SDK are distinct releases, not a single 2025 launch.

Worked experiment: use the same permitted evidence and rubric for one phased worker, a fixed research/write/review pipeline, and a supervisor with independent researchers. Record per-artifact factual support, role violations, malformed handoffs, duplicate work, total input/output tokens, p95 latency and partial-task rate. If supervisor routing is wrong on 10% of labeled states, first replace deterministic branches with code, then test structured role enums, state examples and model choice on held-out states. Do not simply add agents.

Persistent memory can hold source-backed research, user-consented preferences or tool outcomes across runs. Distinguish authoritative facts from episodic summaries. Include provenance, tenant access, version, expiry and deletion policy; summarized or vector-retrieved text is untrusted evidence, not permission. Store verified artifacts outside long conversation history, and reread current versions before acting. More memory can preserve stale errors as well as useful context.

Recall and stretch: complete answers

  1. Patterns: supervisor routes; peers communicate under a scheduling policy; graphs encode explicit transitions and can represent either.
  2. When split? Independently useful parallel subtasks, bounded contexts or tool separation must beat a simpler measured baseline.
  3. Separate reviewer? A different task/context and external evidence can help; same or different models can share mistakes, so measure the gain.
  4. Debugging? Versioned state, artifacts, validated transitions, traces and checkpoints; none makes replayed external side effects automatically safe.
  5. Runaways? Infinite handoffs need local/global caps; duplicate work needs ownership, IDs and deduplication. Neither is unique to LLM systems.
  6. Supervisor prompt? Keep the decision contract narrow and structured; deterministic rules belong in code. A short prompt does not make an LLM deterministic.
  7. Frameworks? LangGraph: state graph; CrewAI: roles/tasks/processes; AutoGen: customizable agent conversations. Compare the exact version's capabilities.
  8. Workflow first? Simpler sufficient control makes costs and failures easier to bound; it is not a universal quality hierarchy.
  9. 10% routing errors? Inspect labeled confusion cases, enforce allowed roles/conditions, add examples or test a better router; use deterministic routing where possible.
  10. Memory stretch: durable, access-controlled, versioned evidence and preferences can avoid rework, but must support deletion, stale-data checks and untrusted-input handling.

Explain which artifact each worker owns, demonstrate rejection of the stale approval, and distinguish a partial result from a completed task. Evaluation (122), serving (124), safety, checkpointing and model choice all apply; the original nonexistent/misnumbered downstream destinations are not course facts.

Optional resources and onward navigation

The original optional pre-read links are retained as navigation, not fact-check evidence or promises about video length, quality or API stability. Use the versioned primary sources in the teaching for consequential claims. Original pacing was a suggested 90-minute session, not a completion guarantee.

Next in the original sequence: S122 · LLM Evaluation — LLM-as-Judge, RAGAS, Golden Sets. Publication and release navigation are adjudicated by the parent.

Hub: The 6-Month Learning Plan. The original 130-session scaffold is retained as navigation, not evidence that every destination has been reviewed.

Review and execution boundary

Reviewed against complete original and pre-edit current teaching with claim-specific primary evidence and independently replayed coverage. The executable CPU program is a bounded teaching test, not a pretrained model, serving, RAG or training benchmark. External model/service recipes remain source-supported and unexecuted. No weights, datasets, GPU, paid API or new service were used. Source publication is not end-to-end application acceptance.

Pause / Recall / Apply

Can you explain it without the page?

Close the example. Reconstruct the core idea, then change one assumption. Mark complete when you’re ready; you can always undo it.

Stored in this browser only. No account, no sync. Clearing browser data removes your record.