a2a docsconceptsgrants

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