Architecture
Subsystem map and the end-to-end lifecycle of a chat completion.
Scope:
quill-router(this repo) at commit778120494(2026-10-19). All paths are relative to the repo root/Users/elevenarms/Code/trustedrouter/quill-router. The attested gateway is in a separate repo,quill-cloud-proxy, which is not checked out here. Anything this doc says about the gateway's internals comes from this repo's contract code, docs and comments, not from gateway source.
1. Overview in one page
TrustedRouter is an OpenAI-, Anthropic- and OpenRouter-compatible LLM router. Its main selling point is that the prompt path is verifiable: prompts go only through an attested enclave, and the control plane never sees prompt or output bytes. Three planes share the work:
| Plane | Hostname | Code | Sees prompts? | Owns |
|---|---|---|---|---|
| Attested gateway (API / prompt plane) | api.trustedrouter.com (alias api.quillrouter.com, regional api-<region>.quillrouter.com) |
quill-cloud-proxy (separate repo), running in GCP Confidential Space (plus AWS/Azure variants) |
Yes, only inside the enclave | TLS termination in the enclave, provider calls, streaming, fallback attempts, token counting, inference receipts |
| Control plane | trustedrouter.com (aliases allyrouter.com, uptimerouter.com) |
this repo, Python/FastAPI, trusted_router.main:app on Cloud Run |
No (except the browser /chat-proxy, see Gotchas) |
Accounts, workspaces, API keys, BYOK envelopes, catalog and pricing, routing decisions, credit reserve/settle/refund, billing (Stripe/Adyen/PayPal/Lightning/x402), console UI, public site, status, trust page |
| Fast path (pilot) | internal VPC only | fastpath/ (Go) |
No | Lease-based fast admission and batched settlement, so the per-request Spanner reservation leaves the hot path. Pilot on 2 workspaces |
The README states the trust boundary directly (README.md:127-131): "api.trustedrouter.com is the
attested prompt path and must terminate TLS inside Confidential Space. trustedrouter.com is the
control plane and must never serve a production inference fallback." The code enforces it:
/chat/completions, /messages, /responses and /embeddings are mounted on the control plane
only when environment in {local,test} (src/trusted_router/main.py:880-883,
main.py:912-919).
What the control plane does for each production request. The enclave makes two or three
server-to-server calls to the control plane's /internal/gateway/* routes
(src/trusted_router/routes/internal/gateway.py:2453-2600):
authorize: validate the key hash, resolve the model and routing candidates (applying privacy filters), estimate cost, and reserve credits and key caps atomically in Spanner. It returnsauthorization_id,route_candidates, any BYOK ciphertext, and the regions.- Optional
heartbeat(Stage D): usage heartbeats during long streams. settle(success) orrefund(failure or disconnect): book actual tokens to the hold, write the generation record, and enqueue analytics, activity, broadcast and auto-refill work.
Prompt and output bytes never cross this boundary. Only hashes, token counts and metadata do.
Storage in production is TR_STORAGE_BACKEND=spanner-clickhouse (scripts/deploy/rollout.sh:274).
Spanner is the system of record for money and identity. ClickHouse holds request analytics,
fed through a durable Spanner outbox. Postgres-wire backends (Azure/AWS standalone clouds) and an
in-memory store (tests/local) implement the same Store protocol.
2. System context (C4 level 1)
flowchart LR
dev["Developer app / SDK<br/>OpenAI, Anthropic or OpenRouter client"]
browser["Browser user<br/>console, chat playground"]
subgraph TR["TrustedRouter"]
gw["Attested gateway<br/>api.trustedrouter.com<br/>quill-cloud-proxy in Confidential Space"]
cp["Control plane<br/>trustedrouter.com<br/>FastAPI trusted_router.main:app on Cloud Run"]
fp["Fast path pilot<br/>fastpath/ Go service, VPC only"]
sp[("Cloud Spanner<br/>money, identity, outboxes")]
ch[("ClickHouse<br/>request analytics")]
end
prov["Upstream LLM providers<br/>OpenAI, Anthropic, Vertex, Bedrock, Tinfoil, ..."]
pay["Payments<br/>Stripe, Adyen, PayPal, Lightning, x402"]
obs["Observability<br/>Sentry and Axiom, control plane only"]
sinks["Customer broadcast sinks<br/>PostHog, OTLP webhooks"]
dev -- "HTTPS, TLS ends in enclave" --> gw
browser -- "HTTPS" --> cp
gw -- "/internal/gateway authorize, settle, refund<br/>metadata only" --> cp
gw -- "prompt and completion" --> prov
cp -- "lease admit and settle, pilot only" --> fp
cp --> sp
fp --> sp
cp -- "outbox drain" --> ch
cp -- "checkout sessions" --> pay
pay -- "signed webhooks" --> cp
cp --> obs
cp -- "metadata broadcast outbox" --> sinks
cp -- "/chat-proxy byte pipe, browser only" --> gwDiagram source
flowchart LR
dev["Developer app / SDK<br/>OpenAI, Anthropic or OpenRouter client"]
browser["Browser user<br/>console, chat playground"]
subgraph TR["TrustedRouter"]
gw["Attested gateway<br/>api.trustedrouter.com<br/>quill-cloud-proxy in Confidential Space"]
cp["Control plane<br/>trustedrouter.com<br/>FastAPI trusted_router.main:app on Cloud Run"]
fp["Fast path pilot<br/>fastpath/ Go service, VPC only"]
sp[("Cloud Spanner<br/>money, identity, outboxes")]
ch[("ClickHouse<br/>request analytics")]
end
prov["Upstream LLM providers<br/>OpenAI, Anthropic, Vertex, Bedrock, Tinfoil, ..."]
pay["Payments<br/>Stripe, Adyen, PayPal, Lightning, x402"]
obs["Observability<br/>Sentry and Axiom, control plane only"]
sinks["Customer broadcast sinks<br/>PostHog, OTLP webhooks"]
dev -- "HTTPS, TLS ends in enclave" --> gw
browser -- "HTTPS" --> cp
gw -- "/internal/gateway authorize, settle, refund<br/>metadata only" --> cp
gw -- "prompt and completion" --> prov
cp -- "lease admit and settle, pilot only" --> fp
cp --> sp
fp --> sp
cp -- "outbox drain" --> ch
cp -- "checkout sessions" --> pay
pay -- "signed webhooks" --> cp
cp --> obs
cp -- "metadata broadcast outbox" --> sinks
cp -- "/chat-proxy byte pipe, browser only" --> gwMulti-cloud: each cloud (GCP, AWS, Azure) is a standalone TrustedRouter. Only identity
federates; credits, keys and analytics stay in their own cloud
(docs/storage-portability/multi-cloud-separation.md). Peer planes resolve keys lazily from the
home plane (src/trusted_router/services/federation.py) and forward deferred debt home
(src/trusted_router/services/home_settlement.py).
3. One image, six process "surfaces"
One container image serves several process roles, chosen by TR_SERVICE_SURFACE
(src/trusted_router/config.py:438-445). create_app() registers only the routes that belong to the
chosen surface (main.py:709-909).
| Surface | Purpose | Notable routes | Deploy script |
|---|---|---|---|
combined |
Local/test default. In prod it is the legacy pre-split service, permitted only with TR_ALLOW_DEPLOYED_COMBINED_SURFACE=true (main.py:235-244) |
everything | scripts/deploy/rollout.sh:408-410 |
public |
Anonymous marketing site, catalog, docs, pre-serialized OpenAPI | register_public_routes, catalog, /openapi.json |
scripts/deploy/public_surface.sh |
actions |
Anonymous public actions, memory-backed | register_public_action_routes |
— |
control |
Authenticated account plane: console, keys, billing, OAuth, MCP, chat proxy, webhooks | register_console_routes, register_key_routes, ... |
trust jobs use it |
internal |
Machine-to-machine gateway billing authority: /internal/gateway/*, observer internals |
register_gateway_internal_routes |
scripts/deploy/internal_surface.sh |
observer |
Regional/standalone-cloud status and observer endpoints (Postgres) | register_observer_internal_routes |
azure_control_plane.sh, aws_eu_control_plane.sh |
The six-service split is in progress (issue #712 per config.py:446-449). Until it completes, the
legacy combined service still runs in production next to the split public and internal
companions.
4. Subsystem map
src/trusted_router/ has 371 top-level modules and 533 .py files once subpackages are
included (routes/ 77, services/ 62, synthetic/ 22, content/, evals/). Grouped by prefix
and responsibility:
| Subsystem | Key modules | Responsibility |
|---|---|---|
| App shell | main.py, middleware.py, serve.py, config.py, errors.py, schemas.py, types.py |
FastAPI factory, surface-based route registration, middleware chain (request id, rate limit, read-only mode, security headers, gzip, markdown negotiation), uvicorn runner with a SIGTERM drain, the ~2.4k-line Settings |
| AuthN/AuthZ and keys | auth.py, scopes.py, security.py, storage_keys.py, storage_gcp_keys.py, storage_auth_*, oauth_provider.py, wallet_auth.py, routes/keys.py, routes/auth.py, routes/oauth*.py |
Principal resolution: dev header, then session cookie, then bearer sk-tr-… or trsess-…. Keys are salted SHA-256 with a high-entropy lookup hash. Management vs inference scopes. OAuth "Sign in with TrustedRouter" and app keys |
| BYOK | byok_crypto.py, key_management.py, storage_byok.py, storage_gcp_byok.py, byok_aad_backfill.py, byok_v1_attestations.py, routes/byok.py |
Envelope-encrypt customer provider keys (per-secret DEK wrapped by KMS). The gateway receives ciphertext plus byok_cache_key |
| Catalog and pricing | catalog.py, catalog_data.py (4.5k lines), catalog_registry.py, catalog_ingest.py, catalog_privacy.py, catalog_capabilities.py, pricing.py, provider_lifecycle.py, model_aliases.py, data/*.json |
Builds MODELS/MODEL_ENDPOINTS at import time from the OpenRouter snapshot and provider manifests. Uniform markup (cost x1.055, $0.01/M floor). Privacy tiers per endpoint. Retirements and price cutovers |
| Routing | routing.py, routing_candidates.py, routing_state.py, provider_ranking.py, provider_reliability.py, request_capabilities.py, model_regions.py, regions.py, enclave_regions.py |
Parse OpenRouter-style provider{order,only,ignore,allow_fallbacks,min_privacy,zdr,data_collection,sort,max_price,require_parameters}. Expand meta-models (auto, zdr, e2e, eu, cheap, tiers). Filter and sort endpoint candidates. Process-local latency/throughput hints |
| Providers and adapters (local/test inference only) | providers.py, provider_adapters.py, provider_streaming.py, provider_types.py, adapter.py, provider_payloads.py |
ProviderClient for OpenAI-compatible, Anthropic and Gemini. Converts between Messages/Responses and Chat formats. In production the enclave makes the real provider calls |
| Inference routes (local/test) | routes/inference.py, services/inference.py, services/inference_quota.py, services/inference_errors.py |
Local implementation of /chat/completions, /messages, /responses, /embeddings, with the same reserve/settle semantics, so the dev loop needs no enclave |
| Gateway contract (prod hot path) | routes/internal/gateway.py (6k lines), gateway_boot.py, gateway_timing.py, receipt_keys.py, stage_d.py, stage_d_policy.py, client_context.py, request_tags.py |
authorize, heartbeat, settle, refund, disposition, register-boot, validate, key, resolve-custom-model. Boot-signed requests (X-TR-Boot-Auth). Stage D heartbeat cohort |
| Money core | money.py, storage_gcp_authorize.py, storage_gcp_counter_dml.py, storage_gcp_counters.py, storage_gcp_credit_shards.py, credit_debt.py, typed_balance.py, spend_windows.py, strict_budget.py, billing_policy.py |
Integer microdollars. One-transaction authorize (credit DML, key-cap DML, tr_reservation INSERT, authorization INSERT). 16 credit shards per workspace. Debt rules. Daily/weekly/monthly key windows |
| Settle durability | storage_gcp_settle_outbox.py, services/settle_outbox_*.py, services/auto_refill*.py, storage_gcp_legacy_reservations.py |
Durable settle outbox (enabled in prod, rollout.sh:550) so a settle that fails partway is never lost or reaped for free. Auto-refill handoff to the payment-owning process |
| Async settle (flag-off, shadow) | async_settle_*.py (98 modules, 86 of them async_settle_shadow_v2_*), services/async_settle*.py, billing_snapshot.py, async_settle_ticket.py, routes/settlements.py |
Future: an enclave settle that returns within 2 s and finalizes durably later. Today TR_ASYNC_SETTLE_ENABLED=false and shadow-v2 evidence collection runs only for the probe workspace (rollout.sh:660-664) |
| Fast path bridge | services/fastpath_frontdoor.py, services/fastpath_settle.py, fastpath/ (Go) |
Pilot: the control plane asks the Go front door to admit under a regional lease instead of a Spanner reservation. off, busy or invalid means take today's path |
| Speculation (shadow) | speculation_protocol.py, services/speculation_shadow.py, storage_gcp_speculation_shadow.py, routes/internal/speculation.py |
Ed25519 JWS wire contract for speculative provider starts. Shadow only (TR_SPECULATIVE_PROVIDER_SHADOW_ENABLED=false) |
| Meta / "fusion" models | catalog_data.py:2773-2830 (trustedrouter/synth, fusion, fusion-code, selector, mapreduce, polyphemus-1.0), polyphemus.py, evals/fusion_*.py |
Router-defined virtual models. The control plane resolves candidates and billing (decide_route_endpoint_candidates). Multi-model orchestration runs in the gateway |
| Storage abstraction | store_protocol.py (~97 methods), storage.py (InMemoryStore, STORE proxy, create_store), storage_gcp.py (7.4k), storage_postgres.py (7.9k), storage_models.py, storage_errors.py |
Store protocol. The tests/conformance/ suite is the executable contract across the memory, postgres and spanner-pg backends |
| Analytics | analytics_sink.py, storage_gcp_operational_analytics_outbox.py, storage_operational_analytics.py, operational_analytics*.py, provider_analytics.py, clickhouse/ (top-level drain/rollup jobs) |
A durable Spanner outbox feeds ClickHouse with one-way surrogate IDs and no content. Best-effort benchmark sink. Rollups and Parquet archive |
| Broadcast | storage_broadcast.py, services/broadcast*.py, routes/broadcast.py, routes/internal/broadcast_queue.py |
Customer-configured PostHog/OTLP export, metadata-only unless include_content is set (and then only the enclave gets the destination) |
| Billing and payments | services/stripe_billing.py, adyen_billing.py, paypal_billing.py, lightning.py, x402_billing.py, routes/billing.py, routes/internal/webhook.py, credit_transfer.py, partner_billing.py, custom_model_billing.py, app_markup_billing.py |
Top-ups and idempotent webhook crediting. App and creator markups, payouts |
| Trust and fraud | trust_tiers.py, trust_*.py, *_trust_history.py, verification*.py, phone_verification.py, veriff_verify.py, signup_gate.py |
Account trust tiers from payment, refund and dispute history. Identity verification gates |
| Custom / user models | custom_model_rules.py, user_model_rules.py, storage_*custom_models.py, storage_*user_models.py, services/user_model_*.py |
Custom models (hidden prompt plus markup) and user-provided models (BYO endpoint with owner payouts) |
| Web UI / public site | dashboard.py (6.6k), homepage.py, routes/console/*, routes/public.py, templates/, static/, seo_*.py, og.py, markdown_negotiation.py, content/, frontend/ |
Server-rendered Jinja console and marketing site, docs, llms.txt, markdown for agents |
| Synthetic monitoring and ops | synthetic/* (probes, status, leaderboard, remediator, drift), routes/internal/synthetic.py, sentry_config.py, axiom_config.py |
In-process probe loop, /status, burn-rate SLOs (router_core vs control_plane), remediator |
| Media | image_generation.py, speech.py, transcription.py, video_billing.py, storage_*video_jobs.py, routes/internal/video_jobs.py |
Route types images, audio.speech, audio.transcriptions, videos, with their own reservation rules |
| Growth / marketing | acquisition.py, marketing_*.py, google_ads_*.py, social_rewards*.py, competitor_comparisons.py, token_exchange.py |
Funnel attribution, ads conversions, comparison pages |
Component diagram (control plane internals)
flowchart TB
subgraph MW["middleware.py register_http_middleware"]
m1["UnreadRequestBodyClose"] --> m2["security headers, CSP report-only"] --> m3["rate_limit, process-local"] --> m4["read_only guard"] --> m5["request_id"] --> m6["internal auth before body"] --> m7["GZip, MarkdownNegotiation"]
end
subgraph R["Routers, selected by TR_SERVICE_SURFACE"]
pub["routes/public.py, catalog"]
con["routes/console/*, keys, billing, byok, oauth"]
gwr["routes/internal/gateway.py<br/>authorize, heartbeat, settle, refund"]
inf["routes/inference.py<br/>local and test only"]
hooks["routes/internal/webhook.py<br/>Stripe, Adyen, PayPal"]
end
auth["auth.py principal_from_request"]
rt["routing.py and routing_candidates.py"]
cat["catalog.py, catalog_registry.py, catalog_privacy.py<br/>MODELS, MODEL_ENDPOINTS built at import"]
rs["routing_state.py ROUTING_STATE ranking hints"]
fpc["services/fastpath_frontdoor.py"]
store["storage.py STORE proxy implementing store_protocol.Store"]
gcp["storage_gcp.py SpannerStore<br/>storage_gcp_authorize.py one-txn authorize"]
pg["storage_postgres.py PostgresStore"]
mem["storage.py InMemoryStore"]
sob["storage_gcp_settle_outbox.py"]
oa["operational analytics outbox"]
bc["broadcast outbox"]
sink["analytics_sink.py best-effort ClickHouse sink"]
provs["providers.py and provider_adapters.py<br/>local inference only"]
MW --> R
con --> auth
inf --> auth
gwr --> rt
inf --> rt
rt --> cat
gwr --> rs
gwr --> fpc
gwr --> store
inf --> provs
inf --> store
con --> store
hooks --> store
store --> gcp
store --> pg
store --> mem
gwr --> sob
gcp --> oa
gwr --> bc
gwr --> sinkDiagram source
flowchart TB
subgraph MW["middleware.py register_http_middleware"]
m1["UnreadRequestBodyClose"] --> m2["security headers, CSP report-only"] --> m3["rate_limit, process-local"] --> m4["read_only guard"] --> m5["request_id"] --> m6["internal auth before body"] --> m7["GZip, MarkdownNegotiation"]
end
subgraph R["Routers, selected by TR_SERVICE_SURFACE"]
pub["routes/public.py, catalog"]
con["routes/console/*, keys, billing, byok, oauth"]
gwr["routes/internal/gateway.py<br/>authorize, heartbeat, settle, refund"]
inf["routes/inference.py<br/>local and test only"]
hooks["routes/internal/webhook.py<br/>Stripe, Adyen, PayPal"]
end
auth["auth.py principal_from_request"]
rt["routing.py and routing_candidates.py"]
cat["catalog.py, catalog_registry.py, catalog_privacy.py<br/>MODELS, MODEL_ENDPOINTS built at import"]
rs["routing_state.py ROUTING_STATE ranking hints"]
fpc["services/fastpath_frontdoor.py"]
store["storage.py STORE proxy implementing store_protocol.Store"]
gcp["storage_gcp.py SpannerStore<br/>storage_gcp_authorize.py one-txn authorize"]
pg["storage_postgres.py PostgresStore"]
mem["storage.py InMemoryStore"]
sob["storage_gcp_settle_outbox.py"]
oa["operational analytics outbox"]
bc["broadcast outbox"]
sink["analytics_sink.py best-effort ClickHouse sink"]
provs["providers.py and provider_adapters.py<br/>local inference only"]
MW --> R
con --> auth
inf --> auth
gwr --> rt
inf --> rt
rt --> cat
gwr --> rs
gwr --> fpc
gwr --> store
inf --> provs
inf --> store
con --> store
hooks --> store
store --> gcp
store --> pg
store --> mem
gwr --> sob
gcp --> oa
gwr --> bc
gwr --> sink5. Request lifecycle: production /v1/chat/completions
5.1 Step by step
-
Client → enclave. The client calls
https://api.trustedrouter.com/v1/chat/completionswithAuthorization: Bearer sk-tr-v1-…. TLS terminates inside the Confidential Space enclave. The enclave parses the body, derivesestimated_input_tokens,input_modalitiesandrequested_parameters, and hashes the API key. The raw key and the prompt stay inside the enclave. -
Enclave → control plane
POST /internal/gateway/authorize(routes/internal/gateway.py:2492). Body isGatewayAuthorizeRequest(src/trusted_router/schemas.py:366-431):api_key_lookup_hash,model,models,provider, token estimates,stream,region,tags,metadata,idempotency_key,route_type, and so on. - Middleware:internal_auth_before_body_middlewarechecks the internal token before the body is read (middleware.py:153-161,_internal_auth_before_bodyatmiddleware.py:473). Then request id and the process-local rate limit. - Per-key admission:_AUTHORIZE_ADMISSION.try_acquire(subject)returns 429 when one key has too many authorizations in flight. The handler then moves to a worker thread (gateway.py:534-587). All gateway storage IO is synchronous and runs throughrun_in_threadpool. - Internal auth:require_internal_gatewaycompares the bearer orx-trustedrouter-internal-tokenwithTR_INTERNAL_GATEWAY_TOKENin constant time, and is fail-closed outside local/test (routes/internal/_shared.py:53-65). - Key + workspace snapshot (gateway.py:856-940): one folded read loads the key, workspace, BYOK rows and boot record. It rejects disabled or expired keys (401) and wrong-scope keys, and verifiesX-TR-Boot-Authif present (gateway_boot.py). The workspace must be billing-active (assert_workspace_billing_active). Tags merge from the key and the request. - Idempotency replay: a matchingidempotency_keyplusrequest_fingerprintreturns the original authorization, which prevents double reservation (gateway.py:~1200-1320). - Model resolution and routing:normalize_routing_inputs(body_dict, settings, resolved_region)(gateway.py:1321,routing.py:926) givesNormalizedRoutingInputs(model_ids, preferences).provider_route_preferences(routing.py:633-746) validates everyprovider.*field. An unknown field returns 400 with the exact name.min_privacymaps throughPRIVACY_TIER_ALIASES(catalog_data.py:61-89):any/standard=0,no_store=1,zdr=2,confidential/e2e/e2ee=3. A typo is a 400 so the request can't silently weaken.provider.zdr=trueadds the ZDR requirement.- Meta-model aliases (
trustedrouter/auto,zdr,e2e,eu, tiers, …) expand to ordered pools (routing_candidates.py:138-420).trustedrouter/zdrandtrustedrouter/e2ecarry implicit privacy floors (ROUTING_MODEL_MIN_PRIVACY_TIERS). - Dispatch is by route type: chat →
chat_route_endpoint_candidates(routing.py:253-289), or embeddings, decide (fusion/selector), image, speech, transcription, video, fetch, user model (gateway.py:~1410-1470). - Privacy filter:
_apply_endpoint_provider_filterskeeps only endpoints whereendpoint_meets_privacy_requirement(endpoint, req)holds for every requirement (catalog_privacy.py:231-251). Confidential needs verified confidential compute and provider E2EE and explicit ZDR, and never the model's own vendor. Unknown values fail closed. If nothing passes, the request gets a 400 ("No route candidates match the requested provider filters"). It never falls back to a weaker route. data_collection=denyis a soft filter. It is relaxed when it would empty the set (_filter_candidates_soft_data_collection,routing.py:~1080-1106).min_privacyis never relaxed.- BYOK eligibility per candidate (
_byok_configs_for_candidates,_eligible_gateway_endpoint_candidates), service-tier filtering, thenROUTING_STATE.rank(...)reorders using process-local latency/throughput hints (routing_state.py:71-179; hints only, never an authority).allow_fallbacks=falsetrims to one candidate (gateway.py:~1580-1597). - Estimate:
_endpoint_cost_microdollarsover the candidates (the max across candidates when auto fallback is possible), plus receipt fee, custom-model markup, app markup and additional hosted-tool cost (gateway.py:~1595-1690). - Fast path pilot: for pilot workspaces with credit-only candidates,
_fastpath_admissioncalls the Go front door. If admitted, the typed store reserves nothing and the lease holds the money (gateway.py:~1820-1835,5087). - Reserve (the money step):
_typed_store.authorize_gateway_typed(...)(gateway.py:~1866,storage_gcp.py:5479, implemented instorage_gcp_authorize.py). One Spanner read-write transaction runs the idempotency read, then conditional credit DML on a credit shard, then pause/strict-budget checks, then conditional key-cap DML, then thetr_reservationINSERT, then thegateway_authorizationINSERT (storage_gcp_authorize.py:1-14). Insufficient balance returns 402. If a peer plane has deferred settlement enabled, a local refusal may fall back to the home plane. - Response:
GatewayAuthorizeData(schemas.py:325-348) withauthorization_id, prospectivegeneration_id,route_candidates(provider, endpoint, upstream model, inference location, BYOK ciphertext and cache key),regions,broadcast_destinations,usage_type, timing phases (key_lookup_ms,routing_ms,store_ms,post_commit_ms).
-
Enclave → provider. The enclave tries
route_candidatesin order. A failure before any output token moves on to the next candidate, and it recordsroute_fallbacksandroute_failures. It streams SSE back to the client and counts tokens. -
Optional
POST /internal/gateway/heartbeat(Stage D,gateway.py:611-680). Boot-signed usage heartbeats during long streams. If the final settle is lost, the reaper can book areaped_snapshotcharge from the frozen Stage D pricing snapshot (stage_d.py) instead of releasing the hold for free. Eligibility depends on a signed accepted-image policy (stage_d_policy.py). -
Enclave →
POST /internal/gateway/settle(gateway.py:2552) withGatewaySettleRequest(schemas.py:566+):authorization_id, actual input/output/cache/reasoning tokens,selected_model/selected_endpoint,finish_reason,streamed,elapsed_seconds,first_token_seconds,route_fallbacks, and client context. -AsyncSettlementRoute(routes/settlements.py) intercepts only when async-settle protection is on and the gateway sentX-TR-Settlement-Mode: async-v1|sync. Otherwise it is the legacy handler. -_settle_gateway_with_admission_sync(gateway.py:3721) applies per-key settle admission (503 +Retry-Afterwhen overloaded). Then_settle_gateway_authorization(gateway.py:3776):- One-shot: a settle on an already-settled authorization returns
already_settledand never charges twice (_already_settled_gateway_data,gateway.py:5271). - Prompt-cache normalization (
normalized_prompt_accounting,services/settle_outbox_apply.py:112). Anthropicinput_tokensexclude cached tokens, while OpenAI and Gemini prompt counts include them. - Actual cost in microdollars from the authorized endpoint's frozen terms, then a
Generation.from_settle_body(...)metadata record. No content. - Durable settle outbox (
TR_SETTLE_OUTBOX_ENABLED=truein prod): enqueue the settle intent first (storage_gcp_settle_outbox.py), thentyped_finalize_gateway_authorizationatomically books usage, releases the unused hold, writes the boundedtr_generationrecord and the activity/analytics outbox rows, and marks the outbox row done. If finalize fails, the outbox drain (services/settle_outbox_drain.py) replays it. Deterministic generation IDs make replays idempotent. - Post-commit side effects: auto-refill scheduling (Stripe, owned by the
controlsurface), creator/app/custom-model payouts, metadata broadcast enqueue, and benchmark samples to the best-effortanalytics_sink(analytics_sink.py, a bounded queue that drops rows rather than block). - Fast path authorizations are settled through
_settle_leased_authorization(gateway.py:5181).
- One-shot: a settle on an already-settled authorization returns
-
Refund (
POST /internal/gateway/refund,gateway.py:2561) uses the same_settle_gateway_authorization(success=False). It releases the reservation and key-cap hold after a provider failure or client disconnect before output. Expired holds are reaped (storage_gcp_authorize.py:1224), and that is why the settle outbox and Stage D exist. -
Analytics. Spanner outbox rows are drained into ClickHouse by the
clickhouse/ingest_*jobs, using one-way workspace/key surrogates (storage_gcp_operational_analytics_outbox.py:1-12). ClickHouse failures never affect inference or settle (docs/clickhouse-reliability.md).
5.2 Sequence diagram (streaming chat with settle)
sequenceDiagram
autonumber
participant C as Client SDK
participant G as Attested gateway quill-cloud-proxy
participant CP as Control plane internal surface
participant RT as routing.py and catalog
participant FP as fastpath front door pilot
participant SP as Spanner
participant P as Upstream provider
participant CH as ClickHouse
C->>G: POST /v1/chat/completions stream true, Bearer sk-tr
Note over G: TLS ends in enclave, key hashed, tokens estimated
G->>CP: POST /internal/gateway/authorize key hash, model, provider prefs, estimates
CP->>CP: internal token check, per-key admission, run_in_threadpool
CP->>SP: folded read of key, workspace, BYOK, boot
CP->>RT: normalize_routing_inputs, min_privacy filter, sort, rank
RT-->>CP: ordered route candidates or 400 fail closed
opt pilot workspace
CP->>FP: admit under lease
FP-->>CP: admitted or off, busy, invalid
end
CP->>SP: one RW txn: credit shard DML, key cap DML, tr_reservation, authorization
SP-->>CP: committed or 402 insufficient credits
CP-->>G: authorization_id, generation_id, route_candidates, BYOK ciphertext
loop candidates until first token
G->>P: provider request with operator key or decrypted BYOK
P-->>G: error before output, so try next candidate
end
P-->>G: SSE chunks
G-->>C: SSE chunks then DONE
opt Stage D long stream
G->>CP: POST /internal/gateway/heartbeat boot signed usage
end
alt success
G->>CP: POST /internal/gateway/settle actual tokens, selected endpoint, timings
CP->>SP: enqueue settle outbox intent
CP->>SP: typed finalize: book usage, release remainder, generation record, analytics outbox
CP-->>G: settled, or already_settled on retry
else provider failure or disconnect
G->>CP: POST /internal/gateway/refund
CP->>SP: release reservation and key cap hold
end
CP-)CH: outbox drain jobs and best-effort benchmark sinkDiagram source
sequenceDiagram
autonumber
participant C as Client SDK
participant G as Attested gateway quill-cloud-proxy
participant CP as Control plane internal surface
participant RT as routing.py and catalog
participant FP as fastpath front door pilot
participant SP as Spanner
participant P as Upstream provider
participant CH as ClickHouse
C->>G: POST /v1/chat/completions stream true, Bearer sk-tr
Note over G: TLS ends in enclave, key hashed, tokens estimated
G->>CP: POST /internal/gateway/authorize key hash, model, provider prefs, estimates
CP->>CP: internal token check, per-key admission, run_in_threadpool
CP->>SP: folded read of key, workspace, BYOK, boot
CP->>RT: normalize_routing_inputs, min_privacy filter, sort, rank
RT-->>CP: ordered route candidates or 400 fail closed
opt pilot workspace
CP->>FP: admit under lease
FP-->>CP: admitted or off, busy, invalid
end
CP->>SP: one RW txn: credit shard DML, key cap DML, tr_reservation, authorization
SP-->>CP: committed or 402 insufficient credits
CP-->>G: authorization_id, generation_id, route_candidates, BYOK ciphertext
loop candidates until first token
G->>P: provider request with operator key or decrypted BYOK
P-->>G: error before output, so try next candidate
end
P-->>G: SSE chunks
G-->>C: SSE chunks then DONE
opt Stage D long stream
G->>CP: POST /internal/gateway/heartbeat boot signed usage
end
alt success
G->>CP: POST /internal/gateway/settle actual tokens, selected endpoint, timings
CP->>SP: enqueue settle outbox intent
CP->>SP: typed finalize: book usage, release remainder, generation record, analytics outbox
CP-->>G: settled, or already_settled on retry
else provider failure or disconnect
G->>CP: POST /internal/gateway/refund
CP->>SP: release reservation and key cap hold
end
CP-)CH: outbox drain jobs and best-effort benchmark sink5.3 The local/test path (same semantics, no enclave)
In local/test, routes/inference.py mounts /chat/completions and the other inference routes
(routes/inference.py:150-420):
InferencePrincipal→require_inference_key→principal_from_request(auth.py:90-177,auth.py:361).provider_route_preferences+chat_route_candidatesorchat_route_endpoint_candidates, then_local_routes_meeting_privacy_floor(routes/inference.py:106).- More than one candidate, or
trustedrouter/auto, means meta-route rollover (run_chat_candidatesor_candidate_stream_bytes). Otherwise a single candidate (run_chatorrun_chat_stream). services/inference.py:_run_stream(services/inference.py:162-218) opensreserved_quota(...)(services/inference_quota.py:84), which reserves the key limit and then credits. It streams fromProviderClient.stream_chat, computes the actual cost, callsticket.settle(actual_cost), and runsSTORE.add_generation(...). An exception refunds (QuotaTicket,inference_quota.py:42-80).- Live upstream calls happen only with
TR_ENABLE_LIVE_PROVIDERS=true(default false,config.py:486) and keys from the local keys file.
5.4 Anthropic /v1/messages
- Client config:
ANTHROPIC_BASE_URL=https://api.trustedrouter.com(no/v1, the SDK adds it),ANTHROPIC_API_KEY=sk-tr-…(README.md:62-64). - Production: the enclave serves
/v1/messages. The control-plane contract is the sameauthorize/settle/refund. Model and provider filters go through the samechat_route_endpoint_candidatesand privacy logic. The settle path normalizes Anthropic cache accounting, whereinput_tokensexcludescache_read_input_tokensandcache_creation_input_tokens(schemas.py:574-578). Coverage registry:openrouter_coverage.py:99. - Local/test (
routes/inference.py:307-351):_require_messages_model(:929), then the privacy floor, thenmessages_to_chat_body(adapter.py:83-99), which turns Anthropicsysteminto a leading system chat message and copies allowed params. The request then runs on the samerun_chat/run_messages_stream, and the result is wrapped by_anthropic_messages_envelope(routes/inference.py:506). On the outbound side,chat_to_anthropic(adapter.py:102+) rebuilds Anthropic payloads for the Anthropic provider adapter (provider_adapters.py:338-465). /v1/responsesworks the same way throughresponses_to_chat_bodyand_responses_api_envelope.
6. Control plane vs attested gateway vs fastpath/
flowchart LR
subgraph ENC["Attested gateway, quill-cloud-proxy"]
e1["TLS termination and attestation endpoint /attestation"]
e2["Parse request, estimate tokens, hash key"]
e3["Provider calls, fallback loop, SSE streaming"]
e4["Decrypt BYOK in memory, short TTL cache"]
e5["Inference receipts signed with boot key"]
end
subgraph CPL["Control plane, this repo"]
c1["Key and workspace validation"]
c2["Model, alias and privacy routing decision"]
c3["Reserve, settle, refund in Spanner"]
c4["Generation records, activity, analytics outbox"]
c5["Console, billing, catalog, status, trust page"]
end
subgraph FPL["fastpath, Go, pilot"]
f1["Front door admits under regional lease"]
f2["Owner and auditor batch settlement"]
f3["TLA plus shadows in proofs/"]
end
e2 -- "authorize" --> c1
c1 --> c2 --> c3
e3 -- "settle or refund" --> c3
c3 --> c4
c3 -- "pilot workspaces only" --> f1
f1 --> f2Diagram source
flowchart LR
subgraph ENC["Attested gateway, quill-cloud-proxy"]
e1["TLS termination and attestation endpoint /attestation"]
e2["Parse request, estimate tokens, hash key"]
e3["Provider calls, fallback loop, SSE streaming"]
e4["Decrypt BYOK in memory, short TTL cache"]
e5["Inference receipts signed with boot key"]
end
subgraph CPL["Control plane, this repo"]
c1["Key and workspace validation"]
c2["Model, alias and privacy routing decision"]
c3["Reserve, settle, refund in Spanner"]
c4["Generation records, activity, analytics outbox"]
c5["Console, billing, catalog, status, trust page"]
end
subgraph FPL["fastpath, Go, pilot"]
f1["Front door admits under regional lease"]
f2["Owner and auditor batch settlement"]
f3["TLA plus shadows in proofs/"]
end
e2 -- "authorize" --> c1
c1 --> c2 --> c3
e3 -- "settle or refund" --> c3
c3 --> c4
c3 -- "pilot workspaces only" --> f1
f1 --> f2| Concern | Attested gateway | Control plane | fastpath |
|---|---|---|---|
| Prompt and output bytes | Handles them, never logs them | Never (prod) | Never |
| Who chooses the provider | Executes the ordered candidates and decides fallback at runtime | Decides the candidate set and order, applies privacy floors | — |
| Money | Reports actual usage | Authoritative reserve/settle/refund | Lease admission for pilot workspaces. Python still owns key, model, estimate, authorization row and generation record (services/fastpath_frontdoor.py:1-22) |
| Trust anchor | Hardware attestation, published image digest | Ordinary Cloud Run, Sentry/Axiom allowed | VPC-internal |
| Billing authority it trusts | Only https://trustedrouter.com is baked into the measured image (docs/design/fast-admission-pilot.md) |
— | Called by the control plane in the pilot, not by the enclave |
| Status | Live in us-central1, us-east4, europe-west4, us-west1 (README.md:436-445) |
4 Cloud Run regions behind a global LB | Pilot: TR_FASTPATH_PILOT_ENABLED=true for 2 workspaces (rollout.sh:630-632) |
The long-term design (docs/design/fast-admission-and-batched-settlement.md) has the enclave call
the fast path directly, removing the control plane from request latency. That has not been built.
7. Must-read files (in reading order)
| # | Path:line | Why |
|---|---|---|
| 1 | AGENTS.md:1 |
Hard rules: the three CI gates, the STORE monkeypatch trap, money-code test rules, Spanner query safety, auto-deploy on push to main |
| 2 | README.md:127 |
Trust boundary, internal gateway contract (:501-515), scale and multi-region shape |
| 3 | src/trusted_router/main.py:227 |
create_app(): surface-gated route registration, background loops, exception→HTTP mapping, inference gate at :912 |
| 4 | src/trusted_router/config.py:438 |
service_surface and the ~2.4k-line Settings. Every TR_* flag lives here |
| 5 | src/trusted_router/middleware.py:113 |
Middleware order and why (raw ASGI vs BaseHTTPMiddleware, internal auth before body) |
| 6 | src/trusted_router/auth.py:90 |
principal_from_request: credential precedence and sk-tr-/trsess- prefix dispatch |
| 7 | src/trusted_router/routes/internal/gateway.py:1 |
The production hot path. Authorize :534/:856, settle :590/:3776, refund :2561, route registration :2453 |
| 8 | src/trusted_router/schemas.py:366 |
GatewayAuthorizeRequest and GatewaySettleRequest (:566): the wire contract with quill-cloud-proxy |
| 9 | src/trusted_router/routing.py:633 |
provider_route_preferences: every OpenRouter routing knob, min_privacy parsing |
| 10 | src/trusted_router/routing.py:253 |
chat_route_endpoint_candidates and the filter/sort helpers (:1080-1300) |
| 11 | src/trusted_router/catalog_privacy.py:231 |
endpoint_meets_privacy_requirement: the exact meaning of ZDR and confidential |
| 12 | src/trusted_router/catalog_data.py:61 |
Privacy tier constants and aliases. Meta-model IDs at :2472-2830 |
| 13 | src/trusted_router/catalog_registry.py:1 |
Import-time construction of MODELS/MODEL_ENDPOINTS and uniform pricing (money-critical) |
| 14 | src/trusted_router/routing_candidates.py:138 |
Meta-model pools: auto, zdr, e2e, eu, cheap, monitor, tiers |
| 15 | src/trusted_router/storage_gcp_authorize.py:1 |
The one-transaction authorize. Reaper at :1224 |
| 16 | src/trusted_router/storage.py:3880 |
_StoreProxy/STORE (:3947) and create_store (:3976). Backend selection |
| 17 | src/trusted_router/store_protocol.py:1 |
The Store contract: reserve :913, settle :922, authorize_gateway_typed :1162, typed_finalize_gateway_authorization :1215 |
| 18 | src/trusted_router/storage_gcp_settle_outbox.py:1 |
Durable settle outbox (exactly-once via INSERT-as-claim plus a lease fence) |
| 19 | src/trusted_router/routes/inference.py:150 |
Local inference routes. The easiest way to see the whole lifecycle in one process |
| 20 | src/trusted_router/services/inference_quota.py:84 |
reserved_quota / QuotaTicket: reserve→settle/refund in miniature |
| 21 | src/trusted_router/adapter.py:1 |
Chat ↔ Messages ↔ Responses conversions |
| 22 | src/trusted_router/providers.py:142 |
ProviderClient and its adapters (provider_adapters.py) |
| 23 | src/trusted_router/gateway_boot.py:1 |
Enclave boot registration and per-request X-TR-Boot-Auth signatures |
| 24 | src/trusted_router/services/fastpath_frontdoor.py:1 |
Fast path pilot bridge and its fallback semantics |
| 25 | src/trusted_router/money.py:1 |
Microdollar units and signup credit. Never floats |
| 26 | src/trusted_router/analytics_sink.py:1 |
Why analytics must never affect a request |
| 27 | src/trusted_router/routes/chat_proxy.py:1 |
The one place prompt bytes cross the control plane (browser playground) |
| 28 | docs/storage-portability/multi-cloud-separation.md:1 |
Identity federates, credits do not |
| 29 | docs/design/durable-settle-outbox.md:1 |
Why a lost settle used to mean free inference |
8. Glossary
| Term | Meaning |
|---|---|
| Workspace | The billing and tenancy unit. Owns credits, API keys, BYOK configs and members. api_key.workspace_id is the trusted attribution, never a header (gateway.py:890) |
| Management key vs inference key | Key scopes. Signup issues a one-time management key. Inference requires an inference-scoped key (auth.py:312-345, scopes.py) |
| Lookup hash / key hash | API keys are stored as salted SHA-256. A separate high-entropy lookup hash allows point reads. The enclave sends hashes, not raw keys |
| Microdollars | Integer money unit, 1 USD = 1,000,000 (money.py:5). All ledger math is integer |
| Reserve | Pre-authorization hold of the estimated cost on workspace credit (and key cap) before the provider call. In prod it is one Spanner transaction inside authorize |
| Authorization | The gateway_authorization row created at authorize. Carries frozen terms, candidates and the reservation id. Its terminal dispositions are settled, refunded, reaped_snapshot, and so on |
| Settle | Book actual usage against the authorization. Releases the unused remainder and records the generation. One-shot and idempotent |
| Refund | Release a reservation without charging (provider failure, disconnect) |
| Reaper | Background release of expired holds. Historically it released without charging, which is why the settle outbox and Stage D snapshots exist |
| Settle outbox | Durable Spanner table recording settle intents so an interrupted settle can be replayed exactly once |
| Async settle (v1) / shadow-v2 | A planned mode where the enclave's settle returns fast and finalization is deferred. It currently only collects shadow evidence (flag off) |
| Fast path / lease | Go service (fastpath/) that admits requests against a pre-granted regional credit lease and batches settlement. Pilot only |
| Stage D | Heartbeat cohort. Boot-signed enclaves send usage heartbeats so long streams can be billed from a frozen pricing snapshot if the final settle is lost |
| Boot / boot key | Per-boot enclave identity registered at /internal/gateway/spend-lease/register-boot. Signs authorize, heartbeat and disposition requests, plus inference receipts |
| Inference receipt | Client-verifiable signed receipt from the enclave. A receipt fee (basis points) is frozen on the authorization |
| BYOK | Bring Your Own Key: a customer's provider key, envelope-encrypted (KMS-wrapped DEK). The enclave gets the ciphertext and decrypts it in memory |
| ZDR | Zero Data Retention, min_privacy=zdr (tier 2): the endpoint has an explicit zero-retention policy |
| No-store | Tier 1: the provider does not store request or response content |
| Confidential / E2E / E2EE | Tier 3: verified confidential compute and provider-side E2EE and explicit ZDR, never served by the model's own vendor. Fails closed |
| min_privacy | Hard request-level privacy floor in provider.min_privacy. Never relaxed |
| data_collection=deny | Soft retention preference, relaxed if it would leave no candidates |
| Meta-model | A router-defined virtual model (trustedrouter/auto, zdr, e2e, eu, cheap, free, monitor, frontier, fusion, selector, polyphemus-1.0, …) that expands to an ordered candidate pool or an orchestration |
| Candidate / route candidate | One (Model, ModelEndpoint) pair the gateway may try, in order |
| Endpoint | A specific provider deployment of a model, with price, region, privacy flags and capabilities |
| Surface | Process role selected by TR_SERVICE_SURFACE (combined, public, actions, control, internal, observer) |
| Home plane / peer plane | Multi-cloud: keys are issued on the home plane, and peers federate them lazily with zero local balance |
| Custom model / user model | Custom: a catalog model plus a hidden prompt and markup. User-provided: a BYO endpoint whose owner earns payouts |
| Broadcast | Customer-configured metadata export (PostHog, OTLP) through a durable outbox |
| router_core vs control_plane SLO | Status separates prompt-path availability from dashboard/billing availability |
| Conformance suite | tests/conformance/, the executable Store contract across backends |
9. Gotchas
- Prod control plane does not serve inference.
_control_plane_inference_enabledreturns true only for local/test (main.py:912-919). If you debug a prod chat bug inroutes/inference.py, you are reading the wrong code. Prod goes through the enclave →routes/internal/gateway.py. /chat-proxy/v1/chat/completionsterminates TLS on the control plane. It forwards bytes unparsed to the attested API for the browser playground only (routes/chat_proxy.py:1-30). SDK traffic must never use it.- The
STOREproxy.monkeypatch.setattr(STORE, …)poisons later tests (once 136 failures). Patch the backend class (AGENTS.md:42-45).typed_billing_store()exists becauseisinstanceon the proxy lies (storage.py:3954-3968). - The catalog is built at import time.
MODELSandMODEL_ENDPOINTScome fromdata/*.jsonwhen the module loads. An hourly price-refresh bot commits tomain, so rebase before trusting a failure (AGENTS.md:47-49). - Money code needs differential/conformance tests. Run the proof oracle when touching
settle/billing:
uv run pytest -q -n 4 -m proof_oracle tests/test_async_settle_proof_oracle.py(AGENTS.md:39-40,81-87). - Async settle is huge and dormant. 98 modules (
async_settle_shadow_v2_*alone is 86) are shadow/evidence machinery behindTR_ASYNC_SETTLE_ENABLED=false. Do not mistake it for the live settle path, which is_settle_gateway_authorization. - Config is code. Cloud Run env vars come from the
ENV_VARSarrays inscripts/deploy/rollout.shand the companion surface scripts. Setting them by hand is overwritten on the next rollout. Pushingsrc/**to main auto-deploys (AGENTS.md:60-66). - Combined surface is fail-closed in deploys. A missing
TR_SERVICE_SURFACEdefaults tocombined, and that raises outside local/test unless the migration bridge flag is set (main.py:235-244,rollout.sh:12-15). - Gateway storage is synchronous. Every gateway handler runs in
run_in_threadpoolto avoid head-of-line blocking. AnyIO's 40-token pool is shared, which is safe only because Cloud Run concurrency is ~8 (gateway.py:2454-2467). - Rate limiting is process-local by design (
middleware.py:144-146). Fleet-wide limits belong at the front door. (The README's "rate limits use the configured store" line is out of date.) min_privacyvsdata_collection. The first is a hard filter and the second is soft. ZDR and confidential compute are separate dimensions: a TEE without a retention promise does not satisfy ZDR (routing.py:1131-1135).- Anthropic cache-token semantics differ. Anthropic's
input_tokensexcludes cached tokens, while OpenAI and Gemini include them. Settle normalizes this (schemas.py:574-578,settle_outbox_apply.py:112). - Never scan
tr_entities.bodyin prod Spanner, even read-only withLIMIT. Usescripts.inspect_payment_ownerand ClickHouse (AGENTS.md:70-79). A body scan caused the September 6 CPU incident. - The README mentions Bigtable for activity. The current write path is the Spanner
tr_generationbounded record plus the Spanner analytics outbox → ClickHouse (storage_gcp_generation_records.py:1-6,docs/storage-portability/analytics-ingestion.md). Treat Bigtable references as historical unless the code says otherwise. - Unknown routing fields return 400 and unimplemented OpenRouter fields return 501
not_supported_in_alpha. Nothing is silently ignored (README.md:334-336). - Each cloud is its own TrustedRouter. An AWS-issued key does not work on GCP, and credits
don't move without an explicit transfer (
multi-cloud-separation.md). - Analytics must never affect money. The benchmark sink drops rows when its queue is full, and
the outbox enqueue rides the settle commit only on the one-commit happy path (
AGENTS.md:83-87,analytics_sink.py:1-25).