Metadata-Version: 2.4
Name: autoforge-cli
Version: 0.1.0a0
Summary: Autonomous orchestration for AI-driven software development (deterministic controller over Claude Code / OpenCode / gh).
Author: Ruoshi Li
License-Expression: MIT
Project-URL: Homepage, https://github.com/iceboundrock/AutoForge
Project-URL: Documentation, https://github.com/iceboundrock/AutoForge/tree/main/docs
Project-URL: Source, https://github.com/iceboundrock/AutoForge
Project-URL: Issues, https://github.com/iceboundrock/AutoForge/issues
Project-URL: Changelog, https://github.com/iceboundrock/AutoForge/blob/main/CHANGELOG.md
Keywords: ai,agents,orchestration,code-review,github,claude-code,opencode
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: yaml
Requires-Dist: pyyaml>=6; extra == "yaml"
Dynamic: license-file

# 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:

```text
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/`
for a remote run and to `<git dir>/autoforge/state` for a
[local run](docs/local-mode.md#resume-and-recovery).

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.

There is a second, explicit workflow for when there is no GitHub to
orchestrate: [Local mode](#local-mode-no-github) drives a feature Markdown
file against your working tree with the same controller and the same
verification discipline, and the controller makes zero `gh` invocations.

## 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](docs/security.md) lists what AutoForge protects
  and what it does not, the GitHub token permissions it needs and the
  recommended settings; [SECURITY.md](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](docs/agent-guides/control-result-protocol.md),
  [workflow](docs/agent-guides/workflow.md))
- **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 and the next issue exist.
  ([GitHub safety](docs/agent-guides/github-safety.md))
- **Agents never merge.** Every agent prompt carries the rule "never merge a
  pull request" (LOCAL prompts forbid `git merge` and have no PR to merge).
  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](docs/agent-guides/github-safety.md#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`; a local review is bound
  to a workspace fingerprint in the same way.
  ([Review binding](docs/agent-guides/workflow.md#bind-reviews-to-pr-head-sha-and-to-the-pr-identity))
- **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](docs/agent-guides/state-and-recovery.md))
- **Agents are separated from your checkout and shell; sandboxing is
  opt-in.** A remote 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](docs/adr/0002-executor-nothing-outlives-the-invocation.md),
  [Security boundaries](docs/security.md#other-limits)).
  By default the agent is not sandboxed: it runs as the operator, with
  network access and the allow-listed provider credentials.
  ([Usage](docs/usage.md#how-agents-are-separated-from-your-checkout))
  `sandbox.mode: required` (Linux, `bwrap`) runs every agent, LOCAL
  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` falls back to logical isolation
  only in LOCAL, visibly, and refuses like `required` in REMOTE; `doctor`
  shows which this host gets
  ([ADR 0005](docs/adr/0005-outer-sandbox-and-host-resource-capability-model.md),
  [Usage](docs/usage.md#outer-sandbox),
  [Configuration](docs/configuration.md#outer-sandbox)).
- **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 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](docs/agent-guides/github-safety.md#agents-hold-no-github-credential))
- **The controller's local run is GitHub-free by construction, not by
  convention.** Local mode needs no GitHub: a local run never constructs the
  GitHub client, so no authentication is needed and none is consulted. The
  agent is outside that construction: it is asked not to touch GitHub and
  gets no GitHub credential (as above), and only a moved git anchor (a
  commit, reset, checkout or branch switch) is detected;
  a `gh` write or a push from the agent is not
  (see [Local mode](#local-mode-no-github)).
- **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](docs/agent-guides/secrets-and-logging.md))

## Prerequisites

[Compatibility](https://github.com/iceboundrock/AutoForge/blob/main/docs/compatibility.md)
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](https://docs.astral.sh/uv/getting-started/installation/))
- `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. `gh` is **not** needed for
  [local mode](https://github.com/iceboundrock/AutoForge#local-mode-no-github)
- `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](https://github.com/iceboundrock/AutoForge/blob/main/docs/configuration.md#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](https://github.com/iceboundrock/AutoForge/blob/main/docs/pi.md)). `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),
or
`autoforge local doctor` for the local-mode subset (no `gh`, no GitHub
authentication, no `origin` remote).

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](https://github.com/iceboundrock/AutoForge/blob/main/docs/compatibility.md#agent-clis).

## 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 needs 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>`):

```bash
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:

```bash
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, once a version is published.** Until the first upload
([#295](https://github.com/iceboundrock/AutoForge/issues/295)),
`autoforge-cli` is not on PyPI and the command below fails: uv reports that
`autoforge-cli` was not found in the package registry. The
[releases page](https://github.com/iceboundrock/AutoForge/releases) lists
each version that has been published, with the SHA-256 digests of its
files; if it lists none, install from a git commit or a built wheel
instead. Install a published version only after checking that its PyPI
project page links back to this repository:

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

`--prerelease allow` is needed while every published version is a
prerelease (`0.x`, [Releasing](https://github.com/iceboundrock/AutoForge/blob/main/docs/releasing.md#version-scheme)).
[Releasing](https://github.com/iceboundrock/AutoForge/blob/main/docs/releasing.md)
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 (or, in LOCAL mode, whose feature file it
implements); `autoforge doctor` checks the rest.

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

```bash
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](https://github.com/iceboundrock/AutoForge/blob/main/docs/development.md) has the checks CI runs.

To configure profiles, bounds or the merge gate, copy
`autoforge.example.yaml` to `autoforge.yaml` and pass it with `--config`
before the subcommand; see [Configuration](https://github.com/iceboundrock/AutoForge/blob/main/docs/configuration.md).

## Quick start

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).

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

# 2. preview without touching anything (no subprocess, no gh call, no state file)
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. 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. Every command,
where a run stops, and how to recover are in
[Running the remote workflow](docs/usage.md).

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, and `epic resume`
recovers after a crash, a kill or a reboot
([Running an EPIC in parallel](docs/usage.md#running-an-epic-in-parallel)).
It is experimental: it is verified offline only and has not yet driven a
real EPIC ([Current maturity](#current-maturity)).

## Local mode (no GitHub)

Sometimes there is no GitHub to orchestrate: a coding interview, a scratch
repository, an air-gapped machine, or simply a change you want driven by the
same review loop without opening an Issue. `autoforge local` is a separate,
explicit workflow for exactly that: the same controller, the same
"never trust the agent" discipline, none of the GitHub lifecycle.

```text
features/<slug>.md  →  INITIALIZING → ANALYZE_EXECUTE → REVIEW
                                         → clean ─────────────▶ DONE
                                         → findings ──────────▶ FIX → REVIEW
                                         → findings after the fix budget ─▶ BLOCKED
```

The *controller* never touches GitHub in a local run: no `gh` invocation,
no Issue, no PR, no comment, no push, no branch, no merge, no EPIC update, no
follow-up issue, and no replan. The `GitHubClient` is not even constructed.
Nothing is faked either: there are no placeholder PR URLs in local state or
local prompts.

That is a property of the controller, not of the agents it launches. An agent
is a same-UID subprocess with `gh` on `PATH` and network access; it is
instructed not to use them, and it gets no GitHub or git credential through
anything AutoForge controls, so a `gh` write or a push fails unless the agent
finds a credential on its own (by reading the operator's files by absolute
path, say). Beyond that, the controller can neither prevent nor detect a
`gh` write, a `git push` or a network request made by the agent. What the
controller *does* detect is a moved git anchor: HEAD and the checked-out branch
are compared with the values the run was pinned to before and after every
phase, and before every correction relaunch within one, so a commit, reset,
checkout or branch switch blocks the run before another agent starts. It detects
nothing else. Real containment is a sandbox, which LOCAL v1 does not provide
under the default `sandbox.mode: disabled`
([ADR 0001](docs/adr/0001-local-mode-workspace-identity-and-filesystem-boundary.md)
§2.2 and §8.1; isolation is tracked in #10). With `sandbox.mode: required`
every LOCAL agent and validation command runs inside ADR 0005's outer
sandbox, where the git directory is read-only, so a commit, stash or branch
switch fails outright; the agent still runs as your UID on the host
network. `preferred` does the same where the host can enforce and, where it
cannot, says at every launch that it runs with logical isolation only
([Local mode](docs/local-mode.md#what-the-controller-does-and-does-not-do)).

```bash
autoforge local doctor                    # config, git, agent CLIs; never gh
autoforge local init add-transaction-filter
# a human (with or without an AI) refines features/add-transaction-filter.md
autoforge local run features/add-transaction-filter.md --dry-run
autoforge local run features/add-transaction-filter.md
```

The specification is frozen for the whole run (its SHA-256 is re-checked
before and after every agent phase), every review is bound to a fingerprint
of the working tree, and the controller never commits, stages, pushes,
stashes, resets or switches branches in a local run. The [Local mode guide](docs/local-mode.md) covers the
workspace fingerprint, dirty working trees, the review/fix bound, validation
commands, and resume and recovery.

## Documentation

The [documentation index](docs/README.md) lists everything by intent. The
most common entry points:

| I want to… | Read |
|---|---|
| know which OS, Python, git, `gh`, agent CLI and sandbox host it runs on | [Compatibility](docs/compatibility.md) |
| run the remote workflow, merge, unblock, troubleshoot or recover a run | [Running the remote workflow](docs/usage.md), [troubleshooting and recovery](docs/usage.md#troubleshooting-and-recovery) |
| work without GitHub | [Local mode](docs/local-mode.md) |
| run phases through Pi on a DeepSeek API key or a ChatGPT subscription | [Pi operator guide](docs/pi.md) |
| configure profiles, bounds and the merge gate | [Configuration](docs/configuration.md), [`autoforge.example.yaml`](autoforge.example.yaml) |
| know what AutoForge protects, which GitHub token it needs, or report a vulnerability | [Security boundaries](docs/security.md), [SECURITY.md](SECURITY.md) |
| understand the phases and legal transitions | [Workflow](docs/agent-guides/workflow.md) |
| know what is verified on GitHub, and what gates a merge | [GitHub safety](docs/agent-guides/github-safety.md) |
| know how state, locking and crash recovery work | [State and recovery](docs/agent-guides/state-and-recovery.md) |
| see how the code is divided | [Architecture](docs/agent-guides/architecture.md), [module map](src/autoforge/AGENTS.md) |
| contribute, run the checks, write tests | [Development](docs/development.md), [Testing](docs/agent-guides/testing.md) |
| cut or publish a release | [Releasing](docs/releasing.md) |
| read the design records | [ADR 0001](docs/adr/0001-local-mode-workspace-identity-and-filesystem-boundary.md), [ADR 0002](docs/adr/0002-executor-nothing-outlives-the-invocation.md), [ADR 0003](docs/adr/0003-pi-agent-provider.md) |

Coding agents working on this repository start at [AGENTS.md](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`; 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; LOCAL mode against a feature Markdown file.

**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](docs/adr/0005-outer-sandbox-and-host-resource-capability-model.md)).
`sandbox.mode: disabled` and a `preferred` fallback are 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.

**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
`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`.

**Experimental: implemented and verified offline:** the parallel EPIC
scheduler (`autoforge epic run|resume|status|stop|archive`, 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](docs/pi.md)). No built-in profile uses it, and its one
recorded live run is a LOCAL review on DeepSeek (#135).

The Public Alpha release claims these capabilities at exactly these levels
([#255](https://github.com/iceboundrock/AutoForge/issues/255), conditions
C1–C7). An earlier registration prerelease of the package (#295) is not the
Public Alpha and claims no more than this.

**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).
