Personal Agent Protocol draft 0.2 published, draft 0.3 in progress · What changes in poppy.json →
PAP Checker/Blog

Personal Agent Protocol Draft 0.3: What Changes, Shown on Real Runs

Draft 0.2 is frozen and draft 0.3 is in progress, with 15 breaking changes. Company-defined scopes, scope hints, Session Keys, a signed browser join and excluded tasks, shown on real runs against test stores.

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:

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:

Kettle Street Noodles' own sign-in page: PAP Playground wants to use your Kettle Street Noodles account, continue as test customer, and one permission, See your orders, with the store's description and the scope orders:read

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:

PAP Playground's sign-in card at Northlight Flowers: asks for orders:read, tagged changes nothing and reads or changes your personal data, and orders:write, tagged can make changes that can't be undone and reads or changes your personal data, each with the store's description, and a note that the marked scopes need your go-ahead on the company's page

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:

PAP Playground's protocol trace at Kettle Street Noodles: my_orders 403 insufficient_scope, the person signs in for orders:read, the code exchange with a proof from the Session Key, then change_order 403 insufficient_scope, a second sign-in for orders:read orders:write, and the change proposed, approved, confirmed and succeeded

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."

Kettle Street Noodles' sign-in page the second time: signed in as the test customer, See your orders and Change your orders, both ticked, with the store's description of each and Approve and Decline buttons

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:

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:

PAP Playground's protocol trace at Northlight Flowers: the session starts with a new Session Key, the agent's browser starts signed as PAP Playground, loads the browser-session page and gets a form with a one-time code, submits the form with a DPoP proof signed with the Session Key, answered 303 then 200, and opens the account page

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.

The agent's browser, live, at Northlight Flowers: an agent session bar, the Your orders page signed in as the test customer, the order with Spring Tulips, and a Connected agents panel showing PAP Playground 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:

The conversation response in PAP Playground's trace: a message with role company, text saying the driver can leave the order with the concierge because it contains no alcohol, and an author object with id aut_assistant, name Kettle Street Noodles assistant and type agent

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:

PAP Playground at Harbour Row Stays: the task Cancel my booking, the agent reading the excluded tasks, and its answer that the guesthouse doesn't let personal agents cancel bookings, that the guest can cancel on the website or with the front desk, and that it can move the booking to other dates instead

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:

Outside OAuth, the protocol adds poppy.json, Mediated Sign-In, the browser join form, conversations and extensions.

Smaller changes worth knowing

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:

  1. In the issuer's metadata, add grant_types_supported (JWT bearer, refresh token, plus the sign-in grants you offer), list your scopes in scopes_supported, and replace poppy_domains with protected_resources.
  2. In poppy.json, set "protocol_version": "0.3", remove auth.direct and auth.device, rename auth.custom_scopes to auth.scopes, and describe every scope you list. Add auth.scope_hints if you can.
  3. Replace poppy:read and poppy:write with your own scopes, and answer missing access with insufficient_scope.
  4. On the token endpoint, name sessions by their DPoP key, support dpop_jkt, and drop session_id and signed_in.
  5. If you offer browser sessions, switch to the GET form with a one-time code.
  6. Optionally, publish excluded_tasks.url for 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:

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

Check yours

See what a personal agent reads on your domain, or paste a poppy.json before you publish it.

More from the blog