Seams and modules¶
The package split (phase 2, 2026-09-02) turned one 3,300-line file into modules with named seams. The seams are the point: each is a place where a later implementation, a test double, or a port in another language can stand in without the rest noticing. Development lists every file; this page lists what each seam promises.
The seams¶
| Seam | Where | Promise |
|---|---|---|
Project |
holophyte/config/project.py |
Everything about where a project's state lives, as a value: repository path, state directory, store path, config path. No module-level globals name a project; a function that needs one takes it. Two projects can exist in one process, which is what the tests, the daemon and a future port need. |
Board |
provider.py |
The board as a protocol: the loop's members (claim_next, fetch_task, set_state, comment, labels and the rest) and the filing members file, update, stored_body. board_for() builds a project's board from [board] kind; LinearBoard lazily imports the GraphQL module; FileProvider reads a directory of <ID>.md files for tests and offline runs. Neither the loop nor --file-ticket names Linear. |
store.read |
store/read.py |
Typed, read-only views; the only SQL outside store/__init__.py. Every consumer that renders state (report, sweep, findings, serve) goes through it. |
runs |
holophyte/loop/runs.py |
The loop's store seam: open_store, set_phase, heartbeat_while, record_round, warn_on_run, review_round_cap. Six helpers, so a wiring change extends one file instead of threading SQL through the loop. |
gates |
holophyte/loop/gates.py |
Worktree cutting and reuse, the verify gate, process-group reaping. Takes a project and a ticket, returns a red or green report. |
roles |
holophyte/agents/roles.py |
agent_route() (which command, which model, from [agents]) and agent() (one turn of a role in a process group with a budget). The implementer and the reviewer are both routes; review_runner is the reviewer's transport. |
reply_parsing |
holophyte/review/reply_parsing.py |
Reviewer prose in, structured findings and a verdict out: the CRITERION Cn: checklist parser, the witness-test resolver, the finding key. |
findings |
holophyte/review/findings.py |
The FINDINGS.md window renderer, byte-stable, from EndedRun and ReviewRound rows only. |
board |
holophyte/board/projection.py |
Linear as a notice board: mirror a ticket into the store with its contract snapshot, push status, detect drift at merge, escalate a twice-failed ticket, file and update tickets from files. |
reexec |
holophyte/loop/reexec.py |
Replace the process with the same command line, through an EXEC seam tests can intercept, shared by the loop and the project daemon; and systemctl --user on the deploy units (the loop's, the sweep's), shared by the supervisor and the daemon's actions. |
host |
holophyte/host/registry.py |
The host registry, host.toml: the projects the host daemon serves and the host sweep watches, by path, re-read when it changes; written by project add and project remove alone. A route name resolves through Host.project() alone. Opens no store. |
config |
holophyte/config/reader.py, holophyte/config/checks.py, holophyte/config/agent_settings.py, holophyte/config/worktree_settings.py, holophyte/config/serve_settings.py, holophyte/config/config_tables.py |
Every config.toml table as a typed value with defaults, validated at startup; unknown keys are startup errors. |
What depends on what¶
flowchart TB
cli[cli] --> loop
cli --> supervisor
cli --> serve
cli --> report
cli --> board
loop --> gates
loop --> agents
loop --> runs
loop --> board
loop --> findings
loop --> reexec
loop --> pr
loop --> babysitter
babysitter --> pr
pr --> findings
pr --> gates
pr --> read
files --> gates
serve --> files
config -.lazy.-> pr
supervisor --> reexec
supervisor --> report
agents --> review_runner[review_runner]
runs --> review
runs --> store
gates --> store
board --> provider
board --> ticket_template[ticket_template]
provider -.lazy.-> linear_provider[linear_provider]
findings --> read[store.read]
report --> read
serve --> read
supervisor --> read
supervisor --> store
read --> store
loop --> config
supervisor --> config
serve --> config
gates --> config
agents --> config
board --> config
cli --> serve_host
cli --> sweep_host
serve_host --> serve
serve_host --> host
sweep_host --> supervisor
sweep_host --> host
everything[every module] --> project[Project]
Arrows point at what a module imports. The three modules the PR merge mode
and the console brought: github.py (the push, the pull request and its merge
API; imports store.read, findings and gates), babysitter.py (the
thread verdicts of a PR pass; imports github.py) and files.py (touched-file
counts read from git for the daemon; imports gates, and is imported by
serve). config imports github.py lazily, at the startup route check only.
Three rules hold the graph in this shape: serve reads through
store.read, and its action endpoints (serve_actions) write only through
the store API (store.record_intervention(), store.requeue(),
store.operator_notes.send_back());
holophyte.config never imports factory or the loop (no cycles); and
nothing outside store/ writes SQL. The host forms sit on top of the
project ones: serve_host is serve's handler under a /projects/NAME
prefix, one Project per registry entry, and sweep_host runs the
supervisor's sweep and reconcile per store, bounded by deadline.
Configuration as the second seam¶
Everything an operator would otherwise patch is a config.toml table on
the project, read at startup and refused if unknown:
| Table | Chooses |
|---|---|
[agents] |
the implementer, reviewer and adjudicator commands |
[worktree] |
setup commands run in each fresh worktree and their cap |
[supervisor] |
stale threshold, strikes, time-box grace, review-overlap threshold, sweep interval, restart grace |
[loop] |
stop on failure; claim order by identifier or priority; spawn_supervisor; the review-round cap from review_rounds, review_rounds_per_lines and review_rounds_max; workers, the pool's ceiling |
[board] |
the Linear project, or board, and team this project claims from |
[report] |
the host label rendered instead of the machine name |
[merge] |
approve (auto or human), mode (local or pr) and pr_rounds, the babysitter-pass cap |
[console] |
daemons, the HOST:PORT peers the console page fans out to |
[serve] |
name, the route and unit name; token_file, the bearer token a project daemon reads for a non-loopback bind; the opt-ins |
The host's own file, host.toml, is the third: the registry's
[[project]] paths, and the host daemon's bind, machine token and
actions, the host sweep's sweep_sec and the console's daemons
(The host registry).
Config has each with a commented example.
What a port would replace¶
The store schema and the ticket template are the cross-language contracts.
A Rust daemon replaces server.py against the same store; a Rust verify
gate replaces gates.py with the same clause-by-clause report; the Python
test suite run against the other binary is the acceptance oracle. That
ordering is why the seams came first.