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— thelooppackage; empty.holophyte/pr/__init__.py— theprpackage; empty.holophyte/babysit/__init__.py— thebabysitpackage; empty.holophyte/review/__init__.py— thereviewpackage; empty.holophyte/story/__init__.py— thestorypackage; empty.holophyte/board/__init__.py— theboardpackage; empty.holophyte/isolation/__init__.py— theisolationpackage; empty.holophyte/agents/__init__.py— theagentspackage; empty.holophyte/host/__init__.py— thehostpackage; empty.holophyte/serve/__init__.py— theservepackage; empty.holophyte/config/__init__.py— theconfigpackage; empty.holophyte/cli/__init__.py— theclipackage; empty.holophyte/cli/cli_project.py— project registration, listing and admission commands.holophyte/cli/cli_story.py— the--file-story SLUG,--approve-story KEY-nand--witness-pass KEY-noptions 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,--abortand the rest.holophyte/cli/board_verbs.py— the modes on the built board:--sweep [--act],--board-diff,--board-import,--move,--cancel, andrequire_board().holophyte/cli/host_modes.py— the host forms of--status,--serveand--supervise, the read-only modes, andstart_supervisor().holophyte/holo/__init__.py— theholocommand's package.holophyte/holo/cli.py—main(), theholoconsole scriptpyproject.tomldeclares:--versionprints the package version and the checkout's shortHEAD; a subcommand resolves its project, translates through the grammar table and runs as_legacy_cli(), a write command throughrun_write(),reportthroughreport_page, or answers as a read;holo projectisfactory.py project.holophyte/holo/completion.py—holo completion bash|zsh|fish's scripts and the hiddenholo __completehelper they call: candidates from the parser the grammar table builds, and ticket keys fromholo board --jsoncached 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— theholoreadsruns,run N,attention,boardandticket KEY: each calls the daemon's view in process, prints its body for--jsonor a compact listing of it, and maps its status to the exit.holophyte/holo/resolve.py— aholocommand's project from-p,HOLO_PROJECT, the current repository orclient.toml'sdefault_project, the client config reader, and the refusal when none answers.holophyte/holo/grammar.py— theholocommand table: one canonical row perfactory.pymode, thesend-back,steer,startandstoprows, the interventions actions each write row records, theticket VERBandloop start|stopaliases, and their translation to the argvbuild_parser()parses; and the read rows,run show Namong them.holophyte/holo/mcp_server.py—holo mcp: the MCP SDK's low-levelServeron stdio, listing the tool table's rows and answering a call by running itsholocommand; the SDK is imported only here and in the HTTP form below, and without itholo mcpexits 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 anOrigin(403) and every method butPOST(405); the writes are listed only underhost.toml's[serve] actions, and it exits 0 when the checkout'sHEADmoves.holophyte/holo/mcp_tools.py— theholo mcptool table: each tool's name, input schema, tier and theholo ... --jsonargv 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 asrecorded, printed as JSON with--jsonor as a✓/✗line.holophyte/holo/transport.py— aholocommand run onclient.toml'shostover ssh: the remote command line, the ssh call, its exit code and thevialine;holo follow's JSON lines rendered as each arrives andholo status --watch's status object asked for each frame;HOLO_TRANSPORT=localkeeps it local. Withtransport = "http", the route table of the commands the host daemon serves, the bearer call tourl, and the refusal of every other command.holophyte/holo/render.py— the pieces everyholopage shares: a clock time in the client's zone, a relative age, a short hash, and a symbol coloured only on a terminal withoutNO_COLOR.holophyte/holo/status_page.py—holo statuswithout--json: the--status --jsonobject, 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--jsonor rendered as counts first, then the window's runs and, with--notes, the notes.holophyte/holo/run_page.py—holo run Nwithout--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'sunit_action(), or runs the loop in the terminal with--foreground; andholo 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 theProjectvalue.holophyte/config/reader.py—load_config(),config_table(),KNOWN_KEYSand 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 andbudget_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 withCONSOLE_KEYSandsplit_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 theagent()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, itsresultlines kept, and a turn quiet forturn_quiet_secended as stalled.holophyte/agents/turn_progress.py— a streaming Claude turn's tool calls counted and labelled, recorded as aturn_progressevent 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'sBLAST 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: thestale_approval_rereviewevent, and the failure when the review again at that head still cites one.holophyte/review/findings.py—FINDINGS.mdas 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 ashadow_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 acomplexityverdict, 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-ticketand the escalation. Ticket status lives in the store and is projected onto a Linear workflow state bymirror_push()— one way, last write wins, never read back — so the provider'sset_stateis the only writer of that state, and the mapping table besidemirror_pushsays which state each status shows as.holophyte/host/supervisor.py— the stale-run sweep and thePROJECT --superviseloop.holophyte/host/sweep_host.py—--supervise [--once]with no project: the host sweep over every store inhost.toml, its per-run home lock,sweep.json, the round-robin reconcile under a deadline, and one project's failure its ownerror.holophyte/board/board_sync.py—observe_board()(KO-739): in store mode the host sweep's ask of the board'sstates()for every open ticket, once perboard_ask_sec— the state name and column recorded, an issue seen gone twice walkedabandonedor its live run paused. On every pass between those asks it delivers what is queued: the pending notes, and the queued pushes after onestates()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, andadmit()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): theSWEEP_HEADERStable, 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 guardedretire_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'sopen_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, theSupervisorHeldrefusal and thesupervisor_running()probe.holophyte/serve/server.py—--serve PORT|HOST:PORT, the HTTP daemon: it reads by default and writes only through[serve] actionsandconfig_edit.holophyte/serve/views.py— the daemon's store read views:/status,/attention,/boardand/tickets/ID, with the supervisor and parked-ticket items they share.holophyte/serve/serve_config.py— the daemon's/configroutes (KO-394):GET's redacted read andPUT's validated, recorded and backed-up write,textand{"patch": ...}bodies alike.holophyte/serve/serve_actions.py— the daemon'sPOST /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'shold,release-hold,pauseandresumeactions (KO-609): the CLI's store calls behind the action token, each requiring a note, thepausedattention item, andGET /holds, the project admission state and its change history.holophyte/serve/serve_merge.py— the daemon'sGET /runs/N/mergeandPOST /actions/merge: a run parked for a human's merge read for readiness, and released through the store'sapproveonly when ready.holophyte/serve/serve_ask.py— the daemon'sPOST /actions/askandGET /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/ledgerand/runs/N/files, their query parsers and the origin-link pair.holophyte/serve/serve_history.py— the daemon'sGET /history: every open run and every run started since a time, one row per run.holophyte/serve/serve_findings.py— the daemon'sGET /findings: the window's runs with their rounds,roundandadjudicationledger rows and advisories, and its follow-up rows with their settled state.holophyte/serve/serve_stories.py— the daemon'sGET /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'sHEADmoves, 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-treeagainstHEAD:console, a rebuild into a staging directory swapped in on success, the old build kept on failure.holophyte/serve/serve_host.py—--servewith no project: one daemon for every project inhost.toml, each under/projects/NAME, the root's/statusand/attentionfor the host, the machine token, one project's failure its own 503 orerror, andrun-sweep's host ledger.holophyte/serve/contract.py— the poll and run routes' response types: oneTypedDictper body/status,/attentionand/peersanswer, a project's and a host root's, and/runs/N,/runs/N/mergeand/runs/N/asks,ROUTESnaming each, andmain(), 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/attentionand/status.holophyte/serve/contract_emit.py— aTypedDictturned 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) andPUT /projects/NAME/tickets/ID, an edit authoredconsole.holophyte/redact.py— secret values in aconfig.tomltext, found by walking its TOML syntax: hidden forGET /config, put back forPUT.holophyte/files.py— the files a run touched, read from git in the project's checkout under a timeout: what/runs/N/filesanswers.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, itscomplexityevent 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 theESCALATION:choice the escalated turn declares.holophyte/loop/contradiction.py— an acceptance criterion the implement or fix turn declares unmeetable as written: the brief, theCONTRADICTION: criterion CNlines, thecontradictionevent 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 thetrim: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 ashadow_resultevent.holophyte/loop/shadow_spawn.py—start_shadow: a fresh claim's detachedfactory.py --shadowbeside the primary, recorded asshadow_started; and that mode, one shadow a project undershadow.lock, elseshadow_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 pendingholo steernotes: 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):mainmerged 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-ffmerge onto main.holophyte/story/story_drift.py— a story child at the local merge gate after siblings merged since its claim: thestory_driftevent 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): theOUTCOME: NOT_REPRODUCEDdeclaration, the evidence check in place of round 1, and thenot_reproducedpark.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 thewitness_precheckandwitness_precheck_resultsevents.holophyte/loop/witness_turns.py— the witness pre-check's gaps sent back to the implementer before the round 1 review, at most twowitness_turnturns 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— theLocksprotocol aProjectcarries as itslocksattribute, andFileLocks, whosemerge()islive_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/--repointstore 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_nextover_admit_ticket's questions and_claim_run's lease and board label, then_cut_worktree/reuse_leftover,run_worktree_setupunder_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] setuprunner (run_worktree_setup) andretire_worktreefor 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 firststore.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 mostREADMIT_LIMITtimes 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 itmerged, andwalk_closed()abandons the story of a parent the board canceled.holophyte/board/native_board.py—NativeBoard(KO-754), the boardboard_for()builds for[board] kind = "native": its reads answer from the project's store, filing, editing and the body read-back go throughstore.board, the lease and state writes do nothing, andcomment()records a note. It never asks Linear.holophyte/review/freshness.py— the claim's freshness check (KO-709):stale_reasons()asks themainref for each file a ticket names, andpark_stale()mirrors a stale ticketneeds_spec, comments once and moves it to Backlog. The claim reads amainjust fetched from origin. On a native boardrecheck_stale(), called fromsync_board()on every pass, returns a ticket parked for stale landmarks (not one the critic parked) toready(orblocked_on_deps) once none of its reasons holds, and--move KEY-n readyre-checks it the same way. The move also re-readies a critic park, running only the deterministic checks, and records arequeueintervention 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 --updateand--move KEY-n ready.holophyte/review/landmarks.py— what counts as a lost landmark:MainTreecaches main's file list, blobs andgit grepholders for one check;path_findings()andsymbol_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 afterin,from,ofor(, or with a path leading its item before:. Advisories print at filing and become a claimed run'swarningevents; they never park.holophyte/environment_git.py— excludes the filtered.envfrom 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_patternsand refuses a match by location and pattern index.holophyte/loop/pause_notice.py— a paused run's pull request gets theholophyte:pausedlabel and one notice comment;--resumeremoves 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 thegh/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 againstorigin, 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; underpr_draft = "ui", holding the draft for the maintainer to mark ready.holophyte/host/ci_wake.py— when the reconcile sends a run parkedciback 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 ofholophyte/pr/github.py:pull_status()for the parked-run reconcile,pr_state()andfold_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_basewith 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=Nquery, at most twice.holophyte/pr/mockup.py— a ticket's## Mock-upmedia links: the filing checks against[merge.media_bucket] public_base, the download of each.pngsnapshot and.zipbundle into the task worktree's ignored.holophyte/mockup/with itsmanifest.json, and the implementer's brief that lists them.review_carrynames that directory and.holophyte/captured/(the PNGs of the last UI capture) for every review workspace to copy read-only, andreviewer_brieflists 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 throughmain's merge queue (KO-712): the rules read for amerge_queuerule, theenqueuePullRequestmutation, 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 markedanalysis, 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-onlyadjudicatetriage of the untriaged Devinanalysiscomments 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 theclaudeorcodexCLI: prompt, schema, parsing, usage, and the probe with its fallback.holophyte/loop/follow_ups.py— a fix turn'sFOLLOW_UPlines 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_storywrites a story proposal,standalonefiles 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 lastSCOPE: in_story|standalone: REASONline, andjudge()runs the turn on the adjudicator seat as terminal adjudication does, defaults tostandaloneon a failed, timed-out, outage or unreadable reply, and records itsfollow_up_scopeevent.holophyte/loop/failure_triage.py— a failed run's cause question (infra,codeorspec) after close-out, itsfailure_triageevent, and the one factory requeue a confidentinfraanswer 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 ofmain, 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 arouteoutage 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, theADDRESS/DECLINE/HUMANverdict 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, animgto itsalt,detailsblocks 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_roundsis spent.holophyte/babysit/description_review.py— a covering review whose every blocker is taggedpath: "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] carrydirectories, 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 andred_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.jsonbeside the store names the witness set the loop's witness step is running:mark_running()writes it,clear_running()removes it andrunning_set()reads it for/status'switnesses, 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'smergeShais 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 pathschanged 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,carriedFromnaming 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-blockingwitness-tree.lock, checked out detached at the commit and cleaned keeping each[worktree] setuptable step'skeeppaths;witness-tree.jsonstamps each table step's key over itsrunand its inputs at the commit, so a step whose key matches and whosekeeppaths 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 oneunmetorregresseddecision per witness;settle_owed()keeps a story the lock kept from settling pending for the next pass at the same tip, andrerun_owed()keeps a red whose rerun did not run from parkingregresseduntil the next pass runs it again. Its close note names each open proposal.holophyte/story/proposal_decisions.py—--decide KEY-n pNanswers a story's proposed child: accepting files it in Backlog through the board, depending on the child that raised it with aRole: scaffoldingline (on Linear a sub-issue of the parent carrying the board's label, mirrored into the store), thenaccept_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()overfactory.py --workerchildren 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 therunEventswriters, 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 §3TICKET_TRANSITIONStable andtransition()/walk_ticket(),mirror_ticket, §2'spickable()/pickable_tickets()and the Mermaid state-graph renderer, re-exported from the package.store/board.py—file_ticket()andedit_ticket(): a native board's ticket numbered fromprojects.ticketSeq, validated byticket_problems()as--file-ticketvalidates, and an edit refused at a stale revision (KO-750).store/revisions.py—record_board_fields(): oneticketRevisionsrow per change of a ticket's board-owned fields, healing a row an older build changed first (KO-736).store/notes.py—record_note(): oneticketNotesrow 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()andmark_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, andgap_layer_counts()counts each gap's latest layer in ladder order.store/follow_ups.py—record_follow_up()stores a pendingfollowUpsrow 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 foursettle_*()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 aproposedstoryProposalsrow and settles the follow-up,open_proposals()reads a story's open proposals,story_duplicate()finds the child or proposal a follow-up repeats, andaccept_proposal()andreject_proposal()answer one with itsdecideintervention.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, andabandon_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 bystore/read.py.
At the root:
factory.py— the entry point: importsclifrom the package and calls it. Holds nodeforclassof its own.provider.py— theProviderprotocol 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 sameTicketfields as the magic lines.ticket_depends.py— finds a ticket body's estimate line and rewrites itsDepends 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 DIRexits 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 declaresrelative/path: exact literallines 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 fromlanding/public/(design note 6); the rootwrangler.jsoncserves this manual at https://docs.holophyte.ai. Its screenshots and video are not in the repository: they live in the R2 bucketholophyte-mediaunderdemo/, served at https://media.holophyte.ai/demo/. After a new capture,scripts/landing_media.sh CAPTURE_DIRuploads 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 fromruns/reviewRoundsas 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, onetest_*.pyper surface, withtests/fake_agent.py,tests/procs.pyandtests/waiting.pyas shared helpers. Run it withHOLOPHYTE_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:
HOLOPHYTE_CONSOLE_URLin the environment.console.jsonin Electron's user-data directory,{"url": "…"}; the operator's own URL lives there, never in the repo.- 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.