Browser Session
PAP can carry one Session across APIs, conversations, and the company's
website. When discovery advertises web.browser_session_endpoint, the personal
agent signs a short-lived browser assertion and has a browser it controls POST
that assertion to the company. The company verifies it, sets its own Session
cookie, and redirects to the requested page.
Personal-agent responsibilities​
- Use the advertised HTTPS endpoint as the exact assertion audience.
- Set
return_toonly to the company's domain or one of its subdomains. - Use the pairwise User ID and active Session ID that belong together.
- Limit
exp - iatto at most 60 seconds and generate a unique, unpredictablejtiwith at least 128 bits of entropy. - Sign with the distinct protected-header type
poppy-browser+jwt. - Submit
assertionin a top-level form POST from a browser the agent controls. It must never appear in a query string or the user's ordinary browser.
BrowserSessionClient resolves clock, randomness, signing, and the controlled
form POST through Reactor. It returns only the endpoint, safe return location,
and expiry; the compact assertion never leaves the host boundary.
In TypeScript, the agent opens an operation's page in its own browser (the
host's BrowserFormPosts contribution, with its own cookie jar), after
operationPage has checked the page is on the company's domain:
import { BrowserSessionClient, operationPage } from "@datalayer/personal-agent-protocol";
const domain = company.document.organization.domain;
const page = operationPage(operation, domain);
const joined = await new BrowserSessionClient({ reactor }).join({
clientId,
pairwiseUserId,
browserSessionEndpoint: company.document.web!.browser_session_endpoint!,
sessionId: session.sessionId,
returnTo: page,
companyDomain: domain,
});
console.log(joined.returnTo);
Company responsibilities​
verify_browser_assertion in Python and verifyBrowserAssertion from the
TypeScript ./server entry check:
- ES256 or RS256 signature against the agent's verified JWKS;
- exact
poppy-browser+jwttype and endpoint audience; - a lifetime no longer than 60 seconds and an assertion active now;
- the expected client, pairwise User ID, active Session, and company-domain return target; and
- atomic one-use replay acceptance of
(client_id, jti)until expiry.
After verification, the company creates its own cookie. That cookie must be
Secure and HttpOnly, use SameSite=Lax or None, follow later sign-in,
sign-out, and scope changes, and never outlive the PAP Session. Cookie creation
is intentionally a company web-framework responsibility rather than an SDK
side effect.
Both reference companies (Python and TypeScript, with web set in their
configuration) do this at /poppy/browser-session: the __Host-poppy_session cookie
is Secure, HttpOnly and SameSite=Lax, has no lifetime of its own, and
stands for the Session only while it is live, so signing out reaches the
website on its next request. Each operation's page (/operations/{id}) shows
it in that Session and confirms or cancels it through a same-site form with a
CSRF token bound to the cookie; the conformance cases
lifecycle.browser-joins-session, lifecycle.browser-assertion-refused, and
operations.web-confirm-at-most-once pass in all four language pairings.
What the examples prove​
Both examples create a real ES256 assertion, pass it through a Reactor form POST contribution, verify it, and submit it a second time to demonstrate replay rejection. Their tool results contain boolean policy evidence only.
Run the Pydantic AI example or the Agent Runtimes example. See upstream Web browsing.