Agentic AI

Personal Agent Protocol (PAP / Poppy)

The Personal Agent Protocol (PAP, codenamed Poppy) is an open draft standard from Sierra and Meta that lets a person's own AI agent discover a company, prove who it is, get the user signed in, and then complete tasks through APIs, web pages or the company's own agent. It standardizes the missing identity-and-access layer between consumer agents and businesses.

The Personal Agent Protocol (PAP), codenamed Poppy, is an open standard that lets a person’s own AI agent deal directly with a business: find out whether the company supports agents, prove who the agent is, get the user signed in with limited permissions, and then finish a task through an API, a web page or the company’s own agent. Sierra announced it on October 6, 2026, built with Meta and a group of partners that Sierra and Meta named as Genesys, Instinct, Rocket, Shopify, Stripe and Walmart (plus NiCE and Decagon on Meta’s side). It is a draft: version 0.1 is frozen, 0.2 is the current revision, and the authors say any part can change incompatibly before 1.0. It matters because it targets a problem the other agent protocols skip: not how an agent calls a tool, but how a company knows whose agent is knocking and what it is allowed to do.

The Problem: Agents Without a Front Door

Today a consumer agent that wants to check an order or rebook a flight usually does what a person would do: it drives the website. That is slow, breaks when the page changes, and gets blocked by bot defenses that cannot tell a helpful agent from a scraper. When it fails, the agent falls back to a support line or a web chat widget built for humans. Sierra’s pitch is that a direct, authenticated connection could finish the same tasks faster and more safely.

Three needs sit underneath that, and none of the existing protocols covers all of them:

  • Discovery. How does an agent learn that a company supports agents at all, and where?
  • Identity and consent. How does the company recognize which agent this is, and how does the user grant it narrow access without handing over a password?
  • Visibility and control. How can a company set limits on what agents do and see what they did? The stated principle is that consumers decide who acts for them and companies decide what those agents may do.

MCP standardizes tool access, A2A standardizes agent-to-agent messaging, and agentic commerce protocols standardize shopping and payment. PAP sits underneath them as the session and sign-in layer that says who is calling.

The Flow at a Glance

%%{init: {'theme': 'base'}}%%
graph TD
    classDef default fill:#ffffff,stroke:#4338CA,stroke-width:2px,color:#0F172A,rx:8px,ry:8px;
    classDef data    fill:#EEF0F7,stroke:#0D9488,stroke-width:2px,color:#0F172A,rx:8px,ry:8px;
    classDef process fill:#F7F8FC,stroke:#6366F1,stroke-width:2px,color:#0F172A,rx:8px,ry:8px;
    classDef output  fill:#4338CA,stroke:#4338CA,stroke-width:2px,color:#ffffff,rx:8px,ry:8px;

    A[Personal agent]:::data -->|"1. fetch /.well-known/poppy.json"| D[Company discovery document]:::process
    D -->|"2. verify issuer metadata lists the domain"| I[Company OAuth issuer]:::process
    A -->|"3. signed JWT assertion plus DPoP proof"| I
    I -->|"4. session token, starts signed out"| S[Signed-out session]:::data
    S -->|"5. direct, device or mediated sign-in"| U[User approves scopes]:::data
    U --> T[Signed-in session]:::data
    T --> P1[APIs: OpenAPI or MCP]:::output
    T --> P2[Conversations with company agent]:::output
    T --> P3[Browser session via cookie]:::output

Discovery: /.well-known/poppy.json

A participating company serves a JSON document at /.well-known/poppy.json. It carries the protocol version, the organization’s name and domain, the OAuth issuer and the sign-in types it supports, the entry points for conversations, a browser-session endpoint, a feedback endpoint, a list of APIs (OpenAPI or MCP) and any extensions.

The most useful design detail is the two-way check. The document must name the same domain that served it, and the issuer’s standard OAuth metadata (RFC 8414) must in turn list that domain in a poppy_domains field. That stops an attacker from copying a legitimate company’s discovery document onto their own site, and stops a malicious issuer from claiming a company it does not control. One issuer represents exactly one company, though a company can span several domains.

# Scenario: before trusting anything a site says about itself, the agent
# verifies the discovery document against the issuer. A copied document fails.
def trusted_discovery(host: str) -> dict | None:
    doc = httpx.get(f"https://{host}/.well-known/poppy.json", follow_redirects=False).json()
    if doc["organization"]["domain"] != host:
        return None                                         # document claims another domain
    meta = httpx.get(doc["auth"]["issuer"] + "/.well-known/oauth-authorization-server").json()
    if meta["issuer"] != doc["auth"]["issuer"]:
        return None                                         # issuer mismatch
    if host not in meta.get("poppy_domains", []):
        return None                                         # issuer does not vouch for this domain
    return doc

Agent Identity: A URL, a Key and a Signed Assertion

A personal agent identifies itself with a client_id that is an HTTPS URL. Fetching that URL returns the agent’s metadata: where its public keys live (jwks_uri), its redirect URIs, and the authentication method, which is private_key_jwt. Registration with each company is optional; companies may allowlist, blocklist or revoke an agent by client_id and rate-limit session creation.

To start a session the agent sends a JWT bearer grant (RFC 7523). The assertion’s claims are tightly constrained:

ClaimRuleWhy it exists
issThe agent’s client_idSays which agent is calling
subA stable, opaque per-company user ID, not derived from personal dataLets a company recognize the same user without learning who they are across companies
audThe company’s issuer string, never an endpoint URLStops an assertion made for one company being replayed at another
iat / expexp at most five minutes after iatKeeps stolen assertions short-lived
jtiAt least 128 random bits, rejected if reusedStops replay of a captured assertion

Each user also gets a separate DPoP key pair. DPoP (RFC 9449) binds a token to a key held only by the agent: every request carries a proof JWT signed with that key, covering the HTTP method, URL and a hash of the token. A stolen session token is useless without the private key. One pragmatic exception: an MCP server that cannot do DPoP receives a plain Bearer token issued for that server’s resource URL only, and companies must reject it everywhere else.

Interactive: Would This Assertion Be Accepted?

The checks above are cheap to state and easy to get wrong. The widget below is a simplified model of a company’s validation, not the spec’s exact error mapping. Change the inputs and watch which rule trips.

Interactive: a personal agent sends a session-start assertion. Adjust what it sends and see whether the company's issuer accepts it.

    Drag the lifetime past 300 seconds, or switch aud to a token endpoint URL, and the session disappears. Every rule is a small piece of anti-replay or anti-impersonation hygiene that costs the legitimate agent almost nothing.

    Sessions, Tokens and Signing In

    PAP separates two tokens that are easy to blur together. The session token is short-lived, opaque and key-bound; it carries the agent through one working session. The account token is a long-lived OAuth refresh token that proves the user once signed in; it is accepted only at the token and revocation endpoints, and only from its own client_id. Ending a session does not touch the account token, and signing out revokes it.

    Every session starts signed out. The spec does not use the phrase “guest access”, but a signed-out session is the closest thing: the company can recognize the same opaque user across sessions and may allow limited, read-only tasks like checking stock, while limiting everything else. Signing in then upgrades the session, and a session can only ever be signed in to one account (a different account returns account_mismatch).

    There are three ways to sign in, chosen to keep the user’s credentials out of the agent’s hands:

    Sign-in typeHow it worksWhere the user’s password goes
    DirectOAuth authorization code flow with PKCE; the user signs in on the company’s own page and approves scopesOnly to the company
    DeviceOAuth device grant; the agent shows a link and code, the user approves on another deviceOnly to the company
    MediatedThe agent passes credentials to a company endpoint and relays one-time codesThrough the agent, so credentials must be discarded or kept only in a store the user chose

    Scopes are company-defined (an example is orders:read versus orders:write), plus two broad ones, poppy:read and poppy:write. The company must not grant more than was requested and allowed for the sign-in type used, and may limit mediated sign-in to read access.

    Getting Work Done: Three Doors

    Once connected, an agent can reach the company three ways, all under the same session:

    • APIs. The discovery document lists openapi (3.0 or 3.1) or mcp (Streamable HTTP) entries. Errors arrive in WWW-Authenticate with codes like sign_in_required or insufficient_scope, so the agent knows whether to prompt the user or ask for more scope.
    • Web browsing. If the company offers a browser-session endpoint, the agent signs a short-lived browser assertion (60 seconds at most) and posts it from its own browser; the company sets its normal cookie and redirects. The agent keeps using the real website, but as a recognized, signed-in session.
    • Conversations. A company agent can talk to the personal agent through a small message API: start a conversation, send messages, read events, request a human with a handoff, and close. Every message declares the sender as agent or human, so each side knows whether a person or an AI is speaking.
    # Scenario: the personal agent could not finish a refund through the API, so it
    # opens a conversation with the company's own agent and escalates to a person.
    # Endpoint shapes follow the 0.2 draft; verify against the live spec.
    conv = client.post(conv_endpoint, json={"context": {"locale": "en-US", "user_available": True}}).json()
    
    client.post(f"{conv_endpoint}/{conv['id']}/messages", json={
        "id": new_message_id(),                    # chosen by the agent; makes retries idempotent
        "sender": "agent",
        "text": "Order 8841 arrived damaged. The user wants a refund to the original card.",
        "data": {"order_id": "8841", "reason": "damaged"},
    })
    
    events = client.get(f"{conv_endpoint}/{conv['id']}/events", params={"wait": 20}).json()
    for e in events:
        if e["type"] == "authorization":           # sign_in_required or insufficient_scope
            ask_user_to_approve_more_access(e)
        elif e["type"] == "user_requested":        # company needs the person directly
            hand_to_user(e)
    
    # if the company agent is stuck: request a human responder
    client.post(f"{conv_endpoint}/{conv['id']}/handoff")

    Security: Treat the Company as Untrusted Input

    The spec’s security section is the most reusable idea in it for anyone building agents. Text coming from a company, whether a message, an API or MCP result, or a web page, is data, never instruction. Only the user can approve an action, and nothing a company says counts as consent. That is the same discipline needed to resist prompt injection, written down as a protocol rule. A few other rules worth copying:

    • Keep secrets out of model context. Account tokens, session tokens, private keys, device codes and one-time codes should never be visible to the language model.
    • Bind assertions to one audience. Because aud is the issuer and browser assertions use their own typ, an assertion captured at one endpoint is useless at another.
    • Share the minimum. The agent gives each company only what the task needs and never leaks one company’s information to another.
    • Don’t connect to internal addresses. Clients must not follow protocol URLs to loopback, private or link-local addresses unless configured to.

    The feedback.endpoint is a nice small touch: an agent can report a task the protocol could not complete (a kind of missing, plus a short task and description) with no personal data, and the company must treat that text as data too.

    What’s New (2025-2026)

    DateEvent
    Oct 6, 2026Sierra announces the Personal Agent Protocol, built with Meta, with Shopify, Stripe, Walmart, Genesys and others as partners
    Oct 2026Draft 0.1 published (now frozen); draft 0.2 becomes the current revision
    • Deliberately left out of the draft. The authors list payments, push notifications (how a company reaches an agent when no request is open), version negotiation, richer attachments, and self-hosted personal agents that need to sign in without a server learning which companies each user works with.
    • Open and reviewable. The spec and docs are Apache 2.0 on GitHub, with a “Path to 1.0” page and a company implementation kit with JSON Schemas and a validator. The names “Personal Agent Protocol” and “Poppy” are not covered by the license.
    • Pairs with the rest of the stack. The payments gap points directly at agentic commerce protocols, and the MCP and OpenAPI entry points mean existing company tools can be exposed behind a PAP session without rewrites.

    Practical Guidance

    SituationRecommendation
    You run customer-facing APIs or supportPublish a discovery document, point it at your existing OpenAPI or MCP, and prefer direct sign-in so passwords never touch the agent
    You build a consumer agentFetch and verify discovery before anything else, keep one opaque ID and one DPoP key per user per company, and keep tokens and keys out of model context
    You need the agent to payNot covered yet; look at agentic commerce protocols in the meantime
    You want to ship to productionWait: the authors say 0.2 can still change incompatibly, so build against it for feedback, not for a launch
    A task needs a decisionKeep a human in the loop; company text is never consent

    PAP does not make an agent smarter. It makes an agent recognizable: a company can tell whose agent this is, what the user allowed it to do, and revoke that permission in one place. That is the front door consumer agents have been missing.

    How to Use: A personal agent discovers a company and starts a Poppy session

    python
    import time, uuid, httpx, jwt  # PyJWT
    
    # Scenario: a user's personal agent wants to check an order at shop.example.com.
    # Step 1 is discovery; step 2 is proving who the agent is with a signed
    # assertion. Field names follow the Poppy 0.2 draft; check the current spec
    # before relying on the exact shape, since it may change before 1.0.
    COMPANY = "shop.example.com"
    AGENT_ID = "https://agent.example.org/client.json"   # client_id is an HTTPS URL
    
    doc = httpx.get(f"https://{COMPANY}/.well-known/poppy.json").json()
    issuer = doc["auth"]["issuer"]
    
    # Two-way check: the document must name the host that served it, and the
    # issuer's own metadata must list that domain back.
    meta = httpx.get(f"{issuer}/.well-known/oauth-authorization-server").json()
    assert doc["organization"]["domain"] == COMPANY
    assert COMPANY in meta["poppy_domains"] and meta["issuer"] == issuer
    
    now = int(time.time())
    assertion = jwt.encode(
        {"iss": AGENT_ID, "sub": opaque_user_id_for(COMPANY),   # stable, opaque, not derived from PII
         "aud": issuer,                                          # the issuer string, never an endpoint URL
         "iat": now, "exp": now + 120,                           # at most 5 minutes
         "jti": uuid.uuid4().hex + uuid.uuid4().hex},            # 128+ random bits, single use
        agent_private_key, algorithm="ES256")
    
    session = httpx.post(meta["token_endpoint"], data={
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "assertion": assertion,
    }, headers={"DPoP": dpop_proof("POST", meta["token_endpoint"])}).json()
    print(session["signed_in"], session["scope"])   # starts signed out; sign-in comes next
    

    Ready to build?

    Leverage AI technologies to build your product stack

    Superteams can help you build, deploy and launch AI application stacks using open source technologies — from architecture through to production.

    Talk to Superteams