Passo 7 · Módulo 3 · Contexto & operação · Contexto & operação · Evals, observabilidade & custo
Curso de Harness Engineering · pela lente do Alembic · Visual Course

Evals, observabilidade e custo

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?

Leia primeiro (fonte primária)
walkinglabs — "Learn Harness Engineering" (PT-BR): evals, observability & cost attribution

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).

Suposições tolas (o que presumimos de você)
  • Você já viu as lições anteriores ou aceita os termos: gate, tier (T0–T4), Result never-throws, cintura estreita. Se não, o glossário do curso cobre cada um.
  • Você sabe o que é JSON e já ouviu "log" e "métrica". Não precisa conhecer OpenTelemetry — vamos construir a intuição do zero.
  • Você não precisa saber estatística de avaliação de modelos. A lição mostra por que o Alembic foge dela de propósito.
Ao final desta lição você vai saber
  • Distinguir oráculo determinístico de LLM-as-judge — e por que o Alembic tira o modelo do veredito.
  • Ler o painel de N lentes (coherence/faithfulness/domain) com quórum + veto, e o piso determinístico do coordinated-validator.
  • Explicar o emitter OTEL dep-free: cada evento vira um span em spans.jsonl com chaves GenAI semconv.
  • Diferenciar custo per-call de per-run, e entender por que o BudgetGuard bloqueia a próxima chamada.
  • Nomear os três gaps honestos: golden/adversarial set, custo per-tenant/per-feature, e monitoramento de drift.
Leia a versão simples, ou abra a camada técnica em qualquer seção.
1

A grande ideia


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.

Por baixo do capô

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.

A clean teaching diagram contrasting two ways to judge an AI decision. Left half: a precise measuring ruler laid against a small object, with a single fixed reading repeated three
A clean teaching diagram contrasting two ways to judge an AI decision. Left half: a precise measuring ruler la
2

Em uma imagem


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.

pedido tier route + adapter Proof Gate Verifier (N lentes) park (T4) ou ship BudgetGuard.check barramento de eventos → emitter OTEL → spans.jsonl verde = oráculo determinístico laranja = custo / decisão de gasto cinza tracejado = observabilidade
Esquerda → direita: o caminho da unidade. Os gates (verde) decidem emitir ou parkar com oráculos; o BudgetGuard (laranja) intercepta antes da chamada paga; o barramento (cinza) deságua em spans.jsonl. Os três escutam o mesmo fluxo.
A regra que une os três: nenhum deles confia na prosa do modelo. Eval prova evidência estruturada; observabilidade grava fatos do evento; custo soma tokens reais contra um teto. Tudo é fail-closed e determinístico onde dá.
três perguntas · três sistemas · uma regra (não confiar na prosa do modelo) Está certo? EVALS oráculos determinísticos + piso + painel de lentes O que aconteceu? OBSERVABILIDADE emitter OTEL dep-free spans.jsonl (GenAI semconv) Quanto custou? CUSTO per-call + BudgetGuard fail-closed + per-run
O mapa-mãe da lição: cada pergunta tem um sistema, e cada sistema obedece à mesma regra — a resposta vem de fato estruturado, não da retórica do modelo.
3

Evals: oráculos, não juiz


"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.

Faça sua aposta antes de revelar

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á?

rejected — e na hora. 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.
anatomia de UM oráculo (ClaimOracle) — puro, mesma entrada → mesma saída VerifierEvidence consensus · votes · pack (imutável, read-only) predicado puro sem I/O · sem modelo NUNCA lê a prosa do maker { proven: boolean, evidence: string } a evidência que consultou
Um oráculo recebe só evidência imutável, aplica um predicado puro, e devolve se provou + qual evidência usou. Auditável por construção — a "razão" não é prosa, é o dado.

O verificador de coerência (verifyDecision) decompõe a decisão do conselho em cinco afirmações, cada uma com seu oráculo:

decomposeClaims() — verifier.ts quorum — validVoteCount ≥ MIN_VALID_AGENTS HARD not-fail-closed — !consensus.failClosed soft verdict-matches-score — decision == thresholds HARD consensus-spread — spread ≤ THRESHOLD soft votes-self-consistent — cada voto casa o próprio score HARD qualquer HARD falha → rejected
Cinco afirmações; três são hard (quórum, veredito↔score, votos consistentes). Uma hard que falha → 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.

verifyPanel() — verifier-panel.ts · quorum = 2 de 3 COHERENCE a decisão é sã por dentro? (quórum, score, spread) FAITHFULNESS ancorada no ContextPack? conf ≥ 0.5 · força ≥ 3 DOMAIN invariantes do negócio (GO tem validation? diverso?) agrega: ≥2 verificam → verified QUALQUER rejeição hard → rejected (veto)
Três lentes perspectiva-diversas. Quórum padrão = 2 de 3 para emitir. O preserve-dissent veto: uma rejeição hard em qualquer lente derruba o painel inteiro, mesmo que as outras duas tenham verificado.
Por que isso não é LLM-as-judge: o comentário em 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.

coordinated-validator.ts — o piso que o LLM só eleva PISO DETERMINÍSTICO (sem LLM, sem juízo) proof falhou → critico · vazio → alto · TODO/FIXME → medio · placeholder → critico fail-closed: qualquer critico → FAILED · qualquer alto → PARTIAL · zero achados → PERFECT LENTES LLM (escolhidas pelo risco: trivial=2 · lite=4 · full=6) requirements · completeness · coherence · correctness · security · risk — cada uma só ADICIONA severidade só sobe ↑
Lente que dá erro degrada o run (registrado, não-bloqueante) — nunca derruba o portão nem apaga o piso. Esse é o ponto: a parte automática (o piso) é à prova de modelo; a parte cara (LLM) só agrava.
risco → nº de lentes (LENSES_BY_TIER) trivial 2 lentes lite full T4 ou high-stakes (legal/secret/irreversible) → sempre full
Mais risco, mais lentes — cada lente extra é uma chamada de modelo, então o tier barato roda menos. assessRiskTier decide por tier + tamanho + palavras-chave.
achados → nota (gradeFindings, fail-closed) algum critico → FAILED algum alto → PARTIAL qualquer achado → VERIFIED zero achados → PERFECT
Só PERFECT/VERIFIED liberam emissão (verdict pass); PARTIAL → needs-review; FAILED → fail. PERFECT exige zero achados — o padrão mais alto é o silêncio.

O que é, e o que ainda NÃO é (o gap honesto de evals)

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.

Os pisos de faithfulness (calibrados)

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.

A teaching illustration of a three-lens inspection panel converging on one verdict. Three separate circular lenses arranged left to right, each a different colour, each pointed at
A teaching illustration of a three-lens inspection panel converging on one verdict. Three separate circular le
4

Observabilidade: o evento vira span


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.

a mesma coisa que aconteceu, em dois formatos LOG (texto solto) "rodou o modelo, ok, gastou tokens" sem campos · sem pai/filho · difícil consultar SPAN (fato estruturado) trace_id · span_id · parent_span_id name · start/end_time · status attributes (gen_ai.*) · consultável · aninhável
O log é prosa; o span é dado. Só o dado se aninha numa árvore, se consulta por chave e exporta para um backend sem reparse.
log (texto solto)

"rodou o modelo, deu certo, custou alguns tokens". Legível por humano, difícil de consultar, sem estrutura, sem pais/filhos.

span (fato estruturado)

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.

um run = um trace (trace_id = runId) · cada evento = um span span_id = run-0 (raiz, sem parent) name: run.start · gen_ai.system: alembic run-1 · tier.route · alembic.tier: T2 · parent: run-0 run-2 · model.call · gen_ai.usage.input_tokens / output_tokens run-3 · error · status.code: error · message: … start_time == end_time (um evento de console é um PONTO no tempo, não um intervalo)
O span raiz é 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.
buildAttributes() — dois namespaces convivendo numa span gen_ai.* (padrão OTLP — portável) gen_ai.system: alembic gen_ai.operation.name: <kind> gen_ai.request.model gen_ai.usage.input_tokens / output_tokens alembic.* (facetas de orquestração) alembic.run_id · alembic.seq alembic.lane · alembic.level alembic.phase · alembic.tier alembic.task_id
As chaves 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).
Ponto no tempo, não duração (a nuance honesta)

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.

O mapeamento de atributos (GenAI semconv + alembic.*)

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.

Gaps de observabilidade

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 teaching diagram of a budget gate that stops spending before it happens. A horizontal fuel-gauge style meter with a solid filled portion (money already spent) and a hatched porti
A teaching diagram of a budget gate that stops spending before it happens. A horizontal fuel-gauge style meter
5

Custo: a trava vem ANTES do gasto


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ê gastou (tarde demais). O BudgetGuard do Alembic projeta o custo da próxima chamada e a bloqueia antes se ela estouraria o teto.

Faça sua aposta antes de revelar

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:

PER-CALL accountFor(modelId, usage) preço × tokens (do registry) desconhecido → undefined PRE-FLIGHT BudgetGuard.check(estimate) spent + projected > cap? sim → budget_exceeded (bloqueia) PER-RUN funnel: costUsd = budget.spentUsd() soma do run inteiro — e o gap: não há rollup per-feature / per-tenant / per-journey — fechar = taggar spans/budget com uma dimensão tenant (o namespace alembic.* é a casa natural)
Per-call mede; pre-flight bloqueia; per-run soma. T0 é sempre $0 (a tier T0 precifica em zero), então o substrato grátis nunca é bloqueado, qualquer que seja o teto.
Detalhe que revela a cultura: custo de modelo desconhecido retorna 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".)

A semântica exata do guard

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.

O gap de atribuição (per-tenant/per-feature)

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.

6

No código


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.

packages/council/src/verifier.ts — um dos cinco oráculos
// "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}`,
    };
  },
}
packages/etl/src/budget.ts — check() bloqueia a PRÓXIMA chamada
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() };
}
packages/harness/src/otel.ts — evento → span (puro, sem relógio)
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` };
};

Acesse você mesmo

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.

7

Experimente


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.

Lab A — o painel de N lentes flipa o veredito

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.

coherence (quórum, score, spread)
faithfulness (evidência ≥ 1) hard
domain (GO tem validation? diversidade?)
Veredito do painel: verified 3/3 lentes verificam · quórum 2 batido.
DicaRepare numa assimetria: derrubar uma lente soft só baixa para 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.

Lab B — o BudgetGuard bloqueia antes de gastar

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.

teto: $1,00 gasto: $0,00 restante: $1,00
Dispare uma chamada — o guard projeta o custo e decide ANTES.
LembreO guard não é um relatório do que já gastou — é um portão na entrada. A decisão acontece antes da chamada paga sair. Por isso ele limita a confiabilidade do loop sem precisar de um contador de iterações (ver lição 0005).

Recapitulando em slides

Três perguntas

Está certo? O que aconteceu? Quanto custou?

Evals, observabilidade e custo. A regra comum: nenhuma resposta depende de acreditar no modelo.

Evals

Oráculo determinístico, não LLM-juiz

Decompõe a decisão em afirmações atômicas; prova cada uma com um predicado puro. Mesma evidência → mesmo veredito.

evidênciaveredito (N×)

Painel + piso

N lentes, quórum + veto; piso só sobe

3 lentes (coherence/faithfulness/domain), quórum 2, veto hard. O coordinated-validator tem um piso que o LLM eleva, nunca silencia.

Observabilidade

Cada evento vira um span em spans.jsonl

Emitter OTEL dep-free, chaves GenAI semconv, um run = um trace. Ponto no tempo, never-throws, exporter real = swap de uma linha.

run-0 (raiz)run-1 · parent run-0

Custo

A trava vem ANTES do gasto

Per-call mede, pre-flight bloqueia a próxima chamada, per-run soma. T0 = $0. Modelo desconhecido → undefined, nunca um zero enganoso.

Os gaps honestos

Golden/adversarial · per-tenant · drift

Sem conjunto adversarial, sem rollup por tenant/feature, sem monitor de drift. Owned com fronteiras nomeadas — essa honestidade é o roadmap.

1 / 6setas
Você é o professor agora: pergunte-se "se eu adicionasse um conjunto adversarial de 50 prompts, onde no pipeline ele rodaria — e contra qual oráculo?". A próxima lição (0008 · Segurança, multi-tenant & shipping) fecha o curso com o gap que conecta custo e isolamento — e o clímax: o sinal do usuário que ninguém realimenta.
8

Exemplo guiado


Acompanhe a unidade "fábrica de petições" cruzando os três sistemas, passo a passo.

A unidade atravessa evals → observabilidade → custo
1
O conselho vota e produz um DebateResult: aggregateScore acima do GO_THRESHOLD, quórum batido. O BudgetGuard.check já tinha liberado cada chamada de voto (cada uma projetada e somada).
2
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.
3
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.
4
Cada passo acima publicou um evento no barramento; o emitter OTEL gravou um span por evento em spans.jsonl — o voto carrega gen_ai.usage.*, o gate carrega alembic.phase. Tudo aninhado sob run-0.
5
Ao final, o funnel reporta 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.)
9

Dois comparativos


Oráculo determinístico vs LLM-as-judge

DimensãoOráculo determinístico (Alembic)LLM-as-judge
ReprodutibilidadeTotal — 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 medePropriedades estruturais que você defineJulgamento aberto (flexível, porém opaco)
AuditávelSim — cada oráculo devolve a evidência consultadaDifícil — a "razão" é prosa não-verificável
Onde o Alembic usaVerificação de gates (verifier panel)Só o voto do conselho (o maker), nunca o checker
maker-checker: quem constrói NUNCA é quem valida MAKER (conselho) chama o modelo · vota · decide estocástico (LLM-as-jury) produz DebateResult + ContextPack views read-only CHECKER (verifier) oráculos puros sobre a evidência determinístico · sem adapter · sem mutação não re-roda modelo · não edita voto
O checker só recebe views imutáveis e não tem superfície de mutação — é o que o torna um checker e não um segundo maker. A estocástica fica de um lado da seta; a régua, do outro.

Log de custo vs BudgetGuard pre-flight

Log de custo (post-hoc)

Soma o que gastou. Útil para relatório, inútil para impedir o estouro — o dinheiro já saiu quando você lê.

Reativo · informa · não bloqueia

BudgetGuard (pre-flight)

Projeta a próxima chamada e bloqueia antes se estouraria. Fail-closed: na dúvida, não gasta. T0 sempre passa.

Preventivo · decide · bloqueia

Nota técnicaOs dois coexistem: o guard usa record() para manter o total (vira o "log" per-run) e check() para o pre-flight. Mesma fonte de verdade, dois usos.

Flashcards — vire para recuperar

Eval
O que torna um oráculo "determinístico"?
clique para virar
É um predicado puro sobre a evidência (votos, scores, quórum) — sem I/O, sem modelo. Mesma evidência → mesmo veredito sempre. Nunca lê a prosa do maker.
Painel
Quórum vs veto — qual ganha?
clique para virar
O veto. Quórum (2 de 3) decide emitir; mas uma rejeição hard em qualquer lente derruba o painel inteiro, mesmo com as outras verdes.
Observabilidade
Por que start_time == end_time?
clique para virar
Um evento de console é um ponto no tempo, não um intervalo. Latência fim-a-fim vem do durationMs do funnel, à parte. (gap: sem prefill/decode split.)
Custo
Por que custo desconhecido → undefined?
clique para virar
Reportar 0 para algo impreciso seria mentira contábil. O Alembic omite o costUsd em vez de fabricar um zero enganoso.
Custo
Quando o BudgetGuard bloqueia?
clique para virar
Quando spent + projected > cap, antes da chamada paga. T0 (projeção $0) sempre passa. Fail-closed.
Gap
Cite os três gaps desta lição
clique para virar
(1) conjunto golden/adversarial + harness de drift; (2) custo per-tenant/per-feature; (3) export OTLP real + monitor de drift de qualidade.
o gap de drift — o que os spans VEEM e o que eles NÃO veem ao longo do tempo t capturado: latência · tokens · erros (spans) NÃO capturado: qualidade da resposta caindo (gap) ↘ ninguém mede isto
Spans gravam latência, tokens e erros — a linha sólida. Mas a qualidade da resposta pode degradar silenciosamente (a linha tracejada) sem disparar nada: não há monitor de drift nem conjunto adversarial. É o gap honesto que conecta evals (#15) e observabilidade (#16).
10

As dez verdades


As Dez de evals, observabilidade e custo
  1. As três perguntas — está certo? o que aconteceu? quanto custou? — separam infraestrutura de demo.
  2. O Alembic verifica com oráculos determinísticos, não com um modelo-juiz: mesma evidência, mesmo veredito.
  3. O verificador é read-only por arquitetura — não tem adapter nem mutação; é checker, não um segundo maker.
  4. Cinco afirmações na coerência; três são hard. Uma hard que falha → rejected.
  5. O painel roda três lentes perspectiva-diversas; quórum 2 de 3, com veto de rejeição hard.
  6. O coordinated-validator tem um piso determinístico que o LLM eleva, nunca silencia; lente que falha degrada, não derruba.
  7. Observabilidade = um emitter OTEL dep-free; cada evento vira um span em spans.jsonl com chaves GenAI.
  8. Um run = um trace; spans aninham sob a raiz seq 0; é ponto no tempo, never-throws, exporter real = swap de uma linha.
  9. Custo: per-call mede, pre-flight bloqueia a próxima chamada, per-run soma. T0 = $0; desconhecido → undefined.
  10. Os gaps honestos — golden/adversarial, per-tenant, drift — são roadmap nomeado. A honestidade é o valor.
11

Cheque seu entendimento


Três perguntas — sua pontuação aparece embaixo

Responda cada uma; a explicação aparece em cada opção.

1. Por que o verificador de gates do Alembic NÃO é "LLM-as-judge"?
É a (b). O comentário do 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.
2. Teto $2,00, já gastou $1,90, próxima chamada paga projetada em $0,30. O BudgetGuard.check retorna…
É a (c). O guard projeta 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.
3. No painel de N lentes, 2 lentes verificam mas a faithfulness dá rejeição hard (zero evidência). Veredito?
É a (a). O "preserve-dissent veto": 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.
Acertos: 0/3
Da próxima vez que alguém disser "a gente avalia com um modelo-juiz", você vai ouvir a pergunta que falta: "e quando ele tem um dia ruim?". A régua não tem.