Headless Tools and Tool Calling
Purpose and Scope
Headless tools are tools whose schema is visible to an agent while the real execution happens somewhere else, commonly in a user interface or browser runtime. Tool-calling UI is the related presentation pattern: the client renders each tool request, its inputs, loading state, result, and any error as part of the conversation. LangChain supports these patterns because tool calls are not treated as opaque strings. They are structured requests from the model, and tool results come back as structured messages that can be correlated with the original request.
In the Python repository, the key source-level contract is the relationship between a tool-capable model, a prompt with an agent scratchpad, and ToolMessage results. The classic helper create_tool_calling_agent binds a sequence of BaseTool instances to a language model, requires an agent_scratchpad placeholder, and returns a Runnable that produces either an action or a finish. That same shape is what lets an external client show pending work and then resume once a matching result exists. Sources: libs/langchain/langchain_classic/agents/tool_calling_agent/base.py, libs/core/langchain_core/messages/tool.py
The official frontend docs describe two closely related client responsibilities. For ordinary tool-calling UI, the client renders tool calls emitted by an agent, often as typed cards rather than raw JSON. For headless tools, the server registers a schema-only definition, the client implements the matching behavior, and the client sends the result back so the run can continue. In both cases, the important engineering rule is that names, argument schemas, and call identifiers must stay aligned across the backend agent and the frontend implementation.
Relevant Source Files
libs/langchain/langchain_classic/agents/tool_calling_agent/__init__.py— package entry point for the classic tool-calling agent module.libs/langchain/langchain_classic/agents/tool_calling_agent/base.py— definescreate_tool_calling_agent, the prompt requirements, the modelbind_toolsrequirement, and the runnable assembly used for tool-capable agents.libs/core/langchain_core/messages/tool.py— definesToolMessage,ToolOutputMixin, tool-result metadata, artifact handling, and validation/coercion behavior for tool outputs.libs/langchain_v1/langchain/agents/middleware/provider_tool_search.py— defines middleware for deferring selected tools behind provider-native tool search for supported providers.libs/langchain_v1/langchain/agents/middleware/shell_tool.py— defines middleware that exposes a persistentshelltool, including shell session state, execution result shape, and safety-oriented execution policy integration.libs/langchain_v1/langchain/agents/middleware/tool_call_limit.py— defines middleware state, limit error behavior, and generated tool or final AI messages when a tool-call budget is exceeded.
Core Primitives
A tool call has three practical parts: a tool name, structured arguments, and an identifier that links the request to the result. The repository evidence focuses most directly on the result side through ToolMessage. A ToolMessage is a BaseMessage with type set to tool, a required tool_call_id, a content payload, optional artifact data, and a status of success or error. The tool_call_id is essential for parallel or streamed interfaces because it is how a UI connects a finished result to the specific pending call that produced it. Sources: libs/core/langchain_core/messages/tool.py
The artifact field is important for UI authors because it separates model-facing content from application-facing output. For example, a tool can send concise text back to the model while preserving a richer object, file payload, image data, or diagnostic record for the application. This is the same design pressure that headless tools address: not every useful result belongs inside the model context. A frontend can render the artifact or store it locally while the model receives only the portion needed to continue reasoning.
The classic agent helper defines the backend half of the tool-calling loop. create_tool_calling_agent(llm, tools, prompt, *, message_formatter=format_to_tool_messages) accepts a BaseLanguageModel, a sequence of BaseTool instances, and a ChatPromptTemplate. It verifies that the prompt includes agent_scratchpad, verifies that the model implements bind_tools, binds the provided tools to the model, and returns a Runnable sequence. The scratchpad is where intermediate actions and tool outputs are transformed back into messages for the next model turn. Sources: libs/langchain/langchain_classic/agents/tool_calling_agent/base.py
System-to-Code Mapping
| Concern | Source-level component | What it means for UI and headless tools |
|---|---|---|
| Agent tool binding | create_tool_calling_agent(llm, tools, prompt, message_formatter=...) | The backend exposes tool schemas to a model and requires a scratchpad for intermediate tool messages. |
| Tool result correlation | ToolMessage(tool_call_id=...) | Clients and runtimes can match a result to the exact tool call that requested it. |
| Rich result handling | ToolMessage.artifact | Applications can keep UI-only or storage-only data separate from model-facing content. |
| Error signaling | ToolMessage.status and limit-generated tool messages | Tool failures or policy blocks can be surfaced to both the model and the user interface. |
| Large tool catalogs | ProviderToolSearchMiddleware(searchable_tools=...) | Selected tools can be deferred behind provider-native tool search rather than sent in every request. |
| Local command execution | SHELL_TOOL_NAME = "shell" and ShellSession | A middleware-provided tool can run sequential commands in a persistent session under an execution policy. |
| Call budgets | ToolCallLimitMiddleware state and ExitBehavior | Agents can be constrained per run or thread so UI flows do not spiral into unbounded tool use. |
The source files show a layered design rather than a single frontend API. Messages define the durable protocol for tool results. The classic agent factory defines how tool schemas are attached to a model and how previous tool outputs are fed back through the prompt. Middleware then modifies the behavior of agent runs by adding tools, deferring schemas, or blocking calls. A UI can sit above these layers and render tool activity without needing to know whether the tool ran on the server, in a sandbox, through a provider search mechanism, or in the browser. Sources: libs/langchain/langchain_classic/agents/tool_calling_agent/base.py, libs/langchain_v1/langchain/agents/middleware/provider_tool_search.py, libs/langchain_v1/langchain/agents/middleware/shell_tool.py, libs/langchain_v1/langchain/agents/middleware/tool_call_limit.py
Execution Flow
A standard tool-calling run begins when the application invokes an agent with user input and any required chat history. The prompt must reserve agent_scratchpad for intermediate action/result messages. The model receives the conversation and tool schemas, then decides whether to answer directly or request tool execution. When a tool call is executed, the result is represented as a ToolMessage with the corresponding call identifier. The next model call receives that message in the scratchpad, allowing the model to incorporate the result into its next decision or final answer. Sources: libs/langchain/langchain_classic/agents/tool_calling_agent/base.py, libs/core/langchain_core/messages/tool.py
For a tool-calling UI, the same flow becomes a stream of renderable states. The client can show a pending card when a tool request appears, display structured arguments for review, show a running indicator while the tool executes, and replace the card with output when the matching ToolMessage arrives. If the result has status="error", the UI can render an error state while still preserving the model-facing message. If the result includes an artifact, the UI can render richer application data without forcing that data into the model context.
For a headless tool, the execution step moves to the client. The backend still gives the model a normal tool schema, but the browser or app implements the behavior. This is useful for browser-only APIs such as local storage, geolocation, clipboard, canvas, file pickers, or device-local data that should not be sent to the server. The client must return a result that can be converted into the same tool-result protocol. In Python terms, the resumed run needs the equivalent of a ToolMessage whose tool_call_id matches the model's request.
Middleware and Policy Controls
ProviderToolSearchMiddleware addresses a different but related tool-calling problem: agents may have many tools, and sending every schema on every turn can bloat model requests. The middleware accepts searchable_tools, normalizes them to tool names, marks selected tools for deferred loading, and injects a provider-native search descriptor when the provider supports it. The source identifies Anthropic and OpenAI as supported provider keys for server-side tool search. If a deferred tool is needed, the provider can retrieve the full schema through its native mechanism. Sources: libs/langchain_v1/langchain/agents/middleware/provider_tool_search.py
ShellToolMiddleware demonstrates how LangChain can add a concrete tool with runtime state. It exposes the shell tool, tracks shell session resources in agent state, and uses execution policies such as host, Docker, or sandbox-oriented policies imported by the module. The default description instructs the model to confirm the working directory, prefer absolute paths, avoid unnecessary directory changes, and expect truncation or timeout for long-running commands. For UI authors, this kind of tool is a strong candidate for approval gates or explicit rendering because the action is external, stateful, and potentially sensitive. Sources: libs/langchain_v1/langchain/agents/middleware/shell_tool.py
ToolCallLimitMiddleware is a policy layer for runaway or unsafe tool use. Its state tracks counts per tool name and globally with the special __all__ key, distinguishing thread-level counts from run-level counts. Its ExitBehavior type allows continue, error, or end. In continue mode, exceeded calls can be blocked with tool error messages while other calls proceed. In error mode, the middleware raises ToolCallLimitExceededError. In end mode, it injects a tool message and final AI message for the exceeded single call. Sources: libs/langchain_v1/langchain/agents/middleware/tool_call_limit.py
API Components
| Component | Public shape | Notes |
|---|---|---|
create_tool_calling_agent | create_tool_calling_agent(llm: BaseLanguageModel, tools: Sequence[BaseTool], prompt: ChatPromptTemplate, *, message_formatter: MessageFormatter = format_to_tool_messages) -> Runnable | Requires agent_scratchpad and an LLM with bind_tools(). |
MessageFormatter | Callable[[Sequence[tuple[AgentAction, str]]], list[BaseMessage]] | Converts intermediate agent actions and tool outputs into messages. |
ToolMessage | ToolMessage(content=..., tool_call_id=..., artifact=None, status="success") | Represents the result of executing a tool and correlates with a tool call. |
ToolOutputMixin | Mixin for direct tool return objects | Non-mixin custom tool outputs are coerced to string and wrapped in ToolMessage. |
ProviderToolSearchMiddleware | `ProviderToolSearchMiddleware(searchable_tools: list[str | BaseTool] |
SHELL_TOOL_NAME | "shell" | Name of the persistent shell tool exposed by shell middleware. |
CommandExecutionResult | output, exit_code, timed_out, truncated_by_lines, truncated_by_bytes, total_lines, total_bytes | Structured shell execution result useful for display and diagnostics. |
ExitBehavior | `"continue" | "error" |
When building a client, treat these components as contracts rather than implementation details. Use the tool name and argument schema to decide which UI component should render. Use the call identifier to join request and result. Use status and exception-derived messages to drive error states. Use artifact or structured execution result fields for richer UI when available. Use middleware configuration to make unsafe, expensive, or high-volume tool flows explicit instead of leaving every decision to the model.
Implementation Guidance
Keep schema definitions and implementations aligned. For server-side tools, this means the BaseTool name and schema should match the behavior registered with the agent. For headless tools, it means the shared definition used by the backend must match the browser implementation exactly. If the agent emits memory_get but the client registered memoryGet, the client cannot safely execute the call. If the arguments are not serializable or do not match the expected schema, the tool result may fail before it can become a valid message.
Render tool calls as application events, not as hidden model internals. A basic interface can show the tool name, parsed arguments, running state, output, and error. A more advanced interface can pause before executing sensitive tools, request user approval, redact private values, or keep local artifacts out of the model transcript. The repository's ToolMessage design supports this because it distinguishes content, artifact, status, and call correlation instead of forcing every result into a single text field. Sources: libs/core/langchain_core/messages/tool.py
Finally, set policy defaults before exposing powerful tools. Use provider-side search when a large tool catalog makes request size a concern. Use call limits when a tool is expensive, irreversible, or easy for a model to call repeatedly. Use approval UI for shell-like, browser-local, or user-data tools. The next useful pages are the broader tools page for defining tool schemas, the middleware pages for lifecycle controls, and the event-streaming or frontend pages for rendering incremental agent state.