# PatchToolCallsMiddleware: Ensuring Tool Call Consistency in DeepAgents

> Discover PatchToolCallsMiddleware in DeepAgents to ensure tool call consistency and message history integrity in your agent pipelines automatically. Learn how it handles missing tool responses.

- Repository: [LangChain/deepagents](https://github.com/langchain-ai/deepagents)
- Tags: internals
- Published: 2026-03-17

---

**The PatchToolCallsMiddleware is a built-in agent middleware that guarantees message history consistency by ensuring every tool call emitted by an AIMessage is paired with a corresponding ToolMessage, automatically inserting synthetic placeholders when tool responses are missing.**

The `langchain-ai/deepagents` framework relies on strict message sequencing to manage complex agent interactions across sub-agents and the main orchestrator. When large language models invoke external tools, the resulting conversation history must maintain a balanced sequence of `AIMessage` tool calls and `ToolMessage` responses to satisfy LangGraph runtime expectations. The **PatchToolCallsMiddleware** serves as a critical safeguard in this execution pipeline, preventing state management errors caused by orphaned or dangling tool calls.

## What is PatchToolCallsMiddleware?

**PatchToolCallsMiddleware** is a state-management middleware defined in [`deepagents/middleware/patch_tool_calls.py`](https://github.com/langchain-ai/deepagents/blob/main/deepagents/middleware/patch_tool_calls.py). It implements the `before_agent` hook, which executes immediately before the agent begins its next reasoning turn. Its sole responsibility is to audit the existing `messages` list for incomplete tool call cycles and repair any inconsistencies by injecting synthetic completion messages.

When an LLM decides to invoke a tool, it appends a `ToolCall` entry to its `AIMessage`. Under normal operation, the framework later receives a `ToolMessage` containing the tool’s result, identified by a matching `tool_call_id`. However, if the tool response is missing—due to user interruption, tool crashes, or runtime discards—the message stream ends with a **dangling tool call** that violates the "tool call-result" contract expected by downstream middleware and the LangGraph runtime.

## How PatchToolCallsMiddleware Works

The middleware operates through a systematic validation process during the `before_agent` lifecycle phase.

### Scanning for Unmatched Tool Calls

During the `before_agent` execution, the middleware iterates through the `messages` list in [`deepagents/middleware/patch_tool_calls.py`](https://github.com/langchain-ai/deepagents/blob/main/deepagents/middleware/patch_tool_calls.py). For every `AIMessage` containing a `tool_calls` array, it verifies whether a subsequent `ToolMessage` with a matching `tool_call_id` exists later in the conversation history.

### Synthetic Message Injection

If a matching `ToolMessage` is absent, the middleware constructs a synthetic placeholder explaining the cancellation:

```python
tool_msg = (
    f"Tool call {tool_call['name']} with id {tool_call['id']} was "
    "cancelled - another message came in before it could be completed."
)
ToolMessage(content=tool_msg, name=tool_call["name"], tool_call_id=tool_call["id"])

```

This synthetic message is wrapped in an **Overwrite** container and returned as a state update, ensuring the agent's message history is replaced with the patched version before any downstream logic processes it.

## Integration in the DeepAgents Pipeline

According to the source code in [`deepagents/graph.py`](https://github.com/langchain-ai/deepagents/blob/main/deepagents/graph.py), **PatchToolCallsMiddleware** is a standard component of both the general-purpose subagent stack and the main DeepAgent orchestrator. It is positioned strategically to catch inconsistencies after filesystem operations but before the next reasoning cycle.

In the general-purpose subagent configuration:

```python
gp_middleware = [
    TodoListMiddleware(),
    FilesystemMiddleware(backend=backend),
    create_summarization_middleware(model, backend),
    AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
    PatchToolCallsMiddleware(),
]

```

And in the main DeepAgent stack:

```python
deepagent_middleware = [
    TodoListMiddleware(),
    FilesystemMiddleware(backend=backend),
    SubAgentMiddleware(backend=backend, subagents=all_subagents),
    create_summarization_middleware(model, backend),
    AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
    PatchToolCallsMiddleware(),
]

```

Because it is placed **after** `FilesystemMiddleware` and **before** any logic that consumes the message list, it acts as a universal safety net for all tool calls generated by the LLM, regardless of which sub-agent emitted them.

## Usage Examples

### Automatic Application via create_agent

When building agents through the high-level API, the middleware is typically included in the default stack, but you can explicitly declare it:

```python
from deepagents import create_agent, PatchToolCallsMiddleware

# The middleware ensures tool call consistency automatically

agent = create_agent(
    model,
    tools=my_tools,
    middleware=[PatchToolCallsMiddleware()],  # Optional – added by default

)

```

### Manual State Patching

For advanced use cases requiring manual state manipulation, you can invoke the middleware directly to repair stale conversation histories:

```python
from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware
from langgraph.runtime import Runtime
from langchain.agents.middleware import AgentState

# Message list with a missing tool result (dangling tool call)

state = AgentState(messages=[
    SystemMessage(content="You are a helpful assistant."),
    HumanMessage(content="Give me the weather."),
    AIMessage(content="Sure", tool_calls=[ToolCall(id="42", name="get_weather", args={})]),
])

# Execute the patching logic before the next agent step

patched = PatchToolCallsMiddleware().before_agent(state, Runtime())

# patched["messages"] now contains a synthetic ToolMessage for id "42"

```

### Unit Test Verification

The repository's test suite in [`libs/deepagents/tests/unit_tests/test_middleware.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/tests/unit_tests/test_middleware.py) demonstrates the expected patching behavior:

```python
def test_missing_tool_call():
    input_messages = [
        SystemMessage(content="You are a helpful assistant."),
        HumanMessage(content="Hello"),
        AIMessage(
            content="I'm doing well",
            tool_calls=[ToolCall(id="123", name="get_events_for_days", args={})],
        ),
        HumanMessage(content="What's the weather?"),
    ]
    middleware = PatchToolCallsMiddleware()
    state_update = middleware.before_agent({"messages": input_messages}, None)
    patched = state_update["messages"].value
    assert patched[3].type == "tool"   # the inserted placeholder

```

Additional integration tests in [`libs/deepagents/tests/unit_tests/middleware/test_subagent_middleware_init.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/tests/unit_tests/middleware/test_subagent_middleware_init.py) verify that the middleware functions correctly within sub-agent initialization contexts.

## Summary

- **PatchToolCallsMiddleware** ensures every `AIMessage` containing `tool_calls` has a corresponding `ToolMessage` response, preventing dangling tool calls from breaking the execution pipeline.
- The middleware runs during the `before_agent` phase in [`deepagents/middleware/patch_tool_calls.py`](https://github.com/langchain-ai/deepagents/blob/main/deepagents/middleware/patch_tool_calls.py), scanning message history and injecting synthetic cancellation messages when responses are missing.
- It returns state updates wrapped in an **Overwrite** container to replace the agent's message list with the patched version.
- The component is integrated into both the general-purpose subagent and main DeepAgent middleware stacks in [`deepagents/graph.py`](https://github.com/langchain-ai/deepagents/blob/main/deepagents/graph.py), positioned after filesystem operations to catch all tool call inconsistencies.

## Frequently Asked Questions

### What happens if a tool call is missing its response?

If an `AIMessage` contains a `tool_calls` entry but no subsequent `ToolMessage` with a matching `tool_call_id` exists, **PatchToolCallsMiddleware** automatically inserts a synthetic `ToolMessage` stating that the tool call was cancelled due to an intervening message. This prevents the LangGraph runtime from encountering an unbalanced message sequence that could cause state-management errors.

### Where is PatchToolCallsMiddleware installed in the middleware stack?

According to [`deepagents/graph.py`](https://github.com/langchain-ai/deepagents/blob/main/deepagents/graph.py), the middleware is appended at the end of both the general-purpose subagent middleware list and the main DeepAgent middleware list. This placement ensures it executes after `FilesystemMiddleware` and `SubAgentMiddleware` but before the next agent reasoning turn, catching any inconsistencies introduced by sub-agent operations or filesystem interactions.

### Can I use PatchToolCallsMiddleware with custom agent implementations?

Yes. You can import `PatchToolCallsMiddleware` from `deepagents.middleware.patch_tool_calls` and instantiate it directly in custom middleware stacks. The middleware expects standard LangGraph `AgentState` objects containing a `messages` key and can be invoked manually via the `before_agent` method, as demonstrated in the manual state patching example above.

### How does the middleware handle multiple missing tool calls?

The implementation scans the entire message history and processes every `AIMessage` with `tool_calls` independently. If multiple tool calls lack corresponding `ToolMessage` responses, **PatchToolCallsMiddleware** generates a synthetic placeholder for each missing response, ensuring the final message list maintains complete pairing for every tool invocation ID before the agent continues execution.