Aquela unidade de trabalho já passou pelo tier routing, pelo adapter e pelos gates. Agora vem a pergunta que separa demo de infraestrutura: como você sabe que ela está certa, o que aconteceu por dentro, e quanto custou — sem confiar na palavra do modelo?
Esta lição destila os princípios #15/#16/#17 do currículo e os confronta com o HARNESS-MAP.md (Parte III/IV) + os arquivos reais council/src/verifier-panel.ts, coda/src/coordinated-validator.ts, harness/src/otel.ts, adapters/src/cost.ts e etl/src/budget.ts. A tese: o Alembic verifica com oráculos determinísticos (não LLM-juiz), instrumenta com spans OTEL, mede custo per-call/per-run — e é honesto sobre o que ainda é gap (golden/adversarial set, per-tenant, drift).
spans.jsonl com chaves GenAI semconv.BudgetGuard bloqueia a próxima chamada.Depois que o modelo responde, três perguntas decidem se você tem um produto ou um truque: está certo? (evals), o que aconteceu lá dentro? (observabilidade) e quanto custou? (custo). As três têm uma regra em comum no Alembic: a resposta nunca depende de acreditar no modelo.
Pense na nossa unidade de trabalho — uma oportunidade ("fábrica de petições personalizadas") que o conselho votou GO. Um sistema ingênuo perguntaria a um modelo: "essa decisão está boa?". Mas pedir a um modelo para julgar outro modelo é como pedir ao réu que escreva a própria sentença: às vezes acerta, nunca é reprodutível. O Alembic faz diferente — ele decompõe a decisão em afirmações atômicas e prova cada uma com um oráculo determinístico: um predicado puro sobre a evidência (os votos, os scores, o quórum), que dá o mesmo veredito toda vez.
Pense como… a diferença entre um juiz de opinião ("achei convincente") e uma régua ("tem 30,0 cm"). A régua não tem um dia bom e um dia ruim. O oráculo é a régua: mede a evidência, não a retórica. Onde a analogia quebra: uma régua mede uma grandeza física fixa; o oráculo mede propriedades estruturais que nós definimos (quórum batido? veredito casa com o score?) — então a qualidade do eval é a qualidade das afirmações que você escolheu provar.
O verifier.ts define type ClaimOracle = (evidence: VerifierEvidence) => { proven: boolean; evidence: string } e comenta a invariante literal: "Same evidence in => same verdict out." O verificador é read-only por arquitetura — recebe só views imutáveis (o DebateResult do "maker" e o ContextPack), não tem adapter, não tem registry, não pode re-rodar um modelo nem editar um voto. Essa separação é o que o torna um checker e não um segundo maker.
As três perguntas mapeiam aos três princípios do currículo: #15 evals (golden/regression/adversarial/LLM-judge/human), #16 observability (traces/spans/tokens/latência/erros/drift), #17 cost attribution (per-feature/workflow/tenant). O HARNESS-MAP.md marca evals e custo como owned com gaps específicos, e observabilidade como owned e portável. Nesta lição cada parte vem com seu gap nomeado — essa honestidade é o valor.
Os três sistemas vivem em pontos diferentes do caminho da unidade de trabalho. Evals ficam nos gates (decidem emitir ou parkar). Observabilidade é transversal — escuta o barramento de eventos e grava tudo. Custo fica na entrada de cada chamada paga, antes do gasto acontecer.
BudgetGuard (laranja) intercepta antes da chamada paga; o barramento (cinza) deságua em spans.jsonl. Os três escutam o mesmo fluxo."Eval" assusta porque a indústria associa a palavra a conjuntos dourados, probes adversariais e modelos-juízes. O Alembic faz a parte mais barata e mais confiável primeiro: transforma "a decisão está sã?" em uma lista de afirmações verificáveis e prova cada uma com um predicado puro. Sem modelo no meio.
Um conselho votou GO, mas o quórum mínimo não foi batido (votos válidos de menos). Você roda o verificador. Qual veredito ele dá?
quorum é uma afirmação hard em verifier.ts (validVoteCount >= MIN_VALID_AGENTS); uma afirmação hard que falha força o veredito a rejected, sem olhar o resto. Não é "ah, mas o GO parecia forte" — a régua não negocia.O verificador de coerência (verifyDecision) decompõe a decisão do conselho em cinco afirmações, cada uma com seu oráculo:
rejected. Todas provadas → verified. Caso contrário (só soft pendente) → needs-review.Mas um verificador rodado N vezes dá o mesmo veredito N vezes — então fan-out só compra sinal se cada verificador olhar por uma lente diferente. É isso que o painel de N lentes faz: roda três perspectivas sobre a mesma evidência e agrega por quórum, com veto.
verifier-panel.ts é explícito — "A single deterministic verifier run N times yields the same verdict N times". O modelo (com sua estocástica) só aparece no voto do conselho (o "maker"); a verificação é toda predicado puro. É a separação maker-checker: quem constrói nunca é quem valida.Há um segundo portão, aditivo e opt-in (--coordinated): o coordinated-validator. Ele adapta o padrão "coordenador" — o risco da unidade escolhe quantas lentes rodam, cada lente revisa via um adapter injetado, e — o pulo do gato — há um piso determinístico que o LLM pode elevar, nunca silenciar.
assessRiskTier decide por tier + tamanho + palavras-chave.pass); PARTIAL → needs-review; FAILED → fail. PERFECT exige zero achados — o padrão mais alto é o silêncio.O que o Alembic tem (owned): regressão = a suíte de testes + o Proof Gate (cada plano declara seus proof[] como bash -c, fail-closed); golden = o gerador de cursos é byte-estável e o adapter offline é determinístico, então snapshots são testes dourados; LLM-as-jury = o voto do conselho (consenso ponderado, sub-quórum → NO_GO); human = o park T4 + approve/reject. E a separação limpa verificador determinístico vs conselho estocástico é uma força de design real.
O gap (marcado no HARNESS-MAP): não há um conjunto adversarial dedicado nem um harness de regressão de drift sobre a saída do modelo especificamente. A suíte cobre código, não qualidade do modelo ao longo do tempo. Fechar isso = um golden-set de prompts + medir a saída contra ele a cada mudança. É roadmap, não bug — e a lição te ensina a enxergar a fronteira.
FAITHFULNESS_CONFIDENCE_FLOOR = 0.5 (confiança média mínima dos sinais), FAITHFULNESS_STRENGTH_FLOOR = 3 (força de pico, escala 1–5), DEFAULT_PANEL_QUORUM = 2. O comentário do arquivo: "uma oportunidade legitimamente-forte os ultrapassa". A afirmação hard da lente é evidence-present (≥1 sinal) — sem evidência, a lente rejeita, e o veto derruba o painel.
Eval responde "está certo?". Observabilidade responde "o que aconteceu lá dentro?". No Alembic, tudo que o harness faz é publicado num barramento de eventos. Um emitter OTEL dep-free escuta esse barramento e transforma cada evento numa linha de span em <runDir>/spans.jsonl — usando as chaves padrão do OpenTelemetry para IA generativa.
"rodou o modelo, deu certo, custou alguns tokens". Legível por humano, difícil de consultar, sem estrutura, sem pais/filhos.
trace_id, span_id, name, start/end_time, attributes (chaves GenAI), status. Consultável, aninhável, portável.
"OTEL" é OpenTelemetry — um padrão aberto de telemetria. "Dep-free" quer dizer: sem a biblioteca @opentelemetry/* instalada (founder-gated). O Alembic escreve o formato à mão, então entrega o valor de observabilidade hoje; quando o SDK real for liberado, o exporter é "um swap de uma linha atrás de uma flag" — o contrato do spans.jsonl e o mapeamento de atributos GenAI não mudam.
seq 0; todo evento posterior aninha sob ele via parent_span_id = "<runId>-0". O mapeamento (toOtelSpan) é puro: lê o at do próprio evento, sem relógio de parede.gen_ai.* são as do padrão — um consumidor OTLP lê tokens/modelo sem shim. As alembic.* carregam o que é só nosso (lane, fase, tier) sem colidir. É exatamente o alembic.* que vira a casa natural do futuro tenant (o gap de custo).Como start_time === end_time, um span aqui marca quando algo aconteceu, não quanto durou. Latência fim-a-fim vem do durationMs que o funnel grava à parte. O HARNESS-MAP nota: não há divisão prefill vs decode nem TTFT — isso seria gap se o Alembic quisesse roteamento sensível a latência.
E o ponto que torna isso confiável: o emitter nunca lança exceção. Um span malformado é pulado; uma escrita que falha é engolida e logada. Telemetria jamais pode quebrar um run — exatamente como o barramento descarta um evento malformado em vez de explodir.
Toda span recebe gen_ai.system: 'alembic' + gen_ai.operation.name (o kind do evento). Quando o payload carrega modelo/uso, eles caem nas chaves padrão: gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens — então um consumidor OTLP lê tokens/modelo sem nenhum shim específico do Alembic. As facetas de orquestração (lane, phase, level, taskId, tier) usam o namespace alembic.* para coexistir com as chaves padrão.
O subscriber espelha o sseStream: bus.subscribe(listener) devolve um unsubscribe; aqui o listener vira uma linha JSONL em vez de um frame SSE. As escritas são serializadas numa cadeia de promises (chain = chain.then(...)) para preservar a ordem de emissão, e flush() espera a cauda — um teste consegue ler um spans.jsonl completo. Acesse você mesmo: rode qualquer alembic run … e abra <dataDir>/runs/<run-id>/spans.jsonl.
O export OTLP real (ligar o spans.jsonl a um Jaeger/Honeycomb) é deferido/founder-gated. E drift (qualidade ao longo do tempo) não é monitorado — spans capturam latência/tokens/erros, não regressão de qualidade da resposta.
A terceira pergunta: "quanto custou?" — e, mais importante, "como impedir que custe demais?". Aqui mora a distinção que a maioria dos sistemas erra. Um log de custo te diz quanto você já gastou (tarde demais). O BudgetGuard do Alembic projeta o custo da próxima chamada e a bloqueia antes se ela estouraria o teto.
Teto = $1,00. Você já gastou $0,98. A próxima chamada (paga, tier T2) é projetada em $0,05. O que o BudgetGuard.check retorna?
budget_exceeded — bloqueado, e a chamada paga nunca acontece. A conta é spent + projected = 0,98 + 0,05 = 1,03 > 1,00 (budget.ts, na linha roundUsd(spent + projectedUsd) > cap). O guard é fail-closed: na dúvida, não gasta. Repare: $0,98 ainda estava abaixo do teto — o que estoura é a projeção.São três camadas, cada uma pura e testável:
undefined, não 0. Reportar 0 para algo que você não sabe precificar seria uma mentira contábil — então o Alembic omite o costUsd em vez de fabricar um zero enganoso. (cost.ts: "rather than reporting a misleading zero".)check(estimate): se projectedUsd === 0 (T0/grátis) → libera na hora. Senão, se roundUsd(spent + projectedUsd) > cap → devolve o bloco tipado { ok:false, reason:'budget_exceeded', spentUsd, projectedUsd, capUsd, message }. Caso contrário → { ok:true, projectedUsd, remainingUsd }. Depois da chamada, record(spend) soma o custo real (aceita um número, um ModelRunResult ou um UsageEstimate; custo grátis soma $0). Tudo arredondado a 6 casas (roundUsd) para evitar ruído de float acumulado.
A agregação é per-run apenas. Não há rollup por feature, por tenant ou por jornada. A marketing-factory tem um brief por cliente como superfície de entrada, mas o custo não é "bucketizado" por cliente. Fechar isso conecta com a lição 0008 (multi-tenant): adicionar uma dimensão de tenant ao budget + às spans. É o mesmo gap visto de dois ângulos — isolamento e contabilidade.
Veja a régua e a trava lado a lado. À esquerda, um oráculo de coerência — um predicado puro sobre a evidência. À direita, o coração do BudgetGuard: a projeção que bloqueia a próxima chamada.
// "Same evidence in => same verdict out." (puro, sem I/O, sem modelo) { claim: { id: 'verdict-matches-score', statement: 'The board verdict matches its aggregate score thresholds.', }, oracle: (e) => { const score = e.consensus.aggregateScore; const expected = score >= GO_THRESHOLD ? 'GO' : score >= PIVOT_THRESHOLD ? 'PIVOT' : 'NO_GO'; return { proven: e.consensus.decision === expected, // régua, não opinião evidence: `aggregate=${score} expected=${expected} actual=${e.consensus.decision}`, }; }, }
check(estimate: UsageEstimate): BudgetCheck { const projectedUsd = priceEstimate(estimate); if (projectedUsd === 0) { // T0/grátis sempre libera return { ok: true, projectedUsd: 0, remainingUsd: remaining() }; } if (roundUsd(spent + projectedUsd) > cap) { // fail-closed return { ok: false, reason: 'budget_exceeded', spentUsd: roundUsd(spent), projectedUsd, capUsd: cap, message: `projected $${projectedUsd} on top of $${roundUsd(spent)} exceeds cap $${cap}`, }; } return { ok: true, projectedUsd, remainingUsd: remaining() }; }
export const toOtelSpan = (event: HarnessEvent): OtelSpan => { const spanId = `${event.runId}-${event.seq}`; const base = { trace_id: event.runId, // um run = um trace span_id: spanId, name: event.kind, start_time: event.at, end_time: event.at, // ponto no tempo attributes: buildAttributes(event), // gen_ai.* + alembic.* status: buildStatus(event), }; // a raiz (seq 0) não tem pai; todo span posterior aninha sob ela return event.seq === 0 ? base : { ...base, parent_span_id: `${event.runId}-0` }; };
No repo /Users/acf/Documents/Projects/appfy/alembic: os oráculos estão em packages/council/src/verifier.ts (coerência) e verifier-panel.ts (as três lentes); o coordinated-validator em packages/coda/src/coordinated-validator.ts; o guard em packages/etl/src/budget.ts e a precificação em packages/adapters/src/cost.ts; o emitter em packages/harness/src/otel.ts. Rode pnpm --filter @alembic/council test para ver os oráculos sob teste, e qualquer alembic run … gera o spans.jsonl que você pode abrir.
Dois laboratórios ao vivo. O primeiro deixa você derrubar lentes e ver o veredito do painel virar (incluindo o veto hard). O segundo é um simulador do BudgetGuard: dispare chamadas e veja a trava agir antes do gasto.
Cada interruptor representa uma lente verificando (verde) ou falhando. A lente faithfulness tem uma afirmação hard — se ela falha, é veto. Tente: derrube só a domain (soft) e veja o quórum segurar; depois derrube a faithfulness e veja o veto.
needs-review se o quórum não for mais batido; derrubar a lente hard derruba para rejected na hora, mesmo com as outras duas verdes. Veto > quórum.Teto fixo de $1,00. Dispare chamadas; a barra sólida é o gasto, a faixa hachurada é a projeção da próxima. Quando gasto + projeção > teto, a chamada é bloqueada e o gasto não sobe. Note que a chamada T0 nunca é bloqueada.
Acompanhe a unidade "fábrica de petições" cruzando os três sistemas, passo a passo.
DebateResult: aggregateScore acima do GO_THRESHOLD, quórum batido. O BudgetGuard.check já tinha liberado cada chamada de voto (cada uma projetada e somada).verifyDecision roda os cinco oráculos sobre a evidência. Todas as hard (quórum, veredito↔score, votos consistentes) provam; as soft também. Veredito da lente de coerência: verified.verifyPanel roda faithfulness (a evidência do ContextPack tem confiança média ≥ 0,5 e força de pico ≥ 3) e domain (o GO tem um sinal de validation e há ≥2 tipos de sinal). 3/3 verificam → painel verified, emissão liberada.spans.jsonl — o voto carrega gen_ai.usage.*, o gate carrega alembic.phase. Tudo aninhado sob run-0.costUsd = budget.spentUsd() — a soma per-run. Agora você tenta: e se um voto fosse incoerente (score alto mas decision NO_GO)? Qual oráculo pega, e qual veredito sai? (Resposta: votes-self-consistent, que é hard → rejected.)| Dimensão | Oráculo determinístico (Alembic) | LLM-as-judge |
|---|---|---|
| Reprodutibilidade | Total — mesma evidência, mesmo veredito N× | Estocástica — pode variar entre rodadas |
| Custo por eval | $0 (predicado puro, sem chamada) | Custo de uma chamada de modelo por eval |
| O que mede | Propriedades estruturais que você define | Julgamento aberto (flexível, porém opaco) |
| Auditável | Sim — cada oráculo devolve a evidência consultada | Difícil — a "razão" é prosa não-verificável |
| Onde o Alembic usa | Verificação de gates (verifier panel) | Só o voto do conselho (o maker), nunca o checker |
Soma o que já gastou. Útil para relatório, inútil para impedir o estouro — o dinheiro já saiu quando você lê.
Reativo · informa · não bloqueia
Projeta a próxima chamada e bloqueia antes se estouraria. Fail-closed: na dúvida, não gasta. T0 sempre passa.
Preventivo · decide · bloqueia
record() para manter o total (vira o "log" per-run) e check() para o pre-flight. Mesma fonte de verdade, dois usos.durationMs do funnel, à parte. (gap: sem prefill/decode split.)0 para algo impreciso seria mentira contábil. O Alembic omite o costUsd em vez de fabricar um zero enganoso.spent + projected > cap, antes da chamada paga. T0 (projeção $0) sempre passa. Fail-closed.rejected.spans.jsonl com chaves GenAI.seq 0; é ponto no tempo, never-throws, exporter real = swap de uma linha.undefined.Responda cada uma; a explicação aparece em cada opção.
verifier-panel.ts é literal: "a single deterministic verifier run N times yields the same verdict N times". O modelo (estocástico) só aparece no voto do conselho (o maker); a verificação é toda oráculo puro. (a) confunde com a tier do modelo; (c) inverte o fluxo.BudgetGuard.check retorna…spent + projectedUsd e bloqueia se > cap, antes da chamada paga (budget.ts). (a) ignora a projeção — o que estoura é a soma futura; (b) inverte: o pre-flight é justamente antes do gasto.anyRejected → 'rejected' tem prioridade sobre a contagem de quórum (verifier-panel.ts). Veto > quórum. (b) aplicaria se ninguém rejeitasse hard; (c) seria o caso de só ter soft pendente sem bater quórum.