Use this file to discover all available pages before exploring further.
Some tool operations may be sensitive and require human approval before execution. Deep Agents support human-in-the-loop workflows through LangGraph’s interrupt capabilities. You can configure which tools require approval using the interrupt_on parameter.
When an interrupt is triggered, the agent pauses execution and returns control. Check for interrupts in the result and handle them accordingly.
from langchain_core.utils.uuid import uuid7from langgraph.types import Command# Create config with thread_id for state persistenceconfig = {"configurable": {"thread_id": str(uuid7())}}# Invoke the agentresult = agent.invoke( {"messages": [{"role": "user", "content": "Delete the file temp.txt"}]}, config=config, version="v2",)# Check if execution was interruptedif result.interrupts: # Extract interrupt information interrupt_value = result.interrupts[0].value action_requests = interrupt_value["action_requests"] review_configs = interrupt_value["review_configs"] # Create a lookup map from tool name to review config config_map = {cfg["action_name"]: cfg for cfg in review_configs} # Display the pending actions to the user for action in action_requests: review_config = config_map[action["name"]] print(f"Tool: {action['name']}") print(f"Arguments: {action['args']}") print(f"Allowed decisions: {review_config['allowed_decisions']}") # Get user decisions (one per action_request, in order) decisions = [ {"type": "approve"} # User approved the deletion ] # Resume execution with decisions result = agent.invoke( Command(resume={"decisions": decisions}), config=config, # Must use the same config! version="v2", )# Process final resultprint(result.value["messages"][-1].content)
When the agent calls multiple tools that require approval, all interrupts are batched together in a single interrupt. You must provide decisions for each one in order.
config = {"configurable": {"thread_id": str(uuid7())}}result = agent.invoke( {"messages": [{ "role": "user", "content": "Delete temp.txt and send an email to admin@example.com" }]}, config=config, version="v2",)if result.interrupts: interrupt_value = result.interrupts[0].value action_requests = interrupt_value["action_requests"] # Two tools need approval assert len(action_requests) == 2 # Provide decisions in the same order as action_requests decisions = [ {"type": "approve"}, # First tool: delete_file {"type": "reject"} # Second tool: send_email ] result = agent.invoke( Command(resume={"decisions": decisions}), config=config, version="v2", )
When "edit" is in the allowed decisions, you can modify the tool arguments before execution:
if result.interrupts: interrupt_value = result.interrupts[0].value action_request = interrupt_value["action_requests"][0] # Original args from the agent print(action_request["args"]) # {"to": "everyone@company.com", ...} # User decides to edit the recipient decisions = [{ "type": "edit", "edited_action": { "name": action_request["name"], # Must include the tool name "args": {"to": "team@company.com", "subject": "...", "body": "..."} } }] result = agent.invoke( Command(resume={"decisions": decisions}), config=config, version="v2", )
Subagent tools can call interrupt() directly to pause execution and await approval:
from langchain.agents import create_agentfrom langchain_anthropic import ChatAnthropicfrom langchain.messages import HumanMessagefrom langchain.tools import toolfrom langgraph.checkpoint.memory import InMemorySaverfrom langgraph.types import Command, interruptfrom deepagents.graph import create_deep_agentfrom deepagents.middleware.subagents import CompiledSubAgent@tool(description="Request human approval before proceeding with an action.")def request_approval(action_description: str) -> str: """Request human approval using the interrupt() primitive.""" # interrupt() pauses execution and returns the value passed to Command(resume=...) approval = interrupt({ "type": "approval_request", "action": action_description, "message": f"Please approve or reject: {action_description}", }) if approval.get("approved"): return f"Action '{action_description}' was APPROVED. Proceeding..." else: return f"Action '{action_description}' was REJECTED. Reason: {approval.get('reason', 'No reason provided')}"def main(): checkpointer = InMemorySaver() model = ChatAnthropic( model_name="claude-sonnet-4-6", max_tokens=4096, ) compiled_subagent = create_agent( model=model, tools=[request_approval], name="approval-agent", ) parent_agent = create_deep_agent( model="google_genai:gemini-3.5-flash", checkpointer=checkpointer, subagents=[ CompiledSubAgent( name="approval-agent", description="An agent that can request approvals", runnable=compiled_subagent, ) ], ) thread_id = "test_interrupt_directly" config = {"configurable": {"thread_id": thread_id}} print("Invoking agent - sub-agent will use request_approval tool...") result = parent_agent.invoke( { "messages": [ HumanMessage( content="Use the task tool to launch the approval-agent sub-agent. " "Tell it to use the request_approval tool to request approval for 'deploying to production'." ) ] }, config=config, version="v2", ) # Check for interrupt if result.interrupts: interrupt_value = result.interrupts[0].value print(f"\nInterrupt received!") print(f" Type: {interrupt_value.get('type')}") print(f" Action: {interrupt_value.get('action')}") print(f" Message: {interrupt_value.get('message')}") print("\nResuming with Command(resume={'approved': True})...") result2 = parent_agent.invoke( Command(resume={"approved": True}), config=config, version="v2", ) if not result2.interrupts: print("\nExecution completed!") # Find the tool response tool_msgs = [m for m in result2.value.get("messages", []) if m.type == "tool"] if tool_msgs: print(f" Tool result: {tool_msgs[-1].content}") else: print("\nAnother interrupt occurred") else: print("\n No interrupt - the model may not have called request_approval")if __name__ == "__main__": main()
When run, this produces the following output:
Invoking agent - sub-agent will use request_approval tool...Interrupt received! Type: approval_request Action: deploying to production Message: Please approve or reject: deploying to productionResuming with Command(resume={'approved': True})...Execution completed! Tool result: Great! The approval request has been processed. The action **"deploying to production"** was **APPROVED**. You can now proceed with the production deployment.
The decisions list must match the order of action_requests:
if result.interrupts: interrupt_value = result.interrupts[0].value action_requests = interrupt_value["action_requests"] # Create one decision per action, in order decisions = [] for action in action_requests: decision = get_user_decision(action) # Your logic decisions.append(decision) result = agent.invoke( Command(resume={"decisions": decisions}), config=config, version="v2", )