LangChain and LangGraph in Late 2026: What Is New and Who Should Use It

TL;DR
LangChain 1.4 and LangGraph 1.2 added per-call approvals, built-in MCP, node timeouts and DeltaChannel. What shipped since May 2026 and who should pick it.
LangChain in late 2026 is a thin agent layer (create_agent plus middleware) sitting on LangGraph, a durable graph runtime, with Deep Agents as an opinionated harness on top and LangSmith handling tracing, evals and deployment. Since May 2026 the stack got materially better at the unglamorous production parts - per-call human approval with validated answers, node timeouts and error handlers, graceful drain, built-in MCP, and cheaper long threads via DeltaChannel - which makes it the strongest default for Python teams building long-running, approval-heavy agents. The cost is pace: releases land weekly and several of the headline features still carry a beta label.
Last updated: October 17, 2026
This post walks through every notable change from May to October 2026, explains how the pieces fit, and ends with who should pick the stack and who should not. Every code example below was type-checked against the exact versions named (details in each section). I did not make any model API calls, so nothing here claims a live agent run.
The stack in one picture#
The naming has shifted enough over two years that it is worth resetting. As of October 17, 2026 the current releases are:
| Layer | Python (PyPI) | TypeScript (npm) | What it does |
|---|---|---|---|
| LangChain | langchain 1.4.6, langchain-core 1.6.12 | langchain 1.5.18, @langchain/core 1.2.18 | create_agent, middleware, model and tool abstractions, MCP |
| LangGraph | langgraph 1.2.16 | @langchain/langgraph 1.5.1 | State graphs, checkpointing, interrupts, streaming, node policies |
| Deep Agents | deepagents 0.7.28 | deepagents 1.15.1 | Harness: filesystem, subagents, planning, code interpreter |
| LangSmith | langsmith 0.14.11, langgraph-cli 0.4.36 | @langchain/langgraph-sdk 1.12.5 | Tracing, evals, Engine, deployment |
%% title: How the LangChain stack fits together in October 2026
flowchart TD
App[Your app] --> CA[create_agent + middleware]
App --> DA[Deep Agents harness]
DA --> CA
CA --> RT[LangGraph runtime]
RT --> CP[(Checkpointer)]
RT --> AS[Agent Server]
AS --> DEP[LangSmith Deployment]
RT -.->|traces| LS[LangSmith tracing + evals]
LS -.-> ENG[Engine: issues and fixes]Two naming points trip people up:
- LangGraph Platform is now LangSmith Deployment. The LangSmith docs describe it as "a workflow orchestration runtime purpose-built for agent workloads" and call it framework-agnostic. Cloud, Hybrid, Self-hosted and Standalone server all run the same Agent Server runtime.
create_agentis a LangGraph graph. Anything that works on a LangGraph graph - checkpointers,interrupt(),Command(resume=...), the streaming APIs - works on an agent built withcreate_agent. That is why so many features below land in LangGraph first and show up in LangChain a release later.
Release timeline, May to October 2026#
| Date (2026) | Package | Headline |
|---|---|---|
| May 12 | langgraph 1.2.0 | Node timeouts, error handlers, RunControl drain, DeltaChannel (beta), v3 streaming (beta) |
| May 12 | langchain 1.3.0 | version="v3" in stream_events for agents |
| May 12 | deepagents 0.6.0 | CodeInterpreterMiddleware, harness profiles, ContextHubBackend, DeltaChannel for history |
| Jun 10 | @langchain/langgraph 1.4.0 | JS port of drain, DeltaChannel, error handlers, timeouts, setNodeDefaults() |
| Jun 18 | langchain (JS) 1.5.0 | Tool-dispatched subagents surface on run.subagents |
| Jul 24 | deepagents 0.7.0 | 65% fewer input tokens on a default turn; todos opt-in |
| Sep 1 | langchain 1.4.0 | MCP moves into langchain.mcp (beta) |
| Sep 21 | langgraph 1.2.12 | interrupt(value, response_schema=...) |
| Sep 24 | LangSmith Engine v2 | Red Teaming, trend detection, validated fixes |
| Oct 7 | Managed Deep Agents v0.9 | Self-scheduled follow-ups, per-run config, Slack |
| Oct 12 | @langchain/langgraph 1.5.0 | Deterministic parallel DeltaChannel writes; Postgres migration |
| Oct 14-15 | langchain 1.4.6 / JS 1.5.18 | Per-call human-in-the-loop |
| Oct 16 | @langchain/langgraph 1.5.1 | isInvalidResume(), resume fixes |
For context, a few earlier changes set this up. langchain 1.1.0 (November 25, 2025) added model profiles (a .profile on chat models, sourced from models.dev) and ModelRetryMiddleware. @langchain/langgraph 1.1.0 (January 14, 2026) introduced StateSchema, which accepts any Standard JSON Schema library (Zod 4, Valibot, ArkType). langgraph 1.1.0 (March 10, 2026) added the opt-in version="v2" for invoke() and stream(), where invoke() returns a GraphOutput with .value and .interrupts. You will see version="v2" in the examples below for that reason. Note that the changelog dates langchain 1.4.0 to September 1, while the PyPI upload is September 3.
Per-call human approval (langchain 1.4.6, JS 1.5.18)#
This is the change I would try first. HumanInTheLoopMiddleware has always paused the agent before gated tools run, but until now it paused once per model turn: a single interrupt listing every gated call, answered with a list of decisions matched by position. If the model asked for three tool calls, your UI had to keep three decisions in the right order.
With interrupt_mode="per_call", each gated tool call raises its own interrupt as it starts. According to the changelog and the middleware source:
- Each interrupt's value is a
ToolApprovalRequestwithtype: "tool_approval",tool_call_id,name,argsanddescription. - Each interrupt carries a
response_schemalisting only the decisions that tool allows (fromapprove,edit,reject,respond). - You answer with one decision per interrupt, keyed by interrupt ID, and LangGraph rejects an invalid answer before saving it to the checkpoint.
- An edit cannot switch to a different tool, and edited arguments are checked against the tool's own schema.
Batched mode remains the default. LangChain says per-call is planned to become the default in the next major release.
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email."""
return f"sent to {to}"
@tool
def delete_file(path: str) -> str:
"""Delete a file from the workspace."""
return f"deleted {path}"
agent = create_agent(
"anthropic:claude-sonnet-5",
tools=[send_email, delete_file],
middleware=[
HumanInTheLoopMiddleware(
interrupt_on={
"send_email": True, # approve, edit, reject or respond
"delete_file": {"allowed_decisions": ["approve", "reject"]},
},
interrupt_mode="per_call",
)
],
checkpointer=InMemorySaver(),
)
config = {"configurable": {"thread_id": "review-42"}}
result = agent.invoke(
{"messages": [{"role": "user", "content": "Email Alice the report, then delete it."}]},
config,
version="v2",
)
# One interrupt per gated call, each a ToolApprovalRequest with its own response_schema.
answers = {}
for pending in result.interrupts:
request = pending.value # {"type": "tool_approval", "name": ..., "args": ..., ...}
if request["name"] == "delete_file":
answers[pending.id] = {"type": "reject", "message": "Keep the report."}
else:
answers[pending.id] = {"type": "approve"}
agent.invoke(Command(resume=answers), config, version="v2")
Checked with python -m py_compile on Python 3.13 against langchain==1.4.6, langgraph==1.2.16 and langchain-core==1.6.12. To exercise the interrupt flow without an API key, I also ran the same script with a fake chat model that emits both tool calls: it produced two tool_approval interrupts, ran send_email, and wrote a rejection ToolMessage for delete_file. Answering the delete_file interrupt with {"type": "edit", ...} raised a validation error before anything was saved, and langgraph.errors.is_invalid_resume() returned True for it.
The TypeScript version is the same shape. result.__interrupt__ holds the pending interrupts:
import { createAgent, humanInTheLoopMiddleware, tool } from "langchain";
import { MemorySaver, Command } from "@langchain/langgraph";
import { z } from "zod";
const sendEmail = tool(async ({ to }) => `sent to ${to}`, {
name: "send_email",
description: "Send an email.",
schema: z.object({ to: z.string(), subject: z.string(), body: z.string() }),
});
const deleteFile = tool(async ({ path }) => `deleted ${path}`, {
name: "delete_file",
description: "Delete a file from the workspace.",
schema: z.object({ path: z.string() }),
});
const agent = createAgent({
model: "anthropic:claude-sonnet-5",
tools: [sendEmail, deleteFile],
middleware: [
humanInTheLoopMiddleware({
interruptOn: {
send_email: true,
delete_file: { allowedDecisions: ["approve", "reject"] },
},
interruptMode: "per_call",
}),
],
checkpointer: new MemorySaver(),
});
const config = { configurable: { thread_id: "review-42" } };
const result = await agent.invoke(
{ messages: [{ role: "user", content: "Email Alice the report, then delete it." }] },
config,
);
const answers: Record<string, unknown> = {};
for (const pending of result.__interrupt__ ?? []) {
const request = pending.value as { name: string };
if (!pending.id) continue;
answers[pending.id] =
request.name === "delete_file"
? { type: "reject", message: "Keep the report." }
: { type: "approve" };
}
await agent.invoke(new Command({ resume: answers }), config);
Type-checked with npx tsc --noEmit (TypeScript 7.0.2, strict, moduleResolution: bundler) against langchain@1.5.18, @langchain/core@1.2.18, @langchain/langgraph@1.5.1 and zod@4.6.7. As a negative control, interruptMode: "nope" fails to compile.
%% title: Per-call human approval in langchain 1.4.6
sequenceDiagram
participant C as Client
participant A as Agent
participant H as HITL middleware
participant G as LangGraph
C->>A: invoke(messages)
A->>H: model asks for send_email + delete_file
H->>G: interrupt per call, with response_schema
G-->>C: two interrupts, each with an id
C->>G: Command(resume={id: decision})
G->>G: validate answer before saving
G->>H: decisions for each call
H->>A: run approved call, reject the other
A-->>C: final answerThe validation piece builds on langgraph 1.2.12 (September 21), which added response_schema to interrupt() itself (interrupt(value, *, response_schema=None)), and @langchain/langgraph 1.5.1 (October 16), which added isInvalidResume(error) to mirror Python's is_invalid_resume. You can use the same mechanism in your own graphs, not only through the middleware. Why it matters: a malformed approval used to be something your resume code discovered after the fact. Now it fails at the boundary and the checkpoint stays clean.
LangGraph runtime: timeouts, error handlers, drain#
langgraph 1.2.0 (May 12) added three node-level controls that teams previously wrote by hand.
Per-node timeouts. Pass timeout= to add_node. A number is a hard wall-clock cap; TimeoutPolicy(run_timeout=..., idle_timeout=..., refresh_on=...) adds an idle limit that resets on progress (refresh_on="auto") or only on explicit runtime.heartbeat() calls (refresh_on="heartbeat"). When a limit fires, LangGraph raises NodeTimeoutError, clears that attempt's writes, and hands off to the retry policy. In Python, timeouts work on async nodes only, because they rely on asyncio cancellation.
Error handlers. Pass error_handler= to run a recovery function after retries are exhausted. It receives a typed NodeError (with .node and .error) and can return a Command to update state and route somewhere else. The changelog calls out Saga and compensation patterns as the use case.
Graceful drain. Create a RunControl, pass it to invoke/stream, and call request_drain() from any thread. The run stops after the current superstep, saves a resumable checkpoint and raises GraphDrained. Wire it to SIGTERM and a rolling deploy stops losing in-flight work.
import asyncio
import signal
from typing import Annotated, TypedDict
import operator
from langgraph.errors import GraphDrained, NodeError
from langgraph.graph import END, START, StateGraph
from langgraph.runtime import RunControl
from langgraph.types import Command, RetryPolicy, TimeoutPolicy
class State(TypedDict):
order_id: str
log: Annotated[list[str], operator.add]
async def call_vendor(state: State) -> dict:
# Must be async: LangGraph enforces timeouts through asyncio cancellation.
await asyncio.sleep(0.1)
return {"log": [f"vendor accepted {state['order_id']}"]}
def vendor_failed(state: State, error: NodeError) -> Command:
# Runs only after the retry policy is exhausted (including NodeTimeoutError).
return Command(
update={"log": [f"{error.node} failed: {type(error.error).__name__}"]},
goto="notify",
)
def notify(state: State) -> dict:
return {"log": ["ops notified"]}
builder = StateGraph(State)
builder.add_node(
"call_vendor",
call_vendor,
retry_policy=RetryPolicy(max_attempts=3),
timeout=TimeoutPolicy(run_timeout=60, idle_timeout=10),
error_handler=vendor_failed,
)
builder.add_node("notify", notify)
builder.add_edge(START, "call_vendor")
builder.add_edge("call_vendor", END)
builder.add_edge("notify", END)
graph = builder.compile() # add a checkpointer so a drained run can resume
async def main() -> None:
control = RunControl()
asyncio.get_running_loop().add_signal_handler(
signal.SIGTERM, control.request_drain, "sigterm"
)
try:
await graph.ainvoke({"order_id": "A-17", "log": []}, control=control)
except GraphDrained as drained:
print(f"stopped at a superstep boundary: {drained.reason}")
Compiled and imported against langgraph==1.2.16 on Python 3.13. The compiled graph's nodes resolve to __start__, __error_handler__call_vendor, call_vendor, notify and __end__, so the handler is a real graph node you will see in traces. No model is involved here, so I also ran it locally: with the timeout shrunk to 0.2 seconds and a 5-second sleep, the final state was ['call_vendor failed: NodeTimeoutError', 'ops notified']. With a checkpointer attached and drain requested up front, the run raised GraphDrained with reason sigterm and left a resumable checkpoint.
The JS port landed in @langchain/langgraph 1.4.0 (June 10), citing the Python PRs. One difference to remember: JS timeouts are in milliseconds (a number, or { runTimeout, idleTimeout, refreshOn }), Python's are in seconds.
import {
StateGraph,
StateSchema,
ReducedValue,
RunControl,
Command,
START,
END,
isGraphDrained,
} from "@langchain/langgraph";
import { z } from "zod";
const State = new StateSchema({
orderId: z.string(),
log: new ReducedValue(z.array(z.string()).default(() => []), {
reducer: (left: string[], right: string[]) => left.concat(right),
}),
});
const graph = new StateGraph(State)
.addNode(
"callVendor",
async (state) => {
await new Promise((resolve) => setTimeout(resolve, 100));
return { log: [`vendor accepted ${state.orderId}`] };
},
{
retryPolicy: { maxAttempts: 3 },
timeout: { runTimeout: 60_000, idleTimeout: 10_000 },
// Runs only after retries are exhausted, including NodeTimeoutError.
errorHandler: (_state, { node, error }) =>
new Command({
update: { log: [`${node} failed: ${(error as Error).name}`] },
goto: "notify",
}),
ends: ["notify"],
},
)
.addNode("notify", () => ({ log: ["ops notified"] }))
.addEdge(START, "callVendor")
.addEdge("callVendor", END)
.addEdge("notify", END)
.compile();
const control = new RunControl();
process.once("SIGTERM", () => control.requestDrain("sigterm"));
try {
const out = await graph.invoke({ orderId: "A-17" }, { control });
console.log(out.log);
} catch (err) {
if (isGraphDrained(err)) console.log("stopped at a superstep boundary");
else throw err;
}
Type-checked with npx tsc --noEmit (TypeScript 7.0.2, strict) against @langchain/langgraph@1.5.1 and zod@4.6.7, and run locally on Node 22, where it printed [ 'vendor accepted A-17' ].
If you want the same policies on every node, StateGraph.setNodeDefaults() (JS 1.4.0) sets a graph-wide retryPolicy, cachePolicy, errorHandler and timeout, and per-node options still win. The installed Python 1.2.16 source has the matching set_node_defaults().
%% title: What happens when a LangGraph node fails in 1.2
flowchart TD
S[Superstep starts] --> D{Drain requested?}
D -->|yes| GD[Save checkpoint, raise GraphDrained]
D -->|no| N[Node attempt]
N --> R{Outcome}
R -->|ok| W[Write state]
R -->|timeout or exception| T{Retries left?}
T -->|yes| N
T -->|no| E{error_handler set?}
E -->|yes| H[Handler gets NodeError]
H --> CMD[Command: update + goto]
E -->|no| F[Run fails]One thing these features do not change: a node that was interrupted mid-run still re-executes from its first line on resume. I covered that crash behavior, durability modes and idempotency keys in Durable AI Agent Frameworks Compared, so I will not repeat it here. Timeouts and error handlers decide what happens when a node fails; they do not make side effects idempotent.
DeltaChannel and v3 streaming#
Long threads have a storage problem. A normal reducer channel re-serializes the full accumulated value into every checkpoint, so a message list that grows by one message per step writes the whole history again on every step. DeltaChannel (beta, langgraph 1.2.0) stores only the incremental write at each step and reconstructs state on read by replaying writes through the reducer. A full snapshot is written every snapshot_frequency updates (default 1000 in both Python and JS) or when a system-wide bound is hit (DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT, default 5000), which caps replay depth.
from typing import Annotated, TypedDict
from langgraph.channels import DeltaChannel
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import START, StateGraph
def append_all(current: list[str], writes: list[list[str]]) -> list[str]:
# Receives a batch of writes; must give the same result batched or one by one.
out = list(current)
for batch in writes:
out.extend(batch)
return out
class State(TypedDict):
events: Annotated[list[str], DeltaChannel(append_all, snapshot_frequency=50)]
def record(state: State) -> dict:
return {"events": [f"event {len(state['events'])}"]}
builder = StateGraph(State)
builder.add_node("record", record)
builder.add_edge(START, "record")
graph = builder.compile(checkpointer=InMemorySaver())
Compiled and run locally against langgraph==1.2.16; three invocations on one thread produced ['event 0', 'event 1', 'event 2']. Note the reducer signature: it receives the current value and a list of writes, and the source docstring requires it to be deterministic and batching-invariant, because LangGraph may replay writes in larger batches than they were produced.
%% title: How DeltaChannel stores a long message history
flowchart LR
W[Step writes new messages] --> DC[Checkpoint stores only the delta]
DC --> K{Every K steps?}
K -->|yes| SNAP[Write full snapshot]
K -->|no| NEXT[Next step]
SNAP --> NEXT
RD[Read state] --> L[Load latest snapshot]
L --> REP[Replay later deltas through reducer]The honest caveats, all from the release notes:
- It is beta. The
DeltaChanneldocstring says the API and on-disk representation may change. - Rollback is a one-way door. Deep Agents adopted
DeltaChannelfor message history and agent files in 0.6.0, and the changelog says rolling back from 0.6.0 is not supported once threads have persisted delta checkpoints. LangChain ships adelta-channel-dumprecovery script for migrating affected threads before a downgrade, and advises against switching a persisted channel between delta and non-delta. - JS 1.5.0 needs a migration.
@langchain/langgraph1.5.0 (October 12) orders parallel writes to the sameDeltaChannelby(task_path, task_id, idx)to match Python.PostgresSaverusers must runsetup()once, from one process, before the new version serves traffic, because it adds atask_pathcolumn. Until then reads and writes fail withcolumn "task_path" does not exist.
v3 streaming (beta, langgraph 1.2.0 and langchain 1.3.0) replaces a flat event firehose with typed projections. stream_events(version="v3") returns a run object with run.values, run.messages, run.lifecycle and run.subgraphs, and run.messages yields one ChatModelStream per LLM call with sub-projections for text, reasoning, tool calls and usage. The v1 and v2 APIs are unchanged.
from delta_channel import graph # the graph above
config = {"configurable": {"thread_id": "stream-demo"}}
run = graph.stream_events({"events": []}, config, version="v3") # beta
for snapshot in run.values: # typed projection: full state after each step
print(snapshot)
print(run.output, run.interrupted)
Run locally against langgraph==1.2.16 (it emits a LangChainBetaWarning). On the JS side, langchain 1.4.0 (May 5) adopted the new streaming primitives, and langchain 1.5.0 (June 18) surfaces tool-dispatched subagents on run.subagents: a named agent (createAgent({ name })) invoked from a tool becomes a typed SubagentRunStream handle with its own scoped messages, tool calls and output. If you render multi-agent runs in a UI, that removes a lot of event-filtering code.
MCP moves into LangChain (langchain 1.4.0)#
MCP support used to live in the separate langchain-mcp-adapters package. Since langchain 1.4.0 it ships in the langchain.mcp namespace, built on FastMCP, installed with pip install "langchain[mcp]". MCPAdapter replaces MultiServerMCPClient. What you get, per the changelog and docs:
- Transport inference. The target can be a URL, a stdio script, an in-process server, an
MCPConfigdict or afastmcp.Client. One safety detail from the source: a bare string must be anhttp(s)URL, so a string from config can never silently launch a local subprocess. Local servers need an explicitPath, transport orMCPConfig. - Elicitation as
interrupt(). When a server asks for input mid-call, the adapter raises a LangGraph interrupt, so a human answers and the run resumes. - Auth. Bearer tokens, OAuth 2.1 with dynamic client registration, or any
httpx.Auth, including per-user auth in a deployment. - Annotations on tools. Each tool carries its MCP provenance under
tool.metadata["mcp"], including annotations such asdestructive_hint.
That last point ties neatly into per-call approval. Here is an agent that gates every tool the server marks destructive:
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain.mcp import MCPAdapter
from langgraph.checkpoint.memory import InMemorySaver
async def build_agent():
async with MCPAdapter("https://example.com/mcp") as adapter:
tools = await adapter.list_tools()
# MCP annotations ride along on tool.metadata["mcp"]["tool"]["annotations"].
destructive = {
t.name: True
for t in tools
if ((t.metadata or {}).get("mcp", {}).get("tool", {}).get("annotations", {}))
.get("destructive_hint")
}
return create_agent(
"anthropic:claude-sonnet-5",
tools,
middleware=[HumanInTheLoopMiddleware(interrupt_on=destructive, interrupt_mode="per_call")],
checkpointer=InMemorySaver(),
)
Compiled and imported against langchain[mcp]==1.4.6 (which pulled in fastmcp 4.1.0); the import raises the expected LangChainBetaWarning. The metadata path comes from langchain/mcp/tools.py, which keeps annotation keys in snake_case to match what langchain-mcp-adapters produced. The tools stay callable after the async with block exits, per the list_tools() docstring.
The namespace is beta, so pin the version. If MCP itself is new to you, start with our MCP servers guide.
Deep Agents: the harness got leaner#
Deep Agents is LangChain's take on an agent harness: a create_agent preset with a virtual filesystem, subagents and context management built in. Three releases changed it meaningfully:
- 0.5.0 (April 7): async subagents that run as non-blocking background tasks (these require LangSmith Deployment), and multimodal
read_filefor PDF, audio and video. - 0.6.0 (May 12): an experimental
CodeInterpreterMiddlewarethat runs code and programmatic tool calls in a scoped QuickJS runtime;HarnessProfilebundles applied automatically per provider or model;ContextHubBackend, which stores agent files as LangSmith Hub commits with version history; andDeltaChannelfor history and files (with the rollback caveat above). - 0.7.0 (July 24): on a default-agent turn, input tokens drop 65% (5,395 to 1,895) on LangChain's evaluation suite, with no quality regression reported.
TodoListMiddlewareis no longer included by default, a breaking change: restore it withmiddleware=[TodoListMiddleware()]if you relied onwrite_todos. Also new: adeletetool,write_fileoverwrites instead of erroring, aFilesystemMiddlewaretool allowlist, a 1,000-matchgrepcap, Bedrock prompt caching viadeepagents[aws], and middleware whose.namematches a built-in replaces it in place.
The 0.7.0 direction is worth noting: rather than adding features to the default prompt, LangChain removed things and measured. RubricMiddleware also ships in deepagents; I wrote about it in LangChain Rubrics Make Agent Evals Part of the Runtime.
The middleware catalog in one paragraph#
Middleware is the extension point for create_agent, and the prebuilt list is now long. The docs list ToolErrorMiddleware, ToolRetryMiddleware, ModelRetryMiddleware, ModelFallbackMiddleware, SummarizationMiddleware, HumanInTheLoopMiddleware, ModelCallLimitMiddleware, ToolCallLimitMiddleware, PIIMiddleware, TodoListMiddleware, LLMToolSelectorMiddleware, ProviderToolSearchMiddleware, ShellToolMiddleware, ContextEditingMiddleware and LLMToolEmulator, plus FilesystemMiddleware, SubAgentMiddleware and RubricMiddleware from deepagents. Custom middleware hooks into before_agent, before_model, wrap_model_call, wrap_tool_call, after_model, after_agent and dynamic_prompt. In practice most production agents I would build need three of these: a retry, a call limit and human approval.
LangSmith: tracing, evals, Engine, deployment#
LangSmith had a busy quarter. The parts most relevant to developers:
- Engine v2 (September 24). Engine scans production traces and groups related ones into issues with a root cause, proposed fix, ground-truth examples and monitoring. v2 adds Red Teaming, detection of inefficient trajectories, and metric trends for error rate, latency and cost. For agents on LangSmith Deployment it validates a proposed fix before queueing a one-click PR. Available on SaaS Plus and Enterprise; LangChain says self-hosted is coming.
- Trajectories and trajectory evaluators. Trajectories is a chronological session view across a main agent and its subagents, and trajectory evaluators are now available on every supported data plane.
- Fine-Tuning. Turns production trajectories into training data, with Baseten or Fireworks, per the October newsletter.
- Managed Deep Agents v0.9 (October 7). Agents can schedule their own follow-ups, be reconfigured per run, and react to Slack messages. v0.8 added per-user memory and custom HTTP channels.
- Deployment quality of life. Studio's top bar gained a Connection button that shows the Agent Server URL. Each plan's included free Serverless deployment is now assigned whether you create it from the web, CLI or API. Scale-to-zero deployments reject rollbacks below LangGraph API 0.13.0, and trace tree requests now return at most 1,000 runs by default (down from 5,000).
Pricing changed too. Per the LangSmith changelog, Engine usage and spend display in LangChain Standard Units (LSUs) at $1 each; existing self-serve Cloud customers move to the October 2026 plans priced in LSUs; and enterprise and self-hosted traces bill at a flat 0.0075 LSU per trace unless contracted otherwise. Check the current plan page before you budget.
For LangSmith Fleet and the on-call workflow, see LangSmith Fleet Turns Agent Ops Into On-Call Work.
What LangChain gets right now#
Credit where it is due. Reading six months of changelogs, a few things stand out:
- JS parity is closing fast. Timeouts, error handlers, drain and
DeltaChannelreached TypeScript four weeks after Python, and the JS release notes cite the Python PRs they port. Per-call approval shipped in both languages within a day. - State is explicit and inspectable. A typed state schema, named nodes (including generated error-handler nodes), and checkpoints you can read. When something goes wrong you can see exactly which node wrote what.
- Human-in-the-loop is validated, not hoped for.
response_schemaon interrupts, rejection before the checkpoint is written, and edits locked to the called tool. The approval payload is a typed contract rather than a free-form dict. - Failure policy lives at the node. Retry, timeout and error handler sit next to the node they govern, which is where reviewers look.
- Deployment is framework-agnostic. The same Agent Server runs in Cloud, Hybrid, Self-hosted and Standalone modes.
- Changelogs carry upgrade help. Breaking releases include migration notes and copy-paste migration prompts for coding assistants, and rollback hazards are spelled out rather than discovered.
Honest tradeoffs#
Stated neutrally, and mostly in LangChain's own words:
- Release cadence is high.
langchain-coreshipped at least 25 PyPI releases between June 11 and October 16, 2026. That is fast fixes, but it also means pinning versions and reading changelogs is part of the job. - Several headline features are beta.
langchain.mcp,DeltaChanneland v3 streaming all carry beta labels or emitLangChainBetaWarning. Deep Agents'CodeInterpreterMiddlewareis marked experimental. - Python usually lands first. Timeouts and error handlers were Python-only from May 12 to June 10. Built-in MCP is described in the Python changelog; check the JS docs before assuming parity for any given feature.
- Checkpoint formats need care. Delta checkpoints are not readable by older versions, and the JS 1.5.0 Postgres migration must run before traffic. Upgrades and rollbacks are operational events, not
npm update. - The LangSmith surface is large. Tracing, evals, Engine, Fleet, Deployment, Managed Deep Agents, Trajectories, Fine-Tuning and a new LSU pricing model. You can use only tracing, but it takes some reading to know which parts you need.
Who should pick it (and who should not)#
Pick LangChain and LangGraph if:
- You are a Python team building long-running or approval-gated agents. Per-call HITL, node timeouts, error handlers and drain are exactly the features those agents need, and they exist today.
- You want tracing, evals and deployment from one vendor, with an Agent Server you can also self-host.
- You already run LangGraph with a Postgres or SQLite checkpointer. Everything in this post is additive.
Consider alternatives if:
- You are building a Next.js chat UI with a short tool loop. The AI SDK is lighter for that; LangChain vs Vercel AI SDK walks through where each one fits.
- You are TypeScript-first and want batteries included. Mastra is the obvious comparison; the Mastra vs LangGraph.js post linked under Continue Reading compares the two.
- You only use Claude and want the shortest path to a capable agent. The Claude Agent SDK vs LangGraph guide under Continue Reading covers that decision.
Upgrade checklist:
- Pin exact versions of
langchain,langgraph,langchain-coreanddeepagents(or the npm equivalents) and upgrade them together. - On
@langchain/langgraph1.5.0 or later withPostgresSaver, runsetup()once, from one process, before the new version serves traffic. - If you relied on
write_todosin Deep Agents, addTodoListMiddleware()back after upgrading to 0.7. - Before upgrading to Deep Agents 0.6+, decide whether you will ever need to roll back; once delta checkpoints persist, you need the recovery script to go back.
- On new agents, try
interrupt_mode="per_call"(JSinterruptMode: "per_call") now rather than migrating when it becomes the default. - Replace
langchain-mcp-adapterswithlangchain[mcp]andMCPAdapter, and gatedestructive_hinttools behind approval.
FAQ#
What is the difference between LangChain and LangGraph in 2026?#
LangGraph is the runtime: state graphs, checkpointing, interrupts, streaming and node policies such as timeouts and error handlers. LangChain is the agent layer on top: create_agent, middleware, model and tool abstractions, and now MCP. An agent built with create_agent is a LangGraph graph, so you can drop down to LangGraph whenever you need custom control flow.
Is LangGraph Platform still a thing?#
Not under that name. The deployment product is LangSmith Deployment, and the runtime it runs is the Agent Server. LangChain describes it as framework-agnostic, and the same Agent Server runs across Cloud, Hybrid, Self-hosted and Standalone server.
Do I still need langchain-mcp-adapters?#
Not in Python, if you are on langchain 1.4.0 or later. Install langchain[mcp] and use MCPAdapter from langchain.mcp, which replaces MultiServerMCPClient. The namespace is beta and raises a LangChainBetaWarning on import, so pin your version.
Should I turn on per-call human-in-the-loop?#
For new agents, yes. Per-call mode gives each gated tool call its own interrupt with a typed response_schema, validates answers before saving them, and LangChain plans to make it the default in the next major release. Batched mode remains the default today, so existing agents keep working unchanged.
Are LangGraph node timeouts available in TypeScript?#
Yes, since @langchain/langgraph 1.4.0 (June 10, 2026). Pass timeout to addNode as a number of milliseconds or as { runTimeout, idleTimeout, refreshOn }, alongside retryPolicy and errorHandler. setNodeDefaults() applies them graph-wide.
Sources#
- LangChain Python changelog
- LangChain JavaScript changelog
- @langchain/langgraph 1.4.0 release
- @langchain/langgraph 1.5.0 release
- langgraphjs releases (1.5.1)
- langgraph 1.2.12 release
- Built-in middleware docs
- LangChain MCP docs
- Human-in-the-loop docs
- LangSmith Deployment docs
- LangSmith changelog
- LangSmith Engine v2 announcement
- October 2026 LangChain newsletter
- Managed Deep Agents v0.9 announcement
- langchain on PyPI, langgraph on PyPI, deepagents on PyPI
- langchain on npm, @langchain/langgraph on npm
Continue Reading#
- Durable AI Agent Frameworks Compared: One Run, Four Stacks - what LangGraph actually re-runs after a crash, and how to make side effects safe
- Claude Agent SDK vs LangGraph: Choosing Your Agent Stack in 2026 - the decision guide for teams standardizing on Claude
- Mastra vs LangGraph.js: TypeScript Agent Frameworks Compared - the fair TypeScript alternative, side by side
- Human-in-the-Loop Patterns for AI Agents: Approvals in 5 SDKs -
interrupt()andHumanInTheLoopMiddlewarenext to four other SDKs' approval APIs
Get the next deep dive like this in your inbox
One email a week on AI Agents and the rest of the AI dev stack. Free.
Read next on AI coding tools
Durable AI Agent Frameworks Compared: One Run, Four Stacks
One agent run crashes mid tool call, waits two days for approval, then retries a charge. How LangGraph, Mastra, Pydantic AI and OpenAI Agents SDK handle it.
14 min readClaude Agent SDK vs LangGraph: Choosing Your Agent Stack in 2026
Claude Agent SDK vs LangGraph head-to-head: architecture, state handling, multi-agent patterns, and real pricing - plus a decision guide for which agent stack fits your team in 2026.
9 min readLangSmith Fleet Turns Agent Ops Into On-Call Work
LangSmith Fleet turns agent ops into on-call work: the June On-Call Copilot, computer use, traces, Slack interrupts and MCP auth as one operator loop.
8 min readNew here? Start with
Technical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.








