Strands vs. LangChain: Choose the Execution Model, Not the Agent Loop
Strands vs. LangChain: Choose the Execution Model, Not the Agent Loop

Every few months, a team asks some version of the same question: we are building an agent: should we use Strands or LangChain?
The question is reasonable, but the comparison is slightly off. The basic model → tool → observation loop is not what makes either framework interesting. Both can do that. The useful question is where each system puts the structure around that loop:
- How do you model state and control flow?
- What happens when an execution stops, fails, or needs a human?
- Can you resume, inspect, replay, or fork a run?
- How much multi-agent orchestration do you want supplied versus composed?
With that framing, the choice becomes much clearer.
What are we comparing?
“LangChain” is now best understood as a stack with distinct layers:
- LangGraph is the runtime: a stateful graph engine with nodes, edges, reducers, checkpoints, interrupts, and streaming.
- LangChain
create_agentis an agent harness built on LangGraph: model, tools, prompt, and middleware compiled into a graph. - Deep Agents is a higher-level preset for longer-running agents, adding planning, filesystem tools, subagents, memory conventions, and context management.
Strands Agents is more deliberately unified. It is an AWS-led SDK for Python and TypeScript centered on Agent, with first-class Graph and Swarm orchestration, workflow tooling, A2A support, sessions, plugins, and observability.
That means this is not a simple framework-versus-framework decision:
LangGraph gives you a runtime to compose unusual execution models. Strands gives you an opinionated SDK with useful agent and multi-agent shapes ready to configure.
Everything else follows from that distinction.
Axis 1: the agent loop, change its behaviour or explain its ending?
Both ecosystems run the familiar cycle: call a model, execute requested tools, feed tool results back, and repeat until the model stops requesting tools. The differentiation is in the controls around that loop.
LangChain: middleware is the extension point
LangChain’s strongest answer is middleware. It lets you change behaviour before and after model calls, wrap tool calls, add fallbacks, retry failures, manage context, enforce limits, or pause for approval without reimplementing the agent loop.
from langchain.agents import create_agent
from langchain.agents.middleware import (
ModelCallLimitMiddleware,
ToolRetryMiddleware,
ModelFallbackMiddleware,
SummarizationMiddleware,
)
agent = create_agent(
model="anthropic:claude-sonnet-4-6",
tools=[search, fetch_page],
system_prompt="You research topics thoroughly.",
middleware=[
ToolRetryMiddleware(max_retries=3, backoff_factor=2.0, jitter=True),
ModelFallbackMiddleware("openai:gpt-5.5", "google_genai:gemini-3.6-flash"),
SummarizationMiddleware(
model="openai:gpt-5.4-mini",
trigger=("fraction", 0.7),
keep=("messages", 20),
),
ModelCallLimitMiddleware(thread_limit=40, run_limit=25, exit_behavior="end"),
],
)This pays off quickly in production. Retries, fallback models, summarization, human approval, PII handling, and limits are recurring requirements. Middleware turns much of that work into configuration rather than bespoke control code.
LangGraph also has a final safety backstop: recursion_limit, which caps super-steps and raises a GraphRecursionError if a graph does not terminate. It is a graph-level limit, not a polished budget model, but it protects against runaway cycles.
Strands: termination is explicit
Strands is especially good at making an invocation’s ending legible. You can impose turn and token budgets, then inspect the returned stop reason.
from strands import Agent
agent = Agent(
name="researcher",
system_prompt="You research topics thoroughly.",
tools=[search, fetch_page],
)
result = agent(
"Summarise the state of agent frameworks",
limits={"turns": 25, "total_tokens": 200_000, "output_tokens": 8_000},
)
if result.stop_reason == "limit_turns":
# The budget was exhausted; decide whether to resume or report partial work.
passThis seems small until an agent ends unexpectedly in production. A named reason (turn budget, token budget, cancellation, content filter, provider token cap, or a natural end of turn) gives operators a concrete place to start.
Strands also treats cancellation and concurrent invocation as first-class concerns. A Python agent can be cancelled safely from another thread; cancellation is observed at defined points in the loop. One agent instance rejects overlapping calls by default, because an invocation mutates its conversation history. Those are sensible defaults for a stateful SDK.
Choose by what hurts more: LangChain is stronger when you need to change what happens during the loop. Strands is particularly pleasant when you need to understand why a run ended.
Axis 2: orchestration, composed vs. configured
This is the axis that should drive your decision: do you need a runtime that lets you build an unusual multi-agent topology, or one that already ships the standard shapes?
LangChain: a runtime you compose
The current LangChain docs no longer feature supervisor or swarm prebuilts. Multi-agent is documented as five patterns (subagents, handoffs, skills, router, custom workflow), with Deep Agents as the built-in option. For anything custom, you build on StateGraph:
from typing import Annotated, TypedDict
from operator import add
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
class State(TypedDict):
messages: Annotated[list, add]
findings: Annotated[list[str], add]
documents: list[str]
category: str
attempts: int
def route(state: State) -> str:
# analysis_node increments attempts
if state["attempts"] >= 3:
return "report"
return "analysis" if not state["findings"] else "report"
builder = StateGraph(State)
builder.add_node("research", research_node)
builder.add_node("analysis", analysis_node)
builder.add_node("report", report_node)
builder.add_edge(START, "research")
builder.add_edge("research", "analysis")
builder.add_conditional_edges("analysis", route, {"analysis": "analysis", "report": "report"})
builder.add_edge("report", END)
graph = builder.compile(checkpointer=InMemorySaver())The state model is the real feature here. Every key can have its own reducer: Annotated[list, add] appends, the default overwrites, and you can write custom merge functions. That’s how you get correct behaviour when parallel branches write to the same field, and Strands has no direct equivalent.
Dynamic fan-out is also cleaner:
from langgraph.types import Send
def fan_out(state: State):
return [Send("analyse_doc", {"doc": d}) for d in state["documents"]]Send spawns one node execution per item at runtime. That is real map-reduce, whereas Strands’ parallelism comes from static graph branches. And Command lets a node update state and route in one return:
from langgraph.types import Command
from typing import Literal
def triage(state: State) -> Command[Literal["billing", "technical"]]:
kind = classify(state["messages"][-1])
return Command(update={"category": kind}, goto=kind)You can dynamically fan work out with Send, route and update state in one step with Command, mix deterministic code with agentic nodes, and define custom joins. If your workflow is unusual, this flexibility is the point, not an implementation detail.
Strands: familiar shapes with safety rails
Strands offers three primary multi-agent patterns: Graph, Swarm, and Workflow.
- A Swarm lets peer agents decide when to hand work to one another. It is useful when routing emerges from the task.
- A Graph gives the developer an explicit topology, including conditional edges and cycles.
- A Workflow is appropriate for a known dependency DAG where independent tasks can execute in parallel.
from strands.multiagent import Swarm
# each agent needs a unique name=
swarm = Swarm(
[researcher, coder, reviewer],
entry_point=researcher,
max_handoffs=20,
max_iterations=20,
execution_timeout=900.0,
node_timeout=300.0,
)
result = swarm("Design and implement a REST API for a todo app")Strands’ contribution is that these patterns come with their failure modes already handled: handoff and iteration limits, timeouts, and repeated-handoff detection are part of the configuration.
Choose LangGraph for dynamic fan-out, custom reducers, sophisticated joins, or hybrid deterministic/agentic flow. Choose Strands when your system maps cleanly to a graph, swarm, or dependency workflow and you want a fast path with guardrails.
Axis 3: durability and human-in-the-loop
For many production systems, this axis decides the comparison.
LangChain: checkpoints, replay, and interrupts
With a checkpointer, LangGraph persists graph state at execution boundaries. A persistent backend gives you recovery after a process failure, state inspection, replay, time travel, and the ability to fork from a prior checkpoint.
from langgraph.checkpoint.postgres import PostgresSaver
with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
checkpointer.setup() # first run only: creates the checkpoint tables
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "case-482"}}
graph.invoke(inputs, config=config, durability="sync")
history = list(graph.get_state_history(config)) # newest first
snapshot = history[3]
# update_state forks a new checkpoint; "findings" goes through the add reducer, so this appends
fork_config = graph.update_state(snapshot.config, {"findings": ["corrected"]})
graph.invoke(None, config=fork_config)LangGraph’s interrupt() uses the same persistence machinery. A node can pause for human approval, then resume later, even in another process, with a Command. That makes it suited to audit-heavy, expensive, or long-lived workflows.
Keep the rule in mind for the traps section: the node re-runs on resume.
Strands: interrupts without the replay story
Strands has sessions, snapshots, storage backends, interrupts, and intervention patterns for steering or authorizing agent behaviour. Its multi-agent systems can persist execution state as well.
Strands does have durability. The practical difference is that LangGraph currently exposes a more complete, explicit checkpoint-history, replay, and state-forking model. If you need to answer “what happened in this run?” or “what would have happened if this decision were different?”, LangGraph is the clearer choice.
Axis 4: the long-horizon agent
If you’re building a research or coding agent that runs for many minutes and produces artifacts, this is the axis that matters.
LangChain: deepagents
LangChain has a specific, pre-assembled answer:
from deepagents import create_deep_agent
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
tools=[web_search],
system_prompt="You are a research analyst.",
subagents=[{
"name": "fact_checker",
"description": "Verifies claims against primary sources.",
"system_prompt": "Verify each claim and cite the source.",
"mode": "isolated",
"tools": [web_search],
}],
)Out of the box you get a todo-list planner, a virtual filesystem for intermediate artifacts, a task tool to delegate to subagents, and context summarization. "mode": "isolated" (the default) means the fact checker only sees the delegated task, not the parent’s conversation; "fork" would hand it the full history.
Strands: comparable ingredients, assembled by you
Strands offers comparable components (skills, steering, context management, goal loops, plugins, and sandboxes) but leaves more of their assembly to you. Which you prefer depends on whether your team wants a designed default or a smaller set of composable parts.
Axis 5: ops
| LangChain | Strands | |
|---|---|---|
| Tracing | LangSmith, plus LangSmith Engine for issue detection | OpenTelemetry traces, metrics and logs, exportable to any OTLP backend |
| Evaluation | LangSmith evaluation | Strands Evals SDK: ~25 evaluators, red-teaming, chaos testing, root-cause detectors |
| Streaming | stream_mode (values, updates, messages, custom, checkpoints, tasks, debug) and typed stream_events(version="v3") |
stream_async with multiagent_node_start, multiagent_node_stream, multiagent_node_stop, multiagent_handoff and multiagent_result events; plus bidirectional realtime voice (Nova Sonic, Gemini Live, OpenAI Realtime) |
| Deployment | LangSmith Deployment / Agent Server: cloud, hybrid, self-hosted, standalone | AgentCore, Lambda, Fargate, App Runner, EKS, EC2, Docker, K8s, Terraform |
| Models | init_chat_model("provider:model") across providers |
Bedrock, Nova, Anthropic, Google, OpenAI, Mistral, Ollama, LiteLLM, llama.cpp, SageMaker, Writer, Vercel |
Two things stand out. The Strands Evals SDK has no direct LangChain equivalent: evaluators for correctness, faithfulness, tool selection accuracy and trajectory, plus red-teaming and chaos testing, in one SDK. If your compliance story needs agent evaluation, that counts.
And Strands has bidirectional streaming for realtime voice. If you’re building a voice agent, between these two, Strands is the only one with this built in.
The traps
Four things that will cost you a day each if nobody tells you.
LangGraph subgraph streaming is opt-in. create_agent returns a compiled graph, so putting one inside a StateGraph makes it a subgraph, and without subgraphs=True on the parent’s stream, stream_mode="messages" won’t emit the inner agent’s tokens at all. You’ll think streaming is broken.
LangGraph’s interrupt() re-runs the node. Resume restarts the node function from the top, not from the interrupt line. Any side effect before the interrupt happens twice unless it’s idempotent.
Strands Python Graph defaults to OR fan-in. A node fires when any incoming edge’s source completes. Strands TypeScript waits for all of them, and LangGraph gives you an explicit join with add_edge([...], node) or defer=True. In Python Strands, you write a condition on every incoming edge:
from strands.multiagent import GraphBuilder
from strands.multiagent.base import Status
from strands.multiagent.graph import GraphState
builder = GraphBuilder() # with "research", "market_data" and "synthesis" nodes added
def all_complete(required: list[str]):
def check(state: GraphState) -> bool:
return all(
n in state.results and state.results[n].status == Status.COMPLETED
for n in required
)
return check
join = all_complete(["research", "market_data"])
builder.add_edge("research", "synthesis", condition=join)
builder.add_edge("market_data", "synthesis", condition=join)Strands’ Python and TypeScript SDKs are not the same API. Python uses a GraphBuilder, TS a declarative new Graph({nodes, edges, sources, maxSteps}). Python’s Swarm has max_handoffs + max_iterations, TS has one maxSteps. Python’s Swarm also has a mutable SharedContext; TS passes only serialized handoff context. Exceeding the step limit throws in TS but returns a FAILED result in Python. If you plan to port between them, read the “SDK Differences” sections first.
So: how do I choose?
Run down this list and stop at the first one that’s true.
- Need durable execution, recovery, audit history, replay, or state forking? → LangGraph.
- Building a long-horizon research or coding agent and want planning, filesystem tools, and subagents wired together? → Start with Deep Agents.
- Orchestration is unusual (runtime fan-out, custom merge logic, elaborate joins, or deterministic steps mixed with agents)? → LangGraph.
- Deep in AWS, deploying through Bedrock AgentCore or adjacent AWS infrastructure? → Strands.
- Orchestration is a standard graph, swarm, or dependency workflow and you want sensible safety boundaries immediately? → Strands.
- None of the above decisive? → Choose the stack your team can operate confidently. The cost of changing frameworks is usually lower than the cost of never shipping the first agent.
The hybrid option
You do not need to standardise on one SDK everywhere. LangSmith Deployment’s own guide deploys a Strands agent through the LangGraph Functional API, and AWS’s market-surveillance reference architecture runs LangGraph as the outer workflow (state, checkpoints, recovery) with Strands agents doing the reasoning inside individual nodes, all on AgentCore.
For a larger organisation, a sensible split can be:
- let product teams choose the agent SDK that fits their interaction model;
- standardise persistence, orchestration conventions, tracing, evaluation, and deployment where centralisation actually reduces risk.
The agent loop is commodity. The durable choices are the state model, the recovery model, and the operating model. Evaluate those first, and the framework decision becomes much less ideological.
Sources: the Strands Agents docs, the LangChain agents, LangGraph Graph API, and LangGraph persistence docs, and AWS’s LangGraph-and-Strands reference architecture.