O Council é o motor de decisão do Alembic. Ele junta um debate qualitativo entre membros (o maker) com uma verificação determinística sobre a evidência (o checker). É o que permite o sistema afirmar “decisão verificada, com dissent preservado” — não apenas “o LLM achou que era uma boa ideia”. Nesta lição você vai seguir uma decisão atravessando debate.ts → verifier.ts → verifier-panel.ts até o gate de emissão T3.
Result, tier e ModelAdapter sem reapresentá-los.loopCount > MAX_LOOPS (parked em T4).Imagine uma decisão cara: “vale a pena emitir este resultado para um tier pago?”. Você não quer que um único modelo responda “sim” e pronto. O Council resolve isso em dois tempos.
Primeiro, vários especialistas debatem — cada um dá uma nota por eixo e uma justificativa. Esse é o lado criativo, o maker. Depois, um auditor que não pode opinar confere se a evidência sustenta a conclusão: tem voto suficiente? a confiança passa do piso? há diversidade? Esse é o checker.
O pacote @alembic/council (em packages/council/src/) implementa: debate.ts (o maker), verifier.ts + verifier-panel.ts (o checker), consensus.ts (quorum + spread), scoring.ts (parse e agregação de notas por eixo) e board.ts (o board loader que ordena os membros e força o contrarian por último).
O ponto central de design: o maker chama modelos e é, por natureza, probabilístico; o checker é uma função pura sobre evidência estruturada (votos, scores, spread, quorum) e nunca re-chama o modelo. É essa assimetria que transforma “o LLM concordou” em “a decisão foi verificada”.
Os quatro estágios do Council, da esquerda para a direita. Só o primeiro chama modelos; os três seguintes são código.
Numa decisão cara (T3), qual estágio é o verdadeiro “portão de qualidade”: o debate dos modelos ou a verificação determinística?
A balança do Council: o maker produz a decisão; o checker, que não pode re-chamar o modelo, só pesa a evidência estruturada que o maker deixou.
A diferença mais importante do Council inteiro cabe em uma frase: quem decide não é quem aprova.
O maker (debate) gera votos, notas e justificativas. O checker (verifier + painel) recebe só o resultado pronto — um DebateResult mais um ContextPack — e aplica oráculos: funções puras que olham a evidência e devolvem “provado” ou “não provado”. O checker não tem acesso ao modelo, então não pode “ser convencido” pelo texto bonito do maker.
Probabilístico. Chama o modelo. Produz evidência.
Determinístico. Não chama o modelo. Só pesa a evidência.
O verifier aceita apenas DebateResult + ContextPack. Não recebe um ModelAdapter, então literalmente não tem como re-chamar um modelo. A fronteira não é uma promessa em comentário — é imposta pelo tipo.
// verifier.ts:19 /** Maker-checker Verifier. READ-ONLY BY ARCHITECTURE. * Aceita só DebateResult + ContextPack. Não pode re-chamar modelo. */ // verifier.ts:84 export type ClaimOracle = (evidence: VerifierEvidence) => { proven: boolean; evidence: string };
Um ClaimOracle é uma função pura de VerifierEvidence para { proven, evidence }. É isso que permite ao sistema escrever um VerificationReport auditável, em vez de devolver “o modelo disse que sim”.
O debate acontece em fases (phases). As fases rodam uma depois da outra (seriais), mas dentro de cada fase os membros falam ao mesmo tempo (paralelos). Por que essa mistura?
Porque há um membro especial — o contrarian — que precisa ver tudo o que já foi dito antes de dar a sua nota. O board loader força esse membro a ser o último. Assim o contrarian sempre recebe as contribuições acumuladas das fases anteriores. Isso é sequenciamento real no código — não uma instrução no prompt pedindo “por favor, discorde”.
Promise.all.priorPhases para que o contrarian veja tudo que já foi dito.Result e nunca lança. Se vier !result.ok, o membro vira um failure tratado, não uma exceção.{ memberId, axes, rationale } que vai virar evidência para o checker.Result isola a falha do membro; a fase segue com os demais votos.)// debate.ts:31 /** The DebateEngine. Phases serial, members within phase IN PARALLEL. * Contrarian is forced last by board loader — real sequencing, * not prompt claim. */ // debate.ts:130 const result = await boundMember.adapter.run(input); // nunca lança if (!result.ok) { return failure… } const scores = parseAxisScores(result.text); const vote = buildVote({ memberId, axes: scores.value, rationale: result.text });
debate.ts:162 — runPhase usa Promise.all para despachar os membros de uma fase em paralelo. As fases entre si permanecem seriais, então a ordenação do board (com o contrarian por último) é respeitada.
O fato de o adapter sempre devolver Result (e nunca lançar) é o que deixa o debate fail-closed sem precisar de try/catch espalhado: um membro que falha é evidência de falha, não um crash.
Cada membro não devolve um “sim/não” — devolve um voto estruturado. É exatamente essa estrutura que o checker vai pesar depois.
rationale ajuda humanos a entender; os axes (e o spread entre eles) são o que os oráculos medem. parseAxisScores é tolerante: texto malformado não derruba a fase.O verifier é o auditor. Ele recebe a evidência que o debate produziu e aplica oráculos: pequenas funções que respondem perguntas objetivas sobre essa evidência. “Quantos agentes válidos votaram?” “O spread entre as notas é apertado o suficiente?” “Atingiu o quorum?”. Nenhuma dessas perguntas olha o texto do maker — só os números estruturados.
E o verifier sabe parar. Se a verificação entra em loop e ultrapassa o limite de tentativas, a decisão não é forçada: ela é estacionada (parked) em T4 — ou seja, mandada para um humano. Travar com elegância é melhor que carimbar uma decisão fraca.
MAX_LOOPS = 3. Acima disso, a decisão é estacionada em T4 em vez de ser forçada.// verifier.ts:19 /** Maker-checker Verifier. READ-ONLY BY ARCHITECTURE. */ // verifier.ts:49 export const MAX_LOOPS = 3; // loopCount > 3 → parkedTier = T4 (humano) // verifier.ts:84 export type ClaimOracle = (evidence: VerifierEvidence) => { proven: boolean; evidence: string };
Os oráculos operam sobre VerifierEvidence — uma estrutura derivada de consensus.ts (que conhece MIN_VALID_AGENTS e o spread das notas). Cada oráculo devolve não só proven mas também uma string evidence, que entra no VerificationReport e torna a decisão auditável linha a linha.
verifier.ts:22–37 — o comentário de arquitetura explica a separação maker/checker e por que o verifier é read-only. Vale ler na fonte: é o melhor resumo da filosofia do pacote.
Um ClaimOracle entra com evidência e sai com um booleano mais uma string que explica o porquê. Essa string é o que torna a decisão auditável.
evidence registra a razão, não só o veredito.O painel olha a mesma evidência por três ângulos diferentes. Verificar por três lentes só compensa porque cada lente enxerga uma coisa distinta.
Um único verificador, repetido, não traz informação nova. Por isso o painel usa três lentes diferentes — cada uma faz perguntas distintas sobre a mesma evidência:
verifyDecision do verifier).Para emitir, o painel precisa de um quorum: por padrão, 2 das 3 lentes precisam verificar. Mas há uma trava acima do quorum: o veto de preserve-dissent. Se qualquer lente tem uma falha dura (hard) — por exemplo, um GO sobre zero evidência — o painel inteiro é rejeitado, mesmo que as outras duas lentes aprovem.
Marque quais lentes passam e se há um hard failure. Observe como o veto pode vencer o quorum.
// verifier-panel.ts:40 export const FAITHFULNESS_CONFIDENCE_FLOOR = 0.5; export const FAITHFULNESS_STRENGTH_FLOOR = 3; /** Número padrão de lentes que precisam verificar (de três). */ export const DEFAULT_PANEL_QUORUM = 2;
Cada lente é um VerifierLens: um conjunto de claims mais a lista de quais são hard (hardClaimIds). Em lensVerdict, se qualquer claim hard falha, a lente é rejected; caso contrário, ela só verifica se todos os seus claims passam.
A lente Faithfulness tem hardClaimIds: ['evidence-present'] — então “GO sobre zero evidência” é um hard failure por construção. É essa modelagem que dá força ao veto de preserve-dissent: o quorum agrega, mas um hard failure rejeita o painel inteiro.
verifier-panel.ts:31–33 (comentário): “a HARD failure in ANY lens … rejects the whole panel; otherwise emission needs a quorum of lenses to verify.” Esse é, textualmente, o gate de emissão T3.
evidence-present é um hardClaimId. Um hard claim que falha é rejected imediato.O contrarian é estruturalmente o último a falar, e o relatório guarda a discordância em vez de apagá-la.
Muitos sistemas de “consenso” escondem a discordância: pegam a média e seguem em frente. O Council faz o oposto — ele preserva o dissent de propósito, por dois mecanismos:
1. O contrarian é forçado por último. O board loader (em board.ts) coloca o membro contrarian no fim da última fase, então ele sempre vê tudo que foi dito e pode discordar com base completa.
2. O verifier e o painel reportam o dissent. Eles foram desenhados para aceitar e registrar a discordância explicitamente, em vez de forçar um consenso artificial. É por isso que o painel tem um preserve-dissent veto: a discordância não é ruído a ser suavizado — é sinal.
O Council consegue afirmar “decisão com dissent preservado e grounded evidence” — em vez de “o LLM achou que era uma boa ideia” — porque separa o maker (debate) do checker (verifier + painel) e baseia o checker em oráculos determinísticos com um veto que protege a discordância.
board.ts — o board loader ordena os membros e força o contrarian por último; isso é sequenciamento de execução, não uma instrução de prompt.
consensus.ts — define MIN_VALID_AGENTS e calcula o spread das notas; é a base de evidência sobre a qual os oráculos do verifier operam.
verifier-panel.ts — a agregação por quorum com o preserve-dissent veto: a discordância é reportada no VerificationReport, não eliminada.
Clique em cada estágio para ver o que ele faz, onde ele vive e a camada que ele acende.
Promise.all roda os membros juntos.DebateResult + ContextPack — nunca um ModelAdapter. Sem modelo, não há como re-chamar; a fronteira é imposta pelo tipo.DEFAULT_PANEL_QUORUM = 2 de 3 lentes. Mas um hard failure em qualquer lente rejeita tudo, vencendo o quorum.loopCount > MAX_LOOPS?MAX_LOOPS = 3. Acima disso, parkedTier = T4: a decisão sobe para um humano em vez de ser forçada.O comentário de arquitetura do maker-checker. São poucas linhas e contêm a filosofia inteira do pacote: por que o checker é read-only e por que isso permite afirmar “verificado”.
Rode os testes do pacote e olhe três arquivos de teste em foco. Eles são a prova viva de cada afirmação desta lição.
pnpm test packages/council # Foco: # - debate.test.ts → phases seriais, membros paralelos, contrarian por último # - verifier*.test.ts → oráculos determinísticos + parked T4 # - verifier-panel.test.ts → 3 lentes + quorum + dissent veto
debate.ts:31 (comentário sobre o contrarian sequencing).verifier.ts:19–37 (a arquitetura maker-checker).VerificationReport completo.loopCount > MAX_LOOPS (a decisão vira parked T4).| Arquivo:linha | Conceito |
|---|---|
council/src/debate.ts:31 | DebateEngine — phases seriais, membros paralelos, contrarian por último |
council/src/debate.ts:130 | adapter.run por membro (sempre Result, nunca lança) |
council/src/verifier.ts:19 | Maker-checker Verifier (read-only por arquitetura) |
council/src/verifier.ts:49 | MAX_LOOPS = 3 → parked T4 |
council/src/verifier.ts:84 | ClaimOracle — função pura sobre evidência |
council/src/verifier-panel.ts:40 | FAITHFULNESS floors (0.5 / 3) + DEFAULT_PANEL_QUORUM = 2 |
council/src/consensus.ts | MIN_VALID_AGENTS + spread |
council/src/board.ts | Board loader + contrarian por último |
pnpm test packages/council é a prova viva — não um “deveria funcionar”.loopCount > 3 → parked T4, em vez de forçar uma decisão fraca.DebateResult + ContextPack; não recebe ModelAdapter, então não pode re-chamar o modelo (a). Ele é o gate real, não opcional (c).