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

Council — evidência empacotada, decisão fail-closed

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.

Você vai conseguir
  • Distinguir o ContextPack "flat" (decidir) do LayeredContextPack L0–L7 (contextualizar)
  • Explicar por que o pack é reproduzível a partir do hash (manifest sha-256)
  • Ligar o RAG offline ao L3 com --retrieve e reordenar com MMR
  • Descrever o quorum fail-closed (MIN_VALID_AGENTS) e o papel do contrarian
Leia a versão simples, ou abra a camada técnica em qualquer seção.
1

A grande ideia


Decisã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.

Por baixo do capô

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.

2

As 8 camadas em uma imagem


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):

L0 · brief (1 linha) L1 · repoMap[] L2 · selectedFiles (íntegra) L3 · relevantSnippets (+ retrieval opt-in) L4 · summaries L5 · debateState (fases anteriores) L6 · artifacts + constraints L7 · manifest content-hashed (sha256 por arquivo + id ctxm_) custo em tokens cresce ↓ sob pressão de orçamento, corte camadas de baixo valor — o hash preserva a reprodutibilidade tokenBudget = { estimatedTokens, maxTokens, withinBudget } por papel/modelo (tokenBudgetSchema, 91-96)
L0 estreito no topo, L7 completo na base — a ordem em que um loop longo quer perder contexto.
3

No código — o lacre de conteúdo


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.

packages/council/src/layered-context-pack.ts:169-190 (buildContextManifest)
// 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.

Camada técnica — comandos desta seção
# 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
4

Retrieval opt-in — o RAG entra pelo L3


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.

harness · 0006-contexto-memoria-rag · s1
--retrieve "query" --family OBRIGATÓRIO searchChunkIndex cosseno desc, desempate por chunk_id · offline $0 --rerank mmr mmrRerank(hits, λ) λ=1 pura relevância λ=0 pura diversidade L3 relevantSnippets sem --retrieve ⇒ L3 vazio, pack BYTE-IDÊNTICO · --retrieve sem --family ⇒ fail-closed (commands.ts:5697-5702) --resolve-text lê o texto real do chunks.jsonl do pacote; sem ele, uma linha-referência determinística
Uma porta, três knobs: --retrieve-top-k (default 10), --rerank mmr, --lambda em [0,1] (default 0.5).
Você roda alembic context-pack a.ts --retrieve "proof gate" e esquece o --family. O que acontece?
Fail-closed explícito: "context-pack --retrieve requires --family" (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.
5

O gate GO/NO_GO — como o pack vira decisão


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.

alembic-completo · 0003-camada-l2-council · s2
A propriedade load-bearing: quorum fail-closed

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.

Camada técnica — comandos desta seção
# 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
Um board válido devolveu só 2 votos (um membro falhou no meio). Qual é o veredito do consenso?
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.
6

Experimente


Duas cartas e um desafio de bancada.

dois packs
Flat vs layered: qual alimenta o runDebate?
clique para virar
O flat (question/framing/signals) — parseContextPack. O layered L0–L7 é o companheiro ADITIVO para contexto sob orçamento; não substitui o flat.
mmr
O que λ=0.5 significa no mmrRerank?
clique para virar
Meio-termo entre relevância pura (λ=1) e diversidade pura (λ=0) — o default. Puro e determinístico (packages/embeddings/src/rerank.ts:107).
Desafio: gere o MESMO pack duas vezes e compare os ids — depois mude UM caractere num arquivo e veja o ctxm_ trocar. Próxima lição: o tribunal de saída — Proof, Validator, Coordinated, Publish e o learning-gate do @alembic/coda.