A grande ideia
Um motor que destila
O Alembic recebe objetivo + plano + contrato e executa unidades em muitos modelos — roteando por custo, provando cada passo, passando por gates. Barato primeiro, caro só no que sobrevive.
1Antes de qualquer fusão, você precisa do hospedeiro. O Alembic é um motor de execução de planos para enxames (swarms) de agentes: recebe um objetivo, um plano executável e um contrato de validação, e então roda unidades de trabalho em muitos modelos — roteando por custo, provando cada passo e passando por gates antes de entregar. Esta lição é a sua forma: seis camadas, um contrato que sustenta tudo, quatro invariantes e o funil que transforma fontes brutas em Learnings.
run(), que nunca lança e devolve uma união discriminada em ok.runWithGuards torna o "nunca lança" estrutural, não uma promessa de comentário.verified-GO e por que GO sozinho não basta para emitir.interface.tier, gate, narrow waist (cintura estreita), adapter e residue são definidos aqui.Os números são verificados na fonte. O complete-map registrava 19 pacotes quando foi escrito; o workspace cresceu para 27 pacotes + apps/cli depois que pacotes como @alembic/hermes entraram. A disciplina do projeto é contar o que está no código, não o que soa redondo.
O Alembic não é um chatbot. É uma fábrica: você entrega um objetivo e um plano, e ela executa o trabalho dividindo-o em unidades, distribuindo-as entre vários modelos de IA, provando cada passo e barrando o que não passa. Tudo é desenhado para gastar o mínimo: o trabalho mais pesado roda de graça, e só os sinais mais fortes chegam a um modelo pago.
O Alembic é um motor de execução de planos para enxames de agentes. A entrada é um trio: um GOAL.md (o objetivo), um alembic.plan.ts (o plano executável, com unidades e provas) e um contrato de validação. A saída é um run verificado, com cada unidade provada por comandos reais (o Proof Gate) e cada milestone escrutinado por um Validador independente.
A lição é a forma dessa máquina: seis camadas (uma pilha onde as dependências só apontam para baixo), uma cintura estreita (toda chamada de modelo tem a mesma forma e nunca lança), quatro invariantes (propriedades sustentadas no código) e um funil de quatro tiers (a destilação cheap-first).
T0 = silencioso, totalmente automático, $0. A escada vai de T0 até T4 (parked — exige council + humano). Cada degrau acima vê menos itens, mais fortes, e custa mais.O Alembic é organizado em camadas de cima para baixo, e a organização é real: o grafo de imports não tem nenhuma aresta para cima e nenhum ciclo. Cada camada só pode depender das que estão abaixo. Leia de baixo para cima — a fundação é o vocabulário; o topo é onde humanos e ferramentas se conectam.
Numa pilha de seis camadas onde "tudo passa por uma chamada de modelo", em qual camada você acha que mora essa chamada — no topo (perto do humano), no meio, ou perto da base?
$0. É o ponto único por onde o sistema fala com qualquer provedor.As seis camadas, de L4 (clientes, no topo) a L-1 (ingestão, na base). A seta à direita é a regra inteira: dependências apontam só para baixo.
| Camada | O que ela detém | Pacotes |
|---|---|---|
| L4 · CLIENTS | As superfícies que humanos e ferramentas usam: a CLI, o servidor harness HTTP+SSE, um servidor MCP somente-leitura, o web cockpit, a TUI. | harness, web, tui, apps/cli |
| L3 · SWARM | Orquestração multi-tier: um orchestrator gera um lead que gera workers, sobre uma fila gated por dependências, com profundidade limitada, isolamento por git-worktree e resume à prova de crash. | swarm |
| L2 · ENGINE | O núcleo de decisão: um DebateEngine qualitativo, score quantitativo 0–10, um Verifier maker-checker independente, e um painel de N-lentes que é o gate de emissão em T3+. | council |
| L1 · ADAPTER | A cintura estreita — toda chamada de modelo tem uma única forma de função que nunca lança. Adapters + offline + um router sem fallback silencioso, com retry, circuit-breaker e contabilidade de custo. | adapters |
| L0 · SUBSTRATE | O chão determinístico de $0: o vocabulário Zod (todo tipo é um z.infer), o leitor de corpus em stream, dedupe SHA-256, stores JSONL append-only endereçados por conteúdo, redação de PII, o budget guard, os run-dirs, o registro de modelos. | contracts (vocabulário), etl (a camada determinística) |
| L-1 · SOURCE | A camada de ingestão que alimenta a wiki que o ETL depois destila — um Collector somente-leitura + um wrapper de agent-browser que só navega, nunca muta. | ingestion |
Cola que atravessa as camadas. Alguns pacotes orquestram através de L2–L4 e se leem melhor como um tier próprio: @alembic/mission (compila missões → run specs), @alembic/vm (executa o alembic.plan.ts injetando os hooks h.*), @alembic/coda (os quatro gates que fecham o run), @alembic/forge (o front-end Forge + Scope Gate) e @alembic/planf3 (o HTML do plano).
Aqui está a ideia mais importante do código. Toda chamada de modelo no sistema inteiro passa por uma única forma de função: uma chamada assíncrona que nunca lança e devolve uma união discriminada em ok. Sucesso e falha são ambos valores de retorno comuns — não existe um segundo caminho "excepcional" para você raciocinar.
// INVARIANTE: ModelAdapter.run NUNCA lança. interface ModelAdapter { run(input: ModelRunInput): Promise<ModelRunResult>; // nunca lança } // ModelRunResult é uma união discriminada em `ok`: ModelRunSuccess = { ok: true; text; usage?; costUsd?; durationMs; modelId; ... } ModelRunFailure = { ok: false; error: { code; message; retryable }; durationMs; ... } ModelRunResult = z.discriminatedUnion('ok', [ Success, Failure ])
Fonte: packages/contracts/src/model.ts — modelRunResultSchema = z.discriminatedUnion('ok', […]) e a interface ModelAdapter com a invariante "run NUNCA lança". Governado por ADR-0009 ("narrow waist — run never throws").
Como a forma é uniforme, toda camada acima de L1 pode ser escrita como um kernel puro que só ramifica em ok. Um 429, um timeout, uma resposta malformada, uma queda do provedor — tudo chega como a mesma falha tipada. Não há try/catch espalhado pela engine, pelo swarm ou pelo funil. Um contrato, aplicado uma vez, comprado em todo lugar.
Existe ainda uma segunda união, mais leve, Result<T, E> (também em ok, com braços value/error) para trabalho falível que não é de modelo — IO de arquivo, parsing, wrapping de subprocesso. Ela espelha de propósito a cintura de modelo, para os dois se lerem idênticos no ponto de chamada. Essa disciplina é a Lição 5.
ok)ok do mesmo jeito nas duas. A cintura de modelo só acrescenta a garantia estrutural de "nunca lança".run() faz quando o provedor retorna HTTP 429?ModelRunResult com ok:false e error.retryable=true — não lança. A mesma forma de um sucesso. O retry é dirigido pela flag.try/catch espalhado?try/catch vive num único lugar (runWithGuards). A falha vira valor tipado uma vez, e todo consumidor a lê de forma uniforme."Nunca lança" não é um comentário que você torce para ser verdade — é imposto estruturalmente por uma única espinha reutilizável, runWithGuards. Cada adapter implementa só um attempt() interno; a espinha o envolve, em ordem, com quatro proteções.
attempt() do adapter (que pode lançar) é embrulhado por estas camadas. Qualquer throw que escapar é convertido em ModelRunFailure — a invariante sobrevive.ModelRunFailure tipada.retryable de cada resultado — com um try/catch externo final "como última rede de segurança para preservar a invariante".runDebateSafe / runSwarmSafe — ou seja, não só uma chamada de modelo, mas um debate ou um swarm inteiro também devolvem um resultado em vez de lançar. Fonte: packages/adapters/src/adapter-core.ts — runWithGuards.ok:false.A arquitetura repousa sobre quatro propriedades (não mais — o mapa enumera exatamente estas). Cada uma é afirmada na fonte, e a maioria é governada por uma ADR.
| # | Invariante | Como é sustentada |
|---|---|---|
| 1 | run() nunca lança; o resultado é uma união discriminada uniforme. | Estrutural, via runWithGuards (adapters/src/adapter-core.ts). ADR-0009. |
| 2 | Engines são agnósticas de adapter E de store — kernels puros com side-effects injetados. | O DebateEngine recebe views readonly + um AdapterRegistry injetado; o ETL roteia todo IO por um FsPort injetável; o funil recebe um registry injetado (então um offline torna o run $0). |
| 3 | IDs endereçados por conteúdo + layout de run-dir determinístico (para os runs darem replay). | O id de um run é o hash SHA-256 do conteúdo da sua spec; os stores são endereçados por conteúdo sobre JSON canônico, então re-anexar conteúdo idêntico é no-op. Plan modules não podem usar Date.now()/Math.random(). |
| 4 | Dissenso é preservado/forçado pelo Verifier, não meramente por um prompt. | O Verifier maker-checker é somente-leitura por arquitetura e prova claims com oráculos determinísticos sobre evidência estruturada, nunca a prosa do maker. "Contrarian-last" é um erro de board fail-closed. ADR-0003. |
A razão de o Alembic existir é o funil: ele transforma um corpus bruto em duas cadeias de valor — um grafo de oportunidades de negócio e um store de Learnings. Faz isso em quatro tiers de custo, cheap-first, de modo que a maior parte do trabalho não custa nada e só os sinais mais fortes chegam a um modelo pago.
O funil de quatro tiers. T0 (topo) é gratuito e roda sobre 100% do corpus; cada tier abaixo é medido e vê menos itens, mais fortes.
runT0Pipeline: caminha o corpus → dedupe SHA-256 → valida contrato → score de 6 eixos → emite residue. Roda sobre 100% do corpus, sem humano, sem modelo.| Tier | O que faz | Custo |
|---|---|---|
| T0 | runT0Pipeline: caminha o corpus → dedupe SHA-256 → valida contrato → score de 6 eixos → emite residue. Roda sobre 100% do corpus. | $0 |
| T1 | runT1Extraction: um BusinessSignal por item de residue via o adapter LOCAL injetado (free-tier, então na prática nunca é bloqueado por budget). | ~$0 |
| T2 | runT2Shortlist: uma shortlist FRONTIER gated por orçamento refina os sinais T1 mais fortes em lotes; toda chamada paga é medida. | metered |
| T3 | runT3Council: um council sintético de 3 membros (otimista / analista / pessimista) mais o painel verifier de N-lentes. | metered |
Três invariantes de segurança que o funil nunca pode regredir: PII antes da saída (um sinal de canal privado é redigido antes da chamada de modelo e re-checado antes de qualquer escrita), budget fail-closed (toda chamada paga é embrulhada num BudgetGuard.check fail-closed — uma quebra projetada bloqueia a chamada e o tier degrada em vez de estourar o gasto) e append-only (resultados fluem para stores endereçados por conteúdo, validados por esquema, append-only; leituras de fonte continuam somente-leitura).
Um desfecho de T3 só emite quando ambos são verdade: a decisão de consenso é GO e isPanelEmissionApproved(report) vale — o painel de N-lentes verificou, não parkou. Uma maioria simples não basta; o painel pode vetar. É isso que mantém o grafo de oportunidades honesto: nada sedimenta sem passar pelo gate de emissão.
packages/harness/src/funnel.ts — if (!isPanelEmissionApproved(report) || consensus.decision !== 'GO') { … }.BusinessSignal sobreviveu a T0 (passou o score), T1 (virou sinal local) e T2 (entrou na shortlist frontier).decision: 'GO'. Até aqui, parece aprovado.isPanelEmissionApproved(report). Uma lente acha a evidência fraca e parka em vez de aprovar.GO === true, mas isPanelEmissionApproved === false. O E dá falso → o sinal vira PARKED, não emite. O grafo continua honesto.NO_GO mas todas as lentes aprovassem? Pense antes de seguir: o E ainda exige os dois verdadeiros — então também daria PARKED. Nenhum dos lados sozinho emite.runWithGuards), então a falha é convertida em valor tipado uma vez e todo consumidor a lê de forma uniforme. O ganho é a ausência de tratamento de erro em todo o resto, não a presença dele no adapter.Date.now() / Math.random(); a VM os rejeita.Passe pelos slides com os botões, as setas do teclado (← →) ou os pontos. Cada um é um dos pilares desta lição.
A grande ideia
O Alembic recebe objetivo + plano + contrato e executa unidades em muitos modelos — roteando por custo, provando cada passo, passando por gates. Barato primeiro, caro só no que sobrevive.
1As seis camadas
L4 clients · L3 swarm · L2 engine · L1 adapter · L0 substrate · L-1 source. O grafo de imports não tem aresta para cima nem ciclo.
2A cintura estreita
Toda chamada de modelo é run(input) → ModelRunResult, união discriminada em ok. Sucesso e falha são o mesmo tipo de retorno — sem caminho excepcional.
As quatro invariantes
1) run nunca lança · 2) engines agnósticas de adapter e store · 3) IDs por conteúdo → replay · 4) dissenso preservado pelo Verifier. Não cinco, não seis.
4verified-GO
T3 só emite quando consenso é GO E isPanelEmissionApproved vale. O painel pode vetar — uma maioria simples não emite. É o que mantém o grafo honesto.
GO e aprovação do painel — o E lógico.)Responda às três. O placar conta sozinho.
run() nunca lança; um 429 aparece como ModelRunFailure com error.retryable. O runWithGuards converte qualquer throw que escape nessa forma e ainda dirige o retry pela flag. E o roteamento não tem fallback silencioso — um modelo ausente devolve erro tipado, nunca um substituto. a/b assumem exceção/abort; d é exatamente o que o router proíbe.GO quanto aprovação do painel. a é falso; b inverte a qualidade (T3 é o mais rigoroso); c confunde o objetivo (é custo, não velocidade).run() → ModelRunResult, e nunca lança.GO E aprovação do painel.