Skip to content

EPIC: approvals interrupt for things that carry no consequence, and every interruption dead-ends the turn #558

Description

@oxoxDev

The problem

Ask an agent to generate anything and the operator gets a wall of approval cards — on the task card and in the Approvals tab — for calls that carry no consequence worth interrupting a person over. Reading a mailbox asks. Writing a scratch file asks. Listing a repository's pull requests asks.

That is not a rendering bug. Four separate design decisions compose into it, and each one is defensible alone:

  1. The default tier parks every write and every external read. default_policy_mode() is supervised (src/company/types.rs), and no shipped preset except signals_opportunity_studio sets [policy] at all — so every company created from a preset, including the marketing one, runs supervised.
  2. Under supervised, anything classified Reach::Consequence parks. That includes file_write, edit, apply_patch, memory_store, workspace_write, run_workflow, mcp_call_tool — and every Composio read.
  3. A parked call is refused, not suspended. openhuman resolves RequireApproval inline and fail-closed: the tool call is blocked, the model is handed a refusal, and the turn continues without the result. Approving afterwards mints a single-use grant and the agent has to be re-dispatched to re-issue the same call. So each approval costs a round trip and the work visibly dead-ends in between.
  4. There is no tier between the two extremes. supervised parks every consequence; full parks nothing but the always_approve list. An operator who wants "don't ask me to read things, do ask me before you send money" has no setting to express that — and cannot change the mode from the console at all, because [policy].mode is manifest-only.

What we want instead

The model Claude Code uses, which the team already lives in daily:

  • Reads never prompt. Not local reads, not reads through a connected third-party account.
  • Writes inside the agent's own sandbox never prompt. Scratch files, patches, the company's own memory.
  • Only consequence that leaves the building prompts — pushing to a remote, sending mail or a message, publishing, spending money, moving cash, filing something, changing an identity.
  • A prompt is a decision, not a dead end. Approving resumes the work rather than requiring the agent to be asked again.
  • Consent persists. An operator who has approved a shape of call once should be able to say "and from now on" durably, the way an allowlist entry does — not re-approve it every card and not have it silently expire.

The rule of thumb from the request, stated exactly: any Composio write may ask; no Composio read should.

Why this should be cheap

Most of the machinery is already built and well-factored. src/policy/consequence.rs already declares every tool's reach explicitly, already has a coverage test that fails when a tool is undeclared, already reads Composio consequence from the action slug rather than the tool name, and already has ~660 provider-classified actions tagged Read/Write/Admin vendored with openhuman. The classification we need is present — it is the verdict attached to it that is wrong, plus a missing tier and a missing console control.

Sub-issues

Split so the cheap, high-value fix can land alone and the deep one can be scheduled honestly.

Metadata

Metadata

Assignees

Labels

cluster:approvalsHuman-in-the-loop boundary, policy, and approval surfacesenhancementNew feature or requestpriority: p1Next. Wrong behaviour a user will hit, or a security weakness behind a condition.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions