O modo de trabalhar que o harness impõe. Um time consegue adotá-lo em um dia, porque toda prática
termina num comando que ou passa ou reporta o que mediu. Os comandos abaixo usam a CLI do harness
(python -m hpp) e, onde um módulo está envolvido, os scripts que esse módulo instala. Os caminhos
de módulo aparecem relativos à distribuição emitida.
01
1. Abra uma unidade de trabalho
O trabalho começa como uma spec, não como uma conversa. Uma spec é uma lista de unidades, cada uma
com um id, o próprio depends_on, ao menos um critério de aceite escrito como algo que um comando
consegue decidir, e um tier que diz quanto risco ela carrega. O harness compila a spec num
WorkGraph e deriva waves das dependências. Unidades na mesma wave podem rodar em paralelo; uma
unidade cujas dependências não fecharam não começa.
Antes de tocar arquivos num repositório compartilhado, a sessão reivindica uma lane: um id, os caminhos que ela vai possuir e um heartbeat que ela vai manter atualizado. Duas lanes vivas não podem se sobrepor em território exclusivo. Uma lane que para de emitir heartbeat perde a reivindicação por tempo, não porque alguém apagou um lock.
Então o primeiro evento é registrado, e o loop sai de planned.
python -m hpp work plan SPEC.json
python -m hpp work waves SPEC.json
python multi-session/lane-kit-1.8.2/scripts/lane_board.py claim --help
python -m hpp event append --type work_started --data '{"work":"ITEM-1","actor":"maker-a"}'
02
2. Declare pronto
"Pronto" é um estado em que o harness entra, não uma palavra que o agente diz. Ele exige que os
critérios de aceite rodem como comandos e saiam com 0, no checkout atual, agora. O done gate recebe
os critérios como argumentos, roda cada um e imprime DONE-GATE: DONE somente quando todos passam.
Quando algum critério ainda não consegue passar, a saída correta é um parcial declarado: o gate
aceita --declare-partial "<o que falta>" e reporta PARCIAL-DECLARADO com esse texto e uma
saída diferente de zero. Sem a declaração ele reporta NOT-DONE. Silêncio não é um estado; um
parcial não declarado é uma falha.
Quando os critérios passam, a evidência é registrada no event log com uma referência a onde a
saída mora. O caminho principal é o hpp evidence run --record-event (novo na
2.6.0): ele mesmo roda o critério, mede o código
de saída, faz hash dos arquivos declarados com --artifact, escreve um registro em
.hpp/evidence/ e acrescenta evidence_recorded somente quando esse bundle passou. Ele exige ao
menos um --artifact, porque um código de saída sozinho não deixa nada para re-derivar depois.
Numa versão sem ele, o mesmo evento é acrescentado à mão com hpp event append, e então carrega
só o que a pessoa escreveu. O loop passa a evidenced. Ele não consegue passar a verified sem
ao menos um registro desses.
python frameworks/operator-kit-1.8.4/scripts/done_gate.py "python -m pytest -q" "python -m py_compile app.py"
python frameworks/operator-kit-1.8.4/scripts/done_gate.py "python -m pytest -q" --declare-partial "external target not yet validated"
python -m hpp evidence run --id ITEM-1 --artifact artifacts/pytest.xml --record-event -- python -m pytest -q --junitxml=artifacts/pytest.xml
python -m hpp event append --type evidence_recorded --data '{"work":"ITEM-1","ref":"artifacts/pytest.txt"}'
03
3. O que conta como prova
Uma prova é reproduzível por alguém que não estava lá. Ela carrega o comando, a saída ou o código de saída, a versão ou o commit contra o qual rodou, o escopo que cobriu e a hora em que rodou. Qualquer afirmação que careça de um dos cinco é o relato de uma crença.
Duas regras adicionais separam prova de luz verde. Um número é publicado com o instrumento que o produziu, para que a próxima pessoa possa re-derivá-lo em vez de confiar nele. E um instrumento só recebe confiança depois de um controle: apontado para um caso sabidamente bom e um caso sabidamente ruim, ele precisa responder de modo diferente. Um checker que diria "zero problemas" sobre uma árvore quebrada não pode ser usado para dizer "zero problemas" sobre uma boa.
O mesmo controle vale para os instrumentos em que um agente se apoia (todos novos na 2.6.0).
Um retriever só recebe confiança depois que o hpp retrieval eval o mediu em consultas rotuladas,
e um decisor só depois que o hpp decide eval o mediu, cada um com as falhas de instrumento
contadas à parte. Um relatório escrito a partir de fontes marca cada afirmação com o id da fonte
em que ela se apoia, e o hpp cite check prova que todo marcador resolve para uma fonte que o
relatório recebeu e sinaliza um número sem marcador; ele não prova que a fonte diz o que a frase
diz.
Quando um checker aprova, a aprovação fica vinculada a bytes. A attestation registra o hash da
spec, o commit base e um snapshot de todo arquivo rastreado e não rastreado. Se o checkout mudar
depois, a aprovação é bloqueada na verificação e precisa ser dada de novo sobre os novos bytes.
Por isso a evidência vem primeiro e a attestation por último. No seu próprio projeto, ponha .hpp/
no .gitignore: o event log e os registros de evidência são estado por checkout, e o snapshot
cobre todo arquivo não rastreado que não esteja ignorado, então sem essa linha um evento ou um
registro escrito depois do attest create bloqueia o attest verify.
python -m hpp cite check --text examples/citations/answer.md --context examples/citations/context.json
python -m hpp retrieval eval examples/retrieval/suite.json \
--retriever-command '["python", "examples/retrieval/keyword_retriever.py"]'
python -m hpp decide eval examples/typed-decisions/gotcha-family-suite.json \
--decider-command '["python", "examples/typed-decisions/baseline_decider.py"]'
python -m hpp attest create --repo . --spec SPEC.md \
--maker maker-a --checker checker-b --session review:001 \
--verdict approved --output .hpp/attestation.json
python -m hpp attest verify .hpp/attestation.json --repo .
04
4. Revise sem caneta
O checker é um ator diferente do maker, de preferência em outro provedor, e não tem ferramentas de
edição de arquivo: o host impõe a ausência de Write e Edit. O Bash continua disponível, então
somente leitura é uma promessa, verificada comparando a árvore de trabalho antes e depois da
revisão — o git status --porcelain capturado dos dois lados precisa bater, e um checker que
alterou a árvore invalida os próprios achados. Sem caneta, o checker não consegue "consertar e
seguir"; ele tem de apontar, com severidade, arquivo e linha, e o maker tem de responder. Esse
atrito é o mecanismo: é o que torna um defeito de processo visível em vez de remendado em silêncio.
O relatório do checker mantém os mesmos critérios entre rodadas, nomeia achados por um código estável em vez de por um adjetivo, e nunca pergunta ao maker algo que ele mesmo poderia verificar rodando um comando. Quando não há checker de outro provedor disponível, a revisão é registrada como não feita, não como feita pelo maker.
A aprovação do checker é um evento. O loop passa de evidenced a checked.
python multi-session/lane-kit-1.8.2/scripts/checker_router.py --maker claude --require
python -m hpp event append --type check_passed --data '{"work":"ITEM-1","checker":"checker-b"}'
05
5. Quando o orçamento acaba
Todo loop declara o próprio orçamento antes da primeira iteração: iterações, tempo e o que mais o host medir. Parar por orçamento é um de três fins legítimos, ao lado de "o critério passou" e "o circuit breaker disparou". Não é uma falha e não é motivo para tentar mais uma vez.
O driver de loop do módulo operator lê um charter com os critérios e um número máximo de iterações. Uma marca de conclusão na saída do modelo não libera o loop; o done gate é reexecutado pelo hook, fora do alcance do modelo, e só o código de saída dele conta. Quando o orçamento é atingido, o driver para, reporta onde parou e o que falta, e espera que uma pessoa estenda o orçamento ou feche o trabalho. Estender o orçamento é uma decisão humana, nunca algo que o loop concede a si mesmo.
python frameworks/operator-kit-1.8.4/hooks/loop_gate.py start \
--charter "<objective>" --criteria "python -m pytest -q" --max-iterations 10
python frameworks/operator-kit-1.8.4/hooks/loop_gate.py status
python frameworks/operator-kit-1.8.4/hooks/loop_gate.py cancel
06
6. Retome depois de uma interrupção
A fronteira de recuperação é o último evento registrado, não a última coisa de que alguém lembra.
Depois de uma queda, de um reset de contexto ou de um novo dia, o próximo passo é derivado do event
log: hpp status projeta o log sobre o loop e nomeia o estado; hpp resume devolve o próximo passo
como JSON. Nenhum dos dois consulta um resumo e nenhum pede ao modelo que reconstrua o que
aconteceu.
Um resumo ou handoff restaurado é material de referência. Antes de reexecutar qualquer coisa que ele descreva, a sessão confere se aquele passo já produziu o próprio efeito (um arquivo, um commit, um evento). Um passo que já rodou não roda de novo. Quando um passo foi aceito mas a confirmação se perdeu, ele é marcado como desconhecido junto com o comando que resolve a dúvida, e é inspecionado antes de ser tentado de novo.
python -m hpp status --json
python -m hpp resume
07
7. Todo gate nasce com um teste que o força a falhar
Um gate que só foi visto passando não se distingue de um gate que está faltando. Então um gate entra na base de código com duas provas. A primeira é um teste que o alimenta com o caso que ele existe para barrar e afirma que ele barra; se o gate fosse removido, esse teste falharia. A segunda é o call site: evidência de que o gate é de fato invocado no caminho vivo, não apenas definido.
A mesma regra vale para consertos de bug. O teste é escrito primeiro e rodado para vê-lo falhar pelo motivo certo; depois o conserto; depois o teste de novo. Um teste escrito depois do conserto prova que o código atual funciona, não que algo foi consertado. Quando os dois lados de um conserto produzem o mesmo resultado, a prova não discrimina, e a causa habitual é uma guarda anterior parando o caminho antes de ele chegar ao defeito.
O benchmark é esta regra aplicada ao próprio harness: dez controles, cada um com um caso positivo e
um negativo, rodados k vezes, com pass^k = 1.00 exigido.
python -m pytest tests/test_policy.py -q
python -m hpp eval run examples/reliable-coding/benchmark-suite.json -k 3 --gate both
python -m hpp benchmark -k 3 --json
08
8. Planeje antes de aplicar
Qualquer coisa que escreve mostra o plano primeiro e escreve numa segunda invocação explícita. O
plano lista o que existe no alvo e será preservado, o que seria escrito e o que continua sendo ação
humana. Nem hpp init nem o instalador de módulos bloqueiam num prompt de terminal; o "sim" é a
reinvocação com --apply, que funciona do mesmo modo para uma pessoa e para um agente agindo em
nome dela.
Settings, hooks e AGENTS.md nunca são escritos pelo harness. O bloco de wiring é impresso para
uma pessoa colar, e o doctor é rodado depois para confirmar o resultado.
python -m hpp init --target ../your-repo
python -m hpp init --target ../your-repo --apply
python installers/kit-forge-1.5.4/kit_doctor.py install --kit <module-dir> --host codex --target ../your-repo
python installers/kit-forge-1.5.4/kit_doctor.py install --kit <module-dir> --host codex --target ../your-repo --apply
python -m hpp doctor
09
9. Um gate humano onde a ação é irreversível
A política de comandos classifica o que um agente está prestes a rodar. Remoção recursiva em
qualquer ordem de flags, force push, push em main ou master, download encanado num shell e SQL
destrutivo são BLOCK. Qualquer push e qualquer transferência de saída são MANUAL: a ação pode
estar certa, e uma pessoa a confirma. No modo enforce o classificador devolve saída 2 e 1,
respectivamente; no modo audit ele reporta o veredito e sai com 0, para que um time consiga medir
falsos positivos antes de trocar de modo.
No loop, o gate humano é um evento. checked vira approved somente por meio de
human_approved, e o verified final exige que esse passo tenha acontecido.
python -m hpp policy check --mode audit --command "git push origin feature"
python -m hpp policy check --mode enforce --command "rm -rf build"
python -m hpp event append --type human_approved --data '{"work":"ITEM-1","by":"operator"}'
python -m hpp event append --type verified --data '{"work":"ITEM-1"}'
10
10. Deixe o grafo decidir a ordem
Escreva uma spec pequena o bastante para compilar
Uma spec é um objeto JSON com uma lista work. Cada unidade tem um id, uma lista depends_on
(vazia quando não depende de nada), ao menos um critério de acceptance escrito como algo que um
comando consegue decidir, e um tier. Uma unidade é pequena o bastante quando uma pessoa consegue
dizer, num comando, o que a fecha; quando uma unidade precisa de um parágrafo, ela é duas
unidades. O compilador não mede tamanho; mede forma. Dependências são escritas, não lembradas: uma
aresta que vive só na conversa não existe para o compilador. Ele recusa uma spec com id duplicado,
dependência desconhecida, lista de aceite vazia ou tier inválido, e diz qual unidade.
python -m hpp work plan SPEC.json
Leia as waves antes de começar
hpp work waves imprime as waves em ordem com as unidades de cada uma. Leia como o plano do dia:
tudo da wave 1 pode começar agora; nada da wave 2 pode começar até que toda unidade da wave 1
tenha passado nos próprios critérios. Uma wave com uma unidade só é um passo serial, e não há o
que fazer a respeito além de terminar a unidade. A ordem dentro de uma wave é alfabética, não é
prioridade: escolha qualquer ordem, ou todas de uma vez. Duas unidades na mesma wave ainda podem
tocar os mesmos caminhos; o grafo não conhece os arquivos, então as lanes são declaradas à parte,
e o Lane Map é o que reporta sobreposição entre territórios vivos e exclusivos.
python -m hpp work waves SPEC.json
python -m hpp map lane LANES.json --now "$(date +%s)"
Quando o grafo acusa ciclo
dependency cycle: a -> c -> b -> a quer dizer que a spec se contradiz: cada unidade do caminho
pede para rodar depois da seguinte, e nenhuma ordem satisfaz todas. O harness nomeia o caminho e
sai com 2; ele nunca descarta uma aresta para fazer a spec compilar. O conserto está na spec, e é
um de três: uma aresta foi declarada ao contrário, duas unidades são na verdade uma, ou uma
unidade faz duas coisas e a segunda metade pertence a uma unidade nova que depende da primeira.
Mude a spec e recompile; o compilador é o teste.
python -m hpp work waves SPEC.json
Decidir que uma unidade não pode rodar em paralelo
Uma unidade roda depois de outra somente quando uma aresta diz isso, e a aresta vale ser declarada
quando a unidade posterior lê o que a anterior produz: um schema antes do código que o usa, um
build antes da checagem que o executa, um rename antes das referências ao nome novo. Quando a
resposta é "elas tocam os mesmos arquivos", isso é pergunta de lane, não de dependência: dê às duas
unidades territórios disjuntos, ou faça uma depender da outra e aceite o passo serial. Uma vez
declarada, a aresta aparece em edges, da dependência para a unidade que espera por ela, com a
relação depends-on, e a unidade posterior passa para uma wave mais tarde; se ela não se moveu,
a aresta não é a que você queria.
python -m hpp work plan SPEC.json
11
O loop, de ponta a ponta
work_started ─▶ evidence_recorded ─▶ check_passed ─▶ human_approved ─▶ verified
[scope] [fresh-evidence] [read-only-checker] [human] [closure]
Cada seta é um evento acrescentado. Cada gate é uma condição que o harness checa antes de
acrescentar. hpp status diz onde você está; hpp resume diz o que vem a seguir.