Repository navigation
Pi Durable Platform v0.3.0
Images
The platform (the Go server with the web UI):
ghcr.io/ncecere/pi-durable-platform:v0.3.0
ghcr.io/ncecere/pi-durable-platform@sha256:01972d4f8cf6dfa2e79f2834f2643590d2aca43ba705a347aa0d39040d198a36
The agent runtime (one agent per container; the platform starts it):
ghcr.io/ncecere/pi-durable-platform-runtime:v0.3.0
ghcr.io/ncecere/pi-durable-platform-runtime@sha256:c4e967f0ff40df422971303e72bddc3f2225876f55029716f4300ca3126da4a5
Deploy by digest. Verify the signatures with cosign v3 or newer (keyless, GitHub Actions OIDC):
cosign verify ghcr.io/ncecere/pi-durable-platform@sha256:01972d4f8cf6dfa2e79f2834f2643590d2aca43ba705a347aa0d39040d198a36 \
--certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/image.yml@refs/tags/v0.3.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
cosign verify ghcr.io/ncecere/pi-durable-platform-runtime@sha256:c4e967f0ff40df422971303e72bddc3f2225876f55029716f4300ca3126da4a5 \
--certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/image.yml@refs/tags/v0.3.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.comRelease assets
<image>-v0.3.0-<os>-<arch>.sbom.spdx.json: each image's SBOM (SPDX JSON) for each platform, as attached to the image.<image>-v0.3.0.digest.txt: each image's reference by digest.checksums.txt: SHA-256 of the files above (sha256sum -c checksums.txt).
The SBOM and build provenance are also attached to the images as attestations:
docker buildx imagetools inspect ghcr.io/ncecere/pi-durable-platform@sha256:01972d4f8cf6dfa2e79f2834f2643590d2aca43ba705a347aa0d39040d198a36 --format '{{ json .SBOM }}'.
Pi Durable Platform v0.3.0
"Apps at scale" (2026-10-20), on top of v0.2.0. Apps built on the platform can page and search long lists,
follow conversations with small event streams instead of whole views, steer a reply in progress, create agents safely
on retries, move agents between teams, and serve many people through one connection. It includes database migrations
00018 to 00020: read Upgrading first.
What changed
For apps
- Paging and title search (#8).
GET /v1/agents/{id}/threadstakeslimit, an opaquecursor(the reply's
nextCursor) andq, a case-insensitive title substring; newest activity first. Withoutlimitthe reply is exactly
what it was. A conversation pages backwards withGET …/threads/{thread}?limit=&before=(the reply's
earlierCursor). - Incremental event streams (#9).
GET …/threads/{thread}/events?format=delta&v=1sends asnapshot, then
numbered changes:message.addedandmessage.updated, partial text (partial.set,partial.appended,
partial.cleared), tools (tool.started,tool.updatedwith elapsed time,tool.finished,tool.removed,
tools.set) and the run's state (run). After a reconnect,Last-Event-IDresumes, or a new snapshot comes. Traffic
follows the new tokens, not the conversation's length. Withoutformatthe stream is unchanged. - Steering (#10).
whenBusy: "steer"onPOST …/messagesjoins the reply in progress (after its current tool
round);followUp, the default, queues behind it. - Safe agent creation (#11). An
Idempotency-Keyheader onPOST /v1/teams/{team}/agents: the same key and request
within 24 hours return the first agent (Idempotent-Replayed: true), a different request with the key is 422
idempotency_key_reused, and concurrent retries create one agent.PUT …/agents/by-key/{key}now also works for a
person in their own personal workspace (with a session, a personal API key or an app they connected), so an app can
keep one agent per purpose for each person; those agents are the person's, not service-managed. In shared teams it
stays for service accounts. - OAuth Connect for apps with many people (#12).
prompt=nonereconnects without a page, or returnslogin_required,consent_requiredor
interaction_required.login_hintnames the account the app expects: a different signed-in account is never
connected silently.- A read-only scope,
agents:read(reads only; anything else is 403insufficient_scope). - App secrets (
pdpcs_…): a platform admin issues one to a trusted app (Admin › Settings › Trusted apps; shown
once, at most two). With it, the app follows everyone who connected it on one stream,GET /v1/app/events, and
reads their usage,GET /v1/app/usage. People appear there only as the app's ownsubjectfor them (returned in
the token response and in/v1/me), never by user ID, email or name.
- Usage for several agents (#24). A repeatable
agentId(up to 100) andgroupBy=agent,dayon usage reports.
In the console
- The test chat pages and searches its threads, loads earlier messages, follows the conversation with the delta stream,
offers Steer while a reply runs, and Enter queues a follow-up. - An agent's Settings has Move, with the dry run's summary before confirming (#13): what the target team must offer,
how many shared threads become visible to whom, and what stops applying. Service-managed agents don't move. - Team › Usage groups by agent and day; Admin › Usage filters by connected app; Admin › Settings issues and deletes app
secrets.
MCP
- Tools an MCP server's app keeps for its own interface (
_meta.ui.visibilitywithout"model", the MCP Apps
extension) are never offered to the model. Tool lists stored before this release are read again in the background when
the platform starts. Showing apps' interfaces is roadmap item #51.
Fixed and hardened (the result of an independent review of this release; none was known to be exploited)
- A v0.2 replica serving requests during a rolling upgrade could create keyed agents that weren't service-managed, so
the team's people could read an app's users' conversations. The database now enforces that a keyed agent in a team
that isn't a personal workspace is service-managed (migration00020, which also fixes such rows). - A runtime start always uses the agent's current configuration: a request in flight during a move can't restart the
agent with the old team's model key or MCP servers, and an agent turned off isn't started by a request authorized while
it was on. Thread MCP servers set during a move don't survive it. prompt=noneno longer tells an app whether the browser is signed in as thelogin_hintaccount, unless the person
connected the app or it is trusted.- The app-level event stream names people only by the app's subjects.
- Removing an app from the trusted list deletes its app secrets (audited); trusting it again needs new ones.
qwith NUL or invalid UTF-8 is 400invalid_queryinstead of 500.
Upgrading from v0.2.0
- Back up the platform database.
- Replace both images (the platform and the runtime, by digest) and restart. The platform applies migrations
00018
(agents'service_managedflag, idempotency keys, agent moves),00019(OAuth scopes, app secrets, per-app
subjects, usage attributed to apps) and00020(the database enforces that keyed agents in shared teams are
service-managed) when it starts.- Existing keyed agents (all made by service accounts in shared teams) are marked service-managed: nothing changes for
them. - With several replicas, a rolling upgrade is safe: a v0.2 replica that creates a keyed agent after the migrations
still gets a service-managed one (the database sets it).
- Existing keyed agents (all made by service accounts in shared teams) are marked service-managed: nothing changes for
- Agents upgrade as they wake. Conversation paging, delta streams and steering are served by the agent's runtime.
An agent that was running when you upgraded keeps its old runtime until it hibernates; until then it ignoreslimit
andbefore(the whole conversation comes back),format=delta(whole-view events) andwhenBusy(a follow-up).
Thread list paging and search work at once (the platform serves them). - MCP tool lists are read again once, in the background, for every server registered before this release; a server
that can't be reached keeps its list until the next start. - Apps using OAuth Connect keep working unchanged. To use the new features: store the
subjectfrom the token
response, useprompt=nonewithlogin_hintfor silent reconnects, and ask a platform admin for an app secret to
use the app-level stream and usage. - No new settings.
Rolling back. Going back to v0.2.0's images works with the schema at 00020: v0.2 ignores the new columns and
tables (idempotency keys, app secrets, per-app subjects), treats every keyed agent as service-managed again (so a person's
keyed agents in their personal workspace become service-managed for them: they keep them but can't read them until you
upgrade again), and its by-key inserts get the trigger's protection.
Known limitations
Unchanged from v0.2.0, except these, which this release resolves: no paging, whole-view
events, messages always queue, by-key agents for service accounts only, no idempotency on agent creation, OAuth
Connect without login_hint and prompt=none, and no agent transfer. New or remaining:
- Usage of a shared team agent used through two apps at once is attributed to neither app (usage counts for an app
only when it's unambiguous which app's person the agent is answering). - During a move, the running-agents limit is checked against the old team for a start already under way.
- Delta streams keep a copy of the view per open stream, and the number of open streams per person isn't limited.
- Conversation paging needs the agent's runtime to be v0.3 (see Upgrading).
- The roadmap plans the rest; v0.4 is about agents that run on their own.
Images and chart
| Artifact | What |
|---|---|
ghcr.io/ncecere/pi-durable-platform |
The platform (distroless, UID 65532, read-only root filesystem) |
ghcr.io/ncecere/pi-durable-platform-runtime |
The agent runtime (Node 22, UID 10001, read-only root filesystem with a writable /tmp) |
oci://ghcr.io/ncecere/charts/pi-durable-platform |
The Helm chart, version 0.3.0 |
Both images are tagged v0.3.0 and latest, for linux/amd64 and linux/arm64. Deploy by digest. The digests are in the
GitHub Release, its <image>-v0.3.0.digest.txt assets and the workflow's job summary.
ghcr.io/ncecere/pi-durable-platform@sha256:01972d4f8cf6dfa2e79f2834f2643590d2aca43ba705a347aa0d39040d198a36
ghcr.io/ncecere/pi-durable-platform-runtime@sha256:c4e967f0ff40df422971303e72bddc3f2225876f55029716f4300ca3126da4a5
ghcr.io/ncecere/charts/pi-durable-platform:0.3.0 (sha256:7a0848ea1f57cbb41aa0d27d32f6725b6173af218487b947cdf2663965fc42c1)
Verifying the images and the chart
Verify the signatures with cosign v3.0 or newer
(keyless, GitHub Actions OIDC):
cosign verify ghcr.io/ncecere/pi-durable-platform@sha256:<digest> \
--certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/image.yml@refs/tags/v0.3.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
cosign verify ghcr.io/ncecere/pi-durable-platform-runtime@sha256:<digest> \
--certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/image.yml@refs/tags/v0.3.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
cosign verify ghcr.io/ncecere/charts/pi-durable-platform:0.3.0 \
--certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/chart.yml@refs/tags/v0.3.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.comThe SBOM (SPDX) and build provenance are attached to each image as attestations, and the Release carries each image's
SBOM per platform with checksums.txt. pi-durable-platform version prints pi-durable-platform 0.3.0 (<commit>); the
runtime image is labelled org.opencontainers.image.version=0.3.0.
Reporting problems
Open an issue at https://github.com/ncecere/pi-durable-platform/issues. Report security problems privately as
SECURITY.md describes.