The comparison chart is already lying to you the moment it asks which agent framework is “best.” It turns an operational decision into a shopping decision: features in rows, names in columns, and a tidy winner at the bottom.
Production does not fail in a table. It fails when a retry repeats a customer-facing action, when a worker resumes with the wrong state, when a handoff loses the reason for a decision, or when nobody can establish whether an agent actually changed anything. Those are contract failures. A framework can help express that contract; it cannot supply one after the fact.
The question is not whether this demos well; it is whether it survives maintenance, handoff, and local constraints.
This is therefore not a scorecard for LangGraph, AutoGen, CrewAI, and LangChain. It is an exit-criteria article: what must be true before you allow one of these tools into a real workflow, and what evidence lets you remove it later without leaving a damaged system behind. The official materials describe different primitives: LangGraph documents persisted graph state through checkpointers and durable cross-thread data through stores; AutoGen AgentChat documents agents and teams; CrewAI documents agents, crews, flows, guardrails, memory, and observability; LangChain presents a framework for building context-aware applications. Those are useful starting points, not operating guarantees. LangGraph persistence AutoGen AgentChat CrewAI documentation LangChain documentation

Start with the workload, not the framework name
An agent framework deserves evaluation only after the workload has a boundary. “Answer support questions” is not a boundary. “Classify an inbound request, collect approved account facts, propose a reply, and require a human approval before sending” is one. It names the input, tools, state, output, and irreversible step.
Write that description before selecting an orchestration model. Then separate the work into three categories.
Read-only work gathers or summarizes information. It can still leak data or invent a conclusion, but it does not alter a customer record, send an email, or mutate a repository. Its central proof is provenance: which inputs, sources, and tool responses informed the output.
Reversible work creates an internal artifact: a draft, a ticket, a proposed patch, or a queued action with a cancellation path. Its central proof is ownership and reversal: where the artifact lives, who reviews it, and what removes it.
Irreversible or externally visible work sends, publishes, charges, deletes, deploys, or changes a system of record. Its central proof is an idempotency key, an approval boundary, and an auditable receipt from the downstream system. Do not let an LLM decide whether those controls are needed. They are needed because the effect matters.
This distinction changes the framework conversation. A conversation-oriented team can be appropriate for creating a research brief. A graph with persisted state can be appropriate where a run must stop and resume. A crew or flow can be useful when distinct roles and a defined process are the operating model. A small LangChain-based application can be enough when the job is a bounded call to a model plus a controlled retrieval or tool layer. The official documentation establishes those building blocks; the workload establishes whether any one deserves the added surface area.
The narrow comparison below deliberately avoids a feature inventory. It asks which production concern the documented primitive gives you a place to implement.
| Framework resource | Documented primitive to inspect | Production question to answer before approval |
|---|---|---|
| LangGraph | Checkpointer for thread state; store for cross-thread data | What state resumes, how is it retained, and what key identifies the run? |
| AutoGen AgentChat | Agents and teams | Which agent is allowed to call which tool, and where is team completion recorded? |
| CrewAI | Agents, crews, flows, guardrails, memory, observability | Which flow owns the handoff, and where does a failed run leave evidence? |
| LangChain | Framework components for context-aware applications | Is an agent runtime needed, or is a bounded chain with explicit tools enough? |
LangGraph’s persistence guide is especially direct about the state question: checkpointers persist snapshots for a thread, while stores persist application-defined data across threads. It also warns that in-memory checkpointing does not survive a process restart and calls out retention for accumulating checkpoints. Treat that as an evaluation prompt. If your design cannot state its thread identifier, retention policy, and recovery behavior, a persistence primitive will preserve confusion rather than solve it. https://docs.langchain.com/oss/python/langgraph/persistence

Treat durable execution as an idempotency problem
“Durable” is often heard as “the run comes back after a failure.” That is incomplete. A resumed run must not repeat an external effect just because the process lost its place between the request and the checkpoint.
For every side effect, define a stable operation key before calling the external service. Store the result keyed by that value. On a retry, return the previously recorded receipt instead of performing the effect again. This is ordinary application engineering, and it remains necessary inside an agent workflow.
The following Python example is intentionally independent of a specific framework so it can sit behind a LangGraph node, a CrewAI tool, an AutoGen agent tool, or a conventional Laravel-facing service. It runs as written, uses SQLite for a transactional local record in a single-process illustration, and models a charge-like operation without contacting a payment provider. Replace perform_external_effect with the actual adapter; keep the idempotency boundary.
1import hashlib
2import sqlite3
3from pathlib import Path
4
5DB = Path("agent-effects.sqlite3")
6
7
8def setup() -> None:
9 with sqlite3.connect(DB) as conn:
10 conn.execute("""
11 CREATE TABLE IF NOT EXISTS effects (
12 operation_key TEXT PRIMARY KEY,
13 receipt TEXT NOT NULL
14 )
15 """)
16
17
18def operation_key(order_id: str, action: str) -> str:
19 raw = f"{order_id}:{action}".encode("utf-8")
20 return hashlib.sha256(raw).hexdigest()
21
22
23def perform_external_effect(order_id: str, operation_key: str) -> str:
24 # Replace this with a provider call that also receives operation_key.
25 return f"receipt:order:{order_id}"
26
27
28def run_effect(order_id: str, action: str) -> str:
29 key = operation_key(order_id, action)
30 with sqlite3.connect(DB) as conn:
31 row = conn.execute(
32 "SELECT receipt FROM effects WHERE operation_key = ?", (key,)
33 ).fetchone()
34 if row:
35 return row[0]
36
37 receipt = perform_external_effect(order_id, key)
38 conn.execute(
39 "INSERT INTO effects(operation_key, receipt) VALUES (?, ?)",
40 (key, receipt),
41 )
42 return receipt
43
44
45if __name__ == "__main__":
46 setup()
47 print(run_effect("order-42", "send-confirmation"))
48 print(run_effect("order-42", "send-confirmation"))
The code has an important limitation: a database transaction cannot make an arbitrary remote call atomic, and concurrent workers can still race between the local lookup, the remote effect, and the insert. That is why the downstream adapter should accept the same idempotency key, and why a production design needs both a reconciliation path for the small window between an external success and a local record and a concurrency-safe claim or outbox strategy. Naming those limitations is stronger than pretending a framework-level retry setting removes them.
LangGraph’s persistence documentation gives a useful vocabulary for the rest of this design: a thread-scoped checkpoint is not the same thing as application data shared across runs. Keep the customer action receipt in the system of record or a purpose-built effect ledger, not only in conversational state. https://docs.langchain.com/oss/python/langgraph/persistence
Make multi-agent handoffs observable and bounded
Multi-agent designs fail in a mundane way: the system cannot say who owns the next action. A “researcher,” “reviewer,” and “writer” is a readable diagram, but a role is not an operational boundary until it has an input schema, permitted tools, output schema, timeout, and completion record.
AutoGen’s stable AgentChat material distinguishes agents and teams. That is a useful model when the work truly calls for interaction among specialized participants. It is not a reason to split a linear task into a conversation. Before using a team, demonstrate that each member has a distinct decision or tool boundary. Otherwise, one agent with explicit stages is easier to test and easier to replace. https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/agents.html
CrewAI’s documentation describes agents, crews, and flows, and presents flows as orchestration with state and support for long-running work. It also places guardrails, memory, and observability in the product vocabulary. Use that vocabulary to write checks, not slogans. Which guardrail rejects a malformed handoff? Where is the flow state persisted? What trace or log record links a user request to a tool call and final artifact? The framework has a place for the concern; your deployment still needs a defined policy. https://docs.crewai.com/en/concepts/flows https://docs.crewai.com/en/concepts/observability
A practical handoff record has a small, boring shape:
run_id: the stable correlation identifier across agents and tools.stage: a controlled vocabulary, not a free-form status sentence.input_digest: a fingerprint of the approved input or source set.owner: the agent, service, or human responsible for the next transition.artifact_uri: where the draft, report, or patch can be inspected.decision: accepted, rejected, paused, or failed.reason: a short explanation suitable for the next operator.
Do not pass the whole chat transcript as the handoff. Keep it as supporting evidence when appropriate, but pass a structured contract that a later stage can validate. This is also the moment to draw a hard timeout. If a tool does not return, transition to paused or failed with an artifact showing what was attempted. A live process without a completed artifact is not a successful run.

Evaluate with release gates, rollback, and evidence
An agent evaluation is not a single prompt test. It is a release gate containing normal cases, expected failures, forbidden actions, and evidence requirements. The point is to make a failed evaluation actionable: either repair the workflow, narrow its authority, or stop the rollout.
Use a manifest that the team can review without executing code. The example below is valid YAML and gives a deployment system, a test harness, or a human operator the same criteria. It contains no invented framework configuration; it is deliberately portable across the documented frameworks.
1workflow: account-change-assistant
2release:
3 mode: staged
4 approval_required: true
5inputs:
6 required:
7 - account_id
8 - requested_change
9 - actor_id
10controls:
11 idempotency_key: "account_id:requested_change:request_id"
12 allowed_tools:
13 - account_lookup
14 - draft_change
15 prohibited_actions:
16 - direct_database_write
17 - send_external_email
18verification:
19 smoke_cases:
20 - name: authorized-draft
21 fixture: fixtures/authorized-draft.json
22 expect:
23 decision: draft_created
24 artifact_required: true
25 - name: denied-request
26 fixture: fixtures/denied-request.json
27 expect:
28 decision: rejected
29 tool_calls: 0
30 evidence:
31 - run_id
32 - input_digest
33 - authorization_result
34 - artifact_uri
35 - trace_uri
36rollback:
37 trigger: "missing evidence, forbidden tool call, or failed smoke case"
38 action: disable_workflow_and_preserve_run_artifacts
39 owner: on-call-engineer
Notice the separation between evaluation and rollback. A test can say “reject this request.” A rollback can say “disable the workflow, preserve evidence, and give an owner a clear response.” The first is a correctness expectation; the second is an operating instruction.
LangChain is relevant here as a framework resource, not a default answer. Its documentation centers the composition of models, tools, and application logic. That is often sufficient when a workflow has one controlled decision path. Do not import a full multi-agent design merely because a framework makes it available. The smallest design that produces a testable artifact and a rollback path is the one to operate first. https://python.langchain.com/docs/introduction/
Define the exit criteria before the pilot starts
Most framework evaluations have entry criteria: install the library, connect a model, make a demo task complete. Mature evaluations also have exit criteria. Exit does not mean failure. It means the organization can remove or replace the framework without losing its workflow contract.
Set these criteria in writing before a pilot receives real inputs:
- State exit. Export or reconstruct the data required to resume or close a run: identifiers, stage, approved inputs, artifacts, and downstream receipts. If a checkpointer or memory layer is removed, the business record still explains the effect.
- Tool exit. Keep tool adapters behind application interfaces. A workflow can call
account_lookuporcreate_draft; it should not spread provider-specific invocation code through Laravel jobs and Vue-facing endpoints. - Prompt and policy exit. Store prompts, schemas, allowed-tool lists, and approval rules in reviewed project files. Treat them like application configuration, not invisible dashboard state.
- Evidence exit. Preserve a readable execution record independent of the framework UI. Use the run identifier to connect logs, artifacts, and the final decision.
- Rollback exit. Demonstrate a disable path that stops new externally visible actions while preserving incomplete runs for review.
- Maintenance exit. Give a new engineer a short runbook: where the workflow starts, where state lives, how to run verification, and who owns an exception.
These criteria are compatible with every resource in the narrow table. They also make their trade-offs visible. LangGraph persistence focuses attention on what state is retained. AgentChat teams focus attention on the handoff boundary. CrewAI flows and observability focus attention on process ownership and inspection. LangChain’s composition model focuses attention on whether a smaller application structure is enough. None of that substitutes for a release contract.
The related work on /ai-agent-operations/ makes the same operational point from the cron side: an agent must leave proof, not just remain alive. /developer-tools/ is the companion reading for the toolchain around that proof. For repository-changing workflows, add the preflight discipline in the agent edit contract before you give an agent write authority.

What you should do Monday morning
- Pick one workflow that already has a real owner and a clear consequence. Avoid a broad “assistant” project. Write its input, tool list, artifact, and irreversible boundary on one page.
- Decide whether the workflow needs persisted execution, a team handoff, a flow, or only a bounded chain. Use the documented primitive as a reasoned fit, not a brand preference.
- Add a stable
run_idand an idempotency key before the first external effect. Record a receipt that survives a worker restart. - Write two smoke fixtures: one authorized happy path and one denied or malformed request. Assert that the denied path makes no prohibited tool call.
- Create the YAML release and rollback manifest in the repository. Assign an owner for the disable action and an owner for reviewing incomplete runs.
- Run the smallest verification command in CI and locally. A shell smoke test can be this plain:
1set -eu
2rm -f agent-effects.sqlite3
3python3 durable_effect.py > /tmp/agent-effects.out
4lines=$(wc -l < /tmp/agent-effects.out)
5[ "$lines" -eq 2 ]
6first=$(sed -n '1p' /tmp/agent-effects.out)
7second=$(sed -n '2p' /tmp/agent-effects.out)
8[ "$first" = "$second" ]
9printf 'idempotency smoke test passed\n'
Put the Python example in durable_effect.py, run this command, and keep the fixture output with the review record. The test proves the narrow contract shown here: repeat invocation returns the stored receipt. It does not prove an external provider honors your key, so add an adapter-level test for that boundary as well.
- Schedule a handoff review after the first maintained change, not after a polished demo. Ask a different engineer to find the run record, explain the last decision, and execute the rollback instruction. Any hesitation is a failed operating test.
The framework choice becomes easier after this work because the question is finally concrete. You are not choosing the framework with the most labels. You are selecting the smallest documented set of primitives that can carry your state, tool boundaries, evidence, and exit plan.
