Draft 0.2 of the Personal Agent Protocol was published on 24 October 2026 and is now frozen at its own address. The same weekend, draft 0.3 opened at the spec's main address, and by 26 October its changelog listed 30 changes, 15 of them tagged Breaking. Several change how a company describes sign-in, which scopes it offers, and how an agent's session is named.
We moved our test agent, our three test stores and the checker to draft 0.3 on 26 October, then ran real tasks against them. This post walks through what changes in 0.3, using screenshots from those runs. One thing first: 0.3 isn't final. The changelog says the list "grows until 0.3 is published", so anything below can still change.
What is Personal Agent Protocol draft 0.3?
Draft 0.3 is the third version of the Personal Agent Protocol (PAP, or "Poppy"), the open standard from Sierra and Meta for how a person's AI agent finds a company, starts a session, signs the person in and acts for them. It's served at personalagentprotocol.org/docs/spec and marked in progress. Draft 0.2 stays frozen at /docs/0.2/spec, and 0.1 at /docs/0.1/spec.
Most of 0.3 moves the protocol closer to OAuth as it already exists. Things that 0.2 kept in poppy.json or in custom parameters now come from the company's OAuth server, or use the standard OAuth name. The changelog splits changes into major ones (design decisions) and minor ones (gaps, consistency, following an RFC), and tags each one that can break a 0.2 implementation.
If you're new to the protocol, How the Personal Agent Protocol works goes through a real run step by step, on draft 0.1. What changed in draft 0.2 covers the version before this one.
The runs behind this post
On 26 October we ran three tasks on PAP Playground, a test personal agent, against three PAP Sandbox test stores, all speaking draft 0.3:
- Kettle Street Noodles (food delivery): "Move my delivery to Saturday and add a side of gyoza." The agent worked through the restaurant's MCP server.
- Northlight Flowers (retail): look at the account page in the agent's browser, then add a birthday card to an order.
- Harbour Row Stays (lodging): "Cancel my booking", a task the store has chosen not to offer to agents.
I maintain PAP Playground, PAP Sandbox and PAP Checker. These are test stores with a test customer and no real orders, so the runs show how the draft behaves on the wire, not how any real company has built it.
Sign-in types and scopes now come from the OAuth server (§3.1, §3.2, §4.4)
In 0.2, poppy.json listed the sign-in types a company offered (auth.direct, auth.device, auth.mediated), each with its scopes, and the protocol defined two scopes of its own, poppy:read and poppy:write.
In 0.3, auth.direct and auth.device are gone. A company offers Direct Sign-In when grant_types_supported in its OAuth server metadata includes authorization_code, and Device Sign-In when it includes urn:ietf:params:oauth:grant-type:device_code. That list is now required, and must include the JWT bearer and refresh token grants. The changelog's reason is that there's then "no second list to drift out of sync with the issuer".
poppy:read and poppy:write are gone too. Each company defines its own scopes, usually the OAuth scopes it already has, and lists them in scopes_supported. It describes each one in the OAuth security scheme of an OpenAPI description it lists, or in auth.scopes, which replaces auth.custom_scopes. Names starting with poppy: stay reserved. Only Mediated Sign-In keeps its own list, auth.mediated.scopes.
So the test restaurant now asks for orders:read and orders:write, and the guesthouse for bookings:read and bookings:write. This is how the restaurant's sign-in page looked when our agent asked to read orders:

The words on that page are the company's own: the description it wrote for orders:read. In 0.2 every company's "see your account" meant the same protocol scope. In 0.3 the agent can show the person what this company means by it.
Scope hints: how much a scope can do, before sign-in (§3.1, §4.4)
A new, optional auth.scope_hints lets a company say, for each scope, whether its changes are none, reversible or irreversible, and whether it touches personal_data. The changelog says the idea comes from MCP's tool annotations, applied to a whole scope. OAuth and OpenAPI describe a scope with one string, so there was nowhere else to put it.
"scope_hints": {
"orders:read": { "changes": "none", "personal_data": true },
"orders:write": { "changes": "irreversible", "personal_data": true }
}
An agent can use the hints to decide before sign-in which scopes need the person's go-ahead. Our test agent shows them on its sign-in card:

The draft is clear that hints are the company's claims, not the person's approval. The person still approves on the company's page, and can approve fewer scopes.
One sign-in per need: insufficient_scope (§4.4)
In 0.2, a request that needed the person's account got a custom sign_in_required error. In 0.3 it gets insufficient_scope, the standard OAuth error from RFC 6750, and the error names the scopes the request needs. A signed-out token has no scopes, so the standard error fits.
The draft also says agents should request only the scopes a task needs. Our agent takes that literally: it asks for what its next call needs. At the restaurant, it tried to read the orders and got 403 insufficient_scope naming orders:read. The person signed in for that. Then it proposed the change, got insufficient_scope again naming orders:write, and asked a second time:

The second time, the company's page listed both scopes, with the new one's description saying what it can do: "A change can add dishes and charge the difference, which can't be undone. You still approve each change."

The task needed both scopes, so asking for both at the start would also fit the rule. Two prompts ask more of the person; one up front asks for more access before it's used. The draft leaves that choice to the agent.
Session Keys replace session_id (§4.2, §4.3, §4.5)
In 0.2, a session was named by a custom session_id parameter on every token request. In 0.3, each session has its own DPoP key, the Session Key, and the key's thumbprint names the session. session_id is gone from every request and response, so Poppy's token requests are standard OAuth requests.
What that means in practice:
- A proof from a new key starts a new session. A proof from a known key renews it or signs it in.
- Agents use a new key for each session, instead of one per person. A session can't change its key.
- Companies must support
dpop_jkt, and agents must bind the authorization code to the Session Key with it (or with a proof on a pushed authorization request, at an optionalpushed_authorization_request_endpoint). - A session that has ended, or belongs to someone else, now gets the standard
invalid_grantinstead ofinvalid_session.account_mismatchstays. - Token responses no longer carry
signed_in: the grant says whether a token is signed in.
In the trace above, that's "Start a Session (JWT bearer grant, new Session Key)", and later "Exchange the code (proof from the Session Key)". The authorization URL carried dpop_jkt, so the code the restaurant issued could only be exchanged with that session's key.
The issuer lists your domains in protected_resources (§3.2)
Before an agent uses a poppy.json, it checks that the issuer really speaks for that domain. In 0.2 the issuer's metadata listed the domains in a custom poppy_domains field. In 0.3 it uses protected_resources, the field RFC 9728 registers, with https://{domain} for each domain, exactly in that form: no path, no port, no trailing slash. It can list other resources too, such as MCP servers. Our agent's trace shows the check on every run: "Issuer https://food.papsandbox.com lists https://food.papsandbox.com in protected_resources".
Two related rules tightened as well. A redirect of the well-known URL must now end at exactly /.well-known/poppy.json with no query, so a file uploaded elsewhere on a company's domain can't speak for it (§3). And an MCP server that doesn't support DPoP gets its Bearer tokens only through Token Exchange (RFC 8693), so a company that lists one must support that grant (§4.3). Our restaurant's MCP server is Bearer-only, and its trace shows "A Bearer Session Token for the MCP server (Token Exchange, RFC 8693)". The florist's MCP server takes DPoP tokens directly.
Browsers join a session with a signed one-time code (§5, §9.6)
A personal agent can browse a company's website in the same session, signed in as the person. In 0.2, the agent's browser posted a signed browser assertion to join. Any browser could post one, so, as the changelog explains, a malicious agent could make a victim's browser join a session signed in to the attacker's account.
In 0.3 the browser loads web.browser_session_endpoint with a GET and gets back a form holding a one-time code, plus a SameSite=Strict cookie that ties the code to that browser. The agent signs a DPoP proof with its Session Key, using the code as the proof's nonce, puts it in the form and has the browser submit it. The company checks the cookie, the code and the proof, then sets the session's cookie. The browser assertion and return_to are gone. A new section, §9.6, explains the two attacks the design prevents.
Here's the florist run, where the agent opened the account page in its own browser:

After the person signed in, the same browser showed the florist's account page as the session saw it: the test customer's order, and PAP Playground listed under connected agents with orders:read.

A website that refuses an action because the session lacks a scope should now show a visible error naming the missing scopes, so the agent can ask for them (§5).
author replaces sender (§7.4)
When the agent talks to the company's own agent, each message used to carry sender. In 0.3 it carries an author object. Its type takes sender's old values, agent or human, and a company message can also say which of the company's people or AI personas wrote it, with an optional id, display name and image.
We asked the restaurant's agent whether the driver could leave the order with a concierge. The reply came back with its author:

Tasks a company doesn't offer (§8.1, §8.2)
A company can now publish a list of the tasks it chose not to offer through the protocol, at excluded_tasks.url. Each entry is a short task phrase with an optional alternative: instructions, references to an API operation, an MCP tool or a web page, or both. An agent can then tell the person straight away, instead of searching every interface for a way to do it. An openapi or mcp reference must name an API the company's poppy.json lists.
{
"tasks": [
{
"task": "Cancel a booking",
"alternative": {
"instructions": "Bookings are cancelled by the guest on the website or by the front desk, under the cancellation policy. A booking can be moved to other dates instead, through the API or the Company Agent.",
"references": [{ "type": "web", "url": "https://stays.papsandbox.com/policies#cancellation" }]
}
}
]
}
That's the guesthouse's list. When we asked our agent to cancel a booking there, it didn't sign in or call anything. It read the list and passed on the alternative:

Feedback changes to match (§8.3). A company can answer a feedback report with HTTP 200 and an alternative when the task can be done after all, instead of 202 with no body. Agents must accept either, and shouldn't report a task the company already lists as excluded.
Mediated Sign-In is transitional (§4.7, §9.5)
Mediated Sign-In, where the agent collects the person's credentials and sends them to the company, is now described as a transitional step, like OAuth's password grant. Companies should offer it only until they can add Direct or Device Sign-In, and stop once those cover the same scopes. The protocol plans to remove it, on a date set when 1.0 is published. Agents should prefer Direct or Device Sign-In when it covers the task.
What Poppy adds to OAuth (§4.10)
A new section lists what a company's authorization server needs beyond standard OAuth. It adds no requirements, but it's the most useful page in 0.3 for anyone scoping the work:
- sessions named by DPoP keys;
- JWT bearer assertions the agent issues itself, naming itself in
issand the person's user ID insub(most authorization servers need custom work to accept these, §4.2); - one account per session (
account_mismatch); - described scopes;
- account tokens that can start new sessions;
- Bearer tokens only by Token Exchange, each for one MCP server;
- a few fields and codes:
refresh_token_expires_in,extension_requiredand the reservedpoppy:prefix.
Outside OAuth, the protocol adds poppy.json, Mediated Sign-In, the browser join form, conversations and extensions.
Smaller changes worth knowing
- Secrets stay out of the model (§9.2). Session tokens, private keys, authorization codes, PKCE verifiers and device codes must now be kept out of model context, as account tokens already were. That was a SHOULD before.
- Device Sign-In follows RFC 10027 (§4.6). Companies that offer it, and agents that use it, follow that RFC's best practices for cross-device flows, and agents use Direct Sign-In when they can.
- Agent IDs (§4.2). An agent that acts for a person through several agents can name one in an optional
agent_idclaim. - Loopback redirect URIs (§4.1). An app on the person's own computer can receive the authorization response.
- Sign-out (§4.9). Revoking an account token should also invalidate the session tokens from the same sign-in.
What it means for a company that publishes poppy.json
If you've published a 0.2 poppy.json and want to move to 0.3:
- In the issuer's metadata, add
grant_types_supported(JWT bearer, refresh token, plus the sign-in grants you offer), list your scopes inscopes_supported, and replacepoppy_domainswithprotected_resources. - In
poppy.json, set"protocol_version": "0.3", removeauth.directandauth.device, renameauth.custom_scopestoauth.scopes, and describe every scope you list. Addauth.scope_hintsif you can. - Replace
poppy:readandpoppy:writewith your own scopes, and answer missing access withinsufficient_scope. - On the token endpoint, name sessions by their DPoP key, support
dpop_jkt, and dropsession_idandsigned_in. - If you offer browser sessions, switch to the GET form with a one-time code.
- Optionally, publish
excluded_tasks.urlfor the tasks you don't offer.
The poppy.json guide has the field-by-field list. Since 0.3 can still change, there's a case for waiting until it's published, especially as an agent built for 0.2 won't read a 0.3 document, and the other way round (§3.1). If you do move, a checker that applies the rules of the version you declare, such as ours, covers the public documents.
What it means for agent builders
The agent side changes as much as the company side. You read sign-in types and scopes from the issuer's metadata, not from poppy.json. You mint a new DPoP key for each session and send dpop_jkt on every authorization request. You handle insufficient_scope and invalid_grant instead of the custom errors, read author instead of sender, and check excluded_tasks before trying a task. For MCP servers that only take Bearer tokens, you use Token Exchange. And the browser join becomes a GET, a form and a signed proof.
What's still open
Building both sides on the same day turned up a few questions the 26 October text doesn't settle yet:
- A code exchange with a key that has no session yet (§4.5, §4.6). Direct and Device Sign-In sign in "that key's session", but a new key otherwise starts one, and a code exchange carries no user ID.
- What
insufficient_scopenames for a signed-in token (§4.4, §6). Only the missing scope, or the scopes held plus the missing one, which is what the agent has to request again? - When a company should stop offering Mediated Sign-In (§4.7). Direct and Device Sign-In "can grant any scope in
scopes_supported", which must include the mediated scopes, so every company that offers both looks covered. - Moving one domain at a time (§3.2). Documents that name the same issuer must have the same
auth, which a company moving several domains to 0.3 one at a time can't meet. - Telling an agent you support
dpop_jkt. Companies must support it, but no metadata field says so.
The open topics on the path to 1.0, including payments, are unchanged.
FAQ
Is Personal Agent Protocol draft 0.3 final?
No. It's in progress, and the changelog says the list grows until 0.3 is published. Draft 0.2 is the latest published version: it was published on 24 October 2026 and is frozen.
Is draft 0.3 backward compatible with 0.2?
No. 15 of the 30 changes listed on 26 October are tagged Breaking, and before 1.0 an agent uses only a poppy.json whose major.minor version it supports.
What replaces poppy:read and poppy:write?
Your own scopes. The protocol no longer defines any. You list them in scopes_supported in your OAuth server metadata and describe each one, in an OpenAPI security scheme or in auth.scopes. Names starting with poppy: stay reserved.
What are scope hints?
An optional auth.scope_hints entry per scope that says whether its changes are none, reversible or irreversible, and whether it touches personal data. Agents use them to decide which scopes need the person's go-ahead before sign-in.
What replaced session_id?
The Session Key. Each session has its own DPoP key, and the key's thumbprint names the session, so token requests no longer carry session_id.
What are excluded tasks?
A public list, at excluded_tasks.url, of the tasks a company chose not to offer through the protocol, each with an optional alternative, so an agent can tell the person straight away.
Should my company declare protocol_version 0.2 or 0.3?
Declare the version whose rules you meet. A "0.2" document is checked against the frozen 0.2 text. Moving to "0.3" now means following a draft that can still change.
Sources
- Personal Agent Protocol specification, draft 0.3, in progress (read 26 October 2026)
- Personal Agent Protocol specification, draft 0.2, published 24 October 2026, frozen
- Changelog, personalagentprotocol.org (read 26 October 2026)
- Guides, draft 0.3, personalagentprotocol.org (read 26 October 2026)
- Operations extension, draft 0.3, personalagentprotocol.org
- Path to 1.0, personalagentprotocol.org (read 26 October 2026)
- Test runs on PAP Playground against PAP Sandbox test stores, 26 October 2026