# Human-in-the-Loop Middleware

Ref: https://docs.langchain.com/oss/python/langchain/middleware/built-in

This notebook demonstrates `HumanInTheLoopMiddleware`, which pauses agent
execution for human approval before executing specific tool calls.


## Setup

Configure `.env` before running. See `.env.sample`.



```python
import rich
from dotenv import load_dotenv

load_dotenv()
```




    True



## HumanInTheLoopMiddleware

Built-in middleware that requires human approval for specific tool calls:

**How it works:**

1. `after_model` hook checks if AIMessage contains tool calls
2. For tools in `interrupt_on`, calls `interrupt()` to pause execution
3. Human provides decision: `approve`, `edit`, or `reject`
4. On `reject`, creates ToolMessage with error status

**Requires checkpointer** - state must be persisted across interruptions.

**Configuration:**

- `True`: Allow all decisions (approve/edit/reject)
- `False`: Auto-approve (no interruption)
- `InterruptOnConfig`: Custom allowed decisions and description

**Note:** `edit` decision allows modifying tool arguments before execution (not covered here).



```python
from datetime import datetime
from typing import Any

from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain_anthropic import ChatAnthropic
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph.state import CompiledStateGraph
from langgraph.types import Command


@tool
def get_current_time() -> str:
    """Get the current time."""
    return datetime.now().isoformat()


@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email to the specified recipient."""
    return f"Email sent to {to} with subject '{subject}'"


model = ChatAnthropic(model="claude-sonnet-4-5-20250929")
checkpointer = InMemorySaver()

# Require approval for send_email, auto-approve get_current_time
hitl = HumanInTheLoopMiddleware(
    interrupt_on={
        "send_email": {
            "allowed_decisions": ["approve", "reject"],
            "description": "Review email before sending",
        },
        "get_current_time": False,  # Auto-approve
    }
)

agent: CompiledStateGraph[Any] = create_agent(
    model=model,
    tools=[get_current_time, send_email],
    system_prompt="You are a helpful assistant.",
    checkpointer=checkpointer,
    middleware=[hitl],
)
```

## Basic Approval Flow

When agent tries to send an email, execution pauses for approval.



```python
config: RunnableConfig = {"configurable": {"thread_id": "hitl-test-1"}}

# This will trigger an interrupt when agent tries to send email
result = agent.invoke(
    {"messages": [{"role": "user", "content": "Send an email to bob@example.com saying hello"}]},
    config,
)

rich.print("result =", result)
```


    result =
    {
        'messages': [
            HumanMessage(
                content='Send an email to bob@example.com saying hello',
                additional_kwargs={},
                response_metadata={},
                id='11d9a628-4132-43b0-a193-e1149cc47534'
            ),
            AIMessage(
                content=[
                    {
                        'id': 'toolu_01U8QZANyJg89cRDqzpaySGH',
                        'input': {'to': 'bob@example.com', 'subject': 'Hello', 'body': 'Hello!'},
                        'name': 'send_email',
                        'type': 'tool_use'
                    }
                ],
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_012t6Mt1yCkWKjhZ3MoBvuAE',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'tool_use',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 642,
                        'output_tokens': 91,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--fb286f67-2aaa-4db2-a403-00c0437c27b9-0',
                tool_calls=[
                    {
                        'name': 'send_email',
                        'args': {'to': 'bob@example.com', 'subject': 'Hello', 'body': 'Hello!'},
                        'id': 'toolu_01U8QZANyJg89cRDqzpaySGH',
                        'type': 'tool_call'
                    }
                ],
                usage_metadata={
                    'input_tokens': 642,
                    'output_tokens': 91,
                    'total_tokens': 733,
                    'input_token_details': {
                        'cache_read': 0,
                        'cache_creation': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            )
        ],
        '__interrupt__': [
            Interrupt(
                value={
                    'action_requests': [
                        {
                            'name': 'send_email',
                            'args': {'to': 'bob@example.com', 'subject': 'Hello', 'body': 'Hello!'},
                            'description': 'Review email before sending'
                        }
                    ],
                    'review_configs': [{'action_name': 'send_email', 'allowed_decisions': ['approve', 'reject']}]
                },
                id='71340ef38a5f34dd8192034edf772714'
            )
        ]
    }




```python
# Check the interrupt state and approve
state = agent.get_state(config)
rich.print("next =", state.next)
rich.print("tasks =", state.tasks)

if state.next and state.tasks:
    # Collect all pending actions from interrupts
    decisions: list[dict[str, Any]] = []

    for task in state.tasks:
        for intr in task.interrupts:
            if "action_requests" in intr.value:
                for action in intr.value["action_requests"]:
                    rich.print(f"[yellow]Pending: {action['name']}[/yellow]")
                    rich.print(f"  Args: {action['args']}")
                    # Approve this action
                    decisions.append({"type": "approve"})

    rich.print(f"[bold yellow]Sending decisions: {decisions}[/bold yellow]")
    result = agent.invoke(
        Command(resume={"decisions": decisions}),
        config,
    )
    rich.print("result =", result)
else:
    rich.print("[bold red]Not in interrupted state[/bold red]")
```


    next =
    ('HumanInTheLoopMiddleware.after_model',)




    tasks =
    (
        PregelTask(
            id='2b94d006-f4d0-14a8-ce2f-d9bbb2c8548b',
            name='HumanInTheLoopMiddleware.after_model',
            path=('__pregel_pull', 'HumanInTheLoopMiddleware.after_model'),
            error=None,
            interrupts=(
                Interrupt(
                    value={
                        'action_requests': [
                            {
                                'name': 'send_email',
                                'args': {'to': 'bob@example.com', 'subject': 'Hello', 'body': 'Hello!'},
                                'description': 'Review email before sending'
                            }
                        ],
                        'review_configs': [{'action_name': 'send_email', 'allowed_decisions': ['approve', 'reject']}]
                    },
                    id='71340ef38a5f34dd8192034edf772714'
                ),
            ),
            state=None,
            result=None
        ),
    )




    Pending: send_email




      Args: {'to': 'bob@example.com', 'subject': 'Hello', 'body': 'Hello!'}




    Sending decisions: [{'type': 'approve'}]




    result =
    {
        'messages': [
            HumanMessage(
                content='Send an email to bob@example.com saying hello',
                additional_kwargs={},
                response_metadata={},
                id='11d9a628-4132-43b0-a193-e1149cc47534'
            ),
            AIMessage(
                content=[
                    {
                        'id': 'toolu_01U8QZANyJg89cRDqzpaySGH',
                        'input': {'to': 'bob@example.com', 'subject': 'Hello', 'body': 'Hello!'},
                        'name': 'send_email',
                        'type': 'tool_use'
                    }
                ],
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_012t6Mt1yCkWKjhZ3MoBvuAE',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'tool_use',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 642,
                        'output_tokens': 91,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--fb286f67-2aaa-4db2-a403-00c0437c27b9-0',
                tool_calls=[
                    {
                        'name': 'send_email',
                        'args': {'to': 'bob@example.com', 'subject': 'Hello', 'body': 'Hello!'},
                        'id': 'toolu_01U8QZANyJg89cRDqzpaySGH',
                        'type': 'tool_call'
                    }
                ],
                usage_metadata={
                    'input_tokens': 642,
                    'output_tokens': 91,
                    'total_tokens': 733,
                    'input_token_details': {
                        'cache_creation': 0,
                        'cache_read': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            ),
            ToolMessage(
                content="Email sent to bob@example.com with subject 'Hello'",
                name='send_email',
                id='fcac0f06-d525-4657-b71f-6f361e642be3',
                tool_call_id='toolu_01U8QZANyJg89cRDqzpaySGH'
            ),
            AIMessage(
                content='I\'ve sent an email to bob@example.com with the subject "Hello" and a simple greeting 
    message.',
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_01AN65o7H2qX5FPDKePCAiak',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'end_turn',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 760,
                        'output_tokens': 26,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--af62f484-6ff5-4fef-a560-f1af0b89800b-0',
                usage_metadata={
                    'input_tokens': 760,
                    'output_tokens': 26,
                    'total_tokens': 786,
                    'input_token_details': {
                        'cache_read': 0,
                        'cache_creation': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            )
        ]
    }



## Rejection Flow

User can reject a tool call and provide a reason.



```python
config2: RunnableConfig = {"configurable": {"thread_id": "hitl-test-2"}}

# Request to send email with full details
message = "Send an email to carol@example.com with subject 'Meeting' and body 'See you at 10am'"
result = agent.invoke(
    {"messages": [{"role": "user", "content": message}]},
    config2,
)

rich.print("Paused for approval...")
rich.print("result =", result)
```


    Paused for approval...




    result =
    {
        'messages': [
            HumanMessage(
                content="Send an email to carol@example.com with subject 'Meeting' and body 'See you at 10am'",
                additional_kwargs={},
                response_metadata={},
                id='8e4187cd-7b14-406a-9511-ca87d504ef8a'
            ),
            AIMessage(
                content=[
                    {
                        'id': 'toolu_0181E44oXwxKFrsXthS5ixVW',
                        'input': {'to': 'carol@example.com', 'subject': 'Meeting', 'body': 'See you at 10am'},
                        'name': 'send_email',
                        'type': 'tool_use'
                    }
                ],
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_01KKRabXCdiiLGuv8ojiqghp',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'tool_use',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 657,
                        'output_tokens': 96,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--9d98c573-d2c6-41d5-9ff6-9c6b25901136-0',
                tool_calls=[
                    {
                        'name': 'send_email',
                        'args': {'to': 'carol@example.com', 'subject': 'Meeting', 'body': 'See you at 10am'},
                        'id': 'toolu_0181E44oXwxKFrsXthS5ixVW',
                        'type': 'tool_call'
                    }
                ],
                usage_metadata={
                    'input_tokens': 657,
                    'output_tokens': 96,
                    'total_tokens': 753,
                    'input_token_details': {
                        'cache_read': 0,
                        'cache_creation': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            )
        ],
        '__interrupt__': [
            Interrupt(
                value={
                    'action_requests': [
                        {
                            'name': 'send_email',
                            'args': {'to': 'carol@example.com', 'subject': 'Meeting', 'body': 'See you at 10am'},
                            'description': 'Review email before sending'
                        }
                    ],
                    'review_configs': [{'action_name': 'send_email', 'allowed_decisions': ['approve', 'reject']}]
                },
                id='802f4da8ccdadd34da4414c7236034e6'
            )
        ]
    }




```python
# Check tasks and reject the email
state = agent.get_state(config2)
if state.next and state.tasks:
    # Collect all pending actions from interrupts
    reject_decisions: list[dict[str, Any]] = []

    for task in state.tasks:
        for intr in task.interrupts:
            if "action_requests" in intr.value:
                for action in intr.value["action_requests"]:
                    rich.print(f"[yellow]Pending: {action['name']}[/yellow]")
                    rich.print(f"  Args: {action['args']}")
                    # Reject this action
                    reject_decisions.append({"type": "reject", "message": "User rejected this action."})

    rich.print(f"[bold red]Sending decisions: {reject_decisions}[/bold red]")
    result = agent.invoke(
        Command(resume={"decisions": reject_decisions}),
        config2,
    )
    rich.print("result =", result)
else:
    rich.print("[bold red]Not in interrupted state[/bold red]")
```


    Pending: send_email




      Args: {'to': 'carol@example.com', 'subject': 'Meeting', 'body': 'See you at 10am'}




    Sending decisions: [{'type': 'reject', 'message': 'User rejected this action.'}]




    result =
    {
        'messages': [
            HumanMessage(
                content="Send an email to carol@example.com with subject 'Meeting' and body 'See you at 10am'",
                additional_kwargs={},
                response_metadata={},
                id='8e4187cd-7b14-406a-9511-ca87d504ef8a'
            ),
            AIMessage(
                content=[
                    {
                        'id': 'toolu_0181E44oXwxKFrsXthS5ixVW',
                        'input': {'to': 'carol@example.com', 'subject': 'Meeting', 'body': 'See you at 10am'},
                        'name': 'send_email',
                        'type': 'tool_use'
                    }
                ],
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_01KKRabXCdiiLGuv8ojiqghp',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'tool_use',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 657,
                        'output_tokens': 96,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--9d98c573-d2c6-41d5-9ff6-9c6b25901136-0',
                tool_calls=[
                    {
                        'name': 'send_email',
                        'args': {'to': 'carol@example.com', 'subject': 'Meeting', 'body': 'See you at 10am'},
                        'id': 'toolu_0181E44oXwxKFrsXthS5ixVW',
                        'type': 'tool_call'
                    }
                ],
                usage_metadata={
                    'input_tokens': 657,
                    'output_tokens': 96,
                    'total_tokens': 753,
                    'input_token_details': {
                        'cache_creation': 0,
                        'cache_read': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            ),
            ToolMessage(
                content='User rejected this action.',
                name='send_email',
                id='e658385c-fdf1-4686-a065-71ab3c940494',
                tool_call_id='toolu_0181E44oXwxKFrsXthS5ixVW',
                status='error'
            ),
            AIMessage(
                content='It looks like the email was not sent because the action was rejected. This could happen if you
    need to confirm the action or if there are permission restrictions. Would you like me to try again or would you 
    like to modify the email details?',
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_01HRbHTNeLb6oPojrSHjWbQW',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'end_turn',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 778,
                        'output_tokens': 51,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--ef2bf43a-c719-400d-aab8-d8e4d74f7134-0',
                usage_metadata={
                    'input_tokens': 778,
                    'output_tokens': 51,
                    'total_tokens': 829,
                    'input_token_details': {
                        'cache_read': 0,
                        'cache_creation': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            )
        ]
    }



## Multiple Tool Calls

When the model requests multiple tool calls at once, you provide decisions for each.



```python
# Create agent that requires approval for all send_email calls
checkpointer3 = InMemorySaver()
hitl_multi = HumanInTheLoopMiddleware(
    interrupt_on={
        "send_email": {"allowed_decisions": ["approve", "reject"]},
    }
)

agent_multi: CompiledStateGraph[Any] = create_agent(
    model=model,
    tools=[get_current_time, send_email],
    system_prompt="You are a helpful assistant. When asked to send multiple emails, call send_email for each.",
    checkpointer=checkpointer3,
    middleware=[hitl_multi],
)

config_multi: RunnableConfig = {"configurable": {"thread_id": "multi-test"}}

# Request multiple emails at once
message = "Send two emails: one to alice@example.com saying 'Hi', one to bob@example.com saying 'Hello'"
result = agent_multi.invoke(
    {"messages": [{"role": "user", "content": message}]},
    config_multi,
)
rich.print("result =", result)
```


    result =
    {
        'messages': [
            HumanMessage(
                content="Send two emails: one to alice@example.com saying 'Hi', one to bob@example.com saying 'Hello'",
                additional_kwargs={},
                response_metadata={},
                id='a2ad24f6-1668-4ad3-a7be-1dfd0aafe5ea'
            ),
            AIMessage(
                content=[
                    {
                        'id': 'toolu_018huTN7kzCWKeyovWs4nsT9',
                        'input': {'to': 'alice@example.com', 'subject': 'Hi', 'body': 'Hi'},
                        'name': 'send_email',
                        'type': 'tool_use'
                    },
                    {
                        'id': 'toolu_01PXcSAu3SGYiPeqgzvWr539',
                        'input': {'to': 'bob@example.com', 'subject': 'Hello', 'body': 'Hello'},
                        'name': 'send_email',
                        'type': 'tool_use'
                    }
                ],
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_01BHn1bFUC553xEGFEBsQefH',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'tool_use',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 673,
                        'output_tokens': 165,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--c19bfd01-0a98-452d-a482-f4835fd94dd4-0',
                tool_calls=[
                    {
                        'name': 'send_email',
                        'args': {'to': 'alice@example.com', 'subject': 'Hi', 'body': 'Hi'},
                        'id': 'toolu_018huTN7kzCWKeyovWs4nsT9',
                        'type': 'tool_call'
                    },
                    {
                        'name': 'send_email',
                        'args': {'to': 'bob@example.com', 'subject': 'Hello', 'body': 'Hello'},
                        'id': 'toolu_01PXcSAu3SGYiPeqgzvWr539',
                        'type': 'tool_call'
                    }
                ],
                usage_metadata={
                    'input_tokens': 673,
                    'output_tokens': 165,
                    'total_tokens': 838,
                    'input_token_details': {
                        'cache_read': 0,
                        'cache_creation': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            )
        ],
        '__interrupt__': [
            Interrupt(
                value={
                    'action_requests': [
                        {
                            'name': 'send_email',
                            'args': {'to': 'alice@example.com', 'subject': 'Hi', 'body': 'Hi'},
                            'description': "Tool execution requires approval\n\nTool: send_email\nArgs: {'to': 
    'alice@example.com', 'subject': 'Hi', 'body': 'Hi'}"
                        },
                        {
                            'name': 'send_email',
                            'args': {'to': 'bob@example.com', 'subject': 'Hello', 'body': 'Hello'},
                            'description': "Tool execution requires approval\n\nTool: send_email\nArgs: {'to': 
    'bob@example.com', 'subject': 'Hello', 'body': 'Hello'}"
                        }
                    ],
                    'review_configs': [
                        {'action_name': 'send_email', 'allowed_decisions': ['approve', 'reject']},
                        {'action_name': 'send_email', 'allowed_decisions': ['approve', 'reject']}
                    ]
                },
                id='f0eb55e3cd14accfaed74012258801aa'
            )
        ]
    }



## Auto-Approve with Memory

Auto-approve previously approved tool calls using external state, not custom middleware.

This demonstrates how to build auto-approval logic **outside** the middleware,
using `state.tasks` to check pending actions and decide automatically.

**NOTE:** This example stores approved calls in memory (`approved_calls`).
In production, you would persist this to a database.



```python
# Use HumanInTheLoopMiddleware with external auto-approve logic

checkpointer_auto = InMemorySaver()
hitl_auto = HumanInTheLoopMiddleware(
    interrupt_on={
        "send_email": {"allowed_decisions": ["approve", "reject"]},
    }
)

agent_auto: CompiledStateGraph[Any] = create_agent(
    model=model,
    tools=[get_current_time, send_email],
    system_prompt="You are a helpful assistant.",
    checkpointer=checkpointer_auto,
    middleware=[hitl_auto],
)

# Memory for approved calls: (tool_name, args_str) -> True
approved_calls: set[tuple[str, str]] = set()


def check_and_resume(
    agent: CompiledStateGraph[Any],
    config: RunnableConfig,
    approved: set[tuple[str, str]],
) -> Any:
    """Check pending actions and auto-approve if previously approved."""
    state = agent.get_state(config)

    if not (state.next and state.tasks):
        rich.print("[dim]Not in interrupted state[/dim]")
        return None

    auto_decisions: list[dict[str, Any]] = []

    for task in state.tasks:
        for intr in task.interrupts:
            if "action_requests" not in intr.value:
                continue

            for action in intr.value["action_requests"]:
                key = (action["name"], str(sorted(action["args"].items())))

                if key in approved:
                    rich.print(f"[bold green]Auto-approved: {action['name']}[/bold green]")
                    auto_decisions.append({"type": "approve"})
                else:
                    rich.print(f"[yellow]New action: {action['name']}[/yellow]")
                    rich.print(f"  Args: {action['args']}")
                    # For this example, approve and remember
                    auto_decisions.append({"type": "approve"})
                    approved.add(key)
                    rich.print("[bold green]Approved and remembered[/bold green]")

    return agent.invoke(Command(resume={"decisions": auto_decisions}), config)
```

### Test: First call (new action)



```python
config_auto: RunnableConfig = {"configurable": {"thread_id": "auto-approve-test"}}

# First call - will pause for approval
result = agent_auto.invoke(
    {"messages": [{"role": "user", "content": "Send email to alice@example.com saying 'See you tomorrow'"}]},
    config_auto,
)
rich.print("result =", result)
```


    result =
    {
        'messages': [
            HumanMessage(
                content="Send email to alice@example.com saying 'See you tomorrow'",
                additional_kwargs={},
                response_metadata={},
                id='234a6953-9df2-4e79-9d1d-043fa1c67399'
            ),
            AIMessage(
                content=[
                    {
                        'id': 'toolu_01HXfHjWQzxdZmpuUxCq6r5P',
                        'input': {
                            'to': 'alice@example.com',
                            'subject': 'See you tomorrow',
                            'body': 'See you tomorrow'
                        },
                        'name': 'send_email',
                        'type': 'tool_use'
                    }
                ],
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_01XVfbPLHqkv7BvjiihQYaCs',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'tool_use',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 646,
                        'output_tokens': 95,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--22139990-c59c-48fa-9807-46a43c13cffa-0',
                tool_calls=[
                    {
                        'name': 'send_email',
                        'args': {
                            'to': 'alice@example.com',
                            'subject': 'See you tomorrow',
                            'body': 'See you tomorrow'
                        },
                        'id': 'toolu_01HXfHjWQzxdZmpuUxCq6r5P',
                        'type': 'tool_call'
                    }
                ],
                usage_metadata={
                    'input_tokens': 646,
                    'output_tokens': 95,
                    'total_tokens': 741,
                    'input_token_details': {
                        'cache_read': 0,
                        'cache_creation': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            )
        ],
        '__interrupt__': [
            Interrupt(
                value={
                    'action_requests': [
                        {
                            'name': 'send_email',
                            'args': {
                                'to': 'alice@example.com',
                                'subject': 'See you tomorrow',
                                'body': 'See you tomorrow'
                            },
                            'description': "Tool execution requires approval\n\nTool: send_email\nArgs: {'to': 
    'alice@example.com', 'subject': 'See you tomorrow', 'body': 'See you tomorrow'}"
                        }
                    ],
                    'review_configs': [{'action_name': 'send_email', 'allowed_decisions': ['approve', 'reject']}]
                },
                id='ac6f036531d17c438e5d7ea7c0f6fa69'
            )
        ]
    }




```python
# Check and approve - this is a new action, so it will be remembered
result = check_and_resume(agent_auto, config_auto, approved_calls)
rich.print("result =", result)
rich.print("approved_calls =", approved_calls)
```


    New action: send_email




      Args: {'to': 'alice@example.com', 'subject': 'See you tomorrow', 'body': 'See you tomorrow'}




    Approved and remembered




    result =
    {
        'messages': [
            HumanMessage(
                content="Send email to alice@example.com saying 'See you tomorrow'",
                additional_kwargs={},
                response_metadata={},
                id='234a6953-9df2-4e79-9d1d-043fa1c67399'
            ),
            AIMessage(
                content=[
                    {
                        'id': 'toolu_01HXfHjWQzxdZmpuUxCq6r5P',
                        'input': {
                            'to': 'alice@example.com',
                            'subject': 'See you tomorrow',
                            'body': 'See you tomorrow'
                        },
                        'name': 'send_email',
                        'type': 'tool_use'
                    }
                ],
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_01XVfbPLHqkv7BvjiihQYaCs',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'tool_use',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 646,
                        'output_tokens': 95,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--22139990-c59c-48fa-9807-46a43c13cffa-0',
                tool_calls=[
                    {
                        'name': 'send_email',
                        'args': {
                            'to': 'alice@example.com',
                            'subject': 'See you tomorrow',
                            'body': 'See you tomorrow'
                        },
                        'id': 'toolu_01HXfHjWQzxdZmpuUxCq6r5P',
                        'type': 'tool_call'
                    }
                ],
                usage_metadata={
                    'input_tokens': 646,
                    'output_tokens': 95,
                    'total_tokens': 741,
                    'input_token_details': {
                        'cache_creation': 0,
                        'cache_read': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            ),
            ToolMessage(
                content="Email sent to alice@example.com with subject 'See you tomorrow'",
                name='send_email',
                id='6efe3c0d-8abe-4d6a-aaa4-4542de13abfe',
                tool_call_id='toolu_01HXfHjWQzxdZmpuUxCq6r5P'
            ),
            AIMessage(
                content='I\'ve successfully sent the email to alice@example.com with the message "See you tomorrow" as 
    both the subject and body.',
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_01BconWr7b54As1mPaLbCWja',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'end_turn',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 770,
                        'output_tokens': 30,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--7e7ad180-26cd-4a0b-afbb-b24c11260b81-0',
                usage_metadata={
                    'input_tokens': 770,
                    'output_tokens': 30,
                    'total_tokens': 800,
                    'input_token_details': {
                        'cache_read': 0,
                        'cache_creation': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            )
        ]
    }




    approved_calls =
    {('send_email', "[('body', 'See you tomorrow'), ('subject', 'See you tomorrow'), ('to', 'alice@example.com')]")}



### Test: Same action again (auto-approved)



```python
config_auto2: RunnableConfig = {"configurable": {"thread_id": "auto-approve-test-2"}}

# Same action again - will pause but auto-approve
result = agent_auto.invoke(
    {"messages": [{"role": "user", "content": "Send email to alice@example.com saying 'See you tomorrow'"}]},
    config_auto2,
)
rich.print("result =", result)
```


    result =
    {
        'messages': [
            HumanMessage(
                content="Send email to alice@example.com saying 'See you tomorrow'",
                additional_kwargs={},
                response_metadata={},
                id='8be1e05a-1d31-4688-9b4b-5183c638aca6'
            ),
            AIMessage(
                content=[
                    {
                        'id': 'toolu_01KnupYA7EuLtCZyUkE2GaMb',
                        'input': {
                            'to': 'alice@example.com',
                            'subject': 'See you tomorrow',
                            'body': 'See you tomorrow'
                        },
                        'name': 'send_email',
                        'type': 'tool_use'
                    }
                ],
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_01ThMYFCjw98y81EwhEnmiKr',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'tool_use',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 646,
                        'output_tokens': 95,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--39df35df-db02-4417-b379-21b06b2911ba-0',
                tool_calls=[
                    {
                        'name': 'send_email',
                        'args': {
                            'to': 'alice@example.com',
                            'subject': 'See you tomorrow',
                            'body': 'See you tomorrow'
                        },
                        'id': 'toolu_01KnupYA7EuLtCZyUkE2GaMb',
                        'type': 'tool_call'
                    }
                ],
                usage_metadata={
                    'input_tokens': 646,
                    'output_tokens': 95,
                    'total_tokens': 741,
                    'input_token_details': {
                        'cache_read': 0,
                        'cache_creation': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            )
        ],
        '__interrupt__': [
            Interrupt(
                value={
                    'action_requests': [
                        {
                            'name': 'send_email',
                            'args': {
                                'to': 'alice@example.com',
                                'subject': 'See you tomorrow',
                                'body': 'See you tomorrow'
                            },
                            'description': "Tool execution requires approval\n\nTool: send_email\nArgs: {'to': 
    'alice@example.com', 'subject': 'See you tomorrow', 'body': 'See you tomorrow'}"
                        }
                    ],
                    'review_configs': [{'action_name': 'send_email', 'allowed_decisions': ['approve', 'reject']}]
                },
                id='94b624e70f6f29336b7fff18e5253001'
            )
        ]
    }




```python
# Check and auto-approve - same action, so it should be auto-approved
result = check_and_resume(agent_auto, config_auto2, approved_calls)
rich.print("result =", result)
```


    Auto-approved: send_email




    result =
    {
        'messages': [
            HumanMessage(
                content="Send email to alice@example.com saying 'See you tomorrow'",
                additional_kwargs={},
                response_metadata={},
                id='8be1e05a-1d31-4688-9b4b-5183c638aca6'
            ),
            AIMessage(
                content=[
                    {
                        'id': 'toolu_01KnupYA7EuLtCZyUkE2GaMb',
                        'input': {
                            'to': 'alice@example.com',
                            'subject': 'See you tomorrow',
                            'body': 'See you tomorrow'
                        },
                        'name': 'send_email',
                        'type': 'tool_use'
                    }
                ],
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_01ThMYFCjw98y81EwhEnmiKr',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'tool_use',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 646,
                        'output_tokens': 95,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--39df35df-db02-4417-b379-21b06b2911ba-0',
                tool_calls=[
                    {
                        'name': 'send_email',
                        'args': {
                            'to': 'alice@example.com',
                            'subject': 'See you tomorrow',
                            'body': 'See you tomorrow'
                        },
                        'id': 'toolu_01KnupYA7EuLtCZyUkE2GaMb',
                        'type': 'tool_call'
                    }
                ],
                usage_metadata={
                    'input_tokens': 646,
                    'output_tokens': 95,
                    'total_tokens': 741,
                    'input_token_details': {
                        'cache_creation': 0,
                        'cache_read': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            ),
            ToolMessage(
                content="Email sent to alice@example.com with subject 'See you tomorrow'",
                name='send_email',
                id='96039921-0821-4c82-9ef6-ef82200a9b00',
                tool_call_id='toolu_01KnupYA7EuLtCZyUkE2GaMb'
            ),
            AIMessage(
                content='I\'ve sent the email to alice@example.com with "See you tomorrow" as both the subject and body
    of the message.',
                additional_kwargs={},
                response_metadata={
                    'id': 'msg_01Djqv2mExjySeVL8ULJBdFH',
                    'model': 'claude-sonnet-4-5-20250929',
                    'stop_reason': 'end_turn',
                    'stop_sequence': None,
                    'usage': {
                        'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 0},
                        'cache_creation_input_tokens': 0,
                        'cache_read_input_tokens': 0,
                        'input_tokens': 770,
                        'output_tokens': 30,
                        'server_tool_use': None,
                        'service_tier': 'standard'
                    },
                    'model_name': 'claude-sonnet-4-5-20250929',
                    'model_provider': 'anthropic'
                },
                id='lc_run--219f2cb7-bed5-4eca-a9ae-d13c9cbaf764-0',
                usage_metadata={
                    'input_tokens': 770,
                    'output_tokens': 30,
                    'total_tokens': 800,
                    'input_token_details': {
                        'cache_read': 0,
                        'cache_creation': 0,
                        'ephemeral_5m_input_tokens': 0,
                        'ephemeral_1h_input_tokens': 0
                    }
                }
            )
        ]
    }


