Repository navigation
feat: complete PAP draft 0.1 SDK coverage - #3
Merged
Merged
Conversation
…on (§4.8, §4.9) AccountTokenClient refreshes a Session with the stored Account Token (fresh client assertion and DPoP, one nonce retry), replaces a rotated token before returning, asks only for narrower scopes or an RFC 8707 resource, reports a wider request as re_consent_required, deletes the token on invalid_grant (the Session continues signed out), and revokes at the revocation_endpoint (RFC 7009), keeping the token for a retry until the company answers. Uses are serialized per token through the new Reactor point pap.accountTokenLock.
MediatedSignInClient collects exactly the company's declared fields through the new Reactor point pap.protectedInput (the host's trusted surface, never a model), sends them once to auth.mediated.endpoint on the company's own domain with the Session's DPoP token and no redirect followed, continues a code_required answer with the one-time code alone from the Session that started it, reports failed and expired as statuses, and keeps the Account Token behind an opaque reference.
…nd closing (§7.6–7.12) ConversationClient.stream reads SSE through a new streamed DPoP request (one nonce retry, no redirect followed), yields text-delta pieces as non-actionable TextDelta, de-duplicates events, stops at closed, and reopens from Last-Event-ID with a StreamReconnected that names the partial messages to drop; a company that does not stream answers sse_unsupported. handoff, close and start_direct join the client; direct_conversation_open carries the open Direct Conversation's ID; a token change mid-conversation keeps its ID.
operations_endpoint uses the extension only when the company's poppy.json and the agent's client metadata both list operations version 1. OperationsClient reads, confirms and cancels: a user approval names one operation, revision and digest of its summary and terms, refused locally for any other revision; terms_changed returns the new revision and the term paths that changed; a standing permission (new Reactor point pap.standingPermissionStore) must cover the company, account, action and every limit, reserving a use that is spent on confirmation and released on refusal, and never for a revision requiring the user's approval; a lost answer is read back by the same ID, never assumed failed; extension_required names the extension.
…emonstration personal_agent_protocol.company is a framework-neutral company served over plain ASGI: discovery and OAuth metadata; Sessions from verified client and Session assertions with DPoP-bound tokens; Device Sign-In with pacing and slow_down; Mediated Sign-In with a one-time code and attempt limit; Account Tokens rotated on every refresh, narrowed only, revoked with their Sessions signed out; conversations with idempotent messages, cursor pages, SSE with text-delta, handoff, Direct Conversations and authorization events; and Operations v1 at most once. Every replaceable part is a Reactor point (pap.company.store, accounts, agent, operationExecutor, clientPolicy, clientResolver, replayCache). personal_agent_protocol.demo gives examples a self-contained world: a real ES256 agent identity served at its own origin, an in-process network routing domains to ASGI apps (no sockets, the SSRF policy unchanged), a shared clock, and Manzanita Outfitters, the fictional company of the upstream example. The end-to-end tests take a user from a question, through an authorization event and Device Sign-In, to a confirmed exchange performed once, then narrow and revoke the Account Token; and sign in through Mediated Sign-In with a code.
Ledgerly, an accounting firm, is a PAP company to Ada's personal agent and a PAP personal agent at Northwind Bank for the same Ada, with its own client_id and JWKS on its own origin, connected through Device Sign-In she approves on the bank's page. Draft 0.1 defines no company-to-company protocol, so this is composition, and every approval is an Operation: Northwind's payment is relayed to Ada quoting its summary as written, confirmed at the bank with approved_by user only for the exact revision she approved; a standing permission is itself an operation she approves, after which payments within its bounds are confirmed with approved_by standing_permission and those beyond come back to her.
AccountTokenClient mirrors the Python client over fetch: fresh client assertion and DPoP with one nonce retry, rotation replacing the stored token before returning, narrower scope/resource only, re_consent_required for a wider request, invalid_grant deleting the token, and RFC 7009 revocation that keeps the token for a retry when refused. Uses are serialized through the new Reactor point pap.accountTokenLock (InProcessAccountTokenLock by default); pap.protectedInput is declared for Mediated Sign-In.
MediatedSignInClient mirrors the Python client: exactly the declared fields from the Reactor point pap.protectedInput, sent once to the mediated endpoint on the company's own domain with no redirect followed, a code_required continuation with the code alone from the Session that started it, failed and expired as statuses, rate_limited with Retry-After, and the Account Token kept behind an opaque reference.
…nd closing ConversationClient.stream reads SSE from the response body, yields text-delta as a non-actionable TextDelta, de-duplicates events, stops at closed, and reopens from Last-Event-ID with a StreamReconnected naming the partial messages to drop; a company that does not stream answers sse_unsupported. handoff, close and startDirect join the client, and direct_conversation_open carries the open Direct Conversation's ID, as in Python.
OperationsClient mirrors Python: operationsEndpoint negotiates version 1 from discovery and client metadata; approve() records one revision and a SHA-256 digest of its canonical summary and terms; terms_changed reports the changed term paths; standing permissions are checked term by term through the new Reactor point pap.standingPermissionStore (decimal maxima, allowed values, expiry, uses), reserved and then spent or released; a lost confirmation is read back by the same ID; cancel and extension_required are handled.
…In, conversation streaming and control, Operations, and the two demo journeys
…ons guides; document the reference company; update status and coverage
…ation streaming and control, Operations, and the two journeys
…med terms PersonalAgentHost and AgentIdentity are identity-compared (eq=False): pluggy keeps plugins in a set, and a dataclass with eq=True is unhashable, so reactor.stop() raised. The exchange executor states the price difference of the revision that was confirmed instead of a fixed USD 70.00.
…pose operations The reference company now serves Direct Sign-In (§4.5): /oauth/authorize verifies the agent and its exact redirect URI (never redirecting to an unverified one), accepts S256 PKCE only, and shows a consent page naming the agent, its domain and each scope in plain words, never framed nor cached; approve_authorization and deny_authorization are the user's decision on that page, answering the redirect with code, state and iss; the authorization_code grant takes a code once, within a minute, with its verifier, signing the existing Session in with what was granted. A new Reactor point pap.company.api serves /api/openapi.json and /api/*, advertised in discovery; a call that proposes an operation answers 202 with the same operation object as a conversation (Operations §4.1). ApiClient calls it with the Session's DPoP token and returns the body or the proposed operation; sign_in_required and insufficient_scope carry the scope. Manzanita gains its API, and the demo kit a scripted browser.
served_over_tls(network) starts one uvicorn server on 127.0.0.1 with a certificate for every domain on the network, issued by a throwaway CA, and routes requests by Host. While it runs every client of the network dials it over TCP and TLS, keeping each domain's name for SNI and certificate verification, so personal agents and companies talk as they would across the internet with no change to their code. The user-with-a-company journey (discovery, Direct Sign-In, an API read, an operation confirmed once) passes over it: the Python agent ↔ Python company real-HTTP run. uvicorn is the new server extra (and in test).
…e parity specs Python serializes an operation's url, terms, expires_at and result.data, and a message's or event's optional members, as null; the TypeScript parsers refused them, so a TypeScript agent could not read a Python company's operations. Both SDKs now accept either spelling. fixtures.test.mjs runs the shared protocol/fixtures and test_models.py's error cases, case for case.
…rver) The port of personal_agent_protocol.company: CompanyService.handle answers discovery and OAuth metadata, the JWT-bearer grant with client and Session assertions verified against the agent's JWKS, DPoP-bound Sessions, Direct Sign-In (consent page, approve/deny, PKCE), Device Sign-In with pacing, Mediated Sign-In with a one-time code, Account Tokens (rotation, narrowing, revocation, disconnect), conversations (JSON, SSE, handoff, Direct, authorization events, closing, retention), the OpenAPI API point with 202 operations, and Operations performed at most once (failed, unknown outcome, settle, revise, cancel, expiry). Every replaceable part is a pap.company.* Reactor contribution with the Python ids and in-memory defaults; companyRequestListener serves it over node:http or node:https. Exported only from ./server.
…ourney src/demo/manzanita.ts ports the fictional company (accounts, agent, API, exchange, gift-wrap, return and repair executors); it is in neither published entry. company.test.mjs drives the TypeScript company with the TypeScript clients in one process, the same cases as the Python company suites. protocol/scenarios/user-with-a-company.json defines the journey every language pairing runs.
TypeScript agent <-> TypeScript company on node:https with a throwaway CA (pairing.test.mjs); TypeScript agent <-> Python company (served_over_tls, the agent in node) and Python agent <-> TypeScript company (node:https, the agent in Python) in test_pairings.py, which also runs Python <-> Python from the same scenario. The user's actions at the company cross processes as JSON lines.
- account_mismatch only for a Session signed in to another account, and invalid_session for another agent's or user's Session (§4.4, §4.8); signing a Session in to a second account is account_mismatch. - Token assertions: typ must be JWT (a browser assertion is refused at the token endpoint) and aud one string (§4.2, §5). - A failed proof at an API or conversation is 401 with WWW-Authenticate; token errors carry the DPoP challenge with scope (§4.3, §6). - A conversation that used the account belongs to it: a signed-out token gets sign_in_required (§7.2, §7.11). - Operations: the same action through another channel is the same operation (§4); a url on the website; executors may fail partway (OperationFailed, the result says what changed) or leave the outcome unknown (OperationOutcomeUnknown, in_progress with Retry-After, never failed); settle() ends one later; revise() gives a new expires_at. - Retention: forget_events() expires old cursors (cursor_expired, 410). - disconnect(): account settings revoke an agent's tokens and sign out its Sessions. - Manzanita proposes gift wrap (a standing permission can cover it), a return that fails partway, and a repair whose outcome stays unknown.
Accepted connections inherit TCP_NODELAY from the listener, so each small TLS record is not held for a delayed ACK: a request over loopback drops from about 42 ms to under 1 ms.
…e late confirms - extensions: plain or domain-scoped names (third parties name theirs under a domain, §3.3), negotiation keeping only what both sides list at one major version, and extension_required read from a 403. - apis: known_apis() skips API types draft 0.1 does not define (§6); conversation_endpoint() picks the poppy protocol (§7.1); token errors are also read from WWW-Authenticate. - models: KNOWN_EVENT_TYPES and ConversationEvent.known, to skip unknown event types (§7.5). - operations: a confirm answered with a cancelled or expired operation is not_confirmed, the record returned as it is (Operations §5.2).
…on company over TLS
protocol/fixtures/conformance/{protocol,security,lifecycle,conversations,
operations}.json describe 112 cases (30 decided by the SDK alone, 82 run against a company), each naming its ledger requirements:
protocol major/minor versions, unknown fields, API, protocol, and event types,
extension names and negotiation, extension_required, multi-domain issuers;
duplicate JSON members, SSRF and DNS rebinding, redirect limits and downgrade,
oversized metadata, JOSE algorithm confusion, audiences, clock skew, replay,
and every DPoP mismatch; parallel Sessions, renewal, account mismatch,
rotation, narrowing, sign-out and disconnect; message IDs, cursors, has_more,
SSE reconnect, text-delta, attribution, handoff, Direct Conversations,
closure, indistinguishable not-found; every operation state and error,
revision races, standing-permission bounds and concurrent use, cross-channel
identity, confirm timeout recovery, cancellation, and partial results.
Cases the SDK decides alone run in test_fixtures.py. The rest run in
personal_agent_protocol.conformance: a personal-agent suite against any
company base URL, with a demo-kit identity, a UserDriver for what only the
user (or a demo company's operator) can do, and a Scenario for the company's
paths and prompts. A case missing a capability is skipped, never passed.
Reports as JSON (schema_version 1) and JUnit.
test_conformance.py runs all 82 runner cases against Manzanita over real TCP
and TLS (demo.conformance.ManzanitaConformance): positive, negative, and
recovery, all passing in about a second.
The ledger and fixtures ship in the wheel (force-include); requirements.py
reads them. Adds pyyaml.
…ed, never retried early - Message.context: locale is a language tag, time_zone an IANA name, user_available a boolean; other facts pass through (§7.4). - API, conversation, and operation refusals with 429 carry retry_after in details, so the caller waits at least that long (§4.3); the SDK never retries a rate-limited request itself. - Fixture protocol.message-context-fields; rate_limited also covers ApiClient.
protocol/requirements.yaml has one atomic entry for every MUST, MUST NOT, SHOULD, and SHOULD NOT of the specification (101) and the Operations extension (18): section, level, role, the upstream text, the implementation (module:symbol), the tests (file::name) and conformance cases that show it, or a partial/open/not-applicable status with its reason. The Guides' recommendations are kept apart under guide (41, source: guide). test_requirements.py checks every test, implementation symbol, and conformance case exists, that fixtures and entries name each other, and that each entry's text carries its own level.
…er sessions and operation pages MCP (§4.3, §6, Operations §4.2), when a pap.company.mcp contribution is present: - the token endpoint issues a Bearer Session Token for an existing Session when the request has no DPoP proof and resource is the MCP url (renewal or Account Token grant); any other proof-less request is invalid_dpop_proof. A new token replaces only the Session's tokens of its own type, so a Session holds DPoP and Bearer tokens at once. - /mcp (Streamable HTTP, JSON answers) accepts only those Bearer tokens: a DPoP token, another resource's, or an expired one is 401 with a Bearer challenge naming the protected resource metadata (RFC 9728), whose authorization_servers is the company's issuer. - tools are called with the token's account and scopes; a consequential tool proposes the operation in structuredContent.operation, the same record as through the API or a conversation. - every DPoP endpoint refuses a Bearer token (invalid_token). Web (§5, Operations §4.4), with web=True: the browser-session endpoint verifies a browser assertion (type, audience, lifetime, replay, owner, live Session, return_to on the company's domain) and sets the company's own Secure, HttpOnly, SameSite=Lax cookie with no lifetime of its own; each request reads the Session as it is now, so sign-out shows at once and the cookie dies with the Session. An operation's url shows it there and can confirm or cancel it through the same at-most-once path as the endpoint. Manzanita lists an MCP server (list_orders, exchange_jacket) and a website.
…peration from every channel - SessionClient.bearer_for_mcp (signed out) and AccountTokenClient.bearer_for_mcp (signed in, rotation stored as with refresh) send the Session's token request without DPoP and with resource set to the MCP server's url, and accept only a Bearer token for that Session (§4.3). - mcp.McpBearerAuth (httpx.Auth) adds the token only to requests for that exact URL and refuses every other request before it is sent; it works with any httpx-based MCP client. mcp.McpClient is a minimal Streamable HTTP client (initialize, tools/list, tools/call; JSON or SSE answers), with no MCP SDK dependency, so no mcp extra is needed. - operations.operation_from parses the one Operation from an API 202 body, an MCP tool result's structuredContent, a message, or the object itself; operation_page checks an operation's url is the company's own HTTPS page before the agent's own browser opens it (Operations §4.2, §4.4). - demo.kit.ControlledBrowser: the agent's own browser (pap.browserFormPost) with its own cookie jar. test_mcp_and_web.py: the advertised MCP server and its metadata; a signed-in Bearer reading and proposing the same operation as the API; the Bearer refused at every other endpoint (locally and by the company) and a DPoP token refused at MCP; Bearer only for the MCP url; sign-out reaching MCP at once; extension_required through MCP; the operation confirmed on the website being the same operation, performed once; the website following sign-out and refusing bad browser assertions.
…er and browser sessions Eight more runner cases (90 in all, all passing over TLS against Manzanita): the MCP server's protected resource metadata and Bearer challenge; a Bearer for its url, signed in or out, with scopes enforced; the Bearer refused at every API, conversation, and operations endpoint and no Bearer for another resource; sign-out reaching MCP; the same operation through MCP and the API; the agent's own browser joining a Session (cookie flags, sign-out followed); bad browser assertions setting no cookie; an operation confirmed on its page performed once. Scenario gains mcp_read_tool, mcp_propose_tool, and web_confirm_form; the context takes a browser_http client for the agent's own browser. Ledger: PAP-4.3-09, PAP-4.3-10, PAP-5-10 to PAP-5-12, PAP-6-04 to PAP-6-06, and GUIDE-pa-tokens-01 are implemented with their tests and cases.
…renewal is signed out - Account Tokens rotated from one sign-in share a family: revoking the current one (or disconnecting) signs out every Session signed in with any of them, including tokens issued before a rotation (§4.9). Found by the CLI, where an MCP Bearer refresh rotated the token the DPoP Session was signed in with. - Renewing with the Session assertion returns a signed-out token; a signed-in Session is renewed with its Account Token (§4.2, §4.8). - Conformance case lifecycle.sign-out-every-session (91 runner cases, all passing over TLS); ConformanceReport.from_json reads a saved report back.
…ts, conformance Thin adapters over the public SDK, added by PapCliPlugin.provide_cli: pap sessions start|renew|show|end, pap auth direct|device|mediated|disconnect, pap api openapi|mcp, pap conversations start|send|events|follow|handoff|direct| close, pap operations show|approve|confirm|cancel, pap requirements list|show, pap conformance run|report, pap agent key import|show, and pap exit-codes. - The CLI's personal agent is a Reactor host (cli_host): token store, signing and DPoP keys, pairwise IDs, protected input, and the user's browser are contributions, defaulting to owner-only files under XDG_STATE_HOME/pap and replaceable by plugins under personal_agent_protocol.plugins. - Protected input (signing key, Direct Sign-In callback, credentials, codes) comes from a hidden prompt, --stdin, or --input-fd, never an option; --no-input stops with exit code 4. Only opaque references are printed. - Approval is typed at the CLI's prompt for one exact operation ID and revision; confirm sends only that approval (exit 6 otherwise). - --json prints schema_version 1; exit codes 0-6 and 130 are documented in the module, the guide, and pap exit-codes. test_cli_protocol.py: help for every group and command, shell completion, no secret-bearing option anywhere, a user with Manzanita end to end (key import, Session, Mediated Sign-In from stdin, OpenAPI and MCP, approve and confirm, conversations, sign-out, renewal, end) with no secret in any output, Device Sign-In and its timeout, Direct Sign-In from a file descriptor, every exit code, a broken extension skipped, and the requirements and conformance commands.
…Python API reference lib/demo/manzanita.js was tsc's unbundled output, whose @datalayer/reactor imports Node cannot resolve: the company and pairing specs failed in CI. It is now an esbuild entry beside index and server.
- sessions.Who: a TypedDict for the tenant/user/issuer keywords every client call takes, used by the conformance context, the CLI, and Ledgerly. - OperationsClient.confirm passes its arguments explicitly; event and stream readers are AsyncGenerators (closable with contextlib.aclosing). - _DialLocal matches httpcore's AsyncNetworkBackend signatures; no shadowed or redundant names, casts, or ignores. - The DPoP token type is a named constant, so no S105 noqa is needed under either ruff version.
…ed versions - test_cli_protocol: detect command groups without click.Group (Typer 0.27 vendors click), run with plain output (NO_COLOR, TERM=dumb, no FORCE_COLOR), read the JSON document after the prompt input CliRunner echoes, and compare exit codes as int values (IntEnum str differs on 3.10). - test_pairings: run_journey split into one method per step (C901). - test_pairings, test_real_sockets: the uvicorn skip comes after the imports, so no E402 noqa is needed. - UP038 in test_company_end_to_end and test_requirements; mdformat on the Pydantic AI examples README.
There was a problem hiding this comment.
🟡 Changes recommended
Assertion security, buffering limits, stream recovery, Bearer validation, and capability documentation contain unresolved issues.
19 open findings
Enforce response size limits during streaming · New Limit buffered request body size · New Reject non-positive assertion lifetimes · New Keep replay entries valid through the skew window · New Reject signed-in responses from the MCP token endpoint · New Handle mid-stream SSE read failures · New Reject assertions with non-positive lifetimes · New Retain replay markers through the accepted skew window · New Align the release target and package versions · New Resolve JWKS from one metadata snapshot · New Map NumericDate conversion failures to SessionError · New Clean up unused account token locks · New Resolve DPoP providers only for DPoP requests · New Add tzdata as a runtime dependency · New Fetch metadata once when resolving JWKS · New Correct TypeScript capability claims in release notes · New Document the exported TypeScript company implementation · New Update the implementation coverage matrix · New Document actual TypeScript company coverage · New
What changed in this PR
This PR substantially expands PAP draft 0.1 and Operations v1 coverage across both SDKs, reference-company implementations, conformance tooling, examples, and documentation.
Changes:
- Adds sign-in, Account Token, conversation streaming/control, Operations, API, and company-server capabilities.
- Introduces shared conformance scenarios, real-TLS pairing tests, demo worlds, and requirement tracking.
- Updates public exports, packaging, CLI commands, documentation, and release metadata.
| File | Description |
|---|---|
src/server.ts |
Exports TypeScript company APIs. |
src/reactor.ts |
Adds protected input and token locks. |
src/models.ts |
Aligns null handling across SDKs. |
src/index.ts |
Exports new client APIs. |
src/company/verify.ts |
Verifies TypeScript assertions. |
src/company/node.ts |
Adds Node HTTP adapter. |
src/company/index.ts |
Builds TypeScript companies. |
src/__tests__/support/tls-company.mjs |
Serves test companies over TLS. |
src/__tests__/support/serve-company.mjs |
Supports cross-language company tests. |
src/__tests__/support/run-agent.mjs |
Runs TypeScript pairing journeys. |
src/__tests__/support/lines.mjs |
Adds JSON-lines IPC. |
src/__tests__/support/journey.mjs |
Defines shared journey execution. |
src/__tests__/pairing.test.mjs |
Tests TypeScript TLS pairing. |
src/__tests__/mediated.test.mjs |
Tests mediated sign-in. |
src/__tests__/fixtures.test.mjs |
Tests shared protocol fixtures. |
src/__tests__/device.test.mjs |
Tests Device Sign-In. |
src/__tests__/conversation-control.test.mjs |
Tests streaming and controls. |
src/__tests__/accounts.test.mjs |
Tests Account Token lifecycle. |
README.md |
Repositions the demo image. |
pyproject.toml |
Adds dependencies and protocol assets. |
protocol/scenarios/user-with-a-company.json |
Defines the shared journey. |
protocol/fixtures/conformance/operations.json |
Adds Operations cases. |
protocol/fixtures/conformance/conversations.json |
Adds conversation cases. |
personal_agent_protocol/transport.py |
Adds streamed DPoP requests. |
personal_agent_protocol/sessions.py |
Adds MCP Bearer Sessions. |
personal_agent_protocol/requirements.py |
Loads the requirement ledger. |
personal_agent_protocol/reactor.py |
Adds new Reactor contracts. |
personal_agent_protocol/models.py |
Validates message context fields. |
personal_agent_protocol/extensions.py |
Adds extension negotiation helpers. |
personal_agent_protocol/demo/northwind.py |
Adds the Northwind demo. |
personal_agent_protocol/demo/conformance.py |
Adds the demo conformance world. |
personal_agent_protocol/demo/__init__.py |
Documents demo worlds. |
personal_agent_protocol/company/verify.py |
Verifies Python assertions. |
personal_agent_protocol/company/asgi.py |
Adds the ASGI adapter. |
personal_agent_protocol/company/__init__.py |
Builds Python companies. |
personal_agent_protocol/cli_plugin.py |
Registers protocol CLI groups. |
personal_agent_protocol/apis.py |
Adds the Python API client. |
personal_agent_protocol/__version__.py |
Bumps the Python version. |
personal_agent_protocol/__tests__/test_two_companies.py |
Tests composed companies. |
personal_agent_protocol/__tests__/test_requirements.py |
Validates ledger references. |
personal_agent_protocol/__tests__/test_real_sockets.py |
Tests real TLS sockets. |
personal_agent_protocol/__tests__/test_direct_and_api.py |
Tests Direct Sign-In and APIs. |
personal_agent_protocol/__tests__/test_conformance.py |
Tests conformance reporting. |
personal_agent_protocol/__init__.py |
Exports new Python APIs. |
package.json |
Bumps and expands TypeScript build. |
examples/pydantic-ai/user-with-a-company/README.md |
Documents the complete journey. |
examples/pydantic-ai/two-companies/README.md |
Documents company composition. |
examples/pydantic-ai/README.md |
Catalogs new examples. |
examples/pydantic-ai/operations/README.md |
Documents Operations example. |
examples/pydantic-ai/mediated-sign-in/README.md |
Documents mediated sign-in. |
examples/pydantic-ai/mediated-sign-in/app.py |
Implements mediated example. |
examples/pydantic-ai/device-sign-in/README.md |
Documents Device Sign-In. |
examples/pydantic-ai/conversation-streaming/README.md |
Documents SSE example. |
examples/pydantic-ai/conversation-streaming/app.py |
Implements SSE example. |
examples/pydantic-ai/conversation-control/README.md |
Documents conversation controls. |
examples/pydantic-ai/account-tokens/README.md |
Documents Account Tokens. |
examples/pydantic-ai/account-tokens/app.py |
Implements Account Token example. |
docs/sidebars.js |
Adds new documentation pages. |
docs/docs/sdk/typescript.mdx |
Updates TypeScript capabilities. |
docs/docs/protocol/status.mdx |
Updates protocol status. |
docs/docs/protocol/coverage.mdx |
Adds capability coverage matrix. |
docs/docs/index.mdx |
Adds SDK introduction. |
docs/docs/guides/personal-agent.mdx |
Updates lifecycle guidance. |
docs/docs/guides/operations.mdx |
Adds Operations guidance. |
docs/docs/guides/mediated-sign-in.mdx |
Adds mediated guidance. |
docs/docs/guides/extensions.mdx |
Updates extension guidance. |
docs/docs/guides/device-sign-in.mdx |
Adds Device Sign-In guidance. |
docs/docs/guides/conversations.mdx |
Adds conversation guidance. |
docs/docs/guides/company.mdx |
Documents reference companies. |
docs/docs/guides/cli.mdx |
Documents protocol CLI commands. |
docs/docs/guides/account-tokens.mdx |
Adds Account Token guidance. |
docs/docs/examples/user-with-a-company.mdx |
Documents the full journey. |
docs/docs/examples/two-companies.mdx |
Documents composed relationships. |
docs/docs/examples/operations.mdx |
Documents Operations flow. |
docs/docs/examples/mediated-sign-in.mdx |
Documents mediated flow. |
docs/docs/examples/index.mdx |
Expands the examples index. |
docs/docs/examples/device-sign-in.mdx |
Documents device flow. |
docs/docs/examples/conversation-streaming.mdx |
Documents SSE recovery. |
docs/docs/examples/conversation-events.mdx |
Links streaming support. |
docs/docs/examples/conversation-control.mdx |
Documents control flow. |
docs/docs/examples/account-tokens.mdx |
Documents token lifecycle. |
CHANGELOG.md |
Adds 0.4.0 release notes. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+145
to
+147
| def _payload(self, response: httpx.Response) -> object: | ||
| if len(response.content) > self.max_response_bytes: | ||
| raise ApiError("response_too_large", "API response exceeded limit") |
Comment on lines
+31
to
+35
| body = b"" | ||
| while True: | ||
| message = await receive() | ||
| body += message.get("body", b"") | ||
| if not message.get("more_body"): |
Comment on lines
+56
to
+57
| if expires - issued > _MAX_LIFETIME: | ||
| raise SessionError(error, "Assertion lives too long", status_code=_status(error)) |
Comment on lines
+63
to
+64
| if not await replay_cache.mark_once(f"assertion:{issuer}:{claims['jti']}", expires_at=expires): | ||
| raise SessionError(error, "Assertion has already been used", status_code=_status(error)) |
Comment on lines
+395
to
+398
| expected = "DPoP" if bearer_resource is None else "Bearer" | ||
| if token.token_type != expected or ( | ||
| bearer_resource is not None and token.session_id != session_id | ||
| ): |
Comment on lines
+46
to
+49
| async resolve(clientId: string): Promise<readonly [ClientMetadata, JsonWebKeySet]> { | ||
| const identity = this.#identity; | ||
| if (identity === undefined) throw new Error("IdentityClientResolver is not bound"); | ||
| return [await identity.fetchMetadata(clientId), await identity.fetchJwks(clientId)]; |
Comment on lines
+19
to
+23
| - **Company:** a framework-neutral reference company assembled from Reactor | ||
| points (`pap.company.*`), served over ASGI (Python) and node:http(s) | ||
| (TypeScript): discovery, Sessions, Direct, Device and Mediated Sign-In, | ||
| Account Tokens, conversations, an OpenAPI API and an MCP endpoint, the | ||
| browser session, Operations performed at most once. |
Comment on lines
+9
to
+13
| The Python SDK ships a **reference company**, | ||
| `personal_agent_protocol.company`: a framework-neutral PAP company over plain | ||
| ASGI, assembled from Reactor contributions. It is the counterpart the SDK's | ||
| own end-to-end tests and demo worlds run against, and a starting point for a | ||
| real integration. The TypeScript SDK has no company side yet. |
Comment on lines
+64
to
+68
| | Area | Personal-agent client | Company side | Runnable flow | | ||
| | --- | --- | --- | --- | | ||
| | Discovery, identity, Sessions, DPoP | Python and TypeScript | Python reference company | [Company discovery](/examples/company-discovery/), [Session lifecycle](/examples/session-lifecycle/) | | ||
| | Direct and Device Sign-In | Python and TypeScript | Device: Python reference company | [Device Sign-In](/examples/device-sign-in/) | | ||
| | Mediated Sign-In | Python and TypeScript | Python reference company | [Mediated Sign-In](/examples/mediated-sign-in/) | |
Comment on lines
+34
to
+38
| | Operations v1 | Client implemented in Python and TypeScript: negotiation, exact approval (revision + digest), `terms_changed`, recovery after a lost confirm, cancel, `extension_required`, and standing permissions through `pap.standingPermissionStore`; company handlers in the Python reference company | | ||
| | Extensible `pap` CLI | Reactor host/plugin, non-secret profiles, company discovery, agent identity/JWKS inspection, extension diagnostics, and live discovery command implemented | | ||
| | Generated API references | Python and TypeScript public exports inventoried; documentation CI rejects stale pages | | ||
| | Company/server SDK | Planned | | ||
| | Company/server SDK | Python reference company (`personal_agent_protocol.company`) over plain ASGI, every replaceable part a Reactor point; TypeScript has no company side yet | | ||
| | Demo worlds and end-to-end journeys | Python only (`personal_agent_protocol.demo`): a user with a company (Manzanita) and two companies composed (Ledgerly at Northwind), real keys over in-process HTTP | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Scope
This is the single rolling PR for completing the remaining PAP plan items across SDK implementation, conformance tests, documentation, and paired examples. Keep all subsequent PAP checkbox work on this branch and PR.
First release slice: v0.3.3
Validation