Ao fim desta lição você entende as DUAS cadeias de valor do ADR-0002 sobre um único T0 determinístico, por que os budgets são separados e fail-closed, como a rota Learnings produziu pela primeira vez na história (06-07: 1.022 learnings) — e as duas armadilhas que quase impediram isso.
Esta lição destila o ADR-0002 (status: Implemented 2026-07-03) + a implementação em packages/harness/src/funnel.ts e packages/etl/src/budget.ts — e a primeira produção REAL da segunda cadeia.
O funil de destilação tem dois clientes com ROI diferente: Signals (negócio — semeiam ventures/receita, vão para Business/opportunity-graph.jsonl) e Learnings (engenharia — melhoram o próprio motor, vão para Skills/learning/learnings.jsonl). O ADR-0002 decide: as duas cadeias compartilham o T0 determinístico (caminhada + score + dedupe + índice, $0, roda uma vez) e se separam nos tiers pagos, cada uma com SEU budget, SEU store e SUA cadência.
"a single frontier budget is contested by ~128k community items and ~2k learning items at once, starving learning or forcing an arbitrary per-family cut" docs/adr/0002-two-value-chains-shared-t0.md — a opção "one funnel, two outputs" rejeitada
E o roteamento é por prior, não por partição (mesmo ADR): a família do item é um prior barato aplicado no T0 — mas um repo estrelado pode render um Learning E um Signal.
Pense como… uma vinícola com uma única prensa (T0) e duas linhas de engarrafamento com verbas separadas: vinho de venda (signals) e vinho de estudo do enólogo (learnings). Esgotar a verba de uma linha PARA aquela linha — a prensa e a outra linha seguem. Quebra: na vinícola a uva vai para UMA garrafa; aqui o mesmo item pode virar as duas coisas.
O knob dos signals é --budget (ou ALEMBIC_BUDGET_USD); o knob OPCIONAL dos learnings é --learnings-budget (ou ALEMBIC_LEARNINGS_BUDGET_USD) — presente, ativa a segunda cadeia via RouteBudgets/resolveRouteBudgets (packages/etl/src/budget.ts, conforme a linha de status do próprio ADR-0002). Ausente, a run é byte-idêntica à de um knob só — inclusive o payload --json, que nem carrega a chave routes. Cada rota reporta seus registros no SEU store: "signals → opportunity-graph, learnings → learnings" (packages/harness/src/funnel.ts:109, RouteReport).
O contrato de gasto é duro: cada rota consome do SEU cap e, esgotado, aquela rota bloqueia — a outra segue intacta. No código: runLearningsRoute cobra "against the learnings cap ONLY — exhausting it fail-closes THIS route without" afetar a irmã (funnel.ts:809-814). Um cap de $0 para learnings não é "grátis": é a rota ATIVA e imediatamente bloqueada — o que torna o budget significativo (funnel.ts:753).
Spend-safety na extração: o extrator de learnings pede um shape estrito ("Keys: statement (a concise reusable lesson), confidence (number 0-1)", funnel.ts:775) — resposta malformada parseia para ZERO learnings, $0, nenhuma fabricação (funnel.ts:811).
# um knob: só a cadeia SIGNALS (byte-idêntico ao comportamento antigo) alembic distill /Users/acf/Documents --offline --budget 5 # dois knobs: ativa a cadeia LEARNINGS com cap PRÓPRIO alembic distill /Users/acf/Documents --budget 5 --learnings-budget 2 # no --json de uma run de DOIS knobs (e só nela) aparece: # report.routes.signals {records, budgetBlocked, ...} # report.routes.learnings {records, budgetBlocked, ...} # e a linha-resumo "learnings-route:" no texto
Até 06-07 a rota Learnings era código testado que nunca tinha produzido um registro. Aí veio a primeira run real sobre Bookmarks — e a cadeia entregou:
sourceRefs: [item.sourcePath] (funnel.ts:796); no learning derivado de consenso do council, sourceRefs herda os sourceRef dos sinais do pack (funnel.ts:666). É por isso que o lote fechou com 100% de sourceRefs: o campo é estrutural, não boa vontade.A armadilha que custou uma noite: classifyFamily (packages/etl/src/priors.ts:70) deriva a família do PRIMEIRO segmento do path relativo ao corpus. Se você aponta o distill na RAIZ da família (ex.: …/Resources/Bookmarks), o primeiro segmento relativo vira o conteúdo interno — nenhuma família reconhecida ⇒ Unknown ⇒ prior tier T0/priority 0.1 (priors.ts:63) ⇒ residue 0, silenciosamente. Nada falha; simplesmente nada sobe.
E como escopar UMA família sem pagar a caminhada do resto? O truque cpio: materialize num diretório de trabalho só os package.jsonl (as bridge-lines) da família desejada, PRESERVANDO o prefixo do path — cpio -pdm copia a árvore relativa exatamente como listada. O distill enxerga um corpus mínimo com o prefixo de família correto.
# escopo de 1 família com prefixo preservado (o truque cpio) cd /Users/acf/Documents find Resources/Bookmarks -name 'package.jsonl' | cpio -pdm /tmp/corpus-bkm alembic distill /tmp/corpus-bkm --budget 0 --learnings-budget 2 # cheque de sanidade: a família apareceu no residue? alembic status
walkCorpus NÃO segue symlinks — um corpus montado por links simbólicos caminha vazio. Materialize arquivos reais (o cpio resolve as duas coisas de uma vez).A segunda armadilha não era path — era roteamento de modelo. pickCheapestForTier (packages/contracts/src/registry.ts:244) escolhe o modelo mais barato do tier; num EMPATE de custo, fica o PRIMEIRO declarado no registry. No T2 havia um empate de 0.0005 — e o primeiro declarado era um modelo cujo acesso o gateway NEGAVA (deepseek-v4-pro, AccessDenied). Resultado: a cadeia inteira montada, e o extrator batendo num modelo proibido.
O fix (PR #157) foi de UMA linha de intenção: declarar gemini-3.5-flash ANTES do deepseek-v4-pro no registry — o empate agora resolve para o modelo que o gateway realmente serve. Sem isso, os 1.022 nunca teriam nascido.
registry: [deepseek-v4-pro, gemini-3.5-flash] T2 tie 0.0005 → deepseek-v4-pro gateway → AccessDenied → rota morta
registry: [gemini-3.5-flash, deepseek-v4-pro] T2 tie 0.0005 → gemini-3.5-flash gateway → OK → 1.022 learnings
pickCheapestForTier, a ORDEM de declaração no registry é uma decisão de produção, não estética. Está no rol de gotchas oficiais do curso: "empate de custo = ordem de declaração".Estado real dos stores destilados na verdade de 2026-07-06:
| store | volume | observação |
|---|---|---|
| signals (Business/opportunity-graph) | 2.589 | a cadeia de negócio, acumulada |
| enriched | 2.677 | 676 plausíveis · 399 força-4 (enriquecimento T3 local) |
| learnings | 1.022 | únicos, 100% sourceRefs+hash, μ0.94 — isolados em ~/.alembic-learnings-bkm |
A decisão pendente: promoção. Os 1.022 learnings existem, com proveniência — mas ainda estão em quarentena num store isolado, e nenhuma perna do motor os LÊ de volta (é a "perna 2" aberta que a Lição 10 detalha). Promover significa decidir: quem consome, com que piso de qualidade, para dentro de quê. A régua doutrinária já existe — o ADR-0006 fixa "frontier-quality via o modelo mais barato acima de um piso de profundidade" como o padrão de qualidade do corpus — mas a decisão de promoção dos stores destilados segue aberta com o founder.
alembic distill <corpus> --budget 5 SEM --learnings-budget. O que acontece com a cadeia Learnings?routes aparece no --json — ela só existe em runs de dois knobs, funnel.ts:157). Budgets nunca se misturam: cada rota tem cap, spend e bloqueio próprios (ADR-0002).