Advertisement
Verified Partner
Enterprise Cloud Infrastructure, GPU Clusters & AI APIs
Deploy high-throughput inference and quant trading pipelines with ultra-low latency.
Explore Platform →

Autonomous Multi-Agent Architecture: LangGraph, CrewAI & Tool Loops

By Marcus Sterling • Advanced • 17 min read • Updated 2026-09-12

What You Will Master in This Tutorial

  • Understand StateGraph architecture: Nodes, Edges, and Conditional Routing.
  • Implement self-correcting code generation with automated test feedback.
  • Configure Human-In-The-Loop approval gates for critical actions.

1. Building Stateful Agent Graphs

Single prompt calls struggle with complex software workflows. Multi-agent graphs divide tasks into specialized roles (Planner, Coder, Reviewer, Tester) that communicate over a shared state.

PYTHON
from typing import TypedDict, List

class AgentState(TypedDict):
    task: str
    code: str
    review_notes: List[str]
    approved: bool

def coder_node(state: AgentState):
    # Generates or modifies implementation code
    return {"code": "def solve(): pass", "review_notes": []}

def reviewer_node(state: AgentState):
    # Inspects code against quality checklist
    is_valid = "def solve" in state["code"]
    return {"approved": is_valid, "review_notes": ["Looks good"] if is_valid else ["Missing function"]}
Note: Agent graphs allow cyclical error correction loops until code passes verification.
Advertisement
Verified Partner
Quantitative Trading Systems & 30 AI Business Blueprints
Build predictable monthly recurring revenue with retainers & automated bots.
View Blueprints →

Knowledge Check: Test Your Understanding

1. Why are graph-based agent frameworks superior to linear chains for coding?

Frequently Asked Questions

What is the difference between LangGraph and CrewAI?
LangGraph is low-level and gives you precise control over state machines and conditional branches. CrewAI provides a high-level role-playing abstraction (agents with roles, goals, and backstories) ideal for quick agent orchestration.

2. Step by step: wire the StateGraph with conditional routing

Section 1 defined the state and the two nodes. A graph becomes an agent when you wire them with edges — including the edge that loops back, which is the entire reason to choose LangGraph over a chain. Here is the complete assembly, reusing the exact coder_node and reviewer_node from section 1:

PYTHON
from typing import TypedDict, List

from langgraph.graph import StateGraph, START, END


class AgentState(TypedDict):
    task: str
    code: str
    review_notes: List[str]
    approved: bool


def coder_node(state: AgentState):
    # Generates or modifies implementation code
    return {"code": "def solve(): pass", "review_notes": []}


def reviewer_node(state: AgentState):
    # Inspects code against the quality checklist
    is_valid = "def solve" in state["code"]
    return {
        "approved": is_valid,
        "review_notes": ["Looks good"] if is_valid else ["Missing function"],
    }


def route_after_review(state: AgentState) -> str:
    # The conditional edge: approve -> finish, reject -> loop back to coder
    return END if state["approved"] else "coder"


graph = StateGraph(AgentState)
graph.add_node("coder", coder_node)
graph.add_node("reviewer", reviewer_node)
graph.add_edge(START, "coder")
graph.add_edge("coder", "reviewer")
graph.add_conditional_edges(
    "reviewer",
    route_after_review,
    {"coder": "coder", END: END},
)

app = graph.compile()

result = app.invoke({
    "task": "Write solve()",
    "code": "",
    "review_notes": [],
    "approved": False,
})
print(result["approved"], result["review_notes"])

Read the wiring, not the nodes: nodes return partial state updates, edges define the possible paths, and the router function is the only place control flow lives. Swap the deterministic reviewer for an LLM call and the topology does not change — which is why the human-in-the-loop gate from this tutorial’s objectives drops in cleanly as one more node between reviewer and END, pausing the graph before anything irreversible runs.

Add the human gate the objectives promised

A reviewer node that is itself an LLM still lets the graph run unattended end to end. For anything irreversible, insert a human node that pauses the graph with LangGraph’s interrupt: execution stops, the state is checkpointed, and a person resumes it with a decision. It needs a checkpointer so the paused state survives:

PYTHON
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command, interrupt


def human_gate(state: AgentState):
    decision = interrupt({
        "question": "Approve this code for the next step?",
        "code": state["code"],
    })
    return {"approved": decision == "approve"}


# graph.add_node("human", human_gate) and route reviewer -> human -> END/coder
app = graph.compile(checkpointer=MemorySaver())

config = {"configurable": {"thread_id": "review-1"}}
app.invoke(
    {"task": "Write solve()", "code": "", "review_notes": [],
     "approved": False},
    config=config,
)  # runs until the gate, then pauses with state saved

# A human reviews the code and resumes the same thread:
app.invoke(Command(resume="approve"), config=config)

The thread_id is the resumption key: the same graph, the same state, continued hours later by a different process if necessary. That pause-and-resume property is the concrete difference between a demo agent and one you may leave running near production systems.

3. A complete worked example

The same pipeline in CrewAI trades the graph for roles. Where LangGraph made you declare state and edges, CrewAI asks for agents with goals and tasks in sequence — the coder/reviewer pair from section 1, expressed as a crew:

PYTHON
from crewai import Agent, Crew, Process, Task

coder = Agent(
    role="Python Developer",
    goal="Write correct, minimal Python for the assigned task",
    backstory="Senior Python engineer who favors small, testable functions.",
    verbose=True,
)

reviewer = Agent(
    role="Code Reviewer",
    goal="Reject any code that fails the quality checklist",
    backstory="Staff engineer known for strict, specific review notes.",
    verbose=True,
)

write_task = Task(
    description="Implement the function described here: {task}",
    expected_output="A single Python code block with the implementation.",
    agent=coder,
)

review_task = Task(
    description=(
        "Review the implementation produced by the previous task. "
        "Check: correct signature, edge cases handled, no placeholder code."
    ),
    expected_output="Either APPROVED, or a numbered list of required fixes.",
    agent=reviewer,
)

crew = Crew(
    agents=[coder, reviewer],
    tasks=[write_task, review_task],
    process=Process.sequential,
    verbose=True,
)

result = crew.kickoff(
    inputs={"task": "Write solve() returning the sum of even numbers in a list"}
)
print(result.raw)

Run both versions on the same task and the trade-off from the FAQ becomes tangible. The LangGraph version gives you the loop for free — a rejection routes back to the coder until approval or the recursion limit — plus a state object you can inspect, persist, and resume. The CrewAI version is a third of the code and reads like an org chart, but the retry discipline lives in the reviewer’s instructions rather than in enforceable control flow. Prototype in CrewAI; harden in LangGraph.

4. Troubleshooting

Going deeper: multi-agent systems in practice

The StateGraph in this tutorial — Planner, Coder, Reviewer, and Tester nodes communicating over a shared TypedDict state — generalizes into a design method. Define the state schema first: the task, the generated code, the review notes, and the approval flag are the whole contract between agents. Keep each node single-purpose, route conditionally on state fields such as approved, and let the graph cycle — generate, test, inspect the errors, retry — because that loop is precisely what a linear chain cannot do. Then put the human-in-the-loop approval gate in front of anything irreversible, and the same architecture that feels like a demo becomes safe to leave running.

Common mistakes

Frequently asked questions

Should my first project use LangGraph or CrewAI?

As the tutorial's FAQ puts it, LangGraph is low-level and gives precise control over state machines and conditional branches, while CrewAI offers a high-level role-playing abstraction — agents with roles, goals, and backstories — for quick orchestration. Start with CrewAI for speed; move to LangGraph when the control flow becomes the interesting part.

Why is a graph better than a simple linear chain for coding agents?

Because of the loops. As the knowledge check emphasizes, graph frameworks support cyclical feedback: an agent can test its output, fail, inspect the errors, and retry. A chain moves in one direction and has no way back to the step that produced the error.

How do the agents in the example actually share information?

Through the shared typed state. Each node receives the AgentState — task, code, review_notes, approved — returns updates to it, and conditional edges read those fields to decide which node runs next.

Get 1 AI agent blueprint every week

Join the free PVT newsletter: one practical AI agent or automation you can build and sell, every week. No spam.