langchain-ai/deepagents · error · RuntimeError

Manual mode callback is unavailable

Error message

Manual mode callback is unavailable

What it means

When the model emits a 'switch_manual' HITL decision, execute_task_textual invokes adapter._on_switch_to_manual. If that callback is None (the UI never wired a manual-mode switch handler), RuntimeError is raised instead of silently ignoring the decision, and the shared turn-error rendering reports the failure.

Source

Thrown at libs/code/deepagents_code/tui/textual_adapter.py:3660

                                    _set_running_unless_deferred(tool_msg)
                                    adapter._sync_tool_widget(tool_msg)
                                for action_request in action_requests:
                                    tool_name = action_request.get("name")
                                    if tool_name in {
                                        "write_file",
                                        "edit_file",
                                        "delete",
                                    }:
                                        args = action_request.get("args", {})
                                        if isinstance(args, dict):
                                            file_op_tracker.mark_hitl_approved(
                                                tool_name, args
                                            )

                            elif decision_type == "switch_manual":
                                if adapter._on_switch_to_manual is None:
                                    msg = "Manual mode callback is unavailable"
                                    raise RuntimeError(msg)  # noqa: TRY301  # shared turn error rendering owns this failure
                                callback_result = adapter._on_switch_to_manual()
                                switched = (
                                    await callback_result
                                    if inspect.isawaitable(callback_result)
                                    else callback_result
                                )
                                if not switched:
                                    msg = "Manual mode could not be persisted"
                                    raise RuntimeError(msg)  # noqa: TRY301  # shared turn error rendering owns this failure
                                decisions = [
                                    cast("HITLDecision", {"type": "switch_manual"})
                                    for _ in action_requests
                                ]

                            elif decision_type == "approve":
                                decisions = [
                                    ApproveDecision(type="approve")
                                    for _ in action_requests

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Register adapter._on_switch_to_manual when constructing the adapter for sessions that can enter manual approval mode.
  2. Handle the 'switch_manual' decision differently (e.g. reject with a message) when the callback is absent.
  3. Update the TUI/adapter integration so the callback is always provided for interactive sessions.

Example fix

// before
adapter = TextualAdapter(..., _on_switch_to_manual=None)
// after
adapter = TextualAdapter(..., _on_switch_to_manual=self._switch_to_manual)
Defensive patterns

Strategy: type-guard

Validate before calling

if adapter._on_switch_to_manual is None:
    decisions = [{"type": "reject", "message": "manual switch unsupported"}]
else:
    decisions = [{"type": "switch_manual"}]

Type guard

def supports_manual_switch(adapter: object) -> TypeGuard[Adapter]:
    return getattr(adapter, "_on_switch_to_manual", None) is not None

Try / catch

try:
    handle_hitl_decision(adapter, decision)
except RuntimeError as exc:
    render_turn_error(f"cannot switch to manual: {exc}")

Prevention

When it happens

Trigger: An interrupt decision of type 'switch_manual' arrives from the agent (e.g. a request to switch out of auto-approve), but the adapter was constructed without _on_switch_to_manual being set.

Common situations: Embedding the textual adapter headlessly or in tests without the full TUI wiring; a UI refactor that dropped the callback registration; running auto-approve flows where manual switching was never expected.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/fcd7c7c07ad2c30e. Report an issue: GitHub.