File a GitHub issue. Walk away. Wake up to a merged pull request.
void_furnace is a dark factory — an autonomous code-generation pipeline that turns operator-filed GitHub issues into merged pull requests on a separate target repo, with no human typing code in the loop. One Claude subprocess plans the work, another writes the code, and a third — the critic — reviews the diff without ever seeing what the coder wrote about it. The factory runs on a systemd timer every 10 minutes. The operator files the issue and reviews the result.
First substrate UAT: PR #11 on
void_furnace_calcmerged 2026-05-15 21:15:21Z. $0.088 synthesized cost across 5 ticks. See postmortems/2026-05-15-phase-1.7-substrate-uat.md.
Why does this exist? A big prompt to one model would produce a working calculator faster and simpler. void_furnace is not for that goal — the calculator is a benchmark scaffold; the loop around it is the product. See documentation/wiki/why-void-furnace.md.
void_furnace operates on a separate target repo — the factory never touches its own code in the same iteration that touches the target's code, and the target repo never sees the factory's machinery. The separation is structural: different repos, different CI, different deploy lifecycle, different review surface.
| Repo | Purpose | Visibility |
|---|---|---|
aberson/void_furnace (this repo) |
Factory machinery: orchestrator, workflows, prompts, scripts, tests | Private |
aberson/void_furnace_calc |
Target product: Python calculator (lib + CLI + Streamlit UI) | Private |
The target's three governance docs (MISSION.md, FACTORY_RULES.md, CLAUDE.md) are read-only from the factory side. The factory authenticates against the target via a fine-grained PAT scoped to that one repo. See documentation/wiki/target-separation.md (built out in Phase G) for the full no-cross-touch model.
The protected-files allowlist enforces the no-cross-touch boundary at PR level: any coder PR that modifies the governance docs, the target's CI workflow, ui-smoke.sh, or the target's pyproject.toml is auto-rejected before the critic ever sees it. The rule is operator-time-only: only the operator can change those files, by hand, with no factory involvement.
The critic's prompt is constructed from the issue body, the unified diff, the test output, and the target repo's three governance docs — and nothing else. The coder's narration, PR description, planner notes, and tool transcript are structurally excluded. It is not possible for coder rhetoric to influence critic verdict, because the prompt-template function signature cannot accept those inputs. An AST regression guard locks the signature shape forever.
- Holdout principle — the critic never sees coder artifacts. Enforced by prompt-template construction with a function signature that literally cannot accept those inputs, plus
tests/test_holdout.py::test_signature_excludes_coder_artifactsasserting the absence. - Three-doc constitution —
MISSION.md,FACTORY_RULES.md,CLAUDE.mdin the target repo govern what the factory may build, how it operates, and what gates it runs verbatim. Precedence is MISSION > FACTORY_RULES > CLAUDE. - Protected files allowlist — coder PRs are auto-rejected if they touch governance docs, CI config,
ui-smoke.sh, orpyproject.tomldeps in the target repo. - Recovery sweep — every tick reconciles 6 enumerated inconsistency cases (stranded in-progress issue, unverdicted PR, exhausted fix budget, stale lock, role-attempt-cap exhaustion, stale needs-fix).
- Runs on Claude today; open models are a future update (TBD) — the live factory (
harness/CURRENT=v1) runs its planner, coder, and critic on Claude Sonnet 4.6 via subscription OAuth (CLAUDE_CODE_OAUTH_TOKEN, not a paid API key). This is the current, proven configuration — the factory's merged-PR track record was produced on v1. A future harness version (v5or later) will run the same loop on an open-weight/local-model pipeline (models served locally by llama.cpp — no paid API key and no subscription). That open runtime is built and has run end-to-end on the substrate with zero OAuth, but it is not yet the default: its coder quality is still being validated, so the live loop stays on Claude/v1 until the open pipeline is proven to ship clean, mergeable PRs.
Each tick fires every 10 minutes and either advances one issue's state or runs a recovery sweep. The 9 factory:* labels are the state machine; the holdout barrier is the load-bearing safety property between coder and critic.
┌─────── one tick (every 10 minutes) ──────────┐
│ │
operator files ──►│ triaging ──► accepted ──► in-progress │
issue │ │ │ │
│ │ ▼ │
│ │ ┌─── coder writes diff │
│ │ │ │
│ │ ▼ │
│ │ ┌─── holdout barrier ─────┐ │
│ │ │ (critic sees diff + │ │
│ │ │ issue + tests + │ │
│ │ │ governance only) │ │
│ │ └─────────────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ critic verdict on PR │
│ │ │ │
│ ▼ ▼ │
│ rejected approved ──► auto-merge
│ (terminal) needs-fix ──► back to in-progress
│ needs-human (escalation)
└──────────────────────────────────────────────┘
The fix loop is bounded: after 2 fix attempts on the same PR, the orchestrator escalates the issue to factory:needs-human so the operator can decide. Recovery sweep catches mid-tick crashes; the kill switch (factory:paused) lets the operator stop work on any issue without killing the timer.
The point of the critic is to answer "is this change good?" — not "did the coder make a convincing case?". Separating those two questions means the critic's verdict is grounded in the diff, the test output, and the target's governance — not in coder narration. The wall:
┌─────────── critic subprocess receives ──────────┐ ┌─── critic NEVER sees ───┐
│ issue body │ │ PR description │
│ unified diff (gh pr diff) │ │ PR title │
│ pytest stdout/stderr │ │ commit message body │
│ ui-smoke stdout/stderr │ │ planner plan/notes │
│ gh pr checks │ │ coder tool transcript │
│ CLAUDE.md gate list (verbatim) │ │ coder "summary of work"│
│ FACTORY_RULES.md gate list (verbatim) │ │ │
└─────────────────────────────────────────────────┘ └─────────────────────────┘
▲ ▲
│ enforced by │
└──── prompt-template function signature ────┘
(compiler-checked + unit-tested)
src/void_furnace/holdout.py exposes the critic-prompt builder with a function signature that takes only the left-side inputs. Adding a right-side input would require changing the signature, which the AST guard test fails on. Future contributors cannot accidentally relax it.
The factory has two other AST-enforced invariants in the same shape: the 9 factory:* labels are defined exclusively in src/void_furnace/labels.py (so no inline label literal can drift); and the src/void_furnace_ui/services/ modules are forbidden from importing streamlit (so the UI render layer cannot reach down past the service layer). The pattern is "make the dangerous thing impossible, then assert that impossibility in CI." Documentation is not a control; signatures and grammars are.
A bulleted timeline. Dates are accurate at landing; the README is intentionally coarser-grained than the postmortems and retrospectives it cites.
The first few months were substrate and pipeline scaffolding; from May 2026 the project has been alternating "ship features the factory needs" with "run the factory against a real target and learn what broke." Each substrate UAT and each retrospective surfaces real-world failure modes that feed back into the plans. The Phase 1.7 Operator UI exists because reading raw journalctl to understand a tick is too slow; the recovery-sweep extension from 4 to 6 cases exists because soak tests found two new inconsistency shapes; bug #50/#51 exist because Gen-0 produced eleven zombie PRs on a single stuck issue.
- Q4 2025 — Initial plan landed. Substrate model chosen (WSL2 + systemd timer on Ubuntu 24.04 LTS under Windows), three-doc constitution model designed, holdout principle written into KDD-2.
- 2026-05-09 — V1 manifest complete. All 12 code-only build steps shipped across Phases 0–2. First end-to-end tick fires; no real target work yet.
- 2026-05-11 — Tier 1 substrate smoke surfaced four substrate-only bugs that 176 unit tests + 4-pass code review had missed. Lesson codified at
.claude/rules/substrate-testing.md: unit tests alone are necessary but not sufficient. - 2026-05-13 — Phase 1.5 V1 hardening (4 of 6 steps): run-dir schema reconciliation, recovery sweep extended from 4 to 6 cases, invariant assertions + heartbeats, triage pre-flight.
- 2026-05-13 —
void_furnace_calcretro target edits applied: HI-1 arbitrary-precisionDecimal, HI-3 single-surface Streamlit UI. Invalidated iter-1 merged PRs #5/#8/#9. - 2026-05-14 — Phase 1.6 critic + triage hardening: deterministic MISSION-named artifact pre-flight + edge-of-range acceptance-test rule.
- 2026-05-15 — Phase 1.7 Operator UI shipped: Streamlit 5-page app at
127.0.0.1:8502, deployed asvoid_furnace_ui.service. PR #11 merged onvoid_furnace_calcat $0.088 across 5 ticks (first substrate UAT PASS). - 2026-05-15 — Phase 1.5 + 1.6 substrate UATs PASS (combined Tier 1).
- 2026-05-20 — Bug fixes #50 (one PR per issue, retries push to existing branch) and #51 (orchestrator skips
factory:needs-human). Closed 8 sites of label-flip-without-state-record. iter-1 zombie PRs cleaned up. - 2026-05-20 — Gen-1 (iteration_id=2) launched against the new Decimal HI-1 + single-surface HI-3 governance.
- 2026-05-20 — V2 Phase A landed: per-iteration aggregation, hard-invariant scoring, 5-gate verdicts. Adds
iteration_idpropagation,iteration_aggregatesSQLite table, post-PR analyzer, and a new Iterations page in the operator UI. - 2026-05-21 — Phase 1.8 Reliability Hardening plan landed (12 step issues). Goal: factory consistently produces mergeable PRs, not just calculator-correct PRs.
- 2026-05-21 — Phase G GitHub presence buildout plan landed (this plan; 12 steps). README rewrite, 15-page wiki, MIT LICENSE, 5-badge cluster, 6 PNG screenshots, repo topics. Visibility flip remains explicitly out-of-scope.
- 2026-05-26 — V2 plan landed (since folded into documentation/master_plan.md): 5 phases closing the issue→factory→retro→governance-PR→reseed loop. V2 Phase 1 shipped:
vf-check-e2eskill orchestratesvf-check-launcher+vf-check-uiplus 6 cross-surface assertions (substrate ↔ launcher ↔ UI agreement). V2 Phase 2 (early) shipped: operator-launcher gains write actions via orthogonal ArgSpectype(text_input / text_area) +to_tempfilefields, plus asubmit-issue-targetcatalog entry that files issues against the target repo with operator-typed bytes routed through tempfiles — full shell-escape immunity at the PowerShell-Commandboundary. - 2026-05-26 — V2 Phase 2-late shipped: L2 launcher startup sweep (bounds the orphan-tempfile leak from bug #92 to one-per-launcher-session); L3
retro-startlauncher entry withArgSpec.validate_patternregex enforcement at Python layer BEFORE PowerShell substitution (closed HIGH-sev subexpression-injection bypass found in review); L4 hid the UI Submit tab behindVOID_FURNACE_UI_SUBMIT_TABenv-var (default OFF) and removed the now-redundant Retro launch hint — operator-launcher's:8503catalog became the canonical write surface. - 2026-05-27 — V2 Phase 2-late L5 shipped: the UI Submit tab is deleted entirely (function, services, env-var flag, related tests).
pages/1_Operator.pynow calls_render_retro_tabdirectly. Added an AST regression guard asserting_render_submit_tabandVOID_FURNACE_UI_SUBMIT_TABcannot be re-introduced. 804 tests passing. - 2026-06-03 — Phase OSS Steps 1–7 shipped (public-readiness mechanics): Hetzner host references reframed to WSL2; committed
runs/test logs untracked; a cross-platform Python public-snapshot builder (scripts/build-public-snapshot.py) + verification gate (scripts/verify-public-snapshot.py) +snapshot-exclude.manifestthat produce and audit curated, history-free public exports (stub prompts, omit the calc constitution, fail-loud on secret/cross-project/dangling-ref leaks);PROMPTS.md+ canonical stub prompts;void_furnace_calcpublic-prep; and a provisional open-modelharness/v2(non-coder roles → open providers, coder → OAuth dev/test bridge,CURRENTstaysv1). 1794 tests passing. The open agentic coder (Steps 8+) and the public visibility flip remained — the flip stays HELD until the open loop is proven on the substrate. - 2026-06-03 — Phase OSS Steps 8–10 shipped (open agentic coder, the flip-blocking track): a spike proved a hand-rolled tool-use loop on a local open-weight model (
qwen2.5:7b-instructvia ollama) can make a real file edit and run a test — it even self-corrected a failing import — at zero API cost; that loop is productized as theOllamaAgenticAdapter(src/void_furnace/models/ollama_agentic.py, providerollama_agentic) behind theModelClientseam (path-confined writes, a process-tree-kill bash timeout, cost pinned to $0 for local runs); and an integration test drives the realcoder.run → adapter.invokepath end-to-end, failing loudly if the wire is severed. The adapter is built but not yet the production coder (that lands with the all-openharness/v3once benchmarked). 1839 tests passing. Steps 11 (per-role open-model benchmark) → 13 (substrate soak with OAuth unset) + the flip remain. - 2026-06-06 — Phase OMR Step 2 (#186) — the open-model runtime is up on the WSL2 substrate: built mainline llama.cpp from source with CUDA (no prebuilt Linux CUDA binary exists — ggml-org#16205), installed llama-swap v223 + both candidate GGUFs (Qwen3-Coder-30B-A3B-Instruct-UD-Q3_K_XL + Qwen3.6-35B-A3B-IQ3_S), generated the config, enabled the service, and verified
coder-30bserves a structuredtool_calls(get_weather({"city":"Paris"})) via--jinjawith zero OAuth. Two Step-1 substrate-only bugs were found + fixed (013bf99, each with a regression test):config.pyemitted a bare--flash-attn(current llama.cpp requires--flash-attn on);install-llama-runtime.shmkdir'd the runtime dir as root before the factory-run config write (nowchowns it). 8 of 18 OMR steps done; #186 closed. Production untouched (harness/CURRENT=v1/OAuth/paused; runtime INERT untilharness checkout v4); the v4 flip stays HELD pending the Step 6 bake-off + soak. - 2026-06-05 — Phase OMR (Open-Model Runtime) — 7 of 18 code steps shipped via
/build-phase(the dependency-buildable ones): allama-serverlaunch-config generator + llama-swap lifecycle manager (scripts/llama_server/); a credential-freellamacppsingle-shot adapter + allamacpp_agenticcoder adapter against llama-server's OpenAI/v1/chat/completions(shared agentic tool layer extracted tomodels/_agentic_tools.py); the coder benchmark re-scored on the produced diff instead of the agentic transcript (fixes theno_diff-blind score); a Python Playwright screenshot wrapper (judges/ui_capture.py); a system-metrics service (services/system_metrics.py, CPU/GPU/mem via psutil + nvidia-smi) surfaced on the /Health page. 2102 tests passing. The remaining 11 steps are operator/substrate (install + RAM), the wait-type model bake-off (35B-VL vs 30B-coder, diff-scored), the bake-off-gatedharness/v4+ VL UI-judge + governance judge + transparency banner, and the smoke/soak/UAT. Production untouched (harness/CURRENT=v1/OAuth/paused); the v4 flip stays HELD until the soak. - 2026-06-06 — Phase OMR Steps 6 + 7: diff-scored zero-OAuth bake-off on the substrate (#190) → coder-30b wins the coder role (mean 3.18 vs 1.67 for general-35b-as-coder), general-35b wins all non-coder roles (triage 9.0, validator 9.0, retro 8.5);
harness/v4/authored (#191) with all roles onllamacpp/llamacpp_agenticper bake-off winners;--timeout-sCLI flag added to benchmark. Code review gauntlet applied 5 fixes. 2108 tests passing. - 2026-06-06 — Phase OMR Steps 10 + 11 + 14 (judges + transparency banner): Step 10 (#194) — Playwright VL UI-judge (
judges/ui_visual_judge.py, holdout-safe gather/classify/sweep/cli,ui_verdictstable,void_furnace ui-judge runCLI); Step 11 (#195) — governance-compliance judge (judges/governance_compliance.py,governance_verdictstable, FACTORY_RULES/MISSION check,void_furnace governance-judge runCLI); Step 14 (#198) — transparency banner:deployed_version.pynow resolves active per-role model (active_modelsdict,"provider:model_id"format) and live llama.cpp runtime params (RuntimeInfofrozen dataclass fromscripts/llama_server/status.read_status),_banner.pyrenders 3-line caption (sha/harness · models · runtime), degrades gracefully when llama-server unreachable. 13 of 18 OMR steps DONE. 2173 tests passing. Production untouched (harness/CURRENT=v1/OAuth/paused); v4 flip HELD until Step-16 soak. - 2026-06-07 — Phase OMR Step 15 smoke gate (#199) ATTEMPTED: v4 routing CONFIRMED (all
harness.model_resolvedlines showharness_version=v4, zero OAuth, usd=0); two blockers: (B1) triage cold-swap timeout — general-35b takes ~194s to load after coder-30b; adapter timeout hardcoded to 180s; (B2) coder no_diff (pre-existing T1/T2, ~27% rate). Added 6 Step 15 launcher buttons;app.pytransparency block moved below the flow diagram. 2175 tests passing. - 2026-06-07 — Phase OMR B1 cold-swap timeout fix (
7ea4ad9):triage.py+retro.pynow readparams.timeout_sfrom harness assignment;harness/v4/models.tomlsetstimeout_s=360for both roles (2× safety margin over the observed 194s cold-swap). v1/OAuth path unchanged. 2177 tests passing. Next: deploy → reset target → re-run Step 15 smoke gate. - 2026-06-07 — Iteration report feature shipped (issues #208–#210):
void_furnace iteration report [--id N] [--list]generates a Markdown summary of a factory run — per-issue label timeline, triage-verdict snippets (120-char truncation), PR diff stats, retry count, cost, GitHub links — and writesiteration-report-<id>.mdto cwd. Two launcher buttons ("Iteration report (latest)", "Iteration report (select)") added to the operator-launcher catalog. 2186 tests passing. - 2026-06-17 — Phase SWF (Sonnet-window fixes) shipped (issues #218–#224): six surgical fixes from an Opus re-review of the commits authored while the main-loop model was silently downgraded Opus→Sonnet (Jun 4–17). The load-bearing ones: the
llamacppagentic adapter's recovered-from-text tool calls now carry OpenAI's requiredtype:"function"(would 400 the next turn on the flaky-parser recovery path); theharness checkout v0all-Claude rollback is fixed (the M5 wiring resolved only the split validator slots and silently routed the adversarial critic to DeepSeek anyway — now a unifiedvalidatorslot drives single-critic mode, and a malformed config fails safe to the R8 fallback instead of crashing the tick); plus the iteration-report's deadvalidator_verdictsquery removed, thellamacppcredential-free regression test restored, the governance judge's protected-files list sourced frompolicy(one source of truth), and two OSS-soak launcher buttons hardened. None touch the livev1/OAuth path. 2198 tests passing. - 2026-07-04 — Phase RG (Readiness Gate) Step 1-prep shipped (#250): a research-tooling judge-bench harness at
scripts/readiness_bench/— a labeled-set builder over the Investigation-01 ground-truth key (4 canonical v5/coder-30b archetypes: ops→TRACTABLE, cli→ENRICHABLE, ui→INTRACTABLE, identity→BLOCKED, loaded from the real seed files + authored specs + escalation-history cases), three feasibility-judge runners behind amodel_client_factoryDI seam — (A) Claude ceiling, (B) local-LLM via the existingLlamaCppAdapter(parameterized model_id → coder-30b or a general-instruct model), (C) a zero-model deterministic classifier — and a scorer + CLI (python -m scripts.readiness_bench --judge deterministic|local|claude). Holdout-safe prompt (byte-identical regardless ofground_truth); the deterministic judge scores 4/4 on real seed text (independently verified). Importsvoid_furnaceread-only; nosrc/changes. The phase HALTS at Step 1 (#251) — the operator judge-path decision gate, which runs the bench on the substrate to pick the feasibility-judge path; Steps 2–7 (#252–#257) are parameterized on that decision. 2354 tests passing. - 2026-07-04 — Phase RG (Readiness Gate) Steps 2–5 shipped (#252–#255): a new intake gate between triage-ACCEPT and the coder that forecasts whether the open coder can build an issue and adapts it — without splitting issues. The judge is (C) deterministic (Step 1 substrate bench: 4/4 on the calc archetypes = the Claude ceiling, but fully local + zero-cost + zero-OAuth; coder-30b-as-judge was too slow and mode-collapsed). New
src/void_furnace/readiness/package (aReadinessJudgeseam + the deterministic classifier, one source of truth — the research bench imports it). A new orchestrator picker branch overfactory:acceptedissues escalates INTRACTABLE/BLOCKED tofactory:needs-humanand auto-enriches ENRICHABLE (appends the strict-mypy annotation clause + re-admits — crash-safe, attempt-capped, deterministic, no LLM).readiness_verdicts+readiness_enrichmentstables; new (reserved)factory:refininglabel; read-only Flow-page annotation. Ships DARK behindVF_READINESS_GATE(default OFF) — inert in production until the operator activates it for the soak. Holdout-safe (reads the issue spec only). 2423 tests passing. Phase halts at Step 6 (#256), the substrate soak that measures the autonomous-success lift. - 2026-07-06 — Phase CV (Convergence & Valid Measurement) automated span shipped (umbrella #262, issues #263–#274 closed): the convergence-fix + valid-measurement track that gates the open-runtime flip. Steps 1–5 fix the FACTORY convergence bugs the RG Step 6 soak surfaced — a per-round-delta + deterministic-progress convergence gate (
orchestrator.py,policy.py); machine red-gate evidence threaded into the coder fix prompt (validator.py→coder.py, fences inlabels.py); a verdict-vs-gates contradiction detector with refute-precondition reform (validator.py); a stale-feedback skip (unchanged-headno_diff) + provider-accurateno_diffmessaging (coder.py,run_dir.py); and hygiene (quiet checks-fetch, escalate-pathreadiness_verdictsrow, coder timeout fromparams.timeout_s). Steps 6–9 fix the benchmark instrument: G2 fallback-abort, G1 productionbuild_promptassembly (deletes the{{key}}substitution path + closes apr_descriptionholdout leak), G3 deterministicexpected_outcomeaccuracy scoring via productionparse_verdict(judge demoted to secondary), and G4 per-suite anchor calibration + k=3-median judge robustness. Step 10 freezes the launcher (deletes the in-app edit/add/icon-picker UI +icon_catalog.py). Step 11 pre-registers the flip-gate D6 criteria (docs). Step 12-prep lands the Windows-inference kit (scripts/llama_server/windows/, dry-run install-verify + tool_calls gate). 2512 tests passing, 0 type errors, 0 lint violations. Only the automated span shipped — the operator/wait/conditional Steps 12–17 (#275–#281: A1 native-Windows-inference migration, substrate smoke, RG-Step-6 re-soak, judges run, thesis test, conditional v6 bake-off) are PENDING; nothing is deployed or flipped, the liveharness/CURRENT=v1/OAuth path is unchanged, and the open-runtime flip stays HELD.
The full architectural context lives in documentation/master_plan.md; deeper background is in the wiki, the postmortems, and the retrospectives. The future direction (V2 self-improvement loop) is sketched in documentation/master_plan.md § V2.
The factory ships its target product when both autonomous iterations close out without operator intervention. Phase 1.8's job is to make that consistent. Phase G's job is to make the repo presentable. The eventual public flip — after Phase G commits land, after a security pass, after a final operator review — is the project's exit gate from "private prototype" to "public proof of concept." Until then, every operator-side button has a halt-the-world stop, and every coder PR sits behind the critic's wall.
Software the factory depends on. Thanks to the maintainers of each.
- uv — Python package manager and runtime.
- Claude Code CLI + Claude Sonnet 4.6 — the LLM subprocess that plans, writes, and reviews.
- Streamlit — both the operator UI and the target product UI.
- pytest — test gate.
- ruff — lint + format gate.
- mypy — strict type gate.
- GitHub Actions — CI.
- systemd — substrate timing on Ubuntu.
- Ubuntu 24.04 LTS — substrate OS.
- WSL2 — runs the Ubuntu substrate under Windows.
Stack
| Layer | Tool |
|---|---|
| Language | Python 3.12 |
| Package manager | uv |
| LLM | Claude Sonnet 4.6 via Claude Code CLI (live today — harness/CURRENT = v1). An open-weight/local-model pipeline via llama.cpp is a planned future update (harness v5+, TBD) |
| Auth | CLAUDE_CODE_OAUTH_TOKEN — subscription OAuth, not a paid API key (live today). The future open-model pipeline needs no API key and no subscription |
| State | SQLite (mutable) + JSONL (telemetry) |
| Runtime | systemd timer + service on Ubuntu 24.04 LTS |
| Host | WSL2 (Ubuntu 24.04 LTS under Windows 11) |
| Target product UI | Streamlit on port 8501 |
| Factory operator UI | Streamlit on port 8502 (void_furnace_ui.service, optional --extra ui) |
Full table with rationale: master_plan.md § Stack.
Setup
cd c:\Users\abero\dev\void_furnace
uv sync
uv sync --extra ui # optional — installs the operator UI depsThe factory expects /etc/void_furnace/secrets.env on the substrate (mode 0600) with CLAUDE_CODE_OAUTH_TOKEN and a GitHub fine-grained PAT. The full procedure (substrate provisioning, systemd-unit install, secret seeding, deploy lifecycle) lives in documentation/runbook.md. Do not read or print secret values — see SECURITY.md for the rationale.
Operator UI
The Streamlit operator UI runs on the substrate at http://127.0.0.1:8502, served by void_furnace_ui.service. Three pages, each a separate st.Page route.
| Page (URL) | Shows |
|---|---|
Flow (/) |
Per-issue stage view of the 9 factory:* labels, current PR (if any), and transcript tail of the active role. List + inline detail expander. |
Operator (/Operator) |
Retro launchpad — ripe-for-retro merged PRs + past retrospectives. Read-only; issues are filed via the operator-launcher's submit-issue-target catalog entry on :8503. |
Health (/Health) |
Status banner, factory timer + heartbeat color, cost, last 10 ticks, per-iteration aggregate table (inline-expandable for HI breakdown), Pause/Resume break-glass card. |
The UI is read-mostly. The canonical write surface for issue submission and retro launching is the operator-launcher Streamlit app on :8503 (24+ catalog entries; see operator-launcher/run-launcher.ps1). The UI Retro tab continues to list ripe-for-retro PRs and past retros, but no longer shows copy-paste launch hints — operators use the launcher's retro-start entry instead.
Usage
The five commands below are the CI gate sequence — .github/workflows/ci.yml runs them verbatim on every PR and push to master. Run them locally before pushing:
cd c:\Users\abero\dev\void_furnace
uv sync # resolve + install into .venv
uv run pytest -q
uv run ruff check .
uv run ruff format --check .
uv run mypy src # strict mode on src/ onlyProject layout
src/void_furnace/ orchestrator, coder, critic, triage, state, recovery, holdout, labels
src/void_furnace_ui/ Streamlit operator UI: app.py + 5 pages + 8 service modules
tests/ pytest suite (~2350 tests; AST guards on holdout + labels)
scripts/ deploy.sh, install-systemd.sh, reset-target.sh, _dashboard.sh
systemd/ void_furnace.service + .timer + void_furnace_ui.service unit files
documentation/plans/ one plan per phase; each becomes GitHub issues via /repo-sync
documentation/wiki/ in-tree docs (the directory; not the GitHub-wiki feature)
documentation/investigations/ one-off deep dives that feed plans
documentation/retrospectives/ per-iteration target-product reviews
documentation/appendix/ plan-companion reference material
postmortems/ dated incident + UAT writeups
seed-issues/ starter task templates the factory consumes
Build pipeline
/plan-init or /plan-feature produce documentation/plans/<name>.md
│
▼
/plan-review gap-check the plan
│
▼
/plan-wrap clean-context check; plan must be
self-contained before repo-sync
│
▼
/repo-init or /repo-sync mint GitHub issues from plan steps
│
▼
/build-phase or /build-step execute steps autonomously
│
▼
/repo-update commit, update docs, push
/build-phase walks the plan and dispatches /build-step per row. /build-step spawns a developer agent in an isolated worktree, runs reviewers (auto or code or full), and merges back on PASS. Plan-pipeline rules live in .claude/rules/ (project-scoped) and dev/.claude/rules/ (workspace-scoped); both are read-only references the agents follow.
- Architecture: documentation/master_plan.md
- Wiki: documentation/wiki/ (built out in Phase G)
- License: MIT