Grants — scoped, signed, time-bound
Every cross-agent call carries a grant token — an HMAC-signed blob that names who called, whom, against which bucket, with what read/write patterns, at what mode, for how long.
Wire format
<base64url(json(payload))>.<base64url(hmac_sha256(secret, payload))>Payload shape:
{
"grant_id": "abc12345",
"issuer": "main-agent:user-7",
"audience": "graph-agent",
"bucket": "user-7-files",
"mode": "read_write_overlay",
"allow_patterns": ["**"],
"deny_patterns": [],
"outputs_prefix": "outputs/",
"expires_at": 1717440000,
"issued_at": 1717439700,
"nonce": "e7f9..."
}Receiving agents call verify_grant — bad
signature, missing audience, or expired TTL → 403.
Runtime scope negotiation
Skills that opt in via @skill(allow_scope_expansion=True) can call
await ctx.request_scope(...) mid-execution. The orchestrator runs
decide_extension — hard ceilings always
deny (cross-bucket); risk-0 read-only short-TTL auto-approves in
auto-mode; risk-2 (mode upgrade / new write_prefix) always asks the
user.
The flow surfaces on the dashboard as a ScopeCard with reason +
diff (old vs requested patterns) + approve/deny buttons.
Audit trail
Every minted grant and every extension writes a row to GrantAudit,
linked via parent_grant_id. Query the chain:
GET /v1/me/grants/{grant_id}returns the row and every transitive extension in one shot.
Reference
a2a_pack.grants— SDK mint + verifya2a_pack.workspace—WorkspaceClient,install_grant- Concepts: scope negotiation (coming)