Frontend Labs
⋯

Part 18 — Agent frameworks & AI governance

Optional deep-dive. Part 12 built the agent loop by hand and Part 14 built its guardrails — both skills are complete on their own. This part covers two things you'll bump into once you take an agent from "working" to "shipped": the frameworks (LangGraph, CrewAI) that package that same loop for production, and the governance/compliance paperwork that increasingly has legal teeth. The two chapters are independent of each other — read either one, or neither, on its own merits.

Chapter 69 — Agent frameworks: LangGraph, CrewAI, and the SDKs

In one sentence

Agent frameworks package the loop, state, and tool-calling patterns you built by hand in Part 12 into reusable abstractions — graphs, crews, runners — so teams stop reinventing checkpointing, streaming, and multi-agent wiring for every project, at the cost of learning that framework's own mental model.

The problem

Chapter 51 built the entire agent loop in about 40 lines: think, act, observe, repeat, with state living in a plain messages list. That's the right way to learn what an agent is — no magic, no hidden control flow. But a few things get genuinely painful once you try to ship that loop as a product:

  • Resuming after a crash or a human-approval pause. Your loop's state is a Python list in memory. If the process dies mid-task, or a tool needs a human to click "approve" (Chapter 60), you need to persist and later reconstruct exact state — you'll write this once, then write it again for the next project, unless something standardises it.
  • Branching control flow. A raw while loop is naturally linear. Real agent systems often need conditional branches, cycles that aren't just "call the model again," and parallel fan-out/fan-in (Chapter 50's orchestrator-workers pattern) — expressible in plain Python, but messy past a certain complexity.
  • Multi-agent wiring. Chapter 54 covered multi-agent patterns conceptually; actually routing messages between five specialised agents by hand is real, repetitive plumbing.
  • Streaming, tracing, and visualisation. Watching an agent's execution graph live, or getting built-in tracing spans, is something you'd otherwise bolt on yourself (Chapter 55's observability requirements).

Frameworks exist to standardise these cross-cutting concerns. They do not give the model better judgement — that point is worth holding onto through this whole chapter.

The landscape

Framework Core abstraction Mental model Reach for it when
LangGraph A directed graph of nodes (functions) and edges (control flow) over a typed state object A state machine you draw before you code You need explicit branching, cycles, checkpointing, or human-in-the-loop interrupts, and want to see the control flow
CrewAI Agents (role + goal + backstory) assigned Tasks, coordinated by a Crew A team of specialists with a manager You want to stand up a multi-persona workflow fast and the control flow is mostly linear/hierarchical
OpenAI Agents SDK / Assistants API Provider-native "Assistant" with built-in threads, tool-calling, and file/code tools A hosted runtime you configure, not one you run You're fully committed to one vendor and want the least code to maintain
Claude Agent SDK The same primitives this guide taught (loop, tools, context) exposed as a maintained library, with subagents and permissions built in The Part 12 loop, hardened and packaged You want Part 12's transparency but don't want to re-write the plumbing (session persistence, hooks, permission prompts) yourself
AutoGen / AG2 Conversable agents that message each other in a group chat A chat room of agents talking to solve a task Research-flavoured multi-agent experiments; less common in production
LlamaIndex Workflows Event-driven steps over typed events A pub/sub pipeline You're already deep in LlamaIndex for RAG (Chapter 47) and want agents in the same codebase

LangGraph, in depth: the graph is the point

LangGraph is worth understanding in more depth than the others because its abstraction — a graph over explicit state — is the most structurally different from Chapter 51's loop, and the difference is instructive.

Three concepts:

  • State — a typed structure (e.g. a TypedDict) holding everything the graph needs: the message history, a scratchpad, a step counter. Every node reads from it and returns updates to it — this is the same messages list from Chapter 51, just given a schema.
  • Nodes — plain functions: (state) -> partial_state_update. A node that calls the LLM, a node that executes a tool, a node that checks a guardrail (Chapter 60) are all just functions with this same shape.
  • Edges — how the graph decides what runs next. A conditional edge is a function that inspects the current state and returns the name of the next node — this is where "if the model asked for a tool, go to the tool node; otherwise, end" lives.

The Chapter 51 loop, expressed as a two-node graph:

from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator

class AgentState(TypedDict):
    messages: Annotated[list, operator.add]   # new messages get appended

def call_model(state: AgentState):
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}

def call_tool(state: AgentState):
    last = state["messages"][-1]
    tool_call = last.tool_calls[0]
    result = TOOLS[tool_call["name"]](**tool_call["args"])
    return {"messages": [ToolMessage(content=str(result), tool_call_id=tool_call["id"])]}

def should_continue(state: AgentState):
    last = state["messages"][-1]
    return "call_tool" if getattr(last, "tool_calls", None) else END

graph = StateGraph(AgentState)
graph.add_node("call_model", call_model)
graph.add_node("call_tool", call_tool)
graph.set_entry_point("call_model")
graph.add_conditional_edges("call_model", should_continue, {"call_tool": "call_tool", END: END})
graph.add_edge("call_tool", "call_model")   # <-- the loop: tool result goes back to the model

app = graph.compile(checkpointer=MemorySaver())   # <-- state persists between runs

Line up should_continue against Chapter 51's loop condition ("if the model returned tool calls, execute them and go around again; otherwise, return the final answer") — it's the identical decision, just made explicit as a named, inspectable function instead of an if buried inside a while.

What the framework actually bought you here, concretely:

  • checkpointer=MemorySaver() — the entire state (message list, step count) is now persisted after every node. Kill the process mid-run and resume exactly where it left off. Building this yourself means serialising state to a database after every step — the framework did it in one line.
  • The graph can be rendered as an actual picture (app.get_graph().draw()), which is a real debugging aid once a graph has more than three nodes.
  • interrupt() (LangGraph's human-in-the-loop primitive) can pause execution before a node runs and wait for external approval — a built-in version of Chapter 60's "human approval for consequential actions" gate.

What it cost you: the control flow now lives partly in your code (call_model, call_tool) and partly in the framework's graph-wiring API (add_conditional_edges, the Annotated[list, operator.add] reducer syntax). Debugging a wrong answer means understanding both layers, not just reading a while loop top to bottom.

CrewAI: role-based orchestration

CrewAI's abstraction is closer to organisational structure than to a state machine:

from crewai import Agent, Task, Crew

researcher = Agent(role="Researcher", goal="Find accurate facts about X",
                    backstory="A meticulous fact-checker.", tools=[search_tool])
writer = Agent(role="Writer", goal="Write a clear summary from research",
               backstory="A concise technical writer.")

research_task = Task(description="Research X thoroughly", agent=researcher)
writing_task = Task(description="Write a summary using the research",
                     agent=writer, context=[research_task])

crew = Crew(agents=[researcher, writer], tasks=[research_task, writing_task])
result = crew.kickoff()

This is faster to stand up than a LangGraph graph for a fixed pipeline of distinct roles (Chapter 54's "genuinely parallel subtasks, different tool sets" case), but the execution is more of a black box — you don't get the same explicit, inspectable control-flow graph, so debugging why the writer produced a bad summary means reading prompt logs rather than following named edges.

Deciding: raw loop vs. a framework

Situation Choice
Learning, or a small, well-understood task Raw loop (Part 12) — you understand every failure mode
Need checkpointing / resume / human-approval pauses LangGraph
Fixed pipeline of a few distinct personas CrewAI
Fully committed to one model vendor, want minimal code That vendor's SDK
Building on Anthropic's stack and want Part 12's model, packaged Claude Agent SDK

Common confusions

  • A framework does not make the model reason better. It manages state and control flow. All of Part 14's guardrails — action guardrails, least privilege, human approval for irreversible actions — still have to be designed and added explicitly; no framework ships them on by default.
  • "Agentic" frameworks are not required to build an agent. Chapter 51's 40-line loop is a complete, correct agent. Reach for a framework because of an operational need (persistence, visualisation, multi-agent plumbing), not by default.
  • Framework version churn is real. These libraries change their APIs faster than the model APIs underneath them. Read the concepts docs after understanding Part 12's raw loop, so you can tell which parts of a framework's API are "the agent loop" (stable) vs. "this release's syntax" (not).

Practice (30 min)

Take your Chapter 51 ReAct loop implementation and re-express the same tool-call cycle as a two-node LangGraph graph, as shown above. Run the same test prompts through both and confirm you get identical tool calls and final answers — the behaviour shouldn't change, only how the control flow is written.

Check yourself

  1. What does an agent framework actually provide that a hand-written loop doesn't?
  2. In LangGraph, what is a conditional edge, and what part of Chapter 51's loop does it replace?
  3. Why doesn't adopting a framework reduce the need for Part 14's guardrails?
  4. When would CrewAI's role-based abstraction be a worse fit than LangGraph's graph?

Further reading

  • Docs: langchain-ai.github.io/langgraph — concepts, then the tutorials.
  • Docs: docs.crewai.com — Agents, Tasks, Crews, Processes.
  • Docs: platform.openai.com/docs/assistants — the Assistants API.
  • Docs: docs.claude.com — the Claude Agent SDK (the primitives from Part 12, packaged).
  • Paper: Wu et al., "AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation" (2023, arXiv:2308.08155).

Generated from real-life-ai-example-v1.md · 8 Oct 2026 16:36