English · Português
House Party Protocol

Method · METHOD.md

Method

The way of working that the harness enforces. A team can adopt it in a day, because every practice ends in a command that either passes or reports what it measured. The commands below use the harness CLI (python -m hpp) and, where a module is involved, the scripts that module installs. Module paths are shown relative to the emitted distribution.

01

1. Open a unit of work

Work starts as a spec, not as a conversation. A spec is a list of units, each with an id, its depends_on, at least one acceptance criterion written as something a command can decide, and a tier that states how much risk it carries. The harness compiles the spec into a WorkGraph and derives waves from the dependencies. Units in the same wave may run in parallel; a unit whose dependencies have not closed does not start.

Before touching files in a shared repository, the session claims a lane: an id, the paths it will own, and a heartbeat it will keep updating. Two live lanes may not overlap on exclusive territory. A lane that stops heartbeating loses its claim by time, not by someone deleting a lock.

Then the first event is recorded, and the loop leaves 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 done

"Done" is a state the harness enters, not a word the agent says. It requires the acceptance criteria to run as commands and exit 0, in the current checkout, now. The done gate takes the criteria as arguments, runs each one, and prints DONE-GATE: DONE only when all pass. When some criterion cannot pass yet, the correct output is a declared partial: the gate accepts --declare-partial "<what is missing>" and reports PARCIAL-DECLARADO with that text and a non-zero exit. Without the declaration it reports NOT-DONE. Silence is not a state; an undeclared partial is a failure.

Once the criteria pass, the evidence is recorded in the event log with a reference to where the output lives. The primary way is hpp evidence run --record-event (new in 2.6.0): it runs the criterion itself, measures the exit code, hashes the files declared with --artifact, writes a record under .hpp/evidence/, and appends evidence_recorded only when that bundle passed. It needs at least one --artifact, because an exit code alone leaves nothing to re-derive later. On a release without it, the same event is appended by hand with hpp event append, and it then carries only what the person wrote. The loop moves to evidenced. It cannot move to verified without at least one such record.

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. What counts as proof

A proof is reproducible by someone who was not there. It carries the command, the output or exit code, the version or commit it ran against, the scope it covered, and the time it ran. Any claim that lacks one of the five is a report of a belief.

Two further rules separate proof from a green light. A number is published with the instrument that produced it, so that the next person can re-derive it instead of trusting it. And an instrument is trusted only after a control: pointed at a case known to be good and a case known to be bad, it must answer differently. A checker that would say "zero problems" about a broken tree cannot be used to say "zero problems" about a good one.

The same control applies to the instruments an agent leans on (all new in 2.6.0). A retriever is trusted only after hpp retrieval eval has measured it on labelled queries, and a decider only after hpp decide eval has, each with instrument failures counted apart. A report written from sources marks each claim with the id of its source, and hpp cite check proves that every marker resolves to a source the report was given and flags a number with no marker; it does not prove that the source says what the sentence says.

When a checker approves, the approval is bound to bytes. The attestation records the spec hash, the base commit and a snapshot of every tracked and untracked file. If the checkout changes afterwards, the approval is blocked on verification and must be given again on the new bytes. The evidence therefore comes first and the attestation last. In your own project, list .hpp/ in .gitignore: the event log and the evidence records are per-checkout state, and the snapshot covers every untracked file that is not ignored, so without that line an event or a record written after attest create blocks 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. Review without a pen

The checker is a different actor from the maker, preferably on a different provider, and holds no file-editing tools: the host enforces the absence of Write and Edit. Bash remains available, so read-only is a promise, verified by comparing the working tree before and after the review — git status --porcelain captured on both sides must match, and a checker that changed the tree invalidates its own findings. Without a pen, the checker cannot "fix it and move on"; it has to point, with severity, file and line, and the maker has to answer. That friction is the mechanism: it is what makes a process defect visible instead of silently patched.

The checker's report keeps the same criteria across rounds, names findings by a stable code rather than an adjective, and never asks the maker something it could verify itself by running a command. When no checker from a different provider is available, the review is recorded as not done, not as done by the maker.

The checker's pass is an event. The loop moves from evidenced to 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. When the budget runs out

Every loop declares its budget before the first iteration: iterations, time, and whatever the host meters. Stopping on budget is one of three legitimate ends, next to "the criterion passed" and "the circuit breaker tripped". It is not a failure and it is not a reason to try one more time.

The loop driver in the operator module reads a charter with the criteria and a maximum number of iterations. A completion tag in the model's output does not release the loop; the done gate is re-run by the hook, outside the model's reach, and only its exit code counts. When the budget is reached, the driver stops, reports where it stopped and what remains, and waits for a person to extend the budget or close the work. Extending the budget is a human decision, never something the loop grants itself.

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. Resume after an interruption

The recovery boundary is the last recorded event, not the last thing anyone remembers. After a crash, a context reset or a new day, the next step is derived from the event log: hpp status projects the log onto the loop and names the state; hpp resume returns the next step as JSON. Neither consults a summary and neither asks the model to reconstruct what happened.

A restored summary or handoff is reference material. Before re-running anything it describes, the session checks whether that step already produced its effect (a file, a commit, an event). A step that already ran is not run again. When a step was accepted but its confirmation was lost, it is marked as unknown together with the command that resolves the doubt, and it is inspected before it is retried.

python -m hpp status --json
python -m hpp resume

07

7. Every gate is born with a test that forces it to fail

A gate that has only been seen passing cannot be distinguished from a gate that is missing. So a gate enters the codebase with two proofs. The first is a test that feeds it the case it exists to stop and asserts that it stops; if the gate were removed, this test would fail. The second is the call site: evidence that the gate is actually invoked on the live path, not merely defined.

The same rule applies to bug fixes. The test is written first and run to see it fail for the right reason; then the fix; then the test again. A test written after the fix proves that the current code works, not that anything was fixed. When both sides of a fix produce the same result, the proof does not discriminate, and the usual cause is an earlier guard stopping the path before it reaches the defect.

The benchmark is this rule applied to the harness itself: ten controls, each with a positive and a negative case, run k times, with pass^k = 1.00 required.

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. Plan before apply

Anything that writes shows its plan first and writes on a second, explicit invocation. The plan lists what exists in the target and will be preserved, what would be written, and what stays a human action. Neither hpp init nor the module installer blocks on a terminal prompt; the "yes" is the re-invocation with --apply, which works the same for a person and for an agent acting on their behalf.

Settings, hooks and AGENTS.md are never written by the harness. The wire block is printed for a person to paste, and the doctor is run afterwards to confirm the result.

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. A human gate where the action is irreversible

The command policy classifies what an agent is about to run. Recursive deletion in any flag order, force pushes, pushes to main or master, piping a download into a shell and destructive SQL are BLOCK. Any push and any outbound transfer are MANUAL: the action may be right, and a person confirms it. In enforce mode the classifier returns exit 2 and 1 respectively; in audit mode it reports the verdict and exits 0, so a team can measure false positives before switching.

In the loop, the human gate is an event. checked becomes approved only through human_approved, and the final verified requires that step to have happened.

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. Let the graph decide the order

Write a spec small enough to compile

A spec is a JSON object with a work list. Each unit has an id, a depends_on list (empty when it depends on nothing), at least one acceptance criterion written as something a command can decide, and a tier. A unit is small enough when one person can say, in one command, what closes it; when a unit needs a paragraph, it is two units. The compiler does not measure size; it measures shape. Dependencies are written, not remembered: an edge that lives only in the conversation does not exist for the compiler. It refuses a spec with a duplicate id, an unknown dependency, an empty acceptance list or an invalid tier, and says which unit.

python -m hpp work plan SPEC.json

Read the waves before starting

hpp work waves prints the waves in order with the units of each. Read it as the plan of the day: everything in wave 1 may start now; nothing in wave 2 may start until every unit of wave 1 has passed its criteria. A wave with a single unit is a serial step, and there is nothing to do about it except finish the unit. The order inside a wave is alphabetical, not a priority: pick any order, or all at once. Two units in the same wave may still touch the same paths; the graph does not know the files, so lanes are declared separately, and the Lane Map is what reports an overlap between live, exclusive territories.

python -m hpp work waves SPEC.json
python -m hpp map lane LANES.json --now "$(date +%s)"

When the graph reports a cycle

dependency cycle: a -> c -> b -> a means the spec contradicts itself: each unit in the path asks to run after the next one, and no order satisfies all of them. The harness names the path and exits 2; it never drops an edge to make the spec compile. The fix is in the spec, and it is one of three: an edge was declared backwards, two units are really one, or a unit does two things and the second half belongs to a new unit that depends on the first. Change the spec and recompile; the compiler is the test.

python -m hpp work waves SPEC.json

Deciding that a unit cannot run in parallel

A unit runs after another only when an edge says so, and the edge is worth declaring when the later unit reads what the earlier one produces: a schema before the code that uses it, a build before the check that runs it, a rename before the references to the new name. When the answer is "they touch the same files", that is a lane question, not a dependency: give the two units disjoint territories, or make one depend on the other and accept the serial step. Once declared, the edge appears in edges, from the dependency to the unit that waits for it, with the relation depends-on, and the later unit moves to a later wave; if it did not move, the edge is not the one you meant.

python -m hpp work plan SPEC.json

11

The loop, end to end

work_started ─▶ evidence_recorded ─▶ check_passed ─▶ human_approved ─▶ verified
   [scope]        [fresh-evidence]   [read-only-checker]   [human]        [closure]

Each arrow is an appended event. Each gate is a condition the harness checks before the append. hpp status tells you where you are; hpp resume tells you what comes next.