English · Português
House Party Protocol

Architecture · ARCHITECTURE.md

Architecture

How the pieces of House Party Protocol fit together, where state lives, and what deliberately does not exist.

01

Four layers

┌──────────────────────────────────────────────────────────────────────────┐
│ HARNESS       python -m hpp                                              │
│               doctor · init · event · status · resume · attest · policy  │
│               work · route · context · map · graph · eval · benchmark    │
│               decide · evidence · retrieval · cite (new in 2.6.0)        │
│               deliberate (new in 2.7.0)                                  │
├──────────────────────────────────────────────────────────────────────────┤
│ PROTOCOL      hpp.manifest.json                                          │
│               roles · loop transitions and gates · exit codes            │
│               host coverage · monitors · bundles · invariants            │
├──────────────────────────────────────────────────────────────────────────┤
│ MODULES       ten versioned directories, each installable on its own     │
│               skills · hooks · commands · agents · rules · scripts       │
├──────────────────────────────────────────────────────────────────────────┤
│ DISTRIBUTION  Claude Code: marketplace.json + plugin per module          │
│               Codex CLI:   verified copy into .agents/ by the installer  │
│                            + plugin channel (skills only) for 7 modules  │
└──────────────────────────────────────────────────────────────────────────┘

The harness is stdlib-only and executes no model. It reads files, runs the commands you name, and writes to a small number of declared paths. decide, evidence, retrieval and cite are new in 2.6.0; deliberate is new in 2.7.0.

02

The hpp/ package

fileresponsibilitysurface
cli.pyargument parsing, dispatch, the exit contract (2 for a refused input, 3 for an internal error)every subcommand, --self-test
manifest.pylocate, load and validate the manifest; cross-check marketplace.json and each module's plugin.json when present, and the Codex marketplace (.agents/plugins/marketplace.json) with each listed module's .codex-plugin/plugin.jsondoctor
state.pyappend-only, hash-chained event log (each line links to the sha256 of the one before it; a broken link names its first divergent step, a log written before the chain reads as legacy); projection of events onto the loop; refusal of invalid transitions before the writeevent append, event verify, status, resume
workgraph.pyvalidate a spec, reject cycles with the path named, order units into waves; link each acceptance criterion to the tests that cite it and, with a JUnit XML report, to the tests that ran it; the plan and the waves as JSON or Mermaid, the coverage as JSONwork plan, work waves, work coverage
findings.pycheck the hpp.findings/v1 document a review lens answers with (stable code, severity, file and line, evidence, the universe inspected), refuse a key the contract does not define, and derive the verdictfindings check (new in 2.8.0)
policy.pyclassify a command as ALLOW, MANUAL or BLOCK; apply a project's .hpp/policy.json, which can only add rules or raise a class, never lower one; map to exit code by modepolicy check
attest.pybind a verdict to repo identity, base commit, spec hash and a full file snapshot; verify later; sign the record with an SSH key on request (--sign-key) and demand that signature on verify (--allowed-signers)attest create, attest verify
signing.pysign the canonical bytes of a record with ssh-keygen -Y sign (namespace hpp) and check them with ssh-keygen -Y verify against an allowed-signers file; without the binary nothing is signed and nothing is reported verifiedused by attest, evidence
hosts.pythe host x module x channel matrix derived from hosts and components in the manifest, rendered for the documentsdoctor --matrix
context.pyfit whole blocks under a character budget with provenance hashes; refuse secret-like inputcontext compile, map context
routing.pychoose a tier and a provider id from declared risk, complexity, context size and stage; fall back only upwardroute
maps.pyLane Map, Agent Map, Context Map and Monitor Map as sorted, data-only projections; JSON or Mermaidmap lane, map agent, map context, map monitor
graph.pycapability, operational, agent, evidence and code views from the manifest; JSON or Mermaidgraph
evals.pypass@k / pass^k runner over a suite of cases with three runner kindseval run, benchmark
decision.pyvalidate an hpp.decision/v1 record made outside the harness (advisory, raise-only, abstention and instrument failure as outcomes); measure a declared decider with selective metrics. Calls no modeldecide validate, decide eval (new in 2.6.0)
evidence.pyrun a declared criterion command (argv, no shell, its own process group), measure its exit code, hash the artifacts it declared and write a self-hashed record; re-derive a record later. Refuses a secret-like command line. The self-hash makes an edit visible; it is not a signature — --sign-key adds one, and verify --allowed-signers demands it. Drives no browserevidence run, evidence verify (new in 2.6.0); evidence mutate runs the criterion on a copy of the workspace, clean and once per mutant, and names the mutants it let through (new in 2.7.0)
retrieval.pyscore a retriever you declare as a command against the ids a suite labels relevant: hit@k, recall@k, precision@k, MRR, nDCG@k, with instrument failures counted apart. Runs no indexretrieval eval (new in 2.6.0)
citations.pycheck that every citation marker in a text resolves to one id of the context it was written from, and flag a quantitative sentence with no marker. Does not judge whether the source supports the sentencecite check (new in 2.6.0)
deliberation.pyHouse Session: validate a panel of pinned deciders (two model families, a judge outside the participants' lanes, the roles its session type needs, a declared budget), count its turns (grounded and ungrounded votes, abstentions, seats not judged, moves without new evidence; with declared evidence, only references that resolve ground a fact), stop it by rule, and seal it with the judge's decision and, over grounded dissent, the judge's steelman as a self-hashed record that re-derives from its own turns; project the panel as one hpp.decision/v1. Calls no modeldeliberate plan, validate, tally, stop, record, verify (new in 2.7.0)
controls.pythe ten executable controls the benchmark runs, each with a positive and a negative casebenchmark, --self-test
install.pyplan-only installation receipt for a bundle on a host; refuses unsupported coverageinstall
wizard.pyhpp init: six stages, readiness, plan versus apply, wire blockinit
term.pycolour tier detection, ANSI output, ASCII glyph fallback, no-TTY behaviourused by init
brand.pypalette, block wordmark and closing lines for the terminalused by init

policy.py holds eight fixed rules; the first match wins and every BLOCK rule is tried before any MANUAL rule. BLOCK: recursive-delete, force-push, main-push, pipe-to-shell, destructive-sql. MANUAL: external-push, external-send, decision-advisor (new in 2.6.0). Anything else is ALLOW. The mode changes only the exit code: audit exits 0 for every verdict, enforce exits 2 on BLOCK and 1 on MANUAL. What each rule matches is in CONCEPTS.md; check one with python -m hpp policy check --mode enforce --command "rm -rf src" (exit 2).

A project hardens the classifier with .hpp/policy.json (hpp.policy/v1, read by policy check from the current directory, or named with --policy FILE): rules of its own with a pattern, a class (BLOCK or MANUAL) and a reason, and built-in MANUAL rules raised to BLOCK. A file that would exempt anything — a rule with action ALLOW, a raise that keeps or lowers a class, a rule that reuses a built-in id — is refused as a whole, and a refused policy refuses the check (exit 2, no verdict printed) instead of falling back to the built-ins. Every verdict says its source (built-in or policy).

Sizes are small by design; the whole package is readable in one sitting. Nothing imports outside the standard library.

03

The flow

  spec (JSON)
     │  hpp work plan
     ▼
  WorkGraph ──── waves ───▶ wave 1 ──▶ wave 2 ──▶ ... (barrier between waves)
     │                        │
     │  hpp route             │  per unit: tier → provider id (never a model call)
     │                        ▼
     │                     lane claim ◀── hpp map lane (liveness from heartbeat)
     │                        │
     │                        ▼
     │                     work_started ─▶ evidence_recorded ─▶ check_passed
     │                        │                 ▲                    │
     │                        │      hpp context compile             │
     │                        │      (what the maker saw, hashed)    ▼
     │                        │                              hpp attest create
     │                        │                              (verdict bound to bytes)
     ▼                        ▼                                      │
  hpp map monitor        .hpp/events.jsonl  ◀── human_approved ◀─────┘
  (up · fresh · skew)         │
                              ▼
                          verified  ── hpp status / hpp resume derive the next step

Every arrow that changes state is an appended event. Every box that decides something reads files and writes at most one declared file.

04

Where state lives

pathwritten bycontentlifetime
.hpp/events.jsonlhpp event appendone JSON object per line: seq, id, type, data and prev_sha256, the sha256 of the line before it (the first links to the hash of nothing); append-only and hash-chained — hpp event verify names the first divergent step, and lines written before the chain read as legacythe workspace's loop history
.hpp/policy.jsonthe operator, by handhpp.policy/v1: rules that add BLOCK or MANUAL, and built-in MANUAL rules raised to BLOCK; read by hpp policy check, refused as a whole if it would exempt anythinguntil the operator changes it
.hpp/profile.jsonhpp init --applyhost, bundle, modules, policy mode, protocol and product version; decision_advisor only when one was declareduntil the operator removes it
.hpp/attestation.jsonhpp attest create --outputthe bound verdict; the path is yours to chooseuntil the bytes it describes change
.hpp/evidence/<id>-<UTC>.jsonhpp evidence run (new in 2.6.0)one record per run, created exclusively: the command, base commit, exit code, verdict, byte count and sha256 of stdout and stderr (never the text), the sha256 of every declared artifact, and the record's own hash; --out picks another directory inside the workspaceuntil the operator removes it; evidence verify blocks it once an artifact it names changes
hpp.manifest.jsonthe projectthe protocol; found by walking up from the current directory, then next to the source package, then the copy shipped inside the installed packageversioned with the product
module CHECKSUMS.txtthe forgesha256 per distributed fileversioned with each emitted module

Everything else the harness consumes is an input you pass on the command line: specs, lanes, monitors, providers, routing requests, context blocks, evaluation suites. There is no hidden cache, no user-level state directory and no environment variable that changes a verdict; the only environment the harness reads is NO_COLOR, FORCE_COLOR, TERM, COLORTERM, CI and the Windows terminal session flag, and those affect rendering only.

The event log is the recovery boundary. Reading it validates that seq is contiguous and that id equals event:<seq>; a corrupt line stops the projection with the line number instead of skipping it. Appending validates the transition against the manifest before opening the file, so a refused event leaves no trace and cannot create the log.

05

The manifest as executable contract

hpp.manifest.json declares the protocol version, the hosts, the modules with their paths, versions, capabilities, relations and per-host coverage, the bundles, the roles, the exit codes, the routing tiers and rule, the map names, the installer path, the monitors and the loop.

hpp doctor rejects a manifest that violates any of: unique hosts, modules, capabilities, paths and monitor ids; relative module paths; coverage values outside native / explicit-command / unsupported; unknown relations; cycles in requires; bundles naming unknown modules or capabilities; exit codes other than 0/1/2/3; routing tiers other than economy/balanced/frontier; monitors with non-positive cadence or freshness.

When marketplace.json sits beside the manifest, the doctor also checks that the product version matches, that the module set matches, that each module's source and version match, that the module directory exists inside the product root, and that its .claude-plugin/plugin.json agrees on name and version. In a source checkout without a marketplace the doctor says so (distribution: source-contract) instead of reporting a check it could not run.

06

Maps

Maps have no state of their own. Each is a projection of one or two inputs, sorted so that the same input yields the same output byte for byte.

mapinputquestion it answers
capabilitymanifestwhich module provides which capability, on which host, in which bundle
operationalmanifest loopwhich event moves which state through which gate
agentmanifest roles and modules, optional eventswho may make, check and approve; what has happened in order
evidencefixedhow a criterion, a record and a verdict relate
codemanifest componentswhich surfaces each module declares (not an AST)
lanelanes JSON, --now, thresholdswho owns what, who is alive, where live claims overlap
contextcontext JSON, --budgetwhat entered the compiled context, what was omitted, with hashes
monitormonitors JSON, --now, --skew-tolerancewhat is observed, how fresh, and which gate consumes it
workspec JSONwhich units can run together and in which order

The Context Map is provenance for one compilation; it is not retrieval and not a knowledge base. The Code Map inventories declared components; it is not a call graph.

07

Two hosts

capabilityClaude CodeCodex CLI
skills and instructionsplugin, nativecopied into .agents/skills, namespaced
lifecycle hooks (Stop, PreToolUse, SessionStart, ...)native once wired by a personthe host has its own; HPP's are not loaded there, and the same scripts run as explicit commands
command policyhook plus CLICLI or preflight
event log, attestation, maps, WorkGraph, evalCLICLI
installmarketplace plugin, or installer copy for explicit-command modulesinstaller copy into .agents/hpp/<module>

The host seam is a small table: settings path and manual gates for Claude Code; AGENTS.md, skills path and runtime path for Codex CLI. hpp init uses it to name the same paths the module installer will touch, and touches none of them. Adding a host means adding a row and a coverage value per module, not changing the six stages.

Hosts and channels

One row per module, rendered from hosts and components in the manifest by hpp doctor --matrix (hpp/hosts.py); tests/test_host_matrix.py keeps this table equal to that render.

moduleClaude CodeCodex CLI
operator-kit 1.8.4native · plugin + hooks after the wiring is pastedexplicit-command · plugin (skills only) + verified copy
kit-forge 1.5.4explicit-command · pluginexplicit-command · verified copy
lane-kit 1.8.2native · plugin + hooks after the wiring is pastedexplicit-command · plugin (skills only) + verified copy
continuity-kit 1.5.3native · plugin + hooks after the wiring is pastedexplicit-command · plugin (skills only) + verified copy
health-kit 1.4.4explicit-command · pluginexplicit-command · plugin (skills only) + verified copy
claude-dev-kit 1.3.8native · pluginunsupported · —
supabase-pack 1.2.3explicit-command · pluginexplicit-command · plugin (skills only) + verified copy
agent-framework-wizard 1.2.5explicit-command · pluginexplicit-command · verified copy
dev-squad-kit 1.2.2native · pluginexplicit-command · plugin (skills only) + verified copy
gotcha-memory 1.1.4native · plugin + hooks after the wiring is pastedexplicit-command · plugin (skills only) + verified copy

On Claude Code every module is a plugin of the marketplace, and a module that declares hooks arms them once the wiring is pasted. On Codex CLI the plugin channel is the product's own marketplace, .agents/plugins/marketplace.json (codex plugin marketplace add rusharlabs/house-party-protocol, then codex plugin add <module>@house-party-protocol): it lists the modules that carry skills and are supported there, and it installs skills only — the hooks are Claude Code's, and the verified copy (kit_doctor.py install --host codex) is what installs runtime, scripts and templates. Each module's .codex-plugin/plugin.json carries an empty hooks object on purpose: with no hooks key, Codex loads the module's hooks/hooks.json by default.

08

Distribution

The published tree is emitted by the forge from module sources. Each module directory carries CHECKSUMS.txt (sha256 per file) and a .zip with the same bytes; marketplace.json lists the modules with source and version; .agents/plugins/marketplace.json lists, in the form Codex CLI reads, the modules of the Codex plugin channel, each carrying a .codex-plugin/plugin.json rendered from its Claude one, and hpp doctor cross-checks the two channels against the manifest (distribution.codex_marketplace); the installer (installers/kit-forge-<version>/kit_doctor.py) runs the six install stages, verifies checksums and executes the module's declared smokes.

sources ──forge──▶ module dir + CHECKSUMS.txt + .zip ──▶ marketplace.json
                        │                                     │
                        └── kit_doctor.py verify ◀────────────┘── hpp doctor (cross-check)

This repository is that published tree: the module directories with their CHECKSUMS.txt, the installer and marketplace.json sit beside the harness, so hpp init verifies distribution integrity and module checksums here (9 of 11 readiness items). Two other layouts carry less and say so: the pip wheel ships the harness, the manifest and the benchmark suite but no module directories, and the harness source tree has none of the emitted artefacts; in both, the doctor, the benchmark and the suite run, and distribution integrity and module checksums are reported as not verified rather than assumed.

09

Exit contract

codemeaningwhere
0ok; in audit mode, every verdict (a refused policy file still exits 2)every command
1warn or manual gate; an eval gate that failedpolicy check (MANUAL in enforce), eval run, benchmark, init with warnings, decide eval when its gate fails (new in 2.6.0), evidence run when the bundle did not pass, evidence verify on an intact record of a run that did not pass, retrieval eval when its gate fails, cite check with a warning (TOO_MANY, UNCITED_CLAIM) (new in 2.6.0), deliberate record when the verdict is blocked or the judge failed, evidence mutate with a blind spot, an incomplete run or no mutant applied (new in 2.7.0), work coverage when a criterion is not covered or, with --junit, not executed, findings check when a lens found something (new in 2.8.0)
2block; a refused input (bad manifest, bad spec, corrupt log, invalid attestation)policy check (BLOCK), attest, decide validate and decide eval on a record or suite that breaks the contract (new in 2.6.0), evidence run on a refused request or an event it could not append, evidence verify on a record that was edited or contradicts itself or whose artifact changed or went missing, retrieval eval on a refused suite or argument, cite check on UNKNOWN_ID, RANGE or EMPTY_MARKER or a refused input (new in 2.6.0), deliberate on a refused panel, turn or judge, a session that has not stopped, or a record that does not verify, evidence mutate when the criterion does not pass on the clean copy (no-control) or a mutant is refused (new in 2.7.0), work coverage on a refused report or a test path with no Python source, findings check on a document that breaks the contract (new in 2.8.0), event verify and status on a broken hash chain, policy check on a refused policy file, attest verify and evidence verify with --allowed-signers on a record whose signature is missing, not accepted or over changed bytes, every validation error
3usage or internal errorinit usage errors, unexpected exceptions

hpp init reports the code it will return inside its JSON report (exit_code) and halts the remaining stages when a stage fails with a blocking hint.

10

Tests and CI

The suite under tests/ is stdlib-only and runs without network. Each test file carries at least one test named CONTROLE that proves the file can fail. In this repository one test skips by design (the emitted copy is covered by the next test); the harness source tree, which has no marketplace.json, skips the two tests that need it. CI runs the suite, hpp doctor and hpp benchmark -k 3 on Linux, macOS and Windows across Python 3.10 to 3.13, and a lint job runs ruff check hpp tests scripts against the rule set in pyproject.toml, with ruff installed from a hash-pinned requirements file; every job has read-only permissions and no step is allowed to fail silently.

python -m pytest tests -q
python -m hpp doctor
python -m hpp benchmark -k 3

11

What deliberately does not exist

absentwhy it is a choice
daemon or background servicea check that did not run was not run; a resident process would be a second thing to verify and would hold state the log cannot see
server, API or control planethe harness is operated from the repository it protects; a remote plane would move the verdict away from the bytes it is about
scheduler or queuecadence in the Monitor Map is declared for the consumer to honour; the harness does not wake itself up
graph databaseevery map is re-derivable from files; a stored graph would drift from its sources and require its own doctor
model executionrouting returns a tier and a provider id; calling a model would make verdicts depend on something the harness cannot reproduce or afford to hold credentials for
credential storagethere is none to leak; secret-like input to the context compiler is refused, and the failure memory redacts by shape
telemetrynothing leaves the machine; git push, curl/wget to a URL and the example decision adapter are classified MANUAL (a transfer by another tool, such as scp, matches no rule)
automatic settings or hook wiringenabling a hook changes what runs on every future tool call; that is a human action, printed for pasting
token countingthe budget is in characters, which every host measures the same way; a token count would tie the harness to a tokenizer

A layer is added only when there is a consumer for it and a benchmark that shows what it costs. Until then, the absence is the feature.