AP2 defines what an agent can pay for. AGP governs whether it should. Map AP2's three VDC types directly to AGP capability tokens and action envelopes — and replace V0.1's static allowlists with dynamic, policy-evaluated authorisation.
AP2 V0.1 trusts agents via static allowlists — you pre-register agent IDs at the payment processor. That works in a lab. In production it means:
| AP2 concept | AGP equivalent | Notes |
|---|---|---|
| IntentMandate | Capability token (standing) | Issued once by the authorising principal; scoped to category + spend ceiling; revocable at any time |
| CartMandate | Action envelope | One envelope per execution; binds principal, amount, merchant, and timestamp; immutably logged |
| PaymentMandate | Capability token (instrument) | Scoped to a payment method; constraints can restrict to specific merchants or currencies |
| Static allowlist | Policy engine (dynamic) | Rules evaluated at request time; no re-registration needed when policy changes |
| Processor trust assertion | Signed action envelope | Processor verifies the AGP signature rather than a list; works across all enrolled processors |
| Dispute evidence | Immutable ledger + envelope chain | AGP provides the full reasoning chain: capability → decision → action → outcome |
When a user grants an agent standing purchase authority, create an AGP capability token instead of adding the agent to an AP2 allowlist. The token carries the same information but is dynamically evaluated and instantly revocable.
# Python SDK — issue an IntentMandate-equivalent capability token import agp client = agp.Client(base_url="http://localhost:8000") # Register the agent as a principal agent = client.registry.register_principal( principal_id="procurement-agent-01", display_name="Procurement Agent", principal_type="agent", ) # Issue a standing capability token (IntentMandate equivalent) token = client.registry.issue_capability_token( principal_id="procurement-agent-01", capabilities=["payment:execute", "cart:create"], constraints={ "category": "office-supplies", "max_amount": 2000, "currency": "GBP", "require_human_approval_above": 500, }, ttl_seconds=86400, # 24 h; refresh daily ) print(token.token_id) # tok_01J...
Each CartMandate execution must be backed by an AGP action envelope — a signed, immutable record that the specific transaction was authorised at the specific moment. The agent obtains the envelope before submitting the AP2 CartMandate.
import agp import requests client = agp.Client(base_url="http://localhost:8000") # --- AGP: obtain action envelope (CartMandate authorisation) --- with client.task_session( principal_id="procurement-agent-01", task_description="Purchase ergonomic keyboard from OfficeWorld", requested_outcome="CartMandate executed for £149 keyboard", risk_level="medium", metadata={ "merchant_id": "officeworld-uk", "amount": 14900, # pence "currency": "GBP", "category": "office-supplies", "ap2_vdc_type": "CartMandate", }, ) as session: result = session.execute() action_id = result.action_id # act_01J... # --- AP2: submit CartMandate with AGP evidence --- cart_mandate_response = requests.post( "https://payment-processor.example.com/ap2/cart-mandates", json={ "merchant_id": "officeworld-uk", "items": [{"sku": "KB-ERGO-01", "quantity": 1, "unit_price": 14900}], "currency": "GBP", }, headers={ "Authorization": f"Bearer {agent_ap2_token}", "X-AGP-Action-ID": action_id, # processor verifies this "X-AGP-Principal": "procurement-agent-01", }, ) print(cart_mandate_response.json()["cart_mandate_id"]) # cm_...
The payment processor verifies the AGP action envelope instead of checking a static allowlist. This works for any AGP-compatible governance server — no per-processor registration required.
# FastAPI middleware — verify AGP action envelope before processing AP2 CartMandate from fastapi import FastAPI, Request, HTTPException import httpx app = FastAPI() AGP_SERVER = "http://agp-server:8000" async def verify_agp_envelope(action_id: str, expected: dict) -> dict: async with httpx.AsyncClient() as c: r = await c.get(f"{AGP_SERVER}/agp/action-envelopes/{action_id}") r.raise_for_status() envelope = r.json() # Validate envelope fields match CartMandate payload meta = envelope.get("metadata", {}) if meta.get("merchant_id") != expected["merchant_id"]: raise HTTPException(403, "merchant_id mismatch") if int(meta.get("amount", 0)) < expected["amount"]: raise HTTPException(403, "amount exceeds authorised envelope") if envelope["status"] != "approved": raise HTTPException(403, "envelope not approved") return envelope @app.post("/ap2/cart-mandates") async def create_cart_mandate(request: Request): action_id = request.headers.get("X-AGP-Action-ID") if not action_id: raise HTTPException(400, "X-AGP-Action-ID required") body = await request.json() total = sum(i["quantity"] * i["unit_price"] for i in body["items"]) envelope = await verify_agp_envelope(action_id, { "merchant_id": body["merchant_id"], "amount": total, }) # Proceed with CartMandate — envelope ID stored for dispute chain return { "cart_mandate_id": "cm_01J...", "agp_action_id": action_id, "agp_principal": envelope["principal_id"], "status": "executed", }
For IntentMandate executions above a threshold (e.g., a standing order for recurring software subscriptions that suddenly spikes), AGP can gate on human approval before issuing the action envelope. The agent waits; the approver gets a push notification.
# High-value CartMandate — require_approval pattern with client.task_session( principal_id="procurement-agent-01", task_description="Renew enterprise software licences — £18,500", requested_outcome="PaymentMandate executed for annual SaaS renewal", risk_level="high", metadata={ "amount": 1850000, # pence — above require_human_approval_above threshold "currency": "GBP", "ap2_vdc_type": "PaymentMandate", "vendor": "acme-saas", }, require_approval=True, approval_timeout_seconds=300, ) as session: try: result = session.execute() # envelope issued only after human approves action_id = result.action_id except agp.ApprovalTimeoutError: raise RuntimeError("Transaction not approved within 5 minutes") except agp.ApprovalRejectedError: raise RuntimeError("Transaction rejected by authorising principal")
AP2 V0.1 revocation means removing the agent from every allowlist, coordinating across processors, and hoping nothing in flight completes before the change propagates. With AGP, revoke the capability token once — all inflight requests fail at the decision engine before any CartMandate reaches the processor.
# Revoke an agent's payment authority instantly client.registry.revoke_capability_token( token_id="tok_01J...", reason="agent compromised — incident INC-2024-0881", ) # Any subsequent task_session.execute() for this agent will raise: # agp.CapabilityRevokedError: tok_01J... revoked at 2026-04-17T14:23:01Z # No processor update required. No in-flight CartMandate will be issued.
AP2's dispute process asks: "Did the agent have authority?" With AGP you can prove it.
Every action envelope is immutably logged with: the capability token that permitted it,
the policy rules that evaluated it, any approval events, and the outcome. The processor
stores the X-AGP-Action-ID — disputes resolve by replaying the envelope.
# Retrieve the full evidence chain for a disputed CartMandate import agp client = agp.Client(base_url="http://localhost:8000") # action_id stored by processor at CartMandate execution evidence = client.ledger.get_envelope_chain("act_01J...") for event in evidence.events: print(f"[{event.timestamp}] {event.type}: {event.summary}") # Output: # [2026-04-17T09:00:00Z] CAPABILITY_ISSUED: tok_01J... (IntentMandate, £2,000 ceiling) # [2026-04-17T14:10:32Z] TASK_CREATED: procurement-agent-01 → OfficeWorld £149 # [2026-04-17T14:10:33Z] POLICY_EVALUATED: office-supplies ✓, amount ✓, currency ✓ # [2026-04-17T14:10:33Z] ENVELOPE_ISSUED: act_01J... approved # [2026-04-17T14:10:34Z] AP2_CART_MANDATE: cm_01J... executed at OfficeWorld
When a UCP checkout session triggers an AP2 payment, both protocols share the same AGP
action envelope. The commerce-session.json schema links all three:
{
"agp_task_id": "tsk_01J...",
"agp_action_id": "act_01J...",
"ucp_checkout_id": "cs_01J...",
"ap2_cart_mandate_id":"cm_01J...",
"ap2_vdc_type": "CartMandate",
"principal_id": "procurement-agent-01",
"amount_pence": 14900,
"currency": "GBP",
"merchant_id": "officeworld-uk",
"created_at": "2026-04-17T14:10:32Z"
}
commerce-session.json schema
in full. The AP2 action envelope and UCP checkout session share the same
X-AGP-Action-ID.
task_session.execute() first. Pass the returned action_id as X-AGP-Action-ID on the AP2 CartMandate request.GET /agp/action-envelopes/{action_id}. Validate merchant, amount, and status. Store action_id alongside the CartMandate record.revoke_capability_token(). All subsequent task sessions for that agent fail before reaching the processor — no allowlist coordination.ledger.get_envelope_chain(action_id). The full chain — IntentMandate issuance → policy evaluation → CartMandate execution — is available on demand.X-AGP-Action-ID header is an AGP convention.
Coordinate with your payment processor to ensure they store and verify it. As AP2
matures, governance header standardisation is expected in a future revision.