Issue → agent → verified PR.
OpenFactory is a small, self-hosted software factory: an orchestrator that turns GitHub / Linear / Sentry issues into pull requests using coding agents (Claude Code or Codex) running inside ascii.dev Box sandboxes. Every issue gets its own disposable VM; every change is adversarially reviewed by a second agent in a second VM before a PR is opened. The orchestrator itself runs on a Box too — one factory box drives the whole fleet, and even intake triage is an agent on that box.
issue ──▶ triage ──▶ fork template box ──▶ plan ──▶ [human gate] ──▶ build + test
│ stop (snapshot)
PR ◀── [human gate] ◀── verdict ◀── review in a fresh box
▲ │ fail
└── fix loop (resume) ◀────┘
- Your template box: a box you set up over SSH — your stack, your
logged-in devtools, your repo checkout. OpenFactory verifies it, snapshots
it, and every run starts as a seconds-fast
forkof that snapshot. - Verification in a fresh fork: the reviewer gets a pristine template fork plus the built branch as a git bundle — exact committed tree, zero setup, and it can't corrupt the builder's workspace. That same box pushes the PR, so a run needs two machines, not three.
- Stop = pause: while a run waits for human approval, its box is stopped
and costs nothing.
resumebrings the same filesystem back in seconds. - TTL as a dead-man's switch: every worker box gets a finite TTL that the orchestrator extends per phase. If the orchestrator dies, boxes self-stop — no runaway spend.
- Snapshots as audit trail: finished runs keep their archived build and
verify boxes. Wake one for an hour from the run page to poke at the exact
tree, or read it cold with
box snapshot tree/pull— no resume needed. - Events as transcripts: the run page replays what each agent actually did (prompts, responses, tool calls) from the platform's box-events API — for live and archived boxes, builder and reviewer.
- Triage on the factory box: the intake gate is a tool-using agent prompted on the orchestrator's own box, with a local checkout of your repo to check signals against real code. It fails open on every error.
Requirements: the box CLI logged in, an API key, and a repo you want the factory to work on.
git clone https://github.com/kunalm2345/openfactory && cd openfactory
ASCII_API_KEY=box_… npm run bootstrapThe bootstrap script creates a factory box, installs OpenFactory as a systemd
service, exposes the dashboard on a stable HTTPS URL, and prints the link
(with a generated DASHBOARD_TOKEN). The dashboard's Welcome page walks
you through the rest:
GH_TOKEN— a fine-grained GitHub PAT (contents + pull requests: read/write) in the Box account environment on box.ascii.dev, so every box inherits it andghjust works.- Agent credentials — connect Claude Code / Codex once in the ascii.dev dashboard (Agents tab).
- Template box — paste a box id (or create a plain box from Settings), SSH in, install your stack, clone your repo, then Finalize — that verifies the checkout and snapshots the box.
For local development: cp .env.example .env, set ASCII_API_KEY, and
npm run dev — the dashboard is on http://localhost:3000.
Point your issue tracker at the running server:
| Source | URL | Notes |
|---|---|---|
| GitHub | POST /webhooks/github |
issues events; every opened issue on the configured repo is triaged. Set GITHUB_WEBHOOK_SECRET. |
| Linear | POST /webhooks/linear |
Issue-created events. Set LINEAR_WEBHOOK_SECRET. |
| Sentry | POST /webhooks/sentry |
Issue alerts become "investigate and fix" runs. Set SENTRY_CLIENT_SECRET. |
The dashboard port is hosted publicly so webhooks can reach it — they verify
their own signatures, and the UI itself is gated by DASHBOARD_TOKEN.
| State | What happens |
|---|---|
TRIAGING |
A tool-using agent on the factory box decides whether the webhook signal deserves a run (duplicates, noise). Skipped signals land in SKIPPED with the reasoning; every failure path admits the run. |
QUEUED |
Admission control: concurrency cap, Box plan limits, creation rate. |
PROVISIONING |
Fork the template box; cut openfactory/<run>-<slug> from the base branch. |
PLANNING |
Agent explores the repo, writes PLAN.md + PLAN_META.json. |
AWAITING_PLAN_APPROVAL |
Box stopped (zero cost) until a human approves. Configurable: auto / manual / manual-for-large. |
BUILDING |
Agent implements the plan, runs tests, commits. Never pushes. |
SNAPSHOTTING |
Box stops; its final snapshot is the exact built tree. The branch is captured as a git bundle, and the full diff is pulled for the run page. |
VERIFYING |
A verify box is forked from the pristine template and receives the bundle. A second agent (a different model family by default) reviews the diff, reruns tests, writes REVIEW.json. The box is then stopped and kept — it pushes the PR next, and its transcript stays readable. |
FIXING |
On a failing verdict, the issue box resumes, gets the findings, and fixes them (bounded rounds), then re-verifies. |
CREATING_PR |
The verify box resumes, resets the branch to the exact reviewed commit, and runs git push + gh pr create with plan and verdict in the body. (If that box is gone, a throwaway fork of the template does it instead.) |
PR_OPEN |
The PR is the run's live artifact: the engine polls GitHub for mergeability and flips the run when the PR merges or closes. Merge from the dashboard, or Fix & merge when it conflicts with the base branch. |
MERGING |
Fix & merge: the build box resumes, origin/<base> is merged into the branch (an agent resolves conflicts if needed), pushed, and squash-merged. |
MERGED / FAILED / REJECTED / CANCELLED / SKIPPED |
The build and verify boxes stay archived as the run's audit trail (stopped, so free — wake either from the run page to inspect it). A PR closed without merging rejects its run. |
Everything is driven by a compare-and-swap state machine over SQLite — the process can be killed at any point and resumes safely. A reconciler loop mirrors real box states, extends TTLs that would expire mid-phase, and reaps orphaned boxes. See docs/design.md for the full state machines, event catalog, and failure-handling rationale.
OpenFactory never installs anything into your template box. You bring one
(paste an id) or create a plain box from Settings, then set it up over SSH:
install your stack, log into your devtools, clone the repo (gh repo clone).
Env vars and secrets live in the ascii.dev dashboard; agent credentials under
its Agents tab. Finalize runs mechanical checks (checkout exists, git can
reach origin) and snapshots the box.
When a PR merges — from the factory or anywhere else — the template refreshes
itself at the next quiet moment: resume → git pull --ff-only → snapshot.
If a pull can't fast-forward, the job fails with the raw git output and you
fix it the same way you built the box: over SSH.
Env vars carry secrets only (ASCII_API_KEY, GH_TOKEN,
DASHBOARD_TOKEN, webhook secrets); everything behavioral lives in the
dashboard Settings page (stored in SQLite): the repository, a model +
reasoning-effort choice per stage (triage / plan / build / review — the
provider is derived from the model), the plan approval gate, review
strictness (any / major / none), max fix iterations, concurrency, and
all six prompt templates.
The hosted Settings page also shows the running commit. Update from main fetches and fast-forwards the factory checkout, verifies the build, and lets systemd restart the orchestrator. Local service files are preserved; a diverged branch or overlapping local edit fails loudly for manual repair.
- Issue text is untrusted input to an agent that holds your GitHub token
(boxes get the account env so
ghworks). Every opened issue is triaged, so don't point the factory at repos where strangers can open issues, and leave PR review as the human boundary. GitHub issues from repos other than the configured one are rejected at intake. - The triage agent runs on the factory box with tools, and it reads untrusted issue text (its prompt marks the body untrusted and forbids acting on it, but that is a prompt, not a sandbox). If your intake sources are public, weigh that trade.
- One run per issue at a time; duplicate webhook deliveries are deduped.
- Approvals never time out: a plan can wait for days — its boxes are stopped, so waiting is free.
- The PR body carries the plan and verdict.
MIT