Passo 2 · Mód 1 · Fundamentos · O funil de destilação
Curso do Produto Alembic · Visual Course

O funil de destilação

Ao fim desta lição você vai pegar o corpus de IA jurídica bruto, passá-lo pelo funil distill e ver nascer a outra metade do par: as LEARNINGS citáveis e o BUSINESS SIGNAL "fábrica de petições personalizadas" — tudo offline, $0, sem fabricar nada.

Leia primeiro (fonte primária)
packages/etl/src/pipeline.ts — "O T0 do funil, linha por linha"

É o coração do funil: caminha o corpus em stream, deduplica por SHA-256, valida o contrato e emite residue para os tiers superiores. Entender este arquivo é entender por que o motor existe — destilar a WIKI_LLM (~330 GB) em sinais acionáveis, sem humano no loop.

Leia a versão simples, ou abra a camada técnica em qualquer seção.

Na lição 1 você viu o Alembic como um par: um motor de destilação e um harness de agentes. Esta lição abre a primeira metade — o funil. É o trecho da história em que o corpus de IA jurídica deixa de ser uma pilha de transcripts e bookmarks e vira duas coisas que você pode usar: o que aprendemos (LEARNINGS) e onde está o dinheiro (BUSINESS SIGNALS).

Suposições tolas (o que assumimos de você)
  • Você leu a lição 1 — ou ao menos sabe que o Alembic tem duas metades.
  • Você consegue abrir um terminal e rodar um comando. Não precisa saber TypeScript: a camada "Técnico" está sempre a um clique, e dá pra pular.
  • Você não precisa de nenhum modelo de IA instalado: tudo aqui roda offline, $0, por padrão.
Ao final desta lição você vai conseguir
  • Explicar o que o funil distill recebe (corpus) e o que ele produz (LEARNINGS + SIGNALS).
  • Descrever os tiers T0→T3 e por que um item só "sobe" se merecer (o residue).
  • Reconhecer os cinco comandos do funil — distill, ingest, wiki-bridge, embed-index, vision-index — e status.
  • Entender por que a privacidade é fail-closed: um sinal de canal privado não vaza com PII.
  • Rodar alembic status e ler as duas lojas append-only que ele conta.
1

A grande ideia


Imagine que você juntou, ao longo de meses, centenas de transcripts de vídeos, bookmarks e repositórios sobre um tema — digamos, IA aplicada ao Direito. É muito material e quase nada está organizado. O funil de destilação é a máquina que lê tudo isso de forma barata e cospe duas coisas separadas: um caderno de aprendizados (com a fonte de cada um) e uma lista de oportunidades de negócio.

No nosso exemplo, dessa pilha de IA jurídica o funil extrai o sinal: "existe dor real e repetida em redigir petições; dá pra montar uma fábrica de petições personalizadas". Esse sinal é a faísca que, nas próximas lições, vira uma venture e a campanha da C.D Advocacia.

Pense como… um alambique (daí o nome Alembic): você despeja um caldo turvo e ele destila dois líquidos limpos em frascos diferentes. A analogia quebra num ponto: um alambique trata todo o caldo igual; o funil é seletivo — a maior parte do material é processada de graça, e só uma minoria "promissora" sobe para um tratamento mais caro.

Por baixo do capô

O funil é o pacote @alembic/etl — descrito no próprio código como "the L0 deterministic substrate (Tier T0, $0)". É um pipeline em tiers: o nível T0 é determinístico e gratuito (sem modelo); níveis superiores (T1–T3) gastam orçamento de modelo só no que passou pelo filtro anterior. A entrada é um corpus de pacotes-wiki; a saída são duas lojas JSONL append-only.

"Offline, $0, determinístico" não é um detalhe: é a disciplina central do produto. O mesmo corpus rodado duas vezes produz exatamente o mesmo resultado, e a segunda execução não reprocessa nada (dedupe por SHA-256). Isso é o que torna o motor confiável o suficiente para rodar sem um humano vigiando.

caldo turvo corpus de IA jurídica alembic o funil (distill) frasco olivo · LEARNINGS o que aprendemos frasco clay · SIGNALS a oportunidade "fábrica de petições" 🔥
A analogia, desenhada: o caldo turvo (corpus) entra no alambique e sai como dois destilados limpos — e o frasco clay contém o nosso sinal nomeado.
A wide concept illustration of a tall copper distillation funnel on a workshop bench: a turbid dark mixture is poured into the wide top, and from the narrow bottom two clean liquid
A wide concept illustration of a tall copper distillation funnel on a workshop bench: a turbid dark mixture is
2

O funil em uma imagem


Leia da esquerda para a direita: o corpus entra, passa por quatro tiers cada vez mais caros e seletivos, e sai pelos dois bicos — LEARNINGS e SIGNALS. A largura encolhe a cada tier de propósito: menos itens, mais atenção.

corpus bruto transcripts · repos T0 dedupe · valida pontua · $0 T1 extrai · PII T2 shortlist · budget T3 verifica SIGNALS oportunidade LEARNINGS aprendizado residue ↑ (sobe de tier) excluído
O funil estreita a cada tier: o corpus de IA jurídica entra inteiro no T0; só o que merece sobe via residue; no fim, dois bicos enchem duas lojas distintas.

Por que rodar de novo é barato: dedupe por SHA-256

O T0 calcula um "carimbo" (hash SHA-256) de cada item. Se já viu aquele carimbo, pula. Por isso a segunda execução do mesmo corpus não reprocessa nada — e adicionar 10 itens novos custa só os 10 novos.

item A (1ª vez) item A (2ª vez) sha256Hex(raw)visto antes? não sim processa → scored duplicate (pula) ledger append-only
O ledger de itens processados é append-only; o hash decide entre scored e duplicate. Re-rodar converge para zero trabalho novo.
Antes de continuar — adivinhe

O corpus tem 10.000 itens. Você acha que o funil chama um modelo de IA (que custa dinheiro) para quantos deles no tier T0?

Zero. O T0 é 100% determinístico e $0 — nenhum modelo é chamado. Ele só deduplica, valida o contrato e pontua com heurística estrutural. Modelos só entram nos tiers superiores, e apenas para o subconjunto que o T0 marcou como "residue" (digno de subir). É assim que se processa 330 GB sem quebrar o orçamento.

3

Os tiers T0→T3


Um tier é uma faixa de tratamento. Quanto mais alto, mais caro e mais cuidadoso — então o funil só promove um item para o tier seguinte se ele passar no filtro do tier atual. Pense numa peneira de cascalho: a primeira peneira é grossa e rápida; só o que fica retido vai para a peneira fina, e só o que sobra dali vai para o ourives examinar.

T0 dedupe SHA-256 valida contrato pontua 0–30 $0 · sem modelo T1 extrai sinais redige PII pii-blocked ✕ T2 shortlist budget-blocked ✕ T3 verifica custo & rigor →
Cada tier filtra para o próximo. O bloqueio aparece no relatório: pii-blocked no T1, budget-blocked no T2 — nada vaza nem estoura o orçamento em silêncio.

Quem decide o tier? Os priors de família

Antes de qualquer modelo, o T0 usa uma regra barata e determinística: de qual família o item veio. Transcripts e comunidades costumam ter sinal fresco, então sobem; bookmarks e repos são material de referência, tratados barato. É um palpite estrutural — o "prior".

FAMILY_PRIORS — tier-alvo por família (packages/etl/src/priors.ts) T2 T1 T0 Transcr. Discord Circle Skool Whatsapp Bookmarks Skills Repos Unknown
O corpus de IA jurídica tem transcripts de palestras (sobem para T2, extração rica) e bookmarks de artigos (ficam no T1). O prior decide isso sem gastar um centavo.
Papo técnico — pode pular O floor é configurável. runT0Pipeline aceita um tierFloor (default T0). routesToResidue(prior, floor) compara o rank do tier-alvo da família com o floor: se for estritamente maior, o item vira uma linha em _alembic-residue.jsonl em vez de ser pontuado inline. Subir o floor = o T0 processa mais tiers sozinho.
Cuidado — armadilha de iniciante Dois subdiretórios são excluídos do funil. Repos/Models e Repos/Prompts (pesos de modelo e corpora de prompt) são pulados inteiros — não são sinal destilável e podem ser gigantes. Se um item "sumiu" do relatório, confira se ele não cai sob um caminho excluído.
A wide cutaway illustration of a four-stage gravel-sieve cascade mounted on a slanted workshop frame: a heap of mixed gravel and small gems enters the top coarse sieve, and at each
A wide cutaway illustration of a four-stage gravel-sieve cascade mounted on a slanted workshop frame: a heap o
4

As duas saídas: LEARNINGS + SIGNALS


Tudo no funil existe para encher dois arquivos. Não é abstração: são dois .jsonl reais no disco, cada linha um registro validado. Um guarda o que aprendemos; o outro guarda as oportunidades. O alembic status literalmente conta as linhas desses dois arquivos.

LEARNINGS Skills/learning/learnings.jsonl id statement confidence 0–1 sourceRefs[] "o que aprendemos, com proveniência"
O caderno de aprendizados.
OPPORTUNITY GRAPH Business/opportunity-graph.jsonl kind signal | edge evidenceQuote strength 1–5 · conf 0–1 sourceRef "onde está a oportunidade"
O grafo de oportunidades.
O nosso sinal, como registro: a "fábrica de petições personalizadas" é um BusinessSignal de kind: 'gap' (uma lacuna de mercado), com um evidenceQuote tirado de um transcript, strength: 4, confidence: 0.7 e um sourceRef apontando para a fonte. Não é uma ideia solta — é uma linha rastreável até a evidência.
// uma linha de Business/opportunity-graph.jsonl (kind: "signal") id:"sig-peticoes-001" kind:"gap"(pain|trend|gap|validation|tech) evidenceQuote:"toda firma reescreve a mesma petição do zero" strength:4/ 5 confidence:0.7 sourceRef:"Transcripts/items/legal/aula-peticoes"
O sinal nomeado como dado real: cada campo do businessSignalSchema preenchido, rastreável até o sourceRef. É isto que a lição 4 forja em venture.

As duas saídas, lado a lado

DimensãoLEARNINGSBUSINESS SIGNALS
Pergunta que respondeO que aprendemos?Onde está a oportunidade?
Arquivo no discoSkills/learning/learnings.jsonlBusiness/opportunity-graph.jsonl
SchemalearningSchemabusinessSignalSchema
Alimenta…memória, contexto, cursosuma venture (lição 4+)
No nosso exemplo"petições têm estrutura repetível""fábrica de petições" 🔥
Flashcard
O que é uma das DUAS saídas do funil que guarda oportunidades?
clique para virar
O BUSINESS SIGNAL — gravado no grafo de oportunidades (Business/opportunity-graph.jsonl). A outra saída são as LEARNINGS.
Flashcard
Por que o T0 é "$0"?
clique para virar
Porque é determinístico e sem modelo: só dedupe (SHA-256), validação de contrato e pontuação heurística. Nenhuma chamada de IA = nenhum custo.
Flashcard
O que é "residue" no funil?
clique para virar
A fila de trabalho para tiers superiores: itens que o prior marcou como dignos de subir viram linhas em _alembic-residue.jsonl em vez de serem finalizados no T0.
Flashcard
O que alembic status conta?
clique para virar
As linhas das duas lojas append-only sob o dataDir: quantos registros de oportunidade e quantas learnings existem. Lojas ausentes contam como zero.
5

Os cinco comandos (+ status)


O funil tem um comando central — distill — e quatro comandos de apoio que preparam e indexam o corpus. Todos rodam offline por padrão. Pense neles como uma esteira: ingest/wiki-bridge colocam material na esteira, distill destila, e embed-index/vision-index criam índices pesquisáveis do que entrou.

ingest wiki-bridge alimentam o corpus distillo funil T0→T3 opportunity-graph learnings statusconta as 2 lojas embed-index vision-index indexam (offline $0)
O mapa: dois comandos alimentam, distill destila para duas lojas, dois indexam, e status lê. Tudo offline, $0, por padrão.

O que cada um faz

ComandoPacoteO que faz
distill <corpus>@alembic/etlO funil T0→T3; emite LEARNINGS + SIGNALS.
ingest <source>@alembic/ingestionPuxa uma fonte bruta e materializa pacotes-wiki.
wiki-bridge <fam>@alembic/ingestionLiga a wiki dir-por-pacote: 1 linha-bridge por pacote.
embed-index <fam>@alembic/embeddingsÍndice de vetores offline (16 dims), dedupe por chunk_id.
vision-index <fam>@alembic/visionDescreve imagens via VLM; dedupe por imagem. Nunca escreve no corpus.
Worked example — a cadeia típica no nosso corpus jurídico
1
Liga a família. alembic wiki-bridge Bookmarks escreve uma linha-bridge por pacote, injetando o isExcluded do @alembic/etl (pula Repos/Models, Repos/Prompts).
2
Destila. alembic distill ~/Documents/Resources --offline roteia as linhas family-prefixed pelos tiers e enche as duas lojas.
3
Indexa. alembic embed-index Bookmarks gera o índice de embeddings offline para busca semântica posterior.
4
Agora você: rode alembic status e leia quantos registros de oportunidade nasceram. Esse é o número que importa.
vocêcorpus/wiki2 lojasíndices wiki-bridge → 1 linha/pacote distill (lê o corpus) emite SIGNALS+LEARNINGS embed-index → vetores offline status conta as linhas das 2 lojas
A mesma cadeia como sequência: cada comando toca uma parte do sistema, e status fecha o loop lendo o resultado.
A wide illustration of a workshop conveyor line: on the left, two intake chutes drop raw bundles onto the belt; the middle of the belt passes through one central glowing copper sti
A wide illustration of a workshop conveyor line: on the left, two intake chutes drop raw bundles onto the belt
6

No código (a saída real, verbatim)


Tudo isto é grounded no repositório. Abaixo, as saídas reais de dois comandos — exatamente as strings que o CLI imprime, copiadas do código que as gera. Use a aba "Simples" para ler em português, ou "Técnico" para ver a função TypeScript por trás.

O distill resume cada tier em uma linha: quantos itens foram pontuados, viraram residue, foram excluídos; quantos bloqueados por PII ou orçamento; e — o que importa — quantos viraram opportunity e learnings. O custo aparece no fim: $0 no modo offline.

$ alembic distill ~/Documents/Resources --offline
distill: ~/Documents/Resources (dry run)
  T0: 128 scored, 64 residue, 2 excluded
  T1: 40 extracted, 3 pii-blocked
  T2: 12 shortlisted, 5 budget-blocked
  T3: 7 verified
  emitted: 9 opportunity, 18 learnings
  cost: $0

Leia de cima para baixo: 128 itens entraram no T0; 64 subiram (residue); 3 foram bloqueados por PII no T1; 5 por orçamento no T2; e no fim nasceram 9 oportunidades (uma delas é a nossa "fábrica de petições") e 18 aprendizados. (Números ilustrativos da forma da saída; [a verificar] num run real do seu corpus.)

A função renderDistill monta essas linhas a partir do FunnelReport. É só formatação de um objeto tipado — a lógica vive em runFunnel/@alembic/etl.

apps/cli/src/commands.ts — renderDistill()
write(`distill: ${corpus}${report.dryRun ? ' (dry run)' : ''}`);
write(
  `  T0: ${report.t0.scored} scored, ${report.t0.residue} residue, ` +
    `${report.t0.filesExcluded} excluded`,
);
write(`  T1: ${report.t1Extracted} extracted, ${report.t1PiiBlocked} pii-blocked`);
write(
  `  T2: ${report.t2Shortlisted} shortlisted, ${report.t2BudgetBlocked} budget-blocked`,
);
write(`  T3: ${report.t3Verified} verified`);
write(
  `  emitted: ${report.opportunityRecordsWritten} opportunity, ` +
    `${report.learningsWritten} learnings`,
);
write(`  cost: $${report.costUsd}`);

E o status — a prova mais simples

Esta é a saída real, registrada como prova offline no RESOURCES.md do curso (citável verbatim):

$ alembic status
status: default tier T4
  phases: discover -> validate -> design -> plan -> build -> review -> ship
  stores (.alembic): 4 opportunity, 0 learnings
default tier T4 phases: discover -> … -> ship stores: 4 opportunity, 0 learnings Tier.T4 factoryPhaseSchema.options countStores(dataDir) cada linha impressa ← uma fonte tipada no código (nada hardcoded à toa)
De onde vem cada linha de status: nada é inventado — o tier vem do enum, as fases do schema, os números de uma contagem real das lojas.

Acesse você mesmo

A função runStatus (em apps/cli/src/commands.ts) chama countStores(dataDir), que percorre readOpportunityRecords e readLearnings do @alembic/etl e conta as linhas. O default tier T4 vem de Tier.T4; as phases vêm de factoryPhaseSchema.options. Rode você mesmo na raiz do repo: pnpm -r build && node apps/cli/dist/index.js status (ou alembic status se o CLI estiver no PATH).

Note: as lojas ausentes leem como zero — por isso 0 learnings num repo recém-clonado. Os números crescem quando você roda um distill real.

Fonte primária deste trecho
apps/cli/src/commands.ts — runDistill / runStatus

Onde a saída de distill e status é montada. As strings acima foram copiadas deste arquivo, não da memória.

7

Privacidade: fail-closed


Parte do corpus vem de canais privados: WhatsApp, Discord, Skool, Circle. Se o funil extrai um sinal dali e ele ainda contém um e-mail, telefone ou token, esse sinal não pode sair sem ser redigido. O Alembic trata isso como uma trava: na dúvida, ele fecha — não vaza.

Pense como… a porta de um cofre que só destranca quando você prova que o documento já foi censurado. Sem a prova, a porta fica fechada. A analogia quebra porque aqui não há "arrombar": o sistema devolve um erro-valor que o chamador é obrigado a tratar, em vez de jogar uma exceção que alguém poderia ignorar.

sinal p/ emitir redigido? sim → emite (ok) não canalprivado? não → emite (ok) sim err(blocked)private_unredacted a porta fica fechada
assertRedactedForEmit: um sinal de canal privado só passa se provar que foi redigido; senão, retorna err (nunca lança). Fail-closed por valor.
PRIVATE_CHANNELS whatsapp discord skool circle
Os quatro canais que exigem redação.
o que é mascarado [REDACTED_EMAIL] [REDACTED_PHONE] [REDACTED_HANDLE] [REDACTED_TOKEN]
e-mail, telefone, @handle e tokens de credencial.
Lembre — o essencial Fail-closed = na dúvida, fecha. A função devolve um Result com err, não uma exceção. Isso é a cintura estreita do Alembic aplicada à privacidade: o erro é um valor que o chamador tem que tratar para seguir.
A wide illustration of a heavy vault door set into a workshop wall, half-open: a document on a tray in front of it has several lines blacked out with censor bars, and a small green
A wide illustration of a heavy vault door set into a workshop wall, half-open: a document on a tray in front o
8

Experimente


Dois laboratórios interativos. No primeiro, escolha uma família e veja o item subir (ou não) pelos tiers, ao vivo. No segundo, monte um sinal, escolha o canal e veja a trava de privacidade decidir entre emitir e bloquear.

Lab 1 · O roteador T0→T3

A regra é a real: priorFor(path).tier dá o alvo da família; routesToResidue(prior, floor) decide se vira residue. Famílias sob Repos/Models e Repos/Prompts são excluídas antes de tudo.

Lab 2 · A trava de privacidade (fail-closed)

Espelha assertRedactedForEmit(signal, channel) + redactText: um canal privado sem redação ⇒ err(private_unredacted); redigido ou público ⇒ ok (com PII mascarada).

Você é o aluno e eu sou seu professor — me pergunte o que quiser. Por exemplo: "por que Bookmarks fica no T1 mas Transcripts sobe pro T2?" ou "o que acontece se eu rodar distill sem --offline?". Na próxima lição a gente abre os tijolos atômicosembed, ocr, triage, memory — as capabilities offline $0 que se compõem para cima.
9

Recapitulando


Primeiro, os pontos-chave em slides. Depois, três perguntas para fixar — cada resposta explica por que está certa e por que as outras tentam te enganar.

O funil em uma frase

De corpus bruto a duas saídas

O funil distill lê o corpus de IA jurídica e produz LEARNINGS + BUSINESS SIGNALS — entre eles, a "fábrica de petições".

Tiers

T0 é grátis; só sobe quem merece

O T0 deduplica/valida/pontua por $0. Famílias com sinal fresco viram residue e sobem para T1–T3, onde o modelo (e o custo) entram.

T0 · $0T1 · T2 · T3 — modelo + budget

Duas lojas

Dois arquivos .jsonl no disco

Skills/learning/learnings.jsonl e Business/opportunity-graph.jsonl. Append-only, validados, content-addressed. status conta as linhas.

Cinco comandos

Alimentar, destilar, indexar

ingest/wiki-bridge alimentam · distill destila · embed-index/vision-index indexam. Todos offline, $0, por padrão.

ingestwiki-bridgedistillembed-idxvision-idx

Privacidade

Fail-closed, por valor

Sinal de canal privado (whatsapp/discord/skool/circle) sem redação ⇒ err, nunca uma exceção. Na dúvida, a porta fica fechada.

A história continua

O sinal vira o próximo capítulo

A "fábrica de petições" sai do funil como um registro. Nas próximas lições, o harness a forja em venture e a Iris opera o atendimento.

1 / 6setas
Três perguntas para fixar
1 · Quantas chamadas de modelo (pagas) o tier T0 faz?
Certa: a segunda. O T0 é "the L0 deterministic substrate (Tier T0, $0)" — sem modelo. A primeira erra o ponto inteiro: gastar IA em tudo é o que o funil evita. A terceira confunde T0 com os tiers superiores, onde o orçamento (budget) realmente decide o quanto se gasta.
2 · O alembic status mostra "0 learnings" num repo recém-clonado. Por quê?
Certa: a terceira. countStores percorre os arquivos das lojas; um arquivo ausente lê como zero. A primeira inventa um "desativar" que não existe. A segunda é falsa: o funil offline ($0) já escreve learnings — --online não é requisito.
3 · Um sinal vindo do Discord ainda tem um e-mail e não foi redigido. O que assertRedactedForEmit faz?
Certa: a primeira. Discord é canal privado; sem prova de redação, a função devolve err (fail-closed por valor). A segunda erra o mecanismo: o Alembic não lança em código de biblioteca — usa Result. A terceira é exatamente o vazamento que a trava existe para impedir.
As Dez coisas para levar do funil
  1. O funil tem uma saída dupla: LEARNINGS (o que aprendemos) + BUSINESS SIGNALS (onde está a oportunidade).
  2. T0 é $0 e determinístico — sem modelo. É o que permite processar 330 GB sem quebrar o caixa.
  3. Um item só sobe de tier (vira residue) se o prior da sua família marcar que vale.
  4. Transcripts/Discord/Circle/Skool sobem (T2); Bookmarks/Skills/Repos/Whatsapp ficam baixo (T1).
  5. Repos/Models e Repos/Prompts são excluídos do funil inteiro.
  6. As duas lojas são append-only, validadas e content-addressed — rodar de novo não duplica.
  7. Dedupe por SHA-256: o mesmo item, duas vezes, é processado só uma.
  8. PII de canal privado é fail-closed: sem redação, err — nunca vaza.
  9. embed-index e vision-index são offline, dedupe por chunk_id / por imagem; vision-index nunca escreve no corpus.
  10. alembic status é a prova mais barata: conta as linhas das duas lojas.