Lição 10 · O cérebro deliberativo · Alembic × Hermes v3 · @alembic/mission
Alembic × Hermes — O Curso de Fusão v3 · Visual Course

Mission — onde o plano vira spec com provas embutidas

Ao fim desta lição você sabe como h.mission() compila unidades em tasks + provas bash -c, por que trabalho T4 estaciona em vez de rodar, como o tier escolhe o modelo mais barato — e por que mudar UMA opção invalida o cache.

Você vai conseguir
  • Compilar de cabeça: unit → worker task + N proof tasks dependentes
  • Explicar o pré-voo do council e o abort fail-closed no NO_GO
  • Operar o ciclo T4: park → approve/reject → propose
  • Prever a chave de cache (SHA-256 de {prompt, opts}) e o triplo check do resume
Leia a versão simples, ou abra a camada técnica em qualquer seção.
1

A grande ideia


O @alembic/mission é o compilador de intenção: dentro de um alembic.plan.ts, h.mission({ units, milestones, tier }) transforma a descrição do trabalho num spec imutável — cada unidade vira uma task de worker, cada string de unit.proof[] vira uma task de comando que SÓ roda depois da unidade, e cada milestone vira um nó que depende das suas unidades. O spec ganha hash de conteúdo e é persistido ANTES de rodar (specs/mission-<n>.json), então o que executou é sempre auditável.

Pense como… uma ordem de serviço industrial: cada item da ordem já nasce com o seu teste de aceitação grampeado — o item sem teste aprovado não conta como entregue. A analogia quebra: aqui até a ORDEM inteira passa por um conselho antes de a fábrica ligar (pré-voo da lição 08).

2

h.mission() — a compilação em uma imagem


O hook valida com missionOptionsSchema, compila com compileMissionToRunSpec, prefixa os ids (mission-<n>), persiste o spec e entrega ao runSwarm (packages/vm/src/hooks.ts:222-246).

h.mission({...}) units: [{id:'u1', tier:'T2', proof:['pnpm -w test']}] milestones: [{id:'m1'}] compila task worker "u1" prompt = título + descrição + provas task "u1-proof-0" command: ['bash','-c','pnpm -w test'] dependsOn: ['u1'] · kind:'proof' milestone "m1" dependsOn: ids das unidades AlembicRunSpec (imutável) + tierFloor · budgetUsd? · specHash persistido: specs/mission-<n>.json só então: runSwarm(spec) proof falha ⇒ exit ≠ 0 ⇒ Proof Gate (lição 09) fecha o run · provas persistem em units/<id>/proof-results.jsonl compileMissionToRunSpec: cada unit.proof[i] → task `${unit.id}-proof-${i}` (mission/src/compiler.ts:140-153)
A prova nasce grampeada na unidade: task própria, dependente, com exit code que decide o destino do run.
Camada técnica — comandos desta seção
# rodar uma missão a partir de um plano (pré-voo + provas + gates)
alembic run --goal GOAL.md --plan alembic.plan.ts --yes
cat <runDir>/specs/mission-1.json | head -40          # o spec imutável persistido

# o hook e o compilador
sed -n '222,246p' packages/vm/src/hooks.ts
sed -n '100,155p' packages/mission/src/compiler.ts
3

Council pré-voo — NO_GO aborta antes do 1º worker


A lição 08 mostrou o mecanismo; aqui está o encaixe: runCouncilGate delibera sobre o goal ANTES de qualquer dispatch — "NO_GO is fail-closed: the mission aborts before any worker is dispatched" (packages/mission/src/council-gate.ts:15-21). As duas constraints fixas do pack são exatamente os temas desta lição: T4/irreversível parkeia, e o plano tem que ser determinístico e replayável.

4

T4 park — o ledger do irreversível


Durante a execução, classifyPark decide o que NÃO pode rodar sozinho (T4, marcador legal/security) e o orquestrador estaciona essas tasks no ledger t4-parked.jsonl, journalizando cada uma (packages/swarm/src/orchestrator.ts:245-260 + park.ts). O humano então fecha o ciclo pela CLI — e o registro é sempre um APPEND num ledger, nunca um estado mutado:

alembic-completo · 0004-camada-l3-swarm · s2
comandoo que gravaregra fail-closed
alembic approve <run> --task-id <id>1 linha em approvals.jsonla task PRECISA existir em t4-parked.jsonl (commands.ts:2122-2149)
alembic reject <run> --task-id <id>1 linha em rejections.jsonlmesma existência exigida (commands.ts:2164-2190)
alembic propose <run>um run-proposta novoreabre as parkeadas lendo o ledger (commands.ts:1952-1968)
Você roda alembic approve r1 --task-id u9, mas u9 nunca foi parkeada. O que acontece?
Aprovar o que nunca foi parkeado seria inventar história: o comando exige a task em t4-parked.jsonl e falha fechado sem ela (commands.ts:2122-2123). O mesmo vale para reject. Ledgers só crescem com fatos.
5

Tier routing — a escada e o mais barato


A escada de autonomia vem de @alembic/contracts: T0 (silencioso) → T1 (log leve) → T2 (um revisor notificado) → T3 (council) → T4 (PARK), mais o marcador ortogonal LOCAL para trabalho preso a modelos locais/$0 (contracts/src/tier.ts:4-37). Na compilação, cada task resolve seu modelo assim: um modelSettings.modelId explícito PINA; senão, pickCheapestForTier(tier) escolhe a entrada mais barata do registry pela soma costPer1kInputUsd + costPer1kOutputUsd (contracts/src/registry.ts:244-257; o override em mission/src/compiler.ts:modelIdForTier).

alembic-completo · 0002-camada-l1-adapters · s2
GOTCHA de produção — empate de custo é ordem de declaração

O reduce usa < estrito: num EMPATE de custo, vence quem foi declarado PRIMEIRO no registry. Foi exatamente o prerequisito da primeira rota de learnings (PR #157): no empate T2 de 0.0005, o gemini-3.5-flash declarado antes segurou a rota que o deepseek-v4-pro (AccessDenied na conta) teria quebrado. Ordem de declaração É comportamento — trate o registry como código, não como lista.

6

Cache & resume — determinismo que sobrevive a quedas


Duas memórias de run distintas. O cache evita repagar trabalho igual: a chave é SHA-256(stableStringify({prompt, opts})) — chaves do objeto ORDENADAS antes do hash — e mora em <runDir>/workflows/<wf-id>/cache.json (packages/vm/src/cache.ts:18-24); h.agent() e h.swarm() leem antes e gravam depois, a menos de --no-cache (vm/src/hooks.ts:106-172, 254-290). O resume retoma um run existente com um triplo check contra o meta.json original: goal igual, plano igual, contrato igual — qualquer divergência é err "resume mismatch" (packages/forge/src/scope.ts:105-113).

h.agent(prompt, opts) tier, modelId, skills… computeCacheKey sha256(stableStringify({prompt, opts})) chaves ordenadas ⇒ hash estável HIT: devolve o resultado salvo — $0, sem modelo (pulado com --no-cache) MISS: roda e grava workflows/<wf>/cache.json (writeCache após o sucesso) --resume <run-id> — triplo check contra meta.json (forge/src/scope.ts:105-113) goal ≠ original ⇒ err · planPath ≠ original ⇒ err · contrato ≠ original ⇒ err ("resume mismatch") iguais ⇒ reusa <dataDir>/runs/<run-id> e o cache — retomar nunca reexecuta o que já provou
Mesmo prompt + mesmas opts = mesma chave = hit. Qualquer opt diferente muda o hash — e paga de novo.
Você muda só o tier de um h.agent() já cacheado. O que acontece com a chave de cache?
computeCacheKey(prompt, opts) hasheia o objeto INTEIRO com chaves ordenadas (cache.ts:18-21) — tier dentro de opts ⇒ chave nova ⇒ miss honesto. --no-cache não altera a chave; só pula leitura/escrita (hooks.ts:113-114).
Camada técnica — comandos desta seção
# retomar um run validando goal/plan/contrato contra meta.json
alembic run --goal GOAL.md --plan alembic.plan.ts --resume <run-id> --yes
alembic run --goal GOAL.md --plan alembic.plan.ts --resume <run-id> --no-cache --yes  # força reexecução

# inspecionar o cache de um workflow
cat <runDir>/workflows/wf-1/cache.json | head -20
sed -n '18,24p' packages/vm/src/cache.ts
7

Experimente


Último retrieval do módulo deliberativo.

compilação
O que cada string de unit.proof[] vira?
clique para virar
Uma task própria `${unit.id}-proof-${i}` com command ['bash','-c',prova] e dependsOn [unit.id] (compiler.ts:140-153). Exit ≠ 0 fecha o run.
escada
Recite a escada T0→T4 e o marcador extra.
clique para virar
T0 silencioso · T1 log leve · T2 um revisor · T3 council · T4 PARK — e LOCAL, ortogonal, para trabalho preso a modelos locais/$0 (tier.ts:4-37).
empate
Empate de custo no registry: quem vence?
clique para virar
O PRIMEIRO declarado — o reduce usa < estrito (registry.ts:252-256). Foi o que segurou a rota de learnings no empate T2 de 0.0005 (PR #157).
resume
Quais 3 coisas o resume compara com meta.json?
clique para virar
goal, planPath e o contrato de validação — qualquer diferença é err "resume mismatch" (scope.ts:105-113). Retomar não pode reescrever a história.
Pergunta para levar: por que o spec é persistido ANTES do runSwarm, e não depois? Próxima lição: o Forge — os 7 passos que transformam um prompt vago em GOAL.md + contrato + plano executável.