Frozen contracts¶
These are the seams where slices otherwise drift — the invariants the codebase
holds frozen. They are verified against pydantic-ai 1.104 and live authoritatively
in CLAUDE.md.
Do not violate
Each of these is a contract other parts of the system rely on. Changing one means re-checking every place that depends on it.
Usage & cost¶
- Usage is recorded per
ModelResponse, never per run.RunUsage/RequestUsagehave nomodel_name(it's onModelResponse.model_name). Iterateresult.all_messages(), pairing eachModelResponse.usagewith its.model_name. - Usage idempotency =
UNIQUE(run_id, request_index), whererequest_indexis the deterministic NthModelResponse— notprovider_response_id(nullable), not a Temporal event count. The counter survives Continue-As-New via the CAN input. - Cost:
genai-pricesis primary, with a versionedmodel_pricingtable for audit (pricing_idpinned).
Streaming & transport¶
- AG-UI is the only streaming envelope, on the Redis bus and the SSE wire.
Inline uses
AGUIAdapter; the Temporal path hand-builds identical events via a shared converter.run_stream*/iterare forbidden inside a Temporal workflow. - Redis STREAMS for the token channel (replay via entry-id = Last-Event-Id);
Pub/Sub only for control/presence. One key convention:
personal_agent_contracts.keys.
Durable runs¶
- BYOK + Temporal: the durable path passes the model as a string +
provider_factory; keys are decrypted inside the model activity. Never serialize keys into workflow history. - Toolsets are snapshotted into the
RunSpecat run start; the workflow never queries live DB state during a run/replay. MCPid=and agentname=are stable. - Agent
name = template.slugfor inline + durable; model/toolsets/instructions are per-run.
Data & schema¶
- Extensions (vector/pgcrypto/citext) are created by CNPG
postInitSQLas superuser — never in Alembic (the app role is unprivileged).
Resilience & readiness¶
- Readiness gates only on hard deps (DB, Redis). Temporal/JWKS are soft
(
/health/deps), so a Keycloak/Temporal blip can't take the API offline.
Auth, tenancy & transport security¶
- SSE/WS auth: the WS token travels via the
Sec-WebSocket-Protocolsubprotocol (echoed on accept), never the query string. SSE has a server-enforced max lifetime ≈ token expiry. - Tenancy: validate
X-Personal-Agent-Orgagainst the token org claim every request; Postgres RLS as defense-in-depth (ScopedDbDepsetspersonal_agent.current_org).
Governance & safety¶
- Untrusted content gates tools: when any untrusted MCP server is in a run,
the assembler drops high-privilege first-party/device tools via
toolset.filtered()(assembler/policy.py); the durable worker mirrors this per-request. - Data-classification is fail-closed: tagged data must never reach a provider
not cleared for it. The same
auto_model.enforce_classificationgate runs at every model-resolution entry (inline, durable chat, triggered workflows, comms triage). - No secrets in spans: content capture defaults off; provider keys never
appear in
ModelSettingsdumps, errors or Temporal inputs.