Ao fim desta lição você sabe como o @alembic/council transforma contexto bruto em um pacote estratificado de 8 camadas com hash de conteúdo — e como um board com contrarian decide GO/NO_GO sem nunca green-lightar no vácuo.
--retrieve e reordenar com MMRDecisão boa começa antes do debate: começa no empacotamento da evidência. O council tem dois pacotes para dois trabalhos. O ContextPack clássico é o bundle chato e flat de decisão — question / framing / signals / constraints — que o runDebate consome (packages/council/src/context-pack.ts:parseContextPack). O LayeredContextPack é o companheiro ADITIVO para loops longos: 8 camadas estratificadas, do brief de uma linha (L0) ao manifest content-hashed de TODOS os arquivos-fonte (L7), com orçamento de tokens explícito por papel/modelo (layered-context-pack.ts:5-20).
Pense como… preparar um processo para um júri: a capa de uma linha, o sumário, as peças-chave na íntegra, os trechos grifados, os resumos, o histórico das sessões anteriores, os anexos e — no fim — o inventário lacrado com o lacre numerado. Sob pressão de tempo, você corta camadas de baixo valor SEM perder o lacre. A analogia quebra: aqui o "lacre" (hash) muda sozinho se um único byte de qualquer peça mudar.
Determinismo declarado no cabeçalho: nada lê clock nem RNG — createdAt é INJETADO pelo chamador, e os ids do pack/manifest são hashes SHA-256 de conteúdo via node:crypto; todo builder devolve Result (fail-closed) em vez de lançar (layered-context-pack.ts:16-19). O orçamento resolve por precedência perModel > perRole > default (selectTokenBudget, linhas 148-157) e a estimativa é o heurístico barato Math.ceil(JSON.length/4), no mínimo 1 (estimateTokens, 141-142) — o MESMO usado pelo composer de memória da lição 06.
Cada camada tem um schema próprio dentro de layeredContextLayersSchema (layered-context-pack.ts:75-88). Ler de cima (mais barato) para baixo (mais completo):
O manifest L7 é o que torna o pack auditável: cada arquivo entra com sha256 + sizeBytes + lineCount, ordenado por path para o id ser independente de ordem.
// trecho real, não editado const records: SourceFileRecord[] = parsed.data .map((file) => ({ path: file.path, sha256: sha256Hex(file.content), sizeBytes: Buffer.byteLength(file.content, 'utf8'), lineCount: lineCount(file.content), })) .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)); const idPayload = JSON.stringify( records.map((r) => ({ path: r.path, sha256: r.sha256 })), ); const id = `ctxm_${sha256Hex(idPayload).slice(0, HASH_PREFIX_LEN)}`;
Qualquer byte alterado em qualquer arquivo — ou um arquivo a mais/a menos — muda o ctxm_…. É detecção de mudança de graça, sem timestamps.
# gerar um pack de 8 camadas a partir de arquivos reais (offline, $0, determinístico) alembic context-pack CLAUDE.md README.md --brief "estado do engine" --role analyst --model local-default alembic context-pack packages/council/src/*.ts --brief "revisar o council" --budget 12000 # ver os schemas das camadas e o manifest sed -n '75,107p' packages/council/src/layered-context-pack.ts sed -n '169,190p' packages/council/src/layered-context-pack.ts
O caminho de LEITURA do RAG offline (lição do embed-index) pluga no pack por UMA porta: --retrieve "<query>". A CLI consulta o índice offline da família via searchChunkIndex (cosseno, determinístico, $0), opcionalmente reordena com mmrRerank e injeta os hits como L3.relevantSnippets (apps/cli/src/commands.ts:5693-5737). Sem --retrieve, L3 fica vazio e o pack é byte-idêntico ao anterior — aditivo de verdade.
alembic context-pack a.ts --retrieve "proof gate" e esquece o --family. O que acontece?commands.ts:5697-5702). O engine não adivinha índice — adivinharia errado em silêncio. Sem --retrieve, aí sim L3 fica vazio por design e o pack é byte-idêntico.Antes de uma missão rodar autônoma, um board pequeno delibera sobre o goal: runCouncilGate monta um ContextPack flat com a pergunta ("Should Alembic execute this mission autonomously?"), o framing e duas constraints fixas — trabalho T4/irreversível é parkeado, e o plano deve ser determinístico/replayável — e roda runDebate (packages/mission/src/council-gate.ts:79-111). O board default tem 3 membros: optimist e pragmatist na fase inicial, e o contrarian SOZINHO na última fase, para atacar risco e ambiguidade depois de ver o caso construído.
Um board com menos de MIN_VALID_AGENTS = 3 votos válidos devolve NO_GO independentemente dos scores (packages/council/src/consensus.ts:20-28,99-117). Um board esparso ou degradado NUNCA green-lighta. Só com quorum o consenso deriva o veredito (GO / PIVOT / NO_GO) do agregado ponderado por weight × trust.
É o mesmo desenho que a lição 09 reusa duas vezes: o Validator Gate (board independente + painel verificador) e o proposer do learning-gate. Empacotar → deliberar → decidir fail-closed é O padrão de decisão do engine, não um caso especial.
# ver o board default e o gate sed -n '24,58p' packages/mission/src/council-gate.ts # DEFAULT_MISSION_BOARD_YAML (contrarian por último) sed -n '79,111p' packages/mission/src/council-gate.ts # runCouncilGate: pack flat -> runDebate -> decision sed -n '20,28p' packages/council/src/consensus.ts # MIN_VALID_AGENTS = 3, fail-closed # o gate em ação (pré-flight de missão; offline adapter no board default) alembic run --goal GOAL.md --plan alembic.plan.ts --yes
consensus.ts:20-28: com menos de 3 votos válidos o resultado é NO_GO ANTES de qualquer score ser consultado — a propriedade de segurança load-bearing. Não há retry de board nem default de PIVOT.Duas cartas e um desafio de bancada.