LangChain: who decides what
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 · holder | Seats |
|---|---|
| RULE · code | 46 |
| RULING · code | 32 |
| RULING · external | 6 |
| RULING · llm | 4 |
| RULING · human | 3 |
| RULE · external | 1 |
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
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_agentpasses nomodel_provider, so at defaults only the string decides.- Held by
lc:libs/langchain_v1/langchain/chat_models/base.py:623-651(_parse_model), registrylc:libs/langchain_v1/langchain/chat_models/base.py:56-99, class importlc:libs/langchain_v1/langchain/chat_models/base.py:153-193; called fromlc: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,chatgptandtext-davincimean OpenAI;claudemeans Anthropic's own API;geminimeans 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, sogpt-oss-20bgoes to OpenAI ando4-minimatches nothing.- Held by
lc:libs/langchain_v1/langchain/chat_models/base.py:547-620(_attempt_infer_model_provider, orderedstartswithtable); called atlc: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 thelangsmith:model prefix, can send calls through LangSmith's gateway instead; which address and key are then used depends on what else is set. An explicitapi_keyalways wins.- Held by
lc:libs/core/langchain_core/utils/_gateway.py:106-179(_resolve_gateway_config), switchlc:libs/core/langchain_core/utils/_gateway.py:74-103; applied inlc: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:prefixlc: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_callmiddleware can swap it for a call (S11). Withcreate_agent(""),init_chat_modelreturns a_ConfigurableModelthat reads model and provider from each call'sconfig["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 atlc:libs/langchain_v1/langchain/agents/factory.py:1047-1048, executed atlc:libs/langchain_v1/langchain/agents/factory.py:1501andlc: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
STARTchannel, 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 ismessagesalone (AgentStateminus its input-omitted fields; astate_schemaor middleware schema adds more), and any other key is dropped with no warning.Nonewith nothing to resume is refused, and a value of a type the writer cannot read raises. A dict with nomessageskey is not refused at intake (the model node then readsstate["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, somap_inputtakes its string branchlg:libs/langgraph/langgraph/pregel/_io.py:88-89), input schemalc:libs/langchain_v1/langchain/agents/factory.py:489-523, empty inputlg: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 readingroleand thentype.systemanddeveloperbecome system messages,assistantan AI message with its tool calls,toola tool result, andremovea 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 throughlg:libs/langgraph/langgraph/graph/message.py:197-204(convert_to_messagesinsideadd_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), orderlc:libs/langchain_v1/langchain/agents/factory.py:1136, offered to the modellc: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
toolsthat is a plain dict is set aside as a provider built-in: it goes to the model'sbind_toolsas 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 vialc:libs/langchain_v1/langchain/agents/factory.py:1155-1157, skipped by the unknown-tool checklc: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_toolsconverts every tool the server lists, with no filter, keeping the server's names; filtering is the integrator's job. The list is read once whenlist_toolsis called, withcache_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 singleClientGroupnames its members' tools was not read.- Held by
lc:libs/langchain_v1/langchain/mcp/adapter.py:221-242(list_tools), conversionlc: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, astrtarget must parse as a URL with schemehttporhttps, or the constructor raises; a local server has to be named as aPath, transport or config object. Onlystrtargets are checked: afastmcpclient the integrator built and passes in, or one given directly toas_langchain_tool, goes around this check.- Held by
lc:libs/langchain_v1/langchain/mcp/adapter.py:84-122(_validate_url_target), called atlc: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(ToolNodecondition),lc:libs/langchain_v1/langchain/agents/factory.py:1715-1811(entry, loop and exit edges).
B. Context: what the model sees
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_promptmiddleware can replace it per call (S11). A system message the caller puts inmessagesis 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), prependedlc:libs/langchain_v1/langchain/agents/factory.py:1503-1504, asynclc: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.pyhas 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 ofstate["messages"]), sent atlc: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_messagesreducer. 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; aRemoveMessagedeletes by ID and raises if the ID is absent; aREMOVE_ALL_MESSAGESmarker 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 themessagesreducer inlc: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
ToolMessagesaying 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 emptytool_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), appliedlc:libs/langchain_v1/langchain/agents/factory.py:1522, written backlc:libs/langchain_v1/langchain/agents/factory.py:249-254,lc:libs/langchain_v1/langchain/agents/factory.py:1540; exit before repairlc: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 requestlc:libs/langchain_v1/langchain/agents/factory.py:1525; structured-output toolslc:libs/langchain_v1/langchain/agents/factory.py:1424-1440; boundlc: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'sbind_toolsnot read). The one built-in exception is tool-based structured output, explicit or auto-detected: then every turn is bound withtool_choice="any", so the model must call some tool. Middleware can settool_choicethroughrequest.override(S11; not read here).- Held by
lc:libs/langchain_v1/langchain/agents/factory.py:1529(tool_choice=None), asynclc:libs/langchain_v1/langchain/agents/factory.py:1588; passed throughlc:libs/langchain_v1/langchain/agents/factory.py:1484-1490; forcedlc: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_formatis set. With the provider strategy, the schema goes to the model as bind arguments fromto_model_kwargs(ajson_schemapayload), andstrict=Trueis 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-1440andlc: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 steplc: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
systemfield. A later one is sent in place only if the model profile setsmid_conversation_system_messagesand 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'ssystementry at the very start ofmessagesjust merges with the agent'ssystem_prompt; only a later one raises.- Held by
lc:libs/partners/anthropic/langchain_anthropic/chat_models.py:739-761(_format_messages), mergelc:libs/partners/anthropic/langchain_anthropic/chat_models.py:372-387, turn testlc:libs/partners/anthropic/langchain_anthropic/chat_models.py:668-680, warninglc: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 normalisationlc: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
tooldecorator'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.BaseToolinstances 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); defaultsinfer_schema=Truelc:libs/core/langchain_core/tools/convert.py:84,parse_docstring=Falselc:libs/core/langchain_core/tools/convert.py:86.
C. Choosing the next action
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=Noneand forces nothing. If no tools were given at all the model is bound with none and can only answer. With aToolStrategyresponse 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 withtool_choice=Noneatlc:libs/langchain_v1/langchain/agents/factory.py:1529), invoked atlc:libs/langchain_v1/langchain/agents/factory.py:1506(asynclc: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_tofrom 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 aToolStrategyformat is set (then it ends only on a fresh structured response and otherwise returns to the model) orafter_modelmiddleware holds the edge.- Held by
lc:libs/langchain_v1/langchain/agents/factory.py:1991-2042(_make_model_to_tools_edge.model_to_tools), wired atlc:libs/langchain_v1/langchain/agents/factory.py:1775-1786when a tools node exists. With no tools node:lc:libs/langchain_v1/langchain/agents/factory.py:1787-1798(_make_model_to_model_edge) underToolStrategy, else the static edge atlc:libs/langchain_v1/langchain/agents/factory.py:1799-1801, else theafter_modeledge atlc: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, orAutoStrategyon a model without provider structured output,lc:libs/langchain_v1/langchain/agents/factory.py:1409-1419), LangChain forces"any"and anyrequest.tool_choiceis ignored. Without structured output,wrap_model_callmiddleware may set one throughrequest.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 underToolStrategy(lc:libs/langchain_v1/langchain/agents/factory.py:1458); the default request carriestool_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 pathlc:libs/partners/openai/langchain_openai/chat_models/base.py:241-248callingparse_tool_callatlc:libs/core/langchain_core/output_parsers/openai_tools.py:26-77(json.loadsatlc:libs/core/langchain_core/output_parsers/openai_tools.py:62,strictdefaulting toFalse); failures becomeinvalid_tool_callsviamake_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 withoutstatus="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 atlc: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
ToolStrategyformat. 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 frompending_tool_calls; the others are dispatched) withlc: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 atlc: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
Commandthat writes agent state or redirects the graph; the tool's own code decides. Undercreate_agenta dict update may set any state key and anygotopasses, 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. ACommandwith noupdate, one aimed at the parent graph and one removing all messages skip the answer check. How a tool'sgotocombines with thetoolsconditional 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-1474and checked by_validate_tool_commandatlg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1537-1613; parent-graphSendgotos are merged atlg:libs/prebuilt/langgraph/prebuilt/tool_node.py:903-928.
D. Gatekeeping: may this action run?
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
toolsnode, and withmiddleware=()no wrapper stands in front of it, so each call runs as soon as the model returns. An integrator can add a stop withHumanInTheLoopMiddleware, a customwrap_tool_call, orinterrupt_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 thetoolsnode asSend("tools", [tool_call]), no check) andlg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1061-1063(with nowrap_tool_callconfigured the call executes directly). TheToolNodeis built with no wrapper whenmiddleware=():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_callhandler the node builds the request, prepends any system message, and calls the model. Content checks exist only if an integrator passes middleware such asPIIMiddlewareor a customwrap_model_call.- Held by
lc:libs/langchain_v1/langchain/agents/factory.py:1534-1536,1501-1506(with nowrap_model_callhandler the request is built andmodel_.invoke(messages)is called; no content check). Async twinlc: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_agenta tool'sCommandwith a dict update is applied unless its messages lack aToolMessagefor this call; that is the only content check, so any other state key and anygotopass. A list-shaped update is always refused here. ACommandwith no dict or list update (gotoalone, 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 checklg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1582-1612; early returnslg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1572-1573,1578-1580; list formlg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1489-1535; parent-graphSendmergelg: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,ToolNodelets that through, and the root loop suppresses it and returns normally with the question under__interrupt__. The asking tool never completes and a laterCommand(resume=...)raises, so the run cannot be continued. At defaults this is reached only if an integrator's tool callsinterrupt()or an adapter MCP tool hits an elicitation (D22).- Held by
lg:libs/langgraph/langgraph/types.py:1037-1046(interrupt()raisesGraphInterrupt; 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 laterCommand(resume=...)raisesRuntimeError("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 asresponse_schema; then the answer is validated before it is saved and a bad one raises. With no schema or adictschema the value is used as it came. Of the callers in this part, HITL'sper_callmode 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_resumeruns only whenresponse_schemais a Pydantic model, TypedDict or dataclass; adictschema 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_MSGPACKis 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(defaultallowed_msgpack_modules = Truewhen strict mode is unset) andlg:libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py:581-631(_check_allowed). Env read at importlg:libs/checkpoint/langgraph/checkpoint/serde/_msgpack.py:12.BaseCheckpointSaverdefaultserdelg:libs/checkpoint/langgraph/checkpoint/base/__init__.py:232,239.langchain_core.loadreviverlg: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_beforeorinterrupt_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. Withcreate_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 atlg:libs/langgraph/langgraph/pregel/_loop.py:677-681(before) andlg: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_statechanges the state first,Command(resume=...)answers aninterrupt(), 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(aNoneinput 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
HumanInTheLoopMiddlewarepassed. A call is held for review when its tool name has an entry ininterrupt_onand anywhenpredicate returns true; a tool with no entry, or withFalse, is waved through. Awhenpredicate (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) andlc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:736-742(per-call). Config resolutionlc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:580-598; waved throughlc: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, oneHITLRequestper model turn) orlc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:750(per_call, oneToolApprovalRequestper gated call). Mode chosen atlc: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. Inper_callmode 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 defaultbatchedmode 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) andlc: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 pinnedlc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:225, argslc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:197-212), checked atlg: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. Arespondanswer 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 unlessedit_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 storedlc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:844-845, read backlc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:876-885, appliedlc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:887-912insidelc:libs/langchain_v1/langchain/agents/middleware/human_in_the_loop.py:1008-1011; noticelc: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_resultsadds theToolMessages after the lastAIMessage;apply_to_outputadds the lastAIMessage'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: lastHumanMessage),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: lastAIMessagecontent). Flag defaultslc:libs/langchain_v1/langchain/agents/middleware/pii.py:585-587; text leaves onlylc: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_eventsorastream_eventsatversion="v3", and only whenapply_to_outputorapply_to_tool_resultsis on. Then text, reasoning, tool-call args, tool events and state snapshots are scrubbed before release, holding back the last 128 characters.invokeand plainstreamdo 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 whenapply_to_output or apply_to_tool_results(lc:libs/langchain_v1/langchain/agents/middleware/pii.py:672-678); lookbacklc:libs/langchain_v1/langchain/agents/middleware/pii.py:39; valueslc:libs/langchain_v1/langchain/agents/middleware/pii.py:102-117, toolslc:libs/langchain_v1/langchain/agents/middleware/pii.py:154-189, deltaslc:libs/langchain_v1/langchain/agents/middleware/pii.py:322-452. Read only by the v3 pathslg: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
ShellToolMiddlewareon and no policy chosen,HostExecutionPolicystarts/bin/bashdirectly 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 unlessworkspace_rootis set) is a starting point, not a limit, and CPU and memory limits are off unless set.DockerExecutionPolicy(no network by default) orCodexSandboxExecutionPolicyhand 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, docstringlc:libs/langchain_v1/langchain/agents/middleware/_execution.py:97-99); command sent uncheckedlc: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 passedenvis 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 {}), fromlc:libs/langchain_v1/langchain/agents/middleware/shell_tool.py:590,645-654(env=NonestaysNone); handed to the policy atlc: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-326andlc: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
FilesystemFileSearchMiddlewarepassed, each tool resolves the requested path underroot_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-checkslc: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_mbdoes not apply. Withuse_ripgrep=False, or whenrgis missing or times out, Python reads every regular file under the cap (default 10 MB) inside the root that matches anyinclude, 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
rgbinary's own defaults, invoked atlc:libs/langchain_v1/langchain/agents/middleware/file_search.py:305-320becauseuse_ripgrep=Trueby default (lc:libs/langchain_v1/langchain/agents/middleware/file_search.py:137, branchlc: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 caplc:libs/langchain_v1/langchain/agents/middleware/file_search.py:383-384, defaultlc:libs/langchain_v1/langchain/agents/middleware/file_search.py:138; reached bylc: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
MCPAdapteron a client with no handler of its own, connected to a modern-protocol server that returns input requests. Each round then becomes aninterrupt(), 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 ownelicitation_handleranswers 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 atlc:libs/langchain_v1/langchain/mcp/elicitation.py:157-172. Clients armed atlc:libs/langchain_v1/langchain/mcp/adapter.py:183-200unless they carry a handler; loop gatelc:libs/langchain_v1/langchain/mcp/elicitation.py:252-263, wiredlc: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,declineorcancel, and a form answer'scontentis passed on without being checked against therequested_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 throughlc: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
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_callmiddleware, 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_callatlg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1302-1313from_execute_tool_syncatlg:libs/prebuilt/langgraph/prebuilt/tool_node.py:953-958(asynclg: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_schemavalidates 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 asargs_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) inrunatlc:libs/core/langchain_core/tools/base.py:1091-1093; ValidationError wrapped by ToolNode atlg: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_agentbuildsToolNodewithouthandle_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 setshandle_tool_errorturns its ownToolExceptioninto an error reply first, and MCPisErrorresults do the same (E12).- Held by
lg:libs/prebuilt/langgraph/prebuilt/tool_node.py:391-399(_default_handle_tool_errors, theToolNodedefault atlg:libs/prebuilt/langgraph/prebuilt/tool_node.py:761), applied atlg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1003-1023(asynclg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1163-1183);ToolNodebuilt with no error setting atlc: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
InjectedToolCallIdwith the real id. A tool that declares a store fails the run when none is configured, andcreate_agentconfigures 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 atlg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1455-1463; state read from channels at execution timelg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1332-1346;InjectedToolCallIdfilled in core atlc: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_concurrencyconfig 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 atlc:libs/langchain_v1/langchain/agents/factory.py:2032; Pregel submits them together (lg:libs/langgraph/langgraph/pregel/_runner.py:258-275), thread pool fromget_executor_for_config(lg:libs/langgraph/langgraph/pregel/_executor.py:58; async gatemax_concurrencyatlg:libs/langgraph/langgraph/pregel/_executor.py:143-145); results applied in task-path order atlg: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 atlg:libs/langgraph/langgraph/pregel/_runner.py:329-332, asynclg:libs/langgraph/langgraph/pregel/_runner.py:540-543) and_panic_or_proceedatlg:libs/langgraph/langgraph/pregel/_runner.py:657-704(cancels in-flight futureslg:libs/langgraph/langgraph/pregel/_runner.py:685-687, re-raiseslg:libs/langgraph/langgraph/pregel/_runner.py:689-694); finished siblings' writes go toput_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_agentadds 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, andcreate_agenthas 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 twinlg:libs/langgraph/langgraph/pregel/_retry.py:686); Pregel defaultretry_policy = ()atlg:libs/langgraph/langgraph/pregel/main.py:748;create_agentadds 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:
ChatAnthropicpassesmax_retries=2, andChatOpenAIpasses nothing unless set, so theopenaiSDK's default applies. Which errors count as retryable is the vendor's choice, andrate_limiteris off unless set.- Held by
the provider SDK client, outside both repos:
lc:libs/partners/openai/langchain_openai/chat_models/base.py:909andlc: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:1414andlc:libs/partners/anthropic/langchain_anthropic/chat_models.py:1661(ChatAnthropic.max_retries = 2). No client-side throttle:rate_limiterdefaults toNone(lc:libs/core/langchain_core/language_models/chat_models.py:375, checked atlc: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_artifacttool'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 stringifiedlc:libs/core/langchain_core/tools/base.py:1418-1419;content_and_artifactsplits off an artifact,lc:libs/core/langchain_core/tools/base.py:1099-1112); ToolNodemsg_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
Commandthat 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: aCommandwith noupdate, 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) andlg: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'sisErrorresult 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_resultraising_MCPToolExecutionError), handled by the tool's ownhandle_tool_error=_handle_mcp_tool_error(lc:libs/langchain_v1/langchain/mcp/tools.py:104-117, wiredlc:libs/langchain_v1/langchain/mcp/tools.py:303) through core's async pathlc: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
LLMToolEmulatormiddleware 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(asynclg:libs/prebuilt/langgraph/prebuilt/tool_node.py:1126).
F. Limits and memory
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_agentbinds that limit, the graph's own start step comes first and sits outside it, and the next tick stops the run with aGraphRecursionErrorand 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 withconfig={"recursion_limit": n}.- Held by
lc:libs/langchain_v1/langchain/agents/factory.py:1899(sets 9,999); enforced atlg:libs/langgraph/langgraph/pregel/_loop.py:1887(self.stop = self.step + recursion_limit + 1) andlg: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_timeoutisNoneandcreate_agentneither 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 atlg:libs/langgraph/langgraph/pregel/main.py:3062;create_agentnever 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 wholerequest.messageslist, 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 frombefore_modelatlc:libs/langchain_v1/langchain/agents/middleware/summarization.py:503-505. Present only withSummarizationMiddlewareinmiddleware=
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'smodelargument, up to 3 attempts viawith_retryatlc:libs/langchain_v1/langchain/agents/middleware/summarization.py:436); replaced by a customsummarizeratlc: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; defaultkeep=("messages", 20)atlc:libs/langchain_v1/langchain/agents/middleware/summarization.py:293,lc:libs/langchain_v1/langchain/agents/middleware/summarization.py:115) andlc: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 tokenslc:libs/langchain_v1/langchain/agents/middleware/context_editing.py:61, keep last 3 tool resultslc: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 atlc: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_modelcompares counts tothread_limit/run_limit); counts incremented inafter_modellc: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 atlc:libs/langchain_v1/langchain/agents/middleware/tool_call_limit.py:294-323; optional per-tool filtertool_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 tograph.compileatlc:libs/langchain_v1/langchain/agents/factory.py:1910; resolved atlg:libs/langgraph/langgraph/pregel/main.py:850-861; with a checkpointer the caller'sthread_idpicks the history (lg:libs/langgraph/langgraph/pregel/main.py:2677-2682refuses an emptyconfigurable)
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(durabilitydefaults to"async"); the loop waits for the save only whendurability_ == "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_formatis 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_commandswritesstructured_response: Noneon every model call that produced none, when aresponse_formatis configured)
G. Judging the result
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_directby 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 hasreturn_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: PydanticTypeAdapter.validate_python), called fromOutputToolBinding.parse(lc:libs/langchain_v1/langchain/agents/structured_output.py:358-370) andProviderStrategyBinding.parse(lc:libs/langchain_v1/langchain/agents/structured_output.py:405-431, afterjson.loadsatlc: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 atlc:libs/langchain_v1/langchain/agents/factory.py:1329-1346andlc:libs/langchain_v1/langchain/agents/factory.py:1286-1305; defaulthandle_errors=Trueatlc: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 vialc: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 explicitToolStrategyorProviderStrategyis 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_strategylc:libs/langchain_v1/langchain/agents/factory.py:578-623: model profile flagstructured_output(lc:libs/langchain_v1/langchain/agents/factory.py:601-614, with a Gemini<3 + tools exclusion), else the name regex listFALLBACK_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), thenlc:libs/langchain_v1/langchain/agents/factory.py:2102-2104intools_to_model(ends if any structured-output ToolMessage hasstatus != "error")
H. Delegation
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_agentcarries 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 ownrecursion_limitat 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 inlg:libs/langgraph/langgraph/_internal/_config.py:351-358thenlg:libs/langgraph/langgraph/_internal/_config.py:382-422(later configs overwrite non-special keys,recursion_limitby the finalelse) viaensure_config(self.config, config)atlg: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=Falseon the child opts out, and a tool that passes the child its ownthread_iddrops the lent saver, leaving only the child's own.- Held by
lg:libs/langgraph/langgraph/pregel/main.py:850-858(_resolve_checkpointer: "none forcheckpointer=False, else the one a parent lends a subgraph through the config, else this graph's own", docstringlg: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 ANYExceptionexceptGraphBubbleUpatlc: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 withinit_chat_modelatlc:libs/langchain_v1/langchain/agents/middleware/model_fallback.py:386; provider cache settings stripped per attemptlc: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; itsis_errorflag is taken at face value atlc: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
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_agentonly passesstorethrough (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)) andlc:libs/partners/openai/langchain_openai/chat_models/base.py:1547-1548; loaded at model construction bylc: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.