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.
É 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.
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).
distill recebe (corpus) e o que ele produz (LEARNINGS + SIGNALS).T0→T3 e por que um item só "sobe" se merecer (o residue).distill, ingest, wiki-bridge, embed-index, vision-index — e status.alembic status e ler as duas lojas append-only que ele conta.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.
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.
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 de IA jurídica entra inteiro no T0; só o que merece sobe via residue; no fim, dois bicos enchem duas lojas distintas.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.
scored e duplicate. Re-rodar converge para zero trabalho novo.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?
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.
pii-blocked no T1, budget-blocked no T2 — nada vaza nem estoura o orçamento em silêncio.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".
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.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.
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.
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.
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.businessSignalSchema preenchido, rastreável até o sourceRef. É isto que a lição 4 forja em venture.| Dimensão | LEARNINGS | BUSINESS SIGNALS |
|---|---|---|
| Pergunta que responde | O que aprendemos? | Onde está a oportunidade? |
| Arquivo no disco | Skills/learning/learnings.jsonl | Business/opportunity-graph.jsonl |
| Schema | learningSchema | businessSignalSchema |
| Alimenta… | memória, contexto, cursos | uma venture (lição 4+) |
| No nosso exemplo | "petições têm estrutura repetível" | "fábrica de petições" 🔥 |
Business/opportunity-graph.jsonl). A outra saída são as LEARNINGS._alembic-residue.jsonl em vez de serem finalizados no T0.alembic status conta?dataDir: quantos registros de oportunidade e quantas learnings existem. Lojas ausentes contam como zero.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.
distill destila para duas lojas, dois indexam, e status lê. Tudo offline, $0, por padrão.| Comando | Pacote | O que faz |
|---|---|---|
distill <corpus> | @alembic/etl | O funil T0→T3; emite LEARNINGS + SIGNALS. |
ingest <source> | @alembic/ingestion | Puxa uma fonte bruta e materializa pacotes-wiki. |
wiki-bridge <fam> | @alembic/ingestion | Liga 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/vision | Descreve imagens via VLM; dedupe por imagem. Nunca escreve no corpus. |
alembic wiki-bridge Bookmarks escreve uma linha-bridge por pacote, injetando o isExcluded do @alembic/etl (pula Repos/Models, Repos/Prompts).alembic distill ~/Documents/Resources --offline roteia as linhas family-prefixed pelos tiers e enche as duas lojas.alembic embed-index Bookmarks gera o índice de embeddings offline para busca semântica posterior.alembic status e leia quantos registros de oportunidade nasceram. Esse é o número que importa.status fecha o loop lendo o resultado.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.
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.
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}`);
status — a prova mais simplesEsta é a saída real, registrada como prova offline no RESOURCES.md do curso (citável verbatim):
status: default tier T4 phases: discover -> validate -> design -> plan -> build -> review -> ship stores (.alembic): 4 opportunity, 0 learnings
status: nada é inventado — o tier vem do enum, as fases do schema, os números de uma contagem real das lojas.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.
Onde a saída de distill e status é montada. As strings acima foram copiadas deste arquivo, não da memória.
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.
assertRedactedForEmit: um sinal de canal privado só passa se provar que foi redigido; senão, retorna err (nunca lança). Fail-closed por valor.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.
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.
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.
Espelha assertRedactedForEmit(signal, channel) + redactText: um canal privado sem redação ⇒ err(private_unredacted); redigido ou público ⇒ ok (com PII mascarada).
distill sem --offline?". Na próxima lição a gente abre os tijolos atômicos — embed, ocr, triage, memory — as capabilities offline $0 que se compõem para cima.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.
alembic status mostra "0 learnings" num repo recém-clonado. Por quê?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.assertRedactedForEmit faz?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.Repos/Models e Repos/Prompts são excluídos do funil inteiro.err — nunca vaza.embed-index e vision-index são offline, dedupe por chunk_id / por imagem; vision-index nunca escreve no corpus.alembic status é a prova mais barata: conta as linhas das duas lojas.