Um motor que envia uma requisição a muitos modelos precisa de dois botões independentes: autonomia (quanta supervisão humana um trabalho exige) e custo (quanto dinheiro uma chamada pode gastar). O Alembic codifica o primeiro como a escada de tiers T0→T4, e o segundo como um registry de modelos precificado por 1k tokens, mais um BudgetGuard que falha fechado (fail-closed). Esta lição liga os dois: como um tier roteia para o modelo mais barato que qualifica, como toda chamada paga é medida, e por que um teto não-positivo significa "só o free tier" — nunca "ilimitado".
run(), que toda chamada de modelo passa. É ali que o orçamento entra.T0→T4 e dizer por que DEFAULT_TIER = T4 é fail-closed.LOCAL é um marcador ortogonal, não um sexto tier.pickCheapestForTier escolhe um modelo e por que a escolha é determinística.cap = Math.max(0, capUsd) e por que um free call sempre passa.A maioria dos sistemas mistura "quão importante é isto" com "quanto posso gastar nisto" num único botão. O Alembic os mantém separados: um trabalho pode ser de baixa autonomia mas caro, ou de alta autonomia mas grátis. São dois mostradores num painel — e mexer num não mexe no outro.
Dois mostradores ortogonais: Tier decide quem revisa; o BudgetGuard decide se a chamada paga acontece.
O Tier é uma escada de exatamente cinco degraus, do mais autônomo (T0) ao parado (T4). Ela é sobre supervisão humana, não sobre dinheiro. Quanto mais alto o degrau, mais olhos humanos o trabalho exige antes de ser aceito.
| Tier | Significado | Autônomo? |
|---|---|---|
| T0 | silencioso / totalmente autônomo, sem humano no loop | sim (o caminho silencioso) |
| T1 | autônomo com logging leve | sim |
| T2 | autônomo, um único revisor é notificado | sim |
| T3 | autônomo, revisão por council exigida | sim |
| T4 | PARK — retido da execução autônoma; precisa de council + humano | não |
Se um trabalho chega ao motor sem ser classificado em nenhum tier, em qual degrau você acha que ele cai por padrão?
DEFAULT_TIER = T4. O padrão é a postura mais cautelosa possível: trabalho não classificado não roda sozinho, espera por council + humano. É o mesmo espírito fail-closed da lição 26.Primeiro, DEFAULT_TIER = Tier.T4 — trabalho não classificado para (lição 26). Segundo, isAutonomous só retorna true para T1–T3; T0 é autônomo por definição (o caminho silencioso) e T4 para. O enum é exatamente cinco entradas, e há uma escada ordenada (TIER_LADDER) que codifica a profundidade de escalonamento.
export const Tier = { T0:'T0', T1:'T1', T2:'T2', T3:'T3', T4:'T4' } as const; /** O tier padrão para trabalho não classificado: parado, não auto-executado. */ export const DEFAULT_TIER: Tier = Tier.T4; // T1–T3 são autônomos (true); T0 é o caminho silencioso; T4 para (false). export const isAutonomous = (tier: Tier): boolean => tier === Tier.T1 || tier === Tier.T2 || tier === Tier.T3;
tier === T1 || tier === T2 || tier === T3. Sem inversões, sem casos implícitos.Há um quinto rótulo no código, LOCAL — mas ele não é um degrau da escada. É um marcador separado que prende o trabalho a modelos locais/$0 "independentemente do tier": caminhos sensíveis a privacidade ou a custo. Uma unidade pode ser T2 E LOCAL ao mesmo tempo — autônoma com um revisor, mas mantida em modelos locais.
LOCAL é declarado como uma constante separada do enum Tier — "marker for work pinned to local / $0 execution, orthogonal to Tier" (packages/contracts/src/tier.ts). O tierSchema aceita apenas T0..T4; LOCAL nunca aparece nele. Por isso ele compõe com qualquer tier em vez de competir com eles.
/** Marcador para trabalho preso a execução local / $0, ortogonal a Tier. */ export const LOCAL = 'LOCAL' as const; export type LocalMarker = typeof LOCAL; // o enum de tiers NÃO inclui LOCAL — são eixos diferentes: export const tierSchema = z.enum([T0, T1, T2, T3, T4]); // sem LOCAL
LOCAL como "o tier mais barato" seria um erro de modelagem: você perderia a capacidade de dizer "este trabalho precisa de um revisor (T2) e tem que ficar local (privacidade)". Como marcador ortogonal, as duas restrições coexistem sem conflito.tierSchema é um z.enum de T0–T4 — 'LOCAL' nunca passa nele. O marcador viaja por fora, num campo próprio.Cada modelo roteável é uma entrada no registry com um adapterId (qual adapter o serve), um tier e dois preços: costPer1kInputUsd e costPer1kOutputUsd. São 11 entradas espalhadas pelos tiers. Os modelos local-* custam zero — é isso que deixa o CI rodar de graça e hermético.
O modelRegistryEntrySchema exige modelId, adapterId, tier e os dois preços não-negativos (mais capabilities opcional). O DEFAULT_MODEL_ID é 'glm-5.2' (um T2). Ids e preços são dados que evoluem — a forma é o contrato.
{ modelId:'local-default', adapterId:'local', tier:T0, in:0, out:0 } // $0 hermético
{ modelId:'local-extract', adapterId:'local', tier:T1, in:0, out:0 }
{ modelId:'qwen3.7-plus', adapterId:'cliproxyapi', tier:T1, in:0.00015,out:0.0005 }
{ modelId:'glm-5.2', adapterId:'cliproxyapi', tier:T2, in:0.0002, out:0.0006 } // DEFAULT_MODEL_ID
{ modelId:'gpt-5.5-xhigh', adapterId:'cliproxyapi', tier:T3, in:0.015, out:0.045 }
{ modelId:'claude-opus-4-8-max', adapterId:'cliproxyapi', tier:T3, in:0.03, out:0.15 }
A divisão por tier (registry as-built): T0 = local-default, local-extract ($0); T1 = kimi-k2.7-code-highspeed, grok-composer-2.5-fast, qwen3.7-plus; T2 = deepseek-v4-pro, gemini-3.5-flash, glm-5.2, qwen3.7-max; T3 = gpt-5.5-xhigh, claude-opus-4-8-max. O adapter local mantém o T0 em $0 para o CI hermético. (Ids/preços são dados ilustrativos e evoluem; a forma é o contrato.)
.optional() e existem só para o smoke check do alembic doctor --client-stack.Quando um tier é escolhido mas nenhum modelId específico é fixado, pickCheapestForTier seleciona a entrada mais barata daquele tier, pelo custo combinado (entrada + saída) por 1k. É uma função pura sobre o registry — um fold determinístico, sem efeitos colaterais.
Filtra os candidatos do tier, e dobra (reduce) mantendo a entrada de menor costPer1kInputUsd + costPer1kOutputUsd. Sem Date, sem random — o mesmo registry sempre devolve a mesma escolha, o que importa para o replay (lição 28).
export const pickCheapestForTier = (tier, registry = MODEL_REGISTRY) => { const candidates = Object.values(registry).filter((e) => e.tier === tier); if (candidates.length === 0) return undefined; return candidates.reduce((cheapest, entry) => { const entryCost = entry.costPer1kInputUsd + entry.costPer1kOutputUsd; // in + out const bestCost = cheapest.costPer1kInputUsd + cheapest.costPer1kOutputUsd; return entryCost < bestCost ? entry : cheapest; }); };
Um modelo barato na entrada mas caro na saída poderia perder numa tarefa de muita geração. Somar os dois dá um único escalar comparável. O ADR-0006 põe um piso de profundidade por cima: o roteador deve pegar o mais barato acima de um piso de qualidade, reservando os modelos frontier (o par T3) para adjudicação difícil — então "mais barato" nunca quer dizer "fraco demais".
pickCheapestForTier(T2) devolve?Date nem random: o mesmo registry sempre devolve a mesma escolha — a pré-condição do replay (lição 28).Veja a mesma ideia em duas alturas — escolha a sua:
Você diz “quero algo do nível T2” e a função pega o mais barato da prateleira T2 — somando entrada e saída. É sempre a mesma escolha, porque ela só olha a prateleira, nunca o relógio nem um sorteio. Se quiser um modelo específico, é só fixá-lo: aí o roteador nem precisa escolher.
A comparação é estritamente menor (<): num empate de custo combinado, o primeiro candidato encontrado vence. Como Object.values(MODEL_REGISTRY) tem ordem de iteração estável, o desempate é determinístico. A pureza (sem IO, sem Date.now(), sem Math.random()) é o que deixa o replay reproduzir exatamente a mesma rota a partir do mesmo estado.
O roteamento decide qual modelo; o BudgetGuard decide se a chamada pode acontecer. Ele nasce com um teto rígido em dólares e tem métodos para check (cabe?) e record (registra o gasto) — e os padrões são deliberadamente paranoicos.
O construtor faz cap = Math.max(0, capUsd). O check deixa passar de graça qualquer chamada com projectedUsd === 0; bloqueia (budget_exceeded) se spent + projectedUsd ultrapassaria o teto; senão aprova e informa o restante. O record soma o gasto real ao acumulado spent.
export const createBudgetGuard = (capUsd) => { const cap = Math.max(0, capUsd); // teto não-positivo vira 0 = só free tier let spent = 0; return { check(estimate) { const projectedUsd = priceEstimate(estimate); if (projectedUsd === 0) return { ok:true, projectedUsd:0, … }; // free sempre passa if (roundUsd(spent + projectedUsd) > cap) return { ok:false, reason:'budget_exceeded', … }; // estouraria ⇒ BLOQUEIA return { ok:true, projectedUsd, remainingUsd: remaining() }; }, record(spend) { spent = roundUsd(spent + costOf(spend)); return spent; }, }; };
cap = Math.max(0, capUsd)Um teto não-positivo não quer dizer "sem limite" — ele é clampado para 0, que significa só o free tier: qualquer chamada com projectedUsd > 0 é bloqueada, porque 0 + qualquer coisa > 0. Assim, o padrão seguro (nenhum orçamento definido) é a postura mais barata possível, nunca um gasto desgovernado. Chamadas free (projectedUsd === 0) sempre passam — é por isso que o funil inteiro pode rodar a $0 e hermético no adapter LOCAL (lição 15).
O BudgetGuard como portão: free passa sempre, paga só passa se couber, estouro bloqueia — e medir vem depois de rodar.
projected passa do cap, a chamada é recusada inteira — não pela metade.Há uma invariante sutil: a precificação é sempre aplicada. Mesmo que um resultado de modelo (ModelRunResult) já traga um costUsd, um modelo free-tier é medido como 0 — o costOf checa isFreeTierModel primeiro. E um modelo pago, sem custo explícito, cai para costForModel(modelId, usage). O gasto nunca é adivinhado e nunca é pulado.
Para um ModelRunResult, costOf retorna 0 se isFreeTierModel(modelId); só então usa costUsd ?? costForModel(modelId, usage) ?? 0. Resultado: chamadas T2/T3 são medidas ao preço do registry, e o acumulado spent é autoritativo — não advisório.
const costOf = (spend) => { if (typeof spend === 'number') return Math.max(0, spend); // … UsageEstimate → priceEstimate(spend) … if (isFreeTierModel(spend.modelId)) return 0; // free SEMPRE 0, ignora o campo return spend.costUsd ?? costForModel(spend.modelId, spend.usage) ?? 0; };
costUsd errado num modelo $0 não conta.Clique num tier para ver, lado a lado, quem revisa, se roda sozinho e quais modelos o roteador escolheria por padrão (o mais barato do tier). Lembre: o custo segue o tier na prática, mas é o BudgetGuard — não o tier — que barra uma chamada cara.
Totalmente autônomo, sem humano no loop — o caminho silencioso.
Modelos do tier: local-default ($0).
tier.ts · isAutonomous = true para T1–T3Mexa nos controles: defina o teto (que será clampado por Math.max(0, …)), o quanto já foi gasto, e o custo projetado da próxima chamada. O veredicto reproduz a lógica real de check — incluindo "free sempre passa" e "0 = só free tier".
-5 e 0 caem para 0 (só free); um positivo passa inteiro. É uma linha de código que define toda a postura padrão.cap = Math.max(0, 10) = 10 · spent = 6 · projected = 3
Um passo de council quer rodar em T2, sem modelId fixado, com um guard de teto US$ 5,00 e US$ 4,90 já gastos. A chamada projeta US$ 0,03. O que acontece?
run() só dispara depois de o check aprovar. Reprovado no passo 2, a chamada nunca chegaria ao passo 3.pickCheapestForTier(T2) dobra sobre as entradas T2 e devolve a de menor (in+out) — determinístico, mesmo registry, mesma escolha.guard.check: projetado é 0,03 > 0, então não é free; 4,90 + 0,03 = 4,93 ≤ 5,00 → cabe. Retorna ok:true, restante 0,07.run() (a cintura estreita) chama o modelo de fato. Se o check tivesse falhado, a chamada nunca aconteceria.guard.record: o modelo é T2 (pago), então costOf usa o custo real → spent sobe para ~4,93. A próxima chamada cara será barrada.createBudgetGuard(0). Em que passo a chamada para — e por quê? (Resposta: no passo 2. Com cap = 0, qualquer projetado > 0 dá budget_exceeded; só uma chamada $0 passaria.)Tier é exatamente T0–T4. LOCAL é um marcador ortogonal que prende o trabalho a modelos $0 independentemente do tier. Uma unidade pode ser T2 e LOCAL: autônoma com um revisor, mas mantida em modelos locais por privacidade ou custo.cap = Math.max(0, capUsd) clampa para 0, e 0 significa só free tier. O padrão sem orçamento é a postura que gasta nada.| Afirmação | Mito? | Realidade |
|---|---|---|
| "Teto 0 = ilimitado" | mito | 0 = só free tier |
| "O tier alto barra a chamada cara" | mito | quem barra é o BudgetGuard |
| "O costUsd do resultado é a verdade" | mito | free-tier vira 0; pago recalcula |
| "pickCheapest compara só a saída" | mito | compara in + out somados |
Três perguntas. A pontuação corre conforme você responde — acerte as três e a revisão fecha.
createBudgetGuard(0) (ou um teto negativo). O que pode rodar?cap = Math.max(0, capUsd) clampa para 0, e o check só aprova se projectedUsd === 0 ou se couber no teto. Com cap 0, só passam as chamadas free (LOCAL/T0) — fail-closed: o padrão gasta nada.modelId fixado, qual pickCheapestForTier(T2) devolve, e a escolha é determinística?costPer1kInputUsd + costPer1kOutputUsd. É puro sobre MODEL_REGISTRY (sem Date/random), então o mesmo registry sempre dá a mesma escolha — o que importa para o replay (lição 28).ModelRunResult de um modelo free-tier traz um costUsd: 0.01 perdido. Como o record o mede?costOf retorna 0 para um modelo free-tier antes de ler costUsd. A precificação é sempre aplicada pelo tier/registry, então um campo perdido não infla o total — a medição é autoritativa, não advisória.Date.now(), new Date() e Math.random().