# Progressive Scope

> Pattern 02 · Granting access · part of Agent Consent Patterns.
> Human page: https://agentconsent.dev/patterns/progressive-scope/

Start minimal and ask for more at the moment of need: in context, tied to the action the agent is blocked on, so the user consents to something concrete instead of a pile of hypotheticals.

## Problem

Upfront consent forces a bad trade. To avoid interrupting the user later, products request every permission the agent might conceivably need at connect time, so the user is asked, before any work has happened, to reason about capabilities in the abstract. Faced with a long list of maybes, people do one of two things: rubber-stamp it all (over-granting, because refusing feels like breaking the tool), or bail (under-adopting, because the ask looks alarming). Either way the grant is made in the worst possible context: no task in front of them, no way to judge whether a scope is actually warranted. The permission the agent needs and the fifty it doesn't arrive looking identical.

## Solution

Grant what the task needs now, and let the agent come back for more when it actually hits a wall. The escalation request appears in context, at the moment of need, and carries everything required to judge it:

- **The specific capability**: exactly one new scope, with its access level, not a re-grant of everything.
- **The reason, tied to the blocked action**: "to reply to the vendor, it needs to send email," not "the assistant would like more access."
- **The standing grant it builds on**: what the agent already has, so the ask reads as one additive step.

The decision is about breadth: allow the capability for just this blocked action, escalate it into the standing grant, or refuse. "Allow always" is styled subordinate to "allow once" on purpose. The interface should never make over-granting the path of least resistance.

> **Live demo** — an interactive Progressive Scope demo runs on the page: https://agentconsent.dev/patterns/progressive-scope/

The demo is the `ProgressiveScope` component from `@agentconsent/react`. Change the `access` prop to re-weight the request; the accent and badge shift from read to write to delete.

## Anatomy

> **Anatomy** — a labeled breakdown of the Progressive Scope component's parts is shown on the interactive page: https://agentconsent.dev/patterns/progressive-scope/

## When to use it

- **Capabilities the agent needs rarely or conditionally**: the escalation it might need once a month shouldn't be in the upfront grant everyone sees.
- **High-consequence scopes** (write, delete, spend) that deserve a decision made against a real action rather than a hypothetical one.
- **Adoption-sensitive onboarding**, where a short initial ask gets people in and trust is earned before the bigger permissions are requested.

## When not to use it

- **Core capabilities the agent needs immediately and always.** Deferring the one scope that makes the product work just adds a speed bump on the happy path. Grant it upfront with Scoped Grant.
- **Hot loops.** If the agent will hit the same wall dozens of times a session, a per-hit escalation prompt is its own fatigue; reach for a standing [Authority Boundary](/patterns/authority-boundary/) or [Spend & Rate Limits](/patterns/spend-rate-limits/).
- **Approving a specific action.** Progressive Scope grants a *capability* in context; approving the specific send or payment is Action Preview and the Irreversibility Gate.

## Real-world examples

- **iOS and Android runtime permissions.** The mobile OSes abandoned install-time permission walls a decade ago: camera access is requested the moment the user taps the camera feature, in context, with the blocked task visible behind the prompt. Android's "Only this time" adds the once/always split this pattern insists on.
- **Google OAuth incremental authorization.** Google's OAuth flow supports requesting additional scopes later (`include_granted_scopes`), so an app can connect read-only and ask for send access the first time the user actually tries to send. Prior grants carry over, and the user consents only to the delta.
- **Slack app scope escalation.** When a Slack app needs a scope it wasn't installed with, it re-requests authorization listing just the added scopes, and the approver sees the increment in context rather than re-reviewing the whole grant.

*Annotated screenshots of these flows are being collected. Products are credited, annotations follow the site-wide callout conventions, and any screenshot is removed on request. See [About](/about/).*

## Accessibility

- **Inline mode** renders a `role="group"` labeled by the request title, so screen readers announce the ask in context without seizing focus. An escalation prompt must not yank the user out of what they were reading.
- **Modal mode** (`asModal`) is a Radix `AlertDialog`: focus is trapped, `role="alertdialog"` is named by the title, and initial focus lands on **deny**. The least-escalation choice is the resting default, never an allow.
- **Escape denies**, routed through `onDeny` like the button. There is no ambiguous dismissal that leaves the escalation half-granted.
- **Access level is text, never color alone**. The request badge reads "Write"/"Delete"; the accent stripe is redundant reinforcement, not the only signal.
- The three decisions are ordinary buttons in a logical order (deny, then the two grants), each with a distinct label, "just this once" and "always allow" are never collapsed into one ambiguous "Allow".

## Anti-patterns

- **Upfront everything.** Requesting the union of all capabilities at connect time is the pattern's antithesis. If the grant screen lists scopes the task hasn't reached yet, they don't belong there.
- **The unexplained interrupt.** "Inbox Assistant wants more access. Allow?". An escalation with no capability named and no reason given is worse than the upfront list, because now it's also a surprise.
- **Nagging.** Re-prompting for the same denied scope every few minutes trains users to allow it just to make the prompt stop. A denial should stick for the session unless the user re-engages.
- **Defaulting to always.** Pre-selecting or visually favoring "allow always" turns a momentary need into a permanent grant by inertia. Make "once" the easy answer and "always" the deliberate one.
- **Escalation as a dark pattern.** Manufacturing a fake "wall" to extract a broad grant mid-task is the abusive version of this pattern. The request must reflect a real, current need.
- **Escalating beyond the principal.** If the capability the agent hits a wall on exceeds what the *user* may do, the honest response is a hard denial, not an "allow". An agent must never escalate past the authority of the person it acts for.

## Code

```tsx
import { ProgressiveScope } from "@agentconsent/react";
import "@agentconsent/react/theme.css";

<ProgressiveScope.Root
  access="write"                       // re-weights the request badge + accent
  onAllowOnce={() => allowForThisAction()}
  onAllowAlways={() => addToStandingGrant()}
  onDeny={() => refuse()}
>
  <ProgressiveScope.Header>
    <ProgressiveScope.Icon>✦</ProgressiveScope.Icon>
    <ProgressiveScope.Title>
      Inbox Assistant needs to send email
    </ProgressiveScope.Title>
  </ProgressiveScope.Header>

  <ProgressiveScope.Reason>
    To reply to the vendor, it needs to send email. So far it has only drafted.
  </ProgressiveScope.Reason>

  <ProgressiveScope.Request
    label="Send email on your behalf"
    description="Compose and send new email as you, in this thread."
  />

  <ProgressiveScope.Current>
    Inbox Assistant can already read this thread.
  </ProgressiveScope.Current>

  <ProgressiveScope.Actions>
    <ProgressiveScope.Deny>Not now</ProgressiveScope.Deny>
    <ProgressiveScope.AllowAlways>Always allow</ProgressiveScope.AllowAlways>
    <ProgressiveScope.AllowOnce>Just this once</ProgressiveScope.AllowOnce>
  </ProgressiveScope.Actions>
</ProgressiveScope.Root>
```

Pass `asModal` with `open`/`onOpenChange` to render as a focus-trapped alert dialog when the escalation must block the task. The once/always split here is the in-context minimum; the full duration ladder ("for this task", "for an hour") is the Consent Memory pattern. All parts are unstyled primitives with `data-acp` attributes; skip `theme.css` and style them yourself, or override the `--acp-*` tokens to retheme.
