Curso / Lição 15  ·  EN
Lição 15 · Motor & método · 2 de 8

O funil: um ETL de 4 camadas com piso de $0

O funil é como o Alembic transforma um corpus de fontes brutas em sinais de negócio validados e learnings — a um custo que começa em exatamente $0 e só sobe à medida que candidatos provam valer o gasto. É uma cascata de quatro camadas: T0 determinística (grátis) → T1 local (~grátis) → T2 shortlist de fronteira medida → T3 council + painel verificador. Só um GO verificado emite. É o motor de dados do qual o ciclo de aprendizado se alimenta. Fonte: packages/harness/src/funnel.ts.

A cascata — estreite o gasto, não o corpus

A camada mais barata toca 100% do corpus; cada camada seguinte é mais cara mas vê apenas os sobreviventes da anterior. A curva de custo verga na direção certa:

T0 · walk determinístico → dedupe SHA-256 → valida contrato → score 6-dim → resíduo $0 T1 · um BusinessSignal por item de resíduo · adapter LOCAL (free tier) ~$0 T2 · shortlist FRONTIER medida refina os T1 mais fortes medido T3 · council + painel N-lentes medido só um GO-verificado (GO ∧ painel-aprovado) sobrevive até o fundo e emite
CamadaO que fazCusto
T0runT0Pipeline: walk → dedupe SHA-256 → valida contrato → score 6-dim → emite resíduo, sobre 100% do corpus (excluindo Repos/Models + Repos/Prompts)$0
T1runT1Extraction: um BusinessSignal por item de resíduo via o adapter LOCAL injetado; free-tier, então na prática nunca bloqueado por orçamento~$0
T2runT2Shortlist: uma shortlist FRONTIER com portão de orçamento refina os sinais T1 mais fortes em lotes; toda chamada paga é medidamedido
T3runT3Council: um council sintético de 3 membros (otimista/analista/pessimista, atende MIN_VALID_AGENTS=3) + o painel verificador N-lentesmedido

O sinal GO-verificado — duas travas, não uma

Um resultado T3 só emite quando ambos: a decisão de consenso é GO e o painel N-lentes aprovou a emissão (verificado, não estacionado). Um GO puro não basta:

// packages/harness/src/funnel.ts:496-514 (condensado) — o portão de emissão
const verified =
  consensus.decision === 'GO' &&
  isPanelEmissionApproved(report);     // painel N-lentes: verificado, NÃO estacionado
if (verified) {
  // emite arestas de oportunidade + learnings; expõe em verifiedSignals (PII-safe)
}

Os BusinessSignal[] GO-verificados são expostos em FunnelReport.verifiedSignals — a ponte PII-safe para a marketing factory (distillAndMarket). A lição 18 cobre o painel; o ponto aqui é que o funil exige consenso e verificação independente antes de gastar esforço a jusante ou emitir qualquer coisa para fora.

Três invariantes de segurança que o funil nunca pode regredir

① PII antes de egress

Um sinal de um canal PRIVADO (whatsapp, discord, skool, circle) é redigido antes da chamada de modelo (extractionInput redige) e protegido de novo por assertRedactedForEmit antes de qualquer escrita (emitSafeSignal). Um sinal de canal privado não-redigido é descartado, nunca emitido. FunnelReport.t1PiiBlocked é um alarme não-zero. Governado pela ADR-0011.

② Orçamento fail-closed

Toda chamada paga (T2/T3) é envolvida por um BudgetGuard.check fail-closed antes do despacho; uma violação projetada bloqueia a chamada e a camada degrada em vez de estourar o gasto. A precificação sempre usa a tarifa de tier do registry (pricingModelId), nunca um override de catálogo — então um modelo de gateway sobrescrito que não está no registry ainda é medido contra o teto pela tarifa do seu tier. Você não consegue contornar o orçamento por acidente.

③ Append-only

Resultados fluem para os dois stores via escritas append-only, content-addressed, validadas por schema; leituras da fonte permanecem read-only. As duas saídas são o grafo de oportunidades BUSINESS (Business/opportunity-graph.jsonl) e o store de LEARNINGS (Skills/learning/learnings.jsonl) — as duas cadeias de valor da ADR-0002.

Por que o funil vive no harness L4, e não no etl. Ele orquestra adapters L1 + council L2 + etl L0. Colocá-lo em etl forçaria o etl a depender para cima de adapters/council, invertendo o grafo de camadas. Então a T0 determinística fica em @alembic/etl (puro, só-contracts) e o orquestrador que chama T0→T3 vive em @alembic/harness. A estratificação é preservada por onde o código fica, não por convenção.
1. Um corpus tem 10.000 itens. Aproximadamente quantos chegam à camada paga T2?
Correto: b. T0 (grátis) toca 100%; T1 (local free-tier) extrai um sinal por item de resíduo; T2 (medida) refina só os sinais T1 mais fortes em lotes. O funil estreita o gasto, pagando tarifas de fronteira só para candidatos que já passaram por dois filtros mais baratos.
2. O consenso é GO mas o painel N-lentes estacionou uma lente. O funil emite o sinal?
Correto: d. O GO-verificado é uma conjunção de duas travas independentes: consenso GO e um painel verificador aprovado (verificado, não estacionado). Qualquer trava falhando significa que nada é emitido para verifiedSignals.
3. Um sinal de origem WhatsApp de algum modo chega ao passo de emissão ainda não-redigido. O que acontece?
Correto: a. PII é redigida antes da chamada de modelo e re-checada antes de qualquer escrita. Um sinal de canal privado não-redigido nunca é escrito; o contador o sinaliza. Fail-closed, não crash — invariante 1 do funil.

Confusões comuns

"Offline significa resultados degradados." Para T0/T1 significa hermético, determinístico e $0 — as camadas locais são o piso do design, não um fallback. alembic distill <corpus> --offline roda o pipeline inteiro com um registry de adapters offline e nunca toca uma API paga.
"O teto de orçamento pode ser contornado com um override de modelo." Não — a precificação sempre resolve pela tarifa de tier do registry (pricingModelId), então até um modelo de gateway sobrescrito no catálogo é medido pelo seu tier. O budget guard vê o custo projetado independentemente de qual nome de modelo concreto foi fixado.