Curso / Lição 1  ·  EN
Lição 01 · O sistema que você está integrando

O Motor

Antes de qualquer fusão, você precisa do hospedeiro. O Alembic é um motor de execução de planos para enxames de agentes: ele recebe um objetivo, um plano executável e um contrato de validação, então roda unidades de trabalho em vários modelos — roteando por custo, provando cada passo, com portão antes de entregar. Esta lição é a forma dele: seis camadas, um contrato que carrega o peso, quatro invariantes e o funil que transforma fontes brutas em Learnings.

6
camadas (L-1 → L4)
4
invariantes — exatamente quatro
19+1
pacotes + apps/cli
~565
testes verdes

As contagens são verificadas na fonte. O mapa registra 19 pacotes de workspace + 1 app; a suíte estava em 415 quando o mapa foi escrito e cresceu para ~565 depois que @alembic/hermes chegou — os mesmos 565 que o estudo de caso da Lição 6 roda.

As seis camadas

O Alembic é estratificado de cima a baixo, e a estratificação é real: o grafo de imports não tem arestas para cima nem ciclos. Cada camada só pode depender das que estão abaixo. Leia de baixo — a fundação é o vocabulário, o topo é onde humanos e ferramentas se conectam.

L4 · CLIENTS cli · http (REST+SSE) · mcp (somente-leitura) · web cockpit · tui L3 · SWARM orchestrator → lead → worker · fila com dependências · profundidade limitada (MAX_DEPTH=2) · park T4 L2 · ENGINE DebateEngine + score 0–10 + Verifier maker-checker + painel de N-lentes (o portão de emissão) L1 · ADAPTER  — a cintura estreita ModelAdapter.run(input) → ModelRunResult   ⟵ NUNCA LANÇA L0 · SUBSTRATE vocabulário Zod · dedupe SHA-256 · stores append-only endereçados por conteúdo · budget · run-dirs L-1 · SOURCE Collector + agent-browser (somente-leitura) → materializa pacotes wiki append-only dependências apontam só para baixo — sem aresta para cima, sem ciclo
CamadaO que possuiPacotes
L4 · CLIENTSAs superfícies que humanos e ferramentas usam: CLI, o servidor harness HTTP+SSE, um servidor MCP somente-leitura, o web cockpit, a TUI.harness, web, tui, apps/cli
L3 · SWARMOrquestração multi-nível: um orchestrator gera um lead que gera workers, sobre uma fila com dependências, com profundidade limitada, isolamento git-worktree e resume à prova de crash.swarm
L2 · ENGINEO kernel de decisão: um DebateEngine qualitativo, score quantitativo 0–10, um Verifier maker-checker independente, e um painel de N-lentes que é o portão de emissão T3+.council
L1 · ADAPTERA cintura estreita — toda chamada de modelo é uma forma de função que nunca lança. Seis adapters + offline + um roteador sem fallback silencioso, retry, circuit-breaker, contabilidade de custo.adapters
L0 · SUBSTRATEO piso determinístico de $0: o vocabulário Zod (todo tipo é um z.infer), o leitor de corpus em streaming, dedupe SHA-256, stores JSONL append-only endereçados por conteúdo, redação de PII, o guarda de budget, run directories, o registro de modelos.contracts (vocabulário), etl (a camada determinística)
L-1 · SOURCEA camada de ingestão que alimenta a wiki que o ETL depois destila — um Collector somente-leitura + um wrapper agent-browser que só pode navegar, nunca mutar.ingestion

Cola que atravessa as camadas. Um punhado de pacotes orquestra através de L2–L4 e é melhor lido como seu próprio nível: @alembic/mission (compila missões → run specs), @alembic/vm (executa alembic.plan.ts injetando os hooks h.*), @alembic/coda (os quatro portões de fechamento de run), @alembic/forge (o front-end Forge + Scope Gate) e @alembic/planf3 (HTML de plano).

A cintura estreita — um contrato que nunca lança

Eis a ideia mais importante do código. Toda invocação de modelo no sistema inteiro flui por uma forma de função: uma chamada async que nunca lança e retorna uma união discriminada chaveada em ok. Sucesso e falha são ambos valores de retorno comuns — não há um segundo caminho excepcional para raciocinar.

// packages/contracts/src/model.ts — a cintura (forma)
interface ModelAdapter {
  run(input: ModelRunInput): Promise<ModelRunResult>;   // NUNCA lança (a invariante)
}
// 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:30-151. Governado pela ADR-0009 ("cintura estreita — run nunca lança").

"Nunca lança" não é um comentário que você torce para valer — é estruturalmente imposto por uma única espinha reusável, runWithGuards. Cada adapter implementa só um attempt() interno; a espinha o encapsula, em ordem, com: (1) validação Zod da entrada na fronteira, (2) um try/catch que converte qualquer throw escapado em um ModelRunFailure tipado, (3) um portão opcional de circuit-breaker, e (4) backoff de retry guiado pelo flag retryable de cada resultado — com um try/catch externo final "como última rede de segurança para preservar a invariante".

Por que uma cintura importa

Porque 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 de provedor — todos chegam como a mesma falha tipada. Não há try/catch espalhado pelo engine, pelo swarm ou pelo funil. O núcleo de orquestração até re-estabelece a fronteira para sub-runs inteiras com runDebateSafe / runSwarmSafe. Um contrato, imposto uma vez, comprado em todo lugar.

Uma segunda união Result<T, E> mais leve (também chaveada em ok, com braços value/error) existe para trabalho falível não-modelo — IO de arquivo, parsing, encapsular subprocessos. Ela espelha deliberadamente a cintura de modelo para que ambas leiam idêntico nos call sites. Essa disciplina é a Lição 5.

As quatro invariantes — exatamente quatro

A arquitetura se apoia em quatro propriedades (não mais — o mapa enumera exatamente estas). Cada uma é afirmada na fonte, e a maioria é governada por uma ADR.

#InvarianteComo é mantida
1run() nunca lança; o resultado é uma união discriminada uniforme.Estrutural, via runWithGuards (adapters/src/adapter-core.ts:118). ADR-0009.
2Engines são agnósticos 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 a run $0).
3IDs endereçados por conteúdo + layout determinístico de run-dir (para runs replicarem).O id de uma run é o hash de conteúdo SHA-256 do spec; stores são endereçados por conteúdo sobre JSON canônico, então re-anexar conteúdo idêntico é no-op. Módulos de plano não podem usar Date.now()/Math.random().
4Dissidência é preservada/forçada pelo Verifier, não apenas 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 rígido de carga do board. ADR-0003.
Por que "exatamente quatro" vale dizer. Um rascunho anterior deste curso alegava seis invariantes. O mapa verificado na fonte enumera quatro. A disciplina do projeto inteiro é contar o que está no código, não o que soa redondo — então: quatro.

O funil — uma destilação de 4 níveis

A razão de existir do Alembic é 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 níveis de custo, barato-primeiro, para que a maior parte do trabalho custe nada e só os sinais mais fortes cheguem a um modelo pago.

T0 · walk determinístico → dedupe SHA-256 → valida contrato → score 6-dim $0 · 100% do corpus T1 · um BusinessSignal por item de resíduo (adapter LOCAL) ~$0 · local T2 · shortlist FRONTIER com portão de budget refina os mais fortes medido T3 · council + verifier N-lentes → verified-GO medido
NívelO que fazCusto
T0runT0Pipeline determinístico: percorre o corpus → dedupe SHA-256 → valida contrato → score 6-dim → emite resíduo. Roda sobre 100% do corpus.$0
T1runT1Extraction: um BusinessSignal por item de resíduo via o adapter LOCAL injetado (free-tier, então na prática nunca bloqueado por budget).~$0
T2runT2Shortlist: um shortlist FRONTIER com portão de budget refina os sinais T1 mais fortes em lotes; toda chamada paga é medida.medido
T3runT3Council: um council sintético de 3 membros (otimista / analista / pessimista) mais o painel verifier de N-lentes.medido
O sinal verified-GO — a barra de qualidade do funil

Um resultado T3 emite quando ambos a decisão de consenso é GO e isPanelEmissionApproved(report) vale — o painel de N-lentes verificou, não estacionou. Maioria simples não basta; o painel pode vetar. É isso que mantém o grafo de oportunidades honesto: nada sedimenta sem passar o portão de emissão.

Três invariantes de segurança que o funil nunca deve regredir: PII antes da saída (um sinal de canal privado é redatado antes da chamada de modelo e re-checado antes de qualquer escrita), budget fail-closed (toda chamada paga é encapsulada num BudgetGuard.check fail-closed — uma quebra projetada bloqueia a chamada e o nível degrada em vez de gastar demais), e append-only (resultados fluem para stores endereçados por conteúdo, validados por schema, append-only; leituras de fonte permanecem somente-leitura).

Confusões comuns

"Nunca-lança só significa que envolveram tudo em try/catch." Em parte — mas o ponto é que o try/catch vive num lugar só (runWithGuards), então a falha é convertida em valor tipado uma vez e todo consumidor a lê uniformemente. A vitória é a ausência de tratamento de erro em todo o resto, não a presença dele no adapter.
"Os níveis são sobre velocidade." São sobre custo e confiança. T0 é grátis e roda em tudo; cada nível acima é mais caro e vê itens menos numerosos e mais fortes. Quando você chega a um modelo de fronteira pago (T2/T3) está gastando dinheiro só em sinais que já sobreviveram a dois passos mais baratos.
"Determinismo é um nice-to-have." É carga estrutural: IDs endereçados por conteúdo sobre JSON canônico são o que torna uma run replicável — re-rodar um corpus inalterado produz só duplicatas e não toca deltas. É também por isso que módulos de plano não podem chamar Date.now() / Math.random(); a VM os rejeita.
1. Um provedor retorna HTTP 429 (rate-limited) no meio de uma run. Como isso chega ao engine acima de L1?
Correto: c. A cintura estreita significa que run() nunca lança; um 429 vem como um ModelRunFailure com error.retryable setado. runWithGuards converte qualquer throw escapado nesta forma e até dirige o retry pelo flag. E o roteamento não tem fallback silencioso — um modelo ausente retorna um erro tipado, nunca um substituto.
2. Quantas invariantes arquiteturais o mapa enumera, e o que impõe "engines são agnósticos de store"?
Correto: b. Exatamente quatro invariantes. O agnosticismo de store/adapter é estrutural: os kernels recebem seus side-effects por injeção em vez de importá-los, o que também os torna testáveis em memória e deixa um registry de adapter offline rodar o funil inteiro hermeticamente de graça.
3. Por que o funil roda quatro níveis em vez de mandar tudo para um modelo de fronteira?
Correto: d. O funil é um filtro de custo. O piso determinístico T0 é grátis e roda em tudo; T2/T3 com portão de budget gastam dinheiro só em sinais que já passaram passos mais baratos, e uma emissão T3 exige tanto um consenso GO quanto aprovação do painel.