Repository navigation
Pi Durable Platform v0.2.0
Images
The platform (the Go server with the web UI):
ghcr.io/ncecere/pi-durable-platform:v0.2.0
ghcr.io/ncecere/pi-durable-platform@sha256:3f988afa89eadec2b4f38b132cc34016300e10d11e7c08c0258a6546b195222d
The agent runtime (one agent per container; the platform starts it):
ghcr.io/ncecere/pi-durable-platform-runtime:v0.2.0
ghcr.io/ncecere/pi-durable-platform-runtime@sha256:11a098887c13377c985dae169d7dbbaedc854717b1d277e3cdf5aec45602ce8e
Deploy by digest. Verify the signatures with cosign v3 or newer (keyless, GitHub Actions OIDC):
cosign verify ghcr.io/ncecere/pi-durable-platform@sha256:3f988afa89eadec2b4f38b132cc34016300e10d11e7c08c0258a6546b195222d \
--certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/image.yml@refs/tags/v0.2.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
cosign verify ghcr.io/ncecere/pi-durable-platform-runtime@sha256:11a098887c13377c985dae169d7dbbaedc854717b1d277e3cdf5aec45602ce8e \
--certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/image.yml@refs/tags/v0.2.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.comRelease assets
<image>-v0.2.0-<os>-<arch>.sbom.spdx.json: each image's SBOM (SPDX JSON) for each platform, as attached to the image.<image>-v0.2.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:3f988afa89eadec2b4f38b132cc34016300e10d11e7c08c0258a6546b195222d --format '{{ json .SBOM }}'.
Pi Durable Platform v0.2.0
"Run it anywhere, safely" (2026-10-19), on top of v0.1.2. The platform now ships deployment manifests, runs
as several replicas, exports metrics and traces, can re-encrypt its secrets, and closes the safety gaps the v0.1.0 notes
listed, plus the findings of an independent review of this release. It includes database migration 00017: read
Upgrading first.
What changed
Deploying
- A Helm chart and a Kustomize base (
deploy/) with RBAC, NetworkPolicies (and a Cilium flavour), Pod Security
restricted, the Kubernetes and Agent Sandbox launchers, an Ingress, a PodDisruptionBudget and a ResourceQuota. The
chart is the source of truth: the Kustomize base is generated from it, and CI checks that the two render the same
objects, with Helm 3 and Helm 4. The chart is published, signed with cosign, to
oci://ghcr.io/ncecere/charts/pi-durable-platformon release tags. CI installs it into a kind cluster with two
replicas and checks the isolation (an agent reaches only its database, the gateway and DNS), using the images built for
the commit under test (#1). - Several platform replicas (#2). Set
replicaCount: 2(the Kustomize componentcomponents/replicas; the chart
adds rolling updates that leave none missing, spreading over nodes and zones and, if you turn it on, a
PodDisruptionBudget). The runtime reports its open streams and its work, the reaper believes that report, only one
replica reaps at a time and starting an agent is serialised across replicas, so no replica can hibernate an agent whose
stream is open on another. Sessions, sign-in and OAuth state, rate limits and thread events were already shared through
PostgreSQL. The design and the inventory of what is per replica are indocs/replicas.md. - Metrics and tracing (#3).
/metricson its own listener (METRICS_ADDR; the chart enables it on port 9090 and
allows nobody to reach it until you say who may), with requests by route pattern, agent wakes (cold, warm pool or
already running), hibernations, model and MCP proxies, OAuth, sign-ins, rate limits, background loops, group re-checks
and database pools. Labels are values the platform defines; no id, email, team name or URL is ever a label. Optional
traces over OTLP/HTTP (OTEL_EXPORTER_OTLP_ENDPOINT). A Grafana dashboard and example alerts are indeploy/grafana/,
and the chart can create a ServiceMonitor, the NetworkPolicy for your scraper and the dashboard ConfigMap.
Operating
pi-durable-platform rekey(#4) re-seals every stored secret (agent credentials, model providers, MCP servers
and the stored identity provider tokens) under the currentENCRYPTION_KEY;--dry-runshows what it would do and
doctorcounts the values still under the previous key. Once it reports none,ENCRYPTION_KEY_PREVIOUScan go. A
test fails for any sealed column it doesn't cover.- A configuration reference (#23).
.env.examplelists every setting, and a test keeps it complete. - The agents database is harder to interfere with (#7). An agent's ownership is a row in its own schema, taken over
only when the owning database session is gone, so another agent's role can't block it. New commandsprune-rolesand
prune-agent-storage.doctorreports agent roles that belong to no agent, and warns when PUBLIC may connect to the
platform database. - Vulnerability checks in CI:
govulncheckand audits of the web app's and the runtime's production dependencies,
weekly and whenever a dependency file changes.
For apps and people
- Platform-chosen thread IDs (#5).
POST /v1/agents/{id}/threadsreturns a new random thread ID. MCP servers are set
only on a thread that exists or that the samePUTcreates (the caller becomes its creator; sendvisibilityon the
PUTfor a private thread). Use thePOSTand your threads can't be pre-seeded. - IdP group re-checks (#6). With
OIDC_ALLOWED_GROUPS, the platform asks the identity provider every 15
minutes (OIDC_GROUP_RECHECK_INTERVAL) whether each recently active person is still in an allowed group. When not,
their sessions, API keys and connected apps stop working, open event streams close, and Admin › People shows "Group
access ended"; signing in again restores access. If the provider can't answer, access lasts a grace period
(OIDC_GROUP_RECHECK_GRACE, 24 hours). It needs the refresh token of the person's sign-in, so theoffline_access
scope. A person without a stored token is never re-checked: this is logged, counted and shown bydoctor.
Fixed and hardened (the result of an independent review of this release; none was known to be exploited)
- The reaper reads a runtime's
/statusthrough a 64 KiB limit; an agent could otherwise send a reply large enough to
exhaust the platform's memory. - Agents whose database role created large objects or stray tables can be deleted;
prune-rolesremoves such leftovers. - An agent's role can no longer block platform start-up by holding the agents database's prepare lock.
- A client's
traceparentno longer forces a request to be traced. - Open event streams re-check access every 30 seconds, so a person who is suspended, denied or removed from a team stops
receiving events. - MCP servers set on a reserved thread ID don't reach another person's first message.
- A stored identity provider token that can't be opened ends access after the grace period instead of exempting the
person; a rotated refresh token survives a restart in the middle of a check; people in more than 200 groups are matched
on the full list. - A status report from an agent's runtime that arrived after a thread was deleted brought the thread back, as a shared
record carrying the deleted thread's title, even for a private thread (in v0.1.x too). A single report now only
updates a thread the platform has; the runtime's list at start-up still adopts threads the platform doesn't know. - A reserved thread (no message yet) can be deleted; the runtime has no conversation for it, so the delete returned 404.
golang.org/x/net0.60.0 fixes four HTTP/2 advisories (GO-2026-6603, -6611, -6612, -6617) thatgovulncheckreports
as reachable. v0.1.2 and earlier are affected.
Upgrading from v0.1.2
-
Back up the platform database (the migration is forward-only in practice: see rolling back).
-
Replace both images (the platform and the runtime, by digest) and restart. The platform applies migration
00017
when it starts.- It deletes MCP servers that were set on a thread nobody had started (earlier releases accepted them, and a later
private thread with that ID would have inherited them) and makes a thread's servers belong to its record from then on. - It adds the tables for group re-checks. Nobody is re-checked until they sign in again, so nobody is locked out by the
upgrade. - With several replicas, a rolling upgrade runs both versions for a while: until every replica is upgraded, a request
to set MCP servers on an unrecorded thread can fail against the new reference. Apps that use
POST /v1/agents/{id}/threadsare unaffected.
- It deletes MCP servers that were set on a thread nobody had started (earlier releases accepted them, and a later
-
Agents upgrade as they wake. An agent that was already running keeps its old runtime until it hibernates; until
then it doesn't reportopenStreams, so the reaper judges it by the streams of the replica it is on. An agent database
from v0.1.x opens with the new runtime without any step. -
Keep agents' roles out of the platform database. Run
pi-durable-platform doctor. If it warns that PUBLIC may
connect, run on the server (as the platform database's owner):REVOKE CONNECT ON DATABASE <platform database> FROM PUBLIC; GRANT CONNECT ON DATABASE <platform database> TO <the platform's role>;
and the same
REVOKEfor the other databases on that server that agents must not reach, or limit agent roles to the
agents database inpg_hba.conf(a rule is indeploy/examples/cloudnative-pg/).
The platform doesn't do this itself: other roles may connect to those databases. -
If you set
OIDC_ALLOWED_GROUPS: the identity provider must issue refresh tokens foroffline_access(the Helm
chart adds the scope for you; otherwise add it toOIDC_SCOPES, and allow refresh tokens for the client in the
provider). People sign in again to store a token.doctorsays how many recently active people have none, and the
pdp_group_recheck_people{state="without_token"}metric tracks it. WithoutOIDC_ALLOWED_GROUPSnothing changes. -
Metrics are off unless you set
METRICS_ADDR(the chart sets it, on port 9090 and unreachable until
networkPolicy.metrics.fromnames your scraper). Tracing is off unlessOTEL_EXPORTER_OTLP_ENDPOINTis set. -
New settings, all optional:
METRICS_ADDR,OTEL_EXPORTER_OTLP_ENDPOINT,OTEL_SERVICE_NAME,
OTEL_TRACES_SAMPLER_ARG,OIDC_GROUP_RECHECK_INTERVAL,OIDC_GROUP_RECHECK_GRACE.
Rolling back. Going back to v0.1.2's images works, but the schema stays at 00017: v0.1.2 ignores group denials
(people who lost access get their API keys back until you upgrade again) and a request that sets MCP servers on an
unrecorded thread fails against the new reference. goose down for 00017 exists and discards the group re-check
tokens and denials.
Known limitations
Unchanged from v0.1.0, except these, which this release resolves: one platform replica,
no re-encryption command, no deployment manifests, no metrics, tracing or dashboards, advisory locks in the agents
database (see step 4 above for what remains: a database server is a trust boundary you configure), and thread IDs chosen
by clients (use POST /v1/agents/{id}/threads; a client-chosen ID still works). New or remaining:
- Per-replica caches. Each replica caches the identity provider's discovery document and apps' logos itself, so a
consent page can show an app's initials instead of its logo on some requests with two replicas. - The
locallauncher is one process and can't be shared between replicas (the chart has no other launcher than
kubernetesandagent-sandbox). - The agent limit can be exceeded by one under concurrent wakes (
too_many_running). - Traces stop at the runtime: the platform passes
traceparentto the runtime, but the runtime doesn't read it, so
its own model and MCP calls start new traces. - Open streams and keys: a stream closes within 30 seconds when a person loses access. A revoked API key or app grant
doesn't close a stream that is already open until then, and a service-account key isn't re-checked. - Group re-checks are fail-open for people without a stored token (so a provider that issues no refresh tokens
can't lock everyone out);doctorand the metrics show it. - The roadmap plans the rest, each with a GitHub issue labelled
roadmap.
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.2.0 |
Both images are tagged v0.2.0 and latest, for linux/amd64 and linux/arm64. Deploy by digest. The digests are in the
GitHub Release, its <image>-v0.2.0.digest.txt assets and the workflow's job summary.
ghcr.io/ncecere/pi-durable-platform@sha256:3f988afa89eadec2b4f38b132cc34016300e10d11e7c08c0258a6546b195222d
ghcr.io/ncecere/pi-durable-platform-runtime@sha256:11a098887c13377c985dae169d7dbbaedc854717b1d277e3cdf5aec45602ce8e
ghcr.io/ncecere/charts/pi-durable-platform:0.2.0 (sha256:328a90a22c2d2a82a1e74a7b9667f3fa4bfe5d8ddd8a950967f7596b1a106cb8)
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.2.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.2.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
cosign verify ghcr.io/ncecere/charts/pi-durable-platform:0.2.0 \
--certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/chart.yml@refs/tags/v0.2.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.2.0 (<commit>); the
runtime image is labelled org.opencontainers.image.version=0.2.0.
Reporting problems
Open an issue at https://github.com/ncecere/pi-durable-platform/issues. Report security problems privately as
SECURITY.md describes.