Agentic AI · Module 21 of 41 · 10 concepts · about 19 min

Agent Frameworks

What an agent framework is and what it adds to a hand-written loop, then the main ones in turn: LangChain, LangGraph, AutoGen and its successors, CrewAI and the OpenAI Agents SDK; each mapped back to the concepts it implements, and how to choose.

At work: The question a team faces in week two: write the loop ourselves, or adopt a framework, and which. Interviewers ask it to see whether you know what is underneath.

What a framework gives you

What it is

An agent framework is a library that supplies the parts around the model so you do not write them yourself. Nearly all of them provide the same things under different names:

  • a loop or runner (module 10);
  • a way to define tools from functions (module 3);
  • state and persistence (module 6);
  • workflow wiring: graphs, routes, hand-offs (module 14);
  • human approval: pause and resume (module 15);
  • multi-agent plumbing (modules 16 to 18);
  • tracing (module 25);
  • ready-made integrations with models, stores and tool servers.

Why it matters

A framework saves time on plumbing and costs you visibility and control.

  • For one loop and a few tools, the hundred lines of module 10 are simpler to debug than any framework.
  • For saved state, approvals, branching flows and several agents, a framework saves weeks.
  • Because every framework implements the same concepts, knowing the concepts lets you learn any of them in a day and switch when needed.
  • The risk is treating the framework as the agent and losing sight of what is sent to the model.

In the real world: A team that began with a hand-written loop adopts a graph framework when approvals and long-running jobs arrive; its tool layer, written as plain functions, moves across unchanged.

Visual example

Rosetta stone

Learn the left-hand column once. The rest are translations.

The same concepts under each framework's names
conceptLangGraphOpenAI Agents SDKCrewAIAutoGen AgentChat
One agentA node, or create_agentAgentAgentAssistantAgent
The loopGraph executionRunnerCrew kickoffTeam run
A tool@tool function@function_toolToolPython function
StateTyped State objectRun context, sessionFlow stateTeam state
PersistenceCheckpointerSessionFlow persistenceSave and load state
Flow controlEdges, conditional edgesHandoffsProcess, FlowsTeam types, termination
Human approvalinterrupt and resumeTool approvalHuman input on a taskA user proxy agent
DelegationSub-graphs, supervisorHandoffs, agents as toolsManager, delegationGroup chat, selector

Choosing a framework first and designing the agent to fit it. Design the agent from the task (tools, state, flow, approvals), then pick the framework whose parts match, or none.

LangChain

What it is

LangChain is the oldest and broadest of the frameworks: a large library of building blocks for model applications. Its core ideas:

  • Models: one interface over many providers, so the model can be swapped.
  • Tools: a function plus a name, description and schema. The @tool decorator builds one from a Python function and its docstring.
  • Chains: steps joined in a fixed sequence, the output of one feeding the next. This is the sequential workflow of module 14.
  • Agents: a model in a tool-calling loop. create_agent builds a standard one; it runs on LangGraph underneath.
  • Memory: short-term memory is the message history kept in the agent's state and saved by a checkpointer; long-term memory is a store the agent reads and writes.
  • Middleware: hooks around the loop for summarising history, requiring approval, or redacting data.

Why it matters

Its strength is breadth: hundreds of integrations with models, vector stores, document loaders and tools.

  • It is the quickest route from nothing to a working prototype.
  • A prebuilt agent hides the loop, which is convenient until you need to see inside it.
  • Much older tutorial material uses earlier APIs (initialize_agent, AgentExecutor, ConversationBufferMemory) that have since been replaced; check the date of anything you read.

In the real world: A start-up's prototype used LangChain for its integrations and shipped in a week; before launch the team replaced the prebuilt agent with an explicit graph so they could add an approval step.

from langchain.agents import create_agent
from langchain.tools import tool

@tool
def get_order(order_id: int) -> str:
    """Look up one order: total, delivery fee and how late it arrived."""
    return lookup(order_id)

agent = create_agent(
    model="anthropic:claude-opus-5-5",
    tools=[get_order],
    system_prompt="You are Kirana Kart's support agent. Look up the order before answering.",
)

result = agent.invoke({"messages": [{"role": "user", "content": "Was order 88213 late?"}]})
print(result["messages"][-1].content)

Stacking abstractions until nobody can say what prompt reached the model. Turn on tracing from the first day and read the actual messages for a few runs.

LangGraph: graphs, nodes, edges and state

What it is

LangGraph models an agent as a graph, which is a state machine (module 6) you draw in code:

  • State: a typed object holding everything the run knows: messages, plan, counters. Every node reads it and returns an update to it.
  • Nodes: functions that do one step: call the model, run tools, validate, ask a person. A node receives the state and returns the fields it changed.
  • Edges: fixed connections saying which node runs next.
  • Conditional edges (conditional routing): a function that looks at the state and returns the name of the next node. This is where branching and loops come from.
  • Reducers: a rule for combining updates to one field, such as 'append to the message list' instead of 'replace it'.

Why it matters

A graph makes the control flow explicit, which is exactly what agents usually hide.

  • You can see, and draw, every path the system can take.
  • A loop is a cycle in the graph with a conditional edge that exits; nothing more.
  • Fixed steps and free agent steps sit in the same structure, so 'workflow with an agent inside' is natural.
  • The price is more code than a prebuilt agent, and concepts to learn before the first run.

In the real world: A claims team's graph has nine nodes, of which two call a model; the diagram generated from the code is what they showed the auditor.

from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]     # reducer: new messages are appended
    steps: int

def call_model(state: State) -> dict:
    reply = model_with_tools.invoke(state["messages"])
    return {"messages": [reply], "steps": state["steps"] + 1}

def run_tools(state: State) -> dict:
    return {"messages": execute_tool_calls(state["messages"][-1])}

def route(state: State) -> str:                  # a conditional edge
    if state["steps"] >= 8:
        return END
    return "tools" if state["messages"][-1].tool_calls else END

graph = StateGraph(State)
graph.add_node("model", call_model)
graph.add_node("tools", run_tools)
graph.add_edge(START, "model")
graph.add_conditional_edges("model", route, ["tools", END])
graph.add_edge("tools", "model")                 # the cycle: this is the agent loop
app = graph.compile()

Putting everything in one giant node 'because it is simpler'. You then have a graph with one box and none of its benefits. A node should be one step you would want to see, retry or test alone.

Practise it: Design a Multi-Agent AI System, Design a Multi-Agent Incident Response System

LangGraph: persistence, checkpoints and interrupts

What it is

LangGraph's other half is durability:

  • A checkpointer saves the state after every step. Each conversation or job is a thread with an id; invoking the graph with the same thread id continues from where it stopped.
  • Persistence backends range from in-memory (for tests) to SQLite and Postgres (for production).
  • An interrupt pauses a node and returns a value to the caller: 'approve this refund?'. The run stops, with its state saved.
  • Resuming: the caller invokes the graph again on the same thread with a Command(resume=...) carrying the answer, and the node continues.
  • Time travel: any earlier checkpoint can be loaded, inspected, edited and re-run.

Why it matters

This is module 6 and module 15 as library features.

  • Crash recovery, long waits and human approval all come from the one mechanism of saved checkpoints.
  • Short-term memory across turns is simply the thread's saved messages.
  • Interrupts require a checkpointer: without saved state there is nothing to resume.
  • Because a resumed node re-runs from its start, anything before the interrupt inside that node must be safe to repeat.

In the real world: A loan-approval graph pauses at a manager's sign-off for a day or more; a deployment in between is invisible because the thread's checkpoint lives in Postgres.

from langgraph.checkpoint.memory import InMemorySaver      # use a Postgres saver in production
from langgraph.types import Command, interrupt

def approve_refund(state: State) -> dict:
    call = state["pending_refund"]
    if call["amount"] > 500:
        decision = interrupt({"question": "Approve this refund?", "refund": call})   # pauses here
        if decision != "approve":
            return {"messages": [tool_message(call, "Declined by a supervisor.")]}
    return {"messages": [tool_message(call, issue_refund(**call))]}

app = graph.compile(checkpointer=InMemorySaver())
thread = {"configurable": {"thread_id": "ticket-4411"}}

result = app.invoke({"messages": [user("Refund ₹1,800 for order 88213")], "steps": 0}, thread)
print(result["__interrupt__"])          # the question, waiting for a person

# Hours later, possibly on another server:
app.invoke(Command(resume="approve"), thread)

Doing a side effect before interrupt in the same node. On resume the node starts again from its first line and the side effect happens twice. Put the interrupt first, or make the effect idempotent.

AutoGen, AG2 and Microsoft Agent Framework

What it is

AutoGen, from Microsoft Research, popularised multi-agent conversation: agents that solve a task by exchanging messages. Its ideas:

  • Agent roles: each agent is defined by a system message and tools: an assistant, a critic, a coder, a user proxy that stands in for a person or runs code.
  • Conversations as the control flow: instead of a graph, the task proceeds as agents take turns speaking.
  • Coordination by team type: round-robin (fixed order), selector (a model chooses who speaks next), or hand-off based.
  • Termination conditions: the conversation ends on a key phrase, a message count or a time limit.

Status matters here: Microsoft put AutoGen into maintenance mode in late 2025 and directs new work to Microsoft Agent Framework, which merges AutoGen's ideas with Semantic Kernel. AG2 is a separate community-run fork of the original AutoGen.

Why it matters

Conversation-driven design is easy to start and hard to control.

  • It suits open-ended collaboration: a writer and a critic, a coder and a tester.
  • Because the flow is whatever the agents say, it is less predictable than a graph and harder to bound in cost.
  • A shared transcript grows quickly, the context explosion of module 18.
  • Even if you never use it, the pattern (roles, turn-taking, termination) appears in every framework and in interviews.

In the real world: A research group's AutoGen prototype produced impressive demos; the production rewrite kept the writer and critic roles but moved them into a graph with a two-round limit.

# AutoGen AgentChat style. Legacy AutoGen is in maintenance mode; the same shape
# carries over to its successors, with different imports.
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination
from autogen_agentchat.teams import RoundRobinGroupChat

writer = AssistantAgent("writer", model_client=client,
                        system_message="Draft the customer reply from the facts given.")
critic = AssistantAgent("critic", model_client=client,
                        system_message="Check the draft against the facts. Reply APPROVE if correct.")

team = RoundRobinGroupChat(
    [writer, critic],
    termination_condition=TextMentionTermination("APPROVE") | MaxMessageTermination(6),
)
result = await team.run(task="Order 88213 was 62 minutes late; fee ₹25 refunded. Write the reply.")

A group chat with no message cap. Two polite agents can thank each other until the budget runs out. Always combine a success condition with a hard limit.

CrewAI

What it is

CrewAI organises work as a team with job descriptions. Its four ideas:

  • Agents: each has a role, a goal and a backstory (which together become its system prompt), plus tools.
  • Tasks: a description of one piece of work, its expected output, and the agent assigned to it. A task can take earlier tasks' outputs as context.
  • Crews: a set of agents and tasks run under a process: sequential (tasks in order) or hierarchical (a manager agent assigns and reviews).
  • Flows (workflows): event-driven, code-first orchestration with explicit state, for when you need precise control around one or more crews.

Why it matters

It is the most approachable way to express a role-based team.

  • A crew reads like a project plan, which makes it easy for non-specialists to follow.
  • Sequential crews are really a workflow of agent steps; hierarchical crews are a supervisor design.
  • The role-and-backstory abstraction can hide what the prompt actually says, and autonomy inside a crew is harder to bound than in a graph.
  • Flows exist because production systems needed deterministic control around the crews.

In the real world: A marketing team builds a research-write-review crew in an afternoon; when they need a compliance approval between two tasks they wrap the crew in a Flow.

from crewai import Agent, Crew, Process, Task

researcher = Agent(
    role="Policy researcher",
    goal="Find the exact policy that applies to the customer's complaint",
    backstory="You know Kirana Kart's help centre and quote it precisely.",
    tools=[search_help_centre],
)
writer = Agent(
    role="Support writer",
    goal="Write a clear, accurate reply to the customer",
    backstory="You write short, plain replies and never promise what was not done.",
)

find_policy = Task(
    description="Find the policy for this complaint: {complaint}",
    expected_output="The policy text and its title, quoted exactly.",
    agent=researcher,
)
write_reply = Task(
    description="Write the reply using the policy found.",
    expected_output="A reply under 80 words.",
    agent=writer,
    context=[find_policy],                    # receives the first task's output
)

crew = Crew(agents=[researcher, writer], tasks=[find_policy, write_reply],
            process=Process.sequential)
result = crew.kickoff(inputs={"complaint": "My order came an hour late."})

Writing elaborate backstories and leaving expected_output vague. The backstory is flavour; the expected output is the contract the next task depends on.

OpenAI Agents SDK

What it is

The OpenAI Agents SDK is a small library built on a few primitives:

  • Agent: a model with instructions, tools and optional hand-offs. It works with OpenAI models and, through adapters, with others.
  • Runner: runs the agent loop: call the model, run tools, follow hand-offs, repeat until a final output or a turn limit.
  • Tools: Python functions decorated with @function_tool; hosted tools; and MCP servers.
  • Handoffs: an agent transfers the conversation to another agent. To the model, a hand-off looks like a tool it can call. This is the peer-to-peer design of module 17.
  • Guardrails: checks that run on the input, the output or a tool call, and can stop the run.
  • Sessions: stored conversation history, so a later run continues an earlier one.
  • Tracing: every run, model call, tool call and hand-off is recorded by default.

Why it matters

Its architecture is deliberately thin: the loop, plus hand-offs.

  • There is little to learn, and the loop behaves like the one you wrote in module 10.
  • Hand-offs make triage-to-specialist systems very short to write.
  • Agents can also be used as tools, which gives a supervisor design.
  • It has less built-in for complex fixed flows and durable long-running work than a graph framework; many teams add their own orchestration around it.

In the real world: A support team ships a triage agent with four specialists in under two hundred lines; the default tracing is what they use to find why one specialist is over-used.

from agents import Agent, Runner, function_tool

@function_tool
def get_order(order_id: int) -> str:
    """Look up one order: total, delivery fee and how late it arrived."""
    return lookup(order_id)

billing = Agent(name="Billing", instructions="Handle refunds and charges.", tools=[get_order])
delivery = Agent(name="Delivery", instructions="Handle late or missing orders.", tools=[get_order])

triage = Agent(
    name="Triage",
    instructions="Decide which specialist should handle the customer and hand off.",
    handoffs=[billing, delivery],
)

result = Runner.run_sync(triage, "My order 88213 came an hour late.", max_turns=8)
print(result.final_output)

Reading 'handoffs' as free-for-all routing. Each agent's allowed hand-offs are the list you give it; keep those lists short and one-directional where you can, or agents pass a request back and forth.

Practise it: Design an AI Customer Support Agent

Choosing a framework, or none

What it is

Choose by what the task needs, in this order:

  • One loop, a few tools, short tasks: no framework. A hundred lines and a database table.
  • Fixed flow with agent steps, approvals, long-running or resumable work: a graph framework with persistence, such as LangGraph, or a durable-execution engine.
  • A conversation routed between specialists: a hand-off-based SDK.
  • A role-based team for content or research, built quickly: a crew-style framework.
  • Tied to one cloud or model vendor's platform: that vendor's SDK, for the integrations.

Why it matters

Whatever you pick, protect yourself against change:

  • keep tools as plain functions with their own tests, independent of the framework;
  • keep prompts in your own files;
  • keep your evaluation set outside the framework, so you can compare one against another;
  • pin versions; these libraries make breaking changes often;
  • make sure you can always print what was sent to the model.

Frameworks come and go. The concepts in modules 1 to 20 are what last.

In the real world: A company standardises not on a framework but on three rules: tools as plain functions, an evaluation set per agent, and traces for every run. Teams choose their framework within those rules.

Visual example

Choice

The first row is a legitimate answer more often than people expect.

Which framework fits which need
optioncontrol flow isstrongest atwatch for
No frameworkYour own loopClarity, debugging, small agentsRebuilding persistence yourself
LangChainPrebuilt agent, middlewareIntegrations, fast prototypesHidden prompts; outdated tutorials
LangGraphAn explicit graphDurable, controllable workflowsMore code; concepts to learn
OpenAI Agents SDKA loop with hand-offsSimple multi-agent routing, tracingComplex fixed flows
CrewAIRoles and tasksQuick role-based teamsBounding autonomy and cost
AutoGen lineageA conversationOpen-ended collaborationMaintenance status; runaway chats

Answering 'which framework?' with a brand loyalty. The honest answer is 'it depends on whether I need durable state, fixed flows, hand-offs or none of these', followed by which.

Interview questions: agent frameworks

What it is

The questions interviewers ask about agent frameworks, from the first screening call to the senior round. Cover the answer, say yours aloud, then compare.

Why it matters

Reading an idea and explaining it are different skills.

  • Each answer below is the two or three sentences an interviewer is listening for.
  • The reasoning behind each one is in the cards above. If an answer surprises you, go back to that card.

In the real world: A candidate is asked which framework they prefer; the one who answers 'for what?' and then maps three task shapes to three choices is rated above the one who names a favourite.

Q: What does an agent framework do?

It supplies the plumbing around the model: the loop, tool definitions, state and persistence, flow control, approvals, multi-agent wiring, tracing and integrations. The model call and the tool functions are still yours.

Q: What is LangGraph?

A library that models an agent as a graph over a typed state: nodes are steps, edges and conditional edges decide what runs next, and a checkpointer saves state after each step so runs can pause, resume and be replayed. It is a state machine with model calls in some nodes.

Q: How does LangGraph implement human-in-the-loop?

A node calls interrupt, which saves the state and returns a payload to the caller. The run resumes when the graph is invoked on the same thread with a resume command. It needs a checkpointer, and code before the interrupt in that node runs again on resume.

Q: LangChain versus LangGraph?

LangChain is the broad toolkit: model interfaces, tools, integrations and a prebuilt agent. LangGraph is the lower-level runtime for explicit, durable control flow, and LangChain's agents run on it.

Q: How does AutoGen differ from a graph approach?

Control flow is a conversation among role-playing agents with a termination condition, not an explicit graph. It is flexible and less predictable. Note that it is now in maintenance mode, with Microsoft Agent Framework and the AG2 fork as successors.

Q: What are CrewAI's core concepts?

Agents with a role, goal and backstory; tasks with an expected output assigned to agents; a crew that runs them sequentially or under a manager; and Flows for deterministic orchestration around crews.

Q: What are the OpenAI Agents SDK's primitives?

Agents, a Runner that executes the loop, function tools, hand-offs between agents, guardrails on input and output, sessions for history, and built-in tracing.

Q: When would you not use a framework?

For a single loop with a few tools. Hand-written code is shorter, fully visible and easier to debug. Adopt a framework when you need persistence, approvals, complex flows or multi-agent wiring.

Explaining a framework by its API without being able to say which underlying concept each piece implements.

Exercise: map a framework back to the concepts

What it is

A short build that uses this module's ideas. Write it yourself before looking anything up; the checks at the end tell you whether it works.

Why it matters

An idea you have only read fades in a week.

  • Building it once, even in fifty lines, is what makes it yours.
  • Interviewers can tell the difference between someone who has read about a loop and someone who has debugged one.

In the real world: A staff engineer makes this the entry ticket for adopting any new framework: 'show me the raw request, and show me where the limit is'.

Your task. Take one framework's quick-start agent and identify, in its source or trace, each concept from the first twenty modules.

Steps

  1. Pick any one framework from this module and install it with a pinned version.
  2. Build its simplest tool-calling agent with one tool.
  3. Turn on tracing or debug logging and capture the exact request sent to the model: the system prompt, the tool definitions and the messages.
  4. Find where each of these lives: the loop, the step limit, the tool executor, the state, the stop condition.
  5. Change the step limit to 1 and observe what the framework returns.
  6. Rewrite the same agent as the hand-written loop from module 10 and compare line counts and what you can see.

You are done when

  • You have the raw model request printed or saved.
  • You can name the framework's term for each of: loop, tool, state, persistence, stop condition.
  • You know what happens at the step limit, from having seen it.
  • You can say one thing the framework gave you for free and one thing it hid.

Stretch. Repeat with a second framework and extend the Rosetta-stone table with what you found.