diff --git a/drafts/agent-surface.md b/drafts/agent-surface.md
index bf4e2a4..2574c6f 100644
--- a/drafts/agent-surface.md
+++ b/drafts/agent-surface.md
@@ -595,24 +595,39 @@ indicators.
Relevant standards:
- OAuth 2.0:
+- OAuth 2.0 Proof Key for Code Exchange:
+
- OAuth 2.0 Token Revocation:
- OAuth 2.0 Token Introspection:
- OAuth 2.0 Token Exchange:
- OAuth 2.0 Resource Indicators:
+- OAuth 2.0 Rich Authorization Requests:
+
+- OAuth 2.0 Pushed Authorization Requests:
+
+- Best Current Practice for OAuth 2.0 Security:
+
Agent Surface Protocol uses the term **grant** for the semantic object,
even when an OAuth access token is the transport representation.
-If OAuth is used, `agent_delegation` MAY be represented as an OAuth extension
-grant type. `agent_delegation` is not a standard OAuth grant type in this draft.
+The OAuth Grant Lifecycle Profile in this draft uses standard OAuth flows and
+extension parameters; it does not define an `agent_delegation` OAuth grant type.
+Implementations MAY use:
-Implementations MAY also use existing OAuth flows, including:
-
-- Authorization Code with PKCE and additional agent delegation parameters.
+- Authorization Code with PKCE and an Agent Grant
+ `authorization_details` object.
- OAuth Token Exchange to exchange a user-authorized credential for an
agent-scoped grant credential.
- Resource Indicators to constrain the resource server or app surface.
+The collision-resistant authorization-details type identifier defined by this
+draft is:
+
+```text
+https://github.com/0al-spec/agent-surface/authorization-details/agent-grant
+```
+
### Agent Passport
[Agent Passport](https://github.com/0al-spec/agent-passport) provides agent
@@ -849,7 +864,13 @@ surface discoverable.
"token_url": "https://example.com/oauth/token",
"introspection_url": "https://example.com/oauth/introspect",
"revocation_url": "https://example.com/oauth/revoke",
- "grant_types": ["agent_delegation"],
+ "grant_types_supported": [
+ "authorization_code",
+ "urn:ietf:params:oauth:grant-type:token-exchange"
+ ],
+ "authorization_details_types_supported": [
+ "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant"
+ ],
"token_binding": ["runtime", "agent_passport_hash"],
"pkce_required": true
},
@@ -1073,6 +1094,10 @@ Grant constraints filter events the same way they filter actions: a grant
constrained to one repository SHOULD NOT receive events about other
repositories, even when the event scope matches.
+`grant.revoked` is an application control event rather than an event authorized
+by the revoked grant. Its payload, authentication, and processing requirements
+are defined in the OAuth Grant Revocation Profile.
+
Event delivery semantics — transport, ordering, acknowledgement, and replay —
are not defined in this draft; see Open Questions.
@@ -1188,6 +1213,8 @@ surface, scopes, and caveats.
"issuer": "https://code.example.com",
"surface_version": "2026-06-25"
},
+ "locations": ["https://code.example.com/agent-actions"],
+ "actions": ["pull_request.get", "comment.create"],
"scopes": [
"pull_request.read",
"pull_request.comment"
@@ -1203,11 +1230,13 @@ surface, scopes, and caveats.
"mode": "deny"
}
},
+ "credential_profile": "proof_bound",
"credential_binding": {
"method": "dpop",
"runtime_id": "application_runtime_456",
"agent_id": "local_agent_789",
- "passport_hash": "sha256:..."
+ "passport_hash": "sha256:...",
+ "jkt": ""
},
"audit": {
"local_receipt": "required",
@@ -1216,6 +1245,16 @@ surface, scopes, and caveats.
}
```
+This object is the authoritative Agent Grant wire shape. `locations` restricts
+the action endpoints at which the grant may be used, and `actions`, when
+present, is an authoritative allow-list of Agent Surface action identifiers.
+OAuth `authorization_details` uses this same shape with the additional RFC 9396
+`type` discriminator; it does not define aliases for Grant Object fields.
+`credential_binding` is authorization-server output and MUST repeat the bound
+runtime, agent, and passport tuple. A DPoP binding MUST additionally contain
+`jkt`; an mTLS binding MUST instead contain `x5t#S256`. Those values use the
+same encoding and semantics as the corresponding standard `cnf` members.
+
Numeric caveats need defined accounting. In this draft, `max_actions` counts
side-effecting action requests accepted by the application under the grant.
Reads and denied requests do not consume the budget, and neither do idempotent
@@ -1234,14 +1273,14 @@ discover surface
-> verify manifest
-> choose agent
-> verify Agent Passport
- -> request grant
+ -> request grant with authorization_details
-> user consent
- -> issue grant
+ -> issue or exchange Grant Credential
-> store grant in runtime
-> start session
- -> mediate actions
+ -> introspect / verify and mediate actions
-> issue receipts
- -> expire / revoke / renew
+ -> expire / revoke / notify / renew
```
### Grant Issuance Models
@@ -1312,6 +1351,410 @@ Cons:
semantics, and stronger interop work.
- Too large for the first MVP.
+### OAuth Grant Lifecycle Profile
+
+This profile maps an Agent Grant onto OAuth Rich Authorization Requests, Token
+Exchange, Token Introspection, and Token Revocation. It applies when the Agent
+Surface Manifest declares OAuth endpoints and the Agent Grant authorization
+details type defined by this draft.
+
+The authorization server and resource server MAY be operated by the same
+application, but they retain their OAuth roles. The authorization server issues
+and manages Grant Credentials. The application action endpoint is the resource
+server and continues to enforce the semantic Agent Grant for every action.
+
+#### Rich Authorization Request Profile
+
+An Agent Grant authorization request MUST use the RFC 9396
+`authorization_details` parameter encoded as a JSON array containing exactly
+one object whose `type` is:
+
+```text
+https://github.com/0al-spec/agent-surface/authorization-details/agent-grant
+```
+
+The authorization server metadata MUST list this value in
+`authorization_details_types_supported`. The Agent Surface Manifest `auth`
+object MUST mirror that value and the standard `grant_types_supported` values
+used by the deployment; conflicting metadata makes the OAuth profile invalid.
+
+Example, shown decoded from its form-encoded authorization request parameter:
+
+```json
+[
+ {
+ "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant",
+ "locations": ["https://code.example.com/agent-actions"],
+ "actions": ["pull_request.get", "comment.create"],
+ "delegate": {
+ "runtime": "application_runtime_456",
+ "agent": "local_agent_789",
+ "passport_ref": "agent-passport://local-agent",
+ "passport_hash": "sha256:..."
+ },
+ "resource_server": {
+ "app_id": "code.example.com",
+ "issuer": "https://code.example.com",
+ "surface_version": "2026-06-25"
+ },
+ "scopes": ["pull_request.read", "pull_request.comment"],
+ "constraints": {
+ "repositories": ["example-org/example-repo"],
+ "pull_requests": [13],
+ "expires_at": "2026-06-25T20:00:00Z",
+ "write_approval": "required",
+ "max_actions": 20
+ },
+ "credential_profile": "proof_bound",
+ "audit": {
+ "local_receipt": "required",
+ "app_receipt": "required"
+ }
+ }
+]
+```
+
+The Agent Grant authorization details type has the following contract. Except
+for the required RFC 9396 `type` discriminator, its field names and semantics
+are the authoritative Grant Object wire shape defined above:
+
+- `type`, `delegate`, `resource_server`, `scopes`, `constraints`,
+ `credential_profile`, and `audit` are REQUIRED.
+- `delegate` MUST contain `runtime`, `agent`, and `passport_hash`; it MAY contain
+ `passport_ref`.
+- `resource_server` MUST contain `app_id`, `issuer`, and `surface_version`.
+- `constraints` MUST contain `expires_at`; other fields use the semantics of the
+ Agent Grant object.
+- `credential_profile` MUST be `compatibility_bearer` or `proof_bound` and maps
+ to the credential profiles defined in this draft.
+- RFC 9396 common fields `locations` and `actions` MAY restrict the request to
+ published action endpoints and Agent Surface action identifiers. When
+ `actions` is present, the granted value is an authoritative allow-list: every
+ invoked action MUST be a member. The authorization applies to the product of
+ the granted actions, locations, scopes, and resource filters; every allowed
+ combination MUST be published by the surface and semantically compatible.
+- `grant_id`, `subject`, and `credential_binding` MUST NOT be supplied by the
+ client in an authorization request; they are authorization-server output.
+- The request MUST NOT supply `subject.user` or another asserted user identity;
+ the authorization server derives the subject from its authenticated user
+ session or, at the token endpoint, from the validated `subject_token`.
+
+The authorization server MUST reject unknown fields, unknown action or scope
+values, a mismatched `resource_server.app_id` or
+`resource_server.surface_version`, an unverified passport hash, or constraints
+that are invalid for the published surface. It MUST use the RFC 9396
+`invalid_authorization_details` error for malformed or unsupported Agent Grant
+authorization details.
+
+Authorization Code use of this profile MUST use PKCE with the `S256` challenge
+method. Deployments SHOULD use Pushed Authorization Requests when supported so
+the rich grant request is integrity-protected and is not exposed in browser
+URLs, history, or intermediary logs.
+
+The OAuth `scope` request parameter MUST NOT be used in an authorization or
+token-exchange request that carries this Agent Grant `authorization_details`
+type. The authorization server MUST reject such a request with `invalid_request`.
+Independent OAuth authorization therefore requires a separate request and
+credential; this profile never silently drops or unions independent scopes.
+
+In token and introspection responses, the standard OAuth `scope` member MAY be
+an exact space-delimited projection of the granted Agent Grant `scopes` for
+legacy resource-server integration. If present, it MUST contain exactly that
+projection; the granted `authorization_details` remains authoritative. A
+resource server MUST reject a credential when the two representations conflict.
+
+Consent MUST present the application, runtime, agent, passport evidence,
+requested actions and scopes, resources, expiration and budgets, approval
+requirements, credential profile, data exposure, and receipt requirements. The
+user MAY approve a strict subset. The authorization server MUST compare the
+requested and approved objects according to Agent Grant semantics rather than
+using raw JSON equality, and MUST NOT enrich the result with additional
+authority.
+
+The token response MUST return the granted `authorization_details` as required
+by RFC 9396. For this type, the returned object MUST be enriched with the
+authoritative `grant_id`, `subject`, delegate binding, effective constraints,
+and `credential_binding` assigned by the authorization server. The
+authorization server and resource server MUST retain or receive the same
+granted object for later action verification and introspection.
+
+#### OAuth Token Exchange Profile
+
+A runtime MAY exchange a user-authorized subject token for an Agent Grant
+Credential using RFC 8693. The request uses the standard token endpoint and
+form-encoded parameters:
+
+```http
+POST /oauth/token HTTP/1.1
+Host: code.example.com
+Content-Type: application/x-www-form-urlencoded
+DPoP:
+
+grant_type=urn:ietf:params:oauth:grant-type:token-exchange
+&resource=https%3A%2F%2Fcode.example.com%2Fagent-actions
+&requested_token_type=urn:ietf:params:oauth:token-type:access_token
+&subject_token=
+&subject_token_type=urn:ietf:params:oauth:token-type:access_token
+&authorization_details=
+```
+
+The Token Exchange request has these additional ASP requirements:
+
+- The runtime MUST authenticate to the token endpoint. For a
+ `proof_bound` request, it MUST authenticate using the key or channel binding
+ that will identify the bound runtime.
+- A DPoP-bound exchange MUST include a `DPoP` HTTP header containing a proof for
+ the token request, as required by RFC 9449. The authorization server MUST
+ validate that proof independently of OAuth client authentication and derive
+ the issued token's `cnf.jkt` and Agent Grant `credential_binding.jkt` from the
+ proof key. An mTLS-bound exchange instead derives `cnf["x5t#S256"]` and the
+ corresponding credential binding from the client certificate presented on
+ the token request; it does not use the example's DPoP header.
+- `subject_token` MUST represent the authenticated user's authorization for the
+ requested application and MUST be valid at the time of exchange.
+- `resource` MUST contain exactly the published Agent Surface action resource
+ URI. An `audience` value MAY additionally name the same logical resource
+ server but MUST NOT add another target.
+- `requested_token_type` MUST be
+ `urn:ietf:params:oauth:token-type:access_token` for the OAuth Grant Credential
+ profile in this draft.
+- `authorization_details` MUST contain exactly one Agent Grant object of the
+ type defined above and MUST be semantically equal to or narrower than the
+ authorization approved by the user.
+- If OAuth client authentication does not establish the runtime identity, the
+ request MUST include an `actor_token` representing the runtime and the
+ corresponding `actor_token_type`. The authorization server MUST verify it and
+ bind the output credential to that runtime. For a `proof_bound` request, the
+ actor token MUST itself be sender-constrained or presented through the same
+ bound channel authentication.
+
+The authorization server MUST validate the subject token, runtime identity,
+agent and passport binding, resource, requested scopes, constraints, and
+credential profile. The exchange MUST NOT increase authority, widen resources,
+relax approval or receipt requirements, extend beyond the approved expiration,
+or replace `proof_bound` with `compatibility_bearer` without fresh user consent.
+
+RFC 8693 does not itself create lifecycle linkage between input and output
+tokens. This ASP profile does: the authorization server MUST record the source
+authorization or parent grant from which the Agent Grant was derived. Revoking
+or invalidating that source authority MUST revoke or suspend every derived Agent
+Grant unless an independently approved grant replaced it.
+
+Issuance also MUST preserve cumulative caveats across that derivation graph.
+Every accepted action or cost charge MUST atomically consume both the derived
+grant's local budget and the authoritative remaining budget of every ancestor
+authorization from which it derives. Repeating an exchange therefore cannot
+multiply `max_actions`, `max_cost_usd`, or another stateful budget. The
+authorization server MUST treat semantically equivalent exchanges with the
+same source authorization, client and delegate tuple, target resource,
+normalized Agent Grant details, and proof-binding key as idempotent: it MUST
+reuse the same `grant_id` and accounting state, although it MAY rotate the
+access-token representation.
+
+Example successful response:
+
+```json
+{
+ "access_token": "",
+ "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
+ "token_type": "DPoP",
+ "expires_in": 1800,
+ "scope": "pull_request.read pull_request.comment",
+ "grant_id": "grant_123",
+ "authorization_details": [
+ {
+ "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant",
+ "locations": ["https://code.example.com/agent-actions"],
+ "actions": ["pull_request.get", "comment.create"],
+ "subject": {
+ "user": "app-user-7f3a"
+ },
+ "delegate": {
+ "runtime": "application_runtime_456",
+ "agent": "local_agent_789",
+ "passport_ref": "agent-passport://local-agent",
+ "passport_hash": "sha256:..."
+ },
+ "resource_server": {
+ "app_id": "code.example.com",
+ "issuer": "https://code.example.com",
+ "surface_version": "2026-06-25"
+ },
+ "scopes": ["pull_request.read", "pull_request.comment"],
+ "constraints": {
+ "repositories": ["example-org/example-repo"],
+ "pull_requests": [13],
+ "expires_at": "2026-06-25T20:00:00Z",
+ "write_approval": "required",
+ "max_actions": 20
+ },
+ "credential_profile": "proof_bound",
+ "credential_binding": {
+ "method": "dpop",
+ "runtime_id": "application_runtime_456",
+ "agent_id": "local_agent_789",
+ "passport_hash": "sha256:...",
+ "jkt": ""
+ },
+ "audit": {
+ "local_receipt": "required",
+ "app_receipt": "required"
+ },
+ "grant_id": "grant_123"
+ }
+ ]
+}
+```
+
+The response MUST include `access_token`, `issued_token_type`, `token_type`,
+`expires_in`, `grant_id`, the exact `scope` projection defined above, and the
+granted `authorization_details`. This also satisfies the RFC 8693 requirement to
+return `scope` when the issued scope differs from the request. The `token_type`
+and method-specific credential confirmation data MUST match the selected
+credential profile and the binding established at the token endpoint. A
+refresh token SHOULD NOT be issued by default; if one is issued, it MUST
+preserve the tuple binding, attenuation, and revocation linkage of the Agent
+Grant and follow RFC 9700 refresh-token replay protections.
+
+Token responses containing a Grant Credential or its authorization details MUST
+use `Cache-Control: no-store` and `Pragma: no-cache`.
+
+#### Grant Introspection Profile
+
+The manifest `agent_api.grant_introspection_url` MAY identify the same RFC 7662
+endpoint as `auth.introspection_url`. A protected resource or runtime
+introspects a Grant Credential using an authenticated RFC 7662 request with the
+required `token` parameter and optional `token_type_hint`. The endpoint MUST
+authenticate and authorize the caller and disclose only grant data that caller
+needs.
+
+For an inactive, unknown, or undisclosable credential, the response MUST be:
+
+```json
+{
+ "active": false
+}
+```
+
+It MUST NOT reveal whether the credential was unknown, expired, revoked, or
+outside the caller's authority.
+
+For an active Grant Credential, the response MUST include the RFC 7662 fields
+`active`, `client_id`, `scope`, `token_type`, `exp`, `iat`, `sub`, `aud`, and
+`iss`, plus the ASP fields `grant_id`, `resource_server`, `delegate`,
+`constraints`, `credential_binding`, and `authorization_details`. An active
+proof-bound credential MUST additionally include the method-specific standard
+`cnf` confirmation member. The `sub` value SHOULD be a stable app-scoped
+pseudonymous user identifier. `client_id` identifies the OAuth client;
+`delegate.runtime` is the authoritative ASP runtime binding and MAY differ from
+`client_id`.
+
+The `authorization_details` member MUST contain the granted Agent Grant object,
+filtered only to data the authenticated caller may receive. Top-level `sub`,
+`grant_id`, `resource_server`, `delegate`, `constraints`, and
+`credential_binding` are projections of that object and MUST match it; `sub`
+corresponds to `subject.user`. A resource server MUST treat a mismatch as an
+invalid grant proof rather than selecting one representation.
+
+For DPoP, `cnf` MUST contain `jkt` as specified by RFC 9449; for mTLS, it MUST
+contain `x5t#S256` as specified by RFC 8705. The confirmation value MUST match
+the method-specific value in the Agent Grant `credential_binding`. The resource
+server MUST reject a missing or mismatched confirmation member. A Compatibility
+Bearer Credential MUST NOT fabricate a `cnf` member.
+
+```json
+{
+ "active": true,
+ "client_id": "application_runtime_456",
+ "scope": "pull_request.read pull_request.comment",
+ "token_type": "DPoP",
+ "exp": 1782417600,
+ "iat": 1782415800,
+ "sub": "app-user-7f3a",
+ "aud": "https://code.example.com/agent-actions",
+ "iss": "https://code.example.com",
+ "cnf": {
+ "jkt": ""
+ },
+ "grant_id": "grant_123",
+ "resource_server": {
+ "app_id": "code.example.com",
+ "issuer": "https://code.example.com",
+ "surface_version": "2026-06-25"
+ },
+ "delegate": {
+ "runtime": "application_runtime_456",
+ "agent": "local_agent_789",
+ "passport_ref": "agent-passport://local-agent",
+ "passport_hash": "sha256:..."
+ },
+ "constraints": {
+ "repositories": ["example-org/example-repo"],
+ "pull_requests": [13],
+ "expires_at": "2026-06-25T20:00:00Z",
+ "write_approval": "required",
+ "max_actions": 20
+ },
+ "credential_binding": {
+ "method": "dpop",
+ "runtime_id": "application_runtime_456",
+ "agent_id": "local_agent_789",
+ "passport_hash": "sha256:...",
+ "jkt": ""
+ },
+ "authorization_details": [
+ {
+ "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant",
+ "locations": ["https://code.example.com/agent-actions"],
+ "actions": ["pull_request.get", "comment.create"],
+ "subject": {
+ "user": "app-user-7f3a"
+ },
+ "delegate": {
+ "runtime": "application_runtime_456",
+ "agent": "local_agent_789",
+ "passport_ref": "agent-passport://local-agent",
+ "passport_hash": "sha256:..."
+ },
+ "resource_server": {
+ "app_id": "code.example.com",
+ "issuer": "https://code.example.com",
+ "surface_version": "2026-06-25"
+ },
+ "scopes": ["pull_request.read", "pull_request.comment"],
+ "constraints": {
+ "repositories": ["example-org/example-repo"],
+ "pull_requests": [13],
+ "expires_at": "2026-06-25T20:00:00Z",
+ "write_approval": "required",
+ "max_actions": 20
+ },
+ "credential_profile": "proof_bound",
+ "credential_binding": {
+ "method": "dpop",
+ "runtime_id": "application_runtime_456",
+ "agent_id": "local_agent_789",
+ "passport_hash": "sha256:...",
+ "jkt": ""
+ },
+ "audit": {
+ "local_receipt": "required",
+ "app_receipt": "required"
+ },
+ "grant_id": "grant_123"
+ }
+ ]
+}
+```
+
+The response MUST describe current authoritative state and MUST use
+`Cache-Control: no-store`. A resource-server enforcement point that does not
+share the authorization server's authoritative grant state MUST introspect on
+every action and MUST NOT positively cache `active: true`. This prohibition is
+required by this profile's immediate revocation semantics; deployments that
+need positive caching must define and advertise a different bounded stale-use
+profile rather than claiming conformance to this one.
+
### Grant Credentials and Proof
An Agent Grant MAY be represented or proven by one of several mechanisms:
@@ -1420,6 +1863,9 @@ Applications MUST verify every action against grant state:
not rejected by itself
- for a proof-bound server session, the session is active, bound to the grant
and runtime, and authenticated with the bound key or channel credential
+- when the grant contains `actions`, the requested action identifier is a
+ member of that authoritative allow-list, is served at a granted `location`,
+ and remains compatible with the granted scopes and resource constraints
- scope permits the action
- resource constraints permit the target object
- expiration has not passed
@@ -1719,6 +2165,88 @@ in-app view of active agent grants — comparable to OAuth application
management pages — where a grant can be inspected and revoked without going
through the runtime.
+### OAuth Grant Revocation Profile
+
+The manifest `agent_api.grant_revocation_url` MAY identify the same endpoint as
+`auth.revocation_url`. When it does, a runtime requests revocation using RFC
+7009: an authenticated form-encoded `POST` containing the Grant Credential in
+the required `token` parameter and, optionally, an `access_token`
+`token_type_hint`.
+
+The endpoint MUST authenticate the runtime client and, for a credential it can
+locate, verify that the credential was issued to that client. A successful
+request and a request containing an unknown or already invalid credential both
+return HTTP 200 with no response body, as required by RFC 7009. The runtime MUST
+stop using the credential after that response. An HTTP 503 response means
+revocation is not confirmed; the runtime MUST continue treating the credential
+as sensitive, MUST NOT initiate new actions with it, and SHOULD retry according
+to `Retry-After`.
+
+For the Agent Grant profile, revoking a Grant Credential through
+`grant_revocation_url` revokes the semantic Agent Grant, not only that token.
+The application MUST immediately mark the grant inactive, reject every
+credential derived from it, invalidate refresh tokens and proof-bound sessions,
+and cascade revocation to child or exchanged grants whose authority derives
+from it. Revocation initiated through the application's user-facing grant view
+MUST produce the same state transition.
+
+When an active grant changes to revoked and the manifest declares an event
+subscription endpoint, the application MUST emit a `grant.revoked` control event
+with this minimum envelope:
+
+```json
+{
+ "id": "event_01J2ABCDEF",
+ "type": "grant.revoked",
+ "occurred_at": "2026-06-25T18:30:00Z",
+ "issuer": "https://code.example.com",
+ "audience": "application_runtime_456",
+ "payload": {
+ "grant_id": "grant_123",
+ "app_id": "code.example.com",
+ "runtime_id": "application_runtime_456",
+ "agent_id": "local_agent_789",
+ "passport_hash": "sha256:...",
+ "revoked_at": "2026-06-25T18:30:00Z",
+ "effective_at": "2026-06-25T18:30:00Z",
+ "reason": "user_revoked",
+ "parent_grant_id": null,
+ "cascade": true
+ }
+}
+```
+
+Required event fields are `id`, `type`, `occurred_at`, `issuer`, `audience`, and
+`payload`. The payload MUST contain `grant_id`, `app_id`, `runtime_id`,
+`agent_id`, `passport_hash`, `revoked_at`, `effective_at`, `reason`, and
+`cascade`; `parent_grant_id` is REQUIRED for a child grant and otherwise MAY be
+null. Defined reason values are `user_revoked`, `application_revoked`,
+`runtime_revoked`, `credential_compromise`, `parent_revoked`, `policy_changed`,
+and `superseded`. A runtime MUST still enforce revocation when it receives an
+unknown future reason value and MAY preserve that value as opaque audit data.
+
+The event MUST be delivered over an application-authenticated event channel
+bound to the manifest issuer and target runtime. The runtime MUST verify
+`issuer`, `audience`, tuple binding, and channel authenticity before acting on
+it. Delivery of this control event MUST use event-channel authority independent
+of the revoked grant and MUST disclose no more grant data than the target
+runtime already possessed. A future signing profile MAY additionally define an
+application signature for portable event verification.
+
+After accepting the event, the runtime MUST atomically mark the grant inactive,
+discard cached active introspection state, stop new actions and credential use,
+cancel or downgrade affected sessions according to app policy, cascade the
+state to locally tracked child grants, and record a runtime receipt. Event
+processing is idempotent by `id`; a duplicate event MUST NOT create duplicate
+receipts or repeat external side effects.
+
+The event is notification, not the enforcement mechanism. The application MUST
+reject the revoked grant immediately even if delivery is delayed or lost. A
+runtime that misses the event learns the inactive state from introspection or a
+rejected action. General event ordering, acknowledgement, replay cursor,
+retention, and backpressure remain outside this profile and are defined by the
+future Event Delivery Semantics work.
+
### Grant Revoked
If a grant is revoked:
@@ -1728,10 +2256,9 @@ If a grant is revoked:
- active sessions SHOULD be cancelled or downgraded to read-only according to
app policy
- receipt generation SHOULD record the revocation event
-- the event channel SHOULD deliver `grant.revoked` as a final event before the
- app closes the subscription; delivery of this one event MUST NOT itself
- require an active grant, or the runtime could never learn about the
- revocation through the event channel
+- when the manifest declares an event subscription endpoint, the app MUST emit
+ `grant.revoked` according to the OAuth Grant Revocation Profile before it
+ closes the subscription
### Runtime Disconnected
@@ -2024,6 +2551,21 @@ An application conforms to the Grant-Enforcing profile when it:
- supports idempotency for side-effecting actions
- supports grant revocation
+### OAuth Grant Lifecycle Application
+
+An application conforms to the OAuth Grant Lifecycle Application profile when
+it:
+
+- satisfies the Grant-Enforcing Application profile
+- advertises the Agent Grant authorization-details type and supported standard
+ OAuth grant types
+- validates and returns Agent Grant `authorization_details` according to the
+ Rich Authorization Request Profile
+- implements the OAuth Token Exchange Profile without privilege amplification
+- returns the active and inactive Grant Introspection Profile contracts
+- implements RFC 7009 semantic grant revocation, derivation cascade, and the
+ authenticated `grant.revoked` control event when an event endpoint is declared
+
### Receipt-Producing Application
An application conforms to the Receipt-Producing profile when it:
@@ -2061,6 +2603,8 @@ An application runtime conforms to this profile when it:
models, and ungranted secondary runtimes
- treats a separately granted child runtime as its own controlling runtime and
preserves parent linkage, attenuation, and cascade revocation
+- implements RAR, Token Exchange, introspection, and revocation processing when
+ using the OAuth Grant Lifecycle Application profile
- implements the Proof-Bound Credential Profile when the application requires
the Proof-Bound Grant-Enforcing Application profile
- enforces local policy and approval rules
@@ -2112,24 +2656,26 @@ To support Agent Surface Protocol, the next slices are:
2. Application runtime discovers and validates the surface.
3. User chooses "Connect my local agent".
4. Runtime verifies the selected agent's Agent Passport.
-5. Runtime shows consent:
+5. Runtime sends an Agent Grant `authorization_details` request.
+6. The app authorization server shows consent:
- app: code.example.com
- agent: local-agent
- scopes: pull_request.read, pull_request.comment
- repository: example-org/example-repo
- duration: 2 hours
- writes: require approval
-6. User approves.
-7. App issues grant_123 and a grant credential.
-8. Runtime stores grant_123 and the grant credential.
-9. App starts a pull-request review session.
-10. Agent reads typed PR context through runtime-mediated resources.
-11. Agent proposes a review comment.
-12. User or app approves the write.
-13. Runtime sends comment.create with an idempotency key and grant credential.
-14. App verifies grant and writes the comment.
-15. Runtime and app issue linked receipts.
-16. User revokes grant or grant expires.
+7. User approves a subset or the complete request.
+8. App issues or token-exchanges grant_123 and its bound Grant Credential.
+9. Runtime stores the authoritative granted details and credential.
+10. App starts a pull-request review session.
+11. Agent reads typed PR context through runtime-mediated resources.
+12. Agent proposes a review comment.
+13. User or app approves the write.
+14. Runtime sends comment.create with an idempotency key and Grant Credential.
+15. App verifies or introspects current grant state and writes the comment.
+16. Runtime and app issue linked receipts.
+17. User revokes grant_123; the app rejects it immediately.
+18. App emits authenticated `grant.revoked`; runtime stops affected work.
```
## Open Questions
@@ -2168,6 +2714,8 @@ To support Agent Surface Protocol, the next slices are:
- OAuth 2.0:
+- OAuth 2.0 Proof Key for Code Exchange:
+
- OAuth 2.0 Token Revocation:
- OAuth 2.0 Token Introspection:
@@ -2176,6 +2724,12 @@ To support Agent Surface Protocol, the next slices are:
- OAuth 2.0 Resource Indicators:
+- OAuth 2.0 Rich Authorization Requests:
+
+- OAuth 2.0 Pushed Authorization Requests:
+
+- Best Current Practice for OAuth 2.0 Security:
+
- OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access
Tokens:
diff --git a/review/agent-surface-rfc-review.html b/review/agent-surface-rfc-review.html
index 07e80c0..1be17ae 100644
--- a/review/agent-surface-rfc-review.html
+++ b/review/agent-surface-rfc-review.html
@@ -633,22 +633,35 @@ OAuth
Relevant standards:
Agent Surface Protocol uses the term grant for the semantic object,
even when an OAuth access token is the transport representation.
-If OAuth is used, agent_delegation MAY be represented as an OAuth extension
-grant type. agent_delegation is not a standard OAuth grant type in this draft.
-Implementations MAY also use existing OAuth flows, including:
+The OAuth Grant Lifecycle Profile in this draft uses standard OAuth flows and
+extension parameters; it does not define an agent_delegation OAuth grant type.
+Implementations MAY use:
-- Authorization Code with PKCE and additional agent delegation parameters.
+- Authorization Code with PKCE and an Agent Grant
+
authorization_details object.
- OAuth Token Exchange to exchange a user-authorized credential for an
agent-scoped grant credential.
- Resource Indicators to constrain the resource server or app surface.
+The collision-resistant authorization-details type identifier defined by this
+draft is:
+https://github.com/0al-spec/agent-surface/authorization-details/agent-grant
+
Agent Passport
Agent Passport provides agent
identity, capability, policy, lifecycle, signature, and integrity evidence.
@@ -844,7 +857,13 @@ Example Manifest
"token_url": "https://example.com/oauth/token",
"introspection_url": "https://example.com/oauth/introspect",
"revocation_url": "https://example.com/oauth/revoke",
- "grant_types": ["agent_delegation"],
+ "grant_types_supported": [
+ "authorization_code",
+ "urn:ietf:params:oauth:grant-type:token-exchange"
+ ],
+ "authorization_details_types_supported": [
+ "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant"
+ ],
"token_binding": ["runtime", "agent_passport_hash"],
"pkce_required": true
},
@@ -1039,6 +1058,9 @@ Events
Grant constraints filter events the same way they filter actions: a grant
constrained to one repository SHOULD NOT receive events about other
repositories, even when the event scope matches.
+grant.revoked is an application control event rather than an event authorized
+by the revoked grant. Its payload, authentication, and processing requirements
+are defined in the OAuth Grant Revocation Profile.
Event delivery semantics — transport, ordering, acknowledgement, and replay —
are not defined in this draft; see Open Questions.
Risk Taxonomy
@@ -1129,6 +1151,8 @@ Grant Object
"issuer": "https://code.example.com",
"surface_version": "2026-06-25"
},
+ "locations": ["https://code.example.com/agent-actions"],
+ "actions": ["pull_request.get", "comment.create"],
"scopes": [
"pull_request.read",
"pull_request.comment"
@@ -1144,11 +1168,13 @@ Grant Object
"mode": "deny"
}
},
+ "credential_profile": "proof_bound",
"credential_binding": {
"method": "dpop",
"runtime_id": "application_runtime_456",
"agent_id": "local_agent_789",
- "passport_hash": "sha256:..."
+ "passport_hash": "sha256:...",
+ "jkt": "<base64url-thumbprint>"
},
"audit": {
"local_receipt": "required",
@@ -1156,6 +1182,15 @@ Grant Object
}
}
+This object is the authoritative Agent Grant wire shape. locations restricts
+the action endpoints at which the grant may be used, and actions, when
+present, is an authoritative allow-list of Agent Surface action identifiers.
+OAuth authorization_details uses this same shape with the additional RFC 9396
+type discriminator; it does not define aliases for Grant Object fields.
+credential_binding is authorization-server output and MUST repeat the bound
+runtime, agent, and passport tuple. A DPoP binding MUST additionally contain
+jkt; an mTLS binding MUST instead contain x5t#S256. Those values use the
+same encoding and semantics as the corresponding standard cnf members.
Numeric caveats need defined accounting. In this draft, max_actions counts
side-effecting action requests accepted by the application under the grant.
Reads and denied requests do not consume the budget, and neither do idempotent
@@ -1171,14 +1206,14 @@
Grant Lifecycle
-> verify manifest
-> choose agent
-> verify Agent Passport
- -> request grant
+ -> request grant with authorization_details
-> user consent
- -> issue grant
+ -> issue or exchange Grant Credential
-> store grant in runtime
-> start session
- -> mediate actions
+ -> introspect / verify and mediate actions
-> issue receipts
- -> expire / revoke / renew
+ -> expire / revoke / notify / renew
Grant Issuance Models
Model A: App-Issued Grant
@@ -1237,6 +1272,369 @@ OAuth Grant Lifecycle Profile
+
This profile maps an Agent Grant onto OAuth Rich Authorization Requests, Token
+Exchange, Token Introspection, and Token Revocation. It applies when the Agent
+Surface Manifest declares OAuth endpoints and the Agent Grant authorization
+details type defined by this draft.
+The authorization server and resource server MAY be operated by the same
+application, but they retain their OAuth roles. The authorization server issues
+and manages Grant Credentials. The application action endpoint is the resource
+server and continues to enforce the semantic Agent Grant for every action.
+Rich Authorization Request Profile
+An Agent Grant authorization request MUST use the RFC 9396
+authorization_details parameter encoded as a JSON array containing exactly
+one object whose type is:
+https://github.com/0al-spec/agent-surface/authorization-details/agent-grant
+
+The authorization server metadata MUST list this value in
+authorization_details_types_supported. The Agent Surface Manifest auth
+object MUST mirror that value and the standard grant_types_supported values
+used by the deployment; conflicting metadata makes the OAuth profile invalid.
+Example, shown decoded from its form-encoded authorization request parameter:
+[
+ {
+ "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant",
+ "locations": ["https://code.example.com/agent-actions"],
+ "actions": ["pull_request.get", "comment.create"],
+ "delegate": {
+ "runtime": "application_runtime_456",
+ "agent": "local_agent_789",
+ "passport_ref": "agent-passport://local-agent",
+ "passport_hash": "sha256:..."
+ },
+ "resource_server": {
+ "app_id": "code.example.com",
+ "issuer": "https://code.example.com",
+ "surface_version": "2026-06-25"
+ },
+ "scopes": ["pull_request.read", "pull_request.comment"],
+ "constraints": {
+ "repositories": ["example-org/example-repo"],
+ "pull_requests": [13],
+ "expires_at": "2026-06-25T20:00:00Z",
+ "write_approval": "required",
+ "max_actions": 20
+ },
+ "credential_profile": "proof_bound",
+ "audit": {
+ "local_receipt": "required",
+ "app_receipt": "required"
+ }
+ }
+]
+
+The Agent Grant authorization details type has the following contract. Except
+for the required RFC 9396 type discriminator, its field names and semantics
+are the authoritative Grant Object wire shape defined above:
+
+type, delegate, resource_server, scopes, constraints,
+credential_profile, and audit are REQUIRED.
+delegate MUST contain runtime, agent, and passport_hash; it MAY contain
+passport_ref.
+resource_server MUST contain app_id, issuer, and surface_version.
+constraints MUST contain expires_at; other fields use the semantics of the
+Agent Grant object.
+credential_profile MUST be compatibility_bearer or proof_bound and maps
+to the credential profiles defined in this draft.
+- RFC 9396 common fields
locations and actions MAY restrict the request to
+published action endpoints and Agent Surface action identifiers. When
+actions is present, the granted value is an authoritative allow-list: every
+invoked action MUST be a member. The authorization applies to the product of
+the granted actions, locations, scopes, and resource filters; every allowed
+combination MUST be published by the surface and semantically compatible.
+grant_id, subject, and credential_binding MUST NOT be supplied by the
+client in an authorization request; they are authorization-server output.
+- The request MUST NOT supply
subject.user or another asserted user identity;
+the authorization server derives the subject from its authenticated user
+session or, at the token endpoint, from the validated subject_token.
+
+The authorization server MUST reject unknown fields, unknown action or scope
+values, a mismatched resource_server.app_id or
+resource_server.surface_version, an unverified passport hash, or constraints
+that are invalid for the published surface. It MUST use the RFC 9396
+invalid_authorization_details error for malformed or unsupported Agent Grant
+authorization details.
+Authorization Code use of this profile MUST use PKCE with the S256 challenge
+method. Deployments SHOULD use Pushed Authorization Requests when supported so
+the rich grant request is integrity-protected and is not exposed in browser
+URLs, history, or intermediary logs.
+The OAuth scope request parameter MUST NOT be used in an authorization or
+token-exchange request that carries this Agent Grant authorization_details
+type. The authorization server MUST reject such a request with invalid_request.
+Independent OAuth authorization therefore requires a separate request and
+credential; this profile never silently drops or unions independent scopes.
+In token and introspection responses, the standard OAuth scope member MAY be
+an exact space-delimited projection of the granted Agent Grant scopes for
+legacy resource-server integration. If present, it MUST contain exactly that
+projection; the granted authorization_details remains authoritative. A
+resource server MUST reject a credential when the two representations conflict.
+Consent MUST present the application, runtime, agent, passport evidence,
+requested actions and scopes, resources, expiration and budgets, approval
+requirements, credential profile, data exposure, and receipt requirements. The
+user MAY approve a strict subset. The authorization server MUST compare the
+requested and approved objects according to Agent Grant semantics rather than
+using raw JSON equality, and MUST NOT enrich the result with additional
+authority.
+The token response MUST return the granted authorization_details as required
+by RFC 9396. For this type, the returned object MUST be enriched with the
+authoritative grant_id, subject, delegate binding, effective constraints,
+and credential_binding assigned by the authorization server. The
+authorization server and resource server MUST retain or receive the same
+granted object for later action verification and introspection.
+OAuth Token Exchange Profile
+A runtime MAY exchange a user-authorized subject token for an Agent Grant
+Credential using RFC 8693. The request uses the standard token endpoint and
+form-encoded parameters:
+POST /oauth/token HTTP/1.1
+Host: code.example.com
+Content-Type: application/x-www-form-urlencoded
+DPoP: <proof-jwt>
+
+grant_type=urn:ietf:params:oauth:grant-type:token-exchange
+&resource=https%3A%2F%2Fcode.example.com%2Fagent-actions
+&requested_token_type=urn:ietf:params:oauth:token-type:access_token
+&subject_token=<user-authorized-token>
+&subject_token_type=urn:ietf:params:oauth:token-type:access_token
+&authorization_details=<percent-encoded-agent-grant-details>
+
+The Token Exchange request has these additional ASP requirements:
+
+- The runtime MUST authenticate to the token endpoint. For a
+
proof_bound request, it MUST authenticate using the key or channel binding
+that will identify the bound runtime.
+- A DPoP-bound exchange MUST include a
DPoP HTTP header containing a proof for
+the token request, as required by RFC 9449. The authorization server MUST
+validate that proof independently of OAuth client authentication and derive
+the issued token's cnf.jkt and Agent Grant credential_binding.jkt from the
+proof key. An mTLS-bound exchange instead derives cnf["x5t#S256"] and the
+corresponding credential binding from the client certificate presented on
+the token request; it does not use the example's DPoP header.
+subject_token MUST represent the authenticated user's authorization for the
+requested application and MUST be valid at the time of exchange.
+resource MUST contain exactly the published Agent Surface action resource
+URI. An audience value MAY additionally name the same logical resource
+server but MUST NOT add another target.
+requested_token_type MUST be
+urn:ietf:params:oauth:token-type:access_token for the OAuth Grant Credential
+profile in this draft.
+authorization_details MUST contain exactly one Agent Grant object of the
+type defined above and MUST be semantically equal to or narrower than the
+authorization approved by the user.
+- If OAuth client authentication does not establish the runtime identity, the
+request MUST include an
actor_token representing the runtime and the
+corresponding actor_token_type. The authorization server MUST verify it and
+bind the output credential to that runtime. For a proof_bound request, the
+actor token MUST itself be sender-constrained or presented through the same
+bound channel authentication.
+
+The authorization server MUST validate the subject token, runtime identity,
+agent and passport binding, resource, requested scopes, constraints, and
+credential profile. The exchange MUST NOT increase authority, widen resources,
+relax approval or receipt requirements, extend beyond the approved expiration,
+or replace proof_bound with compatibility_bearer without fresh user consent.
+RFC 8693 does not itself create lifecycle linkage between input and output
+tokens. This ASP profile does: the authorization server MUST record the source
+authorization or parent grant from which the Agent Grant was derived. Revoking
+or invalidating that source authority MUST revoke or suspend every derived Agent
+Grant unless an independently approved grant replaced it.
+Issuance also MUST preserve cumulative caveats across that derivation graph.
+Every accepted action or cost charge MUST atomically consume both the derived
+grant's local budget and the authoritative remaining budget of every ancestor
+authorization from which it derives. Repeating an exchange therefore cannot
+multiply max_actions, max_cost_usd, or another stateful budget. The
+authorization server MUST treat semantically equivalent exchanges with the
+same source authorization, client and delegate tuple, target resource,
+normalized Agent Grant details, and proof-binding key as idempotent: it MUST
+reuse the same grant_id and accounting state, although it MAY rotate the
+access-token representation.
+Example successful response:
+{
+ "access_token": "<opaque-grant-credential>",
+ "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
+ "token_type": "DPoP",
+ "expires_in": 1800,
+ "scope": "pull_request.read pull_request.comment",
+ "grant_id": "grant_123",
+ "authorization_details": [
+ {
+ "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant",
+ "locations": ["https://code.example.com/agent-actions"],
+ "actions": ["pull_request.get", "comment.create"],
+ "subject": {
+ "user": "app-user-7f3a"
+ },
+ "delegate": {
+ "runtime": "application_runtime_456",
+ "agent": "local_agent_789",
+ "passport_ref": "agent-passport://local-agent",
+ "passport_hash": "sha256:..."
+ },
+ "resource_server": {
+ "app_id": "code.example.com",
+ "issuer": "https://code.example.com",
+ "surface_version": "2026-06-25"
+ },
+ "scopes": ["pull_request.read", "pull_request.comment"],
+ "constraints": {
+ "repositories": ["example-org/example-repo"],
+ "pull_requests": [13],
+ "expires_at": "2026-06-25T20:00:00Z",
+ "write_approval": "required",
+ "max_actions": 20
+ },
+ "credential_profile": "proof_bound",
+ "credential_binding": {
+ "method": "dpop",
+ "runtime_id": "application_runtime_456",
+ "agent_id": "local_agent_789",
+ "passport_hash": "sha256:...",
+ "jkt": "<base64url-thumbprint>"
+ },
+ "audit": {
+ "local_receipt": "required",
+ "app_receipt": "required"
+ },
+ "grant_id": "grant_123"
+ }
+ ]
+}
+
+The response MUST include access_token, issued_token_type, token_type,
+expires_in, grant_id, the exact scope projection defined above, and the
+granted authorization_details. This also satisfies the RFC 8693 requirement to
+return scope when the issued scope differs from the request. The token_type
+and method-specific credential confirmation data MUST match the selected
+credential profile and the binding established at the token endpoint. A
+refresh token SHOULD NOT be issued by default; if one is issued, it MUST
+preserve the tuple binding, attenuation, and revocation linkage of the Agent
+Grant and follow RFC 9700 refresh-token replay protections.
+Token responses containing a Grant Credential or its authorization details MUST
+use Cache-Control: no-store and Pragma: no-cache.
+Grant Introspection Profile
+The manifest agent_api.grant_introspection_url MAY identify the same RFC 7662
+endpoint as auth.introspection_url. A protected resource or runtime
+introspects a Grant Credential using an authenticated RFC 7662 request with the
+required token parameter and optional token_type_hint. The endpoint MUST
+authenticate and authorize the caller and disclose only grant data that caller
+needs.
+For an inactive, unknown, or undisclosable credential, the response MUST be:
+{
+ "active": false
+}
+
+It MUST NOT reveal whether the credential was unknown, expired, revoked, or
+outside the caller's authority.
+For an active Grant Credential, the response MUST include the RFC 7662 fields
+active, client_id, scope, token_type, exp, iat, sub, aud, and
+iss, plus the ASP fields grant_id, resource_server, delegate,
+constraints, credential_binding, and authorization_details. An active
+proof-bound credential MUST additionally include the method-specific standard
+cnf confirmation member. The sub value SHOULD be a stable app-scoped
+pseudonymous user identifier. client_id identifies the OAuth client;
+delegate.runtime is the authoritative ASP runtime binding and MAY differ from
+client_id.
+The authorization_details member MUST contain the granted Agent Grant object,
+filtered only to data the authenticated caller may receive. Top-level sub,
+grant_id, resource_server, delegate, constraints, and
+credential_binding are projections of that object and MUST match it; sub
+corresponds to subject.user. A resource server MUST treat a mismatch as an
+invalid grant proof rather than selecting one representation.
+For DPoP, cnf MUST contain jkt as specified by RFC 9449; for mTLS, it MUST
+contain x5t#S256 as specified by RFC 8705. The confirmation value MUST match
+the method-specific value in the Agent Grant credential_binding. The resource
+server MUST reject a missing or mismatched confirmation member. A Compatibility
+Bearer Credential MUST NOT fabricate a cnf member.
+{
+ "active": true,
+ "client_id": "application_runtime_456",
+ "scope": "pull_request.read pull_request.comment",
+ "token_type": "DPoP",
+ "exp": 1782417600,
+ "iat": 1782415800,
+ "sub": "app-user-7f3a",
+ "aud": "https://code.example.com/agent-actions",
+ "iss": "https://code.example.com",
+ "cnf": {
+ "jkt": "<base64url-thumbprint>"
+ },
+ "grant_id": "grant_123",
+ "resource_server": {
+ "app_id": "code.example.com",
+ "issuer": "https://code.example.com",
+ "surface_version": "2026-06-25"
+ },
+ "delegate": {
+ "runtime": "application_runtime_456",
+ "agent": "local_agent_789",
+ "passport_ref": "agent-passport://local-agent",
+ "passport_hash": "sha256:..."
+ },
+ "constraints": {
+ "repositories": ["example-org/example-repo"],
+ "pull_requests": [13],
+ "expires_at": "2026-06-25T20:00:00Z",
+ "write_approval": "required",
+ "max_actions": 20
+ },
+ "credential_binding": {
+ "method": "dpop",
+ "runtime_id": "application_runtime_456",
+ "agent_id": "local_agent_789",
+ "passport_hash": "sha256:...",
+ "jkt": "<base64url-thumbprint>"
+ },
+ "authorization_details": [
+ {
+ "type": "https://github.com/0al-spec/agent-surface/authorization-details/agent-grant",
+ "locations": ["https://code.example.com/agent-actions"],
+ "actions": ["pull_request.get", "comment.create"],
+ "subject": {
+ "user": "app-user-7f3a"
+ },
+ "delegate": {
+ "runtime": "application_runtime_456",
+ "agent": "local_agent_789",
+ "passport_ref": "agent-passport://local-agent",
+ "passport_hash": "sha256:..."
+ },
+ "resource_server": {
+ "app_id": "code.example.com",
+ "issuer": "https://code.example.com",
+ "surface_version": "2026-06-25"
+ },
+ "scopes": ["pull_request.read", "pull_request.comment"],
+ "constraints": {
+ "repositories": ["example-org/example-repo"],
+ "pull_requests": [13],
+ "expires_at": "2026-06-25T20:00:00Z",
+ "write_approval": "required",
+ "max_actions": 20
+ },
+ "credential_profile": "proof_bound",
+ "credential_binding": {
+ "method": "dpop",
+ "runtime_id": "application_runtime_456",
+ "agent_id": "local_agent_789",
+ "passport_hash": "sha256:...",
+ "jkt": "<base64url-thumbprint>"
+ },
+ "audit": {
+ "local_receipt": "required",
+ "app_receipt": "required"
+ },
+ "grant_id": "grant_123"
+ }
+ ]
+}
+
+The response MUST describe current authoritative state and MUST use
+Cache-Control: no-store. A resource-server enforcement point that does not
+share the authorization server's authoritative grant state MUST introspect on
+every action and MUST NOT positively cache active: true. This prohibition is
+required by this profile's immediate revocation semantics; deployments that
+need positive caching must define and advertise a different bounded stale-use
+profile rather than claiming conformance to this one.
Grant Credentials and Proof
An Agent Grant MAY be represented or proven by one of several mechanisms:
@@ -1334,6 +1732,9 @@ Grant Verifica
not rejected by itself
- for a proof-bound server session, the session is active, bound to the grant
and runtime, and authenticated with the bound key or channel credential
+- when the grant contains
actions, the requested action identifier is a
+member of that authoritative allow-list, is served at a granted location,
+and remains compatible with the granted scopes and resource constraints
- scope permits the action
- resource constraints permit the target object
- expiration has not passed
@@ -1585,6 +1986,77 @@ Revocation
in-app view of active agent grants — comparable to OAuth application
management pages — where a grant can be inspected and revoked without going
through the runtime.
+OAuth Grant Revocation Profile
+The manifest agent_api.grant_revocation_url MAY identify the same endpoint as
+auth.revocation_url. When it does, a runtime requests revocation using RFC
+7009: an authenticated form-encoded POST containing the Grant Credential in
+the required token parameter and, optionally, an access_token
+token_type_hint.
+The endpoint MUST authenticate the runtime client and, for a credential it can
+locate, verify that the credential was issued to that client. A successful
+request and a request containing an unknown or already invalid credential both
+return HTTP 200 with no response body, as required by RFC 7009. The runtime MUST
+stop using the credential after that response. An HTTP 503 response means
+revocation is not confirmed; the runtime MUST continue treating the credential
+as sensitive, MUST NOT initiate new actions with it, and SHOULD retry according
+to Retry-After.
+For the Agent Grant profile, revoking a Grant Credential through
+grant_revocation_url revokes the semantic Agent Grant, not only that token.
+The application MUST immediately mark the grant inactive, reject every
+credential derived from it, invalidate refresh tokens and proof-bound sessions,
+and cascade revocation to child or exchanged grants whose authority derives
+from it. Revocation initiated through the application's user-facing grant view
+MUST produce the same state transition.
+When an active grant changes to revoked and the manifest declares an event
+subscription endpoint, the application MUST emit a grant.revoked control event
+with this minimum envelope:
+{
+ "id": "event_01J2ABCDEF",
+ "type": "grant.revoked",
+ "occurred_at": "2026-06-25T18:30:00Z",
+ "issuer": "https://code.example.com",
+ "audience": "application_runtime_456",
+ "payload": {
+ "grant_id": "grant_123",
+ "app_id": "code.example.com",
+ "runtime_id": "application_runtime_456",
+ "agent_id": "local_agent_789",
+ "passport_hash": "sha256:...",
+ "revoked_at": "2026-06-25T18:30:00Z",
+ "effective_at": "2026-06-25T18:30:00Z",
+ "reason": "user_revoked",
+ "parent_grant_id": null,
+ "cascade": true
+ }
+}
+
+Required event fields are id, type, occurred_at, issuer, audience, and
+payload. The payload MUST contain grant_id, app_id, runtime_id,
+agent_id, passport_hash, revoked_at, effective_at, reason, and
+cascade; parent_grant_id is REQUIRED for a child grant and otherwise MAY be
+null. Defined reason values are user_revoked, application_revoked,
+runtime_revoked, credential_compromise, parent_revoked, policy_changed,
+and superseded. A runtime MUST still enforce revocation when it receives an
+unknown future reason value and MAY preserve that value as opaque audit data.
+The event MUST be delivered over an application-authenticated event channel
+bound to the manifest issuer and target runtime. The runtime MUST verify
+issuer, audience, tuple binding, and channel authenticity before acting on
+it. Delivery of this control event MUST use event-channel authority independent
+of the revoked grant and MUST disclose no more grant data than the target
+runtime already possessed. A future signing profile MAY additionally define an
+application signature for portable event verification.
+After accepting the event, the runtime MUST atomically mark the grant inactive,
+discard cached active introspection state, stop new actions and credential use,
+cancel or downgrade affected sessions according to app policy, cascade the
+state to locally tracked child grants, and record a runtime receipt. Event
+processing is idempotent by id; a duplicate event MUST NOT create duplicate
+receipts or repeat external side effects.
+The event is notification, not the enforcement mechanism. The application MUST
+reject the revoked grant immediately even if delivery is delayed or lost. A
+runtime that misses the event learns the inactive state from introspection or a
+rejected action. General event ordering, acknowledgement, replay cursor,
+retention, and backpressure remain outside this profile and are defined by the
+future Event Delivery Semantics work.
Grant Revoked
If a grant is revoked:
@@ -1593,10 +2065,9 @@ Grant Revoked
- active sessions SHOULD be cancelled or downgraded to read-only according to
app policy
- receipt generation SHOULD record the revocation event
-- the event channel SHOULD deliver
grant.revoked as a final event before the
-app closes the subscription; delivery of this one event MUST NOT itself
-require an active grant, or the runtime could never learn about the
-revocation through the event channel
+- when the manifest declares an event subscription endpoint, the app MUST emit
+
grant.revoked according to the OAuth Grant Revocation Profile before it
+closes the subscription
Runtime Disconnected
If the runtime disconnects:
@@ -1836,6 +2307,20 @@ OAuth Grant Lifecycle Application
+An application conforms to the OAuth Grant Lifecycle Application profile when
+it:
+
+- satisfies the Grant-Enforcing Application profile
+- advertises the Agent Grant authorization-details type and supported standard
+OAuth grant types
+- validates and returns Agent Grant
authorization_details according to the
+Rich Authorization Request Profile
+- implements the OAuth Token Exchange Profile without privilege amplification
+- returns the active and inactive Grant Introspection Profile contracts
+- implements RFC 7009 semantic grant revocation, derivation cascade, and the
+authenticated
grant.revoked control event when an event endpoint is declared
+
Receipt-Producing Application
An application conforms to the Receipt-Producing profile when it:
@@ -1870,6 +2355,8 @@ Exam
2. Application runtime discovers and validates the surface.
3. User chooses "Connect my local agent".
4. Runtime verifies the selected agent's Agent Passport.
-5. Runtime shows consent:
+5. Runtime sends an Agent Grant `authorization_details` request.
+6. The app authorization server shows consent:
- app: code.example.com
- agent: local-agent
- scopes: pull_request.read, pull_request.comment
- repository: example-org/example-repo
- duration: 2 hours
- writes: require approval
-6. User approves.
-7. App issues grant_123 and a grant credential.
-8. Runtime stores grant_123 and the grant credential.
-9. App starts a pull-request review session.
-10. Agent reads typed PR context through runtime-mediated resources.
-11. Agent proposes a review comment.
-12. User or app approves the write.
-13. Runtime sends comment.create with an idempotency key and grant credential.
-14. App verifies grant and writes the comment.
-15. Runtime and app issue linked receipts.
-16. User revokes grant or grant expires.
+7. User approves a subset or the complete request.
+8. App issues or token-exchanges grant_123 and its bound Grant Credential.
+9. Runtime stores the authoritative granted details and credential.
+10. App starts a pull-request review session.
+11. Agent reads typed PR context through runtime-mediated resources.
+12. Agent proposes a review comment.
+13. User or app approves the write.
+14. Runtime sends comment.create with an idempotency key and Grant Credential.
+15. App verifies or introspects current grant state and writes the comment.
+16. Runtime and app issue linked receipts.
+17. User revokes grant_123; the app rejects it immediately.
+18. App emits authenticated `grant.revoked`; runtime stops affected work.
Open Questions