LangChain: who decides what

The gallery · Make a map of your own system · The repository

An agent built with LangChain's create_agent(model, tools) at its defaults, and the LangGraph runtime that runs it: choosing the provider, what the model sees, tool calls and their errors, the opt-in middleware (human review, PII, summarization, limits, retries, the shell tool), step limits, saving progress and the end of a run. Read in langchain-ai/langchain at commit efebcf50 and langchain-ai/langgraph at commit ee3c043b. The legacy langchain-classic package, the older create_react_agent, LangGraph's functional API, and its command line and SDK are outside this map. Claims about how the code behaves refer to those commits. Both projects are MIT-licensed; this page quotes short identifiers and cites files and lines at those commits.

How to read this page

A seat is a judgment the agent makes over and over: which provider answers, what the model sees, may this tool call run, is the run over. Each card records the question, how it is answered, and who or what holds it, with the code that implements or exposes that decision. Seat ids (A1, B11 …) are links.

Each answer describes what an agent built with create_agent does by default, running on LangGraph. Middleware is opt-in, and an answer that names one describes it at its own defaults. Exceptions named are the main ones, not a complete list.

The chip on each card has two words. The first is about the question: a RULE has one right answer given its inputs, so a run that answers differently is a bug; a RULING is one that competent people could answer differently. Enforcing a limit someone set is a RULE; choosing what to keep, what to trust or when the work is done is a RULING, even when code holds it.

The second word is who holds it: llm = a language-model call; model = another learned model; code = code in their repositories; human = a person; external = code outside the repositories that the agent hands the decision to.

Paths start with lc: for the langchain-ai/langchain repository and lg: for langchain-ai/langgraph, each from its root. A bare :120-140 or (323) means lines in the file named just before it. In the langchain repository, the langchain package lives in libs/langchain_v1/; libs/langchain/ is the legacy classic package.

Counts

92 seats across 9 stages: A 11, B 11, C 7, D 23, E 13, F 12, G 8, H 5, I 2.

Kind of question · holderSeats
RULE · code46
RULING · code32
RULING · external6
RULING · llm4
RULING · human3
RULE · external1

What this edition does not have

There is no list of gaps. No coverage figure: this edition does not say how many of the agent's model calls and decision points the seats leave out. Each card shows the question, the answer and its holder; for most seats we also recorded what checks the judgment and how it has failed, but those notes are not shown here.

What is left out of this edition

3 of the 92 cards are left out of this edition. The counts above include them.

A. Intake and routing (11 seats)

A1 Given "openai:gpt-5.5", which company's API will the agent call? RULE · code
Answered how

The text before the first colon picks the provider only if it is one of the 30 keys in the built-in table; that entry names the integration class, which is imported on demand and built with the rest of the string as the model name. Any other prefix is not treated as a prefix, so the whole string goes to A2's name inference, and "llama3:8b" fails there. create_agent passes no model_provider, so at defaults only the string decides.

Held by

lc:libs/langchain_v1/langchain/chat_models/base.py:623-651 (_parse_model), registry lc:libs/langchain_v1/langchain/chat_models/base.py:56-99, class import lc:libs/langchain_v1/langchain/chat_models/base.py:153-193; called from lc:libs/langchain_v1/langchain/agents/factory.py:1047-1048.

A2 Given just "gemini-2.5-pro" or "gpt-oss-20b", which provider does the agent decide to call? RULING · code
Answered how

When the string has no recognised prefix, code walks an ordered, case-insensitive table of name prefixes: gpt-, o1, o3, chatgpt and text-davinci mean OpenAI; claude means Anthropic's own API; gemini means Google Vertex AI, with a DeprecationWarning that this default will change; further entries cover Cohere, Fireworks, Bedrock, Mistral, DeepSeek, xAI, Perplexity and Upstage. A name that matches nothing raises at construction. The match reads the name only, so gpt-oss-20b goes to OpenAI and o4-mini matches nothing.

Held by

lc:libs/langchain_v1/langchain/chat_models/base.py:547-620 (_attempt_infer_model_provider, ordered startswith table); called at lc:libs/langchain_v1/langchain/chat_models/base.py:635.

A3 Will the agent's model traffic go straight to the provider, or through LangChain's hosted gateway, and with whose key? RULE · code
Answered how

By default each call goes to the provider's own endpoint with the provider's key. For ChatAnthropic, ChatOpenAI and ChatFireworks, setting LANGSMITH_GATEWAY, or using the langsmith: model prefix, can send calls through LangSmith's gateway instead; which address and key are then used depends on what else is set. An explicit api_key always wins.

Held by

lc:libs/core/langchain_core/utils/_gateway.py:106-179 (_resolve_gateway_config), switch lc:libs/core/langchain_core/utils/_gateway.py:74-103; applied in lc:libs/partners/anthropic/langchain_anthropic/chat_models.py:1724-1733, lc:libs/partners/openai/langchain_openai/chat_models/base.py:1420-1426, lc:libs/partners/fireworks/langchain_fireworks/chat_models.py:1196-1204; langsmith: prefix lc:libs/langchain_v1/langchain/chat_models/base.py:27-38.

A4 Once the agent is running, can a different model answer a later step? RULE · code
Answered how

Every model step builds a new request naming the one model fixed at creation, so at defaults the same model answers every step. A wrap_model_call middleware can swap it for a call (S11). With create_agent(""), init_chat_model returns a _ConfigurableModel that reads model and provider from each call's config["configurable"], so the choice moves to the caller per run.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1523-1532 (sync), lc:libs/langchain_v1/langchain/agents/factory.py:1582-1591 (async); the model is fixed at lc:libs/langchain_v1/langchain/agents/factory.py:1047-1048, executed at lc:libs/langchain_v1/langchain/agents/factory.py:1501 and lc:libs/langchain_v1/langchain/agents/factory.py:1506.

A5 If the caller sends extra fields or the wrong shape, does the run refuse or carry on? RULE · code
Answered how

The compiled graph's input channel is the single START channel, so the whole input is written there and the START node's writer decides. A dict keeps only keys in the input schema, which at defaults is messages alone (AgentState minus its input-omitted fields; a state_schema or middleware schema adds more), and any other key is dropped with no warning. None with nothing to resume is refused, and a value of a type the writer cannot read raises. A dict with no messages key is not refused at intake (the model node then reads state["messages"]; what happens there was not traced).

Held by

lg:libs/langgraph/langgraph/graph/state.py:1456-1492 (_get_updates, the START node's writer), lg:libs/langgraph/langgraph/graph/state.py:1356 (input_channels=START, so map_input takes its string branch lg:libs/langgraph/langgraph/pregel/_io.py:88-89), input schema lc:libs/langchain_v1/langchain/agents/factory.py:489-523, empty input lg:libs/langgraph/langgraph/pregel/_loop.py:1186-1187.

A6 When the caller sends plain strings or role/content dicts, which become user, system, assistant or tool messages? RULE · code
Answered how

Every input entry goes through add_messages, which coerces it. A bare string becomes a user message; a (role, content) pair and a dict use the role as given, the dict reading role and then type. system and developer become system messages, assistant an AI message with its tool calls, tool a tool result, and remove a deletion; a serialized {"lc": 1, "type": "constructor", ...} dict is mapped through a fixed class-name table first. Nothing restricts which role a caller may supply.

Held by

lc:libs/core/langchain_core/messages/utils.py:719-796 (_convert_to_message), lc:libs/core/langchain_core/messages/utils.py:606-694 (_create_message_from_message_type), reached through lg:libs/langgraph/langgraph/graph/message.py:197-204 (convert_to_messages inside add_messages).

A7 If two tools are both called search, which one will the model see and run? RULING · code
Answered how

Tools are stored in a dict keyed by name, middleware tools first and then the caller's, so the last one registered under a name silently replaces the earlier one. The model is offered that dict's values, so the replaced tool can be neither seen nor run, and a caller's tool beats a middleware tool of the same name. Provider tools given as dicts are not in this dict, so this step does not compare them.

Held by

lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:787-794 (last writer wins), order lc:libs/langchain_v1/langchain/agents/factory.py:1136, offered to the model lc:libs/langchain_v1/langchain/agents/factory.py:1155.

A8 Which of the tools given to the agent are executed by the model provider, out of the agent's sight? RULE · code
Answered how

Any entry in tools that is a plain dict is set aside as a provider built-in: it goes to the model's bind_tools as given, never enters the tools node, and is skipped by the unknown-tool check. The code decides this on the Python type alone and does not look inside the dict, so "runs at the provider" is the factory's assumption about dict tools, not something it checks. Everything else runs locally in the tools node.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1131-1133 (dict test), bound via lc:libs/langchain_v1/langchain/agents/factory.py:1155-1157, skipped by the unknown-tool check lc:libs/langchain_v1/langchain/agents/factory.py:1385-1387.

A9 When an MCP server is connected, which of its tools does the agent get? RULING · code
Answered how

When an integrator passes MCP tools, list_tools converts every tool the server lists, with no filter, keeping the server's names; filtering is the integrator's job. The list is read once when list_tools is called, with cache_mode="use" passed to the client by default; what the client cache then serves is FastMCP's code, outside these checkouts. Tools from two separate adapters that share a name fall into A7; how a single ClientGroup names its members' tools was not read.

Held by

lc:libs/langchain_v1/langchain/mcp/adapter.py:221-242 (list_tools), conversion lc:libs/langchain_v1/langchain/mcp/tools.py:234-304.

A10 If an MCP address arrives as a string (from config, a request, or a model), can it launch a local script? RULE · code
Answered how

In MCPAdapter, a str target must parse as a URL with scheme http or https, or the constructor raises; a local server has to be named as a Path, transport or config object. Only str targets are checked: a fastmcp client the integrator built and passes in, or one given directly to as_langchain_tool, goes around this check.

Held by

lc:libs/langchain_v1/langchain/mcp/adapter.py:84-122 (_validate_url_target), called at lc:libs/langchain_v1/langchain/mcp/adapter.py:196-197.

A11 For a given set of tools and options, which steps does the agent's graph contain and how do they connect? RULE · code
Answered how

The graph is fixed at creation. A tools step exists only if there are local tools or a tool-wrapping middleware; then the model and tools steps form a loop. With no tools and no structured output it is one model call and then the end; with no tools but tool-based structured output the model step can loop back to itself. Middleware hooks add their own nodes around these.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1140-1148 (ToolNode condition), lc:libs/langchain_v1/langchain/agents/factory.py:1715-1811 (entry, loop and exit edges).

B. Context: what the model sees (11 seats)

B1 What standing instructions does the model get each step, and are they saved with the history? RULE · code
Answered how

LangChain adds no system prompt of its own. If the caller passes system_prompt, it is kept outside state and put in front of the conversation on every model call, so it is never saved into the history. A @dynamic_prompt middleware can replace it per call (S11). A system message the caller puts in messages is a different thing: that one is stored in state like any other message.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1050-1056 (string → SystemMessage; None → none), prepended lc:libs/langchain_v1/langchain/agents/factory.py:1503-1504, async lc:libs/langchain_v1/langchain/agents/factory.py:1562-1563; state gets only the model's output, lc:libs/langchain_v1/langchain/agents/factory.py:248-255.

B2 Does the model see the whole conversation every time, or does the agent cut or summarise it? RULING · code
Answered how

At defaults the model sees the whole conversation on every step: state["messages"] passes through, changed only by B4's repair, and nothing trims, summarises or counts it. factory.py has no token or size check. When the history outgrows the model's window the failure is left to the provider. Summarisation and context editing are opt-in middleware (S11).

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1522-1530 (request carries all of state["messages"]), sent at lc:libs/langchain_v1/langchain/agents/factory.py:1502-1506.

B3 How does the agent's history decide what to keep when messages are added, replaced or deleted? RULE · code
Answered how

Every update goes through the add_messages reducer. A message with no ID gets a fresh one; a new ID is appended; an ID that matches an earlier message silently replaces it in place; a RemoveMessage deletes by ID and raises if the ID is absent; a REMOVE_ALL_MESSAGES marker keeps only what follows it in that update. Identity is the ID alone, so a model reply reusing an earlier ID would overwrite that turn (read from the mechanism, not observed).

Held by

lg:libs/langgraph/langgraph/graph/message.py:190-247 (add_messages), bound as the messages reducer in lc:libs/langchain_v1/langchain/agents/middleware/types.py:352.

B4 If the model's previous tool call had broken or truncated arguments, what does it get told next turn? RULE · code
Answered how

On every model step the agent scans the history for invalid tool calls with an ID and no tool result, and adds an error ToolMessage saying the call "could not be executed - arguments were malformed or truncated", then writes the repaired history back. A reply with only invalid calls has an empty tool_calls, so routing ends the run first; the repair reaches the model only on a later invocation that carries the history (a checkpointer, or the caller resending it). If valid calls came alongside, the tools step runs and the repair lands on the next step of the same run. Calls without an ID are left as they are.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:676-707 (_invalid_tool_call_message, _patch_invalid_tool_calls), applied lc:libs/langchain_v1/langchain/agents/factory.py:1522, written back lc:libs/langchain_v1/langchain/agents/factory.py:249-254, lc:libs/langchain_v1/langchain/agents/factory.py:1540; exit before repair lc:libs/langchain_v1/langchain/agents/factory.py:2016-2019.

B5 Does the model get the full toolbox every time, or a selection? RULING · code
Answered how

At defaults the model is offered every registered local tool plus any provider dict tools on every step, with nothing filtered. Structured-output tools are added per call when a tool-based response format is in force. If there are no tools at all the model is bound without any. An LLM tool selector or a provider-side tool search can be added as middleware (S11).

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1154-1157 (default_tools), used for every request lc:libs/langchain_v1/langchain/agents/factory.py:1525; structured-output tools lc:libs/langchain_v1/langchain/agents/factory.py:1424-1440; bound lc:libs/langchain_v1/langchain/agents/factory.py:1484-1491.

B6 Is the model free to answer in plain text, or is it forced to call a tool? RULING · code
Answered how

At defaults each request carries tool_choice=None, so the agent forces nothing and the choice is left to the provider's own default (each provider's bind_tools not read). The one built-in exception is tool-based structured output, explicit or auto-detected: then every turn is bound with tool_choice="any", so the model must call some tool. Middleware can set tool_choice through request.override (S11; not read here).

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1529 (tool_choice=None), async lc:libs/langchain_v1/langchain/agents/factory.py:1588; passed through lc:libs/langchain_v1/langchain/agents/factory.py:1484-1490; forced lc:libs/langchain_v1/langchain/agents/factory.py:1474-1481.

B7 When the agent must return data in a fixed shape, how is that shape put in front of the model? RULE · code
Answered how

Only when response_format is set. With the provider strategy, the schema goes to the model as bind arguments from to_model_kwargs (a json_schema payload), and strict=True is also set for OpenAI-compatible models not on the Responses API. With the tool strategy, each schema becomes one extra tool, narrowed to the schemas in force for that call.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1443-1456 (provider strategy), lc:libs/langchain_v1/langchain/agents/factory.py:1428-1440 and lc:libs/langchain_v1/langchain/agents/factory.py:1474-1481 (tool strategy).

B8 When a tool returns data, what exactly does the model get to read, and is any of it cut? RULING · code
Answered how

Two layers convert a tool's return. Core keeps a string or a sequence of strings and typed content blocks, and turns anything else into JSON or, failing that, str(). ToolNode then keeps a string or a list of typed blocks and re-serialises everything else, so a list of plain strings reaches the model as one JSON string. The result goes in full: neither layer cuts by size at defaults. Context editing middleware can later clear old results (S11).

Held by

lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1475-1476, lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:317-344 (msg_content_output); first step lc:libs/core/langchain_core/tools/base.py:1390-1426 (_format_output), lc:libs/core/langchain_core/tools/base.py:1443-1468, lc:libs/core/langchain_core/tools/base.py:1471-1483.

B9 If the history contains a system instruction after the first message, does the model see it where it was, at the top, or not at all? RULING · code
Answered how

Anthropic only; other providers not read. After consecutive system messages are merged, a leading one goes in the system field. A later one is sent in place only if the model profile sets mid_conversation_system_messages and it follows a user turn, or an assistant turn ending in a server tool result, or another in-place system message. Otherwise it raises if a leading system message exists, and if none exists it is moved to the top with a warning, where it applies to the whole conversation. A caller's system entry at the very start of messages just merges with the agent's system_prompt; only a later one raises.

Held by

lc:libs/partners/anthropic/langchain_anthropic/chat_models.py:739-761 (_format_messages), merge lc:libs/partners/anthropic/langchain_anthropic/chat_models.py:372-387, turn test lc:libs/partners/anthropic/langchain_anthropic/chat_models.py:668-680, warning lc:libs/partners/anthropic/langchain_anthropic/chat_models.py:646-656.

B10 When the agent uses a tool from an MCP server, who wrote the name, description and argument schema the model reads? RULE · external
Answered how

Only when MCP tools are passed as tools. The server's name and description go to the model unchanged, and its input schema with one change: object arguments with no declared properties are marked open. Whoever runs the server therefore writes part of what the model reads on each step; LangChain does not check or bound the name or description, and a tool with no description gets an empty string.

Held by

the MCP server, copied at lc:libs/langchain_v1/langchain/mcp/tools.py:296-304; schema normalisation lc:libs/langchain_v1/langchain/mcp/tools.py:212-231.

B11 For a plain Python function given as a tool, where do its name, description and argument list come from? RULE · code
Answered how

A plain function is wrapped with the tool decorator's defaults. Its __name__ becomes the tool name, its whole docstring (dedented and stripped) the description the model reads, and its signature the argument schema. A function with no docstring is refused when the agent is created. BaseTool instances are used as given.

Held by

lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:788-789 (non-BaseTool → create_tool, i.e. lc:libs/core/langchain_core/tools/convert.py:77), lc:libs/core/langchain_core/tools/convert.py:316-329, lc:libs/core/langchain_core/tools/structured.py:257-293 (from_function); defaults infer_schema=True lc:libs/core/langchain_core/tools/convert.py:84, parse_docstring=False lc:libs/core/langchain_core/tools/convert.py:86.

C. Choosing the next action (7 seats)

C1 What does the agent do next: call tools (which ones, with what arguments, how many at once) or give its answer? RULING · llm
Answered how

The chat model alone decides whether to call tools, which, with what arguments and how many in one reply; at defaults LangChain sends tool_choice=None and forces nothing. If no tools were given at all the model is bound with none and can only answer. With a ToolStrategy response format a tool call is forced (C3).

Held by

the integrator's chat model, bound with the client-side and built-in tools at lc:libs/langchain_v1/langchain/agents/factory.py:1484-1491 (request built with tool_choice=None at lc:libs/langchain_v1/langchain/agents/factory.py:1529), invoked at lc:libs/langchain_v1/langchain/agents/factory.py:1506 (async lc:libs/langchain_v1/langchain/agents/factory.py:1565).

C2 After a model turn, does the run go to the tools, back to the model, or end? RULE · code
Answered how

With a tools node: a jump_to from middleware wins; no AI message, or a reply with no well-formed tool calls, ends the run; otherwise each unanswered non-structured call goes to the tools node as its own task; with none pending, a fresh structured response ends the run and anything else returns to the model. With no tools node the run ends after the model call, unless a ToolStrategy format is set (then it ends only on a fresh structured response and otherwise returns to the model) or after_model middleware holds the edge.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1991-2042 (_make_model_to_tools_edge.model_to_tools), wired at lc:libs/langchain_v1/langchain/agents/factory.py:1775-1786 when a tools node exists. With no tools node: lc:libs/langchain_v1/langchain/agents/factory.py:1787-1798 (_make_model_to_model_edge) under ToolStrategy, else the static edge at lc:libs/langchain_v1/langchain/agents/factory.py:1799-1801, else the after_model edge at lc:libs/langchain_v1/langchain/agents/factory.py:1803-1811.

C3 Must the model call a tool this turn? RULE · code
Answered how

At defaults nothing forces a tool call and the model may answer in plain text. When the effective format is ToolStrategy (set directly, or AutoStrategy on a model without provider structured output, lc:libs/langchain_v1/langchain/agents/factory.py:1409-1419), LangChain forces "any" and any request.tool_choice is ignored. Without structured output, wrap_model_call middleware may set one through request.tool_choice; the provider strategy does not pass it on.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1475 (tool_choice = "any" if structured_output_tools else request.tool_choice), reached only under ToolStrategy (lc:libs/langchain_v1/langchain/agents/factory.py:1458); the default request carries tool_choice=None (lc:libs/langchain_v1/langchain/agents/factory.py:1529).

C4 Are the model's raw tool-call arguments well-formed JSON, and what happens to ones that are not? RULE · code
Answered how

The provider integration parses the tool-call arguments. On ChatOpenAI's chat-completions path, arguments that are not valid JSON are set aside as an invalid tool call rather than raising, and empty or falsy arguments become {}. Other providers and OpenAI's Responses path were not traced here.

Held by

provider layer; for ChatOpenAI's chat-completions path lc:libs/partners/openai/langchain_openai/chat_models/base.py:241-248 calling parse_tool_call at lc:libs/core/langchain_core/output_parsers/openai_tools.py:26-77 (json.loads at lc:libs/core/langchain_core/output_parsers/openai_tools.py:62, strict defaulting to False); failures become invalid_tool_calls via make_invalid_tool_call (lc:libs/core/langchain_core/output_parsers/openai_tools.py:80-98).

C5 After the tools run, does the agent go back to the model or stop? RULE · code
Answered how

After the tools run the agent returns to the model, unless every client-side tool in the last reply is marked return_direct (the run ends on the tool output) or a structured-output tool message without status="error" is present (the run ends).

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:2072-2111 (_make_tools_to_model_edge.tools_to_model), wired at lc:libs/langchain_v1/langchain/agents/factory.py:1755-1767.

C6 If the model gives its structured answer and also calls other tools in the same turn, what happens? RULE · code
Answered how

Only with a ToolStrategy format. If one structured call parses, the other tools still run and the run then ends on the structured answer, so the model never sees their results. If it fails to parse and a retry applies, the other tools still run and the run ends with no structured response, so the retry message never reaches the model.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:2021-2032 (structured calls excluded from pending_tool_calls; the others are dispatched) with lc:libs/langchain_v1/langchain/agents/factory.py:2103-2104 (after tools, any structured-output ToolMessage not marked error ends the run); the structured call is parsed in the same model turn at lc:libs/langchain_v1/langchain/agents/factory.py:1307-1328.

C7 When a tool returns a Command, who decides where the agent goes next and what state changes? RULING · external
Answered how

Any tool may return a LangGraph Command that writes agent state or redirects the graph; the tool's own code decides. Under create_agent a dict update may set any state key and any goto passes, provided a message update still answers the tool's own call; a list-shaped update is refused, and a returned list must hold exactly one answer. A Command with no update, one aimed at the parent graph and one removing all messages skip the answer check. How a tool's goto combines with the tools conditional edge was not traced.

Held by

the tool's own code (integrator or third party); its return is accepted at lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1473-1474 and checked by _validate_tool_command at lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1537-1613; parent-graph Send gotos are merged at lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:903-928.

D. Gatekeeping: may this action run? (23 seats)

D1 Should this tool call, as the model wrote it, be allowed to run? RULING · code
Answered how

Nobody is asked by default. The edge after the model sends every pending client-side tool call straight to the tools node, and with middleware=() no wrapper stands in front of it, so each call runs as soon as the model returns. An integrator can add a stop with HumanInTheLoopMiddleware, a custom wrap_tool_call, or interrupt_before=["tools"].

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:2021-2032 (every pending tool call of the last AI message is sent to the tools node as Send("tools", [tool_call]), no check) and lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1061-1063 (with no wrap_tool_call configured the call executes directly). The ToolNode is built with no wrapper when middleware=(): lc:libs/langchain_v1/langchain/agents/factory.py:1094-1108,1140-1148.

D2 Should this request (messages, system prompt, tools) be allowed to go to the model provider as it is? RULING · code
Answered how

Nothing inspects the request at defaults: with no wrap_model_call handler the node builds the request, prepends any system message, and calls the model. Content checks exist only if an integrator passes middleware such as PIIMiddleware or a custom wrap_model_call.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1534-1536,1501-1506 (with no wrap_model_call handler the request is built and model_.invoke(messages) is called; no content check). Async twin lc:libs/langchain_v1/langchain/agents/factory.py:1594-1595,1565.

D3 May a tool's returned Command change the agent's state or where the graph goes next? RULE · code
Answered how

Under create_agent a tool's Command with a dict update is applied unless its messages lack a ToolMessage for this call; that is the only content check, so any other state key and any goto pass. A list-shaped update is always refused here. A Command with no dict or list update (goto alone, for example), one aimed at the parent graph, and one removing all messages skip the answer check. A returned list must hold exactly one terminating message.

Held by

lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1537-1613 (_validate_tool_command); matching-message check lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1582-1612; early returns lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1572-1573,1578-1580; list form lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1489-1535; parent-graph Send merge lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:907-918.

D4 When something inside the run asks a person a question and nothing can save the paused run, does the run stop, continue, or fail? RULE · code
Answered how

interrupt() raises without checking for a checkpointer, ToolNode lets that through, and the root loop suppresses it and returns normally with the question under __interrupt__. The asking tool never completes and a later Command(resume=...) raises, so the run cannot be continued. At defaults this is reached only if an integrator's tool calls interrupt() or an adapter MCP tool hits an elicitation (D22).

Held by

lg:libs/langgraph/langgraph/types.py:1037-1046 (interrupt() raises GraphInterrupt; no checkpointer check there), lg:libs/langgraph/langgraph/pregel/_loop.py:1519-1558 (the root graph suppresses the interrupt, emits it, saves output, returns normally), lg:libs/langgraph/langgraph/pregel/_loop.py:1020-1025 (a later Command(resume=...) raises RuntimeError("Cannot use Command(resume=...) without checkpointer")).

D5 Does the answer a person sends back match the shape the paused step asked for? RULE · code
Answered how

Only when the interrupt() call passed a Pydantic model, TypedDict or dataclass as response_schema; then the answer is validated before it is saved and a bad one raises. With no schema or a dict schema the value is used as it came. Of the callers in this part, HITL's per_call mode passes a schema; batched HITL and the MCP elicitation loop do not.

Held by

lg:libs/langgraph/langgraph/types.py:1014-1018,1026,1033 (_validate_resume runs only when response_schema is a Pydantic model, TypedDict or dataclass; a dict schema or none means the value is used as-is).

D6 May this object type, read from a saved checkpoint, be turned back into a live Python object? RULE · code
Answered how

Only once a checkpointer is passed. Then, on load, listed safe types always pass; unless LANGGRAPH_STRICT_MSGPACK is true or the saver's serializer was given its own allowlist, any other stored type is rebuilt too, with a logged warning and a serde event. In strict mode an unlisted type is blocked.

Held by

lg:libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py:129-136 (default allowed_msgpack_modules = True when strict mode is unset) and lg:libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py:581-631 (_check_allowed). Env read at import lg:libs/checkpoint/langgraph/checkpoint/serde/_msgpack.py:12. BaseCheckpointSaver default serde lg:libs/checkpoint/langgraph/checkpoint/base/__init__.py:232,239. langchain_core.load reviver lg:libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py:31,47,201.

D7 Should the run pause before (or after) this step because the integrator asked for a breakpoint there? RULE · code
Answered how

Only if node names are listed in interrupt_before or interrupt_after, at compile or per call. The loop then stops when a listed node is among the tasks and some channel has changed since the last interrupt, so a resume does not stop again on the same state. With create_agent, interrupt_before=["tools"] stops before any pending call of that step runs; there is no approve or reject vocabulary, and without a checkpointer it ends as D4 does.

Held by

lg:libs/langgraph/langgraph/pregel/_algo.py:156-186 (should_interrupt), called at lg:libs/langgraph/langgraph/pregel/_loop.py:677-681 (before) and lg:libs/langgraph/langgraph/pregel/_loop.py:768-774 (after). Lists come from compile (lc:libs/langchain_v1/langchain/agents/factory.py:1912-1913) or per call (lg:libs/langgraph/langgraph/pregel/main.py:2667-2668).

D8 Should this paused run go on as it is, go on with changed state, or be abandoned? RULING · human
Answered how

Whoever next calls the compiled graph decides: invoke(None, config) continues, update_state changes the state first, Command(resume=...) answers an interrupt(), and not calling leaves it paused. The code read here provides those calls and no reviewer interface. It does not check that the caller is a person, so an integrator's own code can hold this seat.

Held by

the integrator's operator or end user, calling the compiled graph: lg:libs/langgraph/langgraph/pregel/_loop.py:1020-1044 (resume mapping), lg:libs/langgraph/langgraph/pregel/_loop.py:941-945 (a None input counts as resuming), lg:libs/langgraph/langgraph/pregel/main.py:2613 (update_state), lg:libs/langgraph/langgraph/pregel/main.py:1615 (bulk_update_state).

D9 Does this tool call need a person's review before it runs, or is it waved through? RULING · code
Answered how

Only with HumanInTheLoopMiddleware passed. A call is held for review when its tool name has an entry in interrupt_on and any when predicate returns true; a tool with no entry, or with False, is waved through. A when predicate (integrator code) runs again on resume; if it flips to false the reviewer's rejection is skipped and the tool runs, which the docstring states.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:791-794 (batched) and lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:736-742 (per-call). Config resolution lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:580-598; waved through lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:503,848-850.

D10 Should this tool call run as written, run with changes, be refused, or be answered by the person instead? RULING · human
Answered how

Once a call is gated (D9), the run pauses and the reviewer's answer comes back through Command(resume=...): approve, edit, reject or respond, as the tool's config allows. This needs a checkpointer; the middleware does not check one is set. In batched mode decisions match calls by position, so an approval interface that reorders actions misapplies them. The library cannot tell whether a person or the integrator's code sent the answer.

Held by

the reviewer answering the interrupt raised at lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:816 (batched, one HITLRequest per model turn) or lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:750 (per_call, one ToolApprovalRequest per gated call). Mode chosen at lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:496,574-578.

D11 Is the decision the reviewer sent back allowed for this tool, and complete? RULE · code
Answered how

The decision type is always checked against the tool's allowed_decisions. In per_call mode LangGraph also validates the whole answer first: an edit keeps the tool name, and its args are checked when the tool has a Pydantic schema, not when it has a JSON schema. In the default batched mode an edit may name another tool and its args are not checked here.

Held by

batched: lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:637-661 (_process_decision) and lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:819-824 (one decision per gated call). Per-call: lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:236-264 (_decision_schema; name pinned lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:225, args lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:197-212), checked at lg:libs/langgraph/langgraph/types.py:1026,1033.

D12 When a reviewer edits or refuses a call, which call runs, and what message does the model get back? RULE · code
Answered how

A refused call stays in the AI message but gets a synthetic error ToolMessage, which drops it from the pending list, so it does not run and the model is told the user rejected it. A respond answer works the same way with the person's text and success status. An edited call runs with the reviewer's name and args while the model's own call stays in history, and the result carries a notice unless edit_notice=None. In batched mode an edit on a call with an empty-string id is lost and the model's original call runs.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:267-285 (_answer_message); batched edit stored lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:844-845, read back lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:876-885, applied lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:887-912 inside lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:1008-1011; notice lc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:914-978.

D14 Which messages are scanned for personal data before and after each model call? RULING · code
Answered how

With the middleware on and default flags, only the newest HumanMessage, before each model call. apply_to_tool_results adds the ToolMessages after the last AIMessage; apply_to_output adds the last AIMessage's content after the model call, and skips it when empty. Anything earlier is not looked at on that call, so older messages a caller supplies are never scanned, and tool-call arguments are never touched in state; only text leaves are scanned, so non-text content blocks pass.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/pii.py:750-767 (input: last HumanMessage), lc:libs/langchain_v1/langchain/agents/middleware/pii.py:770-793 (tool results), lc:libs/langchain_v1/langchain/agents/middleware/pii.py:840-868 (output: last AIMessage content). Flag defaults lc:libs/langchain_v1/langchain/agents/middleware/pii.py:585-587; text leaves only lc:libs/langchain_v1/langchain/agents/middleware/pii.py:685-717.

D16 Is personal data scrubbed from streamed text, tool inputs and outputs before a stream reader sees it? RULE · code
Answered how

Only for a reader of stream_events or astream_events at version="v3", and only when apply_to_output or apply_to_tool_results is on. Then text, reasoning, tool-call args, tool events and state snapshots are scrubbed before release, holding back the last 128 characters. invoke and plain stream do not run this transformer; there only the state hooks (D14) apply. Installed for either flag, it scrubs every channel alike, while tool-call args in state stay as written, so a stream reader and the tool can see different values.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/pii.py:50-511 (_PIIStreamTransformer), installed only when apply_to_output or apply_to_tool_results (lc:libs/langchain_v1/langchain/agents/middleware/pii.py:672-678); lookback lc:libs/langchain_v1/langchain/agents/middleware/pii.py:39; values lc:libs/langchain_v1/langchain/agents/middleware/pii.py:102-117, tools lc:libs/langchain_v1/langchain/agents/middleware/pii.py:154-189, deltas lc:libs/langchain_v1/langchain/agents/middleware/pii.py:322-452. Read only by the v3 paths lg:libs/langgraph/langgraph/pregel/main.py:3617-3619,3673-3675.

D17 What files, network and processes may a shell command the model wrote reach? RULING · code
Answered how

With ShellToolMiddleware on and no policy chosen, HostExecutionPolicy starts /bin/bash directly on the host and each command the model wrote goes to it unchecked, so files and network are limited only by what the process user can reach. The working folder (a temp folder unless workspace_root is set) is a starting point, not a limit, and CPU and memory limits are off unless set. DockerExecutionPolicy (no network by default) or CodexSandboxExecutionPolicy hand the limit to an outside sandbox.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/shell_tool.py:591-594 (default policy); lc:libs/langchain_v1/langchain/agents/middleware/shell_tool.py:636-637 (/bin/bash); lc:libs/langchain_v1/langchain/agents/middleware/_execution.py:92-146 (host spawn, docstring lc:libs/langchain_v1/langchain/agents/middleware/_execution.py:97-99); command sent unchecked lc:libs/langchain_v1/langchain/agents/middleware/shell_tool.py:829-835.

D18 Does the agent's shell see the host process's environment, including its secrets? RULE · code
Answered how

If the integrator passes no env, the session gets an empty environment under the host and Codex policies, although the docstring says it inherits the parent's. A passed env is used alone, not merged with the parent's. Under Docker the docker CLI gets the host environment and the container only the passed pairs.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/shell_tool.py:739 (self._environment or {}), from lc:libs/langchain_v1/langchain/agents/middleware/shell_tool.py:590,645-654 (env=None stays None); handed to the policy at lc:libs/langchain_v1/langchain/agents/middleware/shell_tool.py:156-158; Popen(env=...) lc:libs/langchain_v1/langchain/agents/middleware/_execution.py:35-48. Docker: lc:libs/langchain_v1/langchain/agents/middleware/_execution.py:322-326 and lc:libs/langchain_v1/langchain/agents/middleware/_execution.py:353-354.

D20 Is this path inside the folder the search tools are allowed to read? RULE · code
Answered how

With FilesystemFileSearchMiddleware passed, each tool resolves the requested path under root_path. A path containing .. or ~ anywhere is refused, a glob pattern that starts with / or has a .. part is refused, and every match is re-checked with symlinks resolved. What is refused or dropped comes back as an ordinary empty result, so the model cannot tell it from no match.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:268-290 (_validate_and_resolve_path), lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:25-41 (_is_within_root), lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:187-188 (glob patterns), per-match re-checks lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:195,334,372, os.walk(..., followlinks=False) lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:366.

D21 Which files inside the allowed folder are searched and can have their lines shown to the model (hidden files, ignored files, very large files)? RULING · external
Answered how

This is content search only. With ripgrep installed, ripgrep's own defaults decide: the command passes no hidden, ignore or size flag, so max_file_size_mb does not apply. With use_ripgrep=False, or when rg is missing or times out, Python reads every regular file under the cap (default 10 MB) inside the root that matches any include, hidden and ignored files included, skipping files it cannot decode or has no permission to read. So which lines reach the model depends on whether a binary is installed.

Held by

the rg binary's own defaults, invoked at lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:305-320 because use_ripgrep=True by default (lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:137, branch lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:246-252). Python fallback (code) lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:349-399, size cap lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:383-384, default lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:138; reached by lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:255-256,321-323.

D22 When an outside tool server pauses a call to ask for input (a form or a link to visit), who answers? RULING · human
Answered how

Only when tools come through MCPAdapter on a client with no handler of its own, connected to a modern-protocol server that returns input requests. Each round then becomes an interrupt(), carrying the server's own text and, in URL mode, its address, unfiltered, for whoever resumes the run; the library cannot tell a person from the integrator's code. With no checkpointer (the default) that pause ends the run as in D4. On resume the call is re-issued from the first round, so a server that works before asking repeats the work. A caller's own elicitation_handler answers instead, and a client with no handler is never asked.

Held by

the person answering the interrupt raised at lc:libs/langchain_v1/langchain/mcp/elicitation.py:360-365, payload built at lc:libs/langchain_v1/langchain/mcp/elicitation.py:157-172. Clients armed at lc:libs/langchain_v1/langchain/mcp/adapter.py:183-200 unless they carry a handler; loop gate lc:libs/langchain_v1/langchain/mcp/elicitation.py:252-263, wired lc:libs/langchain_v1/langchain/mcp/tools.py:281,290.

D23 Should the client accept this server request type (form, sampling, roots, continuation), and does the person's answer cover every question asked? RULE · code
Answered how

Only form and URL elicitations returned in an input-required round are answered. Sampling, roots, a continuation round with no questions, and a legacy server-initiated elicitation all raise instead. Every request needs an answer with action accept, decline or cancel, and a form answer's content is passed on without being checked against the requested_schema.

Held by

lc:libs/langchain_v1/langchain/mcp/elicitation.py:175-192 (non-elicitation requests refused), lc:libs/langchain_v1/langchain/mcp/elicitation.py:347-357 (continuation rounds refused), lc:libs/langchain_v1/langchain/mcp/elicitation.py:195-221 (_build_responses; content passed through lc:libs/langchain_v1/langchain/mcp/elicitation.py:219-220), lc:libs/langchain_v1/langchain/mcp/elicitation.py:228-243 (legacy path refused).

E. Execution and observation (13 seats)

E1 Which code runs a tool call, and what happens when the model names a tool that does not exist? RULE · code
Answered how

ToolNode looks the tool up by exact name. An unknown name gets an error ToolMessage listing the valid names and the run continues. With wrap_tool_call middleware, the unknown call reaches the middleware first and the check runs only if it calls through.

Held by

lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1049 (ToolNode._run_one, exact-name lookup); unknown name answered by _validate_tool_call at lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1302-1313 from _execute_tool_sync at lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:953-958 (async lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1113-1118).

E2 Are the model's arguments valid for the tool, and what does the model hear if not? RULE · code
Answered how

A tool with a Pydantic args_schema validates the model's arguments before running; a Pydantic v2 failure becomes an error ToolMessage with the argument errors, filtered of injected arguments, so the model can retry. Tools with a JSON-schema dict as args_schema (all MCP tools) skip this check. A Pydantic v1 schema failure is not caught as an argument error and stops the run.

Held by

lc:libs/core/langchain_core/tools/base.py:816-834 (BaseTool._parse_input, args_schema.model_validate), reached from _to_args_and_kwargs (lc:libs/core/langchain_core/tools/base.py:993) in run at lc:libs/core/langchain_core/tools/base.py:1091-1093; ValidationError wrapped by ToolNode at lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:967-979 (injected-arg errors filtered by _filter_validation_errors, lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:518-571).

E3 When a tool raises while running, does the model hear about it, or does the whole run stop? RULING · code
Answered how

create_agent builds ToolNode without handle_tool_errors, so the default handler applies: an argument error is told to the model, any other exception re-raises and stops the run. A tool that sets handle_tool_error turns its own ToolException into an error reply first, and MCP isError results do the same (E12).

Held by

lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:391-399 (_default_handle_tool_errors, the ToolNode default at lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:761), applied at lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1003-1023 (async lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1163-1183); ToolNode built with no error setting at lc:libs/langchain_v1/langchain/agents/factory.py:1141-1145.

E4 Which inputs does a tool receive that the model did not choose, and can the model forge them? RULE · code
Answered how

Tools can declare the agent's state, store, runtime or call id as inputs. ToolNode fills state, store and runtime just before the call and drops any model value for an annotated key; core overwrites an InjectedToolCallId with the real id. A tool that declares a store fails the run when none is configured, and create_agent configures none unless given one.

Held by

lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1349-1464 (ToolNode._inject_tool_args); model-supplied values for injected keys are stripped at lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1455-1463; state read from channels at execution time lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1332-1346; InjectedToolCallId filled in core at lc:libs/core/langchain_core/tools/base.py:821-833.

E5 Do several tool calls from one model turn run at the same time, and in what order do their results reach the model? RULE · code
Answered how

Each tool call from one reply becomes its own task in one Pregel step, submitted together, and their results are applied in the order the model listed them, not the order they finish. Sync runs share a thread pool, which may queue some. Async runs have no LangGraph limit unless the max_concurrency config key is set, though a tool with only a sync body still runs in a thread pool and may queue.

Held by

one Send("tools", [call]) per call at lc:libs/langchain_v1/langchain/agents/factory.py:2032; Pregel submits them together (lg:libs/langgraph/langgraph/pregel/_runner.py:258-275), thread pool from get_executor_for_config (lg:libs/langgraph/langgraph/pregel/_executor.py:58; async gate max_concurrency at lg:libs/langgraph/langgraph/pregel/_executor.py:143-145); results applied in task-path order at lg:libs/langgraph/langgraph/pregel/_algo.py:255.

E6 When one tool in a parallel batch fails, what happens to the others and to the run? RULING · code
Answered how

The first tool that fails aborts the run: tasks not yet started are cancelled and the exception is raised. Sync tools already running cannot be stopped and the executor waits for them on exit; async tools in flight are cancelled. Tools that finished are discarded unless a checkpointer keeps their writes.

Held by

lg:libs/langgraph/langgraph/pregel/_runner.py:618-641 (_should_stop_others; loop break at lg:libs/langgraph/langgraph/pregel/_runner.py:329-332, async lg:libs/langgraph/langgraph/pregel/_runner.py:540-543) and _panic_or_proceed at lg:libs/langgraph/langgraph/pregel/_runner.py:657-704 (cancels in-flight futures lg:libs/langgraph/langgraph/pregel/_runner.py:685-687, re-raises lg:libs/langgraph/langgraph/pregel/_runner.py:689-694); finished siblings' writes go to put_writes (lg:libs/langgraph/langgraph/pregel/_runner.py:605-615).

E7 Does the runtime retry a failed step (model call or tool batch)? RULING · code
Answered how

No. create_agent adds its nodes with no retry policy and the compiled graph's default is empty, so a failed model call or tool task raises at once. RetryPolicy's own defaults (3 attempts, 0.5 s, x2, jitter) apply only to graphs that pass one, and create_agent has no argument for it.

Held by

lg:libs/langgraph/langgraph/pregel/_retry.py:574-680 (run_with_retry, raises at once with no policy, lg:libs/langgraph/langgraph/pregel/_retry.py:645-646; async twin lg:libs/langgraph/langgraph/pregel/_retry.py:686); Pregel default retry_policy = () at lg:libs/langgraph/langgraph/pregel/main.py:748; create_agent adds both nodes without one (lc:libs/langchain_v1/langchain/agents/factory.py:1611, lc:libs/langchain_v1/langchain/agents/factory.py:1615).

E8 Is a failed or rate-limited model call retried, and how many times? RULING · external
Answered how

LangChain itself does not retry a model call at defaults; the provider SDK does. For the two providers read: ChatAnthropic passes max_retries=2, and ChatOpenAI passes nothing unless set, so the openai SDK's default applies. Which errors count as retryable is the vendor's choice, and rate_limiter is off unless set.

Held by

the provider SDK client, outside both repos: lc:libs/partners/openai/langchain_openai/chat_models/base.py:909 and lc:libs/partners/openai/langchain_openai/chat_models/base.py:1470-1471 (ChatOpenAI.max_retries, None, passed only when set); lc:libs/partners/anthropic/langchain_anthropic/chat_models.py:1414 and lc:libs/partners/anthropic/langchain_anthropic/chat_models.py:1661 (ChatAnthropic.max_retries = 2). No client-side throttle: rate_limiter defaults to None (lc:libs/core/langchain_core/language_models/chat_models.py:375, checked at lc:libs/core/langchain_core/language_models/chat_models.py:828-829).

E9 How is a tool's return value turned into what the model reads? RULE · code
Answered how

Core turns a tool's return value into a ToolMessage: strings and valid content blocks pass, anything else is stringified, with no length cap in this path. A content_and_artifact tool's artifact is set apart from the content. ToolNode then normalizes a returned ToolMessage's content.

Held by

lc:libs/core/langchain_core/tools/base.py:1390-1426 (_format_output; strings and content blocks pass, anything else stringified lc:libs/core/langchain_core/tools/base.py:1418-1419; content_and_artifact splits off an artifact, lc:libs/core/langchain_core/tools/base.py:1099-1112); ToolNode msg_content_output (lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:317-344) and _normalize_tool_response (lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1466-1487).

E10 What does the model hear about tool calls it sent with malformed arguments? RULE · code
Answered how

At the start of each model call, every malformed tool call with an id that has no reply yet gets a synthetic error ToolMessage ("could not be executed - arguments were malformed or truncated"). If the turn also had valid calls, the model reads it in the same run on the next call. If the turn had only malformed calls the router already ended the run (C2), so it is read only if a later run continues on that history.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:692-707 (_patch_invalid_tool_calls) with the text from _invalid_tool_call_message (lc:libs/langchain_v1/langchain/agents/factory.py:676-689), run at the start of each model call (lc:libs/langchain_v1/langchain/agents/factory.py:1522, lc:libs/langchain_v1/langchain/agents/factory.py:1581); the repaired history replaces state through _build_commands (lc:libs/langchain_v1/langchain/agents/factory.py:249-254).

E11 Is a tool's returned state update accepted? RULE · code
Answered how

A tool's Command that updates messages on the current graph must carry a ToolMessage for its own call id, and a list return must hold exactly one such answer; otherwise the run fails. Not checked: a Command with no update, one addressed to a parent graph, a remove-all update, or answers bound to a sibling call's id.

Held by

lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1537-1613 (_validate_tool_command) and lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1489-1535 (_validate_tool_command_list).

E12 When an MCP tool fails, does the model hear about it? RULING · code
Answered how

Only for tools converted with langchain.mcp.as_langchain_tool. A server's isError result reaches the model as an error ToolMessage with the server's content. A transport failure or unconvertible content raises and stops the run (E3).

Held by

lc:libs/langchain_v1/langchain/mcp/tools.py:174-175 (_convert_call_tool_result raising _MCPToolExecutionError), handled by the tool's own handle_tool_error=_handle_mcp_tool_error (lc:libs/langchain_v1/langchain/mcp/tools.py:104-117, wired lc:libs/langchain_v1/langchain/mcp/tools.py:303) through core's async path lc:libs/core/langchain_core/tools/base.py:1251-1253.

E13 What does the agent observe as the outcome of a tool call? RULING · external
Answered how

The tool's own code decides what comes back; LangChain cannot check it and passes it to the model after formatting (E9). With LLMToolEmulator middleware a model's invented result replaces the real tool (S11 entry, not read here).

Held by

the tool's own implementation (integrator or third-party code), invoked at lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:966 (async lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1126).

F. Limits and memory (12 seats)

F1 How many steps may one agent run take before it is stopped? RULING · code
Answered how

A run may take 9,999 agent steps. create_agent binds that limit, the graph's own start step comes first and sits outside it, and the next tick stops the run with a GraphRecursionError and no answer. That check reads only the count, so a run that finished exactly on step 9,999 raises too (lg #9163, closed not planned). A step is one round of the graph, in which all the tool calls from one reply run together: with tools and no middleware that is about 5,000 model calls. The caller can change the number per run with config={"recursion_limit": n}.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1899 (sets 9,999); enforced at lg:libs/langgraph/langgraph/pregel/_loop.py:1887 (self.stop = self.step + recursion_limit + 1) and lg:libs/langgraph/langgraph/pregel/_loop.py:658-662 (status = "out_of_steps")

F2 How long may one step (a model call or a tools batch) run before it is cut off? RULING · code
Answered how

There is no time limit on a step by default: step_timeout is None and create_agent neither sets it nor exposes it, so a slow model or tool call waits as long as the provider client or the tool allows. An integrator can only set it by assigning the attribute on the compiled graph.

Held by

lg:libs/langgraph/langgraph/pregel/main.py:733 (step_timeout: float | None = None), passed to the runner at lg:libs/langgraph/langgraph/pregel/main.py:3062; create_agent never sets it

F3 What happens when the conversation grows past what the model can read in one call? RULING · code
Answered how

Nothing trims the conversation by default: every model call sends the whole message list. What happens when it no longer fits is the provider's; where the provider raises, the run fails, unless summarization or context-editing middleware was added.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1502-1506 (the whole request.messages list, with the system message prepended, is sent every call; nothing trims)

F4 When should older conversation be summarized away? RULING · code
Answered how

Only if the integrator adds summarization middleware and gives it a trigger (a token count, a message count, or a share of the model's context window, alone or combined). Code checks before each model call; with no trigger it never summarizes.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:704-744 (_should_summarize), called from before_model at lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:503-505. Present only with SummarizationMiddleware in middleware=

F5 What should the summary that replaces older conversation say? RULING · llm
Answered how

When summarization is on and triggers, a second model call writes the summary from, by default, at most the last 4,000 tokens of the older messages; whatever it returns replaces them, unchecked, as a user message. If that window holds no human message the model is not called and a fixed placeholder replaces them.

Held by

the summary model, lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:944-952 (self._summary_model.invoke(prompt); the model is the middleware's model argument, up to 3 attempts via with_retry at lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:436); replaced by a custom summarizer at lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:939-942 / lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:465-469

F6 Which recent messages are kept word for word when older ones are summarized? RULING · code
Answered how

With summarization on, the most recent 20 messages are kept word for word by default, and the cut moves back so a tool result stays with the AI message that called it, which can keep a few more. With 20 messages or fewer nothing is summarized.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:746-756 (_determine_cutoff_index; default keep=("messages", 20) at lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:293, lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:115) and lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:868-917 (_find_safe_cutoff, _find_safe_cutoff_point: never splits an AI tool call from its results; moves the cut back to include the calling AI message)

F7 Which old tool results are blanked out of what the model sees? RULING · code
Answered how

Only with context-editing middleware, whose default edit runs before each model call: once the conversation passes 100,000 tokens (approximate count), all but the 3 most recent tool results are replaced with "[cleared]" in what the model is sent. The saved history keeps them.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/context_editing.py:79-152 (ClearToolUsesEdit.apply: trigger 100,000 tokens lc:libs/langchain_v1/langchain/agents/middleware/context_editing.py:61, keep last 3 tool results lc:libs/langchain_v1/langchain/agents/middleware/context_editing.py:67, placeholder "[cleared]" lc:libs/langchain_v1/langchain/agents/middleware/context_editing.py:35); applied per call to a COPY of the request at lc:libs/langchain_v1/langchain/agents/middleware/context_editing.py:265-274 (state is not changed)

F8 How many model calls may one run, or one conversation thread, make? RULING · code
Answered how

Unlimited by default (only the step limit in F1 applies). With model-call-limit middleware, code compares the counts before each model call and, by default, ends the run with a "limits exceeded" message once a limit is reached; exit_behavior="error" raises instead. A thread limit counts across runs only with a checkpointer; without one it resets each run.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/model_call_limit.py:186-210 (before_model compares counts to thread_limit/run_limit); counts incremented in after_model lc:libs/langchain_v1/langchain/agents/middleware/model_call_limit.py:248-251

F9 How many tool calls may a run or thread make, and what happens to calls over the limit? RULING · code
Answered how

Unlimited by default. With tool-call-limit middleware, after each model call code lets calls through up to the limit and, by default, answers the rest with an error message to the model while the run continues; the integrator can choose to end the run or raise instead. A thread limit counts across runs only with a checkpointer; without one it resets each run.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/tool_call_limit.py:327-471 (after_model; per-call split at lc:libs/langchain_v1/langchain/agents/middleware/tool_call_limit.py:294-323; optional per-tool filter tool_name)

F10 Does a new run pick up where an earlier conversation left off? RULE · code
Answered how

No by default: every run starts from only the messages passed in. With a checkpointer, the caller's checkpoint coordinates (normally thread_id) decide which saved conversation the run continues, and the full message history is restored.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:833 (checkpointer: Checkpointer | None = None) passed to graph.compile at lc:libs/langchain_v1/langchain/agents/factory.py:1910; resolved at lg:libs/langgraph/langgraph/pregel/main.py:850-861; with a checkpointer the caller's thread_id picks the history (lg:libs/langgraph/langgraph/pregel/main.py:2677-2682 refuses an empty configurable)

F11 When is a step's progress saved so a crash cannot lose it? RULING · code
Answered how

Only with a checkpointer. At the default "async", each step is saved in the background while the next step starts, so a crash can lose the latest step; durability="sync" makes the loop wait for each save, and "exit" saves only when the run ends.

Held by

lg:libs/langgraph/langgraph/pregel/main.py:2691-2692 (durability defaults to "async"); the loop waits for the save only when durability_ == "sync" (lg:libs/langgraph/langgraph/pregel/main.py:3080-3081); "exit" skips step saves (lg:libs/langgraph/langgraph/pregel/_loop.py:1289-1291)

F12 Is a structured answer from an earlier turn carried into the next? RULE · code
Answered how

No, when a response_format is configured and the model node runs: each model call that produces no structured answer clears the old one, so a new turn cannot end early on it. A middleware jump that skips the model call would leave the old value in place.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:257-260 (_build_commands writes structured_response: None on every model call that produced none, when a response_format is configured)

G. Judging the result (8 seats)

G1 Is the agent finished with the task? RULING · llm
Answered how

The model decides: with tools in play, the run ends the first time it replies without asking for a tool, and that reply is the answer, unchecked. With no tools and no middleware the first reply ends the run; with structured output, G4 and G6 add their own exits.

Held by

the agent's chat model, by replying without tool calls; read at lc:libs/langchain_v1/langchain/agents/factory.py:2016-2019 (if len(last_ai_message.tool_calls) == 0: return end_destination); with no tools, the model→end edge is unconditional (lc:libs/langchain_v1/langchain/agents/factory.py:1799-1801)

G2 Does a tool's result end the run without the model reading it? RULE · code
Answered how

When every client-side tool in that batch is marked return_direct by the integrator, the run ends on the tool output and the model does not see it; the tool's status is not looked at. The other case is G8: a structured answer in the same reply ends the run after the other tools run, and the model does not read their results.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:2086-2100 (ends when every executed client-side tool in the batch has return_direct=True); exit edge added only if some tool has it or structured output is configured (lc:libs/langchain_v1/langchain/agents/factory.py:1749-1753)

G3 Does the structured answer have the shape the integrator asked for? RULE · code
Answered how

Code checks the answer against the integrator's Pydantic model, dataclass or TypedDict. A plain JSON-schema dict is passed through without checking, though a native-format answer must still parse as JSON.

Held by

lc:libs/langchain_v1/langchain/agents/structured_output.py:78-104 (_parse_with_schema: Pydantic TypeAdapter.validate_python), called from OutputToolBinding.parse (lc:libs/langchain_v1/langchain/agents/structured_output.py:358-370) and ProviderStrategyBinding.parse (lc:libs/langchain_v1/langchain/agents/structured_output.py:405-431, after json.loads at lc:libs/langchain_v1/langchain/agents/structured_output.py:421)

G4 When a structured answer fails the check, does the model get another try, and how many? RULING · code
Answered how

With tool-call structured output and the default handle_errors=True, the error is fed back to the model and it is called again, with no limit short of the step cap in F1. The exception is a failure in a message that also held ordinary tool calls: the run ends instead (G8). Native (provider) structured output is not retried (G6).

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:646-673 (_handle_structured_output_error), applied at lc:libs/langchain_v1/langchain/agents/factory.py:1329-1346 and lc:libs/langchain_v1/langchain/agents/factory.py:1286-1305; default handle_errors=True at lc:libs/langchain_v1/langchain/agents/structured_output.py:243; the retry loop is the model→model edge (lc:libs/langchain_v1/langchain/agents/factory.py:2061-2067) or model→model via lc:libs/langchain_v1/langchain/agents/factory.py:2038-2040

G5 Should the structured answer be requested as a tool call or through the provider's native structured-output feature? RULING · code
Answered how

Only when the integrator passes a bare schema or an AutoStrategy; an explicit ToolStrategy or ProviderStrategy is kept as given. Code then picks on every model call: the provider's native format if the model's profile or a known model-name pattern says it is supported, otherwise a forced tool call.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1409-1419 (AutoStrategy resolution on every model call) via _supports_provider_strategy lc:libs/langchain_v1/langchain/agents/factory.py:578-623: model profile flag structured_output (lc:libs/langchain_v1/langchain/agents/factory.py:601-614, with a Gemini<3 + tools exclusion), else the name regex list FALLBACK_MODELS_WITH_STRUCTURED_OUTPUT (lc:libs/langchain_v1/langchain/agents/factory.py:182-203)

G6 When the provider's native structured answer fails to parse, is the model asked again? RULE · code
Answered how

No: a native structured answer that is not valid JSON or does not match the schema raises straight away. The run fails, unless a middleware around the model call catches it (fallback middleware does, H4).

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1262-1269 (raises, with no call to _handle_structured_output_error)

G7 When the model returns several structured answers at once, which one counts? RULE · code
Answered how

None of them: code rejects the batch, and at the default the model is told it returned several when one was expected and is asked again. If the same message also held ordinary tool calls, those run and the run ends instead (same path as G8).

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:1286-1305 (none counts: MultipleStructuredOutputsError, then the G4 retry policy)

G8 Should the run end when the structured answer arrives in the same message as other tool calls? RULING · code
Answered how

Yes: the other tools still run, then the run ends without the model seeing their results. If the structured answer was invalid, the run still ends, with no answer and no error.

Held by

lc:libs/langchain_v1/langchain/agents/factory.py:2021-2032 (the other calls still run), then lc:libs/langchain_v1/langchain/agents/factory.py:2102-2104 in tools_to_model (ends if any structured-output ToolMessage has status != "error")

H. Delegation (5 seats)

H1 Should part of the work be handed to another agent? RULING · llm
Answered how

There is no built-in hand-off. An agent delegates only if the integrator gives it a tool that runs another agent, and then the model decides when to call that tool, like any other.

Held by

the agent's chat model choosing a tool that wraps another agent; dispatched like any tool call at lc:libs/langchain_v1/langchain/agents/factory.py:2031-2032 (Send("tools", [tool_call]))

H2 How many steps may an agent that is called from inside a tool take? RULING · code
Answered how

Each agent built with create_agent carries its own bound limit of 9,999, which overrides whatever limit the parent run inherited, so an agent called from a tool does not draw on its parent's remaining steps. A tool that passes its own recursion_limit at invoke overrides that.

Held by

the child's own bound limit, lc:libs/langchain_v1/langchain/agents/factory.py:1899, which overrides the inherited (ambient) config in lg:libs/langgraph/langgraph/_internal/_config.py:351-358 then lg:libs/langgraph/langgraph/_internal/_config.py:382-422 (later configs overwrite non-special keys, recursion_limit by the final else) via ensure_config(self.config, config) at lg:libs/langgraph/langgraph/pregel/main.py:2839

H3 Does an agent called from inside a tool share its parent's saved conversation memory? RULE · code
Answered how

Same saver, not the same history, by default: if the parent has a checkpointer, an agent called from a tool saves into it under the tool task's namespace, without needing a thread key. checkpointer=False on the child opts out, and a tool that passes the child its own thread_id drops the lent saver, leaving only the child's own.

Held by

lg:libs/langgraph/langgraph/pregel/main.py:850-858 (_resolve_checkpointer: "none for checkpointer=False, else the one a parent lends a subgraph through the config, else this graph's own", docstring lg:libs/langgraph/langgraph/pregel/main.py:847-849); the lent saver reaches a tool through the task config (lg:libs/langgraph/langgraph/pregel/_algo.py:736-739) and the ambient config (lg:libs/langgraph/langgraph/_internal/_config.py:351-358); an explicit coordinate drops it (lg:libs/langgraph/langgraph/_internal/_config.py:369-381)

H4 If the model call fails, should a different model answer instead? RULING · code
Answered how

Not by default: a failed model call fails the run. With fallback middleware, any ordinary exception (including a structured-output parse error raised inside the call) moves to the next listed model, in order, once each; graph control signals such as interrupts pass straight through.

Held by

lc:libs/langchain_v1/langchain/agents/middleware/model_fallback.py:390-430 (tries the primary, then each fallback in list order on ANY Exception except GraphBubbleUp at lc:libs/langchain_v1/langchain/agents/middleware/model_fallback.py:411-414, lc:libs/langchain_v1/langchain/agents/middleware/model_fallback.py:424-427; models built with init_chat_model at lc:libs/langchain_v1/langchain/agents/middleware/model_fallback.py:386; provider cache settings stripped per attempt lc:libs/langchain_v1/langchain/agents/middleware/model_fallback.py:113-166)

H5 When the agent calls a tool served by an outside MCP server, who does the work, and is the server's success/error report believed? RULING · external
Answered how

When the integrator adds MCP tools, the outside server does the work and decides what counts as an error. A result flagged as an error reaches the model as a failed tool message with the server's own text; any other result is passed on as success, unchecked.

Held by

the remote MCP server, called at lc:libs/langchain_v1/langchain/mcp/tools.py:283-294; its is_error flag is taken at face value at lc:libs/langchain_v1/langchain/mcp/tools.py:174-175 (error → ToolMessage(status="error") with the server's own text, lc:libs/langchain_v1/langchain/mcp/tools.py:104-117); success content is converted and passed to the model as-is (lc:libs/langchain_v1/langchain/mcp/tools.py:120-165, lc:libs/langchain_v1/langchain/mcp/tools.py:172)

I. Learning and feedback (2 seats)

I1 What is written to long-term memory that outlives the conversation, and who decides? RULING · external
Answered how

Nothing by default. With a store, integrator code writes to it: a tool, with arguments the model chooses, or a middleware, which also receives the runtime. The agent loop itself neither saves nor recalls anything.

Held by

the integrator's tool code, given the store by injection at lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1441-1449 (raises if a tool asks for it and none was compiled in); create_agent only passes store through (lc:libs/langchain_v1/langchain/agents/factory.py:834, lc:libs/langchain_v1/langchain/agents/factory.py:1911) and never reads or writes it itself

I2 What does the agent assume a model can do (context size, native structured output)? RULE · code
Answered how

From a table shipped with each provider package, looked up by model name when the model object is built, unless the integrator passes profile=; nothing learned from runs updates it. A missing entry reads as unknown, and failures loading it are swallowed.

Held by

static profile tables shipped in each provider package, e.g. lc:libs/partners/openai/langchain_openai/chat_models/base.py:198-200 (_MODEL_PROFILES.get(model_name)) and lc:libs/partners/openai/langchain_openai/chat_models/base.py:1547-1548; loaded at model construction by lc:libs/core/langchain_core/language_models/chat_models.py:459-473 (errors suppressed); consumed by G5 (lc:libs/langchain_v1/langchain/agents/factory.py:601-604) and by summarization fraction limits (lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:807-817)

How this map was made

Four readers each took a part of the agent and wrote seat cards from the source, and four more rewrote each answer in a short form that describes the default. The map's author then re-read every seat against the cited lines and corrected what the source did not support. Reviews by a model from another family (read-only, at the same commits) checked the answers against the code, and each of their findings was checked against the code before anything changed. A checker confirms that each seat's recorded holder line exists at the pinned commits and carries its anchor text. Nothing on this page was produced by running their code.