Skip to content

Reasoning Action needs user confirmation for tool calls like CoPilot/Claude #2627

Description

@robgruen

Problem

The dispatcher's Reasoning Action (the ReasoningAction fallback that routes complex requests into an LLM reasoning loop) executes tool calls without asking the user for confirmation. Both reasoning adapters auto-approve every tool call:

  • Claude: canUseTool: async () => ({ behavior: "allow" }) + permissionMode: "acceptEdits"packages/dispatcher/dispatcher/src/reasoning/claude.ts (~L584-586)
  • Copilot: onPermissionRequest: approveAllpackages/dispatcher/dispatcher/src/reasoning/copilot.ts (L15 import, L647)
  • Shared adapter: canUseTool: async () => ({ behavior: "allow" })packages/dispatcher/dispatcher/src/reasoning/claudeSDKAdapter.ts (~L155)

The reasoning loop exposes tools including execute_action (runs real, side-effecting dispatcher actions), discover_actions, search_memory, and remember. Because approval is hard-wired to allow-all, the loop can perform side-effecting actions (send email, modify lists, browser automation, etc.) with no human in the loop.

Desired behavior

Before executing a side-effecting reasoning tool call, prompt the user to confirm — the same UX GitHub Copilot and Claude use ("Allow / Deny", ideally with "Allow always for this tool/session"):

  • Show the tool name and a human-readable summary of the arguments (e.g., which action + parameters execute_action is about to run).
  • Offer Allow once / Deny / Allow for session.
  • Deny aborts that tool call and feeds the denial back into the loop so the model can adapt.
  • Read-only tools (discover_actions, search_memory) may run without a prompt; side-effecting tools (execute_action, remember, browser actions) prompt by default.

Suggested approach (implementation notes)

  • Reuse SessionContext.popupQuestion(message, choices, defaultId) (packages/dispatcher/dispatcher/src/execute/sessionContext.ts ~L222) — it already round-trips to the Shell/CLI/RPC clientIO.
  • Add an SDK-agnostic gating hook to ReasoningLoopConfig (packages/dispatcher/dispatcher/src/reasoning/reasoningLoopBase.ts), e.g. onToolApproval?(tool, args): Promise<"allow" | "deny" | "allow_always">. The base already has informational onToolCall/onToolResult callbacks (L31-32); this adds a gating counterpart.
  • Wire real permission callbacks in both adapters instead of allow-all:
    • Copilot: replace onPermissionRequest: approveAllcopilot.ts L647.
    • Claude: replace the canUseTool allow-all — claude.ts ~L586 and claudeSDKAdapter.ts ~L155.
  • Follow the workflow engine's secure-by-default ApprovalFn precedent (examples/workflow/model/src/taskDefinition.ts ~L175, examples/workflow/engine/src/runner.ts; design doc docs/architecture/workflows/engineering/task-policy-temp.md).
  • Add a policy/toggle under the existing reasoning config (config.defaults.yaml ~L14): prompt (default) / allow / per-tool, plus an "allow all" escape hatch for headless runs (mirrors the workflow CLI --allow-all / --dry-run).

The auto-approve posture is already documented as deliberately temporary in docs/architecture/workflows/ir/decisions/0010-copilot-task-family.md §7, which points toward a capability-based permission model.

Acceptance criteria

  • Side-effecting reasoning tool calls prompt for confirmation before executing (Copilot + Claude adapters).
  • The prompt shows the tool name + summarized args.
  • Allow / Deny / Allow-for-session work; Deny aborts the call and the loop continues gracefully.
  • Read-only tools do not prompt (configurable).
  • A config/flag can auto-approve all for headless/automated scenarios, off by default.
  • Works across Shell, CLI, and RPC clients via popupQuestion / clientIO.

Out of scope

  • Full capability-based security model (tracked separately per decision 0010 §7).
  • Per-agent permission policies beyond the reasoning loop.

Metadata

Metadata

Assignees

No one assigned

    Labels

    dispatcherDispatcher / request routing engineenhancementNew feature or requestreasoningReasoning Action / LLM reasoning loopsecurity

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions