Unreleased: subscriptions and credentials¶
Committed changes that carry no release coordinate yet and concern the subscription pool, the credentials in it, and the sign-ins that keep them alive. The remaining unreleased changes are on current changes; published releases are on 0.4.46 to 0.4.13, 0.2.77 to 0.2.42 and 0.2.5 to 0.2.2.
Unreleased: a refusal names the automatic sign-in failure that keeps the pool empty¶
A best request refused because its members await a sign-in said only awaiting sign-in: <the provider's refusal>, while the automatic sign-in that renews them had already failed on its Weles host for a reason only readiness reported. The refusal now ends with ; the automatic sign-in that renews them failed: <member>: <code>: <detail> for every such member whose failed attempt still stands, and the refusal of a pool whose every member is burnt says the automatic Weles sign-in that renews it failed: <code>: <detail> in place of renewed by the automatic Weles sign-in. The sign-in journal is read only once the request is refused, so a pool that serves pays nothing for it. See errors.
Unreleased: the Claude Code request shape is one capture¶
The release, Agent SDK and client versions, beta list, Stainless headers and the fingerprint and
checksum parameters a Claude Code subscription request carries were separate constants, so one
could change without the others. They are now one CLAUDE_CODE capture naming the
Claude Code release they were read from, the Stainless headers in the order that release sends
them, its client deadline header included as captured
(Claude Code).
Unreleased: Brama buys a Claude Code account when the pool is spent¶
A brama maintain pass reads every claude-code account's plan from the provider; when all of them are spent and the pool holds fewer accounts than the operator allows (claude_code_accounts_max in src/subscription_dispatch/acquire/numeric-provenance.json), Weles buys one on the tier the pool's accounts hold and the gateway proves its grant. brama subscription acquire, POST /v1/admin/subscription-pool/acquire and Brama Desktop's Buy account now run the same decision on demand and name the account or number that stopped it. brama subscription hand-over claude-code --harness omp and Desktop's Sign into omp sign every pool account omp lacks into omp through its own login, completed by Weles, so no grant is shared.
Unreleased: the launcher reads every preloaded secret by role¶
The service launcher no longer names a vault item. Each model-router bearer is the token of the item that plays weles-model-router, wisent-backend-model-router, wisent-app-model-router or brama-desktop-model-router; each product's request-sign secret is read from the item playing <product>-agent-auth (wisent-app-agent for Jeden's runtime identity); and the Weles sign-in bearer from the item playing brama-weles-reauth, in the launcher and in a sign-in that runs without BRAMA_WELES_REAUTH_TOKEN. A role is the tag stado:role:<role>; no item in a role, or two, is refused at start with the role and its tag. brama launcher role-field --router ROUTER ROLE FIELD replaces item-field. Before a release carrying this starts anywhere, the vault owner's Skarbiec must register the stado:role: tag namespace and each of those items must carry its role, or the two required model-router clients stop the gateway at start.
Unreleased: the console bearer is read by role, not by item¶
--bearer-item <item>#<field> on brama maintain and on brama subscription list|refresh|attribute|reinstate|disown is replaced by --bearer-role <ROLE>: the console bearer is the token field of the vault item that plays that role, read through stado credentials get --role. A command line, a schedule or a page no longer names a vault item, so replacing or renaming the item changes nothing there, and an item the caller's consumer is not granted is refused by Stado with its own sentence instead of being the one a page happened to name. The old flag is refused by the parser as unknown.
brama probe follows: --bearer-role <ROLE#FIELD> and --signing-role <ROLE#FIELD> replace --bearer-item and --signing-item, and each secret is the field of the one live item tagged stado:role:<ROLE>, found through the entitlements router with the same lookup the launcher uses.
Unreleased: pool accounts are recorded in the vault the gateway reads¶
brama subscription attribute wrote each member's stated account
through whatever skarbiec binary the operator's own machine
resolved. On a machine that reads the fleet vault over the network that is a
retired local copy: the command printed recorded, the fleet vault
kept no brama:account tag, and Weles refused every automatic
sign-in with subscription_identity_missing before a browser opened,
so no Google prompt ever reached a phone. With --gateway (or
--gateway-consumer and --bearer-role) the command now
asks the gateway itself, over
POST /v1/admin/subscription-pool/attribute,
to record the accounts in the vault it serves from; the route answers
403 to a caller that is not the operator console. Brama Desktop's
Subscription Pool screen calls the same route from Record accounts.
The renewal sweep no longer discards a failed account write: it logs
subscription_account_unrecorded with the subscription and the
vault's error.
Unreleased: a subscription the runtime policy does not name is reported, not dropped¶
The runtime policy is generated from the vault's own tags when a release is
installed on a host. A subscription added to the vault after that install is not
in it, and the launcher stage that builds the boot catalogue dropped such an item
without a word: the gateway simply had no such account. Accounts could be in the
vault, tagged correctly and listed by
stado route capability brama, while the gateway answered
no working subscription model for signed agent and
brama subscription refresh claude-code reported
no capability route maps this resource to a vault item and field for each —
the first visible trace, hours after boot, of a decision made at startup.
The stage now writes one line per dropped subscription naming the resource, what
the gateway will answer later, and the remedy — install the release again on that
host so the policy is regenerated from the vault's current tags — and ends the boot
log with the counts: how many the policy does not name, and how many are served.
cargo test --test release_subscription_catalog runs the shipped stage over a
real vault listing and a real policy document and reads back the catalogue it writes.
The gateway says the same thing about itself while it runs: readiness now
compares the vault's fully tagged subscriptions against the catalogue this
process was started with, lists each account the runtime policy does not name
under unroutable_accounts with that remedy, and answers
traffic can be served, but the vault holds a subscription this process was
not started with: the runtime policy does not name it. A process started
without a boot catalogue reports nothing, because there every account would look
unnamed. cargo test --test pool_policy_unnamed seeds a real isolated
Skarbiec vault with two subscriptions and reads the census through the real
entitlements router.
Unreleased: a Claude Code grant travels in Claude Code's own shape¶
With the temperature gone, the deployed gateway's first best request as
tama was still refused: Anthropic answered 429 with the one-word
body Error for claude-code/claude-fable-5-1 and then for
claude-sonnet-4-6, while claude-haiku-4-5 had answered the import
probe minutes earlier and omp was being answered on the same account for the
same models. The grant was fine; the request was not Claude Code's. A request that
presents a Claude Code grant now carries what Claude Code carries — its user agent and
beta list, x-app: cli, and a system prompt opened by the billing attestation
with a checksum of the body and the sentence You are Claude Code, Anthropic's official
CLI for Claude. — on the buffered and streaming paths alike
(how a request presents
it).
Unreleased: an omitted temperature is not invented¶
The first Claude completion through the deployed gateway after the pool was
repaired was refused by the provider: claude-code/claude-opus-4-7 refused
(provider_failure: `temperature` is deprecated for this model.), and the same for
claude-opus-4-8. The caller had sent no temperature; the gateway defaulted it
to 0.7 on every inbound format and sent that default to every provider as a real
setting. A temperature now reaches the provider only when the request stated one, on
the chat, Anthropic messages and Responses wires and on every provider dialect; a
stated value is bounded as before. The gateway's own probes still pin zero, because a
probe's answer should not vary
(shared validation).
Unreleased: one vault listing answers every reader¶
Every reader of the vault's inventory — per-agent discovery, the console's pool, the
readiness sweep's unroutable-account census, the sign-in loop and each credential write's tag
lookup — shelled the entitlements router's bare list, and the router decrypts and
parses the whole vault to answer it. With hundreds of items in the vault and the readiness
sweep and the sign-in loop asking concurrently every half minute, the router alone held most
of a gigabyte and half a core beside a saturated gateway, until the control host's own object
store closed connections for want of memory while the fleet's releases were writing to it. Concurrent
callers now share the one listing in flight instead of each starting a router; nothing is
served after it returns, because the vault changes under this gateway by hands it does not
see (skarbiec delete, a vault sync, an operator's set) and a listing
served afterwards would answer with an account the vault no longer holds — the usage
story deletes a real item mid-run and reads it as forgotten on the next call, and a ten-second
window in 0.4.29 failed it. The per-agent subscription cache above it is unchanged. 0.4.32
(0.4.29 and 0.4.30 were claimed by linux builds submitted with a builder script that refused
its own work root, 0.4.31 by the window; Stado 0.21.15 fixed the builder).
Unreleased: rotation reads the refusal's class, not the provider's words¶
A refused provider answer now carries failure_kind (rate_limited,
authentication, dependency_unavailable, provider_failure),
taken from the HTTP status, or from the error object's code when a Responses stream
fails after its 200. Subscription rotation refreshes and rotates on authentication,
and moves to the next account on rate_limited; any other refusal condemns the route
only. The provider's sentence no longer decides either, so a request error that mentions OAuth
or a limit leaves the account alone. A credential refused again right after a forced refresh is
still retired, and a refresh the provider disowns is still recorded as needing a sign-in.
Unreleased: shared second-factor recovery and observed approvals¶
CLI, Desktop and background sign-ins use one recovery path. Weles can answer an
offered Google phone challenge when no authenticator seed is held. A structured
google_2fa_material_missing refusal orders enrolment for the same resolved
subscription and login; only a confirmed worker run and independent seed readback
permit a new sign-in and credential refresh. An existing authenticator is not rotated.
The saved verdict and pool retain the observed second-factor method and Weles run. Unknown evidence is no longer inferred as “no second factor” from a successful grant. CLI and Desktop distinguish a missing seed from a failed seed inspection. Source changes alone are not a passing real authentication run.
Unreleased: an imported member names its account, so Weles can sign it in when its grant dies¶
Weles resolves an automatic sign-in from the subscription item's account_ref
and a Skarbiec login item for that principal. Every member the pool gained through
import or sync carried the harness's account in its verdict and none in
its item: /readyz reported
subscription_identity_missing for held-claude-code-<account slug>,
wisent-app-claude-secondary, wisent-app-claude-tertiary and
wisent-app-kimi-primary, and the loop that exists to replace a burnt grant could not
start. A stored grant now carries the account the harness recorded as the item's
account_ref, written beside the credential and preserved on every later write; a caller
that knows no account leaves what the item carries. The next thing that blocks the sign-in is
the one the login item lacks — on this fleet, an authenticator seed, which
stado credentials seed-freshness reports as seed_field_empty for every Claude,
Codex and Kimi login. 0.4.28.
Unreleased: traverse every eligible subscription¶
Buffered and streaming requests no longer stop at a fixed prefix of the eligible credential list. Unblocked accounts remain first; every skipped blocked account contributes its recorded refusal and reset to the exhausted pool result. A rate-limited account later in the list is therefore not hidden behind earlier authorization failures. Explicit billing targets still select only the named credential. Rotation stops after a successful response, a non-rotatable provider refusal or the first committed stream byte.
Unreleased: a gateway that has obtained one direct credential says so before the subscription sweep ends¶
The readiness sweep has two halves: one capability per direct provider, then every
agent's subscription discovery and one redemption per active subscription. Until the whole
sweep ended /readyz answered readiness check has not completed, which is
503, and the second half takes as long as Skarbiec and every provider take. A candidate
still inside it when Stado's 90-second readiness window closed was quarantined
(candidate did not become ready within 90s) and the host kept serving the
previous release — one that still rotated borrowed grants, which went on to revoke an
operator's own session on one of those accounts after the release meant to stop it had been
refused a rollout by the gateway it replaces. The first half now publishes its own verdict the moment it is known:
ready when one direct provider credential was obtained, degraded either
way, reason a direct provider credential was obtained; the subscription sweep is still
running; the full report replaces it when the sweep ends. 0.4.25.
Unreleased: a Claude grant rides as the Agent SDK, the way the harness Anthropic answers sends it¶
Anthropic answers a Claude request that does not look like its own harness's with
Third-party apps now draw from your extra usage, not your plan limits, while the
harness on the same account is answered on plan limits in the same minute. Brama still sent
an older Claude Code CLI shape; the harness Anthropic answers sends
Claude Code 2.1.165 as the Claude Agent SDK 0.3.165: user agent
claude-cli/2.1.165 (external, local-agent, agent-sdk/0.3.165), entrypoint
local-agent, the sentence "You are a Claude agent, built on Anthropic's Claude Agent
SDK.", Claude Code's agent beta list, the Stainless client headers and a fresh
x-client-request-id. Brama now sends that shape, byte for byte where the provider
can see it. Members the pool held for accounts a harness also held died with
invalid_grant -- Refresh token not found or invalid — and so did the harness's
own copies, under the operator's open session: Brama had refreshed the shared grants and the
provider revoked the refresh tokens the harness held. A grant taken from a harness is now
borrowed: Brama spends it as it stands and never rotates it, the pool report names the
harness in credential.borrowed_from, a forced refresh disowns the member with
this grant is borrowed from omp, which refreshes it itself, and
brama subscription sync hands every borrowed grant over on every pass — the gateway
answers unchanged while the harness has not rotated it, and stores the new grant
when it has, so the pool follows the harness instead of dying behind it.
Unreleased: every account the machine holds joins the pool by itself¶
The accounts below were added by hand, one import at a time, and the operator's
answer was that this is the opposite of how it should work: the pool has to see every account
the machine holds without a person naming it.
brama subscription sync --reason <why> [--gateway <origin>] (since removed)
lists what every harness holds, derives one stable member id per account
(brama-sub-held-<provider>-<account>), leaves every id the pool already
holds to Brama's own refresh, and imports the rest through the same path import
takes, one verdict per grant. Brama Desktop runs the sweep from the runtime it bundles, once
when its gateway is connected and every thirty minutes after, and its Subscription pool screen
says what the last sweep did, with Sweep now beside it.
Unreleased: an imported grant for an id the pool does not hold yet joins the pool¶
Every Claude call through best is refused with
all bounded 'claude-code' credentials unavailable for agent when the pool holds
one rate-limited account while the machine holds more with quota that no pool member names.
brama subscription import --gateway
answered 404 subscription not found for any id but the one already there, so the
only way to add an account was a sign-in window. An import for an id the pool does not hold
yet now creates that member under the named provider - the CLI sends the provider with the
grant, storing the grant creates and tags its vault item, and brama subscriptions
lists it at once. POST /v1/admin/subscription-pool/grant takes
provider for the same reason and refuses without it, or with one whose grants the
refresh path cannot renew. The first import through the deployed gateway then answered no capability route maps resource `provider:claude-code:brama-sub-wisent-app-claude-secondary` while stado route capability brama listed that route: the gateway read the operator’s routes table file itself, and a route Skarbiec declares from an item’s own tags — every one a sign-in or an import creates — was never in that file. The gateway now asks Skarbiec (route resolve) for the coordinate a resource stands for, the same answer the authority gives, and reports Skarbiec’s own sentence when nothing declares it.
Unreleased: a grant any harness already holds is taken, not asked for again¶
The harnesses beside a gateway are often signed into the accounts the pool needs long
before the pool is. Four harnesses sign
accounts in on an operator's machine - omp, Claude Code, Codex CLI, Kimi
Code - and each keeps the grant its own way.
brama subscription held (since removed) lists what
every one of them holds, never the grants;
brama subscription import <provider> --from <harness> --subscription-id <id> --reason <why> (since removed)
reads that harness's own store read-only, rewrites the grant into the document the
refresh path reads for claude-code, codex or kimi,
refuses one it could not renew before storing anything, stores it as the
subscription's credential, marks the row signed in, proves it with one minimal
completion, and journals the verdict with the harness it came from. With
--gateway and the console's bearer on stdin the same command hands the
grant to a gateway elsewhere through POST /v1/admin/subscription-pool/grant;
Brama Desktop's Take a grant a harness holds… runs it from the runtime it
bundles, so the desktop reads no store and knows no format.
Running that against the deployed gateway found the admin family unreachable in
production: every /v1/admin/* path answers only a brama-desktop
identity with no allowlist, such an identity can only come from the boot table, and
the launcher never put one there - the authority resolves a workload bearer with the
routes it was issued, never without them. Brama Desktop and the terminal alike were
answered 401 model_bearer_unrecognized; only the test harness, which seeds
the console into the table, ever reached the family. The launcher now projects
brama-desktop-model-router#token - the item the desktop acquires - into the
boot table as brama-desktop, optional like the renewal client, and says on
its stderr when it could not read it
(who the admin family answers).
Every grant that reaches Brama without a Weles sign-in - pasted code, harness, console - is now proved by a probe instead of a refresh. A refresh rotates the refresh token and revokes the one it replaced, so proving a grant the harness still holds would have killed the harness's copy and failed every session the operator had open on that account at its next refresh; the probe spends one completion on the access token both sides hold and rotates nothing. The sweep rotates the grant later, as the access token nears its end, and the harness's copy goes stale then - which the import page says plainly.
Unreleased: a Claude account is signed in by hand, without Weles¶
brama subscription sign-in-manual claude-code --subscription-id <id> --reason <why> (since replaced by sign-in --by hand)
prints the provider's authorize page, waits for the code the operator pastes
after logging in with their own browser, exchanges it at the provider's token
endpoint, stores the grant as that subscription's credential in the shape the
refresh path reads, and proves it with one completion. The page carries the
client id, scopes, loopback redirect and PKCE challenge that
omp auth-broker login anthropic sends, so the grant is the same kind
the automatic renewal already keeps alive; the manual step is not repeated
until the provider revokes the refresh token.
Brama Desktop does the same in two steps, because the gateway is usually not
on the machine the person logs in from: POST /v1/admin/subscription-pool/sign-in-manual
returns the page and keeps the PKCE verifier in the gateway under a
sign_in_id for 900 seconds; POST /v1/admin/subscription-pool/sign-in-manual/:sign_in_id
takes the paste and ends in the same exchange, store and proving refresh
(HTTP API).
Both are brama-desktop only. They have since become
POST /v1/admin/subscription-pool/sign-in with "by": "hand" and
…/sign-in/:sign_in_id.
A pool whose every Claude subscription holds no credential — Skarbiec items carrying a document with no grant — while the only working account is signed into the harness on the operator's laptop, and every sign-in Brama offers goes through Weles, which needs a login row and a browser host that account does not have, is a gateway every product is told to use with nothing to answer with while the harness beside it has. The runbook entry A request answers 429 subscription_unavailable now names this command as the repair for that state.
Unreleased: the request path leaves a disowned grant alone¶
A request that ranks an agent's subscriptions no longer reads a grant the
ledger already records as needs_reauthorization: the sweep left
such a grant alone until a sign-in replaced it, and the request path now does
the same, reporting it as awaiting_sign_in instead of paying for a
router child process, a broker redemption and a refused refresh on every
call. A document with no refresh token could be refused several times a minute
for days while callers waited. A
credential the vault refuses outright is remembered for sixty seconds, the
window a failed model discovery already had.
Two events attribute the time a request spends before the provider answers:
credential_obtained (resource, path,
elapsed_ms) once per managed credential, and
subscription_models_discovered (subscriptions,
read, awaiting_sign_in, failed,
elapsed_ms) once per ranked request. The runbook entry
A request is slow before the provider answers
reads them.