PAP and PACT, side by side
Two consent protocols let a person's own agent work with a business for them: the Personal Agent Protocol (PAP, draft 0.3, Sierra and Meta; personalagentprotocol.org) and PACT (1.0, Decagon and Instinct). They answer the same questions — which agent is this, for whom, and how far may it go — in different ways, and one application can use both in the same task.
What each carries
| PAP (draft 0.3) | PACT 1.0 | |
|---|---|---|
| The business is found at | its domain: https://{domain}/.well-known/poppy.json, the organization's domain checked, its OAuth issuer's metadata checked | an A2A Agent Card URL; the interface chosen by binding and version (HTTP+JSON, 1.0) |
| The agent proves itself with | its client metadata and same-domain JWKS; a short-lived Session Token bound to the Session's own DPoP key (the Session Key) | a personal-agent JWT (ES256), iss the platform, its JWKS registered once with the Provider |
| The person is | a pairwise id per company, signed out until needed | sub, a stable opaque id per person, in every JWT |
| Signing in at the business | Direct (OAuth with PKCE), Device (RFC 8628) as the issuer's grant_types_supported offers them, or Mediated (pap.protectedInput); an Account Token afterwards | Device code (RFC 8628), the personal-agent JWT as client authentication; a delegation token |
| What the person allows | scopes asked at sign-in; may be fewer than asked | scopes from the card's deviceCode flow; may be fewer than asked, read back from scope |
| Asking for more | insufficient_scope on the conversation | TASK_STATE_AUTH_REQUIRED with pact.missingScopes (a step-up) |
| How the agent talks to the business | conversations with the company's agent or a person, OpenAPI, MCP, a browser joined to the Session | A2A messages (message:send), one contextId per agent, person and Brand |
| A consequential action | the Operations extension: an exact revision, approved by the person, performed at most once | the business acts within the granted scopes; every reply under delegation carries a signed receipt (pact.receipt) |
| What is kept | the operation, its revision and its confirmation | the receipt: grant, scopes used, actions, verified before it is kept |
| In Agent Runtimes | the PAP SDK's plugins (pap.*), PapPersonalAgentCapability, pap.connections and pap.company in the Appspec | agent_runtimes.protocols.pact (pact.*), pact.connections in the Appspec |
Both keep the person's credentials at the business: the agent shows the business's own link and code, never asks for a password in the conversation, and holds nothing a model reads.
One task, both protocols
examples/protocols/pap_and_pact/app.py is an application, Atlas, asked
where is my jacket order, and when do I fly? The order is at Manzanita
Outfitters, a PAP company (the PAP SDK's demo world); the flight at Example
Air, a PACT Brand (the local Provider). Atlas signs in at each one's own page
with one read scope, asks, and writes one record:
python examples/protocols/pap_and_pact/app.py
Where is my jacket order, and when do I fly?
Manzanita Outfitters: Your order ord_7Hk2: Ridge Jacket, size M, delivered.
Example Air Support: Your booking AB123 flies on 2026-11-02.
The record:
{"protocol": "PAP", "counterpart": "Manzanita Outfitters", "event": "company discovered", ...}
{"protocol": "PAP", "counterpart": "Manzanita Outfitters", "event": "sign-in asked", "error": "insufficient_scope"}
{"protocol": "PAP", "counterpart": "Manzanita Outfitters", "event": "signed in", "scopes": ["orders:read"], ...}
{"protocol": "PAP", "counterpart": "Manzanita Outfitters", "event": "answered", ...}
{"protocol": "PACT", "counterpart": "Example Air Support", "event": "card read", "profile": "delegated", ...}
{"protocol": "PACT", "counterpart": "Example Air Support", "event": "sign-in asked", "scopes": ["bookings:read"]}
{"protocol": "PACT", "counterpart": "Example Air Support", "event": "signed in", "scopes": ["bookings:read"], ...}
{"protocol": "PACT", "counterpart": "Example Air Support", "event": "receipt verified", "scopes_used": ["bookings:read"], ...}
{"protocol": "PACT", "counterpart": "Example Air Support", "event": "answered", ...}
The receipt reaches the record through pact.receiptStore: Atlas's host
contributes its record there below the plugin's in-memory default, as a
Datalayer host contributes the application's record. It runs in one process,
with no network and no model, against local test counterparts.
What never reaches a model
Neither protocol's codes or tokens are in the answer or the record: the
test (test_protocols_pap_and_pact.py) collects both user codes and the
PACT delegation token as the businesses and the host handled them, and
checks that none of them, nor any compact JWT, is in what the tool returns.
On Datalayer
An application reaches a PAP company through the MCP gateway's pap
toolset (pap.connections) and a PACT Brand through its pact toolset
(pact.connections); both are decided by the application's Rules from the
tools' action classes, and the tokens are held by IAM, never by the runtime.