# langchain correction pack · for projects on langchain>=1,<2 (Python)
<!--
     Stale Priors Index · Unattended Works · https://github.com/SamAndrzejewski

     GENERATED FILE — DO NOT EDIT BY HAND. Your edit will be destroyed by the next build.
     Rebuild:  node tools/build-corrections.mjs
     Sources:  data/langchain/facts.json  (the corrections, each verified against a primary source)
               data/langchain/*.json      (the evidence: reproduced model failures)

     Latest langchain: 1.4.0 · verified 2026-09-06
     Coverage: Claude Fable 5, Claude Fable 5.1, Claude Haiku 4.5, Claude Opus 5, Claude Sonnet 5 — langchain/v1 (2026-08-31), langchain/v1r-a (2026-09-01), langchain/v1r-b (2026-09-01), langchain/v2 (2026-09-01), langchain/v3 (2026-09-01), langchain/v5-a (2026-09-06), langchain/v5-b (2026-09-06), langchain/v5-c (2026-09-06), langchain/v5-d (2026-09-06), langchain/v5-e (2026-09-06), langchain/v6-a (2026-09-06), langchain/v6-b (2026-09-06), langchain/v6-c (2026-09-06), langchain/v6-d (2026-09-06), langchain/v6-e (2026-09-06), langchain/v6-f (2026-09-06)

     Paste into CLAUDE.md / AGENTS.md / .cursorrules if your project uses langchain>=1,<2 (Python).
     Do NOT use if you are pinned to langchain 0.3 or earlier.
-->

## What we actually measured

5 Claude models were asked for idiomatic langchain code with no tools, purely from training knowledge. 20 reproduced failures across 22 runs, each verified against the release that broke the belief.

| Model | Stated cutoff | langchain version attribution stops | Lag inside the window |
|---|---|---|---|
| Claude Opus 5 | 2026-05 | 1.0.0 · 2025-10-17 | ~7 months |
| Claude Fable 5.1 | 2026-06 | 1.1.0 · 2025-11-24 | ~6 months |
| Claude Haiku 4.5 | 2025-02 | 0.3.0 · 2024-09-13 | ~5 months |
| Claude Fable 5 | 2026-01 | 1.0.0 · 2025-10-17 | ~3 months |
| Claude Sonnet 5 | 2026-01 | 1.0.0 · 2025-10-17 | ~3 months |

A recent cutoff is not a defence. Every model here loses track of this library's release history well before the date it states as its own cutoff, and the stopping points cluster far tighter than the cutoffs do.

Read that column precisely. It is the newest langchain release whose contents the model can correctly **attribute to that release** — not the newest langchain feature it knows. Past that point a model will often write working code with a newer API while naming the wrong release for it, and that guess runs *early* — it names a release older than the one that shipped the feature. So the question this pack answers is not "does the model know this API" but "can it be trusted about which version the API arrived in" — which is the question that matters when you are pinned to a version.

Latest langchain is **1.4.0** (verified 2026-09-06). Separately from the corrections below, 3 of 22 runs recorded a version fact — the model named a current langchain version from memory and was behind. If a model states a version without checking, assume it is behind and check the registry.

## How to read an entry

Every entry ends with a *Reproduced against* line. Where it names models, we have the generated code that got it wrong, dated, with the model's own words in the run write-up. Where it says no model yet, the correction is verified from the release notes but nothing has been probed for it — it is a fix, not a measurement, and the pack says so rather than blurring the two.

The section an entry sits in is the worst case if you act on the stale belief. The severity in brackets after a model's name is what that particular model's output actually did, which can be milder — a model can hold the wrong belief and still, on the day, write code that runs.

## The corrections

### Breaks the build, or throws at runtime

Act on the stale belief here and the code does not run. Fix these first.

#### `langchain.hub`

**Removed in langchain 1.0.0** (2025-10-17)

`from langchain import hub` no longer works. The hub module moved to `langchain-classic`; the prompt registry is also reachable directly from the LangSmith SDK, which is the path the current docs use.

*The stale belief:* That the hosted prompt registry is reached with `from langchain import hub; hub.pull("owner/name")`, optionally after `pip install langchainhub`.

```python
# Stale
from langchain import hub

prompt = hub.pull("hwchase17/react")

# Current
# pip install langchain-classic
from langchain_classic import hub
prompt = hub.pull("hwchase17/react")

# or, without the compatibility package:
# pip install langsmith
from langsmith import Client
prompt = Client().pull_prompt("hwchase17/react")
```

*Reproduced against: **Claude Sonnet 5** (S1) — langchain/v1, 2026-08-31.*

Source: [LangChain v1 migration guide — update your imports](https://docs.langchain.com/oss/python/migrate/langchain-v1) · [API reference — langchain_classic.hub.pull](https://reference.langchain.com/python/langchain-classic/hub/pull)

#### agent custom state

**Stricter in langchain 1.0.0** (2025-10-17)

Custom agent state must be a `TypedDict`. The Pydantic state classes are gone: `AgentStatePydantic` and `AgentStateWithStructuredResponsePydantic` have no replacement beyond `langchain.agents.AgentState`.

*The stale belief:* That agent state can be declared as a Pydantic model.

```python
# Stale
from langgraph.prebuilt.chat_agent_executor import AgentStatePydantic

# Current
from typing_extensions import TypedDict
from langchain.agents import AgentState

class MyState(AgentState):
    tickets: list[str]
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Custom state](https://docs.langchain.com/oss/python/migrate/langchain-v1) · [LangGraph v1 migration guide — Deprecations](https://docs.langchain.com/oss/python/migrate/langgraph-v1)

#### `AIMessage(example=...)`

**Removed in langchain 1.0.0** (2025-10-17)

The `example` parameter was removed from `AIMessage`. Put that metadata in `additional_kwargs`.

*The stale belief:* That `AIMessage(content=..., example=True)` is valid.

```python
# Stale
AIMessage(content="...", example=True)

# Current
AIMessage(content="...", additional_kwargs={"example": True})
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — example parameter removed from AIMessage](https://docs.langchain.com/oss/python/migrate/langchain-v1)

#### `create_agent(pre_model_hook=...) / post_model_hook`

**Removed in langchain 1.0.0** (2025-10-17)

`pre_model_hook` and `post_model_hook` are not `create_agent` parameters. Their replacement is middleware: a `before_model` / `after_model` hook, or `wrap_model_call` when you want to change only what is sent to the model without editing stored state.

*The stale belief:* That message trimming or output validation is wired in with `pre_model_hook=` / `post_model_hook=` on the agent factory.

```python
# Stale
agent = create_agent(model, tools, pre_model_hook=trim)

# Current
from langchain.agents.middleware import wrap_model_call

@wrap_model_call
def trim(request, handler):
    return handler(request.override(messages=request.messages[-6:]))

agent = create_agent(model, tools, middleware=[trim])
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Pre-model hook](https://docs.langchain.com/oss/python/migrate/langchain-v1) · [API reference — langchain.agents.middleware.wrap_model_call](https://reference.langchain.com/python/langchain/agents/middleware/types/wrap_model_call)

#### `create_agent(response_format=...)`

**Removed in langchain 1.0.0** (2025-10-17)

Prompted structured output — passing a `(instruction_string, Schema)` tuple to `response_format` — was removed. A bare schema is still accepted, and the framework picks a strategy; to choose explicitly, pass `ToolStrategy(Schema)` (artificial tool call, works on any tool-calling model) or `ProviderStrategy(Schema)` (provider-native constrained decoding). The result lands in `result["structured_response"]`.

*The stale belief:* That you can steer structured output by handing `response_format` a prompt alongside the schema.

```python
# Stale
agent = create_react_agent(model, tools,
    response_format=("please generate ...", OutputSchema))

# Current
from langchain.agents.structured_output import ToolStrategy

agent = create_agent(model, tools, response_format=ToolStrategy(OutputSchema))
result["structured_response"]
```

> 1.0.0 also stopped generating structured output in a separate graph node; it happens in the main loop, which removes an extra model call. 1.2.0 (2025-12-15) added strict schema adherence for `ProviderStrategy`.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Prompted output removed](https://docs.langchain.com/oss/python/migrate/langchain-v1) · [LangChain changelog — langchain v1.2.0](https://docs.langchain.com/oss/python/releases/changelog) · 2025-12-15

#### `create_agent(system_prompt=...)`

**Renamed in langchain 1.0.0** (2025-10-17)

The static instruction parameter is `system_prompt`. `prompt=` was the `create_react_agent` name and is not accepted by `create_agent` — passing it raises `TypeError: unexpected keyword argument`.

*The stale belief:* That the agent factory takes `prompt=`.

```python
# Stale
agent = create_agent(model, tools, prompt="You are helpful.")

# Current
agent = create_agent(model, tools, system_prompt="You are helpful.")
```

> 1.0.0 required a plain string here; 1.1.0 (2025-11-24) added support for passing a `SystemMessage` instance, which enables cache control and structured content blocks.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Static prompt rename](https://docs.langchain.com/oss/python/migrate/langchain-v1) · [LangChain changelog — langchain v1.1.0](https://docs.langchain.com/oss/python/releases/changelog) · 2025-11-25

#### langchain text splitters

**Removed in langchain 1.0.0** (2025-10-17)

Text splitters are not part of the v1 `langchain` namespace. They live in their own distribution, `langchain-text-splitters`, imported as `langchain_text_splitters`.

*The stale belief:* That `from langchain.text_splitter import RecursiveCharacterTextSplitter` works.

```python
# Stale
from langchain.text_splitter import RecursiveCharacterTextSplitter

# Current
# pip install langchain-text-splitters
from langchain_text_splitters import RecursiveCharacterTextSplitter
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [What's new in LangChain v1 — Namespace](https://docs.langchain.com/oss/python/releases/langchain-v1)

#### `langchain-classic`

**New requirement in langchain 1.0.0** (2025-10-17)

`langchain-classic` is a separate distribution. `pip install langchain` does not bring it in, so every legacy import above fails with `ModuleNotFoundError: No module named 'langchain_classic'` until it is installed. Any migration advice that only rewrites the import path is incomplete without the install line.

*The stale belief:* That legacy symbols are still reachable from whatever is installed alongside `langchain`.

```bash
# Stale
pip install langchain

# Current
pip install langchain langchain-classic
```

> First published to PyPI 2025-10-07 (1.0.0a1); 1.0.8 is the latest as of 2026-08-31.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Install with](https://docs.langchain.com/oss/python/migrate/langchain-v1) · [PyPI JSON API — langchain-classic release timestamps](https://pypi.org/pypi/langchain-classic/json)

#### `langchain.agents.initialize_agent / AgentExecutor`

**Removed in langchain 1.0.0** (2025-10-17)

`initialize_agent`, `AgentExecutor` and `AgentType` are gone from `langchain.agents`, which now exports `create_agent` and `AgentState`. The old agent machinery is in `langchain-classic`.

*The stale belief:* That agents are built with `initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS)` or by wrapping a runnable in an `AgentExecutor`.

```python
# Stale
from langchain.agents import initialize_agent, AgentType, AgentExecutor

# Current
from langchain.agents import create_agent
```

> These were deprecated long before 1.0.0; 1.0.0 is where they left the package.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [API reference — langchain_classic.agents.initialize.initialize_agent](https://reference.langchain.com/python/langchain-classic/agents/initialize/initialize_agent) · [What's new in LangChain v1 — Namespace](https://docs.langchain.com/oss/python/releases/langchain-v1)

#### langchain.chains (LLMChain, ConversationChain, RetrievalQA)

**Removed in langchain 1.0.0** (2025-10-17)

Legacy chains are not in the `langchain` package. They moved to `langchain-classic`, which `pip install langchain` does not install, so `from langchain.chains import LLMChain` is an `ImportError` on a fresh v1 environment. Either install `langchain-classic` and import from `langchain_classic.chains`, or write the pipeline directly — a prompt piped into a model, or a retrieve-then-answer function.

*The stale belief:* That `LLMChain`, `ConversationChain`, `RetrievalQA` and friends live in `langchain.chains`.

```python
# Stale
from langchain.chains import LLMChain, RetrievalQA

# Current
# only if you really need the legacy chain:
#   pip install langchain-classic
from langchain_classic.chains import LLMChain, RetrievalQA
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — langchain-classic](https://docs.langchain.com/oss/python/migrate/langchain-v1) · [API reference — langchain_classic.chains.llm.LLMChain](https://reference.langchain.com/python/langchain-classic/chains/llm/LLMChain)

#### langchain.memory (ConversationBufferMemory)

**Removed in langchain 1.0.0** (2025-10-17)

The memory classes are not in `langchain`. Conversation memory in v1 is a LangGraph checkpointer keyed by `thread_id`, not an object hung off a chain; the old classes remain available only in `langchain-classic`.

*The stale belief:* That you attach `memory=ConversationBufferMemory(...)` to a chain.

```python
# Stale
from langchain.memory import ConversationBufferMemory

chain = ConversationChain(llm=llm, memory=ConversationBufferMemory())

# Current
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(model, tools, checkpointer=InMemorySaver())
cfg = {"configurable": {"thread_id": "session-1"}}
agent.invoke({"messages": [...]}, cfg)
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [API reference — langchain_classic.memory.buffer.ConversationBufferMemory](https://reference.langchain.com/python/langchain-classic/memory/buffer/ConversationBufferMemory) · [LangChain v1 migration guide — langchain-classic](https://docs.langchain.com/oss/python/migrate/langchain-v1)

#### `langchain.retrievers / langchain.indexes`

**Removed in langchain 1.0.0** (2025-10-17)

Retrievers and the indexing API left the `langchain` namespace for `langchain-classic`. A vector store's own `.as_retriever()` is unaffected and is the normal way to retrieve in v1.

*The stale belief:* That `from langchain.retrievers import ...` and `from langchain.indexes import ...` resolve.

```python
# Stale
from langchain.retrievers import MultiQueryRetriever
from langchain.indexes import index

# Current
# pip install langchain-classic
from langchain_classic.retrievers import MultiQueryRetriever
from langchain_classic.indexes import index
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — langchain-classic](https://docs.langchain.com/oss/python/migrate/langchain-v1)

#### pre-bound models

**Removed in langchain 1.0.0** (2025-10-17)

`create_agent` does not accept a model that already has tools bound to it. Pass the model and the tools separately, and vary the model per call with middleware (`wrap_model_call` + `request.override(model=...)`) if you need dynamic selection.

*The stale belief:* That you can hand the agent factory `model.bind_tools([...])`.

```python
# Stale
agent = create_agent(model.bind_tools(tools), tools)

# Current
agent = create_agent(model, tools)
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Model](https://docs.langchain.com/oss/python/migrate/langchain-v1)

### Runs, but is silently wrong

Nothing errors. The behaviour is simply not what a model trained earlier will tell you.

#### tool extras

**Added in langchain 1.2.0** (2025-12-15)

Provider-specific tool parameters go in `extras`, a flat `dict[str, Any]` on the tool — `@tool(extras={...})` or the `extras` attribute on a tool object. The keys are the provider's own field names (`defer_loading`, `cache_control`, `input_examples`), NOT a provider name wrapping them. There is no `provider_specific` attribute, and `metadata=` is a different field that goes to callback handlers and never reaches the provider payload.

*The stale belief:* That provider-specific tool configuration has no first-class home on a tool, so it must be smuggled in by binding raw provider-format tool dicts, or hung on whichever tool field looks closest.

```python
# Stale
# stale, and silent: no such attribute. Pydantic accepts the assignment on the
# tool object and nothing downstream ever reads it.
tool_obj.provider_specific = {"anthropic": {"defer_loading": True}}

# stale, and silent: `metadata` is a real BaseTool field, but it goes to callback
# handlers and is never serialised into the tool definition sent to the provider.
# (As a DECORATOR keyword this one is not silent -- `@tool(metadata=...)` raises
# TypeError, because the decorator has never taken `metadata`.)
tool_obj.metadata = {"cache_control": {"type": "ephemeral"}}

# stale, and silent: right attribute, wrong shape. langchain-anthropic copies
# across only keys in its own whitelist, so an "anthropic" wrapper key is dropped.
@tool(extras={"anthropic": {"defer_loading": True}})
def u(x: str) -> str: ...

# stale, but NOT broken: rebuilding the provider's tool dict by hand and splicing
# the fields in does reach the wire. It costs you the tool defined twice, the name
# kept in sync by hand, and a separate lookup to execute the call the model returns.
fn = convert_to_openai_tool(search_docs)["function"]
model.bind_tools([{ "name": fn["name"], "description": fn["description"],
                   "input_schema": fn["parameters"],
                   "cache_control": {"type": "ephemeral"}, "defer_loading": True }])

# Current
@tool(extras={"defer_loading": True, "cache_control": {"type": "ephemeral"}})
def my_tool(x: str) -> str:
    ...
```

> The wrong forms above are not hypothetical: the first three are what Opus 5, Sonnet 5 and Fable 5 respectively produced on 2026-09-01 (battery langchain/v2), and the fourth is what four of six draws produced on 2026-09-06 (battery langchain/v6). Raised from S4 to S2 in 2026-09 because three of three tested models got it wrong and the failure is silent: the code runs and the provider instruction is discarded. **Two corrections to this note, both from executing the forms rather than reading them (2026-09-06, JOURNAL/065).** (1) It previously said all three forms "fail the same way -- nothing raises". That is true of the two attribute forms, verified against langchain-core 1.6.2: `tool_obj.metadata = {...}` and `@tool(extras={"anthropic": {...}})` both leave `convert_to_anthropic_tool` output with no `cache_control` and no `defer_loading`. It is NOT true of `@tool(metadata={...})` as a decorator keyword, which raises `TypeError: tool() got an unexpected keyword argument 'metadata'` -- the decorator has never accepted `metadata`, so that form is loud, not silent, and the example above is rewritten as the attribute assignment that actually is silent. (2) A fourth wrong form exists and it WORKS: hand-rebuilding the provider's tool dict and splicing the fields in delivers both, because `AnthropicTool` is a TypedDict and an already-Anthropic-shaped dict is copied whole. Findings charged off that form are scored S3, not S2. The mechanism, verified in langchain-anthropic 1.7.1 `chat_models.py`: `convert_to_anthropic_tool` iterates `tool.extras` and copies across only keys in `_ANTHROPIC_EXTRA_FIELDS` = {allowed_callers, cache_control, defer_loading, eager_input_streaming, input_examples}, placing each at the top level of the tool definition. Re-verified 2026-09-06 against the shipped langchain-core 1.6.2 and langchain-anthropic 1.7.1; absent from langchain-core 1.1.3 and 0.3.22.

*Reproduced against: **Claude Fable 5** (S2), **Claude Opus 5** (S2), **Claude Sonnet 5** (S2) — langchain/v2, 2026-09-01; langchain/v6-a, 2026-09-06; langchain/v6-c, 2026-09-06.*

Source: [LangChain changelog — langchain v1.2.0](https://docs.langchain.com/oss/python/releases/changelog) · 2025-12-15 · [langchain-core 1.6.1 published wheel — langchain_core/tools/base.py, the `extras` docstring example](https://files.pythonhosted.org/packages/8e/25/f50dd65673c819aa33d3c34df58c115dbb6ec627d19f93e6e401dd0fc8d7/langchain_core-1.6.1-py3-none-any.whl) · 2026-08-27 · [langchain-anthropic 1.7.1 published wheel -- chat_models.py, the whitelist that carries extras onto the wire](https://files.pythonhosted.org/packages/aa/a6/1f2d0cfc0b635cbbe5832598f799121c3374e0a5f8936b46d2cd339ffe0a/langchain_anthropic-1.7.1-py3-none-any.whl) · 2026-09-03 · [langchain-core 1.1.3 published wheel -- the release below the window, where tools/base.py has no extras field](https://files.pythonhosted.org/packages/58/41/6db768d4b208a33b4f09d5415e617d489f68167bb5dd27f87c7a49d13caf/langchain_core-1.1.3-py3-none-any.whl) · 2025-12-09

#### create_agent recursion limit

**Behaviour changed in langchain 1.1.0** (2025-11-24)

Since 1.1.0, `create_agent` stamps its own recursion limit onto the graph it compiles, so an agent built by the factory does not run under LangGraph's default. 1.1.0 ended `create_agent` with `.with_config({"recursion_limit": 10_000})`; the shipped release sets `{"recursion_limit": 9_999}`. The number to know is that it is four figures, not 25. A `GraphRecursionError` from a factory-built agent therefore means thousands of supersteps have actually run — a genuine non-terminating loop — and raising the ceiling is not the fix.

*The stale belief:* That a `create_agent` agent inherits LangGraph's 25-superstep default (about twelve tool rounds), so a step-limit error is normal for a long tool loop and the fix is `config={"recursion_limit": 100}`.

```python
# Stale
agent.invoke({"messages": [...]}, config={"recursion_limit": 100})

# Current
agent = create_agent(
    model,
    tools,
    middleware=[ModelCallLimitMiddleware(run_limit=40, exit_behavior="end")],
)
```

> Verified 2026-09-01 at both ends, from the shipped wheels; the changelog carries no line for it, so the introducing evidence is the 1.0.0/1.1.0 diff — 1.0.0 ends `create_agent` with a bare `graph.compile(...)`. Deliberately scoped to the langchain side: LangGraph raised its own `DEFAULT_RECURSION_LIMIT` from 25 to 10000 at langgraph 1.1.0 (2026-03-10) and ships 10007 at langgraph 1.2.11, so "25" is now stale twice over — but the Index charges only the `create_agent` fact, which is inside every tested subject's window. The value moved between introduction and today (10,000 to 9,999), so the durable fact is the shape and the origin, not the integer.

*Reproduced against: **Claude Fable 5** (S2), **Claude Opus 5** (S2), **Claude Sonnet 5** (S2) — langchain/v3, 2026-09-01.*

Source: [langchain 1.0.0 published wheel — langchain/agents/factory.py (no limit set)](https://files.pythonhosted.org/packages/c4/4d/2758a16ad01716c0fb3fe9ec205fd530eae4528b35a27ff44837c399e032/langchain-1.0.0-py3-none-any.whl) · 2025-10-17 · [langchain 1.1.0 published wheel — langchain/agents/factory.py (the change)](https://files.pythonhosted.org/packages/0b/6f/889c01d22c84934615fa3f2dcf94c2fe76fd0afa7a7d01f9b798059f0ecc/langchain-1.1.0-py3-none-any.whl) · 2025-11-24 · [langchain 1.3.18 published wheel — langchain/agents/factory.py (still true now, new value)](https://files.pythonhosted.org/packages/f7/04/374f6014ed6959dbdab92962c2b09e4d0223ed6a82f65694870b46d2c13f/langchain-1.3.18-py3-none-any.whl) · 2026-08-27

#### ModelRetryMiddleware defaults

**Added in langchain 1.1.0** (2025-11-24)

`ModelRetryMiddleware()` with no arguments does **not** re-raise when its retries run out. Its defaults are `max_retries=2` (three model calls in total) and `on_failure="continue"`, and `"continue"` means the middleware swallows the provider exception and returns a `ModelResponse` carrying a synthetic `AIMessage` whose text is `"Model call failed after N attempts with {ExcType}: {message}"`. The agent then proceeds as though the model had replied. Re-raising is opt-in: `on_failure="error"`. Every parameter is keyword-only; the rest of the defaults are `backoff_factor=2.0`, `initial_delay=1.0`, `max_delay=60.0`, `jitter=True` (±25%).

*The stale belief:* That a bounded-retry middleware re-raises the underlying provider exception once attempts are exhausted, so `agent.invoke(...)` fails loudly when the provider is down.

```python
# Stale
agent = create_agent(model, tools, middleware=[ModelRetryMiddleware()])
try:
    result = agent.invoke({"messages": [...]})
except Exception:
    alert_oncall()  # never runs: nothing is raised

# Current
agent = create_agent(
    model,
    tools,
    middleware=[ModelRetryMiddleware(on_failure="error")],
)
```

> Verified 2026-09-01 at both ends. This is the sharpest failure mode in the 1.1.0 surface: a caller who believes the exception propagates writes an `except` that never fires and ships an agent that answers with an error string as if it were a model reply. One default did move after introduction and is recorded rather than smoothed over: `retry_on` was `(Exception,)` at 1.1.0 and is the `default_retry_on` callable in the shipped release, which honours `ModelError.is_retryable` and retries unclassified exceptions. `max_retries` and `on_failure` are unchanged since 1.1.0.

*Reproduced against: **Claude Fable 5** (S2), **Claude Opus 5** (S2), **Claude Sonnet 5** (S2) — langchain/v3, 2026-09-01.*

Source: [LangChain changelog — langchain v1.1.0](https://docs.langchain.com/oss/python/releases/changelog) · 2025-11-24 · [langchain 1.3.18 published wheel — langchain/agents/middleware/model_retry.py](https://files.pythonhosted.org/packages/f7/04/374f6014ed6959dbdab92962c2b09e4d0223ed6a82f65694870b46d2c13f/langchain-1.3.18-py3-none-any.whl) · 2026-08-27

#### `AgentMiddleware.modify_model_request`

**Removed in langchain 1.0.0** (2025-10-17)

`modify_model_request` is not a middleware hook in any released langchain 1.x. It existed in the 1.0 alpha line and was replaced before 1.0.0 GA by `wrap_model_call(request, handler)` — which receives the downstream handler and so can retry, short-circuit or post-process, none of which the old signature could do. The shipped hook set is `before_agent` / `before_model` / `wrap_model_call` / `after_model` / `after_agent` / `wrap_tool_call`, each with an `a`-prefixed async twin. Defining `modify_model_request` on an `AgentMiddleware` subclass raises nothing: the method is simply never called, and the middleware silently does nothing.

*The stale belief:* That middleware customises a model call by defining `modify_model_request(self, request, state, runtime)` and returning a modified request.

```python
# Stale
class StampMiddleware(AgentMiddleware):
    def modify_model_request(self, request, state, runtime) -> ModelRequest:
        request.system_prompt = (request.system_prompt or "") + stamp
        return request

# Current
class StampMiddleware(AgentMiddleware):
    def wrap_model_call(self, request, handler):
        base = request.system_message.text if request.system_message else ""
        return handler(
            request.override(system_message=SystemMessage(content=base + stamp))
        )
```

> Verified 2026-09-01. The string `modify_model_request` appears nowhere in the published 1.0.0, 1.1.0, 1.2.0 or 1.3.18 wheels — this is a fact established by its absence from the shipped artifact across the whole 1.x line, which is why the citation quotes the hook list that replaced it. Scored S2 rather than S1 because Python raises nothing: an unrecognised method on a subclass is legal, so the failure is a middleware that is registered, never fires, and is trusted anyway.

*Reproduced against: **Claude Sonnet 5** (S2) — langchain/v3, 2026-09-01.*

Source: [langchain 1.3.18 published wheel — langchain/agents/middleware/types.py (the shipped hook set)](https://files.pythonhosted.org/packages/f7/04/374f6014ed6959dbdab92962c2b09e4d0223ed6a82f65694870b46d2c13f/langchain-1.3.18-py3-none-any.whl) · 2026-08-27 · [LangChain Python API reference — langchain.agents.middleware.AgentMiddleware](https://reference.langchain.com/python/langchain/)

#### agent streaming node name

**Renamed in langchain 1.0.0** (2025-10-17)

In a `create_agent` graph the model step is named `"model"`, not `"agent"`. Code that filters streamed events on `"agent"` imports, runs, and prints nothing — there is no error to tell you the filter never matched.

*The stale belief:* That the node emitting model output is called `"agent"`, as it was in `create_react_agent`.

```python
# Stale
for token, meta in agent.stream(inputs, stream_mode="messages"):
    if meta.get("langgraph_node") == "agent":   # never true
        print(token.text, end="")

# Current
for token, meta in agent.stream(inputs, stream_mode="messages"):
    if meta.get("langgraph_node") == "model":
        print(token.text, end="")
```

> This is the trap that survives a partial migration: swap `create_react_agent` for `create_agent` and the streaming filter keeps compiling while silently going dark.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Streaming node name rename](https://docs.langchain.com/oss/python/migrate/langchain-v1)

#### `ChatAnthropic(max_tokens=...)`

**Default changed in langchain 1.0.0** (2025-10-17)

`langchain-anthropic` no longer defaults `max_tokens` to a flat 1024; it picks a higher value based on the model. Advice built on the old number — "your answer is being cut off at 1024 tokens" — describes a default that no longer exists. If you actually relied on 1024, set it explicitly.

*The stale belief:* That `ChatAnthropic` caps output at 1024 tokens unless you override it.

```python
# Stale
model = ChatAnthropic(model="...")  # believed: max_tokens defaults to 1024

# Current
model = ChatAnthropic(model="...", max_tokens=1024)  # only if you want the old cap
```

> Setting `max_tokens` explicitly remains good practice, so code written on the stale belief still runs correctly — it is the stated reason that is wrong.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Default max_tokens in langchain-anthropic](https://docs.langchain.com/oss/python/migrate/langchain-v1)

#### ChatOpenAI Responses API output format

**Default changed in langchain 1.0.0** (2025-10-17)

Against the OpenAI Responses API, `langchain-openai` now stores response items in message `content` by default. Code that parsed the previous shape keeps running and reads the wrong structure; restore the old behaviour with `output_version="v0"` or `LC_OUTPUT_VERSION=v0`.

*The stale belief:* That the Responses API output lands in the pre-1.0 content shape.

```python
model = ChatOpenAI(model="...", output_version="v0")  # opt back into the old shape
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Default message format for OpenAI responses API](https://docs.langchain.com/oss/python/migrate/langchain-v1)

### Deprecated, or a better API now exists

Works today. It is the older idiom, and some of it is scheduled for removal.

#### astream_events(version="v3") on a create_agent agent

**Added in langchain 1.3.0** (2026-05-12)

`v2` is no longer the ceiling. From langchain 1.3.0 an agent built by `create_agent` accepts `version="v3"` in `stream_events` / `astream_events` - a content-block-centric protocol with typed, per-channel projections (`run.values`, `run.messages`, `run.lifecycle`, `run.subgraphs`), where `run.messages` yields one `ChatModelStream` per LLM call with sub-projections for text, reasoning, tool calls and usage. `v1` and `v2` are unchanged, so this is an addition rather than a migration. Scope matters: the claim is about the **agent**. The `langchain` package's own `_ConfigurableModel.astream_events` - what `init_chat_model(..., configurable_fields=...)` returns - still types `version: Literal["v1", "v2"] = "v2"` at 1.3.0 and 1.4.0.

*The stale belief:* That `v2` is the highest event-stream protocol version that exists, `v1` being the deprecated original - so a consumer that wants typed, per-channel event projections has to build them itself out of `v2` events.

```python
# Stale
async for event in agent.astream_events(inputs, version="v2"):
    ...  # and reshape v2's flat event dicts by hand

# Current
async for event in agent.astream_events(inputs, version="v3"):
    ...  # typed per-channel projections: run.values / run.messages /
         # run.lifecycle / run.subgraphs
```

> The changelog is the primary source for the langchain-side support and is quoted in full below; the artifact evidence is the dependency floor and the transformer registration that arrived with it (see LF37), plus langgraph 1.2.11, whose `pregel/main.py` carries the v3 machinery and rejects kwargs that would break v3's invariants. Verified 2026-09-06. Do not extend this fact to the configurable chat-model wrapper: that signature was checked at 1.3.0 and 1.4.0 and still stops at v2.

*Reproduced against: **Claude Fable 5.1** (S3) — langchain/v5-a, 2026-09-06.*

Source: [LangChain changelog - langchain v1.3.0, May 12, 2026](https://docs.langchain.com/oss/python/releases/changelog) · 2026-05-12 · [LangChain changelog - langgraph v1.2.0, the same day, on what v3 is](https://docs.langchain.com/oss/python/releases/changelog) · 2026-05-12 · [langchain 1.3.0 published wheel - the langgraph>=1.2.0 floor and the ToolCallTransformer registration](https://files.pythonhosted.org/packages/7b/6f/b9a9721c27fbb6d29a6a7cd89d6a41eeffc7c79b49b9a5cf5beb1d60952d/langchain-1.3.0-py3-none-any.whl) · 2026-05-12

#### `create_agent(transformers=...)`

**Added in langchain 1.3.0** (2026-05-12)

`create_agent` takes a `transformers=` argument: a sequence of scope-aware `StreamTransformer` factories that are registered on the graph it compiles, in addition to the agent's own defaults. Each factory is invoked once per scope (`factory(scope)`), so a subgraph gets its own instance. The agent always registers `ToolCallTransformer` first and appends yours after it, so you keep the built-in behaviour instead of replacing it. This is the supported way to shape an agent's event stream at construction time: you do not have to abandon `create_agent` and hand-build the `StateGraph`, and you do not have to wrap `.astream()` on the consumer side.

*The stale belief:* That the compiled agent is a closed box: `create_agent` gives you no hook into the graph's stream machinery, so scope-aware transformation must be done either by dropping to a hand-built `StateGraph` or by wrapping `.astream()`/`.astream_events()` in your own generator and reconstructing scope from `subgraphs=True` namespace tuples.

```python
# Stale
# stale: the capability is denied, so the requirement is met on the consumer side
agent = create_agent(model, tools, system_prompt="...")

async def my_stream(inputs, config=None):
    per_scope = {}
    async for ns, mode, chunk in agent.astream(
        inputs, config=config, stream_mode=["updates"], subgraphs=True
    ):
        per_scope.setdefault(ns, MyTransformer(ns))
        yield per_scope[ns](mode, chunk)

# Current
agent = create_agent(
    model,
    tools,
    system_prompt="...",
    transformers=[MyTransformer],  # factory, called once per scope
)
```

> Verified against the shipped wheels on 2026-09-06. 1.2.18 has no `transformers` parameter anywhere in `langchain/agents/factory.py` and calls `graph.compile()` without one; 1.3.0 adds `transformers: Sequence[Callable[[tuple[str, ...]], Any]] | None = None` to the signature, imports `ToolCallTransformer` from `langgraph.prebuilt`, and ends the compile call with `transformers=[ToolCallTransformer, *(transformers or ())]`. 1.4.0 keeps it, retypes it `Sequence[TransformerFactory]`, and inserts `AgentMiddleware.transformers` between the built-in and yours. The dependency floor moves in the same release (`langgraph<1.2.0,>=1.1.10` at 1.2.18 -> `langgraph<1.3.0,>=1.2.0` at 1.3.0), because the transformer/mux machinery is langgraph 1.2.0's. Scored S3, not S1: the consumer-side wrapper the stale belief produces does run, so a denial costs a reader unnecessary code rather than a broken build.

*Reproduced against: **Claude Fable 5.1** (S3) — langchain/v5-a, 2026-09-06.*

Source: [langchain 1.3.0 published wheel - langchain/agents/factory.py](https://files.pythonhosted.org/packages/7b/6f/b9a9721c27fbb6d29a6a7cd89d6a41eeffc7c79b49b9a5cf5beb1d60952d/langchain-1.3.0-py3-none-any.whl) · 2026-05-12 · [langchain 1.2.18 published wheel - the release below the window, with no such parameter](https://files.pythonhosted.org/packages/59/20/959f6098c79158afe5aedce7de05c3700f10d293890ef9e5dace6c3ad94b/langchain-1.2.18-py3-none-any.whl) · 2026-05-08 · [LangChain changelog - langchain v1.3.0](https://docs.langchain.com/oss/python/releases/changelog) · 2026-05-12

#### `ModelRequest.system_message`

**Renamed in langchain 1.1.0** (2025-11-24)

The system-instruction field on the `ModelRequest` object handed to middleware is `system_message: SystemMessage | None`. Until 1.1.0 it was `system_prompt: str | None`, a plain dataclass field. `system_prompt` survives as a read-only property returning `self.system_message.text`, and `override(system_prompt=...)` still converts, so pre-1.1 middleware keeps working — the shipped docstring labels the parameter "deprecated, converted to `SystemMessage`". Note the asymmetry that makes this easy to get wrong: `create_agent` itself still takes `system_prompt=`, so the public factory and the middleware-facing request now use different names for the same instruction.

*The stale belief:* That the middleware request carries the system instruction as a plain string field called `system_prompt`, the same name `create_agent` uses.

```python
# Stale
def wrap_model_call(self, request, handler):
    base = request.system_prompt or ""
    return handler(request.override(system_prompt=base + stamp))

# Current
def wrap_model_call(self, request, handler):
    base = request.system_message.text if request.system_message else ""
    return handler(
        request.override(system_message=SystemMessage(content=base + stamp))
    )
```

> Verified 2026-09-01 at both ends, from the shipped wheels rather than from release notes — the vendor changelog carries no line for this rename, so the introducing evidence is the 1.0.0/1.1.0 wheel diff, which is the artifact itself. Scored S3 rather than S2 because the old route genuinely still works; a reader following it gets a deprecation path, not a wrong result. The string route also loses what 1.1.0 added the object for: a `SystemMessage` can carry structured content blocks and provider cache markers that a `str` cannot.

*Reproduced against: **Claude Opus 5** (S3) — langchain/v3, 2026-09-01.*

Source: [langchain 1.0.0 published wheel — langchain/agents/middleware/types.py (the pre-rename state)](https://files.pythonhosted.org/packages/c4/4d/2758a16ad01716c0fb3fe9ec205fd530eae4528b35a27ff44837c399e032/langchain-1.0.0-py3-none-any.whl) · 2025-10-17 · [langchain 1.1.0 published wheel — langchain/agents/middleware/types.py and agents/factory.py](https://files.pythonhosted.org/packages/0b/6f/889c01d22c84934615fa3f2dcf94c2fe76fd0afa7a7d01f9b798059f0ecc/langchain-1.1.0-py3-none-any.whl) · 2025-11-24 · [langchain 1.3.18 published wheel — langchain/agents/middleware/types.py (still true now)](https://files.pythonhosted.org/packages/f7/04/374f6014ed6959dbdab92962c2b09e4d0223ed6a82f65694870b46d2c13f/langchain-1.3.18-py3-none-any.whl) · 2026-08-27

#### `langchain.agents.create_agent`

**Renamed in langchain 1.0.0** (2025-10-17)

`create_agent` from `langchain.agents` is the way to build an agent. `langgraph.prebuilt.create_react_agent` is deprecated as of LangGraph v1 in its favour; it still imports and runs, so this is not an immediate breakage, but every other v1 change below assumes you are on `create_agent` — the parameter names, the middleware hooks and the streaming node name all differ between the two.

*The stale belief:* That `from langgraph.prebuilt import create_react_agent` is the current recommended way to build a tool-calling agent, which it was throughout the 0.2/0.3 era.

```python
# Stale
from langgraph.prebuilt import create_react_agent

agent = create_react_agent(model, tools, prompt="You are helpful.")

# Current
from langchain.agents import create_agent

agent = create_agent(model, tools, system_prompt="You are helpful.")
```

> Two different functions have carried the name `create_react_agent`: the LangGraph prebuilt (deprecated here) and the much older `langchain.agents.react.agent.create_react_agent`, which moved to `langchain-classic`. Neither is the current answer.

*Reproduced against: **Claude Sonnet 5** (S3) — langchain/v1, 2026-08-31.*

Source: [LangGraph v1 migration guide — Deprecations](https://docs.langchain.com/oss/python/migrate/langgraph-v1) · [What's new in LangChain v1 — create_agent](https://docs.langchain.com/oss/python/releases/langchain-v1)

#### model retry middleware

**Added in langchain 1.1.0** (2025-11-24)

`ModelRetryMiddleware` ships in `langchain.agents.middleware` and retries the model call only, leaving tool calls alone. Its keyword-only parameters are `max_retries`, `retry_on`, `on_failure`, `backoff_factor`, `initial_delay`, `max_delay` and `jitter`. Do not hand-roll a `wrap_model_call` retry loop, and do not reach for the LCEL-era `Runnable.with_retry()` on the model — that predates this and wraps the model in a `RunnableRetry` before `create_agent` ever sees it.

*The stale belief:* That model-call retry has no first-class component, so it must be written as a custom `wrap_model_call` middleware, bolted on with `.with_retry()`, or wrapped in a `try/except` around the whole agent invocation.

```python
# Stale
# stale: retries the entire graph, tool calls included -- not what was asked for
for attempt in range(5):
    try:
        result = agent.invoke(payload)
        break
    except (RateLimitError, APITimeoutError):
        time.sleep(2**attempt)

# Current
from langchain.agents.middleware import ModelRetryMiddleware

agent = create_agent(
    model=model,
    tools=tools,
    middleware=[
        ModelRetryMiddleware(
            max_retries=4,
            retry_on=(RateLimitError, APITimeoutError),
            backoff_factor=2.0,
            initial_delay=1.0,
            jitter=True,
        )
    ],
)
```

> Verified 2026-09-01 at both ends, with the parameter list read directly off the shipped `__init__`. Whether `create_agent` actually rejects a `.with_retry()`-wrapped model is an open question in data/langchain/sonnet-5-v2.json and is deliberately not asserted here.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain changelog — langchain v1.1.0](https://docs.langchain.com/oss/python/releases/changelog) · 2025-11-25 · [langchain 1.3.18 published wheel — langchain/agents/middleware/model_retry.py](https://files.pythonhosted.org/packages/f7/04/374f6014ed6959dbdab92962c2b09e4d0223ed6a82f65694870b46d2c13f/langchain-1.3.18-py3-none-any.whl) · 2026-08-27

#### ModelRequest attribute assignment

**Deprecated in langchain 1.1.0** (2025-11-24)

Assigning to any attribute of a `ModelRequest` in middleware is deprecated: 1.1.0 added a `__setattr__` that emits a `DeprecationWarning` on every direct assignment and directs the caller to `request.override(...)`, which returns a new request rather than mutating the shared one. `override()` itself is not new — it existed at 1.0.0 — but until 1.1.0 in-place mutation was an equally supported idiom, and LangChain's own `ContextEditingMiddleware` used it.

*The stale belief:* That middleware modifies the request in place — `request.system_prompt = ...`, `request.messages.append(...)` — and returns it.

```python
# Stale
def wrap_model_call(self, request, handler):
    request.system_prompt = request.system_prompt + stamp
    return handler(request)

# Current
def wrap_model_call(self, request, handler):
    return handler(request.override(system_message=SystemMessage(content=new_text)))
```

> Verified 2026-09-01 at both ends, from the shipped wheels; the changelog carries no line for it. Corroborated inside the library's own source: at 1.0.0 `context_editing.py` mutated `request.messages` in place, and at 1.1.0 the same file deep-copies the list and returns `handler(request.override(messages=edited_messages))`. Scored S3: the warning is a warning, the assignment still takes effect.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [langchain 1.0.0 published wheel — middleware/types.py defines no __setattr__; context_editing.py mutates in place](https://files.pythonhosted.org/packages/c4/4d/2758a16ad01716c0fb3fe9ec205fd530eae4528b35a27ff44837c399e032/langchain-1.0.0-py3-none-any.whl) · 2025-10-17 · [langchain 1.3.18 published wheel — langchain/agents/middleware/types.py](https://files.pythonhosted.org/packages/f7/04/374f6014ed6959dbdab92962c2b09e4d0223ed6a82f65694870b46d2c13f/langchain-1.3.18-py3-none-any.whl) · 2026-08-27

#### SystemMessage as system_prompt

**Added in langchain 1.1.0** (2025-11-24)

`create_agent`'s `system_prompt` accepts a `SystemMessage`, not only a `str`. That is how a fixed instruction carries provider metadata — content blocks with `cache_control` for prompt caching, for instance — without adding middleware and without hand-assembling the message list. The shipped signature is `system_prompt: str | SystemMessage | None`.

*The stale belief:* That `system_prompt` is string-only, so any provider metadata on the system block requires middleware, a custom message list, or binding the hint on the model instead.

```python
# Stale
# stale: assumes str-only, so the cache marker has nowhere to go
agent = create_agent(model=model, tools=tools, system_prompt=LONG_INSTRUCTIONS)

# Current
system = SystemMessage(
    content=[
        {
            "type": "text",
            "text": LONG_INSTRUCTIONS,
            "cache_control": {"type": "ephemeral"},
        }
    ]
)
agent = create_agent(model=model, tools=tools, system_prompt=system)
```

> Verified 2026-09-01 at both ends. All three subjects tested on that date produced this form correctly, so it is carried as a correction for readers rather than as a measured model failure.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain changelog — langchain v1.1.0](https://docs.langchain.com/oss/python/releases/changelog) · 2025-11-25 · [langchain 1.3.18 published wheel — langchain/agents/factory.py](https://files.pythonhosted.org/packages/f7/04/374f6014ed6959dbdab92962c2b09e4d0223ed6a82f65694870b46d2c13f/langchain-1.3.18-py3-none-any.whl) · 2026-08-27

#### bind_tools return type

**Behaviour changed in langchain 1.0.0** (2025-10-17)

Chat model invocation is typed as returning `AIMessage`, not `BaseMessage`. Custom chat models implementing `bind_tools` should narrow their return signature to match.

*The stale belief:* That `bind_tools` is annotated `Runnable[LanguageModelInput, BaseMessage]`.

```python
# Stale
def bind_tools(...) -> Runnable[LanguageModelInput, BaseMessage]:

# Current
def bind_tools(...) -> Runnable[LanguageModelInput, AIMessage]:
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Updated return type for chat models](https://docs.langchain.com/oss/python/migrate/langchain-v1)

#### built-in agent middleware

**Added in langchain 1.0.0** (2025-10-17)

Middleware is the extension point of `create_agent`: hooks `before_agent`, `before_model`, `wrap_model_call`, `wrap_tool_call`, `after_model`, `after_agent`, plus built-ins including `SummarizationMiddleware`, `HumanInTheLoopMiddleware`, `PIIMiddleware` and `TodoListMiddleware`. Hand-rolled graph surgery for summarisation, approval gates or redaction is the pre-1.0 answer.

*The stale belief:* That customising the agent loop means writing your own LangGraph `StateGraph`.

**The code below needs langchain 1.1.0 or later.** Between 1.0.0 and 1.1.0 this correction does not apply — see the note.

```python
from langchain.agents.middleware import SummarizationMiddleware, HumanInTheLoopMiddleware

agent = create_agent(model, tools, middleware=[
    # on langchain 1.0.x this parameter is max_tokens_before_summary=500
    SummarizationMiddleware(model=model, trigger={"tokens": 500}),
    HumanInTheLoopMiddleware(interrupt_on={"send_email": {"allowed_decisions": ["approve", "reject"]}}),
])
```

> CORRECTED 2026-09-02 (JOURNAL/037), by the first mechanical audit of a facts file (`tools/audit/`). The middleware system does arrive at 1.0.0, and the fact is right about that — but the snippet above was written against the current release and does not run on any 1.0.x. `SummarizationMiddleware` took `max_tokens_before_summary` through the whole 1.0 line and the `trigger=` form did not exist until **1.1.0**, where `max_tokens_before_summary` was removed in the same release. Bisected in the installed packages: 1.0.0 and 1.0.8 accept `max_tokens_before_summary` and reject `trigger`; 1.1.0, 1.2.0 and 1.3.18 accept `trigger` and reject `max_tokens_before_summary`. So there is no single summarisation call that works across the `langchain>=1,<2` range this pack claims, and a reader on 1.0.x copying the old text got `TypeError: unexpected keyword argument 'trigger'`. On 1.0.x write `SummarizationMiddleware(model=model, max_tokens_before_summary=500)` instead. `trigger` takes a `TriggerClause` — a total=False TypedDict with optional `tokens`, `messages` and `fraction` keys — so the dict form above is the declared type and not merely an accepted one. `HumanInTheLoopMiddleware(interrupt_on=...)` was checked at both ends and is unchanged: it constructs identically on 1.0.8 and 1.3.18. 1.1.0 (2025-11-24) also added model-retry middleware; the Anthropic-specific middleware moved into `langchain-anthropic` during the 1.0 cycle.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [What's new in LangChain v1 — Middleware](https://docs.langchain.com/oss/python/releases/langchain-v1)

#### `message.content_blocks`

**Added in langchain 1.0.0** (2025-10-17)

Messages carry a provider-agnostic `content_blocks` property: typed blocks for text, reasoning, tool calls and citations that read the same across Anthropic, OpenAI, AWS, Google GenAI and Ollama. `content` is unchanged, so nothing breaks; per-provider branching on `content` is simply no longer necessary. Standard blocks are not serialised into `content` unless you opt in with `output_version="v1"` or `LC_OUTPUT_VERSION=v1`.

*The stale belief:* That normalising reasoning/text/tool-call output requires hand-written per-provider branches.

```python
# Stale
for item in response.content:
    if item.get("type") == "reasoning": ...   # OpenAI
    elif item.get("type") == "thinking": ...  # Anthropic

# Current
for block in response.content_blocks:
    if block["type"] == "reasoning": ...
    elif block["type"] == "text": ...
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [What's new in LangChain v1 — Standard content blocks](https://docs.langchain.com/oss/python/releases/langchain-v1)

#### `message.text`

**Deprecated in langchain 1.0.0** (2025-10-17)

`.text` is a property on message objects. The `.text()` method form still works but emits a warning and is scheduled for removal in v2.

*The stale belief:* That `response.text()` is the way to get a message's text.

```python
# Stale
text = response.text()

# Current
text = response.text
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Text property](https://docs.langchain.com/oss/python/migrate/langchain-v1)

#### multimodal message input

**Added in langchain 1.0.0** (2025-10-17)

Multimodal input has a standard form: `HumanMessage(content_blocks=[{"type": "image", "url": ...}])`. The provider-native `{"type": "image_url", "image_url": {"url": ...}}` shape inside `content` is the old spelling.

*The stale belief:* That images must be passed in each provider's own content shape.

```python
# Stale
HumanMessage(content=[{"type": "image_url",
                       "image_url": {"url": "https://example.com/i.jpg"}}])

# Current
HumanMessage(content_blocks=[{"type": "image",
                              "url": "https://example.com/i.jpg"}])
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Create multimodal messages](https://docs.langchain.com/oss/python/migrate/langchain-v1)

#### run-scoped context (context= / context_schema=)

**Added in langchain 1.0.0** (2025-10-17)

Static per-run data (a user id, a tenant) is passed with `context=` against a declared `context_schema`, and read inside a tool through a `ToolRuntime` parameter, which is stripped from the schema the model sees. The older `config={"configurable": {...}}` route still works and the migration guide says so explicitly — this is a better-idiom change, not a breakage.

*The stale belief:* That `config["configurable"]` is the only way to get request-scoped data into a tool.

```python
# Stale
agent.invoke(state, config={"configurable": {"user_id": "u_1"}})

# Current
from dataclasses import dataclass
from langchain.tools import tool, ToolRuntime

@dataclass
class Context:
    user_id: str

@tool
def get_orders(runtime: ToolRuntime[Context]) -> str:
    """Fetch the caller's orders."""
    return lookup(runtime.context.user_id)

agent = create_agent(model, [get_orders], context_schema=Context)
agent.invoke(state, context=Context(user_id="u_1"))
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Runtime context](https://docs.langchain.com/oss/python/migrate/langchain-v1) · [LangChain docs — Tools: ToolRuntime](https://docs.langchain.com/oss/python/langchain/tools)

### Wrong facts about the library

Not code — versions, minimums and metadata that models state confidently and get wrong.

#### ProviderStrategy strict

**Added in langchain 1.2.0** (2025-12-15)

`ProviderStrategy` takes a `strict` flag that asks the provider to enforce the schema server-side; when set, the strategy puts `"strict": True` into the JSON schema it sends. `ProviderStrategy(Schema)` alone is correct 1.0-era code and still routes to provider-native structured output — `strict=True` is the 1.2.0 addition on top of it.

*The stale belief:* That `ProviderStrategy` has no strictness control, so provider-side schema enforcement is all-or-nothing.

```python
agent = create_agent(
    model=model,
    tools=tools,
    response_format=ProviderStrategy(Verdict, strict=True),
)
```

> Verified 2026-09-01 at both ends. Scored S4: omitting it is an incomplete answer rather than a wrong one, which is why the langchain/v2 battery treated it as supplementary and excluded it from its counts. All three subjects tested on that date produced `ProviderStrategy(Schema)` without it.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain changelog — langchain v1.2.0](https://docs.langchain.com/oss/python/releases/changelog) · 2025-12-15 · [langchain 1.3.18 published wheel — langchain/agents/structured_output.py](https://files.pythonhosted.org/packages/f7/04/374f6014ed6959dbdab92962c2b09e4d0223ed6a82f65694870b46d2c13f/langchain-1.3.18-py3-none-any.whl) · 2026-08-27

#### model profiles (.profile)

**Added in langchain 1.1.0** (2025-11-21)

Chat models carry a `.profile` attribute — a `ModelProfile` TypedDict describing what the model supports, loaded from the provider package. Ask the model object instead of keeping a table of model names: `image_inputs`, `pdf_inputs`, `audio_inputs`, `max_input_tokens`, `structured_output` and `tool_calling` are among its keys. It is `total=False`, so read every key with `.get()`. `create_agent` reads it internally to decide whether a model can do provider-native structured output.

*The stale belief:* That model capabilities must be hard-coded in a name-to-capability table, discovered by trial, or fetched from an external catalogue over HTTP.

```python
# Stale
# stale: a table that goes out of date every time a provider ships a model
VISION = {"gpt-4o", "claude-sonnet-4-5", "gemini-2.5-pro"}
if model.model_name in VISION:
    ...

# Current
profile = getattr(model, "profile", None) or {}
if profile.get("image_inputs"):
    ...  # send the image
else:
    ...  # fall back to a text description
```

> The introducing release is langchain-core 1.1.0 (2025-11-21), which is three days before langchain 1.1.0 (2025-11-24) — the changelog reports the feature under the langchain 1.1.0 heading. Re-verified 2026-09-01 against the shipped langchain-core 1.6.1 wheel, whose `profile` field docstring carries its own `version-added` marker naming 1.1.0. Measured on 2026-09-01: all three subjects tested (Opus 5, Fable 5, Sonnet 5) used `.profile` correctly and all three dated it to the 1.0 line. Key names sharpened 2026-09-01 from the shipped `langchain_core/language_models/model_profile.py`: `ModelProfile` is a `total=False` TypedDict, so every key may be absent and the docstring says to guard accesses with `.get()`. The input-capability keys are `image_inputs`, `image_url_inputs`, `pdf_inputs`, `audio_inputs`, `video_inputs` — not `supports_vision` or a `modalities` list. `image_inputs` and `pdf_inputs` are present from core 1.1.0; `text_inputs`, `tool_call_streaming`, `attachment` and `temperature` were added later.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain changelog — langchain v1.1.0](https://docs.langchain.com/oss/python/releases/changelog) · 2025-11-25 · [langchain-core 1.6.1 published wheel — langchain_core/language_models/chat_models.py](https://files.pythonhosted.org/packages/8e/25/f50dd65673c819aa33d3c34df58c115dbb6ec627d19f93e6e401dd0fc8d7/langchain_core-1.6.1-py3-none-any.whl) · 2026-08-27 · [langchain-core 1.6.1 published wheel — langchain_core/language_models/model_profile.py](https://files.pythonhosted.org/packages/8e/25/f50dd65673c819aa33d3c34df58c115dbb6ec627d19f93e6e401dd0fc8d7/langchain_core-1.6.1-py3-none-any.whl) · 2026-08-27

#### langchain package namespace

**Removed in langchain 1.0.0** (2025-10-17)

The v1 `langchain` package exports five modules and nothing else: `langchain.agents`, `langchain.messages`, `langchain.tools`, `langchain.chat_models`, `langchain.embeddings`. Most are re-exports from `langchain-core`. Describing `langchain` as a kitchen sink of chains, retrievers and integrations describes 0.3.

*The stale belief:* That `langchain` is a large package containing chains, retrievers, memory, the hub and integration code.

```python
from langchain.agents import create_agent
from langchain.messages import AIMessage, HumanMessage
from langchain.tools import tool
from langchain.chat_models import init_chat_model
from langchain.embeddings import init_embeddings
```

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [What's new in LangChain v1 — Namespace](https://docs.langchain.com/oss/python/releases/langchain-v1) · [API reference — the langchain package](https://reference.langchain.com/python/langchain/)

#### langchain release timeline

**New requirement in langchain 1.0.0** (2025-10-17)

langchain 1.0.0 was published to PyPI on 2025-10-17, together with langchain-core 1.0.0 and langchain-openai 1.0.0. 1.1.0 followed on 2025-11-24, 1.2.0 on 2025-12-15, 1.3.0 on 2026-05-12. The latest release as of 2026-08-31 is 1.3.18 (2026-08-27), with langchain-core at 1.6.1. The last 0.3 release before the 1.0 alpha line was 0.3.27 on 2025-07-24.

*The stale belief:* That 0.3.x is the current line, or that 1.0 is unreleased or upcoming.

> Dates are the earliest file upload timestamp per release from the PyPI JSON API. The vendor changelog labels the 1.0.0 entry 'Oct 20, 2025'; the package files were uploaded 2025-10-17.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [PyPI JSON API — langchain release timestamps](https://pypi.org/pypi/langchain/json) · [LangChain Python changelog](https://docs.langchain.com/oss/python/releases/changelog)

#### Python version requirement

**New requirement in langchain 1.0.0** (2025-10-17)

All LangChain packages require Python 3.10 or higher. Python 3.9 support was dropped in v1.

*The stale belief:* That LangChain still supports Python 3.9.

*Reproduced against: no model yet. Verified from the primary source only — this is a correction, not an Index entry.*

Source: [LangChain v1 migration guide — Dropped Python 3.9 support](https://docs.langchain.com/oss/python/migrate/langchain-v1)

## Not corrections — recorded for honesty

Claims seen in a run but not yet verified against a primary source. Never treated as findings:

- Are the beta identifier `advanced-tool-use-2025-11-20` and the server-tool type `tool_search_tool_regex_20251119`, both volunteered by this draw, real Anthropic identifiers? *(open since 2026-09-06)*
- Does `create_agent` accept a model that has been wrapped by `Runnable.with_retry()`? The shipped `factory.py` types the parameter `str | BaseChatModel` and only calls `init_chat_model` on the string branch, so a `RunnableRetry` is passed through un-validated to a code path that later reaches for chat-model methods. Relevant to Sonnet 5's P3 answer in the sibling run. *(open since 2026-09-01)*
- This draw and `v1r-b` were given a byte-identical prompt, the same model alias, on the same day, running concurrently, and placed the boundary thirteen months apart (1.0.0 / 2025-10-17 here; 0.3.0 / 2024-09-13 there). Two draws establish that the instrument is not single-valued; they do not establish the shape of the distribution, which draw is modal, or whether the split is bimodal at all. *(open since 2026-09-01)*
- Every other boundary published by the Index rests on a single draw of this same instrument, including the seven that produce Opus 5's three-day intersection. None has been replicated. *(open since 2026-09-01)*
- See `langchain--claude-sonnet-5--v1r-a--2026-09-01`. This draw agrees with `langchain/v1`; its twin does not, and both were given a byte-identical prompt on the same day. *(open since 2026-09-01)*
- Task 10 did not reproduce v1's S2 stream-filter failure. One divergence in one task is not enough to say whether findings are as unstable as boundaries, and a code-level reproducibility check has never been run. *(open since 2026-09-01)*
- Does `create_agent` accept a model wrapped by `Runnable.with_retry()`? The shipped `factory.py` types the parameter `str | BaseChatModel` and only calls `init_chat_model` on the string branch, so a `RunnableRetry` passes through un-validated into a path that later reaches for chat-model methods. If it raises, this subject's P3 is a build break rather than a partial. Not run, so not claimed. *(open since 2026-09-01)*
- Sonnet 5's langchain boundary now has two measurements 13 months apart — 0.3.0 in v1 (2026-08-31) and a "1.0 alpha/beta, mid-2025" self-placement in v3 (2026-09-01). Is that elicitation variance from differently-shaped prompts, or is the v1 measurement wrong? Every published boundary in the Index rests on a single measurement per model per library, so this is a question about the instrument, not about this run. Re-run `langchain/v1` against Sonnet 5 unchanged and compare before trusting either number. *(open since 2026-09-01)*
- All three subjects named `max_retries=2` correctly and all three asserted the exhausted-retry behaviour backwards. Is a default *count* systematically more guessable than a default *policy*? If so, future batteries should probe policies and treat counts as free. *(open since 2026-09-01)*
- Does the deprecated `langgraph.prebuilt.create_react_agent` still accept `pre_model_hook=`, and is its model node still named `"agent"`, on current LangGraph? *(open since 2026-08-31)*

---

*Findings, code and citations: `data/langchain/` — one JSON file and one write-up per model, each finding carrying the release that broke the belief, its publication date and a verbatim quote from the primary source. Corrections: `data/langchain/facts.json`. This file is generated by `tools/build-corrections.mjs`; if the prose and the data ever disagree, that is a bug in the generator, not a stale pack.*
