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:
- 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.
- 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.
- 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.
- 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.
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:
default_policy_mode()issupervised(src/company/types.rs), and no shipped preset exceptsignals_opportunity_studiosets[policy]at all — so every company created from a preset, including the marketing one, runs supervised.Reach::Consequenceparks. That includesfile_write,edit,apply_patch,memory_store,workspace_write,run_workflow,mcp_call_tool— and every Composio read.RequireApprovalinline 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.supervisedparks every consequence;fullparks nothing but thealways_approvelist. 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].modeis manifest-only.What we want instead
The model Claude Code uses, which the team already lives in daily:
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.rsalready 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.