Agent Frameworks Compared
Pick LangGraph, CrewAI, AutoGen, Agno, Mastra, an agent SDK or plain Python by problem shape, state and routing cost.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Comparisons
- Defaults and numbers
- Troubleshooting
- Questions and tips
Orchestration frameworks side by side#
| Factor | LangGraph | CrewAI | AutoGen | Agno |
|---|---|---|---|---|
| Core abstraction | StateGraph | Crew | ConversableAgent pair | Agent |
| Mental model | State machine | Team of specialists | Chat | Agent with batteries included |
| State | Typed state, checkpointer | Strings through context | Chat history | Session store through storage= |
| Who branches | You, with conditional edges | Manager, or you at build time | Agents, through chat | The next tool call |
| Routing tokens | Only where you write llm.invoke | Hierarchical manager spends tokens | GroupChatManager spends tokens | Thin single-agent path |
| Best fit | Typed state, human interrupts | Short role-driven plans | Proposer-critic dialogue | Single agent, sessions, memory |
| Worst fit | Unknown topology | Complex branching | Known DAG, durable state | Deep branched graphs |
Agent SDKs and runtimes side by side#
| Factor | OpenAI Agents SDK | Claude Agent SDK | AutoGen v0.4 | Mastra |
|---|---|---|---|---|
| Primitives | Agent, Handoff, Guardrail, Session, Tracing | Tools, MCP, hooks, subagents, session store | Core, AgentChat, Extensions | Agents, Tools, Workflows |
| Delegation | Tool named transfer_to_<agent_name> | Subagents with their own context | Messages to an actor inbox | Workflow steps |
| State | Session in SQLite, Redis, custom | append, load, delete with cascade | Private actor state | Composite storage |
| Tracing | On by default | W3C trace context into the CLI | A span for each message | Mastra Studio |
| Use for | OpenAI-first products | Claude-first products | Actor concurrency, fault isolation | TypeScript, Next.js or Vercel |
Run the projects#
| Command | What it does |
|---|---|
python3 scripts/ | Grade one stage |
python3 scripts/ | Run the real SDK tests. A SKIP fails |
node --experimental-strip-types cli.ts --ticket fixtures/ | Scratch workflow. Writes a checkpoint when approval is required |
node --experimental-strip-types optional-mastra/ | Durable Mastra run. Reports a runId |
python3 main.py --ticket fixtures/ | Offline support review in index.html |
main.py --ticket YOUR_TICKET.json --adk --out support-adk-output | Same ticket through real ADK agents |
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
| A hands off to B, B hands back to A | Handoff drift | Add a hop counter |
| Built-in tool skips the check | Tool guardrails fire on function tools only | Add a separate policy |
| Task N+1 ad-libs on free text | Brittle handoffs | Set output_pydantic on Task N |
| Crew costs too many tokens | Manager-LLM token tax | Switch to Sequential |
| Context gone before the first tool call | Long backstories | Keep backstories under 200 words |
| Overhead dominates tiny tasks | Subagent over-spawn | Batch the tasks |
| Sessions grow without limit | Session bloat | Use list_sessions and an expiry policy |
| Resume fails before the tool | Wrong ticket or digest in approval | Keep ticketId and planHash unchanged |
Use X when#
- LangGraph
- typed state, approvals, resume,
Sendfanout. - CrewAI
- a Flow that calls
Crew.kickoff()in a step. - AutoGen
- proposer-critic dialogue.
- Agno
- many short-lived Python agents, FastAPI.
- Mastra
- TypeScript, Next.js or Vercel, Zod-typed tools.
- Plain Python
- two LLM calls and a tool.
Decision tree: recommend(p)#
Actor runtime (AutoGen v0.4 shape)#
SDK tasks in code#
| Task | Code |
|---|---|
| Ask the recommender | python main.py --ask |
| Hand off to an agent | transfer_to_<agent_name> |
| Turn off tracing | OPENAI_AGENTS_DISABLE_TRACING= |
| Fan spans to your backend | add_trace_processor(processor) |
| Run with stored history | Runner.run(agent, |
| Force a typed task output | output_pydantic= |
| List subagent sessions | list_subkeys(session_id) |
Suspend and resume in Mastra#
const workflow = createWorkflow({ id: "ticket-triage", inputSchema: ticketSchema, outputSchema: resultSchema }) .then(classifyStep)intent read or write .then(planStep)lookup or update action .then(executeStep)suspends before an update .commit();if (!resumeData?.approved) return suspend({ ticketId: plan.ticket.id, planHash: digest });digest = planHash(plan)const { workflow, storage } = await persistentWorkflow(tool, "file:" + resolve(args["--db"]));const run = await workflow.createRun({ runId: args["--resume"] });fresh process, same fileresult = await run.resume({ step: "execute", resumeData });approved, ticketId, planHashWhat the projects build and check#
- Mastra builds
- a typed approval workflow. It suspends before an update and resumes from SQLite.
- Mastra output
workflow.json, plusapproval.jsonwith approved=false.- Mastra check
- matching approval gives one tool call. A wrong hash gives zero calls.
- ADK builds
- a redacted support reply or a human escalation, then the same rules in real ADK agents.
- ADK output
- an HTML review page and
support.json. - ADK check
- invoice login ties billing and access, so it escalates.
Defaults and limits#
| Setting | Default | Note |
|---|---|---|
Runner max_hops | 3 | Hop counter against handoff drift |
Runtime max_messages | 100 | Messages for each run_until_idle |
HierarchicalCrew max_steps | 5 | Manager rounds before stop |
| Guardrail mode | Parallel | Lower tail latency, wastes tokens on a trip |
executePlan maxCalls | 3 | Allowed range 1 to 100 |
executePlan maxAttempts | 2 | Allowed range 1 to 10 |
Questions#
- Why is a handoff a tool?
- The model calls
transfer_to_billing_agent. The runtime switches agents. - Why do actors isolate faults?
send(agent_b, msg)only queues. The runtime catches handler crashes.- Can the model change the ADK route?
- No. It raises
ValueError.