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
whileloop 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 samemessageslist 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
- What does an agent framework actually provide that a hand-written loop doesn't?
- In LangGraph, what is a conditional edge, and what part of Chapter 51's loop does it replace?
- Why doesn't adopting a framework reduce the need for Part 14's guardrails?
- 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).