Minimal Agent identity
Cloud Agent deliberately keeps identity small. It stores the Gateway user ID and the issuer that minted it, but it does not copy Gateway account metadata into identity claims or derive permissions from account roles. The Gateway makes the current access decision, and Cloud Agent rechecks that decision instead of reading permissions from the session. The Agent admission contract defines those checks.
Tokens and sessions
Credential |
Claims retained by Agent |
Validation |
|---|---|---|
Gateway identity token |
|
RS256 signature through JWKS, exact configured issuer, |
Agent session |
|
HS256 signature with |
Older Gateways may send additional profile claims. Agent discards them at
verification. The session’s gateway_issuer names the upstream identity
authority; Agent signs the session itself. Sessions last 30 days. Legacy cookies
missing the issuer or containing profile claims, and cookies from another issuer,
are rejected. Users must sign in again after upgrading from those formats.
An active account without Agent permission may establish a minimal session for
the access-required page. A missing/inactive account is refused through the
access contract’s typed subject_unavailable error. Unreadable responses and
proxy failures cannot establish access.
GET /v1/session/me returns id and agent_access. My Settings offers a Profile
section with a read-only account email, editable first and last names, and an
uploaded profile picture. Email is read on demand from the Gateway’s existing
/internal/users/{user_id}/status lookup and is never stored in Agent or added
to session claims. That response’s role and account-status fields do not confer
Agent permissions; current admission is still checked through agent-access.
The names and picture are owner-authored Cloud Agent display data, separate
from Gateway login identity and the automatically generated homepage companion.
The homepage and Profile editor prefer an uploaded profile picture. When none
is saved, they use the owner’s Pokémon companion. Removing a picture restores
the companion; memory refreshes and clears continue to update only its habitat
and badges, and do not change the saved picture. The Profile editor reuses the
memory panel’s current companion, including after a refresh or clear.
GET and revision-checked PATCH /v1/agent/profile require current Agent admission
and return Cache-Control: no-store. Names allow up to 100 characters each.
PNG, JPEG, and WebP uploads are limited to 2 MiB and 16 million pixels; the server
re-encodes them as metadata-free PNGs up to 512 by 512 pixels before saving.
Migration 0063_user_profiles adds agent_user_profiles; it contains no email,
roles, or credentials. Profile editing does not modify Gateway account details.
The frontend shares successful profile and homepage-insights reads in memory,
keyed by account. Returning to New task displays the cached avatar immediately
while refreshing in the background; a failed refresh preserves the last image.
Profile saves update the shared cache immediately and supersede older reads.
The cache lasts only within the browser tab and is cleared before sign-out or
an account change; it does not change the APIs’ Cache-Control: no-store policy.
Tasks, messages, connection identifiers, encrypted Git/MCP credentials, and other user-authored content remain Agent-owned data.
There is no individual session-revocation table. A copied cookie remains valid
until expiry; protected requests still require current Gateway access. Rotating
AGENT_SESSION_SECRET invalidates every session. Withdrawing Gateway permission
prevents subsequent protected requests.
Database identity boundary
agent_identity_namespace has one record binding the database to a
GATEWAY_ISSUER. Both the main API and MCP listener validate the binding before
starting workers. The binding never rewrites owner IDs or infers their issuer
from old rows. Its table is part of 0001_schema_baseline; the retired migration
chain called this change 0029_identity_namespace.
Database state |
Startup/check behavior |
|---|---|
Empty, no binding, issuer configured |
Bind automatically to that issuer |
Owner data present, no binding, issuer configured |
Refuse until an operator explicitly adopts the original issuer |
Bound to the configured issuer |
Continue |
Bound to another issuer, or issuer removed after binding |
Refuse |
Neither Gateway configuration nor binding |
Allow local infrastructure administration; identity/session authentication stays unavailable |
Concurrent initialization is serialized. Adoption can fill an absent binding;
it cannot replace one. Use the exact iss signed by the original Gateway.
GATEWAY_BASE_URL and JWKS URLs are transport locations and can change without
changing the identity namespace. Independent Gateways must use distinct issuers.
Replacing the Gateway’s account database with unrelated identities under the
same issuer is not a supported migration.
Upgrade an existing deployment
Prepare each environment before its distribution advances the Agent release. Merging upstream source does not deploy a named environment, and existing owner rows do not reveal the original Gateway issuer.
Verify the original Gateway’s effective issuer from its running environment. In the Gateway implementation used by the original rollout,
serving.utils.identity_tokens.issuer()readsIDENTITY_ISSUER, falls back tobase_url, and removes trailing slashes. Pin the verified effective value as GatewayIDENTITY_ISSUERand AgentGATEWAY_ISSUER. Verify staging and production separately.Back up the Agent database. If it still uses the migration chain from before 2026-09-10, follow the conversion/rebuild procedure in operations before running migrations.
upgrade headcannot directly convert a retired revision stamp. With the supported schema, migrate to head using the prepared release image.Adopt existing owner data into its original issuer, then verify the binding. In an environment with the Agent package and database configuration:
python -m cloud_agent.identity_namespace --adopt-existing --issuer '<original-issuer>' python -m cloud_agent.identity_namespace --check
The adoption argument must exactly match
GATEWAY_ISSUER. It changes only an absent binding, preserving tasks, connections and owner IDs. There is no persistent auto-adoption switch.For Compose, use the existing project name, active environment file and prepared checkout:
export AGENT_DEPLOYMENT_SHA="$(git rev-parse HEAD)" dc() { docker compose -p '<existing-project>' --env-file '<active-env-file>' \ -f deploy/control-plane/docker-compose.yml "$@" } dc build control-plane migrate dc run --rm --no-deps migrate dc run --rm --no-deps control-plane python -m cloud_agent.identity_namespace \ --adopt-existing --issuer '<original-issuer>' dc run --rm --no-deps control-plane python -m cloud_agent.identity_namespace --check
These commands require the existing Postgres service to be running. They prepare the binding without replacing the API or MCP relay.
Release Agent backend and web app together through the deployment workflow. Verify fresh login,
/me, owner/admin permissions and ownership of existing tasks. Confirm legacy cookies are rejected.
deploy/control-plane/deploy.sh runs the identity check after migration and
before bootstrap, host-readiness waiting or API/MCP replacement. Unadopted owner
data, a missing required issuer or a conflicting binding stops that deployment.
The host rollout can run in parallel, so prepare the identity boundary first.
Both Compose listeners use the same issuer.
A permission-capable Gateway may remove profile claims before or after this
Agent release; Agent treats them as optional. Older Gateways without the access
contract remain incompatible. The local Docker stack uses the fixed issuer
freeinference-local; existing unbound demo owner data needs explicit adoption
of that issuer too.
Rollback and restore constraints
Do not use alembic downgrade base to undo identity binding: the baseline drops
owner tables and agent_identity_namespace together. No partial downgrade
preserves owner data while removing only this record.
Re-upgrading an empty database automatically binds the currently configured issuer. It cannot protect history already deleted by the downgrade.
Restoring owner rows without their matching binding causes the identity check to fail. Restore the original binding or explicitly adopt the verified original issuer using the procedure above.
An older Agent binary may accept profile cookies and ignore the stored issuer. Prefer a compatible forward fix, or stop Agent while keeping the Gateway available. Back up and restore the binding with the data whose owners it names.
The implementation is in
identity.py,
identity_namespace.py and
lifespan.py.
Local verification (2026-09-10)
Gateway make all passed (5,254 tests; 48 skipped). Agent make check passed
(2,161 tests), as did its 719 web tests, lint, type checking and production build.
Fifteen additional PostgreSQL integration cases exercised initial binding,
explicit adoption, preservation of existing owners, concurrent startup and
the actual deployment-check/adoption CLI.
Native Gateway and Agent HTTP listeners also completed PKCE code exchange,
JWKS verification, minimal session creation, /me and an owner API request
against separate fresh PostgreSQL databases with production schemas. The
Gateway identity token and Agent cookie had exactly the documented fields;
legacy and foreign-issuer cookies were rejected, and no account-status request
occurred. Docker Compose rendered each listener’s environment separately;
the control plane and MCP relay both started against the bound database with
those isolated environments. Compose also refused an omitted issuer. Deploy
script tests verified that a failed identity check neither bootstraps nor
replaces listeners. This local check supplied a seeded Gateway login dependency and
access policy; it did not exercise the main site’s password login or deploy
either service.