Skip to main content
By default, an agent token (agent_* or sess_*) is single-agent-scoped — the agent can only act on its own resources. Reading sibling agents, mutating their state, or moving funds between agents requires an explicit, audited capability grant issued by a tenant owner. This is the same model as Unix sudo: low-friction baseline, traceable elevation, no global admin keys floating around in agent configs.

The three scope tiers

tenant_read

Read any sibling agent — wallets, balances, transfers, audit. Standing grants capped at 60 minutes.

tenant_write

Mutate sibling agent state — policies, skills, status, freeze. Standing grants capped at 15 minutes.

treasury

Move funds — settlement on x402/pay, acp/checkouts/:id/complete, ap2/mandates/:id/execute, ucp/checkouts/:id/complete, and sibling send-usdc. One_shot only (DB-enforced).
The default agent tier is implicit — every agent token already has it. You only request elevation when you hit a 403 SCOPE_REQUIRED.

Lifecycle

Every transition writes a row to auth_scope_audit with the actor, route, and environment. Audit rows persist past agent deletion — you always have a complete trail.

For the calling agent

Detect that you need a scope

Hit any privileged endpoint with default scope and you’ll get:

Request elevation

Response (202):

Poll for the decision

Possible terminal states:

Inspect current scope

Returns current_scope (your effective tier right now) and the active grants applicable to the current call.

Lifecycle gotchas

one_shot is one shot. First successful gated call consumes the grant. Subsequent calls 403 until you request again.
Concurrency-safe. Gated routes await recordScopeUse before responding, so two parallel calls can’t share one one_shot — exactly one wins.
No cache lag. The auth middleware re-queries auth_scope_grants on every request, so revoke and consumption reflect immediately.

For tenant owners

Approve / deny via the dashboard

Open /dashboard/security/scopes (Settings → Scope Grants). Pending requests appear at the top with the agent name, requested scope, lifecycle, purpose, and timestamp.
  • tenant_read approves with a single click.
  • tenant_write and treasury open a typed-confirmation modal — you must type the agent’s name to enable the green button. Per-scope risk callouts (move funds, mutate sibling agent state) make the blast radius explicit.
  • Deny prompts for a reason. The reason surfaces back to the agent’s scope_status poll.
Below pending: every active grant in the tenant with a Revoke button. Below that: a filtered audit feed with action chips, agent picker, and free-text search.

Issue a standing grant directly

Click + Issue grant on the Active grants section. Pick agent, scope, lifecycle, duration, and purpose. Treasury auto-locks lifecycle to one_shot.

Per-agent view

/dashboard/agents/[id] has a Scopes tab showing just that agent’s grants + audit history. Useful for ops review of a single agent.

API path for server-to-server automation

Tenant API keys (pk_*) carry full owner-equivalent privilege and can drive the lifecycle without a JWT login.
When issuing via API key, granted_by_user_id is auto-resolved to the tenant’s primary owner so the “every elevation has a real human approver” invariant holds. The audit row records actor_type='api_key' + granted_via_api_key: true for traceability.

Auto-cascading revokes

Two operator actions wipe every active grant for an agent: Audit rows for cascaded revokes carry request_summary.reason: "kill_switch_cascade" so the trail distinguishes operator-driven revokes from manual ones.

Environment scoping

auth_scope_grants and auth_scope_audit carry an environment column populated from the target agent’s env (not the issuer’s). All read endpoints default to caller-env scoping; pass ?env=all to opt out (intended for cross-env tooling, not the dashboard). A live-env dashboard never shows test-env scope events and vice versa. Issuing a grant via a tenant API key with X-Environment: live for a test-env agent still tags the grant test — env follows the agent.

Currently gated routes

Same-agent calls (where caller’s actorId equals the target id) bypass the gate. Tenant API keys + JWT users are unaffected — their existing tenant-wide auth is preserved.

Failure-mode debugging

Hit GET /v1/auth/scopes/active. If current_scope is agent but you expected an elevation:
  • Grant might be consumed (one_shot used) or expired
  • parent_session_id might not match your current session — session-anchored grants only apply to the originating sess_*
  • Env mismatch — agent is test, calling endpoint with X-Environment: live
The logged-in user might be on a different tenant than the requesting agent. JWT auth derives tenant from user_profiles.tenant_id. The audit endpoint also defaults to env-scoping — switch dashboard env or use ?env=all.
Should not happen — every request re-queries scope. If you see this, file a regression.