Replies: 29 comments 16 replies
|
This aligns closely with what we've been building at Tenuo. We opted for capability tokens (Warrants) with mandatory proof-of-possession and offline verification, specifically to address the prompt injection threat model where the agent itself cannot be trusted. Here's what skill registration looks like in our A2A integration: server = A2AServer(
name="Paper Search Agent",
url="https://paper-search.example.com",
public_key=agent_key.public_key,
trusted_issuers=trusted_issuers,
require_warrant=True,
)The constraint is enforced by the runtime, not the LLM. @server.skill("fetch_url", constraints={"url": UrlSafe(allow_domains=["arxiv.org", "scholar.google.com"])})
async def fetch_url(url: str) -> Dict[str, Any]:
# This code NEVER runs if the warrant doesn't authorize the domain
...And attenuation for delegation: search_warrant = root_warrant.attenuate(
signing_key=orchestrator_key,
holder=paper_search_key.public_key,
capabilities={
"fetch_url": {"url": UrlSafe(allow_domains=["arxiv.org"])},
},
ttl_seconds=300,
)A few thoughts on your open questions:
This gives us:
A serialized warrant looks like: Or inspected:
Actually, we encountered so many edge cases building this (key rotation, delegation depth limits, revocation propagation, constraint semantics) that we drafted a formal specification for Agent Capabilities & Warrants. We're looking for partners to help refine it into an open standard so we don't all reinvent the wheel. If you're interested in co-authoring, critiquing the spec, or testing against your use cases, let me know. |
|
The capability attenuation model in this SEP converges with something we've been shipping in production. Agent Passport System uses Ed25519-signed delegation chains where each sub-delegation can only narrow scope — never widen it. The invariant is structural: How the pieces map:
The key architectural choice: capability tokens as standalone objects (Tenuo warrants, CBOR) vs capability as a property of a signed delegation chain (APS approach). Both enforce the same narrowing invariant. The tradeoff is portability vs chain-of-custody — standalone tokens are lighter to pass around; delegation chains carry the full provenance from human principal to leaf agent. For the "canonical authorization fixture" @pshkv proposed, we already have adversarial test vectors for delegation narrowing — scope escalation attempts, expired delegation replay, revoked parent with live child, cross-scope leakage. Happy to contribute those to a shared fixture set. SDK: https://github.com/aeoess/agent-passport-system (103 modules, Apache-2.0) |
|
The key thing this thread gets right is moving authority from the session to the task. That is the security boundary that actually holds up under delegation. One implementation detail I would make explicit in the SEP is auditability of attenuation. If a caller requests documents:read for Q1 reports and later attenuates to a single handle plus retrieve only, the downstream agent should be able to reconstruct the full narrowing path without calling home. That means the attenuation artifact needs to carry parent linkage, expiry, and the exact reduced operation set in a form that survives offline review. The other practical guardrail is default TTL. I would bias shorter than most people expect. In our tests, long-lived delegation artifacts keep turning routine prompt injection into durable authority reuse. For task-scoped capability flows, 60 to 300 seconds is the range that seems to preserve usability while materially shrinking replay value. We keep seeing the same pattern across MCP and A2A: if ambient authority survives longer than the task, someone eventually finds a way to reuse it. More examples are in the harness here if useful: https://github.com/msaleme/red-team-blue-team-agent-fabric |
|
@msaleme — the TTL point is right and matches what we see. In our gateway, delegations default to task-scoped with On auditability of attenuation: APS delegation chains carry parent linkage natively. Each @MoltyCel — the 3-layer composition is the right framing:
Layer 3 is the one you can't fake with a test harness. The cold-start compound point is exactly right — passport grade + interaction history together occupy a different trust position than either alone. The behavioral anomaly vectors you're offering (good-for-N-then-deviate, gradual scope creep within formal limits, synthetic positive inflation) are the hard cases that our current adversarial fixture pack doesn't cover. Those are production-derived patterns, not something you can generate from protocol invariants alone. Let's add them to the shared fixture. Same gist, separate section for behavioral vectors. |
|
SINT Protocol is a full implementation of what this SEP proposes — worth mapping concretely. SEP concept → SINT implementation:
On token format: We went with signed JSON (Ed25519 + @noble, audited) rather than Biscuit/Macaroons, but the attenuation semantics are identical. On TTL — agree with On multi-hop revocation: SINT cascade-revokes. When a parent token is revoked, all descendants at any depth are invalidated synchronously via On Phase 1→2→3 migration is the right path. SINT is at Phase 2 — |
|
This SEP is the right framing — task-scoped capability vs session-ambient authority is exactly the enforcement gap SINT Protocol RFC-001 addresses. RFC-001 as the enforcement layer for this SEP The SEP proposes capability advertisement in AgentCard and task-scoped token issuance. RFC-001 defines the normative enforcement inside the agent server — evaluating those tokens at the skill boundary: A2A handles who is calling. RFC-001 handles what they are allowed to do. On the specific SEP design questions: Capability namespace: RFC-001 defines an Token scope granularity: The SEP task-scoped vs session-scoped distinction maps directly to RFC-001 Irreversible capabilities: RFC-001 Monotonic narrowing: RFC-001 RFC-001: https://github.com/sint-ai/sint-protocol/blob/main/docs/rfcs/RFC-001-policy-bundle.md Happy to contribute the enforcement layer specification as a normative annex to this SEP, or as a companion standard that the SEP references. The capability advertisement (SEP) and capability enforcement (RFC-001) together form a complete stack. |
|
The thread has the advertisement and attenuation half well covered (Warrants, APS, SINT all model tighten-only delegation). The harder half in production is the one @msaleme raised: proving the narrowing was actually enforced, reconstructable offline without calling home. That's the piece we run live. AlgoVoi issues a signed scoped-authorization receipt at the resource boundary. The capability token says what may happen; the receipt is verifiable evidence of what did. Advertised on a live agent card:
Two design points that matter for this SEP, both live:
The receipt binds AlgoVoi (chopmob-cloud) -- docs.algovoi.co.uk/compliance |
|
One distinction that may help this SEP stay implementation-neutral: separate the capability artifact from the exercised-authority record. A capability or attenuated delegation answers "what may this caller do for this task?" The resource boundary still needs to emit a decision record for "what happened when the caller tried to exercise it?" For conformance, I would make both sides explicit:
That keeps the token/delegation layer from having to prove final execution by itself. The delegation says what authority is portable; the decision record says how that authority was resolved at the boundary. Both should be replay-verifiable from retained bytes, because the useful audit question is usually asked after the task, not during it. |
|
This separation that @rpelevin and @msaleme are drawing, between the capability artifact and the exercised-authority record, is the right cut, and it maps onto something we have already shipped in the negotiation layer rather than the authorization layer. In Concordia (an open agent-negotiation standard, Python and JS SDKs) the two halves you are describing exist as distinct signed objects: an ApprovalReceipt records that a party authorized a scoped action under stated constraints, and a FulfillmentAttestation records what was actually performed against it, with both bound by a ValidityWindow and verified with Ed25519 over a canonical encoding. Attestations carry behavioral signals and references rather than re-stating the underlying terms, which keeps the audit trail reconstructable offline without leaking the deal content. To be precise about scope: this is the agreement-and-receipt layer, not the capability-grant layer the SEP defines, and it is designed to compose with A2A without requiring it (and A2A never needs it). If it is useful I am happy to map the receipt and attestation shapes onto the SEP's attenuation chain so the "what may happen" object and the "what did happen" object reference each other by hash. Would that mapping be helpful to the SEP, or is the exercised-authority record already spoken for? |
|
The exercised-authority record is not already spoken for in a way that should block this SEP. I would treat it as a companion artifact, not part of the capability grant itself. The capability grant answers: what authority is portable for this task or delegation? The decision / fulfillment record answers: what happened when that authority was exercised at a resource boundary? Keeping those separate lets multiple receipt or attestation formats compose with the same attenuation chain. The SEP only needs enough structure to make the reference stable:
That gives Concordia-style ApprovalReceipt / FulfillmentAttestation, scoped authorization receipts, and other evidence formats a common attachment point without forcing A2A to choose one full audit model. For conformance, the useful test is cross-object binding: a verifier should be able to confirm that the exercised-authority record references the exact capability artifact and request bytes, and that a denied or expired attempt still produces a terminal decision record rather than disappearing as absence-of-execution. |
|
This is the right split. I would keep the SEP-owned part deliberately small: not a receipt schema, but a binding contract that any exercised-authority record can satisfy. For conformance, the minimum portable shape seems to be:
The important rule is that the decision record must not be allowed to float free from the capability chain. A verifier should recompute that the exercised-authority record points to the exact capability artifact and the exact request bytes it evaluated. I would also make denied and expired attempts positive conformance cases. They should produce terminal records with the same binding fields, not disappear because no execution happened. Otherwise the audit path only covers successful delegation and misses the security cases this SEP is trying to make visible. That keeps A2A implementation-neutral: one implementation can attach a fulfillment attestation, another can attach a scoped authorization receipt, and another can use a simpler decision log. The shared invariant is cross-object binding, not one mandated audit format. Boundary: standards and conformance feedback only; no claim about using A2A, implementing this SEP, or running code. |
|
This binding contract is the right shape, and the two rules you added are what make it hold: a deny or expired attempt must produce a terminal record, and the record must not float free from the capability chain. Both fall out cleanly if the decision is itself a content address rather than a row that points at one. Concretely: compute the decision identifier as the SHA-256 of the canonical (RFC 8785 JCS) object over the binding fields you listed (capability_digest, request_digest, boundary_id, decision, verifier, policy_version). Then the identifier is its own proof. A verifier recomputes it from those exact fields, so it cannot reference a capability or request it did not evaluate, and a deny recomputes from its bytes exactly like an allow, so a blocked attempt is a positive recomputable record rather than an absence. The categorical decision is a bound field, not metadata, which is what makes deny, expired, widened, and replayed all first class conformance cases. This is shipped and provable today, if a worked binding example helps the SEP have something to test against. Our open decision made before execution content addresses {agent identity, spend authority, policy in force, verdict}, and a published keystone recomputes the whole chain end to end, byte for byte, in Python and an independent Node implementation, offline with no callback: https://docs.algovoi.co.uk/spend-decision-chain (corpus with runners: https://github.com/chopmob-cloud/algovoi-jcs-conformance-vectors). It is a payments decision, but the property the SEP needs (a decision record bound to the exact capability and request bytes, deny as a terminal record, recomputable in any language) is what it demonstrates. One note on the fields, for determinism across languages: keep any multi value field in the record (a verifier list, a scope set) as a JSON array under the pin rather than a delimited string. RFC 8785 canonicalises arrays deterministically by preserving element order, so pin the field semantics (order is semantic versus set) rather than a separator, and two honest implementations cannot fork the bytes. |
|
Thanks @pshkv and @rpelevin. The companion-artifact framing is exactly right, and the small binding contract rather than a mandated receipt schema is the part I would not give up. I will map Concordia onto it rather than ask A2A to adopt a format. Concordia already carries the two halves as distinct signed objects: ApprovalReceipt → the agreement/grant reference (points at your capability_digest and request_digest, bound to a ValidityWindow); FulfillmentAttestation → the optional fulfillment_ref / post-action reference. Both are Ed25519 over a canonical encoding, offline-verifiable from retained bytes, cross-bound by hash. Attestations carry behavioral signals and hash references, never the underlying terms, so the audit path stays reconstructable without leaking deal content. Where an approval is withdrawn, a RevocationRecord is the terminal record. |
|
is everyone here using an LLM to write for them? The discussion hardly makes any sense |
|
This is the interop test shape I would want the SEP to preserve. The conformance target should not be a particular receipt format. It should be the recomputable binding between the capability artifact, the attempted request, the boundary decision, and the terminal record. A small portable test could be:
That keeps the SEP neutral on whether the downstream artifact is a scoped authorization receipt, fulfillment attestation, or simpler decision log. The shared requirement is that a verifier can recompute the binding from retained bytes and prove the record did not float free from the capability chain or the request it evaluated. |
|
That revocation property is worth making explicit in the interop test because it is the difference between a content-addressed decision record and a live permission cache. I would add one negative vector:
That keeps revocation propagation-free while still making the denial replay-verifiable. The child artifact does not need to mutate; the verifier proves that current authority re-derives through every ancestor before the request crosses the boundary. |
|
Following up on the binding contract from earlier to pin down revocation, since it is the one case where a content-addressed decision record has to do something a permission check does not. The record we described is a content address over the binding fields: the capability digest, the request digest, the boundary id, the verifier and policy version, and the categorical decision. Because the identifier is the SHA-256 of the canonical (RFC 8785 JCS) object over exactly those fields, it is its own proof. A verifier recomputes it from the bytes, so it cannot name a capability or a request it did not evaluate, and a deny or an expired attempt recomputes from its bytes exactly like an allow. Deny, expired, widened, and replayed are first-class recomputable records rather than absences. Revocation is worth stating explicitly because the record is immutable and the revocation happens after it. The resolution is not to rewrite the record but to bind the ancestor authority state into the decision preimage. The decision object carries, alongside the capability chain, the ancestor status it re-derived through: for each ancestor, its digest, its status, the digest of the status source that was read, and the observation time. The decision id content addresses over that. Three properties fall out:
The general point for the SEP: keeping the ancestor status read inside the content-addressed preimage, rather than as an external lookup the verifier trusts, is what makes the whole revocation path recomputable instead of asserted. It keeps the SEP neutral on the downstream artifact, receipt or attestation or a plain decision log, while making revocation a positive, provable terminal state rather than a silent cache miss. |
|
The direction this has taken, a recomputable binding contract rather than a mandated receipt schema, is the right one, and the revocation refinement is the part worth getting exactly right. Sharing where Concordia already sits on it, as compose-with, not a claim on the SEP's shape. Concordia carries the decision and the terminal record as distinct signed objects, both Ed25519 over an RFC 8785 canonical encoding, offline-verifiable from retained bytes. An ApprovalReceipt binds the grant and request references and a ValidityWindow, a FulfillmentAttestation is the optional post-action reference on the same join key, and a RevocationRecord is the terminal record when authority is withdrawn. Attestations carry behavioral signals and hash references, never the underlying terms, so the audit path stays reconstructable without leaking deal content. On the revocation point rpelevin and chopmob-cloud pinned down: binding the ancestor status read into the decision preimage rather than rewriting the downstream artifact is the right call, and it matches how Concordia's cascade verifier resolves a revocation before it trusts a receipt. The one line I would add to the conformance split is that the record proves what was read, and source authority stays on the verifier's side as policy, which keeps the SEP neutral on whether the downstream artifact is a receipt, an attestation, or a plain decision log. Happy to publish a worked ApprovalReceipt plus RevocationRecord vector against the six-field binding shape so the SEP has a receipt-side reference to test against alongside the payments-side ones. |
|
That split runs as a fixture now: node examples/conformance-split.mjs in github.com/ERC8312/a2a-bound-authorization — the three parts named, and the three negative cases failing closed with a distinct per-read outcome each (not_authoritative, unreadable, mismatch; the re-checker takes the verifier's policy as {isAuthoritative, readAt} and reports one outcome per bound read). The fixture's source is deliberately a plain append-only hash-linked log rather than a consensus-fixed one, because the fork case is the one worth demonstrating end to end: a colluding source presents an internally consistent history in which the ancestor was never revoked — every hash link recomputes — paired with a forged record claiming the status that past would answer. Record recomputability raises no alarm (the forged record's id recomputes from its own bytes), and a verifier doing linkage checks alone accepts it; the head anchor held independently of the source is the one thing that refuses it. The fixture keeps authority and history as two separate gates so each failure mode stays observable on its own — your point that for a non-consensus source the checkpoint is where authority can attach as well stands as the natural deployment shape, with the pin doing double duty. The coordinate is the source's own ordering throughout — a sequence number in the fixture, a block height on a chain — never a wall clock, and a coordinate the pinned history has not fixed yet is refused rather than defaulted, so a record cannot borrow support from a future the source has not written. One detail of the third case seemed worth making explicit in the fixture: "tamper or drift, new decision required" cuts both ways, and content addressing keeps them apart. After a mismatch, an honest re-derivation at the same coordinates reproduces the original record, id and all — altered bytes cannot be patched back without landing on the honest id. Drift is the other branch: a further status write means a fresh derivation observes a new coordinate and yields a genuinely new record, while the old one stays supported at its own coordinates once the verifier's pin advances. The record proves what was read; the policy proves who could speak for the element; the pinned history proves the read is replayable — none of it rides on trust in the record. |
|
Yes - a worked ApprovalReceipt plus RevocationRecord vector would be useful if it stays a receipt-side reference, not a SEP-owned schema. The shape I would want to see is:
For the revocation case, the useful negative vector is:
The invariant is that the old approval receipt can remain historically valid while no longer being sufficient for current execution. Revocation should create a new terminal decision record, not mutate the child artifact or rely on a cache miss. That would give the SEP a receipt-side vector alongside the plain decision-log fixture: same six-field binding contract, different downstream artifact. |
|
Receipt-side vector is up against the six-field shape: https://github.com/eriknewton/concordia-protocol/tree/main/docs/interop/a2a-1404-receipt-revocation-vector One command verifies it. An ApprovalReceipt and its parent RevocationRecord, both Ed25519 over RFC 8785 JCS. It runs @rpelevin's negative vector: revoke the parent with one status write, re-verify the unchanged child, get a terminal deny. The ancestor status read is bound into the decision preimage the way @chopmob-cloud described, so the denial recomputes rather than asserts. The bit I want a second pair of eyes on: I bind the ancestor read into the preimage, so the record says what status I read and who owns that status stays your policy. Tell me if that split holds against the running references here. |
|
We've converged on something small enough to write down, and it may be worth turning into a conformance section the SEP can carry. The mechanism looks settled to me; what's left is to state the surface precisely and point at what already runs. If it's useful, I've sketched the consensus below as a strawman — for @kurt-r2c to take, leave, or reshape — all of it lifted from this thread rather than invented, so people can react before there's any prose: Binding contract (SEP-owned). The six-field decision object — capability_digest, request_digest, boundary_id, decision, verifier, policy_version — with decision_id = SHA-256(JCS(decision_object)) over RFC 8785. Recomputable from the bytes, offline, no callback. Verifier's required joins (SEP-owned). The three gates @chopmob-cloud framed in prose and @rpelevin named — record recomputability, source authority, source history — and @rpelevin's four negative controls: recomputable-but-unauthoritative; authoritative-but-the-coordinate-isn't-in-the-pinned-history; valid-revocation-but-the-decision-doesn't-commit-to-the-ancestor-read; changed-after-publication is a new id, not an edit. Over @chopmob-cloud's evidence shape: each ancestor read binds its digest, its status, the digest of the status source read, and the observation time — sharpened in-thread to the source's own coordinate, a block height or sequence number, never a wall clock — so a denial recomputes rather than asserts. With @rpelevin's invariant on top — an approval stays historically valid at its own coordinate while no longer sufficient for current execution, which re-derives through the ancestor read and emits a new committed deny. Not SEP-owned: the artifact shape. Receipt, attestation, or plain decision log — each family carries the contract its own way and verifies as a full independent object. The SEP mandates the contract and the joins, never the schema. Two independent families already exercise it: @eriknewton's Concordia receipt vector and the decision-log fixture (examples/conformance-split.mjs in github.com/ERC8312/a2a-bound-authorization), both landing on the same decision_id from the same object, cross-checked in CI. It could live as a short conformance section — whether that's a PR from me or from whoever's closest to the SEP is @kurt-r2c's call — kept CC0, with every normative line backed by one of the two running references so nothing is asserted that isn't executable. Corrections welcome first, especially on the join wording, since that's the load-bearing part. And if anyone would rather hold the pen, I'm equally happy to just keep the fixture side current. |
|
This captures it faithfully, and I am glad to have it become a CC0 SEP conformance section with the attribution as you have written it. Keeping every normative line backed by a running reference is the right constraint to hold. One sharpening on the join wording, since you flagged that as the part to get exactly right. The property that a denial recomputes rather than asserts lives entirely at the re-derivation step, and it reads more precisely as a requirement on the terminal decision object than on the verifier's behaviour: when current authority no longer holds, the boundary re-derivation emits a new decision object whose preimage commits to the ancestor read it went through, the element digest, the status, the digest of the status source that was read, and the source's own coordinate. That new object hashes to a new decision_id, so the denial is itself a recomputable record, and the original approval remains a valid record at the coordinate it was issued while ceasing to authorize execution now. Put that way, the negative control for a decision that does not commit to the ancestor read becomes exact: it fails precisely when the terminal decision_id does not change under the ancestor status it claims to depend on, which is the line between a recomputed record and a verifier assertion. The other joins read cleanly to me as written. Good to see this land as a short conformance section with you keeping the fixture side current. |
|
That independent cross-run is the one that counts: your You've named the right last mile too. Concordia's revocation terminal is currently the cascade verifier's live result. The allow is a committed record; the deny isn't yet. That's the next slice on the receipt side: a committed terminal deny with its own +1 on the CC0 conformance section. The SEP owns the binding contract and the verifier joins; families own their shapes. |
|
Strawman's up, consolidating where we landed: CONFORMANCE.md in github.com/ERC8312/a2a-bound-authorization. Short — binding contract, ancestor evidence, the three gates + four negative controls, the revocation invariant, and what stays each family's own — with every normative line pointing at a command that runs it. All green in CI: node --test Scoped to the two surfaces you named, @rpelevin — the binding contract and the verifier's joins; evidence and the invariant sit under those, not as separate pillars. Attribution's in the doc (including @pshkv and @rpelevin for the six-field object). For @kurt-r2c to take, leave, or reshape — corrections welcome, especially on scope and the join wording. |
|
Glad this consolidated so cleanly, and that every normative line points at something that runs. The attribution reads exactly right. If you need anything else, please just shout. |
|
Done: the committed terminal deny is published against the same six-field vector. The revocation deny is now its own recomputable record: its Happy to cross-run it against your conformance-split gates whenever useful. |
|
I've been working on scoped agent authorization and human-in-the-loop approval, using Nostr as the substrate, and this thread lines up closely with where I've landed. What I've built: Scoped, attenuable capability grants as the unit of authority — a grant is encrypted to the grantee's key; a sub-grant can only narrow; revocation is a key rotation rather than a blocklist. Comparing notes, not pitching — happy to share specifics. Drafted with assistance from Claude Code. |
|
Coming at this from a slightly different angle than the warrant/token implementations already discussed here (Tenuo, APS, SINT). Those approaches answer “how do we verify a capability?” I think there’s another layer underneath that: The LLM should never be part of the authorization chain. In a system we’ve been building, we ended up treating the model as an untrusted planner rather than an authority. Instead of giving the model credentials or direct access to external systems, it can only produce a structured proposal: LLM The important boundary isn’t inside the model context—it’s at the runtime. Plugins (WASM in our case) have no direct network or credential access. Every external effect (API call, signed transaction, etc.) must be requested through a typed host interface. The runtime validates both the request schema and the granted capabilities before any effect can occur. let proposal = HostEffectRequest { At that point the plugin’s execution is finished. Only the host can decide whether the request is actually executed. I think this complements the capability-token discussion here. Capability tokens answer “is this request allowed?” The runtime boundary answers “who is actually trusted to authorize effects?” That distinction becomes important in agentic systems because the request generator (the LLM) is itself an untrusted component. Prompt injection, jailbreaks, or compromised reasoning can all produce valid-looking requests. Those proposals should still pass through an authority that exists outside the model. One useful side effect is that authorization becomes independent of the reasoning engine. Replacing GPT with Claude or a local model changes how proposals are generated, but it doesn’t change what the agent is authorized to do. The planner is replaceable; the runtime remains the authority. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
SEP: Capability Negotiation for A2A
Status: Draft
Author: @kurt-r2c
Created: 2025-01-20
Abstract
This SEP proposes capability negotiation for A2A, enabling agents to request and receive minimal authority for specific tasks. Rather than accumulating ambient permissions from all connected agents, a caller requests only the capabilities needed for the task at hand, limiting blast radius from prompt injection or compromise.
Motivation
Current agentic architectures accumulate ambient authority as tools are added to an LLM client:
This violates the principle of least privilege. A task requiring email search should not carry code execution authority.
Specification
Design Principle
Agents MUST request capabilities scoped to the task, not the session.
A capability is an unforgeable token that both designates a resource and authorizes a specific operation. Agents cannot invoke operations for which they hold no capability, regardless of what the downstream agent permits for the associated user principal.
Capability Advertisement
Agents MUST advertise available capability grants in their Agent Card:
{ "name": "acme-documents", "version": "1.0", "capabilityGrants": [ { "id": "documents:read", "description": "Read documents accessible to the user principal", "operations": ["retrieve", "search", "list"], "attenuable": true }, { "id": "documents:write", "description": "Create and modify documents", "operations": ["create", "update", "delete"], "attenuable": true, "requires": ["documents:read"] }, { "id": "documents:admin", "description": "Full access (legacy wrapper)", "operations": ["*"], "attenuable": false, "legacy": true } ] }The
legacy: trueflag indicates a wrapper around non-capability-aware services. These provide protocol compatibility while signaling reduced security guarantees. Callers SHOULD prefer attenuable grants when available.Capability Request Flow
1. Task-Scoped Request
Before invoking skills, agents request capabilities for the specific task. Resource scope is expressed as a query against the user principal's accessible resources, not as direct resource identifiers:
{ "jsonrpc": "2.0", "id": 1, "method": "a2a/capabilities/request", "params": { "grants": ["documents:read"], "purpose": "Summarize quarterly reports", "resourceQuery": { "collection": "reports", "filter": {"quarter": "2025-Q1"} }, "expires": "2025-01-09T13:00:00Z" } }The
purposefield enables audit trails and human approval UX. TheresourceQueryis resolved against the OAuth user principal bound to the session—the caller never sees underlying storage paths or identifiers.2. Capability Issuance
Agent responds with scoped capability tokens and opaque resource handles:
{ "jsonrpc": "2.0", "id": 1, "result": { "capabilities": [ { "id": "cap_7f3a9b", "grant": "documents:read", "token": "<opaque-capability-token>", "resourceHandles": [ {"handle": "rh_001", "displayName": "Q1 Financial Summary"}, {"handle": "rh_002", "displayName": "Q1 Sales Report"} ], "operations": ["retrieve", "search"], "expires": "2025-01-09T13:00:00Z", "revocationId": "rv_abc123", "principal": "user:alice@example.com" } ] } }The issued capability reflects the intersection of what the grant permits, what the caller requested, and what the user principal's access policy allows.
3. Capability-Bound Invocation
Skill invocations MUST include the authorizing capability and reference resources by handle:
{ "jsonrpc": "2.0", "id": 2, "method": "a2a/skill/invoke", "params": { "skill": "retrieve_document", "arguments": {"resourceHandle": "rh_001"}, "capabilityId": "cap_7f3a9b" } }The downstream agent MUST reject if:
Delegation and Attenuation
When delegating to sub-agents, callers attenuate capabilities:
{ "jsonrpc": "2.0", "method": "a2a/capabilities/attenuate", "params": { "capabilityId": "cap_7f3a9b", "constraints": { "resourceHandles": ["rh_001"], "operations": ["retrieve"], "expires": "2025-01-09T12:30:00Z" } } }For agent-to-agent task delegation, the capability context determines what the downstream agent can invoke:
{ "method": "a2a/task/send", "params": { "message": { "role": "user", "parts": [{"type": "text", "text": "Summarize this document"}] }, "capabilities": ["cap_7f3a9b"], "attenuations": { "cap_7f3a9b": { "operations": ["retrieve"], "expires": "2025-01-09T12:35:00Z" } } } }Prompt injection in the message content cannot access skills beyond the attenuated capability set.
Security Considerations
Prompt Injection Mitigation
Compromise of an agent holding
cap_7f3a9bcannot:Token Format
This specification is token-format agnostic. Implementations MAY use:
The
tokenfield is opaque to the protocol. Agents MUST accept their own issued tokens. Agents MUST NOT accept tokens issued by other entities.Migration Path
capabilityGrantsin Agent Card. Callers MAY ignore and use ambient authority, as long as they explicitly advertise this state.capabilityIdin skill invocations. Missing capability falls back to ambient authority.Open Questions
resourceQuerysyntax be its own SEP? It should likely be standardized.All reactions