# How to Configure HumanInTheLoopMiddleware for Granular Tool Approval Workflows

> Learn to configure HumanInTheLoopMiddleware for granular tool approval workflows. Intercept tool calls, inject HumanMessages for approval, and gain full control over agent operations. Enhance your Langchain app today.

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

---

**The `HumanInTheLoopMiddleware` intercepts tool calls by matching names against an `interrupt_on` dictionary, injecting a `HumanMessage` to pause execution until a human approves, modifies, or rejects the operation.**

The `langchain-ai/deepagents` repository provides a robust middleware architecture for enforcing human oversight in autonomous agent workflows. By configuring the `HumanInTheLoopMiddleware` with granular `interrupt_on` mappings, you can specify exactly which tools require explicit approval before execution, creating auditable safety guardrails for sensitive operations like file editing or code execution.

## Core Architecture and Configuration Parameters

The middleware operates on a simple interception model. When a tool call matches a key in the `interrupt_on` mapping, the middleware pauses the agent and emits a `HumanMessage` containing the tool's description and payload.

**Key parameters you will configure:**

- **`interrupt_on`**: A dictionary where keys are tool names (as strings in the agent's tools list) and values are booleans indicating whether to trigger human approval. Example: `{"edit_file": True, "execute": True}`.
- **`default_interrupt_on`**: A global interrupt map applied to every sub-agent that does not provide its own `interrupt_on` configuration. This is passed to `create_subagents` or `create_graph` for broad policy enforcement.

## Where the Middleware is Wired

Understanding the injection points in the source code helps you choose the right configuration strategy for your architecture.

### Sub-Agent Creation Layer

In [`libs/deepagents/deepagents/middleware/subagents.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/middleware/subagents.py), the middleware is appended to the default middleware list when `default_interrupt_on` is supplied:

```python

# line 319

if default_interrupt_on:
    general_purpose_middleware.append(
        HumanInTheLoopMiddleware(interrupt_on=default_interrupt_on)
    )

```

Individual sub-agents can override this global setting with their own `interrupt_on` argument:

```python

# line 355

if interrupt_on:
    _middleware.append(HumanInTheLoopMiddleware(interrupt_on=interrupt_on))

```

### Graph-Level Integration

For top-level graph construction in [`libs/deepagents/deepagents/graph.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/graph.py), the same pattern applies to both general-purpose and deep-agent graphs:

```python

# line 207

gp_middleware.append(HumanInTheLoopMiddleware(interrupt_on=interrupt_on))

```

```python

# line 283

deepagent_middleware.append(HumanInTheLoopMiddleware(interrupt_on=interrupt_on))

```

Reference implementations in the test suite demonstrate concrete usage patterns. In [`libs/acp/tests/test_agent.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/acp/tests/test_agent.py), line 220 shows `{"write_file_tool": True}` and line 267 demonstrates `{"edit_file": True}`. The Nvidia deep agent example at [`examples/nvidia_deep_agent/src/agent.py`](https://github.com/langchain-ai/deepagents/blob/main/examples/nvidia_deep_agent/src/agent.py) line 98 illustrates enabling HITL specifically for the `execute` tool.

## Implementing Granular Tool Approval

Follow these steps to configure fine-grained control over tool execution.

### Step 1: Identify Protected Tools

Examine your tool instantiation to determine the exact names used in the agent's tools list. Common examples include `edit_file`, `execute`, and `write_file` as defined by tools like `EditFileTool()` or `ExecuteCodeTool()`.

### Step 2: Create the interrupt_on Mapping

Define a dictionary mapping tool names to boolean values. Set the value to `True` for any tool requiring human approval.

```python
interrupt_config = {
    "edit_file": True,   # Pause before any file modification

    "execute": True,     # Pause before code execution

    "write_file": True,  # Pause before creating new files

}

```

### Step 3: Attach Middleware at the Appropriate Layer

You can apply HITL globally or per sub-agent. For global application across all sub-agents that don't specify their own config:

```python
from deepagents.deepagents.middleware.subagents import create_subagents

subagents = create_subagents(
    default_model=my_model,
    default_tools=[EditFileTool(), ExecuteCodeTool()],
    default_interrupt_on=interrupt_config,  # Global policy

)

```

For granular, per-sub-agent control:

```python
custom_subagents = create_subagents(
    subagents=[
        {
            "name": "file-editor",
            "description": "Handles safe file edits",
            "tools": [EditFileTool()],
            "interrupt_on": {"edit_file": True},  # Only this sub-agent gets HITL

        },
        {
            "name": "executor",
            "description": "Runs code snippets",
            "tools": [ExecuteCodeTool()],
            "interrupt_on": {"execute": True},
        },
    ],
    default_interrupt_on=None,  # No global HITL

)

```

### Step 4: Handle HumanMessage Responses

When a guarded tool is invoked, the middleware injects a `HumanMessage` into the agent's state. In [`libs/deepagents/deepagents/middleware/subagents.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/middleware/subagents.py) lines 24-28, the implementation adds:

```python
subagent_state["messages"] = [HumanMessage(content=description)]

```

Your UI or downstream consumer must listen for these messages, present the tool description and arguments to the human operator, collect the approval response, and feed it back into the agent's state. Once approved, the middleware converts the response to a `ToolMessage` and proceeds with execution.

## Complete Implementation Example

This example demonstrates a multi-sub-agent system where each tool type has distinct approval requirements:

```python
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from deepagents.deepagents.middleware.subagents import create_subagents
from deepagents.tools import EditFileTool, ExecuteCodeTool, WriteFileTool

# Define the toolset

tools = [EditFileTool(), ExecuteCodeTool(), WriteFileTool()]

# Build sub-agents with specific HITL configurations

subagents = create_subagents(
    default_model=my_model,
    default_tools=tools,
    default_interrupt_on=None,  # No global policy

    subagents=[
        {
            "name": "editor",
            "description": "Edits existing files",
            "tools": [EditFileTool()],
            "interrupt_on": {"edit_file": True},
        },
        {
            "name": "runner",
            "description": "Executes code snippets",
            "tools": [ExecuteCodeTool()],
            "interrupt_on": {"execute": True},
        },
        {
            "name": "writer",
            "description": "Creates new files",
            "tools": [WriteFileTool()],
            "interrupt_on": {"write_file": True},
        },
    ],
)

# Top-level agent with HITL on the task delegation tool itself

agent = create_agent(
    model=my_model,
    system_prompt="You are an autonomous assistant. Use the `task` tool for sub-tasks.",
    tools=[subagents["task"]],
    middleware=[HumanInTheLoopMiddleware(interrupt_on={"task": True})],
)

```

## How Interrupts Work Under the Hood

When `HumanInTheLoopMiddleware` intercepts a guarded tool call, it performs the following sequence:

1. **Interception**: The middleware wraps the tool's `invoke` method, checking if the tool name exists in `interrupt_on` with a truthy value.
2. **Injection**: It creates a `HumanMessage` containing the tool description and intended arguments, injecting this into the agent's message history.
3. **Pause**: The agent state halts, waiting for external input.
4. **Resolution**: Upon receiving human approval, the middleware replaces the placeholder with a `ToolMessage` containing the approved arguments, allowing the original tool to execute with the verified parameters.

This mechanism ensures that no guarded tool executes without explicit human confirmation, while maintaining the full context of the conversation for the operator's review.

## Summary

- **`interrupt_on`** dictionaries map tool names to boolean flags, determining which tools require human approval.
- Configure **global HITL** via `default_interrupt_on` in `create_subagents` or **per-sub-agent** overrides via individual `interrupt_on` arguments.
- The middleware resides in [`libs/deepagents/deepagents/middleware/subagents.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/middleware/subagents.py) (lines 319, 355) and [`libs/deepagents/deepagents/graph.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/graph.py) (lines 207, 283).
- When triggered, the middleware injects a `HumanMessage` (as seen in line 27 of [`subagents.py`](https://github.com/langchain-ai/deepagents/blob/main/subagents.py)) and pauses execution until receiving a response.
- Concrete examples exist in [`libs/acp/tests/test_agent.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/acp/tests/test_agent.py) and [`examples/nvidia_deep_agent/src/agent.py`](https://github.com/langchain-ai/deepagents/blob/main/examples/nvidia_deep_agent/src/agent.py).

## Frequently Asked Questions

### What is the difference between `interrupt_on` and `default_interrupt_on`?

The `default_interrupt_on` parameter applies a global HITL configuration to all sub-agents that do not specify their own `interrupt_on` mapping, as implemented in [`libs/deepagents/deepagents/middleware/subagents.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/middleware/subagents.py) at line 319. The `interrupt_on` parameter, used when defining individual sub-agents (line 355), overrides this global setting for that specific agent only, enabling granular control where some agents require approval while others operate autonomously.

### How do I configure HITL for only specific sub-agents while leaving others unrestricted?

Pass `default_interrupt_on=None` to `create_subagents`, then explicitly define `interrupt_on` mappings only for the sub-agents requiring oversight. According to the source code at line 355, the middleware appends only when `interrupt_on` is truthy, so sub-agents without this key will execute without human interruption.

### What message type does the middleware inject when interrupting a tool call?

The middleware injects a `HumanMessage` object containing the tool's description. In [`libs/deepagents/deepagents/middleware/subagents.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/middleware/subagents.py) at lines 24-28, the code sets `subagent_state["messages"] = [HumanMessage(content=description)]`, which the upstream agent or UI receives as a signal to pause and prompt the human operator for approval.

### Can I use HumanInTheLoopMiddleware with top-level graph agents rather than just sub-agents?

Yes. The [`libs/deepagents/deepagents/graph.py`](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/graph.py) file shows the middleware being appended to both `gp_middleware` (line 207) and `deepagent_middleware` (line 283) when building graph-level agents. You can pass the `interrupt_on` configuration directly to the graph builder functions to enforce HITL at the top level.