A pipeline de gates
Uma run não "executa e termina". Ela atravessa uma sequência de gates — Scope, Council, Proof, Validator, Publish — e cada um pode detê-la. Uns são pré-voo, um fecha a run, outro é opcional, o último exige um humano. Juntos são a disciplina que transforma "o agente produziu algo" em "o agente produziu algo verificado". No fim você vai conseguir, de cabeça, dizer qual gate barra o quê — e por que o ReviewGate do ciclo de aprendizado (lições 4 e 8) é membro exato da mesma família.
Cada número e cada linha de código desta lição foi lida literalmente desses arquivos (rodapé). Por que importa pra missão: os gates são o que faz a autonomia do Alembic ser segura — sem eles, "o agente fez" não significa "está certo".
- Definir gate e fail-closed: o padrão é sempre "não prosseguir / estacionar para humano", nunca "assumir OK".
- Nomear os cinco gates em ordem e dizer, para cada um, o que ele barra e quando (pré-voo, durante, no ship).
- Explicar por que o Proof Gate lê o journal de eventos e não o checkpoint.
- Distinguir Council Gate (pré-voo) de Validator Gate (pós-trabalho) e situar o ReviewGate (
scoreThresholdGate) na mesma família.
01 · O que é um "gate" — e o que "fail-closed" significa
Pense num gate como uma catraca de metrô: ela só abre se você apresentar a prova certa (o bilhete). Sem bilhete, ela fica fechada — esse é o padrão. Um gate de software faz o mesmo: ele recebe uma evidência, decide GO ou NO_GO, e na dúvida fica fechado. É isso que "fail-closed" quer dizer: se algo falha, dá erro, ou é ambíguo, o resultado padrão é não deixar passar, não "passar por garantia".
Guarde essa silhueta: nas seções seguintes, cada um dos cinco gates é uma instância dela. Muda qual evidência entra e qual pergunta o losango faz — a forma fail-closed é sempre a mesma.
A mesma ideia em duas camadas
Antes de descer ao código, fixe o conceito nos dois níveis. Clique para alternar entre a explicação simples e a precisa — é a mesma regra, contada para dois leitores diferentes.
Imagine uma fechadura de porta corta-fogo: quando falta energia, ela tranca sozinha — nunca destrava. O projetista escolheu o estado seguro como padrão. Um gate fail-closed é igual: se a evidência não chega, se um comando dá erro, se a resposta é ambígua, o gate assume "não passa". Você nunca precisa lembrar de "trancar"; o sistema já nasce trancado e só abre com a prova certa na mão.
Por quê? Porque os dois erros possíveis não são iguais. Barrar uma run boa por engano incomoda, mas é reversível: um humano olha e reabre. Deixar passar uma run ruim pode quebrar algo no mundo — e isso, às vezes, não tem volta. Na dúvida, o sistema prefere o erro que dá para desfazer.
Em termos de tipos, todo gate é uma função que devolve um Result<T, Error> (o contrato fail-closed de @alembic/contracts): o caminho de sucesso é explícito (ok(...)) e qualquer outro caminho — exceção capturada, evidência ausente, veredito negativo — colapsa para err(...). Não existe um terceiro estado "provavelmente ok". O chamador (o núcleo do harness) propaga o err e a run para; nenhum gate "degrada para aviso e segue".
Operacionalmente isso se traduz em três desfechos só: prossegue (próximo gate), falha a run fechada (err — ex.: Proof/Scope) ou estaciona para humano (t4-parked.jsonl — ex.: Validator/Publish). A assimetria é deliberada: é o ADR-0005 levado ao código — o passo irreversível exige a assinatura de uma pessoa; tudo que é reversível roda sozinho.
02 · A pipeline, em ordem
Aqui está o caminho inteiro de uma run, da rampa de entrada ao ship. Leia da esquerda para a direita: cada caixa é um gate, e abaixo da fileira está a anotação de onde o ReviewGate do ciclo de aprendizado se encaixa (seção 09).
--council / config do plano), mas o mesmo princípio é imposto dentro do núcleo do harness — no fanout, um NO_GO faz curto-circuito antes de qualquer worker (seção 04).03 · ① Scope Gate — materializa o diretório da run
A rampa de entrada. loadScope(input) (forge/src/scope.ts) copia o GOAL.md, o módulo de plano e o contrato de validação para um diretório de run content-addressed e semeia seu layout. Pense numa obra que recebe uma planta: antes de a primeira parede subir, o canteiro precisa estar montado — fundação, almoxarifado, diário de obra. O Scope Gate é esse canteiro.
A regra-chave: um resume não pode trocar o escopo
Aqui está o ponto de segurança do Scope Gate. Quando você retoma uma run com --resume <run-id>, o goal/plan/contract de agora são comparados com o meta.json salvo. Se qualquer um diverge, ele retorna err('resume mismatch: …'). Você não consegue, por acidente, re-escopar uma run que está retomando — o que evita "continuar" uma run com objetivos diferentes dos originais.
meta.json é onde o escopo fica gravado — e o resume-mismatch é a invariante que impede você de "trocar a planta no meio da obra".04 · ② Council Gate — veto de pré-voo opcional
runCouncilGate (mission/src/council-gate.ts) roda um board antes de o trabalho começar e retorna uma CouncilDecision agregada (lê consensus.decision). NO_GO significa que a missão não deve prosseguir. É a reunião que decide se o projeto sequer começa — não uma revisão do que ficou pronto. É opcional, mas o núcleo do harness impõe a mesma ideia internamente.
DEFAULT_MISSION_BOARD_YAML tem três papéis — optimist, pragmatist e um contrarian (que empurra contra risco, ambiguidade e ações irreversíveis). A decisão não é de um modelo só; é um consenso. Isso é o tema inteiro da Lição 18.05 · ③ Proof Gate — a espinha determinística
Este é o gate que faz "funciona" significar algo. Cada string unit.proof[] numa missão compila para uma task de comando bash -c que depende da task da unit (compiler.ts:140–157). O Proof Gate lê os estados dessas tasks e falha a run fechada em qualquer saída não-zero. É a diferença entre "o agente disse que fez" e "um comando real saiu 0".
proof: ['pnpm -r typecheck', 'pnpm -w test']. Quantas tasks essa unit gera no spec compilado, e que tipo de comando cada proof vira?
['bash', '-c', '<a string>'] com dependsOn: [unit.id] e metadata.kind: 'proof' (compiler.ts:140–157). Se você chutou "uma task que roda os dois comandos", repare: cada proof é sua própria task, indexada por proofIndex — por isso o gate consegue dizer exatamente qual proof falhou.Como a unit vira tasks (a compilação)
O código do gate, condensado
O laço do gate é deliberadamente curto: toda task kind: 'proof' precisa ter saído 0 (status === 'done'); qualquer falha entra num resumo e o gate retorna err. Sem retry, sem degradar para aviso.
// packages/coda/src/proof.ts:55–96 (condensado) — falha fechado, lê o journal const results = proofTasks.map((task) => { const state = stateById.get(task.id); // estado vindo do journal const outcome = state?.status === 'done' ? 'complete' : 'failed'; return { unitId: task.metadata.unitId, proofIndex: task.metadata.proofIndex, outcome }; }); // persiste em units/<unitId>/proof-results.jsonl, depois: const failed = results.filter((r) => r.outcome === 'failed'); if (failed.length > 0) return err(new Error(`Proof Gate failed: ${summary}`)); // run falha fechada
Por que ler o journal e não o checkpoint? (comparativo)
Um detalhe sutil e crucial: o gate lê os estados das tasks do journal de eventos (store.readEvents()), não do checkpoint. O journal é append-only: nada nele é sobrescrito. O checkpoint, sim — uma chamada posterior de runSwarm que compartilha o store pode reescrever o checkpoint.json. Veja o contraste:
06 · ④ Validator Gate — quem constrói não valida
runValidatorGate (coda/src/validator.ts) roda um council independente de validadores mais um painel verificador (verifyPanel) sobre a evidência de cada milestone. A frase que abre o arquivo diz tudo: "Who builds must not validate." O agente que produziu o trabalho nunca é o que assina embaixo. É como um artigo revisado por pares: o autor não é o revisor.
O critério de emissão é estrito: no código, approved é true apenas quando panel.verdict === 'verified' && panel.parkedTier === undefined. Verificado e não estacionado. Qualquer outra coisa — NO_GO, painel rejected, ou um tier que precisa de aprovação — estaciona para revisão humana.
fusion para T4: a primeira coisa que runValidatorGate faz é if (options.t4 && options.fusion) return options.fusion(options) (validator.ts:108–110). Unidades T4 / alto-risco podem ser desviadas para um validador de fusão de alta confiança; quando ausente, o painel verificador trata todos os tiers. O gate é injetado por unit nos casos T4 ou que têm proof (validator.ts:256–257).07 · ⑤ Publish Gate — estaciona quando fechado
Qualquer publicação voltada para fora — o curso visual-teach, um manifesto — deve passar por este gate (coda/src/publish.ts). É o único passo irreversível da pipeline (uma vez público, não volta), então é deliberadamente o que um humano assina. É o botão vermelho com tampa: a tampa é o gate.
t4-parked.jsonl — e você o reabre com alembic approve/propose.Por que o gate humano fica exatamente aqui
A posição do ⑤ Publish não é arbitrária — ela cai na fronteira entre o que se desfaz e o que não se desfaz. Tudo à esquerda dessa linha é reversível e roda sozinho; o ship é o primeiro passo irreversível, e por isso é o único que pede uma pessoa (ADR-0005).
08 · Simulador: passe uma run pelos cinco gates
Agora teste a sua intuição. Clique em cada gate para alternar o veredito dele entre GO e NO_GO. O contador mostra quantos gates a run passou antes de ser barrada, e o diagrama acende até onde ela chegou. Lembre da regra fail-closed: o primeiro NO_GO já para tudo.
Repare: pôr o ② Council em NO_GO para a run no gate 1 (ela nem chega ao Proof) — exatamente o curto-circuito do fanout. E mesmo todos os 4 primeiros em GO, um ⑤ Publish fechado estaciona em vez de publicar.
err e a run falha fechada. Validator e Publish nem rodam.t4-parked.jsonl. Nada foi publicado — e nada se perdeu.09 · O ReviewGate do aprendizado é da mesma família
Aqui a lição fecha o ciclo com as lições 4 e 8. O passo de aprendizado do pacote de fusão é protegido por um ReviewGate — e ele é um membro exato desta família de gates. Os cinco gates acima governam o que uma run pode entregar; o ReviewGate governa o que uma run finalizada pode sedimentar em memória e skills. Mesma filosofia fail-closed, aplicada ao auto-aperfeiçoamento do motor: aprenda só com vitórias validadas.
scoreThresholdGate(0,7) é o baseline conservador; o keystone (ADR-0018) troca-o pelo próprio Validator Gate da coda via createValidatorReviewGate — ou seja, o gate de aprendizado pode literalmente ser o Validator Gate da seção 04. Por isso a matriz de fusão chama o ciclo de aprendizado de CLONE que "compõe com o validator gate" em vez de substituí-lo (ADR-0006 + ADR-0018).10 · Council vs Validator — o comparativo que mais confunde
Os dois rodam um council; por isso é fácil achar que são a mesma coisa. Não são. Um decide se começar; o outro decide se o que foi produzido pode entregar. Veja lado a lado:
Toda a família num mapa só
Council e Validator são dois membros de uma família de seis. Este mapa comparativo posiciona todos eles em duas dimensões: quando agem (pré-voo → durante → ship → pós-run) e como falham fechado (param a run com err, ou estacionam para um humano). Repare no padrão: os dois últimos da linha — Publish e ReviewGate — são os que tocam o irreversível e o auto-aperfeiçoamento, e ambos escalam para humano.
E para fixar a família inteira de uma vez, a tabela abaixo cruza os cinco gates por quando, o que barra e onde no código:
| Gate | Quando | O que barra (fail-closed) | Fonte |
|---|---|---|---|
| ① Scope | rampa de entrada | resume que troca o escopo → err('resume mismatch') | forge/src/scope.ts |
| ② Council | pré-voo (opcional) | NO_GO → missão não começa; fanout não despacha worker | mission/src/council-gate.ts |
| ③ Proof | depois das units | qualquer proof[] não-zero → err, run falha fechada | coda/src/proof.ts |
| ④ Validator | depois das units | verified & não-estacionado é a única emissão; senão estaciona | coda/src/validator.ts |
| ⑤ Publish | no ship | approved=false → t4-parked.jsonl (gate humano, ADR-0005) | coda/src/publish.ts |
| ReviewGate | pós-run (aprendizado) | score < 0,7 (ou Validator NO_GO) → não sedimenta | coda/src/learning-gate.ts |
11 · Caso resolvido: um proof falha numa run real
Junte tudo num caso concreto. Uma unit u1 tem proof: ['pnpm -r typecheck', 'pnpm -w test']; o typecheck quebra. Siga o que cada gate faz — passo a passo — e depois um exercício é seu.
u1 virou 3 tasks: a worker + u1-proof-0 (typecheck) + u1-proof-1 (test), ambas dependsOn:[u1].u1-proof-0 tem status ≠ 'done' → outcome:'failed'. Persiste tudo em units/u1/proof-results.jsonl e retorna err('Proof Gate failed: unit=u1 index=0 …').fusion (validator.ts:108–110); e a emissão só ocorre com verified & parkedTier === undefined — um tier irreversível parka. Se chegar a publicar, o ⑤ Publish ainda exige approved=true (ADR-0005). Lição: proofs verdes são necessários, não suficientes — o lado qualitativo e o gate humano continuam valendo.t4-parked.jsonl para um humano — a ação irreversível é deliberadamente a que exige uma pessoa.12 · Como isso se encaixa
Você dissecou os cinco gates por dentro. Agora dê um passo atrás: onde essa pipeline liga no resto da máquina? Os gates não são um módulo isolado — são a membrana que envolve toda execução. À esquerda deles está tudo que produz uma run (o loop fechado escolhe a unidade, a cintura estreita roteia, o swarm executa); os gates ficam no meio, peneirando; e à direita está o que sai aprovado — o artefato entregue e o combustível que volta para o ciclo de aprendizado. Repare que o ReviewGate do aprendizado é a mesma silhueta fail-closed (seção 09): a pipeline não termina o ciclo, ela o guarda em dois pontos.
Onde você está na metodologia: as Lições 01–03 montaram o motor, a engenharia reversa do Hermes e a matriz de fusão; as Lições 14–19 detalham a cintura estreita, o funil, esta pipeline de gates, o Council e o swarm que executam uma run. Esta Lição 17 é a peça que decide se o que foi executado pode seguir adiante — em cada fronteira (começar, entregar, publicar, sedimentar). Para ver as 30 peças no mesmo mapa e como cada uma liga na seguinte, abra o mapa interativo da metodologia.
- Lição 04 · O loop fechado — o ciclo que vem DEPOIS: ele só roda sobre uma run que passou Proof + Validator, e reusa o próprio Validator Gate desta lição como ReviewGate (ADR-0018) — por isso auto-aplicar foi rejeitado, pra não contornar o gate.
- Lição 08 · reviewAndLearn — o passe gated detalhado: o
scoreThresholdGate(0,7)da seção 09 esmiuçado linha a linha, e como o keystone o troca pelo Validator Gate da coda — a prova de que o ReviewGate é da MESMA família. - Lição 10 · ClarifyGateway (T4) — o mecanismo do gate humano: quando o ④ Validator ou o ⑤ Publish estacionam algo, é a fronteira T4 que segura — o passo irreversível que pede uma pessoa (ADR-0005).
- Lição 18 · Council & Verifier — o board plural por dentro: o ② Council Gate e o painel verificador do ④ Validator são instâncias dele — optimist, pragmatist e contrarian deliberando, nunca um modelo só.
- Lição 14 · A cintura estreita — o que ALIMENTA os gates upstream: os contratos do
@alembic/contractsque roteiam o trabalho até as units cujosproof[]o ③ Proof Gate depois verifica.
13 · Na prática
Chega de diagrama — eis a pipeline como comandos reais. Você não chama cada gate à mão: eles disparam, em ordem, quando você executa uma run com escopo. O comando canônico é o mesmo da Lição 04, só que aqui o foco é assistir os gates dispararem:
# Executa uma run com escopo: os 5 gates disparam em ordem (Scope → Council → Proof → Validator → Publish). # --offline mantem tudo hermetico e $0 (adapter offline deterministico); --yes nao para para confirmar. alembic run --goal GOAL.md --plan alembic.plan.ts --offline --yes # saida tipica (resumida) — cada gate anuncia seu veredito; o 1o NO_GO faz a run falhar fechada: # scope: run-dir materializada em .alembic/runs/<run-id>/ # proof gate: 2/2 ok ← cada unit.proof[] saiu 0 # validator gate: verified ← council independente aprovou a evidencia # run complete: ok # se um proof sair nao-zero, em vez disso: # proof gate: FAILED unit=u1 index=0 → a run para aqui (err), Validator/Publish nem rodam
O ③ Proof Gate não é uma flag — ele nasce do unit.proof[] que você escreve no plano. Cada string vira uma task bash -c que depende da unit (seção 05). É assim que você declara "o que prova esta unidade":
// dentro de alembic.plan.ts — cada string de proof[] vira uma task bash -c que o ③ Proof Gate verifica.
const result = await h.mission({
title: 'mission title',
tier: 'T2',
units: [
{
id: 'u1',
title: 'Implement feature X',
proof: ['pnpm -r typecheck', 'pnpm -w test'], // ← 2 proofs → 2 tasks → o gate exige saida 0 em ambas
},
],
milestones: [{ id: 'm1', title: 'Milestone one' }],
});
E para ligar o passe coordenado do Validador — a peça ADITIVA da seção 06 que roda o veredito multi-lente por tier de risco além do council gate (offline por padrão), sem nunca mudar a decisão nem fazer a run falhar — basta a flag --coordinated:
# Adiciona o passe coordenado do Validador (ADITIVO): grava um veredito sibling por unit, # mas NUNCA muda a decisao do council gate e NUNCA faz a run falhar (offline por padrao). alembic run --goal GOAL.md --plan alembic.plan.ts --coordinated --offline --yes # cada unit ganha um arquivo extra de veredito: # .alembic/runs/<run-id>/units/<unit-id>/coordinated-verdict.json
Fonte da forma exata: apps/cli/src/args.ts (as flags offline, coordinated, yes de runArgsSchema) e a seção "Gate pipeline" do CLAUDE.md — o --coordinated roda runCoordinatedValidatorGate sobre as mesmas units e grava units/<id>/coordinated-verdict.json, ADITIVO ao council gate. [uncertain] as linhas de saída acima são ilustrativas da forma "gate: veredito" (não verbatim de um log) — o que é garantido pelo código é o comportamento: Proof falha fechado em saída não-zero e o coordinated-verdict.json é gravado por unit.
- Clone e entre no repo, depois garanta o build verde:
git clone <repo> alembic && cd alembic·pnpm -r typecheck && pnpm -r build && pnpm -w test - Gere um escopo mínimo (cria
GOAL.md+alembic.plan.ts+ contrato), cujo plano já traz uma unit comproof[]:alembic plan "um passo trivial que so roda um teste" - Rode a run, hermética, e assista os gates:
alembic run --goal GOAL.md --plan alembic.plan.ts --offline --yes - O que procurar na saída: os vereditos em ordem (scope → proof → validator). Se um proof falhar, a run para naquele ponto e o Validator/Publish não rodam — esse é o fail-closed da seção 05.
- O que procurar no dir da run: abra
.alembic/runs/<run-id>/e vejaunits/<unit-id>/proof-results.jsonl(o registro durável de cada proof, seção 05). Se você passou--coordinated, abra tambémunits/<unit-id>/coordinated-verdict.json. - Prove o fail-closed: edite o plano para um
proof[]que falha de propósito (ex.:'bash -c "exit 1"') e rode de novo. O ③ Proof Gate retornaerre a run inteira falha fechada — nada à direita dele roda.
alembic run --goal GOAL.md --plan alembic.plan.ts --yes (adicione --offline para hermético e $0).unit.proof[] no alembic.plan.ts; o ③ Proof Gate o compila numa task bash -c e exige saída 0.--coordinated: grava coordinated-verdict.json por unit, aditivo e não-fatal.alembic run … --coordinated --yes e o coordinated-verdict.json de uma unit veio com um veredito negativo, mas a run terminou verde mesmo assim. Bug ou esperado? Decida antes de revelar.
--coordinated é aditivo: ele só registra um veredito sibling em units/<id>/coordinated-verdict.json e nunca muda a decisão do council gate nem faz a run falhar (CLAUDE.md, "Gate pipeline"). Quem barra de verdade são o ③ Proof e o ④ Validator — o coordenado é observabilidade extra, não um gate fail-closed a mais. Lição: nem toda checagem é um gate: distinga o que decide (fail-closed) do que apenas registra.Fixe os conceitos (flashcards)
Clique pra virar. Tente lembrar a resposta antes de virar — recuperar fixa mais que reler.
checkpoint.json é sobrescrito por uma chamada posterior de runSwarm que compartilha o store; o events.jsonl é append-only e retém todo task-state. O journal é o registro durável.approved === true ⇔ panel.verdict === 'verified' && panel.parkedTier === undefined. Verificado E não-estacionado. Senão: parka p/ humano.scoreThresholdGate(0,7) (inclusivo); o keystone troca pelo próprio Validator Gate da coda. "Aprenda só com vitórias validadas."Revisão cumulativa — recupere de memória
Antes de clicar: responda de cabeça. As opções têm tamanhos parecidos — sem pista pela forma.
proof[] de uma unit sai com código não-zero. O que o Proof Gate faz?metadata.kind === 'proof' deve estar done (saída 0); qualquer falha entra num resumo e o gate retorna err, falhando a run fechada. a contradiz o fail-closed: proofs são a espinha determinística, não degradam para avisos. c inventa um retry que não existe no código — não há repetição. d erra o escopo: o gate avalia todos os proofs e falha se qualquer um falhou; não há "pular e seguir".checkpoint.json sobrescrito por uma chamada posterior de runSwarm, mas o events.jsonl append-only retém todo evento task-state. a e b inventam motivos (criptografia, tamanho) que não aparecem no código. d é falso: o checkpoint existe — o ponto é justamente que ele é sobrescrito, e por isso o gate não confia nele.fanout); o Validator Gate revisa depois a evidência produzida. b é falso: rodam em momentos e sobre objetos diferentes. c inverte a ordem inteira da pipeline. d troca os papéis: é o Council Gate que é opcional (--council); o Validator roda nas units T4/com-proof.