A trilha de ADRs: as decisões que moldaram a fusão
O código diz o que o motor faz; os Architecture Decision Records dizem por quê — e por que as alternativas foram rejeitadas. Cinco ADRs formam a espinha de tudo que você aprendeu, e o ponto desta lição é que eles se restringem mutuamente: lidos juntos, eles deixaram a forma de @alembic/hermes quase forçada — não havia caminho mais fácil que respeitasse os cinco ao mesmo tempo.
Esta lição destila esses cinco arquivos do repo (todos com status Accepted). Os números, datas e identificadores são citados verbatim do docs/adr/* (rodapé). Por que importa pra missão: é a constituição que diz, antes de qualquer linha de código, o que a fusão podia e o que não podia fazer.
- Explicar o que é um ADR e por que ele é append-only (decisões substituem, nunca editam).
- Recitar a decisão central de cada um dos cinco ADRs (0001, 0009, 0006, 0005, 0018) e o que cada um proíbe.
- Mostrar como os cinco se encaixam num grafo de restrições que estreita o espaço de design.
- Provar por que a ADR-0018 teve que ser um ADAPT, e não um CLONE literal do Hermes.
01 · O que é, de fato, um ADR
Imagine o repo como uma cidade e cada decisão arquitetural como uma lei. Um ADR (Architecture Decision Record) é o texto dessa lei: um registro datado e imutável de uma decisão — o contexto, a escolha feita, as opções rejeitadas e as consequências. O repo os mantém em docs/adr/; dezoito estão aceitos.
Anatomia de um ADR · as quatro partes
Todo ADR do repo tem a mesma forma. A parte que mais surpreende quem chega é a terceira — registrar o que foi rejeitado é o que torna a decisão executável (seção 08):
Append-only: a linha do tempo, não o "estado atual"
A diferença entre editar e substituir não é cosmética. Editar apaga a memória da decisão; substituir a preserva. Veja as duas culturas lado a lado:
02 · A trilha num relance
Antes de abrir cada ADR, veja os cinco juntos. Cada linha tem a decisão e, principalmente, o que ela proíbe à fusão — é essa coluna que vira o grafo de restrições da seção 08:
| ADR | A decisão | Como restringe a fusão |
|---|---|---|
| 0001 | Alembic é um motor interno, não um SKU para cliente | Sem UI/billing/onboarding no motor — o @alembic/hermes entrega bibliotecas, não superfícies de produto |
| 0009 | Uma chamada de modelo nunca lança; retorna um resultado discriminado | Todo subsistema hermes retorna ModelRunResult, injeta suas portas e é agnóstico de store |
| 0006 | Profundidade frontier em tudo, via o modelo mais barato acima de um piso; o Validator é o portão de emissão | O loop de aprendizado deve passar um piso de qualidade antes de sedimentar — não pode auto-gravar |
| 0005 | O portão humano fica no ship / gasto irreversível | O ClarifyGateway (Lição 10) é a superfície T4; aprender + distillar rodam sem humano (HITL-free) |
| 0018 | Internalizar o loop do Hermes como uma passagem propose→dispose com gate de Validator | A pedra angular: o revisor propõe, o Validator dispõe, nunca auto-aplica |
As cinco restrições, aninhadas
Não são cinco regras soltas: elas se encaixam como caixas dentro de caixas. A 0001 é a fronteira mais externa; lá dentro, a 0009 define o contrato; dentro dela, os portões (0006 e 0005); e no centro, espremida por todas, a 0018:
2026-06-02; a ADR-0018 em 2026-06-23. Isso importa: a 0018 chegou num espaço de design já cercado pelas outras quatro — por isso ela não pôde inventar uma forma nova, só preencher o vão que sobrava. Decisões antigas restringem as novas, e a trilha registra essa ordem.A trilha como linha do tempo (datada e append-only)
A ordem não é decoração: cada ADR carrega a data em que foi aceito, e é por isso que a 0018 herda as restrições das quatro anteriores. Veja as duas levas na linha do tempo — a primeira leva em 2026-06-02 ergueu as paredes; a 0018, ~21 dias depois, só pôde construir no espaço que sobrou:
03 · ADR-0001 — motor, não produto
"Alembic é o motor/plataforma interno da Appfy, não um SKU para cliente hoje." A consequência é nítida: preocupações voltadas ao cliente — uma API pública para venda, billing, onboarding/marketing de tenant — "vivem em produtos … não no motor". É por isso que a fusão portou ferramentas como bibliotecas (um MemoryStore, uma função webSearch) e nunca um app de agente executável com UI.
Como decisão, ela funciona como um filtro de forma: diante de qualquer capacidade do Hermes, a pergunta vira "isto é uma biblioteca consumível por um produto, ou uma superfície de cliente?". A primeira entra no motor; a segunda é rejeitada para o produto que consome o motor:
04 · ADR-0009 — a cintura estreita (o contrato)
Você conheceu isto como a cintura estreita da Lição 14. Como decisão, ela diz: toda chamada de modelo passa por um único contrato — run(input): Promise<ModelRunResult>, onde ModelRunResult é uma união discriminada, e "run() nunca lança: falhas são valores, não exceções". A própria ADR a chama de "a invariante mais importante do motor".
// ADR-0009 — o contrato, verbatim na forma da união discriminada type ModelRunResult = | { ok: true, /* … */ } | { ok: false, error: { code, message, retryable }, /* … */ }; function run(input): Promise<ModelRunResult> // nunca lança: falha é valor
A opção rejeitada é nomeada — "clientes convencionais que lançam (exceções em erro / 429 / timeout)" — porque eles "espalham try/catch pela orquestração, tornam ad hoc o tratamento de falha parcial, e acoplam chamadores a formatos de erro específicos de provedor". Compare as duas culturas de erro:
Como decisão, a 0009 instala um losango na fronteira de toda chamada de modelo: o resultado é uma exceção lançada (rejeitado) ou um valor tipado (a cintura)? Só o segundo caminho atravessa a cintura estreita — o primeiro é eliminado por contrato:
A lista de consequências é a parte que sustenta a fusão: "retry, circuit-breaking, medição de orçamento e quórum de council operam todos sobre resultados tipados uniformes". Essa uniformidade é exatamente por que todo subsistema hermes pôde adotar a mesma forma de resultado sem casos especiais — e a ADR ainda registra que essa invariante é "load-bearing" para o comportamento fail-closed (ADR-0011) e para o replay (ADR-0008).
05 · ADR-0006 — o Validator é o portão de emissão
A manchete deste ADR é sobre custo — profundidade frontier no corpus inteiro via o modelo mais barato acima de um piso de profundidade — mas sua cláusula sustentadora para a fusão é o princípio de emissão: nada sedimenta sem passar um piso de qualidade. Essa é a regra que o loop de aprendizado teve que obedecer.
scoreThresholdGate(0.7) é a codificação conservadora desse piso até o Validator real do @alembic/coda se ligar. "Auto-gravar" sem esse gate é exatamente o modo de falha que a 0006 existe para impedir.Como decisão, a 0006 instala um losango obrigatório entre "candidato" e "memória". Sem ele, um aprendizado não validado endurece na memória durável:
06 · ADR-0005 — o portão humano no gasto irreversível
Onde um humano precisa estar no loop? A ADR-0005 dá um critério único e contra-intuitivo. O pipeline da Venture Factory — discover → validate → design → plan → build → ship — roda autônomo até o build, porque "código numa branch é reversível". O critério decisivo é "reversibilidade/externalidade, não posição no pipeline — o portão fica onde um erro autônomo deixa de ser 'apagar uma branch' e vira dinheiro ou reputação".
discover → build são HITL-free" — é o "no human-in-the-loop" do WIKI_LLM e a doutrina de "<5% de intervenção humana" da holding. Errar aqui é só apagar uma branch.ship e gasto real (deploy público, marketing pago) defaultam para T4-park (fail-closed); um GO humano os libera. DEFAULT_TIER = T4: na dúvida, pára e espera o humano.A decisão é melhor lida como um fluxograma sobre o pipeline: cada estágio passa pelo mesmo losango — "este passo é reversível?". Enquanto a resposta for sim, segue autônomo; no primeiro "não", entra o portão humano T4. Siga as setas:
É o princípio que o ClarifyGateway implementa: uma pergunta estruturada e bloqueante é a superfície humana T4, usada só onde a reversibilidade se esgota. As duas opções rejeitadas confirmam o critério — "Gate antes do build" foi rejeitada (reintroduz humano onde nada toca o mundo) e "Totalmente autônomo até o ship" também (expõe dinheiro e reputação a um erro não supervisionado).
07 · ADR-0018 — a pedra angular, e por que é ADAPT, não CLONE
A mais nova dos cinco (aceita em 2026-06-23) é a que autoriza todo o subsistema de aprendizado. O Hermes tinha a peça que faltava — depois de um turno, ele forka um revisor "memory/skill-only" que pergunta "algo deve ser salvo ou atualizado?" e grava direto nos stores duráveis (agent/background_review.py). O Alembic internaliza esse loop como @alembic/hermes/learning, mas "remodelado às invariantes do motor": o revisor propõe, e o Validator do Alembic dispõe — gravações são "com gate de Validator, nunca auto-aplicadas".
A ADR declara, palavra por palavra, por que isso é um ADAPT e não um CLONE literal — por duas razões:
- "O Alembic não tem um
AIAgentPython para fazer fork como thread daemon" — então o mecanismo vira uma passagem sobre portas injetadas, não uma thread em segundo plano. - "Mais importante — auto-gravar na memória durável violaria o princípio do validator-como-portão-de-emissão (ADR-0006: nada sedimenta sem passar um piso de qualidade)." Então o revisor propõe e o Validator dispõe.
Leia esses dois pontos e você vê a trilha funcionando como sistema: a 0018 não pode clonar o Hermes porque a 0006 proíbe emissão sem gate e a 0009 proíbe lançar — então a única forma que sobra é o propose→gate→apply sobre portas injetadas. Aqui está a forma que a ADR fixa:
// ADR-0018 — o kernel, dependendo de portas injetadas APENAS (ADR-0009) reviewAndLearn(summary, { proposer, gate, memory }) // gate default conservador: "aprenda só de vitórias validadas" const gate = scoreThresholdGate(0.7); // score >= 0.7 aprova // o Validator real do @alembic/coda entra depois, fornecendo SEU ReviewGate // — sem mudar reviewAndLearn (opt-in por injeção)
A decisão nomeia as quatro garantias: (1) portas injetadas apenas — nenhum adapter ou store concreto construído dentro; (2) gravações com gate de Validator, não auto-aplicadas; (3) default conservador scoreThresholdGate(0.7) com um gate mais rico (o Validator do coda) opt-in por injeção; (4) a passagem é fail-closed, com a saída do proposer Zod-validada na fronteira porque em produção é saída de modelo não-confiável.
ADAPT vs CLONE, lado a lado
A diferença entre copiar o Hermes ao pé da letra (CLONE) e remodelá-lo às invariantes (ADAPT) é o resumo de toda a lição:
| Aspecto | CLONE literal do Hermes (rejeitado) | ADAPT do Alembic (a decisão) |
|---|---|---|
| Mecanismo | thread daemon / processo forkado (background_review.py) | passagem síncrona pós-unit sobre portas injetadas |
| Gravação | auto-aplica direto na memória durável | revisor propõe; o Validator dispõe (gated) |
| Viola? | sim — fura o portão de emissão (ADR-0006) | não — compõe com a pipeline de gates |
| Dedup | mecanismo novo dentro do loop | reusa o dedup do MemoryStore (reforça, não duplica) |
A ADR-0018 também registra o que não faz: "Ainda não conectado ao @alembic/coda nem ao harness — este ADR entrega só as portas e o kernel." Conectar o Validator do coda como o ReviewGate e invocar reviewAndLearn como passe pós-unit é um follow-up que consome essa API sem mudá-la. A restrição (portas injetadas) virou a extensibilidade.
A 0018 compõe — não substitui — o funil e a pipeline
A consequência que a ADR registra textualmente é que o loop "compõe com o funil de destilação e a pipeline de gates em vez de substituir qualquer um dos dois". Dois fluxos alimentam a mesma memória durável, e o resultado é que a próxima run começa mais inteligente:
08 · A trilha é um grafo de restrições, não uma lista de desejos
Aqui fecha a ideia central. Cada ADR remove opções. A 0001 remove "construir um produto". A 0009 remove "lançar no erro". A 0006 remove "auto-aplicar". A 0005 remove tanto "dar gate em tudo" quanto "dar gate em nada". Quando a 0018 é escrita, o espaço de design encolheu tanto que a implementação é quase forçada.
Clique em cada ADR no grafo abaixo para acender as restrições que ele impõe sobre a 0018 — repare como as setas convergem todas para o mesmo ponto:
É o valor de escrever decisões: um agente futuro não pode "simplificar" reintroduzindo uma opção rejeitada, porque a rejeição é versionada e datada. Recordar isso fecha o objetivo desta lição.
Outra forma de ver o mesmo fato: pense no espaço de design como um funil. No topo, muitas formas plausíveis para o loop de aprendizado; cada ADR é um estreitamento que descarta um conjunto delas; no fim do funil sobra uma. Quando a 0018 foi escrita, o funil já estava quase fechado:
09 · Rastreie uma decisão pela trilha (passo a passo → agora você)
Você viu o grafo. Agora rastreie uma escolha de design da fusão pela trilha, devagar — depois um exercício é seu. Recuperar o procedimento (não só ver a conclusão) é o que fixa.
background_review.py forka um revisor que grava direto nos stores duráveis. Tentação: clonar igual.ModelRunResult, nunca lançar. Isso elimina "thread que estoura exceção". Remove "lançar no erro".ReviewProposer), um gate dispõe (scoreThresholdGate(0.7)), e só o aprovado aplica no MemoryStore. É exatamente a ADR-0018.ship/gasto), não no aprendizado. Dica: o procedimento é sempre o mesmo — passe a escolha por cada ADR e veja quais opções cada um remove.10 · A trilha em duas camadas (Simples / Técnico)
A mesma ideia, em dois níveis. Comece pelo "Simples"; quando estiver firme, abra o "Técnico" para a forma precisa:
run(): Promise<ModelRunResult>, união discriminada, nunca lança; adapter/store-agnóstico). ADR-0006 restringe a emissão (nada sedimenta sem piso de qualidade). ADR-0005 restringe o gate humano (só no irreversível; DEFAULT_TIER = T4). A interseção dessas quatro é exatamente reviewAndLearn(summary, { proposer, gate, memory }) com scoreThresholdGate(0.7), fail-closed e Zod na fronteira — a ADR-0018.11 · Confusões comuns
Duas leituras erradas aparecem sempre. A primeira: tratar ADR como "estado atual" editável. A segunda: ler a ADR-0006 só pela manchete (custo) e perder a cláusula em que a fusão de fato se apoia.
Vale ver a 0006 nas suas duas camadas lado a lado — o que está na manchete vs. o que carrega a fusão:
12 · Como isso se encaixa
Esta lição não é sobre uma peça do motor — é sobre a camada que moldou todas elas. Os ADRs são a entrada anterior a qualquer código: são as restrições que decidiram a forma da cintura estreita, do gate de Validator, do loop de aprendizado e do portão humano. Por isso, no mapa da metodologia, a trilha de ADRs fica acima da pipeline, escrevendo as regras que cada estágio depois obedece.
O fluxo abaixo mostra isso: a trilha de ADRs (esta peça, destacada) é a fonte; cada ADR desce e esculpe uma peça concreta da pipeline contracts → adapters → council → harness → gates → loop de aprendizado. Clique nos passos para acender cada restrição na peça que ela moldou:
Note a direção: ao contrário das outras lições — que pegam uma peça e mostram o que entra e o que sai dela — aqui a peça é a própria camada de decisão, e o que "sai" dela é a forma de todas as outras. É a entrada da entrada.
Onde você está na metodologia
No mapa interativo da metodologia, a trilha de ADRs é a constituição — a camada de governança que fica antes do Scope Gate e que toda a pipeline (Scope → Council → Proof → Validator → Publish) herda como restrição. Quando você lê o motor de baixo para cima, os ADRs são o teto; quando você o constrói de cima para baixo, são o alicerce. Veja o conjunto inteiro e como cada peça se conecta no mapa da metodologia.
- Lição 04 · O loop fechado auto-evolutivo — é a ADR-0018 em código: o
reviewAndLearnpropõe→gate→aplica que esta trilha prova ser a única forma possível. - Lição 14 · A cintura estreita — é a ADR-0009: o contrato
run(): Promise<ModelRunResult>que a trilha lista como a restrição "− lançar no erro". - Lição 17 · A pipeline de gates — é a ADR-0006 operando: o portão de emissão que a trilha mostra ser o que torna o aprendizado gated, não automático.
- Lição 26 · Proveniência & segurança — é a ADR-0011 (fail-closed, Zod na fronteira) — a mesma disciplina "decisão rejeitada fica versionada" que faz a trilha vincular, não só informar.
13 · Na prática
A trilha não é um conceito abstrato: são arquivos de texto no repo que você pode ler agora. O melhor jeito de internalizar esta lição é abrir docs/adr/ e percorrer a trilha com seus próprios olhos — o objetivo da lição é exatamente rastrear um ADR até o código que o honra.
Comece listando a trilha inteira e lendo um ADR de ponta a ponta:
# 1 · liste a trilha inteira (os 18 ADRs aceitos) ls docs/adr/ # → 0001-alembic-internal-engine-not-product.md # 0005-human-gate-at-ship.md 0006-frontier-quality-...md # 0009-narrow-waist-run-never-throws.md … 0018-internalize-...md # 2 · leia a pedra angular de ponta a ponta (contexto, decisão, # OPÇÕES REJEITADAS, consequências — a caixa 3 é o segredo) cat docs/adr/0018-internalize-validator-gated-self-improvement-loop.md
Os comandos acima são ls/cat simples — a trilha é só texto versionado, então não há comando alembic dedicado a "ler um ADR". O que existe no CLI alembic é o que materializa uma decisão de escopo num diretório de run, que é o passo seguinte natural a uma decisão registrada.
Adicionar um ADR (o protocolo append-only)
Quando uma decisão nova surge, você não edita um ADR antigo — escreve um novo com o próximo número e sua própria data. O CLAUDE.md do repo aponta docs/adr/ como o registro canônico de decisões de domínio. O padrão dos arquivos existentes é o contrato:
# o próximo número é o maior + 1 (hoje o maior aceito é 0018) # crie docs/adr/0019-<slug-curto>.md seguindo a anatomia das 4 partes: # # ADR-0019: <título imperativo> # **Status:** Accepted (AAAA-MM-DD) ← a SUA data, append-only # <contexto> / ## Decision / ## Considered options / ## Consequences # se a decisão REVISA outra, ela "supersedes" a antiga — nunca apaga.
[uncertain] não há um subcomando alembic adr … no CLI canônico para gerar o arquivo — a trilha é mantida à mão (criar/editar o .md). Se você precisar de um comando que consome uma decisão já registrada, use o fluxo de escopo abaixo.
cd /Users/acf/Documents/Projects/appfy/alembic e então ls docs/adr/. Você verá os 18 arquivos numerados — confira que o maior é 0018-….cat docs/adr/0009-narrow-waist-run-never-throws.md. Procure a seção ## Considered options — é a caixa 3 (rejeitadas) que a seção 01 desta lição destacou.run() retorna um ModelRunResult. Rode grep -rn "ModelRunResult" packages/contracts/src e veja a união discriminada { ok: true } | { ok: false, error } declarada — a decisão virou tipo.pnpm -r typecheck && pnpm -r build && pnpm -w test. Se o motor tivesse um throw atravessando a cintura, os tipos ou os testes reclamariam: a restrição da ADR-0009 é executável, não só documental.grep -rn "scoreThresholdGate" packages/hermes/src e veja o default 0.7 que a trilha citou. Cada decisão deve apontar para uma linha de código que a obedece — se não apontar, é só uma intenção, não uma restrição.O que procurar na saída: em (1) o arquivo 0018-… presente; em (3) a definição do tipo (não só usos); em (4) exit code 0 — verde é a prova de que a decisão escrita continua honrada pelo código. [uncertain] os caminhos exatos dos arquivos-fonte podem variar conforme o pacote evolui; o grep é o jeito robusto de localizar a linha viva.
Fixe os conceitos (flashcards)
Clique pra virar. Tente lembrar a resposta antes de virar — recuperação ativa fixa mais que reler.
run(input): Promise<ModelRunResult> — união discriminada; nunca lança (falha é valor). A invariante mais importante do motor.scoreThresholdGate(0.7). Proposta ≠ verdade; um gate decide.ship/gasto → T4-park; distill + discover→build são HITL-free.AIAgent Python p/ fork; e — mais importante — auto-gravar violaria a 0006. Logo: revisor propõe, Validator dispõe.Revisão cumulativa — recupere de memória
Antes de clicar: responda de cabeça. As quatro opções têm o mesmo tamanho de propósito — sem pista pela forma.
AIAgent Python para fork; e o princípio do portão de emissão) e marca a segunda como "mais importante". a erra o motivo: a ADR não diz que TS não cria threads — diz que "uma thread é a unidade errada" e que não existe runtime AIAgent. b é falso: a fusão leu o background_review.py como fonte de record. d não aparece na ADR — velocidade nunca é o argumento; o argumento é a invariante (0006).ModelAdapter moldada pela cintura estreita", então erros são valores e a passagem inteira pode falhar fechado (err) sem try/catch espalhado. b contradiz a 0006 (nada auto-aplica). c confunde a 0009 (contrato) com a 0006 (custo/modelo barato). d contradiz a 0001 (sem superfície de cliente no motor).0.7 e não outro limiar?", "O que muda quando o Validator do coda entra como ReviewGate?", "Qual ADR eu citaria para justificar não ter UI no motor?". É só dizer.