How Poppy connects agent identity, account authorization and execution
A technical review of Poppy’s working 0.3 draft: discovery, JWT assertions, DPoP session keys, OAuth grants, browser and conversation boundaries, and operation recovery.
Imagine asking an assistant to find satellite imagery of a wildfire so you can compare how it spread over three days. A provider might expose its image catalogue through an API, offer a browser-only map for selecting the area, and require a conversation to resolve a licensing question. It’s one request from your perspective, but the assistant crosses several systems with different ways of identifying it and deciding what it can do.
Poppy, or Personal Agent Protocol, connects those interactions through a common session and authorization model. It covers discovery, agent identity, OAuth grants, browser access and conversations, with an optional extension for confirming individual actions.
The engineering appeal is that a company could support delegated tasks without building a separate account relationship for every interface. I think the technical case for that approach depends on how consistently the task’s authority survives a change of interface.
Author’s note: I started looking into Poppy while the 0.3 draft was evolving, and this grew from a protocol overview into a closer look at what its design choices mean in practice. That’s how a synthesizer, a concert ticket sale and an auction ended up in a piece about authorization.
This review follows the working 0.3 draft as of October 25, 2026. The changelog lists 0.3 as in progress and dates the publication of 0.2 to October 24. The workflows below are hypothetical examples of what companies supporting the relevant parts of Poppy could offer. Code examples are abbreviated and use placeholder values.
Discovery: finding a rare synthesizer
A company publishes /.well-known/poppy.json to describe its supported interfaces and name its authorization issuer. The agent then retrieves the issuer’s OAuth authorization server metadata, which supplies endpoints and supported grants.
The discovery chain includes reciprocal checks. In 0.3, organization.domain has to match the host serving the discovery document, the authorization metadata’s issuer has to match auth.issuer, and its protected_resources list has to include the company’s origin.
Relevant fields in the company’s discovery document might be:
{
"organization": {
"name": "Example Instruments",
"domain": "instruments.example"
},
"auth": {
"issuer": "https://auth.instruments.example"
}
}
The issuer’s metadata would include:
{
"issuer": "https://auth.instruments.example",
"protected_resources": ["https://instruments.example"],
"scopes_supported": ["inventory:read", "orders:write"]
}
The 0.3 changes also restrict discovery redirects to a final URL at the well-known path. Together, those checks establish a relationship between the document’s location and the issuer it names, rather than accepting either claim in isolation.
Discovery also introduces fetches of documents controlled by another party. The implementation guides recommend limits on redirects, response sizes and network destinations, particularly when the service retrieving metadata can reach internal infrastructure.
Suppose a musician asks an assistant to find an out-of-production synthesizer under $2,000. Once it finds a specialist shop’s domain, discovery could tell it how to interact with that shop without a custom integration for that particular seller. The reciprocal checks become useful if the shop outsources authorization to another service. I like this as a way for smaller businesses to make their interfaces discoverable, although it doesn’t solve finding the sellers in the first place or establish that the instrument is authentic. A valid discovery chain is fairly limited evidence when someone is about to buy thirty-year-old electronics.
Agent identity: following a concert ticket release
A personal agent’s client_id is an HTTPS URL that resolves to its metadata document. The document supplies its public signing keys and callback locations. Client authentication uses private_key_jwt, based on RFC 7523.
Starting a session involves three signed objects with different purposes. The client assertion authenticates the personal agent. A separate grant assertion names the company-specific pseudonymous user, and a DPoP proof establishes possession of a fresh session key.
The grant assertion includes claims of this form:
{
"iss": "https://assistant.example/client.json",
"sub": "opaque_user_for_this_company",
"aud": "https://auth.tickets.example"
}
Here, iss identifies the personal agent, sub identifies its user at this company, and aud identifies the company’s authorization issuer. A complete assertion also includes timestamps and a unique identifier.
The company verifies these objects before issuing a signed-out Session Token. It can recognize the agent and the pseudonymous user without that session having access to a customer account yet. Account authorization happens separately when a task requires it.
In 0.3, the session key’s thumbprint identifies the session, replacing the earlier custom session_id parameter. The agent’s published signing keys and its session keys therefore have separate roles: the former authenticate the agent, while the latter bind requests to a particular session.
DPoP, defined in RFC 9449, provides the proof-of-possession mechanism. A resource request carries a signed proof containing the HTTP method, target URI, issuance time and access-token hash, with a nonce when required.
Those fields constrain where and how a copied proof can be used, but they do not constitute approval of the complete business request. The proof does not generally cover the HTTP body, and its target URI excludes the query component. An authenticated request can consequently still contain an action or parameters outside the caller’s authority.
The guides also keep private keys and credentials outside model context. Application code can sign requests on the model’s behalf without making the model a holder of the underlying credentials.
A concert ticket release makes this separation more useful than a routine login example suggests. An assistant could check public availability in a signed-out session and request account access when the person wants to buy. The company can recognize the agent and its pseudonymous user across those stages without treating recognition as purchase authority. I think that’s a sensible foundation for an interaction that becomes more sensitive over time. It doesn’t give the agent a right to enter a queue, preserve its place or bypass purchase limits; those remain decisions for the ticketing service.
Account authorization: renting GPUs within a budget
Direct Sign-In uses the authorization code flow with PKCE. Device Sign-In uses the device authorization grant, allowing the person to authorize through another device. The working draft prefers those flows over Mediated Sign-In, where the agent submits the person’s credentials.
Poppy calls the resulting access token a Session Token and the refresh token an Account Token. Their underlying OAuth roles remain familiar: an access token is presented to a protected resource, while a refresh token obtains subsequent access tokens.
The company defines its own scopes in 0.3, rather than prescribing one universal set of agent permissions. The protocol requires those scopes to be enforced across the supported interfaces, including actions performed through the company’s conversational agent.
This means scope enforcement extends beyond the API gateway. If a support tool executes with broader service credentials, the connection between the caller’s grant and the tool’s authority still has to be preserved. Passing the scopes into a prompt supplies context but does not enforce them at execution time.
The company also keeps session and grant records, including the account association and granted scopes. An Account Token can obtain access for new sessions, so ending a session is different from revoking the agent’s access to the account.
Token revocation affects the relationship between that stored state and already-issued access tokens. A self-contained token can remain cryptographically valid after its grant is revoked. Whether it remains usable depends on invalidation, a current-state check or the remaining token lifetime.
The draft’s account of what it adds to OAuth also identifies custom work for the self-issued session grant. Existing OAuth infrastructure supplies useful components, but supporting Poppy still involves implementing its session semantics.
Consider a researcher asking an assistant to rent GPUs, the chips used for much AI training, and run an experiment overnight with a total budget of $300. A hypothetical instances:create scope could permit renting the machines, but it doesn’t express that budget. If three parts of the assistant work at once, each could book $120 of computing time and collectively commit $360 before any of the charges appear on the bill. This is where I think the separation between account access and task authority becomes particularly consequential. Poppy can carry the provisioning grant, while enforcing the researcher’s instruction requires shared accounting for running instances and pending reservations. A valid token doesn’t resolve that coordination problem.
APIs and browsers: comparing satellite images
Poppy can advertise OpenAPI APIs and remote MCP servers. The working draft uses DPoP where supported, with OAuth Token Exchange providing a compatibility path to a Bearer token for an MCP server that does not support DPoP.
The exchanged token is restricted to its target MCP resource. That limits where it can be used, although possession of the Bearer token is still sufficient to use it within its authority and lifetime. The fallback therefore has a different security property from the proof-of-possession path.
Browser access has its own mechanism. In 0.3, the company issues a one-time code tied to a browser cookie. The agent signs a proof using its session key, and verification allows the company to set the corresponding session cookie. The guides recommend isolated browser storage for each session.
The website then evaluates requests under that session’s scopes. This lets browser activity share authorization context with API activity, although the transports and their credentials remain different.
Using the person’s already-authenticated browser is still an open design discussion. A person and an agent sharing the same cookies creates an attribution problem: signed requests can identify cooperative agent activity, but an unsigned request is not necessarily evidence of human activity.
In the satellite imagery example, the assistant could check when each image was taken through the API, then use the provider’s interactive map to select the geographic area. Sharing session context makes that combination practical without requiring the provider to expose every feature through one interface. I think this is a compelling reason to include browser access in the protocol, especially for services whose visual tools carry information that an API doesn’t expose. The browser still needs to respect the agent’s grant: borrowing the person’s full session could also expose billing controls or imagery projects outside the task. Sharing context is useful only if the narrower authority survives the change of interface.
Conversations: rerouting a passenger and their dog
Conversations add another kind of persistent state. The official conversation implementation pattern describes an adapter with an ordered event log, cursors and message-ID deduplication in front of an existing support platform.
status records whether work is ongoing, idle, queued or closed, while responder identifies whether a human or agent is handling the company side. Message deduplication allows a retried initial request to recover the existing conversation instead of creating a second one.
A handoff changes who responds without inherently granting more authority. The conversation’s account context and the permissions applied to its tools remain relevant even when the next step is handled by a different model or a person.
Now imagine a cancelled flight where the passenger is travelling with a dog and the obvious replacement itinerary includes a carrier that won’t accept it. Their assistant may need to negotiate with the airline’s agent, then continue with a person who can authorize an exception or propose another route. Stable conversation and message identifiers could preserve that exchange through a dropped connection or handoff. I think this is a stronger case for agent-to-company conversations than reproducing an ordinary search interface: the useful work is resolving the exception. A human taking over still doesn’t mean the passenger accepted a new fare or that the replacement ticket was issued.
Operations: bidding in a live auction
The optional Operations extension represents an action as a shared record with versioned terms. The company proposes an operation and the agent confirms a particular revision after obtaining approval or applying a standing permission that covers it.
An abbreviated proposal from a hypothetical auction platform might look like this:
{
"operation_id": "bid_42",
"revision": 2,
"state": "proposed",
"summary": "Place a binding USD 1,200 bid on lot 17. If successful, the total due is USD 1,200 including fees.",
"terms": {
"auction_id": "auction_8",
"lot_id": "lot_17",
"bid": { "amount": "1200.00", "currency": "USD" },
"total_if_won": { "amount": "1200.00", "currency": "USD" },
"binding": true
},
"expires_at": "2026-10-26T12:00:00Z",
"confirmation": null,
"result": null
}
The corresponding confirmation names the revision:
{
"revision": 2,
"approved_by": "user"
}
Before execution, the company checks the operation’s revision, scopes, expiry and approval mode. A repeated confirmation is required to return the existing state rather than perform the action again.
The extension also distinguishes a failed action from an unknown outcome. If a downstream call times out and the company cannot establish whether it took effect, the operation stays in progress.
That requirement has consequences for the execution adapter. Recording a confirmation and dispatching work to another system are separate steps that can fail independently. Depending on the dependency, preserving the operation’s semantics can involve durable dispatch, downstream idempotency or reconciliation.
The record also has a specific evidentiary limit. It establishes that the personal agent asserted approval of a revision, but it does not independently establish that the person saw or approved those terms. The extension explicitly acknowledges that trust boundary.
In a live auction, being outbid while the person considers a proposal shouldn’t turn approval of $1,200 into permission to offer $1,300. If the proposed bid changes, approval needs to cover the new revision, either explicitly or through an applicable standing permission. A timeout after submission also leaves the assistant with an existing operation to recover rather than a reason to place another bid. I think this is the strongest part of the design because the approved terms remain identifiable even while the surrounding situation changes. The result still needs careful interpretation: successfully submitting a bid isn’t the same as winning the auction, and an approval assertion still relies on the agent’s account of what the person authorized.
Where this leaves the integration
Across these examples, Poppy gives the company and the personal agent a common way to refer to the actor, the account grant, the ongoing interaction and, when the extension is used, the proposed action. That could remove a considerable amount of bespoke coordination between interfaces, although it still leaves meaningful engineering work behind each one.
The part I keep coming back to is whether a restriction survives every available route to an action. If an API rejects a change but a browser session or support tool can complete it under broader credentials, the signatures can all be correct while the delegation is still too broad. That’s an integration problem the protocol helps describe, even where it doesn’t supply the enforcement.
The path to 1.0 includes further work around payments, push notifications, attachments and self-hosted agents. The examples above make me think the harder boundary will often appear halfway through a task: the GPU reservation that consumes the remaining budget, the auction bid overtaken during approval or the replacement flight that no longer meets the passenger’s constraints. An assistant that can work through those complications would be useful, but the point where it adapts its plan is also where it can drift beyond the permission it started with.