| title | The proxy - faithful credential-injecting relay + tool resolution | |||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| status | shipped | |||||||||||||||||||||||||||||||||||||||||
| sources |
|
|||||||||||||||||||||||||||||||||||||||||
| related |
|
application.call.service orchestrates resolution, authorization, reservation, relay and
finalization. application.call.resolve selects the target and credential;
infra.upstream.relay.relay only injects credentials and forwards bytes; treg never models an
upstream API. The contract itself is AGENTS.md non-negotiable 4; this fragment maps it to code.
Two operator admissions sit on this road and are specified elsewhere:
published Org flow control runs in run_call_surface after identity and
before money admission (root calls only; routing children take no extra slot or token), and the
exact org/endpoint allowance runs inside the call application after
canonical resolution and money admission, immediately before a cache hit is returned or a
direct/overflow request is sent.
Named catalog calls with authorization metadata select the provider and grant method before
comparing hosts, so two providers sharing one API host (Facebook and Instagram on
graph.facebook.com) resolve to different tools; the relay stays provider-agnostic.
A live-verified free GET catalog row can declare platform_auth: anonymous. A team tool or team
credential still wins. Otherwise _anonymous_offer requires the deployment's provider allow-list
and creates an unmetered virtual tool with no bindings; relay() then injects nothing and strips or
rewrites no caller header. The catalog price is free only when the caller supplies no provider
credential header. No provider-specific anonymous path rules exist in the resolver or relay.
Archive participation (own-key recording, rollout, tool_called cache fields) is
archive's subject; own-tool calls never touch the archive.
Everything not listed here is relayed verbatim: method, path, every query pair including duplicates, caller headers, caller cookies, body bytes.
Inside relay():
- Hop-by-hop headers (
_HOP_BY_HOP, plus any header the caller'sConnectionheader names, RFC 7230) are dropped and re-derived per hop. One value is carried, not re-derived: when the caller declared aContent-Lengthfor a body, the same value is set upstream. Without it httpx frames a streamed bodyTransfer-Encoding: chunked, which Meta's Graph API edge does not read (the body arrives empty). A caller who streamed chunked stays chunked. A request body is carried only when the caller sent one, so a GET gets no chunked frame. - treg's control and edge headers are dropped: every
x-treg-*header by prefix (so a future control header is covered without being listed),_CONTROL(ngrok,x-forwarded-*,x-real-ip,forwarded,via, Cloudflare'scf-*family,true-client-ip,cdn-loop) and whateverTREG_CLIENT_IP_HEADERnames. The MCP layer re-sends the caller's address in that header on in-process calls; a Cloudflare-fronted provider answers any request still carrying it with 403 error 1000._scrub_treg_cookiesremoves treg's own cookies (treg_session,treg_oauth_state) fromCookie, keeping the rest: the dashboard'scredentials:'include'Try-it would otherwise leak the session. Accept-Encodingbecomesidentitywhen the caller sent none (the body is relayed raw withaiter_raw, and httpx would otherwise add its owngzip). Withforce_identityit is overridden toidentitywherever the application will read the body: metered settlement, an owned free poll, and an archivable own-key answer.- Query pairs consumed into catalog path placeholders (
drop_params) are not also sent as query. - The injected credential(s). Each binding overwrites only its target header, query parameter
or top-level JSON field. A JSON binding is the only case that buffers and reserializes the body:
formatting changes, non-ASCII is emitted as UTF-8, duplicate keys fail closed, and it must never
be used where the upstream signs or hashes the raw body. On a connection to treg's own Meta app
the credential also carries
appsecret_proof, replacing any caller value (auth-secrets).
Before relay(), on the platform tier only (service):
relay.scope_shared_idempotency_keyreplaces the caller'sIdempotency-Keywith a digest of (org, label, and the full enforced pin for a pinned agent). Every org shares one provider account on treg's key; a provider honoring the header would otherwise return org A's job to org B, andresource_ownership.produceswould record it as B's. treg's own idempotency table already replays the caller's answer, so nothing is lost.relay.identify_as_tregreplaces the caller'sUser-Agentwithconfig.TREG_USER_AGENT. Providers' Cloudflare rules answer library defaults (Python-urllib) with 403 error 1010, and the account seen there is treg's. The shared client, the async-task poller and the capacity collectors send the same header by default.relay.tag_shared_clientsets the header the provider file names inclient_headertorelay.shared_client_code:t_+ the first 24 hex characters of HMAC-SHA256(TREG_CLIENT_ID_SECRET, provider + team id), replacing a caller value under that name. The provider can split usage per customer on treg's account without learning the team, and two providers' codes for one team differ. Unset secret or no declaration: nothing changes.- A catalog
ceiling_paramis appended toUpstreamRequest.query_items(see non-negotiable 4);relay()forwards it like any other pair.
A team's own key relays Idempotency-Key, User-Agent and the client header verbatim.
On the response, relay() drops hop-by-hop headers (keeping Content-Length on HEAD/204/304) and
any Set-Cookie for treg's own cookie names (fixation), and adds X-Content-Type-Options: nosniff
and Content-Security-Policy: sandbox so a browser navigating to /call/... never runs upstream
HTML under treg's origin. Every other upstream header, including repeated Set-Cookie, is relayed.
- Request headers are rebuilt into an
httpx.Headersmultidict from the raw pairs, preserving duplicates; injection assigns only the named header. - The query is merged onto the URL with
copy_add_param, never passed asparams=: httpx replaces a URL's existing query wheneverparamsis given, even empty, stripping a catalog path's own query.tests/test_relay_path_query.pypins it. - The path comes from
request.scope["raw_path"], not Starlette's decoded path param, so percent-encoding survives (npm's scoped publishPUT /@scope%2fname404s if%2fis decoded). - The body streams (
content=request.stream()), never buffered, except for a JSON binding and forX-Treg-Body-Encoding:_BodyDecodeMiddleware(bootstrap_http.py) decodes a base64/gzip body beforerelay(), so the upstream still receives plaintext (api). - The upstream call uses the shared long-lived
httpx.AsyncClient(app.state.http); keepalive is the biggest latency win. relay()returns a framework-neutralUpstreamResponse. The router's body wrapper and background task share one idempotent close, so full reads, disconnects, stream errors and cancellation close the upstream exactly once.- Every JSON re-encode goes through
treg/jsontext.py:json.loadsaccepts an escaped lone UTF-16 surrogate that a plainensure_ascii=Falsedump then refuses after the upstream answered.dumps/dumps_bytesfall back to ASCII escapes only for such a value. MCP is the one lossy surface: its pydantic serializer cannot write the escape, somcp._bodyreplaces an unpaired surrogate with U+FFFD rather than lose a paid answer.
relay() loops tool.bindings and calls injectors.inject(...) per binding, so one request may
carry several credentials. A format with no {secret} renders literally, which lets a binding
stamp a provider protocol constant (a required API-version header); it still references a secret
for validation and lifecycle and overwrites a caller value. A binding with platform_setting
instead of secret_id injects one of treg's own credentials from settings (the Google Ads developer
token), never stored in the org's secret store so a tenant cannot read or extract it; a missing
setting is a 502 (this server has no <setting> configured). Tier 4 copies the provider's
constant required_headers bindings, so a version pin is identical on own-key and platform calls.
Binding validity is checked at registration (_validate_bindings, also run by register_skill):
known injector, same-org secret, a format that renders with only {secret}, non-empty names, and
no duplicate target within one location. A call-time injector ValueError or an
httpx.RequestError becomes an audited 502.
The Google Ads conversion uploader (adsconv.py) spends treg's platform connection but never
reaches relay(); see ads-conversions.
Before a 4xx/5xx body reaches the caller, an idempotent replay or the evidence row,
evidence.redact_error_response decodes it (gzip/deflate, bounded; own-key calls mirror the
caller's Accept-Encoding) and replaces every spelling of every injected credential
(_secret_renderings, every account's value of a platform setting) with ***. Masking is exact
only: the admin-log pattern nets would rewrite ordinary fields like "key": "uniqueId". A
normalized re-check that still finds a credential, a credential that cannot be rendered, or a body
that cannot be decoded replaces the whole body with a response_redacted error. An unchanged body
keeps its bytes and encoding; a masked one is re-sent identity-encoded with a fresh
Content-Length. 2xx bodies are untouched, save the one member below.
A failed unmetered response is read whole (_read_whole_if_small, up to 8 MiB) so masking sees
every byte; one too large is replaced on a credentialed call and streamed untouched
(_peek_stream_head keeps a head for evidence) only when nothing was injected. Caller request
bodies on unmetered paths are kept for evidence only when Content-Length is declared and within
evidence._ERROR_CALLER_BODY_MAX; larger or chunked uploads keep streaming. Redaction order,
access and retention: data-model.
strip_reported_charge (TREG_STRIP_REPORTED_CHARGE, off by default) removes the provider's own
charge from a metered 2xx answer before the caller sees it (AGENTS.md non-negotiable 4). The member
is the one the row's settlement reads: reported_charge.path_of takes a v2 row's
settle.reported.path, or a row in today's spelling's cost.reported_charge.path under
response (the move scripts/catalog_v2.py makes), so both spellings strip alike; a
header-reported charge, a usage meter and a provider read hard-coded in _observed_cost_micro name
no member. reported_charge.without is pure: it finds the selected objects as paths.select
walks them, cuts every occurrence of the member out of each and keeps every other byte (indentation,
separators, escapes, number spellings); an answer that is not one UTF-8 JSON document, a path
selecting nothing or one ending in an index or a filter returns the bytes unchanged.
service._without_reported_charge applies it at one place: in the metered hand-off after
settlement, capacity signals, classification, the audit row, overflow and error masking, before
the idempotent store and the task record (POST /tasks/{call_id} compares the answer the caller
holds). A served archive hit and an overflow answer pass the same point; the archive recorded the
provider's bytes earlier, so a repeat hit settles from the charge it carries. X-Treg-Upstream-Failed
reads the provider's bytes. X-Treg-Keep-Reported-Charge (truthy) keeps the member; X-Treg-*
headers are never relayed, and a routed child inherits it with the rest of its parent's headers.
Non-2xx, encoded, streamed free fetches and spooled answers are never rewritten. GET /calls/{id}/result applies answer_for_caller to a metered row's archived bytes.
tests/test_reported_charge.py covers the function and both surfaces.
Resolution, authorization and reservation own short sessions. service._execute_call commits the
request's secret-loading session before reservation and again before relay or the live demo's
network call: zero checked-out request connections during upstream I/O, including
rate-smoothing waits. Settlement, first-call recording and idempotency storage use their own
sessions; expire_on_commit=False keeps loaded objects usable. OAuth refresh separates DB work
from token-endpoint I/O. Holding the request transaction through relay deadlocks a saturated pool
(each request holds a slot while its settlement waits for another);
tests/test_call_pool_discipline.py checks occupancy at the network boundary. Pool sizing:
deploy.
* /call/{rest:path} -> routers.call.call_tool() -> routers.call.run_call_surface() (shared
with /catalog/call/ and /table/, which differ only in finish: see table) ->
service.execute_call() -> resolve_call_target(...) -> ResolvedTarget(tool, upstream). Each
resolution use case owns and closes its read session.
Resolution is scoped to the caller's org (Tool.org_id == org_id), so orgs may reuse tool names and
hosts, and only same-org secrets load. Two shapes:
- URL-passthrough:
restis the real upstream URL._normalize_scheme()restores thehttps://a path param collapses tohttps:/. The tool is matched by host (Tool.host), then the longestbase_urlprefix at a path-segment boundary (.../v1must not match.../v10/...and inject the wrong credential). Lengths compare rstripped, so a trailing-slash duplicate is a real409. On a tie, a registry-provider-backed tool (binding to aSecretwith aprovider) beats a hand-registered one; only a genuine ambiguity409s, naming every caller-usable colliding tool and pointing at/call/<name>/<path>. No match is404. - Named:
rest = "<tool>/<path>", looked up byTool.name; upstream =base_url + path. No path relays the base URL itself without a trailing slash (Stripe404s/v1/charges/).
Passthrough candidates are filtered by the caller's ACL (_tool_usable) before the tiebreak, so
a tool the caller cannot see can neither cause a 409 nor win; this only narrows, and whatever
resolves still passes _require_tool_use. When ACL alone removed every match the answer is 403
naming only the host the caller supplied, never the hidden tool.
A named miss falls through, in order: an exact catalog endpoint, then, only for a top-level call
(child_of is None), a hub tool (<team-slug>.<name>). An own tool or catalog id always wins; a
hub tool never shadows either. Hub runs take a per-team hub_limits.slot() and refuse with
429 hub_busy past MAX_RUNS_PER_TEAM rather than queue (hub).
Named-miss diagnostics, all on the error path:
<catalog-id>/<path>(the own-tool shape on a catalog id) answers400naming the endpoint's parameter slots and the--queryform.- A dotted name sharing its first segment with a caller-usable own tool gets
hintanddid_you_meanpointing at/call/<tool>/<path>; for a real catalog id this is attached only if the credential ladder also dead-ends. Provider-local catalog near-id matching takes precedence. - A credential-ladder 404, or a 410 tombstone without
superseded_by, includes_capability_alternatives(ep): same-capability endpoints, cheapest first, marked callable on treg's key or own-credential-only. Synchronous, no DB reads; it suggests, never substitutes.
* /catalog/call/{rest:path} (catalog-only MCP surfaces, gated on claude_connector_enabled) sets
catalog_only and accepts only an exact catalog endpoint id, never calling resolve_call_target,
so a private team tool or passthrough path cannot shadow the catalog entry. Everything after
resolution is shared.
A live catalog row enters _resolve_marketplace_call and its credential ladder.
_marketplace_upstream percent-encodes placeholder values but preserves a value already containing
a valid %HH escape (an encoded Search Console property id must not become %253A). Raw @ stays
literal: it is legal in a path segment and some email-path APIs reject %40. Slashes, query and
fragment delimiters and invalid % stay escaped. A catalog host on a provider that opted into
catalog_targets must resolve through OAuthProvider.profile_for_catalog_host to an exact
approved HTTPS base URL, which keeps its path prefix and supplies the binding; anything else is a
treg 502 before reserve, so catalog data cannot redirect an injected credential.
A retired/broken tombstone is refused with 410, its status_note and optional
superseded_by, before credentials are selected (audited refused_by=retired). An org's own tool
named like the old id already resolved above and is not shadowed.
strict_query rows (_enforce_catalog_query) reject bodies, undeclared or duplicate query
parameters, missing required inputs and unsupported enum values; declared path placeholders pass
and are consumed. body_allowlist rows (_enforce_catalog_body) reject undeclared top-level JSON
fields, missing required fields, invalid declared scalars and arrays outside their declared
cardinality or item enum (an array of objects may require all or any named item fields; an omitted
optional array is allowed). Both apply on every tier and never to raw own-tool relays.
After resolution application.call.authorize runs tool/project ACL, deny, member cap and
public-demo gates in that order, with no hold and no upstream access, and closes its session before
reserve.
_enforce_deny matches the resolved upstream against the org's DenyRule rows, so both call
shapes are equally gated, and the relay never follows redirects, so a blocked host is not reachable
through a 3xx. A provider's old base_url stays in legacy_base_urls: the call is also checked as
if sent there, and catalog tier 1 still finds a team tool registered on it. Path matching is
anchored at a segment boundary. Rules apply to every role including owner (a guardrail, not a
permission tier). A rule with project_id fires only on that project's tools; host/path/method,
member and project are ANDed, each NULL-means-any. _deny_match is pure and mirrors
localrun.check_deny (argv instead of URL).
Just before send, relay() re-resolves the upstream host (host_is_public, gated by
proxy_ssrf_check) and refuses with 502 if any address is non-public: a base_url public at
registration can later resolve to 169.254.169.254 or localhost. Registration
(ssrf.safe_webhook_url) also normalizes numeric IP encodings (2130706433, 0x7f000001,
127.1) through inet_aton before the literal-IP block. Targets must be globally routable
unicast. CGNAT 100.64.0.0/10 is internal service space (Tailscale, WireGuard overlays, Fly.io,
some Kubernetes pod CIDRs, Alibaba Cloud's metadata endpoint 100.100.100.200). NAT64 prefixes
mapping non-global IPv4 do not make a target public; 64:ff9b::/96 stays blocked. A narrow
resolve-vs-connect race remains; closing it needs a custom transport pinning the resolved IP.
Treg refusals carry X-Treg-Error: 1. Application failures map to
caller | treg | upstream | org_connection blame; provider failures remain response data. Header
contracts: api.
_audit records each attempt and mirrors it through _tool_called_props / analytics.capture
(never params or bodies). domain.catalog.results.classify shares hit/miss rules between telemetry
and cache admission and inspects only already-buffered bodies
(archive result admission). Overflow keeps both attempt rows under
one call reference but emits one product event (defer_analytics).
Exceptional exits are recorded once, guarded by the audit marker:
| Exit | Event |
|---|---|
| Pool timeout before caller identity | call_intake_failed, surface only, no team/target |
| Pool timeout after identity | tool_called, outcome=gateway_failed, failure_kind=db_pool |
Unexpected exception while awaiting execute_call |
tool_called, failure_kind=unexpected_exception; Starlette still returns the bare 500 |
Exceptions outside execute_call and body-stream failures after the handler returns are outside
this contract. Never relabel a stream failure as a new 500 after headers were sent.
The guards against a caller-controlled upstream are the path-segment-boundary prefix match and ACL
filtering in Tool resolution, deny rules evaluated on
the resolved upstream, the response headers in the contract, and
the call-time SSRF guard with its address-space list.
injectors._token_from_json rejects a non-string field rather than inject garbage, and
health._probe skips a dangling binding.
A kind: routed row (treg.<capability>, catalog § Routing) never reaches the
credential ladder itself. service._execute_call hands it to application/call/route.py, which
runs each child endpoint through this same use case as a child CallContext (call_ref
{parent}:r{n}), so ladder, reserve, relay, capacity, overflow, audit and cancellation apply per
child. Settlement does not: a child that would charge appends a DeferredSettle to the parent's
deferred_settles and leaves its hold open; a child that is not billable or settles at a
provider-reported zero releases at once so it never blocks the next child's reserve. The parent
closes every child hold exactly once with settle.close_deferred(charge=...): settle each billable
child as its own settle would have, or release all because a failed routed call charges nothing.
A crash before that leaves holds to the reaper, which releases in the caller's favor.
route.child_outcome reads each child's answer and is shared with
replay. The parent assembles {output, raw, _treg} and owns the idempotency label.
A pending outcome (202, X-Treg-Route-Outcome: pending) sends no X-Treg-Cost-Micro.
TREG_PLATFORM_ACCOUNTS adds accounts behind a provider as JSON from provider to labelled
accounts: {"contactout": {"b": {"platform_key_contactout": "..."}}}. The deployment's own
platform_key_<provider> is the account labelled primary, and every reader outside the call path
(balance collectors, archive refresh, overflow verification) uses only it. An account names every
credential that differs per account. keyslots.check_accounts refuses to start the API or the
workers that call providers on an account the call path cannot use (primary key unset, no pasted
platform key, or a missing paired setting). Rows, buckets and telemetry (tool_called.key_slot)
name the label, never a credential, so reordering accounts or regenerating a key changes nothing.
application/call/keyslots.py picks the account:
- A call acting on a task, result or managed resource uses the account that created it
(
key_slotonAsyncTaskRecord,AsyncResourceRecord,ProviderResource; NULL meansprimary). A label configured for the provider in this process but no longer listed is refused with 410platform_account_retiredbefore reserve, and the async worker releases that task's hold. A process holding none of the provider's further accounts answers 503platform_account_unavailable, which the worker backs off on, so a worker that lost its environment never releases a task it could poll once restored. Objects on different accounts: 409platform_account_split. - A call carrying
Idempotency-Keyuses the account the org-scoped key hashes to, so a retry reaching the provider lands where it can be recognized. The idempotency claim cannot remember the account: a failed call deletes its claim, and that is exactly the retry that reaches the provider. - Otherwise an account not out of capacity whose smoothing bucket would wait least, ties rotating.
Rules 1-2 run during resolution (
pinned); rule 3 runs right before relay (assign) and takes the bucket's token with no await between, so a burst that resolved together still spreads.
A non-primary call carries account: <provider>/<label> on its platform binding; the relay injects
that account's values or fails injection_failed, never substituting another account. The primary
carries no account and uses the provider's own bucket, so a one-account provider is unchanged.
Capacity is per account: a signature strikes and locks only the account that answered
(marks.lock_key(..., slot), <key>#<label> for non-primary), a 2xx clears only its own strikes,
and a probe goes to the locked account. A call is refused before reserve only when its pinned
account, or every account of an open call, is out. The sweep reads only the primary, so another
account's lock lifts at expiry or through its own probe.
Platform-tier request guards (_enforce_platform_request, catalog platform_request) run after a
platform offer is selected and before reserve. They bound treg's exposure on its shared accounts:
explicit bounded record counts, exact pricing selectors (a singleton-enum body field that picks a
table price row must equal its value; duplicate JSON keys are rejected so validation and upstream
cannot disagree), and outright refusal of options whose charge or result arrives later at a
caller's webhook, outside settlement's reads. Own keys are unaffected: the provider, not treg,
bears that account's exposure.
Tier 4 spends treg's own vendor account, which can be empty. After _platform_offer says yes,
_resolve_marketplace_call consults the in-process capacity view (domain.capacity.view, loaded
from ratestore before the session opens). Two sources mark a call exhausted: the sweep's
capacity:state:<provider> and the call path's lock capacity:lock:<key>. Then it raises
CallFailure("provider_capacity", 503, blame="treg") before any hold exists, with resets_at
when known and _capability_alternatives; treg names options, never chooses (AGENTS.md). Audit
refused_by="capacity", cost 0. A stale, empty or "ok" view never refuses. A free route
(marks.spends_balance false) counts only its endpoint's own quota lock: balance readings and
locks neither refuse it, admit it as a probe, nor take its 2xx as recovery (metered=False), since
a vendor answering free routes would otherwise erase every paid route's strike.
The breaker (domain.capacity.marks) opens slowly and closes fast. After a tier-4 answer >= 400,
settle._note_capacity_signal classifies it (domain.capacity.signatures.classify). A balance or
quota signature is a strike; a second strike inside the window, far enough after the first that a
concurrent burst counts once, with no 2xx between, locks the provider (balance) or only the
endpoint (quota; allowances are per operation). While locked, resolve admits one real call per
process per interval as a probe (MarketplaceCall.probe_lock_id); its 2xx clears exactly that lock.
A sweep exhausted reading admits the same probe, and its 2xx lifts the reading until the next
sweep (marks.clear_sweep_state, conditional on observed_at). Hold lengths are capped whatever
retry-after says; the sweep never writes this namespace. These writes run on their own short
session after settle closed the hold (capacity_exhausted_mark in
tests/test_call_architecture.py).
A burst or unknown 429 only logs (smoothing handles it). An edge_block (the vendor's CDN
answered: cf-mitigated, an HTML block page, 1xxx problem-JSON) strikes nothing, because one
caller's request shape must not take the provider away from every team. An unrecorded signal (a
4xx matching no row whose body still names credits/quota/balance) emits a capacity unrecorded
event (capacity): the tripwire for a vendor dialect missing from the table.
Tiers 1/2 never consult the view; an org's own key running dry is relayed unchanged, as is the
vendor's 402 on this call. The protection is for the next caller.
Many callers share one platform key, so tier 4 makes its own bursts. Two bounded mechanisms in
service._execute_call run after the DB phase and before relay, platform tier only, never a
refusal (the shared rate window in capacity is the refusing gate):
- Spacer (
infra/upstream/limiter.py): one call perwindow_s / limitper bucket, a token bucket of capacity one, because a burst oflimitat t=0 is legal for a classic bucket and exactly what a sliding-window provider 429s. A call waits at mostDEFAULT_MAX_WAIT_MS, then proceeds; the hold is placed, so the org pays latency, never money. The limit comes fromview.rate_limit(CapacityPolicy.rate_limit); an endpoint with its own upstream allowance (policy._ENDPOINT_RATE_LIMITS,view.rate_bucket) is its own bucket, as is each non-primary account. In-process because its wait needs no round trip, so each replica adds its own allowance; the shared window holds the declared rate across replicas and refuses, with the typedprovider_rate_limited429, only on buckets listed inTREG_PROVIDER_RATE_ENFORCE. - One
retry-afterre-send on a tier-4 429 classifiedburstwithretry-after <= SMOOTHING_RETRY_MAX_S, for a body-less GET/HEAD only: close, sleep, resend the identical request once on the same hold, settle on the second answer. The re-send asks the shared window after the sleep; on a full enforced bucket it is not sent and the first 429 stands. A quota 429, an unknown 429, a POST or a second 429 is relayed as is. No other retries (401/402/5xx).
Both show as X-Treg-Smoothed: wait=<ms> and/or retry=1 on the metered response.
Overflow = the same vendor endpoint, another account of ours. When a tier-4 call fails on
treg's key for a treg-side reason (a balance/quota signature, a burst 429 smoothing could not
absorb, or treg's own rate window refusing it before it was sent, forced trigger burst) and the worker has an enabled OverflowRoute, application.call.overflow.maybe_overflow
runs a child cycle after the primary's settle released its hold:
- Pick a route from
domain.capacity.routes_view, skipping an aggregator marked unhealthy or without a key; checkOverflowSpendagainstoverflow_daily_budget_usdon a short session. - Child hold, own id
{call_ref}:overflow, through the ordinary_platform_reserve. Never the parent's id: release-by-id is a conditional claim, and_finish_cancelled_callreleases both ids exactly once. - One aggregator run with no DB open:
infra.upstream.aggregators.<name>.buildwraps the caller's original query and buffered body; the key (Settings.overflow_key_<name>) is never logged. parseyields vendor status, body and the real in-band cost;_platform_settle(child, observed_override=cost, overflow_spend=...)charges exactly the aggregator's price and folds the day's spend into the same transaction (overflow_spend_in_settle, the one allowlisted overflow write).- The vendor's body is the answer, with
X-Treg-Served-Via: overflow:<name>, the child'sX-Treg-Cost-Microand the parent'sX-Treg-Call-Id; the child's audit row hascredential_tier="platform-overflow". MCP carries the same disclosure asserved_via(an MCP client never sees headers). Money: money § Overflow money.
When the resolver already knows the account is out and a route is on, the ladder skips the direct
attempt (MarketplaceCall.skip_direct): no parent hold, straight to the child. Cancellation checks
the authenticated caller's org before releasing the child hold.
Routes are normally admitted by the aggregator-to-direct price ratio; routes in
routes.request_priced (a flat aggregator fee against a per-row direct price) are admitted by an
absolute fee ceiling instead, with no per-request check, and settle once at the aggregator's price
even for an empty page.
An aggregator failure is data. Its own 401/402/403 or a malformed envelope releases the child
and marks overflow:<name> unhealthy for everyone. A relayed vendor answer the signature table
reads as that vendor's own out-of-credit or quota dialect (VENDOR_DRY) marks only
overflow:<name>:<provider>, so one vendor's cap never takes the others offline. Both are
temporary marks, and the caller gets the typed provider_capacity 503; a second aggregator is never
tried on the same call. The aggregator's per-request refusal (contract: its stricter schema, or a
bare 400/422/404 with no vendor data) is request-scoped: release, charge nothing, mark nothing; the
vendor's own answer stands, and on the skip-direct ladder the caller gets the 503 naming the
refusal, never the aggregator's envelope dressed as the vendor's. malformed is only for what is
not an envelope at all (non-JSON, 5xx, transport error): reading a validation 400 as malformed
takes the aggregator offline for every org.
Shadow mode (TREG_OVERFLOW_MODE=shadow) calls the aggregator, logs status, shape and cost and
records the cost in OverflowSpend (treg pays, budget-bounded); the caller still gets the vendor's
error and pays nothing.
Never on tiers 1/2, a caller-caused 4xx, a 401, a timeout, PUT/PATCH/DELETE, a route the worker has
not enabled, or a team that opted out (Org.platform_overflow_disabled) - checked before any
aggregator is contacted, on both entry points.
A catalog endpoint with an async descriptor resolves like any other (resolve.py freezes a
settlement_basis and the descriptor on the MarketplaceCall). A metered 2xx from it is
deferred (application.asynctasks.defer_submission writes the pending row, hold left open)
unless _submission_rejected finds no accepted submission (not JSON, expect failed, no task id),
which settles at once at the provider's reported charge else zero, or terminal_on_submission
finds a success word in the submission, which settles like a synchronous call. A persistence
failure releases the hold with an alert. The caller receives the provider's body either way; a
"still working" submission is not archived. routers/call._attach_async_descriptor adds
X-Treg-Async, also on an idempotent replay, so a retried --await polls the running task.
Settlement: money.
Tier 4 only: endpoints used as an async descriptor's poll or fetch utility accept only task/result
ids present in an AsyncTaskRecord for the caller's org and provider, with the frozen
endpoint/parameter rule. Legacy pairs use catalog resource_ownership and AsyncResourceRecord
(requires may name a body parameter, so a POST status utility can authorize an opaque id). Formal
submissions mirror their ids into that table too, so removing a live descriptor reference cannot
make its utility fail open. Consumers whose producer provenance is not modeled are platform_blocked
(own key only). Unknown and cross-org ids get the same 403 with no provider contact. Fetch-mode
result ids are learned from an authorized successful poll or the worker's terminal response. With a
pin, _enforce_platform_async_ownership adds pinned_tag_predicates; every pin must match the
submission snapshot and an unknown or foreign id is 404 before relay
(multi-tenancy). Own keys keep the
faithful relay: those ids belong to the caller's own account.
Durable shared-account objects use the catalog managed_resource contract and ProviderResource.
Create relays with no DB connection, then commits ownership before the provider id is returned; a
persistence failure triggers best-effort provider deletion and a treg 502 without the id. Update
changes local state only after upstream success. Delete authorizes active or tombstoned ownership,
treats an owned upstream 404 as deleted, then tombstones, so retries are safe. A managed use
declaration may admit provider-public ids through a bounded GET predicate: cross-org and tombstoned
ids are denied without upstream I/O, and wholly unassigned ids are verified after the DB phase and
before reserve. Platform tier only.
An owned platform status poll with an explicit free price and zero estimate
(MarketplaceCall.free_owned_poll) takes no reservation or settlement of its own; its buffered
response feeds observe_owned_poll, which learns fetch ownership and finalizes the original task on
terminal 2xx evidence. Missing required usage leaves the hold pending; settlement errors keep the
provider response for cron recovery. Polls never use or populate the archive and release any
caller idempotency label so the next poll sees a changed status. They are audited as
kind=async_poll at zero cost and excluded from /calls before pagination; terminal evidence is
archived under the submission's call id.
MarketplaceCall.streamable_free_result marks platform fetch utilities with an explicit free price,
zero estimate, a required fetch ownership rule and no submission, owned poll or produced-resource
evidence. Their successful GETs bypass _buffer_response (ownership is still checked before relay)
and stream in full with headers (including Range) through the router's close-once lifecycle. Audit
size is unknown, not zero; nothing is archived or kept for replay, and the claim is released.
Other responses needing settlement or ownership evidence have a hard 8 MiB complete-body budget:
_buffer_response raises GatewayFailed(response_buffer_limit) on the first overflowing chunk,
before any success headers, and closes the upstream in finally. The hold and claim are released,
the caller gets a 502 at zero cost blamed on treg, and no prefix reaches settlement, archive or
replay. Own-key streams are outside this limit; an own-key async answer is read whole up to
_TASK_ANSWER_MAX to record its task, and a larger one streams untouched (tasks). tests/test_call_response_limits.py covers both
HTTP hops.
Some providers return generated media inside JSON with the usage meters after it (Gemini
generateContent: base64 image and thoughtSignature, tens of MB at high resolution). Streaming
would mean settling after the response starts; a larger buffer would hold that per call in a web
process. Such an endpoint declares spooled_response: true, and _read_evidence hands its metered
2xx to _spool_response:
- The body streams into an unlinked
tempfile.TemporaryFileunderspool_max_bytesand the per-processspool_budget_bytes. A declaredContent-Lengthis claimed whole before the first read, so concurrent answers are admitted or refused whole rather than all stalling half-read; otherwise the claim grows per chunk. Crossing either limit raisesresponse_buffer_limitbefore headers (the budget refusal says it is temporary), closing the upstream infinally. - The file is parsed once in a worker thread, at most
spool_parse_concurrencyat a time. Only the paths settlement reads survive (_spool_evidence_paths: each usage term's top-level object and theexpectleaf at its original place) and become thebodyfor usage settlement, result classification and capacity signatures. A non-object body yields empty evidence; ausagebasis then settles at its reserve. - Settlement runs before the response starts; the router relays the file in chunks with the
provider's headers and a recomputed
content-length. Close returns the budget, andweakref.finalizedoes so for a response dropped unclosed. - A spooled body is never archived or stored for replay; the audit row records the full size.
Non-2xx answers, own-key calls and routed children keep their existing paths.
tests/test_call_spool.pycovers the lifecycle.