No fim desta lição você vai entender como o Alembic pega o signal "fábrica de petições personalizadas" e o forja em uma venture — orquestrando agentes (um lead que distribui trabalho a workers), provando cada passo na fronteira real, e deixando cada uma das cinco porteiras (Scope → Council → Proof → Validator → Publish) liberar — ou travar — o passo seguinte.
Esta lição destila o coração da orquestração: packages/swarm/src (lead→workers), packages/harness/src (o core + HTTP/SSE/MCP), packages/mission/src/compiler.ts (units → tasks + proof) e packages/coda/src (os gates). Para um produto interno, a fonte da verdade é o código + a saída real do CLI — nada de memória.
Na lição 1 você viu o Alembic como duas metades: o motor de destilação (lê o corpus, produz LEARNINGS + SIGNALS) e o harness de agentes (pega um signal e o forja em produto). As lições 2 e 3 cobriram a primeira metade. Agora abrimos a segunda: como o harness orquestra agentes para construir, e como cada porteira garante que nada irreversível escape sem prova. O nosso signal — "fábrica de petições personalizadas" — vira aqui uma venture, passo a passo, com cada passo verificado na fronteira real.
Assumo muito pouco: que você já viu um comando de terminal e sabe que um programa pode "dar certo" (sair com código 0) ou "dar errado" (sair com código diferente de 0). Não assumo que você sabe o que é orquestração de agentes, um gate, SSE ou MCP — tudo isso é definido no primeiro uso. O nome "tolas" é uma piada interna da série For Dummies: você é esperto, só é novo no assunto.
Forjar um produto é trabalho demais para um agente só. O Alembic resolve isso como uma oficina com hierarquia: um orquestrador abre a obra, um lead (líder de equipe) quebra a obra em tarefas e as distribui para vários workers (operários) que trabalham em paralelo. Cada operário faz UMA peça e entrega um relatório.
Mas velocidade sem controle é perigoso. Então, entre o trabalho e o "pronto", existe uma esteira de cinco porteiras (gates). Cada porteira inspeciona a peça e só libera a próxima se a anterior passou. Se uma prova falha, a esteira para — em vez de empurrar trabalho quebrado adiante. É isso que significa falhar fechado.
Pense como… uma linha de montagem de carros com estações de inspeção. O gerente (orquestrador) abre o turno, o chefe de linha (lead) escala os montadores (workers), e cada carro passa por postos de qualidade. Se o teste de freio reprova, o carro não segue para a pintura — a linha trava ali. Onde a analogia quebra: aqui os "montadores" são chamadas a modelos de IA ou subprocessos, e o "carro" é um pedaço de software (ou uma campanha, ou uma venture).
São três papéis, ordenados por profundidade: orchestrator (0) → lead (1) → worker (2). A profundidade é estruturalmente limitada: MAX_DEPTH = 2, e um worker é uma folha que não pode gerar mais ninguém — isso é garantido tanto no schema (subtasks não aninham) quanto em runtime (canSpawn). Todo valor que cruza uma fronteira de durabilidade (linha JSONL, checkpoint, arquivo de relatório) é um schema Zod validado na leitura — o filesystem é a fonte da verdade, então uma linha corrompida é rejeitada na borda, nunca confiada.
O harness em si é um core neutro de transporte: a classe HarnessCore tem quatro verbos (start / poll / fanout / report) e não sabe como foi invocada. CLI, HTTP+SSE e MCP são adaptadores finos sobre o mesmo objeto. As cinco porteiras vivem em packages/coda/src e cada etapa fail-closed devolve Result<T, Error> — a cintura estreita da lição 1.
Ao terminar, você vai conseguir:
orchestrator → lead → worker e por que a profundidade é limitada a 2.units[] e proof[] e dizer o que vira tarefa e o que vira prova.run, serve, cockpit — e o que cada uma expõe.alembic status (tier T4, as 7 fases).O harness tem um conductor único no meio (o HarnessCore) que não sabe como foi chamado. Você pode falar com ele de três jeitos — pela linha de comando, por HTTP, ou por MCP — e os três acionam o mesmo motor. Esse motor conduz duas coisas: o council (a deliberação) e o swarm (a execução).
EventBus transmite cada passo ao vivo. Forjar a venture "fábrica de petições" usa exatamente este caminho.HarnessCore não importa nenhum transporte, não abre porta de rede e não formata saída. Trocar a CLI por um servidor web não muda o motor. Em packages/harness/src/core.ts, o método fanout roda o council antes do swarm de propósito: se o veredito for NO_GO, ele curto-circuita antes de despachar qualquer worker — "uma banca que não aprova não pode gerar trabalho autônomo".Quando uma tarefa carrega subtarefas, ela vira um lead. Em vez de fazer o trabalho sozinho, o lead lança as subtarefas para vários workers em paralelo. Mas ele não solta todos de uma vez: lança um punhado, espera um pouco, libera mais um — uma rajada controlada (ramp). Isso evita sobrecarregar o sistema.
canSpawn).O ramp tem três números: começa lançando 5 de imediato (initialLimit), libera +1 a cada 700 ms (intervalMs), até o teto maxConcurrency. É o "padrão Kimi" de subir devagar.
Um worker não fala com o orquestrador por um canal de memória. Ele escreve um arquivo. Primeiro grava o relatório em <id>.report.md (em progresso); quando termina, renomeia para <id>.complete.md (deu certo) ou <id>.failed.md (falhou). O orquestrador só observa os nomes finais.
rename() é o "commit": atômico. O orquestrador nunca vê um relatório pela metade — e o trabalho sobrevive à morte do processo, coisa que um canal em memória não faria. Um --resume reanexa ao relatório em vez de refazer o trabalho.background: true (padrão tac-9 /background): o worker roda como processo destacado, então sobrevive mesmo se o orquestrador cair no meio. Útil para builds longos. Só vale para tarefas com command (não para chamadas a modelo).Um lead tem maxConcurrency: 20. Cinco workers são lançados de imediato. Três deles terminam em 200 ms. Quantos workers o ramp permite lançar no total ao chegar em 700 ms?
Seis. Os 5 iniciais + 1 liberado no primeiro tick (700 ms). Os três que terminaram não abrem vaga: o ramp conta o total lançado ao longo do tempo, não os em voo. Esse é justamente o detalhe contraintuitivo de drainWithRamp.
units[] e proof[]Você não escreve tarefas de swarm na mão. Você descreve uma missão: uma lista de unidades de trabalho (units), e para cada uma, uma lista de provas (proof) — comandos que precisam passar. O compilador transforma isso em tarefas de verdade.
# cada unit vira UMA tarefa de worker… tasks.push(taskSpecSchema.parse({ id: unit.id, title: unit.title, tier: unitTier, roleHint: 'worker', prompt: buildUnitPrompt(unit), ... })); # …e cada string de proof[] vira uma tarefa de COMANDO # que depende da unit e falha fechado se sair != 0 for (const [index, proof] of unit.proof.entries()) { tasks.push(taskSpecSchema.parse({ id: `${unit.id}-proof-${index}`, command: ['bash', '-c', proof], // roda da raiz do repo dependsOn: [unit.id], // só roda se a unit terminou metadata: { kind: 'proof', unitId: unit.id, proofIndex: index }, })); }
Para a venture "fábrica de petições", uma unit poderia ser "implementar o gerador de petição a partir do template do escritório", com proof: ['pnpm -r typecheck', 'pnpm -w test']. Se o teste reprovar, a prova falha — e a missão inteira falha fechado.
id, title, tier, promptmilestoneId, skillName, fulfillsbash -cdependsOn a unit — só roda depois delacomplete; qualquer outro → failedunits/<id>/proof-results.jsonlproof[] é "documentação" do que testar. Não é. Cada string é executada de verdade, da raiz do repo, e um exit diferente de zero derruba a run. Escreva provas que você realmente quer que travem o trabalho.Entre o trabalho e o "publicado", a peça passa por cinco porteiras em ordem. Cada uma tem um objetivo e o poder de travar. Pense em postos de inspeção: se um reprova, a peça não avança.
NO_GO encerra a fase antes de qualquer worker.| Gate | Arquivo | O que verifica | Se reprova |
|---|---|---|---|
| 1 · Scope | forge/src/scope.ts | copia GOAL.md + plano + contrato pro run-dir | run não inicia |
| 2 · Council | mission/src/council-gate.ts | GO/NO_GO pré-voo (opcional) | NO_GO curto-circuita o swarm |
| 3 · Proof | coda/src/proof.ts | roda as proof[]; exit 0? | devolve err → run falha fechado |
| 4 · Validator | coda/src/validator.ts | council independente revê a evidência | NO_GO/rejeitado → park |
| 5 · Publish | coda/src/publish.ts | ação outward (curso, manifesto) | sem aprovação → t4-parked.jsonl |
t4-parked.jsonl): nunca auto-executa, espera aprovação humana. "Na dúvida, estaciona."units/<id>/proof-results.jsonl — append-only, auditável, lido pelo Validator como evidência.Aqui está o trecho real que torna "falhar fechado" concreto. O Proof Gate junta os resultados das provas; se qualquer uma falhou, ele devolve um erro — e quem chamou derruba a run.
# trecho real, não editado
const failed = results.filter((r) => r.outcome === 'failed');
if (failed.length > 0) {
const summary = failed
.map((f) => `unit=${f.unitId} index=${f.proofIndex} command="${f.command.join(' ')}"`)
.join('; ');
return err(new Error(`Proof Gate failed: ${summary}`));
}
return ok(results);
No repo: packages/coda/src/proof.ts (a função runProofGate). Ela lê os estados das tarefas do journal append-only (events.jsonl) — por isso sobrevive a sobrescritas de checkpoint quando várias chamadas runSwarm compartilham o mesmo store. O Validator depois lê proof-results.jsonl como evidência (readProofResults em validator.ts).
Os três gates fail-closed em ~100 linhas cada. Todos devolvem Result<T, Error> — nunca lançam. É a cintura estreita da lição 1 aplicada à orquestração.
run · serve · cockpitO mesmo motor, três jeitos de operar. run executa uma missão no terminal. serve sobe um servidor HTTP+SSE (e o bind MCP read-only) para acionar e acompanhar runs por rede. cockpit é o painel web sobre os diretórios de run.
start · poll · fanout · report). A CLI, o servidor e o painel são adaptadores finos — trocar a superfície não muda o motor.O serve aceita um POST /runs com { phase:"run", goal, plan, yes } e responde na hora com um 202 (aceito) — a run roda em background, e você acompanha por GET /runs/:id/status ou pelo stream de eventos. O bind MCP é só-leitura: um assistente externo pode ler o andamento, mas nunca disparar trabalho.
Em packages/harness/src/server.ts: node:http puro, tabela ROUTES (POST /runs, GET /runs/:id/status, GET /runs/:id/events) + a rota especial POST /runs/:id/mcp que fala JSON-RPC (initialize / tools/list / tools/call). As ferramentas MCP são harness_status, harness_events, harness_lane (+ as run-scoped context_pack e artifact_read) — todas read-only: não há start/fanout por MCP, então um host não pode mutar a run. Erros JSON-RPC viajam no envelope, então o status HTTP é 200 para uma troca bem-formada.
serve responde 202 imediatamente e roda a run em background; você acompanha por status (pull) ou pelo stream SSE (push). A verdade dura segue no diretório da run.run é para o operador no terminal (forjar a venture localmente, com --yes). serve é para acionar runs de fora e deixá-las rodando — o caso AFK, com observabilidade por SSE. Em ambos, a verdade dura mora no diretório da run.
Para integrar com assistentes sem dar a eles o poder de gastar ou mutar. Um host MCP enxerga status, eventos e artefatos da run — perfeito para um copiloto — mas disparar trabalho continua sendo decisão de quem opera.
Vamos seguir o signal pelo caminho inteiro, do comando à esteira.
alembic run --goal GOAL.md --plan alembic.plan.ts --yes. O Scope Gate copia GOAL.md, o plano e o contrato para o diretório da run.proof[] vira um bash -c que depende dela.pnpm -w test reprova, o gate devolve err e a run falha fechado.tier: 'T4' (publicar a landing do escritório). O que acontece com ela no fan-out — e em qual ledger ela aparece? (Dica: reveja a partição e o park.)Clique Rodar a missão e veja o artefato da venture atravessar as cinco porteiras, uma de cada vez. Depois ligue a chave "injetar prova que falha" e rode de novo: o Proof Gate vai falhar fechado e a esteira para ali. É a teoria desta lição, executável.
Este é um modelo da lógica real: o Proof Gate de proof.ts devolve err quando uma prova sai != 0, e o fanout de core.ts faz o NO_GO do council curto-circuitar o swarm. Aqui você dispara essas duas travas com um clique.
Os seis pontos para levar. Use as setas ← → ou os botões.
O Alembic é conservador por padrão: o tier-default é T4 — ou seja, na ausência de configuração, o trabalho estaciona para um humano. Rode alembic status e confirme:
# saída real, offline, $0 — RESOURCES.md
status: default tier T4
phases: discover -> validate -> design -> plan -> build -> review -> ship
stores (.alembic): 4 opportunity, 0 learnings
As sete fases (discover → validate → design → plan → build → review → ship) são o arco que toda venture percorre. O tier T4 default é a razão de tanto trabalho cair no park: a segurança vem ligada de fábrica.
proof[] é comentário. Cada string roda de verdade e pode derrubar a run.MAX_DEPTH = 2.start.--resume. O resume reanexa ao relatório existente — não re-executa.HarnessCore expõe status; o diretório da run é a fonte durável.Recupere da memória antes de conferir. Cada opção explica por que acerta ou erra.
1. No fanout do harness, por que o council roda antes do swarm?
2. Uma proof[] sai com código 1. O que acontece com a run?
runProofGate filtra as falhas e devolve err(new Error('Proof Gate failed: …')) — fail-closed.3. Um host MCP conectado a POST /runs/:id/mcp pode iniciar uma nova fase?
harness_status/events/lane (+ context_pack/artifact_read) — não há start/fanout por MCP.harness_start. O bind expõe só leitura, por design de segurança.units[] e proof[] de verdade?" ou "o que o Validator faz diferente do Proof?" · A seguir (0005): a venture vai precisar de alguém para operar o atendimento — a Iris, um AI Employee que compõe soul + skills + memória + connectors + agenda.