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.
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).
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).
# 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
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.
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:
| comando | o que grava | regra fail-closed |
|---|---|---|
alembic approve <run> --task-id <id> | 1 linha em approvals.jsonl | a task PRECISA existir em t4-parked.jsonl (commands.ts:2122-2149) |
alembic reject <run> --task-id <id> | 1 linha em rejections.jsonl | mesma existência exigida (commands.ts:2164-2190) |
alembic propose <run> | um run-proposta novo | reabre as parkeadas lendo o ledger (commands.ts:1952-1968) |
alembic approve r1 --task-id u9, mas u9 nunca foi parkeada. O que acontece?t4-parked.jsonl e falha fechado sem ela (commands.ts:2122-2123). O mesmo vale para reject. Ledgers só crescem com fatos.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).
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.
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).
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).# 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
Último retrieval do módulo deliberativo.