O território
Monorepo, não um programa
O Alembic são 27 pacotes + apps/cli em workspaces pnpm, orquestrados pelo Turborepo. Leia o mapa antes da rua.
1Antes de mudar qualquer linha, você precisa ler o repositório como uma engenheira sênior. Não pelo primeiro arquivo que abre — mas pela ordem certa: as configs raiz que governam o monorepo, os contratos que dão forma a tudo, o binário alembic e seus comandos, uma execução de ponta a ponta, as surfaces HTTP/SSE/MCP e, por fim, os testes — que são o mapa real dos invariantes. Este módulo é essa planta-baixa.
packages/* mais apps/cli, orquestrados pelo Turborepo.pnpm-workspace.yaml, package.json, turbo.json, vitest.workspace.ts) — ela explica todo o resto.Result<T,E>, Tier e o ModelRunResult (a cintura estreita) de @alembic/contracts.alembic em apps/cli/src/index.ts e os 30 comandos que ele despacha.alembic distill --offline) do parse até o funil e os stores.type, uma função, um import.monorepo, workspace, contrato, narrow waist (cintura estreita), store e SSE são definidos aqui.pnpm-workspace.yaml, package.json e turbo.json na raiz; packages/contracts/src/ (os tipos); apps/cli/src/index.ts e commands.ts (os comandos); packages/harness/src/http.ts e mcp.ts (as surfaces). Tudo neste módulo é verificado contra esses arquivos.
packages/* + apps/clialembic*.test.ts no workspaceOs números são contados no código, não estimados. O comando para conferir está em cada seção — a disciplina do Alembic é provar, nunca afirmar de memória.
O Alembic não é um único programa. É um monorepo: um repositório que guarda muitos pacotes pequenos que se encaixam. Abrir um arquivo qualquer e tentar entender o todo é como entrar numa cidade por um beco aleatório. A leitura sênior começa pelo mapa: quais são os bairros (os pacotes), quem manda (as configs raiz) e por onde a água corre (uma execução).
O Alembic é um monorepo pnpm com workspaces declarados em pnpm-workspace.yaml (packages/* e apps/*). Hoje são 27 pacotes sob packages/ (de @alembic/contracts a @alembic/marketing-factory) e um app, apps/cli (o binário @alembic/cli). O Turborepo orquestra quatro tarefas — build, typecheck, test, lint — respeitando o grafo de dependências entre pacotes.
O sistema de módulos é ESM com resolução NodeNext e TypeScript em modo strict. Bibliotecas evitam lançar exceções: usam o Result<T,Error> fail-closed de @alembic/contracts. IO de arquivo passa pelo FsPort de @alembic/etl para ser testável.
O mapa do repositório. As configs raiz no topo governam; cada cartão abaixo é um workspace com uma responsabilidade única. Comece sempre de cima.
@alembic/cli importa @alembic/contracts como se fosse uma dependência publicada — mas é a pasta vizinha.Quatro arquivos na raiz são a constituição do projeto. Lê-los primeiro responde "o que existe?", "como eu valido?" e "como rodo os testes?" antes de qualquer detalhe.
pnpm-workspace.yaml — declara os workspaces: packages/* e apps/*. É a lista de bairros da cidade.package.json (raiz) — os scripts: typecheck, build, test, lint, detect, governance, factory. É o painel de controle.turbo.json — o pipeline do Turborepo: as tarefas build · typecheck · test · lint e suas dependências. É quem sabe a ordem.vitest.workspace.ts — junta os projetos de teste de cada pacote. É o mapa de onde os testes vivem.Toda mudança de código tem que manter três comandos verdes. Decore-os — eles são o portão de entrada de qualquer trabalho no Alembic:
pnpm -r typecheck && pnpm -r build && pnpm -w test
.github/workflows/ci.yml) roda exatamente esta sequência. Se um passo falha, o pipeline falha fechado — nada entra na main sem os seis verdes.Você roda pnpm -r build e o build falha em @alembic/swarm com erro de tipo num import de @alembic/contracts — que você acabou de editar. Qual a causa mais provável?
contracts mas não rodou pnpm -r build nele antes. Pacotes dependentes enxergam o contrato pelos .d.ts publicados; sem rebuild, o swarm compila contra a versão antiga. A regra do projeto: depois de mudar um pacote, rode pnpm -r build para os dependentes verem o novo .d.ts.O pacote @alembic/contracts é a cintura estreita do sistema: os tipos e schemas que todos os outros pacotes compartilham. Lê-lo primeiro é o atalho — depois disso, cada função em cada pacote tem um formato que você já reconhece.
Result, Tier e ModelRunResult são as peças do Alembic.Result<T, E = Error> (result.ts): uma união discriminada em ok. Ou { ok: true, value } ou { ok: false, error }. Bibliotecas devolvem Result em vez de lançar — falha vira valor, não exceção.
Tier (tier.ts): a escada de autonomia T0 → T4. T0 é silencioso e $0; T4 é park (exige council + humano). Há ainda o marcador LOCAL para trabalho que deve ficar em modelos locais/$0.
ModelRunResult (model.ts): a união discriminada que toda chamada de adapter devolve — modelRunSuccessSchema (ok: true, com text, usage, costUsd) ou modelRunFailureSchema (ok: false, com um error estruturado e retryable). Sucesso e falha são o mesmo tipo de retorno.
T0 roda em tudo de graça; T4 nunca executa sozinho.Result<T,E>?ok: sucesso {ok:true,value} ou falha {ok:false,error}. Bibliotecas o devolvem em vez de lançar — fail-closed.T0 significa na escada de Tier?$0, sem humano no loop. A escada sobe até T4 (park — exige council + humano).ModelRunResult — união em ok: success (com text) ou failure (com error + retryable). É a cintura estreita.packages/contracts/src/result.ts, depois tier.ts, depois model.ts. São arquivos pequenos e densos — em 15 minutos você lê a "gramática" do projeto inteiro.O programa que você roda no terminal — alembic — vive em apps/cli/src/index.ts. Ele faz três coisas em sequência: analisa os argumentos, despacha para o comando certo e devolve um Result. Hoje são 30 comandos.
index.ts chama parseCliArgs (de args.ts, que usa schemas Zod por comando) e então um dispatch que é uma cadeia de if (parsed.command === '…'). Cada ramo chama uma função de comando em commands.ts (ou em módulos de seam como marketing-seams.ts), que devolve um Result<T, Error>. O USAGE impresso pelo alembic help também mora em index.ts.
Para adicionar um comando, o fluxo é fixo: schema em args.ts → função em commands.ts → ramo em index.ts → linha no USAGE → teste em commands.test.ts ou e2e.test.ts → doc em CLAUDE.md e README.md.
Explore alguns comandos representativos — clique nas abas:
--offline é hermético e custa $0: valida o corpus, monta um registry offline e roda runFunnel, renderizando um FunnelReport.distill e run a employee, marketing, memory e doctor — todos passam pelo mesmo parse → dispatch → Result. Aprender um é aprender a estrutura de todos.apps/cli/src/index.ts e procure por parsed.command ===. Cada ocorrência é um comando. Conte-as: são 30.'doctor'. Veja qual função ele chama no ramo (em commands.ts).Result. Esse é o contrato de saída de todo comando.rg -n "parsed.command ===" apps/cli/src/index.ts | wc -l e confira que dá 30. Depois escolha 'embed' e siga o mesmo caminho até o Result.Ler arquivos isolados ensina os bairros; seguir uma execução ensina como a água corre entre eles. alembic distill --offline é o melhor primeiro trajeto: ele toca CLI, contracts, adapters, ETL e harness numa única corrida — e de graça.
A ordem de leitura sênior. Do contrato à execução às surfaces — os números (30 comandos, 174 arquivos de teste) são verificados no código.
--offline nada vai à rede — é o jeito de estudar o sistema sem gastar nem depender do gateway.alembic status não roda o funil — ele só lê contadores dos stores. Quem executa o trabalho (T0–T3) é o distill. Confundir os dois leva a "por que nada aconteceu?".status consulta, distill produz. Saber qual é qual responde "por que nada mudou?".Além do terminal, o Alembic expõe um servidor. As surfaces (HTTP, streaming SSE e MCP) são finas: elas não contêm lógica de negócio, só traduzem pedidos para o HarnessCore. O núcleo não conhece transporte — por isso é fácil testar e adicionar novas surfaces depois.
As rotas HTTP estão em packages/harness/src/http.ts — o objeto ROUTES declara exatamente três: POST /runs (a única escrita — inicia um run), GET /runs/:runId/status e GET /runs/:runId/events (o stream SSE).
As ferramentas MCP estão em mcp.ts — o registro MCP_TOOLS traz três stateless: harness_status, harness_events e harness_lane. Há ainda ferramentas read-only com escopo de run, como context_pack e artifact_read. Todas leem o snapshot/eventos do run via o HarnessCore e o FsPort.
HarnessCore não muda.POST /runs é a única escrita. Tudo o mais é leitura (status, events) ou ferramentas MCP read-only. Isso mantém a superfície de mutação minúscula e auditável.Documentação envelhece; testes não — eles rodam. Quando quiser saber o que o sistema promete, leia os testes do pacote. São 174 arquivos *.test.ts distribuídos por pacote, cada um protegendo um invariante.
| Onde olhar | Que invariante o teste protege |
|---|---|
packages/etl/src | Append-only e PII: stores só acrescentam; dados sensíveis são barrados. |
packages/adapters/src | run() nunca lança: falha de modelo vira ok:false, não exceção; custo é contabilizado. |
packages/council/src | Scoring e dissenso: votos agregam de forma determinística; o verifier preserva a discordância. |
packages/harness/src | Rotas e MCP: as 3 rotas e as ferramentas respondem com o formato do contrato. |
packages/swarm/src | Resume: um run interrompido retoma do events.jsonl + cache sem refazer trabalho. |
apps/cli/src | e2e: parse → dispatch → Result de ponta a ponta para os comandos. |
find packages apps -name '*.test.ts' | wc -l devolve 174. E vitest.workspace.ts mostra que cada pacote roda seus próprios testes — o @alembic/factory é um caso especial, vendorizado e isolado da raiz.HANDOFF.md — é o catálogo confiável do que o sistema garante.HANDOFF.md e o status semanal ajudam a se orientar, mas uma decisão de release precisa rodar os comandos atuais. Documentos atrasam; o checkout é a fonte.
alembic status só lê contadores. Quem executa o funil (T0–T3) é o distill. Se "nada aconteceu", você provavelmente rodou o leitor, não o executor.
O servidor do harness existe e expõe 3 rotas, mas surfaces extras (console, tRPC) e partes de produto são follow-ups documentados. Ler o código evita prometer o que ainda não está lá.
Mapas antigos citam contagens menores. O workspace cresceu — hoje são 27 pacotes + apps/cli. Sempre conte no código (find … -name package.json), não na memória.
find packages apps -maxdepth 2 -name package.json -not -path '*/node_modules/*' — confira os 27 pacotes + apps/cli.rg -n "parsed.command ===" apps/cli/src/index.ts | wc -l — devolve 30. Abra args.ts e commands.ts para ver o parse → dispatch → Result.rg -n "ROUTES" packages/harness/src/http.ts — confira POST /runs, GET …/status, GET …/events. Depois rg -n "MCP_TOOLS" mcp.ts.package.json e turbo.json na raiz — o contrato de validação local é typecheck · build · test (verde antes de qualquer commit).Passe pelos slides com os botões, as setas do teclado (← →) ou os pontos. Cada um é um pilar deste módulo.
O território
O Alembic são 27 pacotes + apps/cli em workspaces pnpm, orquestrados pelo Turborepo. Leia o mapa antes da rua.
1A ordem
@alembic/contracts primeiro: Result, Tier, ModelRunResult. Depois disso, cada função tem um formato que você já reconhece.
O binário
apps/cli/src/index.ts despacha 30 comandos. Aprender um (o caminho até o Result) é aprender a estrutura de todos.
As surfaces
HTTP, SSE e MCP só traduzem para o HarnessCore. 3 rotas (POST /runs é a única escrita); o core não conhece transporte.
A prova
174 arquivos *.test.ts: append-only, PII, run-nunca-lança, scoring, MCP, resume. Quer saber o que o sistema promete? Leia o teste.
apps/cli, orquestrados pelo Turborepo.build · typecheck · test · lint).Result, Tier, ModelRunResult — a cintura estreita.parse → dispatch → Result; as surfaces (3 rotas + MCP) são finas sobre o núcleo.ModelRunResult é a cintura estreita: modelRunSuccessSchema (ok:true, com text/usage/costUsd) ou modelRunFailureSchema (ok:false, com error + retryable). a viola o "nunca lança"; b joga fora o texto e o erro; d confunde adapter com store.alembic status e alembic distill?status é leitura — conta o que está nos stores. distill é execução — valida o corpus, monta o registry (offline = $0) e roda runFunnel. b inverte os papéis; c é falso; d inverte quem escreve.status ≠ distill, o que é a cintura estreita? Pergunte ao seu agente (o seu professor). O próximo módulo sobe um nível: produto e modelo de negócio — o que essa máquina vira quando vira oferta.