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 between sibling agents —
send-usdc, fund-eoa. One_shot only (DB-enforced).agent tier is implicit — every agent token already has it. You only request elevation when you hit a 403 SCOPE_REQUIRED.
Lifecycle
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
202):
Poll for the decision
status | What to do |
|---|---|
pending | Wait. Operator hasn’t decided yet. Polling once every 5–15s is fine. |
approved | Retry the privileged call. The grant is live. |
denied | Surface denial_reason to your model. Don’t retry the same scope without a new ask. |
Inspect current scope
current_scope (your effective tier right now) and the active grants applicable to the current call.
Lifecycle gotchas
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_readapproves with a single click.tenant_writeandtreasuryopen 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_statuspoll.
Issue a standing grant directly
Click + Issue grant on the Active grants section. Pick agent, scope, lifecycle, duration, and purpose. Treasury auto-locks lifecycle toone_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.
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:| Action | Cascade |
|---|---|
Kill switch (/v1/agents/:id/kill-switch) | Suspends the agent + revokes all active grants. Response includes scopeGrantsRevoked: N. |
Wallet freeze (/v1/agents/:agentId/wallet/freeze) | Locks the wallet + revokes all active grants. |
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
| Method | Path | Required scope (sibling only) |
|---|---|---|
| GET | /v1/agents/:id | tenant_read |
| PATCH | /v1/agents/:id | tenant_write |
| POST | /v1/agents/:agentId/wallet/freeze | tenant_write |
| POST | /v1/agents/:agentId/wallet/unfreeze | tenant_write |
| PUT | /v1/agents/:agentId/wallet/policy | tenant_write |
| POST | /v1/agents/:id/smart-wallet/send-usdc | treasury |
| POST | /v1/agents/:id/fund-eoa | treasury |
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
`SCOPE_REQUIRED` 403 when I expect to have permission
`SCOPE_REQUIRED` 403 when I expect to have permission
Hit
GET /v1/auth/scopes/active. If current_scope is agent but you expected an elevation:- Grant might be
consumed(one_shot used) orexpired parent_session_idmight not match your current session — session-anchored grants only apply to the originatingsess_*- Env mismatch — agent is
test, calling endpoint withX-Environment: live
Approve button doesn't appear / dashboard empty
Approve button doesn't appear / dashboard empty
Stuck-cache after revoke
Stuck-cache after revoke
Should not happen — every request re-queries scope. If you see this, file a regression.
Related
- Agent tokens — the
agent_*bearer credential - Ed25519 sessions —
sess_*per-session tokens - Kill-switch — break-glass with cascade
- Wallet policies — same-agent spending caps (lower tier than
treasuryscope)
