Two customers want the same sentence and need different things.
The reseller. One account is really N end customers. He needs each end
customer's turns on that customer's inference credential, never another's.
The quota-juggler. One tenant, several Claude subscriptions, exhausting
quota on one and wanting the next agent to run on another.
Today inference_credentials is one row per user with four fixed ciphertext
columns and a unique_constraint(:user_id). InferenceCredentials.select/4
is the whole selection rule, and it reads that one row.
Direction
The reseller's answer is principals, not credential sets. ADR 0044 already
wrote the objection down -- "one shared service account is not isolation" --
and Principals.billing_subject_id/1 already gives him one invoice with
per-end-user usage attribution. One gap blocks it (item 10 below).
The juggler's answer is credential sets: inference_credentials becomes
1:N, an agent names its default and a launch may override it.
Relationship to ADR 0052
ADR 0052 (user-owned ChatGPT grants, #2010) is merged as Proposed, and
#2011-#2015 + #2017 build its sections 1, 3 and 6. Sections 4 (selection)
and 5 (sandbox identity) are unbuilt, and that is exactly the ground this
work stands on. This tracker builds section 4's selection plumbing in the
shape 0052 asks for, so the ChatGPT work lands on top of it rather than
colliding, and amends 0052's "no per-agent preference in the first version".
Stack
- ADR 0053 + index refresh
select/4 returns a source struct, not a bare atom (pure refactor)
- Fix: an env/vault secret shadowing a static inference credential resolves
the source to :own (P1, mis-bills on prod today)
- Inference credential pairs move off the shared
.env into process env
inference_credentials gains name + is_default; 1:N
agents.inference_credential_id + allowed_inference_credential_ids
conversations.inference_credential_id per-launch override
- API: CRUD, OpenAPI, contract, TS SDK
- Console
- An owner may set an inference credential on a principal it owns
- Docs, CHANGELOG, SDK bump
The bug in item 3
PlatformInference.gate/3 and select/4 both ask has_own?, which reads only
the inference_credentials table. A vault secret named ANTHROPIC_API_KEY
is invisible to it, but wins in the sandbox env (egress.ex:223-226, and
docs/concepts/secrets.md:212 publishes the rule). So on a deployment holding
platform keys -- ours since 2026-09-03 -- the turn is selected :platform,
stamped "platform" by TurnMachine.with_inference/2, priced by
CreditPricer, counted against PLATFORM_INFERENCE_DAILY_CENTS, and can be
refused by check_ceiling/0, while the tenant's own vault key actually serves
it. #1941 fixed an adjacent facet today; this one stands.
ADR 0052 section 4 already mandates the fix: "Environment/vault API-key
overrides must resolve into the same explicit source decision."
Two customers want the same sentence and need different things.
The reseller. One account is really N end customers. He needs each end
customer's turns on that customer's inference credential, never another's.
The quota-juggler. One tenant, several Claude subscriptions, exhausting
quota on one and wanting the next agent to run on another.
Today
inference_credentialsis one row per user with four fixed ciphertextcolumns and a
unique_constraint(:user_id).InferenceCredentials.select/4is the whole selection rule, and it reads that one row.
Direction
The reseller's answer is principals, not credential sets. ADR 0044 already
wrote the objection down -- "one shared service account is not isolation" --
and
Principals.billing_subject_id/1already gives him one invoice withper-end-user usage attribution. One gap blocks it (item 10 below).
The juggler's answer is credential sets:
inference_credentialsbecomes1:N, an agent names its default and a launch may override it.
Relationship to ADR 0052
ADR 0052 (user-owned ChatGPT grants, #2010) is merged as Proposed, and
#2011-#2015 + #2017 build its sections 1, 3 and 6. Sections 4 (selection)
and 5 (sandbox identity) are unbuilt, and that is exactly the ground this
work stands on. This tracker builds section 4's selection plumbing in the
shape 0052 asks for, so the ChatGPT work lands on top of it rather than
colliding, and amends 0052's "no per-agent preference in the first version".
Stack
select/4returns a source struct, not a bare atom (pure refactor)the source to
:own(P1, mis-bills on prod today).envinto process envinference_credentialsgainsname+is_default; 1:Nagents.inference_credential_id+allowed_inference_credential_idsconversations.inference_credential_idper-launch overrideThe bug in item 3
PlatformInference.gate/3andselect/4both askhas_own?, which reads onlythe
inference_credentialstable. A vault secret namedANTHROPIC_API_KEYis invisible to it, but wins in the sandbox env (
egress.ex:223-226, anddocs/concepts/secrets.md:212publishes the rule). So on a deployment holdingplatform keys -- ours since 2026-09-03 -- the turn is selected
:platform,stamped
"platform"byTurnMachine.with_inference/2, priced byCreditPricer, counted againstPLATFORM_INFERENCE_DAILY_CENTS, and can berefused by
check_ceiling/0, while the tenant's own vault key actually servesit. #1941 fixed an adjacent facet today; this one stands.
ADR 0052 section 4 already mandates the fix: "Environment/vault API-key
overrides must resolve into the same explicit source decision."