Skip to content

Development

The package map as it is at merge time, the tests, and linting. Back to the README.

Files

The holophyte/ package is the factory; factory.py is its entry point. Each module, one line:

  • holophyte/__init__.py — the package docstring: which module owns what.
  • holophyte/loop/__init__.py — the loop package; empty.
  • holophyte/pr/__init__.py — the pr package; empty.
  • holophyte/babysit/__init__.py — the babysit package; empty.
  • holophyte/review/__init__.py — the review package; empty.
  • holophyte/story/__init__.py — the story package; empty.
  • holophyte/board/__init__.py — the board package; empty.
  • holophyte/isolation/__init__.py — the isolation package; empty.
  • holophyte/agents/__init__.py — the agents package; empty.
  • holophyte/host/__init__.py — the host package; empty.
  • holophyte/serve/__init__.py — the serve package; empty.
  • holophyte/config/__init__.py — the config package; empty.
  • holophyte/cli/__init__.py — the cli package; empty.
  • holophyte/cli/cli_project.py — project registration, listing and admission commands.
  • holophyte/cli/cli_story.py — the --file-story SLUG, --approve-story KEY-n and --witness-pass KEY-n options and their dispatch.
  • holophyte/story/story_approval.py — approve() approves a planned story: the one-open-story and revision checks, the baseline witness run at main's tip, then the frozen plan and its children released to Ready in one store transaction.
  • holophyte/story/story_filing.py — files a validated story directory on a native board: its parent, its children in dependency order and its story rows in one store transaction, then the header lines in its files.
  • holophyte/host/registry.py — the host registry, HOLOPHYTE_HOME/host.toml: the projects a host serves and sweeps, by path, reloaded when the file changes; its one writer path is an exclusive temporary file and a rename.
  • holophyte/cli/entry.py — cli(), the entry point: parses the command line and dispatches to the mode it names, the loop itself included.
  • holophyte/cli/arguments.py — build_parser(), every flag and its help, and the checks that refuse a modifier without its mode.
  • holophyte/cli/store_verbs.py — the operator verbs that write the store: --requeue, --approve, --babysit, --repoint, --close, --abort and the rest.
  • holophyte/cli/board_verbs.py — the modes on the built board: --sweep [--act], --board-diff, --board-import, --move, --cancel, and require_board().
  • holophyte/cli/host_modes.py — the host forms of --status, --serve and --supervise, the read-only modes, and start_supervisor().
  • holophyte/holo/__init__.py — the holo command's package.
  • holophyte/holo/cli.py — main(), the holo console script pyproject.toml declares: --version prints the package version and the checkout's short HEAD; a subcommand resolves its project, translates through the grammar table and runs as _legacy_cli(), a write command through run_write(), report through report_page, or answers as a read; holo project is factory.py project.
  • holophyte/holo/completion.py — holo completion bash|zsh|fish's scripts and the hidden holo __complete helper they call: candidates from the parser the grammar table builds, and ticket keys from holo board --json cached per project for 60 seconds.
  • holophyte/holo/follow.py — holo follow: polls the project's narrative run events and ledger entries from a starting time by store id, and the ledger view's schema migrations, prints each new one once, oldest first, as a line or a JSON object, and one ✗ line when a live run's or the supervisor's heartbeat goes stale, once until it recovers.
  • holophyte/holo/reads.py — the holo reads runs, run N, attention, board and ticket KEY: each calls the daemon's view in process, prints its body for --json or a compact listing of it, and maps its status to the exit.
  • holophyte/holo/resolve.py — a holo command's project from -p, HOLO_PROJECT, the current repository or client.toml's default_project, the client config reader, and the refusal when none answers.
  • holophyte/holo/grammar.py — the holo command table: one canonical row per factory.py mode, the send-back, steer, start and stop rows, the interventions actions each write row records, the ticket VERB and loop start|stop aliases, and their translation to the argv build_parser() parses; and the read rows, run show N among them.
  • holophyte/holo/mcp_server.py — holo mcp: the MCP SDK's low-level Server on stdio, listing the tool table's rows and answering a call by running its holo command; the SDK is imported only here and in the HTTP form below, and without it holo mcp exits 1 naming the package and its install command.
  • holophyte/holo/mcp_http.py — holo mcp --http: the same server as the SDK's stateless Streamable HTTP app under uvicorn, a process of its own, not the daemon; a guard before it demands the host's machine token (401 {}), refuses an Origin (403) and every method but POST (405); the writes are listed only under host.toml's [serve] actions, and it exits 0 when the checkout's HEAD moves.
  • holophyte/holo/mcp_tools.py — the holo mcp tool table: each tool's name, input schema, tier and the holo ... --json argv it runs as a subprocess with a timeout, and its result, a JSON object or text, or an error carrying the command's message.
  • holophyte/holo/results.py — a write command's one result: the verb run with its printed line and refusal captured, the interventions row it wrote read back as recorded, printed as JSON with --json or as a ✓/✗ line.
  • holophyte/holo/transport.py — a holo command run on client.toml's host over ssh: the remote command line, the ssh call, its exit code and the via line; holo follow's JSON lines rendered as each arrives and holo status --watch's status object asked for each frame; HOLO_TRANSPORT=local keeps it local. With transport = "http", the route table of the commands the host daemon serves, the bearer call to url, and the refusal of every other command.
  • holophyte/holo/render.py — the pieces every holo page shares: a clock time in the client's zone, a relative age, a short hash, and a symbol coloured only on a terminal without NO_COLOR.
  • holophyte/holo/status_page.py — holo status without --json: the --status --json object, the project or the host form, rendered as needs you, running, quiet and problems sections and a sweep and build footer; with --watch, redrawn every interval in place on a terminal, or after a separator line carrying its time when piped.
  • holophyte/holo/report_page.py — holo report: one object for a window, --since, built read-only from the ended runs, failure kinds, gap layers, human interventions, send-backs and consumed notes, printed with --json or rendered as counts first, then the window's runs and, with --notes, the notes.
  • holophyte/holo/run_page.py — holo run N without --json: the run's detail and files bodies rendered as a header, a timeline of its events and rounds, its files and the next commands its phase and outcome call for.
  • holophyte/holo/units.py — holo start, which records the launch and starts the project's loop unit through the daemon's unit_action(), or runs the loop in the terminal with --foreground; and holo stop, which holds the project and, with --now, aborts its live runs.
  • holophyte/holo/__main__.py — python3 -m holophyte.holo, the same command without an install.
  • holophyte/config/project.py — where a project's state lives (HOLOPHYTE_HOME, the <slug> directory, legacy adoption) and the Project value.
  • holophyte/config/reader.py — load_config(), config_table(), KNOWN_KEYS and the role defaults an absent [agents] table leaves in place.
  • holophyte/config/checks.py — the startup checks: unknown keys, the whole document, agent commands, the default routes and the [worktree] table.
  • holophyte/config/agent_settings.py — the [agents] readers: commands, fallbacks, the review route, the session pattern and budget_scale.
  • holophyte/config/worktree_settings.py — the [worktree] readers: setup commands and cap, carry list, branch prefix and environment allow-lists.
  • holophyte/config/serve_settings.py — the [console] and [serve] readers.
  • holophyte/config/review_settings.py — the [review] reader: the blast-radius path lists and the fan-in and packages thresholds.
  • holophyte/config/config_tables.py — the per-table readers (KO-397): the [supervisor], [loop], [board], [merge] and [report] tables with CONSOLE_KEYS and split_address(), the namedtuples and defaults they own.
  • holophyte/failure_reason.py — bounded failure reasons and their structured event facts for verify, adjudication and fix rounds.
  • holophyte/loop/gates.py — the verify gate: a ticket's command in, a red or green fail-loud report out.
  • holophyte/agents/agent_routes.py — process-owned route state and live console snapshots.
  • holophyte/agents/agent_turns.py — per-turn command labels, timing and run events.
  • holophyte/isolation/launcher.py — host/container implementer launch seam, validated isolation settings, credential boundary and container cleanup.
  • holophyte/isolation/isolation_git.py — Git metadata validation and atomic object copying.
  • holophyte/isolation/isolation_return.py — Git ref and index locks for atomic turn returns.
  • holophyte/isolation/isolation_clone.py — disposable container checkouts and sanitized, fast-forward-only commit imports with environment-file exclusions.
  • holophyte/agents/roles.py — the agent routes and the agent() call, one turn of a role.
  • holophyte/agents/probe_log.py — locked, atomic records of each route’s last probe.
  • holophyte/agents/probes.py — route probes, run once before a role dispatches.
  • holophyte/agents/fallback.py — outage detection and the switch to a probed fallback route.
  • holophyte/agents/review_workspace.py — review refs and the scratch checkouts a configured reviewer or the critic runs in.
  • holophyte/agents/agent_output.py — a turn's text with its route, exit status and transport failure.
  • holophyte/agents/claude_stream.py — a streaming Claude turn read line by line, its result lines kept, and a turn quiet for turn_quiet_sec ended as stalled.
  • holophyte/agents/turn_progress.py — a streaming Claude turn's tool calls counted and labelled, recorded as a turn_progress event at most once a minute.
  • holophyte/review/criterion_ids.py — stable criterion numbers for run prompts and replies, with positional fallback for tickets without stored criteria.
  • holophyte/review/briefs.py — the scope, criteria, tests, trim and evidence briefs a reviewer is sent, with the git scope they read.
  • holophyte/review/reply_parsing.py — reviewer output as structured findings, witness checks and a verdict.
  • holophyte/review/blast_radius.py — a review round's blast-radius tier: the built-in high path list, the diff signals, the raising sources and the implementer's BLAST RADIUS: line.
  • holophyte/review/adversary.py — the adversarial pass beside the primary review: when a round runs it, its brief by depth and scope, the reply's evidence levels and the findings that block.
  • holophyte/review/consolidate.py — one findings list for a fix turn after an adversarial pass: the exact merge, the consolidator's merges and order, the three-concern cap, the held concerns and the high-path raise.
  • holophyte/review/stale_approval.py — a review left only stale cited approvals: the stale_approval_rereview event, and the failure when the review again at that head still cites one.
  • holophyte/review/findings.py — FINDINGS.md as a bounded window over the store's rows.
  • holophyte/cli/report.py — --report: estimate vs actual per finished run (actual, estimate, ratio, rounds, outcome) with mean and median ratio, a read-only query over the store that claims no ticket, cuts no worktree and calls no one.
  • holophyte/cli/shadow_report.py — --report's shadow implementer section: per run with a shadow_result, the primary's and the shadow's first implementation (route, minutes, tokens, cost, verify, round-1 verdict and findings) side by side, with a summary line totalling both.
  • holophyte/cli/complexity_report.py — --report's complexity classifier section: per ended run with a complexity verdict, the profile it used, its first-attempt result, outcome, rounds, minutes and shadow result, with one summary line per verdict.
  • holophyte/loop/run.py — the frozen claimed run and the shared landing entry point.
  • holophyte/loop/stop.py — cooperative pause requests, operator aborts and durable continuations.
  • holophyte/loop/runs.py — the store seam: a run's progress as store rows.
  • holophyte/board/projection.py — Linear as the notice board: the ticket mirror, its pushes, --file-ticket and the escalation. Ticket status lives in the store and is projected onto a Linear workflow state by mirror_push() — one way, last write wins, never read back — so the provider's set_state is the only writer of that state, and the mapping table beside mirror_push says which state each status shows as.
  • holophyte/host/supervisor.py — the stale-run sweep and the PROJECT --supervise loop.
  • holophyte/host/sweep_host.py — --supervise [--once] with no project: the host sweep over every store in host.toml, its per-run home lock, sweep.json, the round-robin reconcile under a deadline, and one project's failure its own error.
  • holophyte/board/board_sync.py — observe_board() (KO-739): in store mode the host sweep's ask of the board's states() for every open ticket, once per board_ask_sec — the state name and column recorded, an issue seen gone twice walked abandoned or its live run paused. On every pass between those asks it delivers what is queued: the pending notes, and the queued pushes after one states() ask naming only their tickets.
  • holophyte/deadline.py — the host sweep's bound on its network calls: check() before each unit of Linear or GitHub work, and admit() before each Linear or GitHub request, which past the bound fails as an outage would; both no-ops outside the sweep.
  • holophyte/host/sweep_report.py — the sweep's report lines (KO-396): the SWEEP_HEADERS table, the per-run and restart lines, sweep_report() as --sweep's whole body, and the review-container and merge-lock sections.
  • holophyte/host/worktree_cleanup.py — retire_finished(): each host sweep pass, and --sweep --act, retires the worktree and local branch of every merged or abandoned ticket whose runs have all ended, through the guarded retire_worktree(); a refusal is kept and recorded.
  • holophyte/board/board_diff.py — --board-diff (KO-738): the board's ready listing against the store's rows for it, read-only on both, one line per difference and a summary.
  • holophyte/board/board_import.py — --board-import [--dry-run] (KO-756): board_import() upserts the Linear board's open_issues() into the store by board id in one transaction, one line per issue and a summary counting the pushes and notes pending for Linear; a dry run rolls back.
  • holophyte/cli/status.py — --status [--json] (KO-596): what the project is doing now — projects and admission, live and parked runs, the ready count, the schema version and the lock holders — read only, no network.
  • holophyte/story/story_views.py — --status's story lines: story_facts() reads each planned, approved or parked story's state, generation, child counts, latest witness verdicts at its last ledgered commit, errors, open decisions, open proposals and age; story_lines() renders them as two lines each, then one line per open proposal.
  • holophyte/cli/store_import.py — --import-store PATH --dry-run (KO-595): plan() reads another store and the project's own, both read-only, and says per table what an import would move — rows, id range, the remap offset and a sha256 of the rows in id order; render() prints it.
  • holophyte/host/supervisor_lock.py — the one-supervisor-per-project lock (KO-396): the lockfile's path, read, acquire and release, the SupervisorHeld refusal and the supervisor_running() probe.
  • holophyte/serve/server.py — --serve PORT|HOST:PORT, the HTTP daemon: it reads by default and writes only through [serve] actions and config_edit.
  • holophyte/serve/views.py — the daemon's store read views: /status, /attention, /board and /tickets/ID, with the supervisor and parked-ticket items they share.
  • holophyte/serve/serve_config.py — the daemon's /config routes (KO-394): GET's redacted read and PUT's validated, recorded and backed-up write, text and {"patch": ...} bodies alike.
  • holophyte/serve/serve_actions.py — the daemon's POST /actions/... routes (KO-395): the body parser, the two unit actions, requeue, and the interventions row each records before it acts.
  • holophyte/serve/serve_levers.py — the daemon's hold, release-hold, pause and resume actions (KO-609): the CLI's store calls behind the action token, each requiring a note, the paused attention item, and GET /holds, the project admission state and its change history.
  • holophyte/serve/serve_merge.py — the daemon's GET /runs/N/merge and POST /actions/merge: a run parked for a human's merge read for readiness, and released through the store's approve only when ready.
  • holophyte/serve/serve_ask.py — the daemon's POST /actions/ask and GET /runs/N/asks: a question on a parked run's pull request, recorded with the park's release, and the console asks on that pull request.
  • holophyte/agents/transcripts.py — opted-in transcript location, rendering and turn event joins.
  • holophyte/agents/transcript_config.py — the daemon transcript root allow-list.
  • holophyte/serve/serve_runs.py — the daemon's run and ledger read routes (KO-395): /runs, /shipped, /ledger, /runs/N, /runs/N/ledger and /runs/N/files, their query parsers and the origin-link pair.
  • holophyte/serve/serve_history.py — the daemon's GET /history: every open run and every run started since a time, one row per run.
  • holophyte/serve/serve_findings.py — the daemon's GET /findings: the window's runs with their rounds, round and adjudication ledger rows and advisories, and its follow-up rows with their settled state.
  • holophyte/serve/serve_stories.py — the daemon's GET /stories/KEY: one story's children with their in-story dependency edges, its witnesses' latest verdicts and its decisions, open or answered.
  • holophyte/serve/serve_watch.py — the daemon's code-moved check and its in-flight request count (KO-648): what re-executes it between requests once the factory checkout's HEAD moves, or, on a socket the service manager handed over (adopted_socket()), what makes it drain and exit.
  • holophyte/serve/console_build.py — the host daemon's startup console build: console/dist/source-tree against HEAD:console, a rebuild into a staging directory swapped in on success, the old build kept on failure.
  • holophyte/serve/serve_host.py — --serve with no project: one daemon for every project in host.toml, each under /projects/NAME, the root's /status and /attention for the host, the machine token, one project's failure its own 503 or error, and run-sweep's host ledger.
  • holophyte/serve/contract.py — the poll and run routes' response types: one TypedDict per body /status, /attention and /peers answer, a project's and a host root's, and /runs/N, /runs/N/merge and /runs/N/asks, ROUTES naming each, and main(), which writes their JSON Schema and zod or --checks them.
  • holophyte/serve/contract_pr.py — the pull request facts and the unmerged dependency a parked pull request waits on, as the contract declares them on /attention and /status.
  • holophyte/serve/contract_emit.py — a TypedDict turned into JSON Schema, JSON Schema into zod source, and the drift check against the committed files; it knows no route.
  • holophyte/serve/serve_board.py — the host daemon's native board writes (KO-758): the gate every one passes (the machine token alone, host [serve] actions, a native board, If-Match) and PUT /projects/NAME/tickets/ID, an edit authored console.
  • holophyte/redact.py — secret values in a config.toml text, found by walking its TOML syntax: hidden for GET /config, put back for PUT.
  • holophyte/files.py — the files a run touched, read from git in the project's checkout under a timeout: what /runs/N/files answers.
  • holophyte/loop/pipeline.py — run_task: a claimed run through its stages in order, from the worktree to the merge or the pull request.
  • holophyte/loop/implement.py — the implement stage and the timed agent turn under its scaled budget, the run cap and the transport retry.
  • holophyte/loop/implementer_profile.py — profiled_run: resolves, probes and records a run's implementer profile, and scopes the run's project to it.
  • holophyte/loop/complexity.py — the complexity question a claimed run asks once, its complexity event and the profile a confident verdict picks.
  • holophyte/loop/escalation.py — a run failed on its merits on an implementer profile requeued once on the default implementer, its brief and the ESCALATION: choice the escalated turn declares.
  • holophyte/loop/contradiction.py — an acceptance criterion the implement or fix turn declares unmeetable as written: the brief, the CONTRADICTION: criterion CN lines, the contradiction event and the park on a question to the operator.
  • holophyte/loop/trim.py — the trim step between the implement turn and the first review: one turn, its commits judged by subject, file scope and verify.
  • holophyte/loop/trim_brief.py — TRIM_BRIEF, the vendored trim procedure the trim turn is sent, and the trim: pass subjects it commits under.
  • holophyte/loop/shadow.py — run_shadow: one shadow implementation of a run's ticket by [agents.implementer_shadow] in its own worktree, recorded as a shadow_result event.
  • holophyte/loop/shadow_spawn.py — start_shadow: a fresh claim's detached factory.py --shadow beside the primary, recorded as shadow_started; and that mode, one shadow a project under shadow.lock, else shadow_skipped.
  • holophyte/loop/review_round.py — the review rounds: verify, review and a fix turn per round, up to the cap the candidate's size earns.
  • holophyte/loop/adjudicate.py — the terminal adjudication once the review rounds are spent.
  • holophyte/loop/steering.py — a live run's pending holo steer notes: the prompt text its next implementer turn carries, their consumption, and the amendments it carried joined to the reviewers' ticket text.
  • holophyte/loop/steer_cap.py — a run that failed on its time or review-round cap while its ticket holds an amendment: the ticket parked on the operator with a question suggesting the steered scope split into a follow-up ticket.
  • holophyte/loop/branch_sync.py — the branch fast-forwarded to origin's copy, and the worktree held to the candidate a park recorded.
  • holophyte/agents/harness.py — harness adapters: a table-form [agents] role's turn argv, session id and resume argv, and the [harnesses] binary paths.
  • holophyte/agents/fix_session.py — review fix-session arms, resume argv and fresh retry.
  • holophyte/agents/session_arms.py — shared run-number assignment for session experiments.
  • holophyte/review/review_session.py — reviewer session-file capture and resume requests.
  • holophyte/loop/dispatch.py — the dispatcher out of the loop module (KO-412): one claim run under its crash containment and close-out, the startup sweep and the queue mirror.
  • holophyte/loop/merge_gate.py — the merge gate out of the loop module (KO-424): main merged into the branch, the pre-merge verify and the drift check under the merge lock, the park for a human's approval, the approved candidate's resumed run, and the --no-ff merge onto main.
  • holophyte/story/story_drift.py — a story child at the local merge gate after siblings merged since its claim: the story_drift event naming the files both changed, and one covering review of the refreshed candidate when there are any, parking the run for a person when it asks for changes.
  • holophyte/review/reproduce.py — a reported defect the implementer could not reproduce (KO-657): the OUTCOME: NOT_REPRODUCED declaration, the evidence check in place of round 1, and the not_reproduced park.
  • holophyte/review/skipped_tests.py — the candidate's added test ids by module, class and name, the verify commands rerun verbosely with their own selection (unittest or pytest), which added tests were skipped or never collected and why, and the fix-round brief asking for a runnable reproduction.
  • holophyte/review/witness_precheck.py — before the round 1 review, each criterion's named test file checked from git and a verbose verify rerun: it exists at the candidate, the branch adds or changes it, and the verify ran it; recorded as the witness_precheck and witness_precheck_results events.
  • holophyte/loop/witness_turns.py — the witness pre-check's gaps sent back to the implementer before the round 1 review, at most two witness_turn turns a run, and the brief of gaps still open for the round 1 reviewer.
  • holophyte/loop/merge_lock.py — the bounded wait for a live merge-lock holder, with waiter heartbeats and paired wait events (KO-496).
  • holophyte/config/locks.py — the Locks protocol a Project carries as its locks attribute, and FileLocks, whose merge() is live_merge_lock() unchanged (KO-594).
  • holophyte/cli/operator.py — the operator commands and the entry point: main (the serial pass or the pool's scheduler) and the self-merge re-exec, report, and the --requeue/--approve/--babysit/ --repoint store verbs.
  • holophyte/admission.py — project admission reads, hold/release CLI commands, and the scheduler's held-project exit after its workers finish.
  • holophyte/loop/claim.py — claiming a ticket and cutting its worktree: _claim_next over _admit_ticket's questions and _claim_run's lease and board label, then _cut_worktree/reuse_leftover, run_worktree_setup under _setup_worktree, and the mid-merge hand-off (merge_conflicts, conflict_brief, _resolve_merge_conflict).
  • holophyte/loop/task_worktree.py — a task worktree's own files and lifecycle: the .env (write_worktree_environment), the capture .gitignore, the [worktree] setup runner (run_worktree_setup) and retire_worktree for a finished checkout.
  • holophyte/loop/claim_store.py — the store-mode claim (Phase 3 stage 3): store_mode(), the one predicate; claim_from_store() takes the first store.read.claimable() row, admits it at its revision, reads that one issue back from the board and claims only at the admitted revision, admitting a moved ticket again at most READMIT_LIMIT times an ask.
  • holophyte/story/story_claim.py — a story's guards: refusal() keeps the store-mode claim off a child whose story is not approved, held_open() notes a board Done on a story parent instead of walking it merged, and walk_closed() abandons the story of a parent the board canceled.
  • holophyte/board/native_board.py — NativeBoard (KO-754), the board board_for() builds for [board] kind = "native": its reads answer from the project's store, filing, editing and the body read-back go through store.board, the lease and state writes do nothing, and comment() records a note. It never asks Linear.
  • holophyte/review/freshness.py — the claim's freshness check (KO-709): stale_reasons() asks the main ref for each file a ticket names, and park_stale() mirrors a stale ticket needs_spec, comments once and moves it to Backlog. The claim reads a main just fetched from origin. On a native board recheck_stale(), called from sync_board() on every pass, returns a ticket parked for stale landmarks (not one the critic parked) to ready (or blocked_on_deps) once none of its reasons holds, and --move KEY-n ready re-checks it the same way. The move also re-readies a critic park, running only the deterministic checks, and records a requeue intervention for either kind. A move to Ready of a ticket already in Ready is refused naming its status (KEY-n is already in ready at status ready; nothing changed). A native park keeps its column, and its note names --file-ticket --update and --move KEY-n ready.
  • holophyte/review/landmarks.py — what counts as a lost landmark: MainTree caches main's file list, blobs and git grep holders for one check; path_findings() and symbol_findings() split a ticket's misses into stale reasons (gone from main) and advisories (a bare basename that matches no tracked file, or a symbol its sentence pairs with one file but main holds in another). A symbol pairs with the path after in, from, of or (, or with a path leading its item before :. Advisories print at filing and become a claimed run's warning events; they never park.
  • holophyte/environment_git.py — excludes the filtered .env from Git staging and refuses candidate pushes containing it.
  • holophyte/commit_hygiene.py — removes configured attribution lines from unpublished commits before task pushes and local merges.
  • holophyte/leak_guard.py — scans unpublished commits, pull request text and ticket bodies for [merge] private_patterns and refuses a match by location and pattern index.
  • holophyte/loop/pause_notice.py — a paused run's pull request gets the holophyte:paused label and one notice comment; --resume removes both (KO-608).
  • holophyte/pr/github.py — [merge] mode = "pr"'s GitHub writes: the startup route check, the push, the pull request and its body, the babysitter's replies and resolves, the merge through the PR API, and the gh/API transport they ride on.
  • holophyte/pr/pr_contexts.py — page the head rollup's commit statuses and normalise them beside check runs for required-context folding (KO-485).
  • holophyte/pr/pr_head.py — bounded settling after a push and the foreign-head guard before the babysitter judges or merges a candidate.
  • holophyte/pr/merge_ready.py — whether a parked run may merge now: its park, [merge] approve, and GitHub's review decision, checks, mergeable, threads and head against origin, as ordered facts and the first failing fact's reason.
  • holophyte/pr/pr_activity.py — authored PR activity and event-backed wake guards.
  • holophyte/pr/pr_ready.py — under [merge] pr_draft, marking the factory's draft pull request ready for review once at the babysitter's ready point, or parking a draft it may not mark; under pr_draft = "ui", holding the draft for the maintainer to mark ready.
  • holophyte/host/ci_wake.py — when the reconcile sends a run parked ci back to the babysitter (its checks finished, or it is green and quiet for [merge] pr_quiet_sec), or re-parks it for a human once its checks stay pending past [merge] check_wait_sec.
  • holophyte/pr/pr_status.py — reading a pull request's state (KO-426), out of holophyte/pr/github.py: pull_status() for the parked-run reconcile, pr_state() and fold_checks() for the babysitter, parse_pr_url().
  • holophyte/media_store.py — standard-library SigV4 PUTs for evidence in an S3-compatible bucket. Operator setup lives in the bucket configuration reference.
  • holophyte/pr/pr_media.py — user-facing diff matching, timed capture, bounded media publishing and evidence shared by PR descriptions and review prompts. See merge configuration for capture commands, destinations and size limits.
  • holophyte/pr/evidence_refresh.py — before the covering review of a babysit fix, writing the capture at the fix head into the pull request's Evidence section, or marking its earlier images stale when that capture failed, and the ledger note recording which.
  • holophyte/pr/evidence_fetch.py — before a review, fetching each Evidence section image under [merge.media_bucket] public_base with the mock-up download and naming each the factory could not fetch, with why, for the reviewer's brief.
  • holophyte/pr/image_fetch.py — after a pull request body write that embeds an image, comparing each image GitHub's image proxy serves with the image at its own URL, and linking a short one again with a ?v=N query, at most twice.
  • holophyte/pr/mockup.py — a ticket's ## Mock-up media links: the filing checks against [merge.media_bucket] public_base, the download of each .png snapshot and .zip bundle into the task worktree's ignored .holophyte/mockup/ with its manifest.json, and the implementer's brief that lists them. review_carry names that directory and .holophyte/captured/ (the PNGs of the last UI capture) for every review workspace to copy read-only, and reviewer_brief lists both with the reviewer's design-match check.
  • holophyte/capture_playwright.py — a standalone Playwright capture runner a project can name as [merge] ui_capture: it runs the ticket's spec from a capture directory through a generated config that imports the project's own. Standard library only, so it runs by path from a project's worktree.
  • holophyte/pr/pullrequest.py — the pull-request stage of the loop: the PR open or adopt, every park on the PR, the merge through the PR API and its ledger line, and the resume of a run parked on its PR.
  • holophyte/pr/merge_queue.py — landing a pull request through main's merge queue (KO-712): the rules read for a merge_queue rule, the enqueuePullRequest mutation, and the bounded wait for the queue's merge commit, a removal, or [merge] check_wait_sec.
  • holophyte/babysit/maintainer_notes.py — private maintainer instructions as pre-approved babysitter threads, contract amendments, and commit event references.
  • store/steer_notes.py — holo steer's notes: a ticket's amendment or hint recorded with its interventions row, or a parked run's send-back linked to its event; a ticket's amendments and pending hints, their consumption, and --withdraw's marking of every standing amendment withdrawn.
  • store/operator_notes.py — atomic send-back and append-only note consumption evidence across attempts on the same pull request.
  • store/console_asks.py — a console ask recorded and its park released in one transaction, the unanswered asks on a ticket's pull request, and each answer's comment.
  • holophyte/babysit/bot_threads.py — advisory bot findings and human-reply escalation.
  • holophyte/babysit/conversation_comments.py — paged human conversation instructions and recognition of quoted factory replies.
  • holophyte/babysit/devin_notes.py — a Devin review comment marked analysis, told apart by the factory's triage reply: a note stays fix-turn context, a finding is an open thread, and one with no reply is untriaged.
  • holophyte/babysit/note_triage.py — one read-only adjudicate triage of the untriaged Devin analysis comments as FINDING or NOTE, each answered on its thread; a FINDING joins the open threads.
  • holophyte/babysit/review_body.py — bot review-body outside-diff findings, their fingerprints and answer markers.
  • holophyte/babysit/thread_answers.py — read-only mention answers and thread replies.
  • holophyte/babysit/thread_mentions.py — mention markers and typed intent triage.
  • holophyte/questions.py — typed choice requests and safe service failures.
  • holophyte/question_cli.py — typed questions through the claude or codex CLI: prompt, schema, parsing, usage, and the probe with its fallback.
  • holophyte/loop/follow_ups.py — a fix turn's FOLLOW_UP lines parsed from its new commits and stored pending, and the merged run's settle step, one project's at a time under a file lock: a feature filed as a Backlog draft or matched to an open one by its fingerprint, a guardrail kept as a findings-ledger row. A feature raised by a child of an open story is first matched against the story's children and proposals, then put to the adjudicator: in_story writes a story proposal, standalone files the draft with a related-story line.
  • holophyte/story/story_scope.py — a story child's feature follow-up put to the adjudicator seat: scope_goal() builds the goal from the story's title, Summary, Goal and children, read_scope() reads the last SCOPE: in_story|standalone: REASON line, and judge() runs the turn on the adjudicator seat as terminal adjudication does, defaults to standalone on a failed, timed-out, outage or unreadable reply, and records its follow_up_scope event.
  • holophyte/loop/failure_triage.py — a failed run's cause question (infra, code or spec) after close-out, its failure_triage event, and the one factory requeue a confident infra answer earns.
  • holophyte/babysit/thread_findings.py — structured PR findings, bounded originals, and read-only normalization of legacy thread rows.
  • holophyte/babysit/babysit_steps.py — records PR babysitting step changes without repeating unchanged steps during polling.
  • holophyte/babysit/babysitter.py — the babysit pass over a pull request: thread answering and fixing, the merge of main, and the waits that drive them.
  • holophyte/babysit/adjudicator_outage.py — a failed thread-pass adjudicator turn, or one with no verdict for any thread, parked as a route outage that the host retries after [merge] route_retry_sec.
  • holophyte/babysit/thread_text.py — the babysitter's thread text: the conventions it quotes, the adjudicator's brief over its threads, the ADDRESS/DECLINE/HUMAN verdict parser, the fix brief and summary parser, the ---- Comment by MODEL ---- replies, the round text, and the parked question.
  • holophyte/babysit/plain_text.py — a review comment as plain text for a parked question (KO-717): HTML reduced to its text, an img to its alt, details blocks and quote markers dropped, and a 600-character cap.
  • holophyte/pr/missing_checks.py — required checks that never reported on a pull request's head: the one empty-commit retrigger per candidate under [merge] retrigger_missing_checks, and the park naming them (KO-652).
  • holophyte/babysit/check_fix.py — a red check's rerun and its check fix rounds per babysit, up to [merge] check_fix_rounds: the brief with each failed Actions job's failure lines and log tail, the round's event and ledger line, and the park on a repeated failure or spent rounds.
  • holophyte/babysit/capture_fix.py — the capture fix round before the covering review of a babysit fix whose capture from a local spec failed, up to [merge] capture_fix_rounds: the brief with the missing views, failing lines and capture log tail, the round's event and ledger line, the capture again at the round's head, and the park when it still fails.
  • holophyte/babysit/check_failures.py — a job log's failure lines (test failure headers and error lines), their normalized signature, and the test whether a red read repeats a failure an earlier round was handed.
  • holophyte/babysit/covering_fix.py — what follows a covering review of a babysit fix that asks for changes: the operator note's fix, an automatic fix round with the review's findings as the note, or the park when the findings repeat or [merge] covering_fix_rounds is spent.
  • holophyte/babysit/description_review.py — a covering review whose every blocker is tagged path: "PR description": the description rewritten with those findings as input before the same commit is reviewed once more.
  • holophyte/babysit/main_checkout.py — the detached main checkout the babysit pass verifies main in, given the task worktree's [worktree] carry directories, or its [worktree] setup, first.
  • holophyte/babysit/main_side.py — the babysit pass's main-side verify after a merge of main turns the branch red: main runs without what only the branch adds, and is not compared when the failing command was left out.
  • holophyte/story/witness.py — run_witnesses() runs a story's witness commands at one commit of main, in the warm tree for a loop pass and otherwise in a scratch worktree, within [story] witness_sec, and appends each verdict to the ledger; main_tip() reads the commit a pass runs at and red_kind() reads a red's kind from its output; witness_pass() runs an open story's witnesses at main's tip unless the ledger already holds it or the project is held, reruns once a red that was green at an earlier commit, and notes each changed verdict on the parent. A loop pass runs the whole witness set only when it is due, and otherwise carries the last real verdicts to the new commit.
  • holophyte/story/witness_running.py — witness-running.json beside the store names the witness set the loop's witness step is running: mark_running() writes it, clear_running() removes it and running_set() reads it for /status's witnesses, ignoring a record whose process has exited.
  • holophyte/story/witness_trigger.py — set_due() answers whether a loop pass runs a story's witness set at a commit: when the story has no real run of every witness since the story's latest approval, when a child's merged run's mergeSha is an ancestor of the commit and not of the set's last real run (the oldest of its witnesses' latest real verdicts, so a pass cut short still owes the rest), when the story's ## Witness paths changed since that run, when its newest real verdict is older than the most recent [story] witness_nightly_hour, or when a git command fails; carry() records each witness's latest real verdict again at the commit, carriedFrom naming the commit it came from.
  • holophyte/story/witness_tree.py — the warm tree a project's loop witness passes reuse: witness-tree/ under the project's state directory, taken under the non-blocking witness-tree.lock, checked out detached at the commit and cleaned keeping each [worktree] setup table step's keep paths; witness-tree.json stamps each table step's key over its run and its inputs at the commit, so a step whose key matches and whose keep paths exist is reused, until an earlier table step runs or a setup fails.
  • holophyte/story/story_close.py — settle_story() ends each witness pass under the merge lock: at main's tip it closes the story when every witness is green with its approved file, queueing the parent's Done, and otherwise parks it on one unmet or regressed decision per witness; settle_owed() keeps a story the lock kept from settling pending for the next pass at the same tip, and rerun_owed() keeps a red whose rerun did not run from parking regressed until the next pass runs it again. Its close note names each open proposal.
  • holophyte/story/proposal_decisions.py — --decide KEY-n pN answers a story's proposed child: accepting files it in Backlog through the board, depending on the child that raised it with a Role: scaffolding line (on Linear a sub-issue of the parent carrying the board's label, mirrored into the store), then accept_proposal() adds it to the story and its approved plan; rejecting records the answer and files nothing.
  • holophyte/host/reconcile.py — the startup reconciles and the GitHub read budget: parked pull requests asked about on GitHub, mirrored tickets Linear closed walked to their terminal status, and the GraphQL budget the reads spend from.
  • holophyte/host/startup.py — eager package imports, build identity and live-worker checkout guard.
  • holophyte/loop/pool_handoff.py — schema-aware restart and child ownership across exec.
  • holophyte/loop/pool.py — the worker pool and its scheduler (KO-343): scheduler() over factory.py --worker children under [loop] workers > 1, worker() as one child, the spawn and wait seams, the claimable count and the [holo2 wN] line prefixing.
  • holophyte/loop/reexec.py — reexec_self, the shared self re-exec the loop and the supervisor both restart themselves through.

The store is its own package:

  • store/__init__.py — the v2 durable state store, one WAL-mode SQLite file: claims and leases, run-phase transitions, review rounds and the ledger.
  • store/operate.py — the operator API: release()/resume()/ requeue()/repoint()/approve()/babysit(), record_intervention(), record_project_intervention() and the runEvents writers, re-exported from the package.
  • store/repair.py — repair_references(): a dry run, then a recorded rewrite of foreign keys that name a dropped table.
  • store/criteria.py — append-only criterion versions, stable ticket-local numbers and open-ticket migration backfill.
  • store/failure_kinds.py — prefix-only backfill for historical run failures.
  • store/enums.py — canonical store vocabularies and generated SQL CHECK clauses.
  • store/schema.py — the schema, its migration ladder and the connection (open/init/transaction), re-exported from the package.
  • store/ddl.py — the table and index DDL the schema module creates.
  • store/project_paths.py — canonical project identity reads that require operator repair for ambiguous legacy relative paths.
  • store/tickets.py — the ticket state machine: ensure_project, the §3 TICKET_TRANSITIONS table and transition()/walk_ticket(), mirror_ticket, §2's pickable()/pickable_tickets() and the Mermaid state-graph renderer, re-exported from the package.
  • store/board.py — file_ticket() and edit_ticket(): a native board's ticket numbered from projects.ticketSeq, validated by ticket_problems() as --file-ticket validates, and an edit refused at a stale revision (KO-750).
  • store/revisions.py — record_board_fields(): one ticketRevisions row per change of a ticket's board-owned fields, healing a row an older build changed first (KO-736).
  • store/notes.py — record_note(): one ticketNotes row per key on a ticket, joining the caller's transaction so a ledger entry and its board note land together (KO-742); mark_note_posted() and mark_note_failed() record the host sweep's post of it (KO-747).
  • store/gap_layers.py — record_gap_layer() appends the correction layer a gap's lesson landed in, and gap_layer_counts() counts each gap's latest layer in ladder order.
  • store/follow_ups.py — record_follow_up() stores a pending followUps row once per run and fingerprint, pending_follow_ups() reads a run's pending rows with those of the parked runs whose candidate it carried, filed_drafts() the drafts filed for a fingerprint, and the four settle_*() writers settle a row as filed, duplicate, ledger or unfiled, each with its run event.
  • store/story_proposals.py — record_proposal() writes a story child's follow-up as a proposed storyProposals row and settles the follow-up, open_proposals() reads a story's open proposals, story_duplicate() finds the child or proposal a follow-up repeats, and accept_proposal() and reject_proposal() answer one with its decide intervention.
  • store/stories.py — file_story() writes a story's rows for a parent and its children, story() reads one back from either, approve_story() freezes its plan with one open story per project, and abandon_story() gives it up and moves its unclaimed children to Backlog.
  • store/instructions.py — persist replies on recorded thread instructions.
  • store/agent_routes.py — atomic fallback intervention and event records.
  • store/launch_backoff.py — persistent route outages, retry deadlines and project-owned startup evidence before a first claim (KO-466).
  • store/working.py — persisted work intervals, finally-safe settlement and non-mutating effective-work reads; excludes orchestration waits.
  • store/writes.py — named transactional writes for board state, questions, pull request metadata, outcome reasons and board ask timestamps.
  • store/read.py — typed read views over the store: one query, one row type, no SQL elsewhere.
  • store/run_reads.py — the typed reads of runs, re-exported by store/read.py.

At the root:

  • factory.py — the entry point: imports cli from the package and calls it. Holds no def or class of its own.
  • provider.py — the Provider protocol the loop talks to a board through.
  • linear_provider.py — the Linear GraphQL client: claim/fetch_task/ set_state/comment, ready-ticket and blocker resolution, issue creation.
  • review_runner.py — exact-SHA staging and the model-neutral local reviewer boundary (see Reviewing).
  • ticket_criteria.py — explicit criterion ids, test and verify witnesses, and blocking post-merge or operator witness checks for native filings.
  • ticket_template.py — parser/validator for the ticket shape; python3 ticket_template.py TICKET.md [...] exits 0 iff the ticket is pickable-ready.
  • ticket_header.py — finds, parses and checks the +++ TOML header a ticket can open with, setting the same Ticket fields as the magic lines.
  • ticket_depends.py — finds a ticket body's estimate line and rewrites its Depends on: list, for story filing and a cancel's rewire.
  • story_template.py — validator for a story directory's body, witness files and children; python3 ticket_template.py --repo PATH --story DIR exits 0 iff the story is valid.
  • ticketTemplate.md — the ticket shape. Its typed fields (ui_change, estimate, depends_on, [mockup], [[evidence]] and the rest) sit in the +++ TOML header above the H1; a body without one keeps the magic-line form. Verify commands go in the "Verify command(s)" section (exit 0 = pass, relative paths only); estimate is the budget in minutes. The optional "Contract checks" section declares relative/path: exact literal lines the gate asserts verbatim, so a required value (a port, a URL) cannot drift while the commands still pass.
  • docker/reviewer.Dockerfile — pinned minimal reviewer image.
  • landing/ — the static landing page at https://holophyte.ai, served by its own Worker from landing/public/ (design note 6); the root wrangler.jsonc serves this manual at https://docs.holophyte.ai. Its screenshots and video are not in the repository: they live in the R2 bucket holophyte-media under demo/, served at https://media.holophyte.ai/demo/. After a new capture, scripts/landing_media.sh CAPTURE_DIR uploads every file the page references from the capture output directory, with its content type.
  • FINDINGS.md (generated, not kept in this repository) — in a project with [report] findings = "repo", a rendered window over the store, not a log: the factory regenerates it at each close-out from runs/reviewRounds as the newest 25 entries below a <!-- store-rendered below --> marker, with everything older counted in one archive line and kept in the store. Text above the marker is frozen pre-store history and is never rewritten; Linear ticket comments stay the full per-ticket archive.
  • tests/ — the stdlib unittest suite, one test_*.py per surface, with tests/fake_agent.py, tests/procs.py and tests/waiting.py as shared helpers. Run it with HOLOPHYTE_HOME=$(mktemp -d) python3 -m unittest discover -s tests.

Linting

ruff check . from the repo root; it exits 0 when the tree is clean. Run it alongside the tests — the developer verify path is:

ruff check .
python3 -m unittest discover -s tests

The configuration lives in ruff.toml: line length 88, Python version py311, and rule sets E, F, W, I, C90 (pycodestyle errors and warnings, pyflakes, import ordering, McCabe complexity). Nothing is formatted, only checked. Every enabled rule is a promise the factory keeps forever, so the selection stays small, and a violation that has to stand is suppressed with a per-line # noqa: <CODE> rather than a file-level or blanket ignore.

Cyclomatic complexity above 12 is a lint failure (ruff C901); an exemption is a per-function noqa: C901 that names its reason in a few words, and its COMPLEXITY_EXEMPT entry in tests/test_comment_budget.py names the ticket that retires it, or None while none is filed.

Module size is a ratchet on the same terms: no source module over 1000 lines, no test module over 1500. tests/test_file_sizes.py holds a an OVER table of unpinned modules over their ceiling, each entry its count as wc -l measures it, and walks every tracked Python file against it — a module that grows past its entry, a module with no entry that passes its ceiling, and an entry whose module is back under the ceiling all fail the suite, and a second check holds every entry to wc -l's exact count. A slice that changes a listed unpinned module's size rewrites its entry in the same commit; a module back under its ceiling leaves the table. Pins are upper bounds; more than 150 lines of slack requires lowering the pin.

Comments are a ratchet too. tests/test_comment_budget.py pins, for each module under holophyte/ and store/ and each top-level module, its count of comment and docstring lines (PINNED) and of lines citing a ticket id (CITED). Neither count may grow, and a lowered count lowers its entry in the same commit; a new module holds at most one such line per 20 lines and cites no ticket. A comment states only a constraint the code cannot express; history belongs in the commit message and the store. The suite reports the in-scope ratio against its 5% goal as a skipped test until the trim reaches it, and code_changes() witnesses that a trim removed only comments and docstrings.

ruff is a developer tool, not a dependency: install it on the host with pip install --user ruff (or uv tool install ruff). It is never vendored.

Console

The console is the browser page the daemon serves at /. It lives in console/ as its own package: Bun is the runtime, test runner and bundler; the page is React 19 with TypeScript, styled with Tailwind v4. The three commands, run from the repo root:

bun --cwd=console install --frozen-lockfile
bun --cwd=console test
bun --cwd=console run build

--cwd=console is the equals form of Bun's global flag and the only form that works here: Bun 1.4 reads the space-separated bun --cwd console X as bun run X inside the directory, so install and test would resolve to package scripts and run build would print usage without building.

install reads the committed console/bun.lock and refuses to drift from it; it never builds — there is no install lifecycle script, and run build is the one path to console/dist/. test runs bun test with console/tests/setup.ts preloaded and discovery rooted at console/tests/ (see console/bunfig.toml, so the nested electron package's tests stay its own), which registers happy-dom so component tests render with @testing-library/react and no browser; console/src/lib/ tests stay free of the DOM. build runs console/build.ts, which hands both pages, console/index.html and console/next.html, to one Bun.build call with bun-plugin-tailwind and writes the static bundle to console/dist/ — each page keeps its name there, next to its own hashed script, and index.html is the stable entry the daemon serves. console/dist/ and console/node_modules/ are git-ignored.

The daemon owns the types of the routes the console polls. python3 -m holophyte.serve.contract renders the TypedDicts in holophyte/serve/contract.py into one JSON Schema per route in console/contract/ and the zod schemas with their inferred types in console/src/lib/contract.gen.ts; both are committed and never edited by hand. --check writes nothing and exits 1 naming each file that differs from a fresh render, or that sits in console/contract/ unrendered; tests/test_serve_contract.py runs it in the unit check, and walks the pinned answers in tests/fixtures/serve/ against their types.

Captures are the screenshots a console pull request carries, taken by holophyte/capture_playwright.py with a real Chromium. The fixture python3 -m tests.console_fixture builds the console, seeds a throwaway host with the demo project, serves it from a real host daemon and runs one command with CONSOLE_URL naming it; that command is the console's capture script, playwright test from the pinned @playwright/test. seed() writes a ticket in backlog, one ready, a working run, a run in review after one changes-requested round, a question park, a pull request parked on CI, one parked ready for its merge, a stranded failure, a merge from today, an older merge and an abandoned failure, so a spec injects only the states beyond those. console/playwright.config.ts is Chromium only, one worker, no retries, a 1440×900 light viewport, baseURL from CONSOLE_URL, and no webServer, since the fixture boots the daemon. A ticket's spec is <KEY>.capture.ts in the capture directory under console/ (so it resolves the console's @playwright/test), and saves NN-slug.png files into CAPTURE_OUT; console/e2e/CAPTURE-0.capture.ts is the smoke spec, which writes the Board as 01-board.png. Neither bun test nor the typecheck reaches console/e2e/, and Playwright's console/test-results/ is git-ignored. The verify's capture command, from the repo root:

OUT=$(mktemp -d) && HOLOPHYTE_TICKET=CAPTURE-0 python3 holophyte/capture_playwright.py --dir console/e2e --config console/playwright.config.ts --boot "python3 -m tests.console_fixture --build 'bun --cwd=console run build' bun --cwd=console run capture" "$OUT"

Bun is a developer tool like ruff, never vendored. The operator step for the writer host: install it with the upstream installer (curl -fsSL https://bun.sh/install | bash) as the factory's user, so the loop's login shell sees bun on PATH. The factory's verify step inherits that PATH, so nothing in the loop changes. The writer host runs Bun 1.4.2.

The new console

The new console is built beside the classic one until it replaces it. It lives in console/src/next/, with its tests under console/tests/next/, and is served at /next.html beside the classic console at /; the classic rail's "New view" and the new sidebar's "Classic console" swap between them, and / reopens the one chosen last (console/src/lib/view.ts) unless the address carries ?classic. The classic console opens on the view a #view= link names (now, board, hosts or shipped), so a new console page can link to the classic view behind it.

console/src/next/focus.ts derives blocked-card headlines and extracts asks. console/src/next/components/LimitedMarkdown.tsx renders asks and latest events with a restricted Markdown vocabulary and expandable line clamps.

Browser appearance choices live in console/src/next/appearance.ts; console/src/next/components/AppearanceTab.tsx renders theme and reading-font radio cards, sharing theme state with the sidebar. Its own console/src/next/theme.css supplies the Night and Dawn tokens and Archivo and IBM Plex Mono faces, keeping the classic palette and fonts separate.

console/src/next/router.tsx routes it with TanStack Router over hash history, since the static route has no single-page fallback: #/ is Overview, #/runs Runs, #/tickets Tickets and #/findings Findings, each in the shell its root renders. Settings lives at #/settings (General) and #/settings/$tab: general, review, agents, capture, hold, toml, hosts and appearance; an unknown tab is a not-found page. console/src/next/settingsDraft.ts holds per-project field and raw drafts for the Settings page. console/src/next/components/TomlEditor.tsx edits and checks the whole TOML file with a line gutter. console/src/next/components/SettingRow.tsx binds the shared field rows, and console/src/next/components/UnsavedBar.tsx lists and discards pending changes. console/src/next/hosts.ts derives the unscoped daemon cards from the shared fleet poll; console/src/next/components/HostsTab.tsx renders their health, projects and last reports. console/src/next/components/RouteTable.tsx lists their agent routes, probes and recorded fallback use. console/src/next/checker.ts places each /tickets/check message on the New ticket field it names and words the Checker panel's summary.

console/src/next/settings.ts defines the grouped tabs and host attention count; console/src/next/pages/Settings.tsx frames the project picker and placeholder panels. The sidebar footer links to Settings. Any other path is a not-found page. The root route's search holds one optional project, the project's name as the sidebar shows it, so /next.html#/runs?project=demo reproduces a scoped view; every route inherits it, the sidebar's links carry it, and scopedHosts() in console/src/next/scope.ts narrows the polled hosts to it. The root reads the fleet once with useFleet() and shares the result with the sidebar and the pages through a React context.

The new console reads its data through one TanStack Query client, consoleQueryClient() in console/src/next/fleet.ts, which its entry mounts; a failed read is not retried, since the next poll is the retry. useFleet() is one query keyed by the origin: pollPeers() fans out to every daemon each POLL_INTERVAL_MS, mergeHosts() keeps each daemon's last good answer through its failures, and every view that reads it shares the one poll. Every body is parsed with the generated schemas in console/src/lib/contract.gen.ts, so a daemon whose answer changed shape fails at the fetch with a contract error naming the endpoint and field. A view that reads another route adds its own query keyed by the daemon base and the path, parsed with that route's generated schema. An action is a mutation, useAction() in console/src/next/actions.ts: when the daemon takes it, it invalidates the fleet query and every route query of that daemon base, so the view re-reads at once, not at the next poll.

The sidebar's project switcher (console/src/next/components/ProjectSwitcher.tsx) sets that scope. Its rows come from switcherRows() in console/src/next/projects.ts: "All projects", then one row per project name, pooling the hosts that serve it, with its held tag, need count and open runs, or "not answering" when every host of it failed its last poll. A find box filters them; arrow keys move, Enter picks, Esc closes. A pick rewrites project on the current route and is kept in session storage under holophyte.next.project, so opening /next.html with an empty hash starts at Overview in that scope; a hash that names a route is shown as written.

console/src/next/pages/NewTicket.tsx presents the ticket template as text fields and files to an editable project’s Backlog from the Tickets page. console/e2e/HOLO-406.capture.ts captures its themes, Tickets entry, filing refusal and narrow layout, and checks the commands’ rendered monospace font. console/src/next/pages/EditTicket.tsx loads a native ticket's current revision into the same fields (console/src/next/components/TicketFields.tsx), marks each changed one and saves it as the next revision with If-Match; console/src/next/ticketEdit.ts rewrites only the fields that changed. When a newer revision lands during an edit, console/src/next/sectionPicks.ts sorts each body section by which side changed it and builds the body from the section the maintainer picks for each one both sides changed.

console/src/next/runDays.ts defines thirty local-day windows and aggregates each day’s runs. console/src/next/pages/RunsHistory.tsx renders their counts, work totals and five-day pager.

The Run page’s next/components/CriteriaList.tsx shows review marks and witness notes, with its verdict count supplied by criteriaClause() in next/run.ts.

The Run page’s console/src/next/components/AgentPanel.tsx renders the agent seat, state and chronological note/question thread from the page’s shared run detail and asks queries.

The Run page’s console/src/next/components/RunDetails.tsx folds files, review rounds and timed agent turns below Progress. Its files and turns queries in next/queries.ts fetch only while the fold is open.

The Turn page (console/src/next/pages/Turn.tsx) shows one recorded turn’s figures and links to its neighbours. console/src/next/turn.ts selects its host and run and shares turn titles with the Details fold. Run and Turn share page framing in console/src/next/pages/runShared.tsx. The Turn page’s console/src/next/components/TurnEntries.tsx lists transcript entries with folded prompts and tool calls, opening failed calls automatically. console/src/next/components/TurnToolbar.tsx searches and filters the Turn transcript, jumps to its first error and controls its folds.

Desktop wrapper

Some operators want the console in the dock with a menubar icon rather than in a browser tab. console/electron/ is a thin Electron shell around the URL the daemon serves: one window (1280×860, never narrower than the 1100px the design assumes), a tray icon built from assets/menubar-template@1x.png and its @2x sibling whose menu carries the SwiftBar drawer's summary, then "Show console", an "Open at login" checkbox that reads and sets the macOS login item, "Developer tools", which opens the window's DevTools (the platform shortcut works too), and "Quit", and nothing of its own — no renderer code, no preload, no IPC, so the app and the browser tab never drift. The window's console errors and failed loads are appended, one timestamped line each, to console.log in Electron's user-data directory (beside console.json; source paths only, never query strings), truncated once it passes 1 MB, so a blank view can be diagnosed afterwards. On macOS closing the window leaves the tray in place and the dock or tray reopens it; elsewhere closing the window quits.

The console URL comes from the first of three sources, and a bad value in that source is a dialog naming it, never a fall-through to the next:

  1. HOLOPHYTE_CONSOLE_URL in the environment.
  2. console.json in Electron's user-data directory, {"url": "…"}; the operator's own URL lives there, never in the repo.
  3. The default http://127.0.0.1:7710/, the first project's port on a host.

Only http and https are accepted.

The tray menu is rebuilt every ten seconds from /peers on that URL and then /status and /attention on each daemon it names (and /runs on an idle one, for its last merge), with the drawer's wording: what needs you first ("Nothing needs you" when nothing does), one line per project (holophyte · working KO-n · hb 12s, or idle · last merge KO-n · 3m), a hosts line (1 host · 3 daemons), then the three fixed entries. Clicking an attention or project line shows the console. The tray glyph is the template when every daemon is idle and the drawer's warn or bad variant for the worst state shown (something needs you; a daemon is unreachable); bun run icon renders those variants from assets/menubar-{warn,bad}.svg into console/electron/dist/, and start and package run it first.

A daemon beyond loopback wants its serve token (see docs/reference/http.md, Authentication); the tray never prompts for one. It reads them from console.json, per HOST:PORT address as /peers names it, either inline or as the path of the file the daemon's own [serve] token_file holds, the same file the drawer's [[daemon]] token_file points at:

{
  "url": "http://127.0.0.1:7710/",
  "tokens": { "writer-2:7710": "…" },
  "token_files": { "writer-3:7710": "~/.holophyte/writer-3.token" }
}

A token_files path may start with ~ or be relative to the user-data directory; tokens wins over token_files for the same address. A daemon whose token is missing or wrong shows needs token on its line until the file is fixed; one that does not answer within two seconds shows unreachable. The window seeds the page's tokens from the same console.json entries once it has loaded, under the keys the Hosts card reads, and reloads once when that changed anything, so a host the tray can see needs nothing pasted. The commands, run from the repo root:

ELECTRON_SKIP_BINARY_DOWNLOAD=1 bun --cwd=console/electron install --frozen-lockfile
bun --cwd=console/electron test
node console/electron/node_modules/electron/install.js
bun --cwd=console/electron run start

test exercises the pure URL resolver in console/electron/config.ts, the summary builder in console/electron/tray.ts and the poll in console/electron/poll.ts (with a fake fetch), and needs no Electron binary, which is why the verify install skips the download. start needs the binary, and a second install after the skipped one reports no changes and leaves it absent, so fetch it by running Electron's own postinstall script directly (the node …/install.js line above; it is a no-op once the binary is present). Then run start builds main.ts to console/electron/dist/ (main.cjs, CommonJS with electron left external, since the package is "type": "module") and launches it through the top-level main.cjs. console/electron/dist/ and console/electron/node_modules/ are git-ignored.

To put the console in the Dock like any app, package it:

bun --cwd=console/electron run icon
bun --cwd=console/electron run package

icon renders assets/logo.svg to console/electron/dist/icon.png at 1024×1024 through @resvg/resvg-js, so no image tool has to exist on the machine. package runs build and icon itself, then electron-builder (configured in console/electron/electron-builder.yml) writes console/electron/dist/mac-arm64/Holophyte.app and, one level up, console/electron/dist/Holophyte-<version>-arm64.dmg (the builder puts each build's artifact in the output directory and the unpacked app in its per-arch subdirectory). The app is ad-hoc signed: the config sets identity: "-" explicitly, since the builder's default is to skip signing when no certificate is in the keychain; notarisation, auto-update and other platforms are not covered. The packaged app finds the console URL exactly as the repository run does: from HOLOPHYTE_CONSOLE_URL or console.json, never from anything baked into the bundle.

console/src/next/components/HostToken.tsx manages each Hosts card’s browser token, including saving, forgetting and undo.

console/src/next/components/HoldTab.tsx shows project admission, reasoned hold and release actions, and the polled admission history.