Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/examples.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright (c) 2023-2026 Datalayer, Inc.
# Distributed under the terms of the Modified BSD License.

# Every public code example runs (PAP plan, documentation): the README's and the docs'
# Python and TypeScript blocks against reference companies, every tool of every Pydantic
# AI example, and every Agent Runtimes PAP application built by the latest Agent
# Runtimes release.

name: Examples

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:

jobs:
python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
with:
version: latest
python-version: '3.12'
activate-environment: true
- run: uv pip install ".[test,examples]"
- name: Import every Pydantic AI example
run: python scripts/validate_pydantic_ai_examples.py
- name: Run every Python docs example and every example tool
run: pytest -q personal_agent_protocol/__tests__/test_docs_examples.py

typescript:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
- run: npm install --no-package-lock
- run: npm run build
- name: Run every TypeScript docs example
run: node --test src/__tests__/docs-examples.test.mjs

agent-runtimes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
with:
version: latest
python-version: '3.12'
activate-environment: true
- name: Install this SDK with the latest Agent Runtimes release
run: uv pip install "agent-runtimes>=1.3.111" .
- name: Build every Agent Runtimes PAP application of that release
run: |
set -euo pipefail
version="$(python -c "from importlib.metadata import version; print(version('agent-runtimes'))")"
echo "agent-runtimes $version"
git clone --quiet --depth 1 --branch "v$version" --filter=blob:none --sparse \
https://github.com/datalayer/agent-runtimes.git agent-runtimes
git -C agent-runtimes sparse-checkout set examples/personal-agent-protocol scripts
python agent-runtimes/scripts/validate_pap_examples.py
16 changes: 16 additions & 0 deletions .github/workflows/py-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,19 @@ jobs:
install-extras: test
run-tests: true
test-command: pytest personal_agent_protocol/__tests__/ --cov=personal_agent_protocol --cov-report term-missing

# The load tests at size, and the canary suite with the examples' tools (PAP plan §5).
load-and-canaries:
runs-on: ubuntu-latest
env:
PAP_LOAD_SIZE: '512'
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
with:
version: latest
python-version: '3.12'
activate-environment: true
- run: uv pip install ".[test,examples]"
- run: pytest -q -s -m load personal_agent_protocol/__tests__/test_load.py
- run: pytest -q -s personal_agent_protocol/__tests__/test_canaries.py
12 changes: 9 additions & 3 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
# Distributed under the terms of the Modified BSD License.

# Publishes the release a tag names to PyPI and npm without stored tokens.
# Each registry trusts this workflow through its own GitHub environment. The
# Each registry trusts this workflow through its own GitHub environment; the
# PyPI distributions carry attestations and the npm package provenance. The
# tag must name both package versions; versions already published are skipped.
# See RELEASE.md.

Expand Down Expand Up @@ -147,9 +148,12 @@ jobs:
with:
name: python
path: dist
# Trusted publishing (OIDC, no token) with PEP 740 attestations: each
# distribution is signed for this workflow run and tag.
- uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true
attestations: true

npm:
needs: build
Expand All @@ -172,8 +176,10 @@ jobs:
with:
name: npm
path: npm-dist
- name: Publish with npm trusted publishing
run: npm publish ./npm-dist/*.tgz --access public
# Trusted publishing (OIDC, no token) with a provenance statement naming
# this workflow run and tag.
- name: Publish with npm trusted publishing and provenance
run: npm publish ./npm-dist/*.tgz --access public --provenance

release:
name: GitHub release
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/typescript.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ on:
- 'personal_agent_protocol/**'
- 'scripts/conformance_report.py'
- 'package.json'
- 'README.md'
- 'docs/docs/**'
- '.github/workflows/typescript.yaml'
pull_request:
branches: [main, develop]
Expand All @@ -22,6 +24,8 @@ on:
- 'personal_agent_protocol/**'
- 'scripts/conformance_report.py'
- 'package.json'
- 'README.md'
- 'docs/docs/**'
- '.github/workflows/typescript.yaml'
workflow_dispatch:

Expand All @@ -36,6 +40,10 @@ jobs:
- run: npm install --no-package-lock
- run: npm run typecheck
- run: npm test
- name: Load tests at size
run: node --test src/__tests__/load.test.mjs
env:
PAP_LOAD_SIZE: '512'

# Every language pairing runs the conformance suite over TLS (PAP plan §5), and the
# compatibility report is generated from their JSON reports.
Expand Down
117 changes: 117 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,123 @@

# Changelog

## 0.9.0 (2026-10-11)

- **The exact draft, in code:** `PROTOCOL_PROFILE` (`0.1`) and
`PROTOCOL_REVISION` (`2026-10-09`, the specification page implemented), with
`OPERATIONS_VERSION` (`1`), exported by both SDKs; the reference companies'
discovery documents and the conformance runners read them, and a test in
each language holds the other SDK, the package descriptions, the README,
`protocol/UPSTREAM.md`, the requirement ledger, and the docs to the same
values.
- **Breaking — unused contribution points removed:** `pap.protocolProfile`
(`PROTOCOL_PROFILES`, `ProtocolProfile(s)`), `pap.extensionHandler`
(`EXTENSION_HANDLERS`, `ExtensionHandler(s)`), and `pap.keyStore`
(`KEY_STORES`, `KeyStore(s)`, `KeyReference`): no SDK code read them. See the
[migration guide](docs/docs/releases/migration.mdx).
- **Aligned Reactor contributions:** `protocol/reactor-points.json` lists every
point (17 for the personal agent, 9 for the company) with its contract,
default, and tenant scope; a table-driven test in each SDK checks its
exports, contracts and defaults against it, and each point's lifecycle
(contributed, replaced by a lower `order`, removed with its plugin, equal
orders failing closed), plus the tenant-scoped `pap.pairwiseUserIdStore` and
`pap.dpopProofProvider`. The alignment fixed:
- **Breaking:** optional points (cache, clock, random source, retry policy,
rate-limit budget where optional, `pap.company.api`, `pap.company.mcp`)
now fail closed on two contributions at the winning order
(`optional_contribution` / `optionalContribution`), and the
lowest-order `pap.company.operationExecutor` for an action performs it.
- The TypeScript company serves `pap.company.mcp` (`CompanyMcps`,
`CompanyMcp`, `ToolResult`, `ToolResults`): `/mcp` with Bearer Session
Tokens for its URL, RFC 9728 metadata, and tools that propose operations,
as in Python; Manzanita's MCP server in both languages. The Python agent
now passes the four MCP conformance cases and `operations.cross-channel-mcp`
against the TypeScript company.
- **Breaking (TypeScript company):** renewing a Session with the Session
assertion gives a signed-out token, as in Python (§4.2).
- `pap.standingPermissionStore` is declared by the core plugin in both SDKs.
- **Every public code example runs in CI:** each Python and TypeScript block
of the README and the docs runs as written against reference companies on
an in-process network (`test_docs_examples.py`, `docs-examples.test.mjs`),
the JSON examples are parsed by their wire models, and every tool of every
Pydantic AI example runs. It found and this release fixes: the caching
example's `CacheBounds(min_seconds=300)` raised (its default lifetime was
outside the bounds), the sessions guide started and renewed with the local
user as the tenant, and a budget example used `build_pap_reactor` without
importing it. A new `Examples` workflow also builds every Agent Runtimes PAP
example application with the latest Agent Runtimes release (1.3.111 or
later) and rejects duplicate application and tool IDs.
- **Release documentation:** [Compatibility](docs/docs/releases/compatibility.mdx)
(the draft, the SDK versions, the four pairings),
[Migration](docs/docs/releases/migration.mdx) (every breaking release),
and [Support](docs/docs/releases/support.mdx) (supported versions, how to
report, the security contact), with the conformance report under Releases.
- **Signed releases:** PyPI distributions are published with attestations
and the npm package with provenance, both from the `Release` workflow.

## 0.8.0 (2026-10-11)

- **Consent pages bind the browser (Guides, consent pages):** the built-in
consent page sets a `__Host-poppy_consent` cookie (`Secure`, `HttpOnly`,
`SameSite=Strict`), stores its digest with the sign-in request
(`PendingAuthorization.browser_binding`, `browserBinding`), and puts a CSRF
token derived from the cookie in its form, which posts to
`/oauth/authorize`. `CompanyService.consent_decision(request)`
(`consentDecision`) returns the user's decision only for a same-origin POST
(`Sec-Fetch-Site`, `Origin`) carrying the bound cookie and its token; a
cross-site POST, a foreign or opaque origin, a missing cookie or token, and a
form replayed from another browser are refused. A hosted consent page gets no
binding. The website's operation page carries a token derived from its
`poppy_session` cookie and refuses a cross-site POST or another browser's
form with `403`. Pages keep `frame-ancestors 'none'` and `X-Frame-Options: DENY`.
- **Overlapping JWKS verified at the company:** during a rotation assertions
signed with either key verify; the retired key is refused once the cached set
expires; unknown `kid`s refetch at most once a minute (tests in both SDKs;
the guide says how long to keep a retired key published).
- **Credentials stay out of logs, errors, URLs, and model context:** a canary
suite in both SDKs runs the demo worlds, every conformance case, and the
Pydantic AI examples' tools with `CANARY`-minted values and canary
passwords, and fails if any credential exchanged appears in a log record, a
raised error, a requested URL, a model-readable body, a message's text, or a
`PapSignIn` model view; the TypeScript suite also checks the built `lib/` and
that the browser entry reaches no demo credential. Fixed the leak it found:
Ledgerly's connect reply put Northwind's device user code in the message
text (it is now in the message's `data` only).
- **Load tests** (`load` mark, `PAP_LOAD_SIZE`; 512 in CI) for replay caches,
shared budgets, device polling, and SSE fan-out, with
[a docs page](docs/docs/protocol/load-tests.mdx). They found and this release
fixes:
- `MemoryReplayCache` never forgot an identifier. It now takes a clock
(the company's), sweeps expired identifiers whenever it doubles, and
reports its `len()` / `size`. The verifiers now remember an identifier
exactly as long as they would accept it: a DPoP proof until its `iat`
leaves the acceptance window (it was `now` plus the window), a client or
Session assertion until `exp` plus the 30-second skew (it was `exp`) — a
replay cache that honoured the old expiries could accept a replay inside
those gaps.
- `SharedRateLimitBudget` slept while holding its per-company lock, so
`max_wait` bounded each caller's own sleep but not its time queued. It now
reserves each caller's turn when it asks (the generic cell rate algorithm):
concurrent callers never exceed the rate and burst, a turn beyond
`max_wait` is refused at once with `rate_limited`, and a timer that fires
early (Node's can) is slept out. TypeScript's default sleep rounds up to
whole milliseconds.
- **Kits:** `check_replay_cache` / `checkReplayCache` check a
`pap.company.replayCache` (of many callers marking one identifier at once,
exactly one wins); `check_store` / `checkStore` take a `concurrency`.

## 0.7.0 (2026-10-11)

- **reactor.ui displays:** `personal_agent_protocol.ui.PapSignIn`
(`application/vnd.datalayer.pap.sign-in+json`, version 1) is a sign-in
waiting for the person (`direct`, `device` or `mediated`; an `https` page
only). The page to open and the device code are for the page alone — a
device's verification URL may carry the code — so neither is in the
model's view, and the text names only the company's domain. `from_answer`
reads a `pap_sign_in` answer and `tool_meta()` is the MCP `_meta` that carries
the display. The core plugin declares the type through `provide_display_types`.
Needs `datalayer_reactor` 1.1 (the new `ui` extra); nothing else imports it.

## 0.6.0 (2026-10-11)

- **Breaking — company store:** `pap.company.store` is an async keyed store of
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@
# 🤖 🌸 Personal Agent Protocol

Python and TypeScript SDKs for the
[Personal Agent Protocol](https://personalagentprotocol.org), currently
targeting [draft 0.1](https://personalagentprotocol.org/docs/spec).
[Personal Agent Protocol](https://personalagentprotocol.org): both implement
[draft 0.1](https://personalagentprotocol.org/docs/spec) (2026-10-09) and its
Operations extension v1. See
[Compatibility](https://personal-agent-protocol.datalayer.tech/releases/compatibility/),
[Migration](https://personal-agent-protocol.datalayer.tech/releases/migration/),
the [conformance report](https://personal-agent-protocol.datalayer.tech/protocol/conformance-report/),
and [Support](https://personal-agent-protocol.datalayer.tech/releases/support/).

![Personal Agent Protocol demo](https://images.datalayer.io/products/personal-agent-protocol/pap-demo.gif)

Expand Down
22 changes: 22 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,25 @@ tag.
Registry configuration must match the repository, workflow filename, and
environment exactly. Renaming any of them requires updating the trusted
publisher configuration.

## Signatures

PyPI distributions are published with PEP 740 attestations
(`attestations: true`) and the npm package with a provenance statement
(`--provenance`); both need the job's `id-token: write`. After a release,
check them:

- PyPI: the release's files list a provenance attestation
(`https://pypi.org/integrity/personal-agent-protocol/<version>/<file>/provenance`).
- npm: `npm view @datalayer/personal-agent-protocol@<version> dist.attestations`
names the provenance predicate, and `npm audit signatures` verifies it.

## Before tagging

Bump `personal_agent_protocol/__version__.py`, `package.json`, the plugin
manifests' versions (`personal_agent_protocol/reactor.py`,
`personal_agent_protocol/company/__init__.py`, `src/reactor.ts`,
`src/company/index.ts`), the [compatibility](docs/docs/releases/compatibility.mdx)
and [support](docs/docs/releases/support.mdx) pages, a migration section for a
breaking release, and the changelog; regenerate the conformance report. The
tests fail when any of them is stale.
28 changes: 9 additions & 19 deletions docs/docs/concepts/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,21 @@ extension runtime. The protocol boundary owns mandatory validation and
security rules; plugins provide replaceable infrastructure and optional PAP
extensions.

The first shared contribution points are:

| ID | Responsibility |
| -------------------------------------- | ----------------------------------------------------------------- |
| `pap.protocolProfile` | Version-specific wire behavior |
| `pap.discoveryTransport` | Credential-free metadata transport |
| `pap.urlSafetyPolicy` | HTTPS, DNS, IP, and redirect policy |
| `pap.extensionHandler` | Named, major-versioned PAP extensions |
| `pap.pairwiseUserIdStore` | Atomic stable identity by tenant, local user, and verified issuer |
| `pap.authorizationStateStore` | Expiring atomic put/take for one-use authorization state |
| `pap.signer` / `pap.dpopProofProvider` | Protected assertion and proof creation |
| `pap.keyStore` / `pap.tokenStore` | Protected key references and opaque credentials |
| `pap.cache` / `pap.browserHandoff` | Bounded public state and trusted human handoff |
Every replaceable service is a contribution point, the same in Python and
TypeScript: [Contribution points](/concepts/contribution-points/) lists them
with their defaults and which are tenant-scoped.

Lower `order` values have higher priority. Equal winning priorities fail
closed instead of selecting a provider by load order.

```mermaid
flowchart LR
App[Application] --> SDK[PAP protocol core]
SDK --> Profile[Protocol profile]
SDK --> Transport[Discovery transport]
SDK --> Safety[URL safety policy]
SDK --> Handler[Extension handlers]
Profile & Transport & Safety & Handler --> Reactor[Datalayer Reactor]
SDK --> Keys[Signer and DPoP proofs]
SDK --> Stores[Token, state and pairwise stores]
Transport & Safety & Keys & Stores --> Reactor[Datalayer Reactor]
```

## What remains in the protocol core
Expand All @@ -51,9 +41,9 @@ assemble a host with safe defaults.
## Two meanings of extension

A **PAP extension** is negotiated on the wire by name and major version. A
**Reactor extension** packages plugins and manages their lifecycle. A Reactor
extension may contribute a PAP extension handler, but the SDK advertises that
wire capability only while a compatible handler is active.
**Reactor extension** packages plugins and manages their lifecycle. The SDK
implements one PAP extension, Operations version 1; see
[Extensions](/guides/extensions/).

Plugins cannot turn off domain/issuer binding, secret redaction, token/resource
binding, replay protection, or exact operation-revision approval.
Expand Down
Loading
Loading