Skip to content
iceboundrockPublic

About

Autonomous orchestration for AI-driven software development.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

AutoForge / 智铸

Autonomous orchestration for AI-driven software development.

智铸:AI 驱动的软件工程自动化控制器。

Early development (Phase 2). The first real AI development loop is wired end to end: GitHub issue → Claude Code implementation → controller push and PR → OpenCode review → Claude Code remediation → repeated review → READY_FOR_MERGE. Merging is opt-in and controller-owned: by default the loop stops at READY_FOR_MERGE for a human; with the merge safety gate open (safety.allow_merge: true in config and --allow-merge on the CLI) the controller itself verifies the PR on GitHub and merges it. Agents never merge. Nothing here is production ready yet.

What AutoForge is (and is not)

AutoForge is not another coding agent. It is a deterministic orchestration layer that drives existing agents through the software lifecycle and refuses to trust anything they claim without checking it against GitHub:

Issue → ANALYZE_EXECUTE (Claude Code) → controller pushes, opens PR → REVIEW (OpenCode)
       → clean ───────────────────────────────▶ READY_FOR_MERGE
       → findings ────────────────────────────▶ FIX (Claude Code) → REVIEW …
       → excessive / low-finding stagnation ──▶ REPLAN_REEXECUTE (OpenCode)
                                                    → controller pushes, opens replacement PR
                                                    → REVIEW round 1
                                       ├─ merge gate closed (default): stops here; human merges
                                       └─ gate open: controller verifies on GitHub
                                          → MERGE (gh pr merge by the controller, no agent)
                                          → UPDATE_EPIC (agent returns the progress text
                                             and the roadmap section and picks the next
                                             issue; the controller posts the progress comment,
                                             splices the section into the EPIC body every N
                                             merges, reads both back, and verifies the next
                                             issue on GitHub, or DONE)

AutoForge itself never writes business code. It holds the workflow state machine, selects which provider, model and effort runs each phase, renders strictly validated prompts from file templates, invokes claude / opencode through provider adapters and gh through a typed client (argv lists only, never a shell), parses the machine-readable CONTROL_RESULT block from agent output, and verifies every claim on GitHub before it advances state. Every invocation is logged, redacted, under <state dir>/logs/<run-id>/; the state directory defaults to .autoforge/.

It is a single-machine client tool, not a distributed service: one process on your own machine, driving your git, gh, and agent CLIs under your credentials, with local per-checkout state and a single OS-level repository lock instead of any coordinator. The expected failure modes are a Ctrl-C, a killed process, and a reboot, which is why side effects are checkpointed before they are performed. What is remote and concurrent is GitHub and the humans acting on it, which is why nothing GitHub-side is believed without being read back. AutoForge never commits to the default branch.

Key properties

Each line is a summary; the linked document is the precise statement.

  • Read the security boundaries before a first run. Agents run as you, with network access and your model credentials, and are not sandboxed by default, so a deliberate read of your credential files by absolute path is outside the credential guarantee below. On a public repository anyone who can comment on the issue or PR an agent reads can try to steer it, and everything an agent reads goes to your model vendor. Run AutoForge on issues whose authors you trust, as a dedicated user or in a VM or container, or under sandbox.mode: required. Security boundaries lists what AutoForge protects and what it does not, the GitHub token permissions it needs and the recommended settings; SECURITY.md says how to report a vulnerability.
  • Agent output is a claim; the controller decides. State advances only on a strictly validated CONTROL_RESULT block plus independent verification, never on prose such as "done" or "LGTM". An invalid transition fails explicitly, and when recovery cannot determine the safe state the run enters BLOCKED rather than guess. (CONTROL_RESULT protocol, workflow)
  • GitHub is read back, not believed. Each phase re-reads what its agent claims to have done, or what the controller did on its behalf: the implementation commit is the worktree's own HEAD and descends from the default branch, and the PR the controller opened for it is open, at that HEAD, on the issue's branch; the review comment the controller rendered from the reviewer's result is on that PR, exactly once, for the right round and SHA; the fix commit is the fixer's own HEAD, descends from the reviewed HEAD and is the PR's head once the controller has pushed it; the follow-up issues the controller created or recorded a deferral in are the one open issue per finding; the EPIC update exists, and the next issue is open and a native sub-issue of the EPIC. (GitHub safety)
  • Agents never merge. Every agent prompt carries the rule "never merge a pull request". The merge is performed by the controller itself (gh pr merge --<method> --match-head-commit <reviewed HEAD>), with no agent invocation.
  • Merging is off by default and opt-in. MERGE is reachable only when both safety.allow_merge: true is set in config and --allow-merge is passed on the CLI; otherwise the run stops at READY_FOR_MERGE and a human merges. Before merging, the controller re-verifies the PR on GitHub and fails closed: a conclusive negative is BLOCKED, inconclusive data keeps the phase for a bounded number of resume --allow-merge re-checks and then is BLOCKED, and nothing is merged in either case. By default a PR that changes a protected path (.github/workflows/) is never merged unattended, and a required check is trusted only if it ran the base branch's job and step structure. doctor checks that the default branch still requires the CI check; the merge gate itself does not yet consult branch rules. The merge is counted only after GitHub reports MERGED at the reviewed HEAD into the reviewed base. (Merge safety)
  • Reviews are bound to a revision. A clean review is bound to the PR it was posted on (by repository and number), its HEAD SHA, its base branch and its merge base. If the HEAD, the base branch or the merge base moves after the review, the run goes back to REVIEW instead of merging, and a different PR than the reviewed one is BLOCKED. (Review binding)
  • State is durable and runs are resumable. State is written atomically and side effects are checkpointed before they are performed. resume re-enters the interrupted phase and reconciles with GitHub first; a corrupt state file fails loudly instead of being reset. One controller runs per checkout under an OS file lock keyed by the git common dir (linked worktrees share it; independent clones are not coordinated). Review rounds, stagnation and a cumulative step budget bound every run, so it cannot loop forever. (State and recovery)
  • Agents are separated from your checkout and shell; sandboxing is opt-in. An agent runs in a per-issue git worktree the controller creates, with an allow-listed environment rather than the operator's whole shell, and the controller blocks if its own checkout's HEAD or branch moved while the agent ran. Whatever an agent leaves running is killed when its invocation ends, and what the kill could not remove is reported rather than hidden; the orphans of a process that makes itself a subreaper, and anything handed to a service manager (systemd-run, at), escape this unseen (ADR 0002, Security boundaries). By default the agent is not sandboxed: it runs as the operator, with network access and the allow-listed provider credentials. (Usage) sandbox.mode: required (Linux, bwrap) runs every agent, validation command and pre-merge command inside an outer sandbox, or refuses it: an empty root with only the system runtime, the workspace, read-only git metadata, a private home and what the provider declares, so controller state, other worktrees and your credential files are out of reach. It still runs as your UID, and agents keep the host network. A host that cannot enforce one is refused before the step, with nothing saved, charged or launched; preferred means required; doctor shows which this host gets (ADR 0005, Usage, Configuration).
  • Agents hold no GitHub credential. The controller makes every GitHub write itself; agents, validation commands and pre-merge commands get no GitHub or git credential through anything AutoForge controls: no token variable gh or git reads can be allow-listed (a token you forward under another name is warned about, and is yours to keep out), gh finds no login, credential helpers, askpass and the SSH agent and keys are switched off where repository configuration cannot switch them back on, curl and wget read no configured header, a launch's shells read none of your zsh startup files, the provider CLIs load no MCP server, plugin or hook, and a launch is refused (by doctor too) while a repository setting, ~/.netrc, a login shell's startup file or an organization connected to OpenCode would still hand one over. The agent reads the GitHub data its phase needs from files the controller wrote, redacted and bounded. A deliberate same-user read of a credential file by absolute path is outside this guarantee. (GitHub safety)
  • Logs and CLI output are redacted. Tokens, API keys and authorization headers are masked (a baseline, with no claim of completeness), and no environment dump is ever written. (Secrets and logging)

Prerequisites

Compatibility has the full matrix and the evidence for each entry.

  • Linux on x86_64. macOS, Linux ARM64 and WSL2 are untested; Windows is unsupported (AutoForge's lock needs POSIX fcntl)
  • CPython 3.11 or 3.12, the versions CI tests; 3.13 and later are untested. uv fetches one when the host has none (--python 3.12 below)
  • uv (install)
  • git, and gh (GitHub CLI 2.48.0 or newer, authenticated): the client lists branch rules and a PR's changed files with gh api --paginate --slurp, which older releases reject; autoforge doctor refuses an older gh up front
  • claude (Claude Code CLI) for analyze_execute / fix profiles
  • opencode (OpenCode CLI) 2.0.0 or newer for the review_*, replan_reexecute and update_epic profiles; 1.x is refused by doctor (see OpenCode profiles)
  • pi 1.0.0 or newer, only if a profile you route to uses provider: pi, holding a DeepSeek API key or signed in with your ChatGPT subscription (see the Pi operator guide). doctor checks only the agent CLIs your configured profiles actually reach
  • a state directory (.autoforge/ by default; --state-dir or state_dir in config) on a filesystem with hard links: run logs and other controller files are published by link(2) so that a crash never leaves one half-written, and there is no non-atomic fallback. vfat/exFAT and some FUSE, SMB/CIFS and overlay mounts have no hard links; a run, and autoforge doctor, refuse such a state directory and say why

Run autoforge doctor to check all of the above (read-only apart from creating the state directory if it is missing and a probe file it removes).

CLI flag syntax in autoforge.example.yaml was checked against the locally installed CLIs (Claude Code 2.1.263, OpenCode 2.0.23, gh 2.100.0); the versions verified since are in Compatibility.

Installation

The distribution is autoforge-cli; the command it installs is autoforge. autoforge on PyPI is an unrelated project: never pip install autoforge.

A git commit, or a wheel built from one, installs from any commit at or after the one that introduced autoforge-cli (#256; an earlier commit installs a package without its prompt templates); the PyPI method installs a published version.

From a git commit. Install the command into its own environment with uv tool. Pin a full commit SHA, or the tag of a published version (v<version>):

uv tool install --python 3.12 'git+https://github.com/iceboundrock/AutoForge@<commit>'
autoforge --version

uv tool install --reinstall with another commit replaces it, and uv tool uninstall autoforge-cli removes it. Add the YAML extra with 'autoforge-cli[yaml] @ git+https://github.com/iceboundrock/AutoForge@<commit>'.

From a built wheel. From a checkout of the commit you want:

uv build                 # dist/autoforge_cli-<version>.tar.gz and -py3-none-any.whl
uv tool install --python 3.12 dist/autoforge_cli-<version>-py3-none-any.whl

From PyPI. The published versions are at https://pypi.org/project/autoforge-cli/, and the releases page lists them. The first, 0.1.0a0 (2026-10-28), is #295's registration prerelease: a pre-Alpha snapshot, not validated for unattended production use, and not the Public Alpha (#255). Each file PyPI serves was built and uploaded by this repository's pypi-publish workflow and carries a PEP 740 attestation naming that workflow. Install only after checking that the PyPI project page links back to this repository:

uv tool install --python 3.12 --prerelease allow autoforge-cli

--prerelease allow is needed while every published version is a prerelease (0.x, Releasing); 'autoforge-cli==0.1.0a0' pins one. A published version carries only the fixes of the commit it was built from, while security fixes land on main (Security policy). Releasing describes how a version is built and published.

Whichever way it was installed, run autoforge from a checkout of the repository whose issues it drives; autoforge doctor checks the rest. Continue a run with the controller that created it, or a newer one: a controller refuses a run a newer one wrote (exit 2, nothing written) and names the code it imported, and doctor's controller row shows which code a command line runs (A run a newer controller wrote).

For development on AutoForge itself, from a clone of this repository:

uv sync                  # create .venv, install autoforge-cli (editable) + dev tools
uv sync --extra yaml     # optional: full YAML config support (PyYAML)
uv run autoforge --help

uv run autoforge is the same command, run from that clone's .venv. Development has the checks CI runs.

To configure profiles, bounds or the merge gate, copy a starter configuration from examples/, or autoforge.example.yaml (every key), to autoforge.yaml and pass it with --config before the subcommand; see Configuration.

Quick start

New to AutoForge? Getting started walks through a first run on a repository of your own: prerequisites, installing, a repository (a new private one, or an existing one you may change) with an EPIC and an issue, a starter configuration, what each doctor row means, a dry run, a real run, and the common first-run failures with their fixes. The commands in brief:

Run these from a checkout of the repository the issues belong to. URLs must be HTTPS GitHub issue URLs, and the EPIC and the issue must be in that same repository (cross-repo runs are rejected). The run records the issue's native parent on GitHub as its EPIC; --epic is used only for an issue with no parent (Which EPIC a run records).

# 1. check the environment (writes nothing but a state-dir probe)
autoforge doctor

# 2. preview, in the checkout: reads its repository, the issue and its EPIC
#    through gh, writes nothing
autoforge run --epic https://github.com/owner/repo/issues/1 \
              --issue https://github.com/owner/repo/issues/2 --dry-run

# 3. create a run and drive it until READY_FOR_MERGE / BLOCKED / FAILED
autoforge run --epic https://github.com/owner/repo/issues/1 \
              --issue https://github.com/owner/repo/issues/2

# 4. inspect it, and continue after an interruption
autoforge status
autoforge resume

With the merge gate closed (the default) the run stops at READY_FOR_MERGE with a banner naming the issue, PR, review round and reviewed HEAD, and a human merges the PR. A push to the PR branch after that review sends the run back to REVIEW of the new head on the next resume. Only with safety.allow_merge: true in config does autoforge resume --allow-merge let the controller verify the PR on GitHub and merge it itself.

A run in BLOCKED stays there until you fix the cause and run autoforge unblock --reason "...", which re-inspects GitHub and either re-enters the one phase that inspection supports or refuses. When a fixer asked the controller to post an acceptance comment on the issue, the run waits for you to approve or decline that exact comment (unblock --approve-action / --decline-action). When it asked for a comment on another issue (one the run's issue references, or the EPIC), you post it yourself, bind an existing comment or withdraw it (unblock --bind-evidence / --decline-action); the controller never posts there, and verifies the comment on GitHub. A fixer may also ask the controller to replace the description of the run's own PR, which it does by default (safety.pr_body_updates); an edit made outside that plan stops the run and is never written over. When a run stops because the PR's implementation marker names another run, or none (pr-owner), stop the other controller, then autoforge unblock --take-over <PR URL> and resume hand the PR to this run. When status warns that GitHub now names another parent for the issue than the EPIC the run recorded, autoforge rebind-epic ... --dry-run previews moving a run stopped at READY_FOR_MERGE or BLOCKED to that parent, before its UPDATE_EPIC writes to the old one. Every command, where a run stops, and how to recover are in Running the remote workflow.

To work through an EPIC's issues several at a time, autoforge epic run https://github.com/owner/repo/issues/1 runs up to scheduler.max_parallel (1-4) of them at once, admitted in dependency order from GitHub's native sub-issues and "blocked by" links, each in its own worker, namespace and worktree. Every worker stops at its settlement boundary (READY_FOR_MERGE, BLOCKED, DONE, FAILED), so nothing is merged without a human. epic status shows each child's category and next step without taking a lock, epic stop drains it, epic resume recovers after a crash, a kill or a reboot, and epic recheck sends an issue settled at READY_FOR_MERGE whose PR was pushed to back to review (Running an EPIC in parallel). It is experimental: it is verified offline only and has not yet driven a real EPIC (Current maturity).

Documentation

The documentation index lists everything by intent. The most common entry points:

I want to… Read
make a first run on a repository of my own, step by step Getting started, starter configurations in examples/
know which OS, Python, git, gh, agent CLI and sandbox host it runs on Compatibility
run the remote workflow, merge, unblock, troubleshoot or recover a run Running the remote workflow, troubleshooting and recovery
run phases through Pi on a DeepSeek API key or a ChatGPT subscription Pi operator guide
configure profiles, bounds and the merge gate Configuration, autoforge.example.yaml
know what AutoForge protects, which GitHub token it needs, or report a vulnerability Security boundaries, SECURITY.md
understand the phases and legal transitions Workflow
know what is verified on GitHub, and what gates a merge GitHub safety
know how state, locking and crash recovery work State and recovery
see how the code is divided Architecture, module map
contribute, run the checks, write tests Development, Testing
cut or publish a release Releasing
read the design records the index; ADR 0001 (superseded by ADR 0009 but for what it says of safefs.py), ADR 0002, ADR 0003, ADR 0004, ADR 0005, ADR 0006, ADR 0007 (partly implemented), ADR 0008, ADR 0009

Coding agents working on this repository start at AGENTS.md.

Current maturity

Supported now (Phase 2): GitHub issue → Claude Code implementation → the controller's push and PR, read back via gh → OpenCode review with round-based model routing → Claude Code remediation of finding IDs → repeated review with SHA binding → READY_FOR_MERGE, entered only in the write that issues the clean round's review certificate (below); the bounded REVIEW/FIX loop with stagnation detection and the REPLAN_REEXECUTE transaction; journal-first recovery of an interrupted push or PR creation; bounded correction retry for malformed results; doctor; redacted per-invocation logs.

Opt-in, and only on a host that can enforce it: the outer sandbox on Linux (sandbox.mode, off by default), with its capability probe, pre-launch gate and doctor rows. It needs Linux 6.12+ with Landlock ABI 6, unprivileged user, PID and IPC namespaces (some distributions restrict them), and a bubblewrap that can mount an overlay (0.11 or later) (ADR 0005). sandbox.mode: disabled is not sandboxed. Its boundary is tested across every provider path on a capable host (make test-sandbox). Hosted CI measures its runner on every run (its bwrap, user-namespace policy and Landlock ABI, in the job summary) and requires the real-backend tests wherever the runner can enforce; where it cannot, they skip with the probe's reason. It is experimental until a make test-sandbox run on a capable host is recorded at the release candidate.

Opt-in, off until configured: controller-run validation of the exact candidate (ADR 0006). validation.commands is empty by default, so nothing is validated until you configure it. Then the controller runs each command itself against the exact commit an agent hands back, in a byte-exact export of its tree, and keeps each outcome in the state file as a durable ValidationRecord, reused for the same candidate, command and policy. A candidate is pushed or published only once every command has passed on it. Before a reviewer launches, the controller requires records for the candidate under review (it validates a PR head that has none, such as a human's push), shows their outcomes in the review prompt's evidence section, and refuses a clean verdict over a failing record. A record says what the controller observed when it ran a command, not that the change is correct, that GitHub CI passed or that the result holds on another host; under sandbox.mode: disabled every command runs unsandboxed as your UID.

Always on, with or without validation: the review certificate a clean round issues on its way to READY_FOR_MERGE. It states which candidate and revision were reviewed clean, which validation set the reviewer was presented, and that every record in it passed; with nothing configured the set is empty, and the certificate certifies a clean review and no validation. It is a prerequisite, not permission to merge: it is not a GitHub approval or a CI result, and it is never shown to an agent or published.

Experimental: implemented and verified, but exercised only against the in-memory fake: the controller-owned MERGE step behind the merge safety gate (off by default), including its pre-merge verification and its review-certificate check (#175), and UPDATE_EPIC. Every claim these phases depend on is re-read from GitHub before state advances, but no automated test has ever merged a real PR or edited a real EPIC, so treat an opened gate as unproven against real services. The supported path keeps the gate closed: a human reviews and merges the PR the run leaves at READY_FOR_MERGE. The gate refuses a certificate that does not apply to the live PR revision or to the validation configuration in force, and still re-reads every fact it decides on from GitHub. A run saved in READY_FOR_MERGE or MERGE before certificates existed holds none: the gate refuses it while its PR is open (BLOCKED, nothing merged), and unblock sends it to a new validated REVIEW round.

Experimental: implemented and verified offline: the parallel EPIC scheduler (autoforge epic run|resume|status|stop|archive|recheck, ADR 0008). Its crash windows are tested by killing real scheduler and worker processes at each one, but against the in-memory GitHub and scripted agents only, and it has not yet driven a real EPIC: run it on an EPIC you watch, with epic status open.

Experimental: acceptance actions on other issues, which the operator posts or binds (unblock --bind-evidence, #243). They are tested offline and have been used once for real, in a run on this repository. They stay experimental until they are used on a repository other than AutoForge.

Experimental and opt-in: Pi as a provider (provider: pi, the Pi operator guide). No built-in profile uses it. Its one recorded live run, a review on DeepSeek (#135), was in LOCAL mode, which is gone; no live run of the live smoke test's REMOTE review is recorded yet.

The Public Alpha release claims these capabilities at exactly these levels (#255, conditions C1–C7). The registration prerelease 0.1.0a0 on PyPI (#295) is not the Public Alpha; it predates LOCAL's removal, and this section does not describe it.

Explicitly not yet: unattended production operation; the controller feeding CI results into the review prompt (the reviewer is asked to inspect gh pr checks itself, and the merge gate reads the checks); the merge gate consulting branch rules (doctor reads them, the gate does not); dequeuing a PR from a merge queue (the controller refuses to merge into queue-protected branches instead).

About

Autonomous orchestration for AI-driven software development.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages