English · Português
House Party Protocol — by Rushar Labs

Manual do harness · v2.11.4 · Claude Code e Codex CLI

Da spec à prova.
Sem salto de confiança.

Um guia operacional do harness: como o hpp init instala, o que cada mapa projeta, como o loop avança, o que a política bloqueia e o que conta como prova. Toda afirmação abaixo carrega o comando que a reproduz. As saídas citadas foram medidas ao longo de várias versões, não contra uma só: as primeiras em 2026-09-23 contra a 2.5.8, a partir de um clone deste repositório na tag v2.5.8 e de uma instalação por pip da mesma versão (os blocos do hpp init dizem qual), e as seguintes até a 2.10.1. Uma linha medida numa versão ou árvore específica a nomeia — Medido na árvore-fonte da 2.10.1, Medido num checkout de main — e uma linha que não nomeia nenhuma está num bloco que carrega o comando para medi-la de novo na sua versão. O que está marcado como novo na 2.6.0 teve as saídas medidas num checkout de main antes dessa versão.

01 · modelo

Harness primeiro; módulos depois.

O produto tem camadas, e as camadas não são intercambiáveis. O harness é a superfície que você opera; o protocolo é o que ele impõe; os módulos são o que ele instala; a distribuição é como cada host os recebe.

Harness

python -m hpp — doctor, init, log de eventos, atestação, mapas, WorkGraph, política, roteamento, contexto, eval e benchmark — e, novos na 2.6.0, registros de decisão, pacotes de evidência, uma régua de recuperação e uma checagem de citações — e, novos na 2.7.0, registros de deliberação da House Session — e, novos na 2.8.0, cobertura de trabalho e achados de revisão — e, novos na 2.9.0, comandos que agem sobre um veredito selado — e, novos na 2.10.0, assinatura SSH opcional, log de eventos encadeado por hash, arquivo de política do projeto e doctor --report — e, nova na 2.11.0, saída Mermaid para os mapas e o WorkGraph. Um pacote Python sem dependência de runtime; nada roda em segundo plano.

Protocolo

hpp.manifest.json — protocolo 2.1: quatro invariantes, três papéis (maker, checker, human-gate), cinco transições do loop com um gate nomeado cada, exit codes, cobertura por host de cada módulo, monitores declarados e bundles.

Módulos

Diretórios versionados, cada um instalável por conta própria. A cobertura por host é declarada como native, explicit-command ou unsupported; nenhum adaptador finge o contrário.

Claude Code

Canal nativo de plugin (/plugin marketplace add e /plugin install) e lifecycle hooks depois que você cola o wiring. O .claude/settings.local.json — hooks e statusLine — continua sendo gate humano.

Codex CLI

Canal de plugin próprio para os módulos que carregam skills (codex plugin marketplace add e codex plugin add; só skills), mais uma cópia verificada por módulo pelo instalador de módulos: skills em .agents/skills, runtime em .agents/hpp/<módulo>, AGENTS.md lido pelo host. O Codex tem lifecycle hooks próprios, mas os do HPP são escritos para o Claude Code e não são carregados lá; as mesmas capacidades são comandos explícitos.

As quatro invariantes

  • Um resultado verificado tem evidência registrada e um gate humano explícito.
  • Um checker é somente-leitura em relação ao workspace do maker.
  • Um loop só avança por um evento registrado.
  • Uma aprovação é inválida quando a spec, o commit ou o snapshot do repositório a que está atada mudam.

Fonte: hpp.manifest.json, chave invariants (o texto original está em inglês).

02 · começar

Diagnostique antes de instalar.

python -m hpp doctor
python -m hpp init --target ../seu-repo
python -m hpp init --target ../seu-repo --apply
python -m hpp benchmark -k 3

O hpp doctor valida o manifesto. Neste repositório ele imprime HPP doctor: ok · modules=10 · hosts=claude-code, codex · hooks=18 (permission gates=9 · llm egress=0) e, porque o marketplace.json está ao lado do manifesto, também cruza cada caminho de módulo, versão e manifesto de plugin; esse resultado só aparece com --json, onde distribution lê {"checked": true, "codex_marketplace": {"checked": true, "plugins": 7}, "modules": 10, "status": "ok"} — os sete são os módulos do canal de plugin do Codex. A partir de uma instalação por pip a linha única é a mesma e o campo lê {"checked": false, "status": "source-contract"} — o comando diz o que não pôde verificar em vez de reportar uma checagem que não rodou.

O hpp doctor --report (novo na 2.10.0) imprime a mesma linha e depois o link do formulário de feedback com o título preenchido, o link da página de suporte e um objeto hpp.doctor-report/v1 para colar no formulário: as versões do hpp e do protocolo, o Python, a plataforma, a contagem de módulos, os hosts, as contagens de hooks por capacidade e o status da distribuição — nenhum caminho da sua máquina. Ele não faz chamada de rede; nada é enviado até você mesmo abrir o link. Com --json imprime um objeto com feedback_url, report e support_url. Medido na árvore-fonte da 2.10.1: o título lê [feedback] hpp 2.10.1 on Windows AMD64, exit 0.

hpp install --bundle reliable-coding --host codex --target ../seu-repo imprime um recibo com "mode": "plan-only" e não copia nada. A cópia é feita pelo instalador de módulos, que faz parte deste repositório em installers/kit-forge-1.5.4/kit_doctor.py, ao lado dos diretórios de módulo a partir dos quais ele instala (uma instalação por pip não carrega nem um nem outro): python installers/kit-forge-1.5.4/kit_doctor.py install --kit <caminho-do-módulo> --host <host> --target ../seu-repo imprime um plano, e o mesmo comando com --apply o executa. O hpp init imprime essas linhas para você no bloco de wiring.

Requisitos: Python 3.10 ou mais novo (pyproject.toml), git no PATH para a atestação e para a contagem de commits do detect; nenhum pacote de terceiro.

03 · init

Seis estágios fixos. Um plano antes de qualquer escrita.

O hpp init cumpre os seis estágios do contrato de instalação. Sem --apply ele imprime o plano e não escreve nada; com --apply escreve exatamente um arquivo, .hpp/profile.json, dentro do target. O wiring do host nunca é escrito — é um bloco que você cola.

EstágioO que medeO que pode pará-lo
detectClassifica o target como greenfield, in-progress ou re-run a partir de .claude/settings*.json (hooks ou statusLine já configurados), AGENTS.md, .agents/, .hpp/events.jsonl, um .hpp/profile.json anterior e a contagem de commits do git. Lista o que já existe e é preservado.Log de eventos ou profile corrompido é reportado como aviso, com o arquivo a inspecionar.
prereqsPython em 3.10 ou acima; o contrato do manifesto; a distribuição quando há marketplace.json ao lado do manifesto; git no PATH.Python abaixo do piso ou divergência manifesto/marketplace interrompe a execução (exit 2). git ausente é aviso, com a dica de instalação para a sua plataforma.
profileQuatro respostas — host, bundle, modo de política e um conselheiro de decisão opcional (novo na 2.6.0; padrão off, nunca contado como default pendente) — vindas de flags, de um arquivo JSON via --profile, do prompt (só em TTY) ou do default, com a origem de cada uma registrada. O plano reporta would-write; --apply escreve; as mesmas respostas de novo são no-op.Um profile gravado com respostas diferentes é conflict: nada é sobrescrito, as chaves divergentes são nomeadas.
configureO plano de módulos para o host escolhido, com native ou explicit-command por módulo, e a verificação do CHECKSUMS.txt de cada diretório de módulo presente na árvore.Módulo sem suporte no host, ou checksum divergente, interrompe a execução (exit 2).
wire-suggestO bloco a colar para o host. Para o Claude Code: as linhas do canal de plugin e depois as do instalador de módulos para os seus módulos explicit-command. Para o Codex CLI: primeiro as linhas do canal de plugin do Codex (codex plugin marketplace add e um codex plugin add por módulo que carrega skills; só skills) e depois uma linha do instalador de módulos por módulo, a cópia verificada que instala runtime, scripts e templates. Por fim, o comando de política como configurado. Antes do bloco ele imprime uma tabela HOOK CAPABILITIES: cada hook que os módulos escolhidos declaram, com seus eventos, sua política de saída (observe, warn ou block) e seus grupos de capacidade. Reporta 0 files written e o número de hooks que declaram capacidades.Nada; ele nunca escreve.
smokeQuatro controles: o classificador de política (rm -rf tem de dar BLOCK, pytest tem de dar ALLOW), um grafo de capacidades não vazio, o log de eventos projetando no loop, e o benchmark com k=1.Um controle reprovado dá exit 1 e o plano diz qual; --no-benchmark reporta esse controle como não verificado em vez de pulá-lo em silêncio.

Prontidão é contada, não estimada

Onze itens, cada um verified, not verified ou failed, cada um com o comando que o reproduz. O que não foi medido aparece como não verificado — nunca como zero e nunca como cem. Esta é a sequência de abertura e a linha de prontidão medidas num target vazio a partir de um clone deste repositório, que carrega os diretórios de módulo, seus CHECKSUMS.txt e o marketplace.json:

> detecting host...           ✓ greenfield · 0 existing item(s) preserved
> checking prerequisites...   ✓ python 3.14.3 · protocol 2.1
> mounting profile...         ✓ would-write · host=claude-code · bundle=reliable-coding · policy=audit · 3 default(s)
> loading modules...          ✓ 6 modules · reliable-coding · claude-code · 6/6 checksums verified
> wiring suggestions...       ✓ 7 commands to paste · 0 files written · 17 hooks declaring capabilities
> verifying evidence...       ✓ policy · graph · events · benchmark
> protocol online.

  READINESS  every line is a check that ran; the command below it reproduces it
  ████████████████░░░░  9/11 verified · 2 not verified · 0 failed

Os dois não verificados são os dois que só uma ação posterior prova: o profile (só plano; --apply o escreve) e o wiring do host (uma colagem que você mesmo faz). Comando: python -m hpp init --target <dir-vazio> --non-interactive --no-animation; o diretório-alvo tinha 0 arquivos depois.

A partir de uma instalação por pip da mesma versão, que carrega o harness, o manifesto e a suíte de benchmark, mas nenhum diretório de módulo e nenhum marketplace.json, o mesmo comando no mesmo target mostra:

> detecting host...           ✓ greenfield · 0 existing item(s) preserved
> checking prerequisites...   ✓ python 3.14.3 · protocol 2.1
> mounting profile...         ✓ would-write · host=claude-code · bundle=reliable-coding · policy=audit · 3 default(s)
> loading modules...          ✓ 6 modules · reliable-coding · claude-code
> wiring suggestions...       ✓ 7 commands to paste · 0 files written · 17 hooks declaring capabilities
> verifying evidence...       ✓ policy · graph · events · benchmark
> protocol online.

  READINESS  every line is a check that ran; the command below it reproduces it
  █████████████░░░░░░░  7/11 verified · 4 not verified · 0 failed

Os dois itens a mais não verificados ali — integridade da distribuição e checksums dos módulos — não têm contra o que ser medidos num wheel, então são reportados como não verificados, nunca como aprovados.

Flags

  • --host, --bundle, --policy-mode audit|enforce — respondem as três perguntas obrigatórias; --modules a,b substitui o bundle por uma lista explícita.
  • --decision-advisor off|typesafe|openrouter|compatible — a quarta pergunta, opcional (padrão off; novo na 2.6.0): registra um conselheiro de decisão tipada que você declara e imprime como integrá-lo. O hpp nunca o chama e não guarda chave.
  • --profile answers.json — as mesmas respostas a partir de um arquivo (chaves host, bundle, policy_mode, modules e, nova na 2.6.0, decision_advisor; chave desconhecida é erro de uso).
  • --yes, --non-interactive, --json, ou CI definido no ambiente — nenhum prompt é alcançado; pergunta sem resposta assume o default e o relatório diz isso.
  • --no-animation — saída plana; NO_COLOR é respeitado; sem TTY a saída sai completa e sem cor.
  • --marketplace — o slug usado no bloco de wiring do Claude Code, para forks.

Exit codes do init: 0 ok · 1 aviso ou falha não bloqueante (git ausente, um controle do smoke reprovou) · 2 interrompido num estágio bloqueante · 3 a própria invocação estava errada (target não é diretório, arquivo de --profile inválido). Uma execução interrompida nunca imprime a linha de fechamento da marca.

04 · mapas

O estado tem mais de uma vista.

Mapas não têm estado próprio. Cada um é uma projeção de uma ou duas entradas, ordenada para que a mesma entrada produza a mesma saída byte a byte. O Monitor Map não sonda nada e o Lane Map não conhece as suas sessões: você fornece last_signal e heartbeats, e --now é explícito para que nenhuma projeção dependa do relógio ambiente.

MapaEntradaPergunta que respondeComando
Capabilitymanifestoqual módulo provê qual capacidade, em qual host, em qual bundlegraph --view capability
Operationalloop do manifestoqual evento move qual estado por qual gategraph --view operational
Agentpapéis e módulos; eventos opcionaisquem pode fazer, checar e aprovar; o que aconteceu, em ordemmap agent · graph --view agent
Evidencefixacomo critério, registro e veredito se relacionamgraph --view evidence
Codecomponentes do manifestoquais superfícies cada módulo declara (não é uma AST)graph --view code
LaneJSON de lanes, --now, limiaresquem é dono do quê, quem está vivo, onde reivindicações vivas se sobrepõemmap lane
ContextJSON de contexto, --budgeto que entrou no contexto compilado, o que foi omitido, com hashesmap context · context compile
MonitorJSON de monitores, --now, --skew-toleranceo que é observado, quão fresco, e qual gate consomemap monitor
WorkJSON da specquais unidades podem rodar juntas e em que ordemwork plan · work waves · work coverage

Cada um imprime JSON por padrão. Em graph, nas quatro visões de map, em work plan e em work waves, --format mermaid imprime no lugar um flowchart Mermaid dos mesmos nós e arestas; work coverage e context compile continuam em JSON. No flowchart os nós são numerados na ordem dos ids e as arestas saem ordenadas, e um ", |, < ou > num rótulo é escrito como #quot;, #124;, #lt; ou #gt;. Os bytes não dependem do sistema: esses comandos escrevem UTF-8 com fim de linha \n, no Windows também, então a mesma entrada dá o mesmo hash em todo lugar.

Monitor Map: quatro estados, e um relógio que pode mentir

EstadoRegra
healthynow − last_signal ≤ freshness
staleo sinal é mais velho que a frescura declarada
skewlast_signal > now + skew_tolerance — timestamp no futuro não é evidência de frescura; tolerância default 5 s, ajustável com --skew-tolerance
unknownnenhum last_signal fornecido
python -m hpp map monitor examples/reliable-coding/monitors.json --now 1000
python -m hpp map monitor examples/reliable-coding/monitors.json --now 1000 --format mermaid

O monitor map não tem grafo próprio; no Mermaid cada monitor é um nó rotulado <id> · <status>, com uma aresta observes para o seu target e uma aresta gates para o seu gate consumidor.

Medido: service-health (sinal 950, frescura 120) → healthy; data-freshness (sinal 700, frescura 120) → stale. Com um arquivo sintético, um sinal em 1010 contra --now 1000 projetou como skew, e como healthy quando se passou --skew-tolerance 20; um monitor sem last_signal projetou como unknown.

Lane Map: vitalidade a partir de heartbeats

Uma lane está alive quando o heartbeat tem no máximo --suspect-after segundos (default 300), suspect até --dead-after (default 900), e dead além disso ou quando declarada fechada. Lane sem heartbeat é unknown. Uma lane morta nunca produz colisão; duas lanes vivas e exclusivas com territórios sobrepostos, sim. Comando: python -m hpp map lane examples/reliable-coding/lanes.json --now 1000. No JSON uma colisão é um registro do campo collisions; com --format mermaid ela é desenhada como uma aresta collides:<caminhos> entre as duas lanes.

Competições: escolher uma de N é pass@N

Novo na 2.6.0

O quadro do lane-kit (lane_board.py) roda um best-of-N: N lanes constroem, cada uma, o seu próprio item para uma mesma tarefa, o compete os declara candidatos e o select registra um vencedor. Quem seleciona tem de estar em outra lane e ser de outra família de modelo que todos os builders dos candidatos, e cada candidato tem de estar CHECKPOINT-READY com evidência, ou VERIFIED. Os perdedores viram NOT-SELECTED, um estado terminal; nenhum candidato chega a MERGED antes de a tarefa ter um vencedor; select --checker-unavailable registra DEFERRED, nunca um vencedor. Escolher uma de N é pass@N, não confiabilidade: o vencedor ainda precisa do seu próprio VERIFIED antes de MERGED, e de pass^k antes que alguém o chame de confiável.

python multi-session/lane-kit-1.8.2/scripts/lane_board.py compete --task TASK-1 --items ITEM-A,ITEM-B --lane lane-a --model claude-opus-5-5
python multi-session/lane-kit-1.8.2/scripts/lane_board.py select --task TASK-1 --winner ITEM-B --lane lane-r --model gpt-5.6-sol --reason "same tests, half the diff"

Medido em 2026-09-27 na fonte do lane-kit 1.6.0, com ids de modelo atuais, num quadro de rascunho onde ITEM-A (lane lane-a, claude-opus-5-5) e ITEM-B (lane lane-b, claude-sonnet-5) foram cada um reivindicado, construído e posto em CHECKPOINT-READY com evidência: compete → exit 0; select por claude-haiku-4-5-20251001 → maker≠checker violated: … SAME model family (claude), exit 1; select por gpt-5.6-sol → vencedor ITEM-B, not_selected ITEM-A, exit 0. Depois disso o ITEM-A não pôde se mover (NOT-SELECTED -> UNDER-REVIEW recusado) e o ITEM-B não pôde ir de CHECKPOINT-READY para MERGED, exit 1 cada. O quadro sai com 0 ok · 1 recusado · 2 uso inválido.

Context compile: blocos inteiros dentro de um orçamento

hpp context compile INPUTS --budget N lê um array JSON de blocos — source (único), priority (um inteiro) e content — e preenche um orçamento de caracteres em ordem de prioridade. Um bloco entra inteiro ou não entra: o que não cabe é listado como omitido, nunca fatiado, e o bloco seguinte ainda é tentado. O separador entre dois blocos (duas quebras de linha) é cobrado do orçamento. Todo bloco, incluído ou omitido, carrega seus chars e seu sha256; o resultado carrega used, remaining e o text compilado. Conteúdo com cara de segredo — atribuição de chave, token, segredo ou senha, cabeçalho PEM, token sk- — é recusado antes de qualquer compilação, e a recusa nomeia a fonte.

python -m hpp context compile examples/reliable-coding/context.json --budget 100

Medido: spec e acceptance incluídos, background omitido, used 86, remaining 14. Um bloco cujo conteúdo era uma atribuição api_key= → hpp: secret-like material refused from source: env, exit 2.

05 · lanes

As lanes se coordenam por arquivos, nunca por uma sessão viva.

Sessões em paralelo — Claude Code ou Codex CLI — trabalham cada uma como uma lane: worktree própria, território declarado e heartbeat. A lane-kit dá a elas quatro arquivos compartilhados e nenhum canal de mensagens próprio. Um arquivo sobrevive à sessão que o escreveu, e os dois hosts conseguem lê-lo.

PeçaArquivoO que garanteQuem escreve
Registro.claude/lanes/registry.jsonquem está viva, com papel, modelo e worktree; uma lane morta é removida no registro seguinte, e o trabalho não commitado dela vira antes um patch de resgatelane_register.py no SessionStart, --heartbeat no PostToolUse
Quadro.claude/lanes/board.jsonl (só acrescenta)quem pegou qual item, cada veredito, cada competiçãolane_board.py
Mailbox.claude/lanes/mailbox/*.mdo contexto que o quadro não carrega — uma mudança de escopo no meio do trabalhoqualquer lane; lida pela lane que ela nomeia
Efeitos.claude/lanes/effects.jsonse um veredito foi só decidido, ou também entregue à lane que precisa agirlane_effects.py, a partir do quadro

O mailbox: escreve uma vez, lê, arquiva

Uma mensagem é um arquivo Markdown em .claude/lanes/mailbox/. A linha de roteamento nomeia a lane de destino — ## To: <lane_id> (a forma em português ## Para:, das versões anteriores, continua valendo). A lane de destino fica sabendo pelo contexto da sessão quando se registra e, se a mensagem chega com ela já rodando, no heartbeat seguinte — uma vez por mensagem, não a cada chamada de ferramenta. Ela lê o arquivo e o move para mailbox/_read/; uma mensagem nunca é editada no lugar. templates/REORIENT-MAILBOX.template.md traz os campos que uma reorientação precisa: de quem, quando, itens afetados, o que mudou, por quê, o que o destino deve fazer, evidência.

cp multi-session/lane-kit-1.8.2/templates/REORIENT-MAILBOX.template.md .claude/lanes/mailbox/2026-09-27-escopo.md
# preencha os placeholders e mantenha "## To: exec-b" exatamente assim
# exec-b vê: 1 unread mailbox message(s): .claude/lanes/mailbox/2026-09-27-escopo.md — read them and archive in mailbox/_read/

Decidido não é entregue

Quando o quadro registra um veredito — VERIFIED, NEEDS-FIX, DEFERRED, NOT-SELECTED ou WITHDRAWN —, lane_effects.py reserva a entrega com um reservation_id derivado de item, decisão, destino, efeito e rodada: uma nova tentativa reusa a reserva, uma rodada nova ganha a sua. Em seguida o próprio quadro entrega: escreve o veredito no mailbox da lane que construiu o item — roteado com ## To:, com o motivo, a evidência e a rodada — e marca a reserva como entregue, com o caminho da mensagem como evidência. Uma lane do Codex CLI também recebe um toque com codex queue; o Claude Code não tem comando que alcance uma sessão em andamento, então uma lane do Claude Code fica sabendo da mensagem no heartbeat seguinte. A entrega vem ligada por padrão. Com mailbox.notify_on_verdict: false no lanes.yaml o quadro não escreve nada: o veredito fica pendente — como também fica quando o mailbox não pode ser escrito — e lane_board.py render o lista em UNDELIVERED até que quem avisa a lane feche a entrega com lane_effects.py deliver.

python multi-session/lane-kit-1.8.2/scripts/lane_effects.py pending
python multi-session/lane-kit-1.8.2/scripts/lane_effects.py deliver <reservation_id> --evidence .claude/lanes/mailbox/_read/2026-09-27-escopo.md

Duas guardas em volta dos arquivos compartilhados

lane_territory_guard.py avisa quando uma lane edita dentro do território exclusivo de outra lane viva, nomeando a dona; nunca bloqueia. lane_git_guard.py vigia o índice git que as lanes compartilham: com outra lane viva, ele pega os comandos que o varrem — add -A/--all/., commit -a, um commit sem pathspec explícito, stash, reset --hard, checkout ., commit --amend. Roda em modo warn por padrão e só passa a block depois de zero falso positivo em uso real, configurado em lanes.yaml; uma lane trabalhando sozinha nunca é barrada.

Medido na árvore da 2.10.0: evals/collision-2lanes.sh → 10/10 linhas válidas, 0 corrupção; evals/collision-git-guard.sh e evals/collision-territory-guard.sh → 18/18 checagens em 3 rodadas cada (pass^k = 1,00); o self-test do registro mostra uma mensagem endereçada à lane aparecendo, e sumindo depois de arquivada.

Hand-offs do operador: encaminhar uma correção, abrir uma revisão, aprovar um item red

Três comandos do quadro pertencem ao operador, não a uma lane — contribuídos por @kleinelizeu junto com o Lane Dashboard. release-fix leva um item em NEEDS-FIX para FIX-QUEUED para uma executora nomeada que está registrada, viva e autorizada a construir o item; só essa lane pode levá-lo de volta a BUILDING, e uma correção cuja lane parou de bater pode ser roteada de novo, com o re-roteamento registrado. start-review leva um checkpoint, ou um item em DEFERRED, para UNDER-REVIEW para uma revisora viva nomeada que não construiu nenhuma tentativa dele. Os dois escrevem um kickoff no mailbox do alvo, roteado com ## To:. approve registra o gate humano de um item red como APPROVED — não MERGED: o merge acontece depois, no git, e é registrado depois.

python multi-session/lane-kit-1.8.2/scripts/lane_board.py release-fix ITEM-1 --target-lane exec-b
python multi-session/lane-kit-1.8.2/scripts/lane_board.py start-review ITEM-1 --target-lane rev-x
python multi-session/lane-kit-1.8.2/scripts/lane_board.py approve ITEM-1

Duas regras do veredito ficaram mais estritas com eles. A família de modelo de quem confere é comparada com toda lane que construiu o item, não só com a que fez o claim, e apelidos da OpenAI como codex-* contam como gpt. A tag pertence ao item: uma vez red, todo evento seguinte é red, qualquer que seja o --tag que um chamador passe.

O Lane Dashboard: o quadro numa página, toda ação também um comando de terminal

scripts/lane_dashboard.py serve o quadro como uma página em 127.0.0.1: as colunas, as lanes vivas, as competições, o mailbox de cada lane (não lidas, anunciadas, lidas), as entregas com o desfecho da campainha, as sessões que ele iniciou, o backlog com as waves e — perguntado ao git, não ao quadro — se cada item revisado está integrado. É opcional. Toda ação nele é um comando de terminal que faz exatamente a mesma coisa, e a página só chama esse comando.

O Lane Dashboard gravado de uma execução real: as métricas e um backlog de três specs de exemplo em duas waves de dependência; um comando de terminal leva EXAMPLE-1 da coluna Building para Ready for review e a página acompanha sozinha; o diálogo Approve merge do item red EXAMPLE-2 nomeia lane_board.py approve EXAMPLE-2 e é cancelado; a troca de idioma passa a página para o português

Gravado de uma execução real pelo scripts/record_lane_dashboard.py (o comando dele, os comandos do seed e a história medida estão em assets/ui/lane-dashboard.gif.txt): um projeto descartável montado pelo scripts/seed_lane_dashboard_demo.py através dos próprios escritores do lane-kit, servido pelo lane_dashboard.py serve --no-orca --port 0. A faixa de terminal é do gravador e traz o único comando que ele rodou e o stdout desse comando; a página moveu o card 0,24 s depois de o comando sair nesta gravação, porque o servidor relê o board a cada 0,75 s e avisa a página. O diálogo foi cancelado, e a página tentou 0 POSTs.

Ação na páginaComando de terminal
Nova spec · Iniciar wavelane_dashboard.py spec add · lane_dashboard.py wave start
Encaminhar correção · Iniciar revisão — a uma lane vivalane_board.py release-fix · lane_board.py start-review
Encaminhar correção · Iniciar revisão — com uma sessão novalane_dashboard.py fix launch · lane_dashboard.py review launch
Aprovar merge · Registrar mergelane_board.py approve · lane_dashboard.py merged
Pedir briefing · Relatório de integraçãolane_dashboard.py brief request · lane_dashboard.py integration report
Iniciar a sessão de novo · Parar uma sessãolane_dashboard.py session relaunch · lane_dashboard.py session cancel
python multi-session/lane-kit-1.8.2/scripts/lane_dashboard.py --project-dir . --open
python multi-session/lane-kit-1.8.2/scripts/lane_dashboard.py merged ITEM-1 --branch feat/item-1 --project-dir .

A mesma linha o inicia no macOS, no Linux e no Windows (com python3 ou py onde esse for o nome do interpretador); ela imprime a URL, e Ctrl+C o encerra. merged escreve MERGED só quando o git mostra a branch de quem construiu integrada no destino, medido naquele momento, e a medição vira a evidência do evento. A ancestralidade é perguntada primeiro (git merge-base --is-ancestor), porque ela vê todo commit, merges inclusive; só uma branch que não é ancestral cai na equivalência por patch-id (git cherry), e um commit de merge da branch que o destino não contém a mantém pendente, porque equivalência não prova um merge.

Modelo de segurança. O servidor é a exceção delimitada que o manifesto abre para módulos. O operador o inicia e o encerra; ele escuta só em loopback e responde só a um Host de loopback; toda ação é um POST que carrega um token emitido para aquela execução, como application/json; a página se recusa a ser emoldurada; um GET não escreve nada e não inicia nada; toda escrita passa pelo lane_board.py e pelo _lane_io.py. Uma sessão de agente só começa quando o operador confirma uma ação, com o modelo declarado em .claude/lanes/dashboard.json: maker ≠ checker compara a família do modelo que uma sessão de fato roda, então uma CLI que serve vários fornecedores é recusada até o modelo dela ser declarado.

LançadormacOSLinuxWindows
terminalTerminal.appo terminal do desktop ($TERMINAL, depois os de costume)um console PowerShell
itermiTerm2, com o script da sessão passado ao osascript como argumento——
tmuxuma janela na sessão tmux em que o dashboard roda, ou em hpp-lanes—
orcaorca, comando POSIXorca-ide, comando POSIX; nunca o leitor de tela GNOMEorca, --shell powershell; Orca antigo é recusado
externalqualquer terminal declarado no dashboard.json como um argv com {script}
headlesso modo não interativo do agente sob um supervisor: tempo limite, botão de parar e log com teto

ORCA_CLI_COMMAND sobrepõe o CLI em qualquer plataforma. Uma lista cortada de worktrees é sinalizada na página e como orca_truncated no snapshot. Veja HPP dentro do Orca para a configuração no mesmo worktree, plugins de projeto e o que não foi verificado no host real.

Medido na árvore da 2.10.0: o self-test do dashboard → 77 de 77 checagens em 9,3 s numa cópia nova do módulo (o instalador dá 30 s a cada self-test); tests/test_lane_dashboard.py e tests/test_lane_board_release.py → 66 testes, com controles ao lado das recusas; 65 passaram e 1 foi pulado no Windows, o que roda o script da sessão num shell POSIX.

06 · loop e waves

O loop só avança por um evento registrado.

planned --work_started--> active --evidence_recorded--> evidenced --check_passed--> checked
        [scope]                   [fresh-evidence]                 [read-only-checker]

checked --human_approved--> approved --verified--> verified
        [human]                      [closure]

O log de eventos é o .hpp/events.jsonl no workspace, somente-acréscimo e, novo na 2.10.0, encadeado por hash: todo evento acrescentado carrega prev_sha256, o sha256 da linha anterior como foi escrita (o primeiro aponta para o hash do vazio). Um evento que não cabe no estado atual é recusado antes de qualquer escrita, e verified exige ao menos um item de evidência registrado. O hpp status projeta o log na máquina acima, nomeia o próximo passo e termina com o estado da cadeia; o hpp resume devolve a mesma resposta em JSON. Nenhum dos dois pede a um modelo que lembre de alguma coisa.

python -m hpp event append --type work_started
python -m hpp status
python -m hpp resume
python -m hpp event verify
CadeiaO que significaevent verify
intacttoda linha aponta para a anteriorexit 0
legacyalgumas linhas foram escritas antes de a cadeia existir e não carregam elo; elas leem como antes, e o status imprime chain=legacy (N unchained)exit 0
brokeno prev_sha256 de uma linha não bate com a linha anterior, ou uma linha não é lida; o relatório nomeia first_divergent, e o status se recusa a projetar o log (exit 2, nomeando o mesmo passo)exit 2
emptyainda não há log, ou não há evento neleexit 0

A cadeia sozinha não vê a edição da sua última linha: a cauda só é ancorada por uma cópia do head_sha256 guardada fora dessa linha — o próximo append, que a carrega como seu prev_sha256, ou uma cópia que você guarda em outro lugar. Uma atestação não o registra; o snapshot dela só amarra o log quando o .hpp/events.jsonl não é ignorado pelo git. O status --json carrega o resumo da cadeia: status, chained, legacy e head_sha256.

Medido num diretório vazio, na árvore-fonte da 2.10.1: hpp status → HPP status: planned · events=0 · next=start work · chain=empty. event append --type verified como primeiro evento → hpp: invalid transition at event 1: planned --verified--> ?, exit 2, nenhum arquivo criado. Depois event append --type work_started → estado active, e hpp status imprime HPP status: active · events=1 · next=record fresh evidence · chain=intact. Depois de um segundo evento, com a primeira linha reescrita: event verify → broken, first_divergent event:2, exit 2, e hpp status → hpp: corrupt event log at line 2: hash chain broken at event:2 (prev_sha256 does not match line 1 as written), exit 2. Um log cuja primeira linha não carrega elo → chain=legacy (1 unchained), e event verify → legacy, exit 0.

Paralelismo segue o WorkGraph

Uma spec lista unidades de trabalho, cada uma com dependências, critérios de aceitação não vazios e um tier (economy, balanced, frontier). O compilador rejeita ciclo de dependência como erro — nunca como wave vazia — e emite waves topológicas: cada unidade fica na primeira wave depois de todas as suas dependências. Ele não dispara nada; quem respeita a barreira entre waves é o operador.

python -m hpp work plan examples/reliable-coding/workgraph.json
python -m hpp work waves examples/reliable-coding/workgraph.json
python -m hpp work waves examples/reliable-coding/workgraph.json --format mermaid

Medido no exemplo: wave 1 spec · wave 2 build, docs · wave 3 verify; contagem por tier economy 2 · balanced 1 · frontier 1. Com --format mermaid (o mesmo diagrama sai de work plan e de work waves) as três waves são três blocos subgraph e cada nó lê id · tier, p. ex. verify · frontier.

Critério citado não é critério rodado

Novo na 2.8.0: hpp work coverage SPEC --tests PATH... [--junit REPORT...]. Um teste cita um critério de aceite com [spec: capability/scenario] na sua docstring; sem relatório o comando responde se todo critério está citado (hpp.spec-coverage/v1). Com o relatório JUnit XML da execução ele responde se um teste que cita rodou: um critério fica executed (um teste que o cita passou e nenhum falhou), failed, cited_not_run (pulado, ausente do relatório, ou citado por docstring de módulo ou de classe), órfão ou desconhecido (hpp.spec-execution/v1). Exit 0 só quando todo critério está coberto ou, com relatório, executado; 1 nos outros casos; 2 sobre um relatório recusado — que declara DOCTYPE ou entidade, ou não é UTF-8 — ou um caminho de testes sem fonte Python. matched_testcases diz quantos casos do relatório foram ligados a um teste que cita. Um relatório se liga aos arquivos que mediu pelo tempo: um arquivo de teste que cita e foi modificado depois do relatório mais antigo entra em stale_sources, e a resposta não fica completa (exit 1) até a execução ser repetida — um relatório mais velho que uma edição não mantém um critério executed.

python -m hpp work coverage examples/cited-and-run/workgraph.json --tests examples/cited-and-run/check_prices.py
python -m pytest examples/cited-and-run/check_prices.py -q -p no:cacheprovider --junitxml out/cited-and-run.xml
python -m hpp work coverage examples/cited-and-run/workgraph.json --tests examples/cited-and-run/check_prices.py --junit out/cited-and-run.xml

Medido no exemplo: só citação → três critérios cobertos, exit 0; pytest → 2 passaram, 1 pulado; com o relatório → bulk-discount em cited_not_run (pulado), dois executados, matched_testcases 3, exit 1.

Lentes de revisão respondem numa forma só

Novo na 2.8.0: o módulo operator traz quatro lentes de revisão sem ferramenta de edição (o somente-leitura é provado quando sentadas pelo house_session.py) — lens-verification-gap, lens-partial-set, lens-deletion, lens-stale-evidence —, cada uma olhando uma mudança atrás de um tipo de defeito. Toda lente responde com um documento hpp.findings/v1: achados com um code estável, uma severity, file e line, a claim e a sua evidence, e inspected, o que a lente olhou. O hpp findings check FILE... [--subject CHANGE] recusa um documento que nomeia outra mudança que não CHANGE, um documento com chave que o contrato não define, inspected vazio ou o mesmo achado duas vezes (exit 2), e deriva o veredito: exit 0 quando toda revisão passou, 1 quando uma lente achou algo. Ele lê a resposta, nunca a mudança.

python -m hpp findings check examples/review-lenses/deletion-clean.json
python -m hpp findings check examples/review-lenses/deletion-clean.json examples/review-lenses/partial-set.json

Medido no exemplo: a revisão limpa → pass, exit 0; com a segunda → warn, um CALLER_NOT_UPDATED em checkout.py:18, exit 1.

Roteamento: primeiro um tier, depois um provedor que você declarou

hpp route --request F --providers F [--policy economy|balanced|frontier], padrão balanced. A requisição declara stage, risk e complexity (low, medium ou high) e context, um inteiro não negativo; cada provedor declara id, tiers, stages e max_context. Risco alto, complexidade alta ou contexto acima de 16000 exige frontier sob qualquer política; médio, ou contexto acima de 8000, exige ao menos balanced; fora isso vale o tier da própria política. Um provedor é elegível quando declara o stage e o seu max_context cobre o contexto. A rota devolve o tier e um dos ids de provedor que você declarou, com empate desfeito pelo id. Quando nenhum provedor elegível oferece o tier pedido, ela sobe — só para cima, para o tier mais próximo — e diz isso em fallback; quando nada no tier ou acima dele é elegível, ela recusa (exit 2). Ela nunca nomeia modelo, lê preço ou ranqueia fornecedores.

python -m hpp route --request examples/reliable-coding/route-request.json --providers examples/reliable-coding/providers.json

Medido: a saída carrega schema (hpp.route/v1), policy, requested_tier, selection (provider, tier), fallback, eligible_providers e rationale. No exemplo (risco baixo, complexidade baixa, contexto 4000), balanced seleciona local-frontier em balanced, economy seleciona local-economy e frontier seleciona local-frontier em frontier. Contra uma lista sintética que só oferecia balanced, uma requisição economy o selecionou com motivo de fallback upgraded-above-requested-tier; uma requisição de risco alto contra uma lista só economy → hpp: no provider satisfies the frontier risk floor, exit 2.

07 · política

Advisory não se fantasia de bloqueio.

O classificador devolve um de três vereditos e nunca executa o comando. O modo decide o que o veredito custa: em audit todo veredito sai com 0 e só é registrado; em enforce o veredito vira o exit code. Um arquivo de política do projeto recusado sai com 2 nos dois modos, sem veredito impresso (abaixo).

ALLOW · exit 0

Nenhuma regra casou

O comando segue. O conjunto de regras é pequeno e explícito; ele não afirma pegar toda forma destrutiva.

MANUAL · exit 1 em enforce

Gate humano

Qualquer git push e qualquer curl ou wget para uma URL — e, novo na 2.6.0, rodar o adaptador de exemplo de decisão tipada (examples/typed-decisions/decide.py; o pacote 2.5.8 devolve ALLOW para ele): publicação ou transferência externa precisa de uma pessoa.

BLOCK · exit 2 em enforce

Recusado

Deleção recursiva em qualquer ordem de flags (-rf, -fr, -r -f, --recursive --force, rmdir /s), force push, push em main ou master, curl | sh, DROP/TRUNCATE.

python -m hpp policy check --mode audit --command "git push origin main"
python -m hpp policy check --mode enforce --command "git push origin main"
ComandoVeredito · regraauditenforce
git push origin mainBLOCK · main-pushexit 0exit 2
git push origin featureMANUAL · external-pushexit 0exit 1
rm -rf srcBLOCK · recursive-deleteexit 0exit 2
python -m pytest -qALLOWexit 0exit 0

As oito células foram medidas nesta árvore com python -m hpp policy check. rm arquivo.txt, grep -rf padroes.txt e cp -rf a b não são bloqueio: a checagem lê o conjunto de opções de cada invocação de rm, não uma grafia.

Uma política de projeto só endurece

Novo na 2.10.0

Um projeto acrescenta às regras embutidas no .hpp/policy.json (hpp.policy/v1), que o policy check lê do diretório atual, ou no arquivo nomeado com --policy FILE. Ele guarda regras próprias — um id, uma action (BLOCK ou MANUAL), um pattern (uma expressão regular, casada sem diferenciar maiúsculas, como as embutidas) e um reason — e, em raise, regras MANUAL embutidas levadas a BLOCK. Todo BLOCK, embutido ou declarado, é tentado antes de qualquer MANUAL, então uma regra declarada não amolece um bloqueio embutido. A saída nomeia o arquivo em policy, e todo veredito diz a sua source: built-in ou policy.

{"schema": "hpp.policy/v1",
 "rules": [{"id": "no-prod-deploy", "action": "BLOCK", "pattern": "\\bdeploy\\b.*--prod\\b",
            "reason": "production deploys go through release review"}],
 "raise": {"external-push": "BLOCK"}}

Um arquivo que isentaria qualquer coisa é recusado inteiro: uma regra com action ALLOW, um raise que mantém ou baixa uma classe, uma regra que reusa um id embutido, uma chave fora do contrato (não existe lista de permissão), um campo faltando ou um pattern inválido. Um arquivo recusado recusa a checagem — exit 2 em audit como em enforce, sem veredito impresso — em vez de cair nas regras embutidas, que imprimiriam ALLOW justamente para o comando que o projeto pediu para bloquear.

Medido na árvore-fonte da 2.10.1 com o arquivo acima em .hpp/: npm run deploy -- --prod → BLOCK, regra no-prod-deploy, source policy, exit 0 em audit e 2 em enforce; git push origin feature em enforce → BLOCK, regra external-push, source policy, exit 2. Com uma regra cuja action é ALLOW → hpp: rule 'let-it-through': action must be BLOCK or MANUAL, not 'ALLOW'; a policy only hardens, exit 2 nos dois modos; com "raise": {"external-push": "ALLOW"} → hpp: raise: 'external-push' is MANUAL and ALLOW would lower it; a policy only hardens, exit 2 em audit; --policy apontando para um arquivo inexistente → hpp: policy file not found: nope.json, exit 2.

08 · prova

Capacidade e confiabilidade não são a mesma métrica.

pass@k mede se um caso passou ao menos uma vez em k execuções. pass^k mede se passou todas as vezes. O gate de capacidade exige pass@k ≥ 0.90; o gate de regressão exige pass^k = 1.0; --gate both exige os dois. Uma suíte que passa no primeiro e reprova no segundo é instável, e o relatório diz em quanto.

python -m hpp eval run examples/reliable-coding/benchmark-suite.json -k 3 --gate both
python -m hpp benchmark -k 3 --json

Medido: HPP benchmark: pass@k=1.00 · pass^k=1.00 · gate=PASS sobre dez controles (contrato do manifesto, enforcement de política, waves do WorkGraph, colisão de lanes, frescura de monitor, proveniência de contexto, piso de roteamento, gate evento/evidência, determinismo do grafo, atestação de evidência), três execuções cada. O relatório JSON carrega o sha256 da suíte e a plataforma em que rodou. Exit 0 quando o gate passa, 1 quando não.

Uma decisão tomada fora é medida, nunca confiada por padrão

Novo na 2.6.0

O harness não chama modelo. Uma decisão pequena tomada fora dele — por uma regra, uma pessoa, um modelo local ou um modelo de decisão tipada hospedado — pode ser registrada como hpp.decision/v1: sempre advisory; com raise-only ela pode elevar um valor declarado e nunca baixá-lo; abstention e instrument-failure são desfechos que não mudam nada. hpp decide eval roda o decisor que você nomeia como comando contra casos rotulados e reporta cobertura, acerto seletivo, erros confiantes, abstenções e falhas separadamente, com uma curva de cobertura por limiar de confiança. A versão 1 mede só perguntas de escolha.

python -m hpp decide validate decision.json
python -m hpp decide eval examples/typed-decisions/gotcha-family-suite.json --decider-command '["python", "examples/typed-decisions/baseline_decider.py"]'
  • decide eval SUITE — --decider-command recebe o argv do decisor como array JSON de strings, executado sem shell; omita-o para reexecutar os registros guardados nos casos da suíte, onde um caso sem registro conta como falha de instrumento. --timeout (padrão 10.0) é o número de segundos por caso antes de esse caso contar como falha de instrumento. O gate: --min-selective-accuracy (padrão 0.9), --max-confident-errors (padrão 0; erro confiante é uma resposta errada com confiança igual ou acima do confidence_floor da suíte, 0.6 quando a suíte não define um) e --max-failures (padrão 0). Exit 0 o gate passou · 1 o gate reprovou, ou nada foi decidido e não há acerto a medir · 2 registro, suíte ou argv inválido.
  • decide validate RECORD — imprime valid e o valor efetivo sobre o qual um consumidor pode agir, com a sua ação (raised, kept, advised ou none). Exit 0 válido · 2 recusado, nomeando a primeira regra quebrada.

Medido num checkout de main: o decisor baseline na suíte de exemplo → 15 casos, 12 decididos, 3 abstenções, 0 falhas de instrumento, cobertura 0.8, acerto seletivo 1.0, every threshold held, exit 0. A mesma suíte sem --decider-command não guarda registros → 15 falhas de instrumento, no decision was made, exit 1. Um registro com "authority": "binding" → hpp: authority must be 'advisory': …, exit 2. O pacote 2.5.8 não tem o comando decide: ali, hpp decide é uma escolha inválida, exit 2.

Como funciona de ponta a ponta — declarar um conselheiro como o Jev, fixar a versão, registrar, validar e medir — está no capítulo 09. O adaptador e os riscos dele estão em examples/typed-decisions.

Pacotes de evidência: o exit code de um critério, medido fora do modelo

Novo na 2.6.0

O hpp não dirige navegador e não chama modelo. O hpp evidence run roda o comando-critério que você declara — uma spec ponta a ponta, uma suíte pytest, qualquer script — como argv sem shell, mede o exit code, faz o hash de cada arquivo de artefato que você declarou e escreve .hpp/evidence/<id>-<UTC>.json (hpp.evidence/v1): o comando, o commit-base, o exit code, o veredito, stdout e stderr como contagem de bytes e sha256 (nunca o texto), os artefatos e um auto-hash. O veredito só é passed quando o comando saiu com 0 e cada padrão declarado casou com um arquivo que esta execução escreveu (um arquivo intocado desde antes da execução sai como unchanged e não conta); fora isso é failed, missing-artifacts, timeout ou could-not-start. O hpp evidence verify re-deriva um registro a partir dos arquivos no disco. O registro tem auto-hash, e só é assinado com --sign-key. O auto-hash torna visível um registro editado; ele não é assinatura — quem pode escrever o arquivo pode reescrevê-lo, e ao hash junto —, então um checker que não pode confiar no maker roda o comando de novo a partir da sua própria lane em vez de se apoiar só no verify, ou pede uma assinatura e a confere com --allowed-signers (assinatura opcional, abaixo).

python -m hpp evidence run --id smoke-page --artifact out/report.html --artifact out/smoke.log -- python examples/evidence/smoke_page.py
python -m hpp evidence verify <record_path>
  • evidence run --id ID [--artifact GLOB]... -- COMMAND — --artifact é um glob relativo ao workspace, repetível; --timeout (padrão 600.0) é o número de segundos antes de a execução contar como timeout; --out é o diretório do registro, relativo ao workspace (padrão .hpp/evidence); --record-event acrescenta evidence_recorded ao log de eventos quando, e só quando, o pacote passou, e exige ao menos um --artifact. Um argv com cara de segredo, um caminho de artefato ou de --out fora do workspace, um id inválido ou um timeout não positivo são recusados antes de qualquer execução. Exit 0 passou · 1 não passou · 2 recusado, ou o evento não pôde ser acrescentado.
  • evidence run ... --sign-key KEY --signer NAME (novo na 2.10.0) — assina o registro; --signer é obrigatório com --sign-key, e os dois são conferidos antes de o comando rodar.
  • evidence verify RECORD — o record_path que o run imprimiu; --allowed-signers FILE (novo na 2.10.0) exige também uma assinatura que esse arquivo aceite. Exit 0 valid, registro íntegro de uma execução que passou · 1 not-evidence, registro íntegro de uma execução que não passou · 2 blocked, o registro foi editado ou se contradiz, ou um artefato mudou ou sumiu, ou — com --allowed-signers — a assinatura falta, não é aceita ou cobre bytes alterados.

Medido num checkout de main, numa cópia da árvore para que out/ e .hpp/ fossem escritos ali: o demo → passed, exit 0, e o verify do seu registro → valid, exit 0. Com --break depois do script, os dois artefatos foram escritos e o veredito foi failed, exit 1; o verify desse registro → not-evidence, exit 1, e o de uma cópia editada para dizer passed → blocked, the record was edited after it was written, exit 2. O primeiro registro, verificado de novo depois que a segunda execução reescreveu out/ → blocked, 2 artifact(s) changed since the run, exit 2: runners ponta a ponta que limpam o diretório de saída no início de cada execução fazem o mesmo, então dê aos artefatos que precisam continuar verificáveis um diretório por execução. --record-event sem work_started no log → exit 2, evento não acrescentado; depois de event append --type work_started ele levou o loop a evidenced. O pacote 2.5.8 não tem o comando evidence: ali, hpp evidence é uma escolha inválida, exit 2.

Novo na 2.7.0: hpp evidence mutate --id ID (--mutants FILE | --generate FILE)... -- COMMAND pergunta se o critério perceberia código quebrado. Ele roda o comando numa cópia do workspace, limpa (tem de passar, senão o veredito é no-control, exit 2, e nenhum mutante roda), depois uma vez por mutante (tem de falhar). --generate muta um arquivo Python operador a operador (==↔!=, <↔<=, >↔>=, and↔or, True↔False), nunca dentro de strings ou comentários; --mutants lê {id, file, find, replace} declarados. Exit 0 sensitive · 1 blind-spots, incomplete ou no-mutants · 2 no-control ou recusado. Medido em examples/criterion-sensitivity: a checagem forte mata 3 de 3 mutantes, exit 0; a fraca, igualmente verde, deixa passar 3 de 3 e os nomeia, exit 1.

Veja examples/evidence para o mesmo padrão em volta de um runner ponta a ponta de verdade.

Régua de recuperação: o recuperador, medido separado da resposta

Novo na 2.6.0

O harness não roda índice e não chama modelo. Um recuperador é um comando que você declara: ele lê {"query": …, "k": …} como JSON no stdin e imprime ids ranqueados, o melhor primeiro. O hpp retrieval eval pontua o top k de cada resposta contra os ids que uma suíte hpp.retrieval-suite/v1 marca como relevantes — hit@k, recall@k, precision@k, MRR e nDCG@k — e conta à parte as falhas de instrumento (não iniciou, timeout, exit diferente de zero, saída que não é JSON, id duplicado): elas nunca são pontuadas, e as médias cobrem só os casos medidos. Sem nenhum caso medido as métricas são nulas e o gate diz por quê, nunca 0%. Se uma boa resposta pode ser gerada a partir do que foi recuperado é outra pergunta, que não se faz aqui.

python -m hpp retrieval eval examples/retrieval/suite.json
python -m hpp retrieval eval examples/retrieval/suite.json --retriever-command '["python", "examples/retrieval/keyword_retriever.py"]'
  • retrieval eval SUITE — --retriever-command recebe o argv do recuperador como array JSON de strings, executado sem shell; omita-o para reexecutar os resultados guardados nos casos da suíte. -k substitui o corte da suíte. --timeout (padrão 10.0) é o número de segundos por caso antes de esse caso contar como falha de instrumento. O gate: ao menos um caso medido, --min-recall (padrão 0.8) para o recall@k médio e --max-failures (padrão 0). Exit 0 o gate passou · 1 o gate reprovou · 2 suíte ou argumento recusado.

Medido num checkout de main: os dois comandos → 7 casos medidos, 0 falhas de instrumento, recall@3 médio 0.786, hit@3 0.857, MRR 0.786, nDCG@3 0.749, mean recall@k 0.786 < 0.8, exit 1 — o baseline por palavra-chave erra por completo a pergunta parafraseada, um 0 medido, e o gate padrão reprova; com --min-recall 0.75 → every threshold held, exit 0. Um recuperador que sai com 3 em todo caso → 7 falhas de instrumento, métricas nulas, no case was measured (7 instrument failures); there is no recall to measure, exit 1. --retriever-command '"python"' → hpp: --retriever-command must be a JSON array of strings (argv, no shell), exit 2. A suíte é sintética e o baseline foi escrito junto com ela: estes números provam que a régua funciona, não que algum recuperador é bom. O pacote 2.5.8 não tem o comando retrieval: ali ele é uma escolha inválida, exit 2.

Veja examples/retrieval para o contrato do recuperador e o formato da suíte.

Checagem de citações: todo marcador resolve, ou o exit code diz qual não resolve

Novo na 2.6.0

Uma resposta escrita a partir de fontes marca cada afirmação com o id da fonte em que se apoia — [ID:<id>] por padrão. O hpp cite check lê a resposta e a lista de itens de contexto a partir da qual ela foi escrita e reporta, sem modelo: um marcador cujo id não está no contexto (UNKNOWN_ID), um marcador que nomeia um intervalo ou uma lista (RANGE) e um marcador vazio (EMPTY_MARKER) bloqueiam; mais de --max-per-sentence marcadores numa frase (TOO_MANY) e uma frase com número, percentual, valor monetário ou data e nenhum marcador (UNCITED_CLAIM) avisam. Ela prova que cada marcador resolve, não que a fonte citada sustenta a frase: esse julgamento precisa de um leitor. O separador de frases e o detector de números são heurísticas, listadas na docstring de hpp/citations.py.

python -m hpp cite check --text examples/citations/answer.md --context examples/citations/context.json
  • cite check --text FILE --context FILE — --context é uma lista JSON de itens com id (e o seu text), ou de ids soltos; --max-per-sentence (padrão 4); --marker recebe uma regex com exatamente um grupo de captura, o id. Texto vazio, texto que é só código, texto ou contexto com cara de segredo e uma regex inutilizável são recusados. Exit 0 limpo · 1 aviso (TOO_MANY, UNCITED_CLAIM) · 2 bloqueio (UNKNOWN_ID, RANGE, EMPTY_MARKER) ou entrada recusada.

Medido num checkout de main: a resposta de exemplo → veredito ok, 7 frases, 5 marcadores, 4 dos 5 ids de contexto citados, exit 0. Em cópias dela: [ID:glossary] trocado por [ID:glossary-v2] → UNKNOWN_ID, exit 2; por [ID:runbook-7,sla-2026] → RANGE, exit 2; o marcador removido da frase com 4 engineers → UNCITED_CLAIM, exit 1. E o limite, medido: 42 minutes trocado por 90 minutes, ainda citado à fonte que diz 42 → veredito ok, exit 0. O pacote 2.5.8 não tem o comando cite: ali ele é uma escolha inválida, exit 2.

Veja examples/citations para outra sintaxe de marcador e o formato do relatório.

House Session: um painel de decisores pinados, contado, parado e selado

Novo na 2.7.0

O harness não chama modelo: os assentos e o juiz respondem fora dele, nas próprias lanes. hpp deliberate confere o que eles disseram. Um painel (hpp.panel/v1) não começa a menos que todo assento nomeie um modelo pinado (um alias como -latest é recusado), os participantes venham de pelo menos duas famílias de modelo, exatamente um juiz sente numa lane que nenhum participante usa, e os papéis sirvam ao session_type — um review exige dois examinadores e um assento dissidente. Cada turno (hpp.turn/v1) lista os turnos que o assento tinha lido: um turno da rodada 1 que leu algum é recusado, então a rodada cega é verificável. Um assento sem turno numa rodada não é julgado, nunca voto contra, e bloqueia o veredito. A sessão para por regra — not-judged, grounded-convergence, paused-budget, no-new-evidence, max-rounds — e escalate leva a uma pessoa a dissidência fundamentada que sobreviveu. record sela o painel, os turnos, a contagem, a parada, o hpp.decision/v1 do juiz, o veredito e a dissidência que perdeu em hpp.deliberation/v2 (a forma selada desde a 2.8.0; um registro v1 só é verificado); verify confere o selo e re-deriva contagem, parada e veredito a partir dos turnos. O selo não é assinatura: uma reescrita coerente de um turno ou do registro do juiz, selada de novo, verifica, então guarde o texto verbatim e a resposta bruta do juiz, cujos hashes o registro carrega. A sessão termina na primeira rodada em que uma regra vale; turnos de rodadas posteriores são recusados, e o registro do juiz precisa vir do provedor e do modelo pinado do assento do juiz. A decisão humana é escrita depois, fora do selo.

Novo na 2.8.0: um painel pode declarar evidence.ids — plan --context FILE os preenche a partir do contexto que os assentos receberam, --evidence RECORD... acrescenta cada registro hpp.evidence/v1 que verifica. Aí um fact só fundamenta uma posição por uma referência que resolve; qualquer outra referência é unsupported, listada por rodada e nunca contada como evidência nova. Quando o veredito deixa de pé uma dissidência fundamentada, o record exige --rationale, um hpp.rationale/v1 do assento do juiz com um steelman por posição dissidente e ao menos um would_change_if. O registro é hpp.deliberation/v2; um registro v1 selado pela 2.7.0 continua verificando.

Novo na 2.8.0, no módulo de lanes: house_session.py families diz se o host consegue sentar duas famílias de modelo (uma só é DEFERRED, nunca um painel de uma família), e house_session.py seat roda o comando de um assento no seu próprio worktree, tirando a impressão digital do worktree antes e depois — um assento que escreveu não tem turno (exit 2), um assento que falhou não é julgado (exit 1). O /deliberate no Claude Code e a skill house-session no Claude Code e no Codex percorrem a sessão inteira.

python -m hpp deliberate plan examples/house-session/review/panel.json
python -m hpp deliberate stop --panel examples/house-session/review/panel.json --turns examples/house-session/review/turns.json
python -m hpp deliberate record --panel examples/house-session/review/panel.json --turns examples/house-session/review/turns.json --judge examples/house-session/review/judge.json --rationale examples/house-session/review/rationale.json --out out/record.json
python -m hpp deliberate verify out/record.json
  • deliberate plan FILE [--context FILE] [--evidence RECORD...] [--out FILE] · deliberate validate FILE [--panel FILE] — validate lê um painel, um turno (com o painel a que pertence) ou um registro, que ele verifica. Exit 0 válido · 2 recusado, nomeando a primeira regra quebrada.
  • deliberate tally --panel FILE --turns FILE · deliberate stop --panel FILE --turns FILE — --turns é uma lista JSON de hpp.turn/v1. Exit 0 relatado · 2 recusado.
  • deliberate record --panel FILE --turns FILE [--judge FILE] [--rationale FILE] --out FILE — recusa sessão que não parou, veredito sem juiz, veredito sobre dissidência fundamentada sem steelman, e juiz que respondeu outra pergunta, outro estado ou de outro modelo que não o do assento do juiz. Exit 0 selado com a resposta do juiz · 1 selado, veredito bloqueado ou juiz falhou · 2 recusado.
  • deliberate verify RECORD — exit 0 íntegro · 2 quebrado: editado depois de selado, ou não se re-deriva dos próprios turnos e do juiz.

Novo na 2.9.0: uma sessão selada alimenta os comandos que agem sobre um veredito, e só pode tornar o resultado mais estrito. hpp route --deliberation PLAN-RECORD --maker-family F deixa uma sessão plan subir o risco do pedido, nunca baixar. hpp attest create ... --deliberation GATE-RECORD --maker-family F é o release gate: uma sessão release-gate cujos fatos se apoiam só em registros hpp.evidence/v1; a attestation grava o veredito mais estrito e o hash do registro, e um painel que não decidiu manda para revisão. lane_board.py select --deliberation DESIGN-RECORD decide uma competição, com o juiz como revisor de registro. Um juiz da família de modelo do maker é recusado em todos.

Medido a partir de um checkout da main: o exemplo de revisão → stop max-rounds, escalate: true; record → veredito high, dissidência do assento c em low, concordância cega 0,33, concordância final 0,67, 591 caracteres de 6000, exit 0; verify → intact, exit 0, e numa cópia com o veredito editado → record_sha256 does not match, exit 2. record sem --judge → a verdict needs the judge's decision record, exit 2. O painel reproduzido como um decisor só na suíte de decisão tipada → 15 sessões, 12 decididas, 3 abstenções, 0 falhas de instrumento, exit 0. As sessões são sintéticas: provam o contrato, não que um painel vence um agente sozinho.

Veja examples/house-session para os papéis que cada tipo de sessão exige e a regra de parada completa.

A atestação amarra uma aprovação a bytes

hpp attest create registra um veredito junto com o hash da spec, o commit-base, um digest do snapshot completo do repositório (rastreado e não rastreado), o maker, o checker e a sessão. Maker e checker têm de ser diferentes, ou o registro é recusado. hpp attest verify re-deriva cada amarração e bloqueia quando qualquer uma se moveu.

python -m hpp attest create --repo . --spec spec.md --output att.json --maker a --checker b --session s1 --verdict approved
python -m hpp attest verify att.json --repo .

Medido num repositório de rascunho: --maker a --checker a → hpp: maker and checker must be different non-empty actors, exit 2. Com atores diferentes o registro sai approved e o verify devolve valid, exit 0; depois de uma linha da spec mudar, o verify devolve blocked com mismatches: spec_sha256, snapshot_digest, exit 2. A atestação exige git.

Uma aprovação velha recusada, renderizada da saída real: um autor recusado como o próprio checker, uma aprovação registrada e verificada, um byte acrescentado a um arquivo commitado, e a mesma aprovação bloqueada com exit 2

Renderizado de uma execução real (scripts/render_terminal_svg.py --animate --session attest): num repositório cujo único commit guarda spec.md e app.py, um byte acrescentado ao app.py depois da aprovação faz o verify devolver blocked só com mismatches: snapshot_digest, exit 2, enquanto o base_commit não se move: uma edição não commitada move o snapshot, não o commit.

Assinatura opcional: quem deu o veredito, conferido offline

Novo na 2.10.0

Um auto-hash mostra que um registro mudou; não diz quem o escreveu. Os dois registros podem carregar uma assinatura SSH. attest create ... --sign-key KEY [--signer NAME] assina a atestação — o signatário padrão é o checker — e evidence run ... --sign-key KEY --signer NAME assina o registro de evidência. O harness nunca lê a chave: ele roda ssh-keygen -Y sign, namespace hpp, sobre os bytes canônicos do registro — a própria assinatura nunca entre eles — e guarda um bloco signature — scheme (sshsig), namespace, signer, o sha256 dos bytes assinados e a signature no formato de texto do OpenSSH. Sem ssh-keygen no PATH, o comando recusa (exit 2) e nada é escrito.

attest verify e evidence verify reportam a assinatura no seu próprio bloco signature. Sem --allowed-signers ela é not-signed ou not-verified (presente, nunca afirmada), e o veredito é decidido exatamente como antes. Com --allowed-signers FILE — o arquivo allowed-signers do ssh-keygen em que quem verifica confia — ela só é verified quando o ssh-keygen -Y verify aceita a assinatura exatamente destes bytes por uma chave que o arquivo lista para o signatário nomeado; qualquer outra coisa é refused, e o registro fica blocked, exit 2: sem assinatura, assinado por outra chave, ou alterado depois de assinado, por mais coerente que o hash tenha sido recalculado.

python -m hpp attest create --repo . --spec spec.md --output att.json --maker a --checker b --session s1 --verdict approved --sign-key ~/.ssh/id_ed25519
python -m hpp attest verify att.json --repo . --allowed-signers allowed_signers
python -m hpp evidence run --id smoke --artifact out.txt --sign-key ~/.ssh/id_ed25519 --signer b -- python check.py
python -m hpp evidence verify <record_path> --allowed-signers allowed_signers

Medido na árvore-fonte da 2.10.1 num repositório de rascunho, com uma chave ed25519 e um arquivo allowed-signers que a lista para o checker, a atestação assinada: verify sem o arquivo → valid, assinatura not-verified, exit 0; com o arquivo → valid, assinatura verified, exit 0; com um arquivo que lista outra chave → blocked, mismatches: signature, exit 2. Uma atestação sem assinatura verificada com o arquivo → blocked, the record is not signed, exit 2. evidence run --sign-key sem --signer → hpp: signing needs a signer: pass --signer with the principal your allowed-signers file lists, exit 2; com ele → passed, e o evidence verify responde not-verified, verified e blocked exatamente como a atestação.

Veja a matriz de prova e o contrato do benchmark.

09 · decisões

Um conselheiro é declarado, medido, e nunca chamado pelo harness.

Algumas decisões são pequenas: escolher uma entre poucas opções sobre uma mudança — o risco dela, a rota, se precisa de uma pessoa. Elas podem vir de fora do harness: uma regra, uma pessoa, um modelo local, ou um modelo hospedado de decisão tipada como o Jev. O hpp continua sem chamar modelo e sem guardar chave. O que ele acrescenta é um registro que se confere e uma régua que mede o conselheiro antes que alguém confie nele.

PeçaOndeChama modelo?
o registro hpp.decision/v1hpp/decision.py · hpp decide validatenão
a réguahpp decide evalnão — roda o decisor que você nomeia, como comando
um baseline léxicoexamples/typed-decisions/baseline_decider.pynão
um adaptador HTTPexamples/typed-decisions/decide.pysim — só quando você o roda, com a sua chave

Como funciona com o Jev

  1. Declare. hpp init --decision-advisor typesafe (ou openrouter, ou compatible) registra qual conselheiro você usa e imprime como ligá-lo. O padrão é off. A chave fica no seu shell — TYPESAFE_API_KEY para a TypeSafe AI, que serve o Jev; OPENROUTER_API_KEY para o mesmo modelo pelo endpoint de decisões do OpenRouter (alpha); --endpoint para um servidor local ou próprio que fale o mesmo formato.
  2. Fixe a versão. O adaptador manda a pergunta, as opções e o estado para uma versão fixada do modelo, como jev-1.13.0. Um alias como jev-latest é recusado, inclusive quando o servidor responde sob um, para que o registro possa dizer quem respondeu.
  3. Registre. A resposta vira um registro hpp.decision/v1 com um de três desfechos: recommendation (uma das suas opções, com a confiança que o decisor informou, ou nula), abstention (ele não quis escolher — não muda nada) ou instrument-failure (timeout, erro HTTP, uma página HTML, uma resposta malformada — não muda nada, nunca é veredito). O registro é sempre advisory; com --declared <valor> ele é raise-only: o conselho pode subir o valor que você declarou, nunca baixar.
  4. Valide. hpp decide validate registro.json imprime o valor efetivo sobre o qual um consumidor pode agir e a ação — raised, kept, advised ou none.
  5. Meça antes de confiar. hpp decide eval roda o baseline e o seu conselheiro na mesma suíte rotulada e reporta cobertura, acerto seletivo, erros confiantes, abstenções corretas e perdidas, falhas de instrumento e, quando há confiança, uma curva de cobertura nos pisos 0,5–0,9. Uma suíte sem nenhuma decisão é reportada como sem amostra, nunca como 0%.
export TYPESAFE_API_KEY=<sua chave>
python -m hpp decide eval examples/typed-decisions/gotcha-family-suite.json \
  --decider-command '["python", "examples/typed-decisions/baseline_decider.py"]'
python -m hpp decide eval examples/typed-decisions/gotcha-family-suite.json \
  --decider-command '["python", "examples/typed-decisions/decide.py", "--provider", "typesafe", "--model", "jev-1.13.0"]'

python examples/typed-decisions/decide.py --provider typesafe --model jev-1.13.0 \
  --question examples/typed-decisions/route-risk-question.json --state-file change.txt --declared low > decision.json
python -m hpp decide validate decision.json

O que o adaptador recusa, e por quê

  • Uma resposta válida pode estar errada. O esquema garante que a resposta é uma das suas opções, não que está certa; confidence é uma fórmula sobre a distribuição da resposta, não a probabilidade de acertar. Calibre um piso por pergunta, com os seus rótulos.
  • A abstenção é nossa. O fornecedor não tem estado de abstenção; confiança baixa vira abstention aqui.
  • O texto julgado pode puxar a resposta. Instruções plantadas no texto podem desviá-la — é por isso que raise-only existe.
  • Nada se reproduz perguntando de novo. O registro guarda o modelo fixado que respondeu e o hash da resposta crua, gravada ao lado em .hpp/decisions/raw/.
  • Toda chamada manda texto para fora da sua máquina. Nunca chame um conselheiro a partir de um hook; o adaptador recusa texto com cara de segredo, faz uma tentativa só, nunca repete, limita o corpo a 1 MiB, recusa redirecionamento e só manda chave por https ou para um endereço de loopback. hpp policy check classifica rodá-lo como MANUAL, como um curl para uma URL.

A versão 1 mede só perguntas de escolha; uma nota ou uma probabilidade de sim não têm definição acordada de certo, então o contrato recusa esses tipos em vez de reportar um 0% vazio. A suíte que acompanha o produto tem quinze casos sintéticos escritos à mão, e o baseline foi escrito junto com ela — a nota dele prova que a régua funciona, não a qualidade do baseline. Monte a sua suíte com casos rotulados seus antes de ler qualquer número como qualidade.

Medido na árvore da 2.10.0: o decisor baseline na suíte do produto → 15 casos, 12 decididos, 3 abstenções, 0 falhas de instrumento, cobertura 0,8, acerto seletivo 1,0, saída 0.

10 · hosts

Mesmo contrato, diferenças visíveis.

Claude CodeCodex CLI
Distribuiçãomarketplace de plugins (/plugin marketplace add rusharlabs/house-party-protocol, depois /plugin install <módulo>@house-party-protocol)um marketplace de plugins próprio para os módulos que carregam skills (codex plugin marketplace add rusharlabs/house-party-protocol, depois codex plugin add <módulo>@house-party-protocol; só skills, lido de .agents/plugins/marketplace.json), mais uma cópia verificada por módulo via kit_doctor.py install --host codex para runtime, scripts e templates
Onde caio store de plugins do host; .claude/settings.local.json para hooks e statusLine.agents/skills para skills, .agents/hpp/<módulo> para runtime; AGENTS.md é o que o host lê
Lifecycle hooksnativos, depois que o wiring é colado (gate humano; o hpp init lista os módulos que declaram hooks)o host tem os seus, mas o HPP não carrega nenhum dos seus hooks lá (cada manifesto de plugin do Codex carrega um objeto hooks vazio); as mesmas capacidades são comandos explícitos
Cobertura no manifestoseis módulos native, quatro explicit-commandnove explicit-command, sete deles no canal de plugin; claude-dev-kit é unsupported, e o hpp init interrompe em vez de planejá-lo ali

Fonte: hosts em cada entrada de módulo do hpp.manifest.json; os caminhos por host são os que o hpp init imprime no bloco de wiring. hpp init --host codex planeja o bundle reliable-coding com os seis módulos como explicit-command, abrindo com as linhas do canal de plugin para os cinco que carregam skills — medido na árvore-fonte da 2.10.1, 12 commands to paste: um codex plugin marketplace add, cinco codex plugin add e seis linhas do instalador. A matriz host × módulo × canal, uma linha por módulo, é renderizada do manifesto por hpp doctor --matrix e mantida em Arquitetura · Hosts e canais; um teste falha quando as duas divergem.

11 · exit codes

Um contrato: 0 ok · 1 warn · 2 block · 3 error.

Comando0123
policy check --mode enforceALLOWMANUALBLOCK · arquivo de política recusado (novo na 2.10.0)—
policy check --mode audittodo veredito—arquivo de política recusado (novo na 2.10.0)—
eval run · benchmarkgate passougate reprovousuíte inválida—
decide eval (novo na 2.6.0)gate passougate reprovou · nada decididoregistro, suíte ou argv inválido—
decide validate (novo na 2.6.0)válido—recusado—
evidence run (novo na 2.6.0)passounão passourecusado · evento não acrescentadoerro interno
evidence verify (novo na 2.6.0)validnot-evidenceblocked · com --allowed-signers, assinatura ausente ou não aceitaerro interno
evidence mutate (novo na 2.7.0)sensitivepontos cegos · incompleto · sem mutantessem controle · recusadoerro interno
work coverage (novo na 2.8.0)completonão coberto · não executadorelatório ou caminho de testes recusadoerro interno
findings check (novo na 2.8.0)toda revisão passouuma lente achou algodocumento quebra o contratoerro interno
retrieval eval (novo na 2.6.0)gate passougate reprovousuíte ou argumento recusadoerro interno
cite check (novo na 2.6.0)limpoavisobloqueio · entrada recusadaerro interno
deliberate record (novo na 2.7.0)seladoveredito bloqueado · juiz falhourecusadoerro interno
deliberate verify · plan · validate (novo na 2.7.0)íntegro · válido—quebrado · recusadoerro interno
attest create · attest verifyapproved · valid—revise/blocked · invalid · recusado · com --allowed-signers, assinatura ausente ou não aceita—
event verify (novo na 2.10.0)intact · legacy · empty—brokenerro interno
initok · no-opaviso, falha não bloqueanteinterrompidoerro de uso
event append · status · mapas · route · context compileok—transição inválida, log corrompido ou cadeia de hash quebrada, entrada inválida, nenhum provedor no piso de risco, contexto com cara de segredoerro interno

Fonte: exit_codes no manifesto, hpp/cli.py e as medições citadas acima. Qualquer erro conhecido (manifesto, instalação, estado, eval, atestação, JSON inválido) sai com 2 e uma mensagem de uma linha; uma exceção inesperada sai com 3.