new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

LangChain · Deep Agents · all subjects

human-in-the-loop

65 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

Human-in-the-loop with interruptOn in ACP

Use the interruptOn configuration option to require user approval in the IDE before the agent runs sensitive tools. When the agent calls a protected tool, the IDE prompts the user to allow or reject the operation, with options to remember the decision for the session.

Gated actions in Deep Agents Code

Gated actions are potentially consequential actions that require approval before running. They include: editing or deleting files (write_file, edit_file, delete), running shell commands (execute), making web requests (web_search, fetch_url), and delegating work to subagents (task). Read-only tools such as ls, read_file, glob, and grep always run without prompting.

Three approval modes in Deep Agents Code

Deep Agents Code has three approval modes for gated actions: Manual (default) asks for approval before every gated action; Auto approves routine actions automatically, asks the model to review uncertain actions, and falls back to human approval after repeated denials or failures; YOLO runs gated actions with no review at all.

Toggle between Manual and Auto modes during session

You can toggle between Manual and Auto modes at any time during a session using Shift+Tab or Ctrl+T keyboard shortcuts. YOLO cannot be entered through the keyboard toggle.

Enable Auto mode in Deep Agents Code

To use Auto mode, first set the environment variable DEEPAGENTS_CODE_EXPERIMENTAL=1 in your shell or ~/.deepagents/.env. Then launch with dcode -y or set mode = "auto" in [startup] section of ~/.deepagents/config.toml. If Auto is requested without the experimental opt-in or in a sandboxed session, it falls back to Manual with a warning.

Enable YOLO mode in Deep Agents Code

To use YOLO mode, launch with dcode --yolo and accept the one-time risk acknowledgement when prompted. The acknowledgement is stored locally and will not appear again on later launches. Alternatively, set mode = "yolo" in [startup] section of ~/.deepagents/config.toml. A session launched in YOLO mode moves to Manual when you press Shift+Tab or Ctrl+T, and you cannot switch back to YOLO with the keyboard toggle.

Auto mode two-stage review process

Auto mode uses two stages for reviewing gated actions. Stage 1: Routine actions run automatically without a prompt. A write to a source file like src/parser.py or a read-only Git command like git status proceeds without prompting. Sensitive targets like .github/workflows/ci.yml or mutating commands like git commit go to the next stage. Stage 2: For anything not clearly routine, the active model checks whether the action matches what the user asked for. Only the literal user prompt can authorize an action. If the model denies a call, the agent gets an error result and can revise its plan.

Auto mode fallback to Manual after repeated denials

After repeated denials or classifier failures, Auto mode stops and shows the normal approval prompt for the next batch, then continues in Auto mode.

Set classifier model for Auto mode review

You can configure which model is used for Auto mode classifier review. Configuration sources in precedence order: (1) /auto model TUI command (takes effect immediately for current session), (2) --auto-classifier-model flag (sets classifier on launch, interactive TUI only), (3) DEEPAGENTS_CODE_AUTO_CLASSIFIER_MODEL environment variable (applies at startup), (4) [models].auto_classifier in config.toml (persistent default), (5) Inherit the main agent model (default when nothing configured). A blank value at any level means inherit from the next source.

Auto classifier model TUI command syntax

Use the /auto model TUI command to manage the classifier model during a session. Run /auto model to open the interactive model picker. Pass a model name as an argument like /auto model openai:gpt-5.6-luna to specify a model directly, or use /auto model clear to go back to inheriting the main model.

Auto classifier model CLI flag

Use the --auto-classifier-model flag when launching Deep Agents Code to set the classifier model. Example: dcode -y --auto-classifier-model openai:gpt-5.6-luna. This flag is only accepted in interactive TUI sessions.

Auto classifier model environment variable

Set DEEPAGENTS_CODE_AUTO_CLASSIFIER_MODEL environment variable to specify which model should review Auto mode decisions. Example: export DEEPAGENTS_CODE_AUTO_CLASSIFIER_MODEL="openai:gpt-5.6-luna". Note: A project .env file cannot set this variable; only shell exports, ~/.deepagents/.env, the CLI flag, and /auto model TUI command work.

Auto classifier model config.toml setting

Set the classifier model in ~/.deepagents/config.toml using: [models] auto_classifier = "openai:gpt-5.6-luna"

Auto mode state validation before execution

The decision plan in Auto mode is bound to the thread, mode, batch, and exact gated calls. Missing or invalid state, a mode race, or a replay falls back to human review. For example, if you switch to Manual while classifier review is in progress, an earlier Auto decision cannot execute silently; the normal approval UI opens instead.

Auto mode scope and limitations

Auto mode has the following limitations: The Manual approval menu can enable Auto for the current thread, and threshold fallback can switch permanently to Manual or perform a one-off review while leaving Auto enabled. The active model is not an independent security authority. MCP read-only annotations are trusted as a deliberate beta tradeoff. Parent-level Auto review does not cover actions performed inside delegated subagents or broader explicitly configured js_eval fan-out. Model providers and tracing backends may still observe classifier inputs and outputs even though the TUI hides them.

Auto and YOLO mode availability

Auto and YOLO are interactive-mode features only. They are not available in non-interactive mode (with -n flag or piped stdin) or in ACP server mode. Headless runs use fail-closed MCP routing and --shell-allow-list for shell access. Auto also falls back to Manual when DEEPAGENTS_CODE_EXPERIMENTAL=1 is not set or when a remote --sandbox is active.

Flag and config precedence for approval modes

When setting approval modes through different configuration sources, the precedence is: --yolo flag takes priority, then -y/--auto-approve flag, then [startup].mode in config. Specific behavior: --yolo flag selects YOLO (interactive only, after acknowledgement); -y or --auto-approve flag selects Auto (requires DEEPAGENTS_CODE_EXPERIMENTAL=1); [startup].mode = "manual" selects Manual; [startup].mode = "auto" selects Auto; [startup].mode = "yolo" selects YOLO; Shift+Tab or Ctrl+T toggle can switch between Manual and Auto (never enters YOLO).

Auto is not sandbox containment or security guarantee

Auto is an authorization heuristic for a local coding agent. It is not sandbox containment, an operating-system boundary, or a guarantee that model-generated actions are safe.

Human-in-the-loop approval modes with -y and --yolo

Potentially destructive tool calls require approval by default. There are three approval modes: Manual mode (default) requires confirmation at all checkpoints, Auto mode (`-y`/`--auto-approve`) uses an LLM classifier, and YOLO (`--yolo`) runs gated actions without review. Toggle between Manual and Auto during an interactive session with `Shift+Tab`. Examples: `dcode -y`, `dcode --yolo`.

Deep Agents Code human-in-the-loop approval

Deep Agents Code requires human approval for sensitive tool operations through its human-in-the-loop feature.

Sandbox security considerations

Sandboxes isolate code execution, but agents remain vulnerable to prompt injection with untrusted inputs. Use human-in-the-loop approval, short-lived secrets, and trusted setup scripts only. Setting class_path in config causes Deep Agents Code to import and run arbitrary Python from that module, with module-level code executing on import. This is the same trust model as model class_path: you control your own machine and your own config file.

Hook context injection for model

Hooks can provide additional context to the model through the additionalContext field in hookSpecificOutput. This is supported by SessionStart, UserPromptSubmit, SubagentStart, PostToolUse, and SubagentStop events. The injected context helps guide the agent's behavior and decision-making.

task() does not enforce parent agent approval workflows

task() dispatches from inside an already-running eval call. It does not go through the normal tool calling path, so approval workflows (interrupt_on in Python, interruptOn in JavaScript) on the parent agent are not enforced per dispatch. Gate the eval tool itself if you need approval before subagent orchestration runs.

Deep Agents support interrupts for human-in-the-loop

Deep Agent frontends can pause delegated work for user approval or missing input using interrupts, without losing the run state.

interrupt_on for human-in-the-loop pausing

Use interrupt_on parameter in create_deep_agent to pause the agent before specific tool calls and collect user input. Takes a dictionary mapping tool names to boolean flags (True = pause before this tool). Example: interrupt_on={'send_email': True, 'delete_record': True}.

JavaScript interrupt_on example for human-in-the-loop

This example shows pausing before specific tool calls in JavaScript: import { createDeepAgent } from "deepagents"; const agent = createDeepAgent({ model: "google_genai:gemini-3.6-flash", tools: [sendEmailTool, deleteRecordTool], interruptOn: { send_email: true, delete_record: true, }, });

Multiple tool calls batched in single interrupt

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, with one decision per action_request in the order they appear in the action_requests array.

interrupt_on parameter configuration

The interrupt_on parameter accepts a dictionary mapping tool names to interrupt configurations. Each tool can be configured with: True (enable interrupts with default behavior allowing approve, edit, reject, respond), False (disable interrupts), or InterruptOnConfig (custom configuration). In Python, you can add an optional when predicate to interrupt only specific calls based on tool arguments.

HumanInTheLoopMiddleware added with interrupt_on

When interrupt_on is set, HumanInTheLoopMiddleware is added to the Deep Agents stack. If a run is cancelled or interrupted before a tool returns a result, PatchToolCallsMiddleware in the same stack repairs the message history automatically.

allowed_decisions control review options

The allowed_decisions list controls what actions a human can take when reviewing a tool call. Options include: approve (proceed with execution), edit (modify tool arguments before execution), reject (deny the action), and respond (human acts as the tool, only for prompts like ask_user, not for side-effecting tools). Do not use respond to deny side-effecting tools because its message may be treated as a successful tool result.

Handling interrupts in Python and JavaScript

When an interrupt is triggered, check for interrupts in the result. In Python, use result.interrupts to check if execution was interrupted, then extract interrupt_value = result.interrupts[0].value. In JavaScript, check result.__interrupt__, then extract const interrupts = result.__interrupt__[0].value. Resume execution using Command(resume={"decisions": decisions}) in both languages, with the same config and thread_id.

Interrupt data structure

The interrupt value contains two fields: action_requests (array of tool calls requiring approval, each with name, args, and other metadata) and review_configs (array with action_name and allowed_decisions for each action). In JavaScript, use actionRequests and reviewConfigs (camelCase), and actionName for the tool name.

Resume execution with decisions

Create a decisions list with one decision per action_request, in the same order. Each decision object must have a type field (approve, edit, reject, or respond). For reject, include an optional message field describing why the action was rejected and what the agent should do next. For edit, provide an edited_action object containing the tool name and modified args. Resume using Command(resume={"decisions": decisions}) with the same config.

Edit tool arguments before execution

When edit is in allowed_decisions, you can modify tool arguments before execution. In the decision object, set type to edit and provide edited_action containing the tool name and modified args. When editing, make changes conservatively because significant modifications to original arguments may cause the model to re-evaluate its approach and potentially execute the tool multiple times or take unexpected actions.

Rejection message for skipped tool calls

When a reviewer returns a reject decision with a type field set to reject, Deep Agents skip the tool call and send rejection feedback back to the agent. If you omit the message field, the default feedback tells the model that the tool was not executed and not to retry the same tool call unless the user asks. For sensitive or side-effecting tools, pass a domain-specific message describing whether the agent should abandon the action, ask a follow-up question, or try a safer alternative.

Subagent interrupt_on configuration overrides

Each subagent can have its own interrupt_on configuration that overrides the main agent's settings. Pass interrupt_on (Python) or interruptOn (JavaScript) in the subagent configuration. When a subagent triggers an interrupt, the handling is the same—check for interrupts on the result and resume with Command.

interrupt() primitive within tool calls

Subagent tools can call interrupt() directly to pause execution and await approval. The interrupt() function pauses execution and returns the value passed to Command(resume=...). This allows a tool to request human approval before proceeding with an action.

Filesystem permission interrupts with mode interrupt

Beyond interrupt_on, you can pause built-in filesystem tools by marking a permission rule with mode="interrupt". When the agent calls write_file or edit_file on a path matching an interrupt-mode rule, create_deep_agent raises a human-in-the-loop interrupt using the filesystem tool's name as the action name. Handle and resume the interrupt the same way as a tool-call interrupt. Filesystem-permission interrupts merge with any interrupt_on passed, so a single review step can cover both custom tools and protected filesystem paths.

Checkpointer required for human-in-the-loop

Human-in-the-loop requires a checkpointer to persist agent state between the interrupt and resume. Pass a checkpointer instance (such as MemorySaver) to create_deep_agent when using interrupt_on.

Same thread_id required when resuming interrupts

When resuming after an interrupt, you must use the same config with the same thread_id. First call uses config with thread_id, and resume call must use the identical config object.

version v2 required for interrupts

When invoking the agent with human-in-the-loop, set version="v2" in the invoke call. This applies both to the initial invocation and when resuming with Command.

Conditional interrupts with when predicate

By default, every tool call listed in interrupt_on pauses for review. To pause only some calls, add a when predicate to a tool's InterruptOnConfig. The predicate receives a ToolCallRequest and returns True to interrupt or False to auto-approve, gating on the tool's arguments. When the when predicate returns False, the call runs without interrupting. When it returns True or when you omit when, the call pauses as usual. Calls that evaluate to False are never added to the interrupt batch.

Allowed decisions configuration example

Example: delete_file with {"allowed_decisions": ["approve", "edit", "reject"]} for sensitive operations; write_file with {"allowed_decisions": ["approve", "reject"]} for moderate risk requiring approval or rejection only; critical_operation with {"allowed_decisions": ["approve"]} to require approval with no rejection allowed.

Python basic interrupt_on configuration example

```python from deepagents import create_deep_agent from langgraph.checkpoint.memory import MemorySaver checkpointer = MemorySaver() agent = create_deep_agent( model="google_genai:gemini-3.6-flash", tools=[delete_file, send_email], interrupt_on={ "delete_file": True, "send_email": {"allowed_decisions": ["approve", "reject"]}, }, checkpointer=checkpointer, ) ```

JavaScript basic interrupt_on configuration example

```typescript const agent = createDeepAgent({ tools: [deleteFile, sendEmail], interruptOn: { delete_file: true, send_email: { allowedDecisions: ["approve", "reject"] }, }, checkpointer, }); ```

Python interrupt handling and resumption pattern

```python from langchain_core.utils.uuid import uuid7 from langgraph.types import Command config = {"configurable": {"thread_id": str(uuid7())}} result = agent.invoke( {"messages": [{"role": "user", "content": "Delete the file temp.txt"}]}, config=config, version="v2", ) if result.interrupts: interrupt_value = result.interrupts[0].value action_requests = interrupt_value["action_requests"] review_configs = interrupt_value["review_configs"] config_map = {cfg["action_name"]: cfg for cfg in review_configs} 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']}") decisions = [ { "type": "reject", "message": "User rejected deleting temp.txt. Do not retry deletion.", } ] result = agent.invoke( Command(resume={"decisions": decisions}), config=config, version="v2", ) print(result.value["messages"][-1].content) ```

Python filesystem permission interrupt example

```python from deepagents import FilesystemPermission, create_deep_agent from langgraph.checkpoint.memory import MemorySaver agent = create_deep_agent( model=model, permissions=[ FilesystemPermission( operations=["write"], paths=["/secrets/**"], mode="interrupt", ), ], checkpointer=MemorySaver(), ) config = {"configurable": {"thread_id": "fs-thread-1"}} result = agent.invoke( {"messages": [{"role": "user", "content": "Save the API key to /secrets/key.txt"}]}, config=config, version="v2", ) if result.interrupts: action = result.interrupts[0].value["action_requests"][0] print(f"Approve {action['name']} on {action['args']}?") result = agent.invoke( Command(resume={"decisions": [{"type": "approve"}]}), config=config, version="v2", ) ```

JavaScript interrupt handling and resumption pattern

```typescript import { v7 as uuid7 } from "uuid"; import { Command } from "@langchain/langgraph"; const config = { configurable: { thread_id: uuid7() } }; let result = await agent.invoke({ messages: [{ role: "user", content: "Delete the file temp.txt" }], }, config); if (result.__interrupt__) { const interrupts = result.__interrupt__[0].value; const actionRequests = interrupts.actionRequests; const reviewConfigs = interrupts.reviewConfigs; const configMap = Object.fromEntries( reviewConfigs.map((cfg) => [cfg.actionName, cfg]) ); for (const action of actionRequests) { const reviewConfig = configMap[action.name]; console.log(`Tool: ${action.name}`); console.log(`Arguments: ${JSON.stringify(action.args)}`); console.log(`Allowed decisions: ${reviewConfig.allowedDecisions}`); } const decisions = [ { type: "reject", message: "User rejected deleting temp.txt. Do not retry deletion.", } ]; result = await agent.invoke( new Command({ resume: { decisions } }), config ); } console.log(result.messages[result.messages.length - 1].content); ```

Python multiple tool calls interrupt handling

```python 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"] assert len(action_requests) == 2 decisions = [ {"type": "approve"}, { "type": "reject", "message": "User rejected this action. Do not retry this tool call.", } ] result = agent.invoke( Command(resume={"decisions": decisions}), config=config, version="v2", ) ```

JavaScript multiple tool calls interrupt handling

```typescript const config = { configurable: { thread_id: uuid7() } }; let result = await agent.invoke({ messages: [{ role: "user", content: "Delete temp.txt and send an email to admin@example.com" }] }, config); if (result.__interrupt__) { const interrupts = result.__interrupt__[0].value; const actionRequests = interrupts.actionRequests; console.assert(actionRequests.length === 2); const decisions = [ { type: "approve" }, { type: "reject", message: "User rejected this action. Do not retry this tool call.", } ]; result = await agent.invoke( new Command({ resume: { decisions } }), config ); } ```

Python edit tool arguments example

```python if result.interrupts: interrupt_value = result.interrupts[0].value action_request = interrupt_value["action_requests"][0] print(action_request["args"]) decisions = [{ "type": "edit", "edited_action": { "name": action_request["name"], "args": {"to": "team@company.com", "subject": "...", "body": "..."} } }] result = agent.invoke( Command(resume={"decisions": decisions}), config=config, version="v2", ) ```

JavaScript edit tool arguments example

```typescript if (result.__interrupt__) { const interrupts = result.__interrupt__[0].value; const actionRequest = interrupts.actionRequests[0]; console.log(actionRequest.args); const decisions = [{ type: "edit", editedAction: { name: actionRequest.name, args: { to: "team@company.com", subject: "...", body: "..." } } }]; result = await agent.invoke( new Command({ resume: { decisions } }), config ); } ```

Python subagent interrupt_on override example

```python agent = create_deep_agent( model="google_genai:gemini-3.6-flash", tools=[delete_file, read_file], interrupt_on={ "delete_file": True, "read_file": False, }, subagents=[{ "name": "file-manager", "description": "Manages file operations", "system_prompt": "You are a file management assistant.", "tools": [delete_file, read_file], "interrupt_on": { "delete_file": True, "read_file": True, } }], checkpointer=checkpointer ) ```

JavaScript subagent interrupt_on override example

```typescript const agent = createDeepAgent({ tools: [deleteFile, readFile], interruptOn: { delete_file: true, read_file: false, }, subagents: [{ name: "file-manager", description: "Manages file operations", systemPrompt: "You are a file management assistant.", tools: [deleteFile, readFile], interruptOn: { delete_file: true, read_file: true, } }], checkpointer }); ```

Python interrupt() primitive tool example

```python from langchain.tools import tool from langgraph.types import interrupt @tool(description="Request human approval before proceeding with an action.") def request_approval(action_description: str) -> str: 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')}" ```

JavaScript interrupt() primitive tool example

```typescript import { tool } from "langchain"; import { interrupt } from "@langchain/langgraph"; import { z } from "zod"; const requestApproval = tool( async ({ actionDescription }: { actionDescription: string }) => { const approval = interrupt({ type: "approval_request", action: actionDescription, message: `Please approve or reject: ${actionDescription}`, }) as { approved?: boolean; reason?: string }; if (approval.approved) { return `Action '${actionDescription}' was APPROVED. Proceeding...`; } else { return `Action '${actionDescription}' was REJECTED. Reason: ${approval.reason || "No reason provided"}`; } }, { name: "request_approval", description: "Request human approval before proceeding with an action.", schema: z.object({ actionDescription: z.string().describe("The action that requires approval"), }), } ); ```

Risk-based interrupt configuration strategy

Configure different tools based on their risk level: high risk tools (delete_file, send_email) get full control with allowed_decisions ["approve", "edit", "reject"]; medium risk tools (write_file) get approval or rejection only with ["approve", "reject"]; low risk tools (read_file, ls) get set to False for no interrupts.

User identity and access control via LangSmith Deployments

LangSmith Deployments support custom authentication to establish user identity and authorization handlers to control access to resources like threads, assistants, and store namespaces. Authorization handlers run after authentication succeeds and can tag resources with ownership metadata (e.g., `owner: user_id`), return filters so users only see their own resources, or deny access with HTTP 403 for unauthorized operations. For a step-by-step tutorial see Make conversations private; for a walkthrough watch the custom auth video.

Interrupt mode behavior for human approval

Set mode="interrupt" to pause for human approval instead of allowing or denying a matching operation outright. When the agent calls a built-in write tool (write_file, edit_file, delete) on a path that matches an interrupt-mode rule, create_deep_agent raises a human-in-the-loop interrupt rather than running the tool, and a reviewer can approve, edit, or reject the call. Interrupt-mode rules are wired into the agent's human-in-the-loop middleware automatically and merge with any interrupt_on you pass, so you handle and resume them the same way as tool-call interrupts.

Interrupt pattern anchoring recommendation

Anchor interrupt patterns with a literal leading segment, for example /secrets/** or /projects/*/secrets/**. Bulk tools (ls, glob, grep, and delete on a directory) fire the interrupt when their search subtree could overlap the rule's anchored prefix, so a fully unanchored pattern like /**/secrets conservatively over-fires.

Give your agent this brain