@giamme/forge 0.0.0-stage → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +181 -0
- package/LICENSE +21 -0
- package/README.md +1854 -2
- package/SKILL.md +122 -0
- package/VERSION +1 -0
- package/forge +74 -0
- package/package.json +26 -4
- package/references/dataset.md +329 -0
- package/references/decompose.md +667 -0
- package/references/fractal-dashboard.md +21 -0
- package/references/fractal.md +307 -0
- package/references/harnesses.md +353 -0
- package/references/jev.md +865 -0
- package/references/memory.md +228 -0
- package/references/planning.md +42 -0
- package/references/ripwire.md +47 -0
- package/references/runtime.md +64 -0
- package/references/solo.md +113 -0
- package/references/ui.md +112 -0
- package/registry.tsv +81 -0
- package/scripts/forge-artifact.sh +78 -0
- package/scripts/forge-contract.py +430 -0
- package/scripts/forge-dataset.py +6 -0
- package/scripts/forge-dispatch.sh +665 -0
- package/scripts/forge-fail-first.py +195 -0
- package/scripts/forge-fractal-dispatch.sh +4 -0
- package/scripts/forge-fractal-options.sh +54 -0
- package/scripts/forge-fractal.py +6 -0
- package/scripts/forge-guard.py +266 -0
- package/scripts/forge-install-fractal.sh +4 -0
- package/scripts/forge-install-ripwire.sh +52 -0
- package/scripts/forge-install.sh +135 -0
- package/scripts/forge-jev-options.sh +259 -0
- package/scripts/forge-jev.py +6 -0
- package/scripts/forge-lib-plans.sh +785 -0
- package/scripts/forge-lib-review.sh +494 -0
- package/scripts/forge-lib-task.sh +302 -0
- package/scripts/forge-lib-verify.sh +30 -0
- package/scripts/forge-memory.sh +575 -0
- package/scripts/forge-metrics.sh +39 -0
- package/scripts/forge-parallel-options.sh +76 -0
- package/scripts/forge-parallel.sh +1717 -0
- package/scripts/forge-plan-check.py +118 -0
- package/scripts/forge-prompt.py +45 -0
- package/scripts/forge-ripwire.py +136 -0
- package/scripts/forge-route.py +344 -0
- package/scripts/forge-runtime.py +533 -0
- package/scripts/forge-schedule.py +312 -0
- package/scripts/forge-shadow.py +305 -0
- package/scripts/forge-solo.sh +347 -0
- package/scripts/forge-ui-options.sh +42 -0
- package/scripts/forge-ui.py +435 -0
- package/scripts/forge-update.sh +71 -0
- package/scripts/forge-verify-coverage.py +956 -0
- package/scripts/forge-version.py +198 -0
- package/scripts/forge_dataset/__init__.py +439 -0
- package/scripts/forge_dataset/capture.py +330 -0
- package/scripts/forge_dataset/checks.py +109 -0
- package/scripts/forge_dataset/cli.py +233 -0
- package/scripts/forge_dataset/export.py +1009 -0
- package/scripts/forge_dataset/harvest.py +716 -0
- package/scripts/forge_dataset/importer.py +412 -0
- package/scripts/forge_dataset/outcomes.py +275 -0
- package/scripts/forge_dataset/routing.py +278 -0
- package/scripts/forge_fractal/__init__.py +83 -0
- package/scripts/forge_fractal/bridge.py +46 -0
- package/scripts/forge_fractal/cli.py +202 -0
- package/scripts/forge_fractal/dashboard.css +138 -0
- package/scripts/forge_fractal/dashboard.html +41 -0
- package/scripts/forge_fractal/dashboard.js +687 -0
- package/scripts/forge_fractal/decomposition.py +262 -0
- package/scripts/forge_fractal/execution.py +659 -0
- package/scripts/forge_fractal/inspection.py +261 -0
- package/scripts/forge_fractal/install.py +158 -0
- package/scripts/forge_fractal/invocation.py +94 -0
- package/scripts/forge_fractal/pipeline.py +124 -0
- package/scripts/forge_fractal/selection.py +179 -0
- package/scripts/forge_jev/__init__.py +376 -0
- package/scripts/forge_jev/backtest.py +322 -0
- package/scripts/forge_jev/cli.py +980 -0
- package/scripts/forge_jev/client.py +418 -0
- package/scripts/forge_jev/corpus.py +156 -0
- package/scripts/forge_jev/coupling.py +244 -0
- package/scripts/forge_jev/features.py +24 -0
- package/scripts/forge_jev/gates.py +100 -0
- package/scripts/forge_jev/memory.py +163 -0
- package/scripts/forge_jev/outcome.py +49 -0
- package/scripts/forge_jev/predict.py +204 -0
- package/scripts/forge_jev/questions.py +661 -0
- package/scripts/forge_jev/review.py +73 -0
- package/scripts/forge_jev/routing.py +271 -0
- package/scripts/forge_jev/subset.py +123 -0
- package/scripts/forge_jev/verify.py +453 -0
- package/scripts/forge_registry.py +140 -0
- package/ui/README.md +287 -0
- package/ui/THIRD_PARTY_NOTICES.md +69 -0
- package/ui/docs/api.md +888 -0
- package/ui/forge_ui/__init__.py +20 -0
- package/ui/forge_ui/__main__.py +135 -0
- package/ui/forge_ui/diffs.py +454 -0
- package/ui/forge_ui/executions.py +436 -0
- package/ui/forge_ui/export.py +204 -0
- package/ui/forge_ui/findings.py +132 -0
- package/ui/forge_ui/fractal.py +583 -0
- package/ui/forge_ui/models.py +216 -0
- package/ui/forge_ui/reader.py +1336 -0
- package/ui/forge_ui/server.py +303 -0
- package/ui/forge_ui/static/app.js +924 -0
- package/ui/forge_ui/static/cloud3d.js +646 -0
- package/ui/forge_ui/static/cloudmodel.js +375 -0
- package/ui/forge_ui/static/cloudui.js +394 -0
- package/ui/forge_ui/static/diffview.js +216 -0
- package/ui/forge_ui/static/favicon.svg +1 -0
- package/ui/forge_ui/static/files.js +776 -0
- package/ui/forge_ui/static/gantt.js +97 -0
- package/ui/forge_ui/static/graph.js +266 -0
- package/ui/forge_ui/static/index.html +55 -0
- package/ui/forge_ui/static/iso.js +765 -0
- package/ui/forge_ui/static/leaderboard.js +315 -0
- package/ui/forge_ui/static/map3d.js +45 -0
- package/ui/forge_ui/static/modelstats.js +249 -0
- package/ui/forge_ui/static/notify.js +179 -0
- package/ui/forge_ui/static/panel.js +526 -0
- package/ui/forge_ui/static/runmodel.js +651 -0
- package/ui/forge_ui/static/scene3d.js +996 -0
- package/ui/forge_ui/static/search.js +119 -0
- package/ui/forge_ui/static/style.css +618 -0
package/README.md
CHANGED
|
@@ -1,3 +1,1855 @@
|
|
|
1
|
-
#
|
|
1
|
+
# forge
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**One model builds it. A different model reviews what it actually built.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/Giamme/forge/releases)
|
|
6
|
+
[](CHANGELOG.md)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img src="ui/docs/media/hero.webp" width="880" alt="A decomposed run in forge-ui's 3D view: eight tasks on a plate in their planned waves, five merged in green and two running in blue, then the same run on wall-clock time, where two first attempts that failed review show in red beside the retries that passed.">
|
|
11
|
+
</p>
|
|
12
|
+
<p align="center"><sub>The run from <a href="#quick-start">Quick start</a> in <a href="#forge-ui">forge-ui</a>: its tasks in the waves the plan gave them, then as they really ran, the failed attempts and their retries included. Demo data.</sub></p>
|
|
13
|
+
|
|
14
|
+
`forge` is a skill for agent CLIs. You hand it a coding task and two models: a **dwarf**
|
|
15
|
+
that implements the change, and a **qa** reviewer that reads the dwarf's real diff — not
|
|
16
|
+
the dwarf's summary of its own work. A model grading its own homework isn't a check; the
|
|
17
|
+
entire value of the second stage is that it sees the code rather than the claim.
|
|
18
|
+
|
|
19
|
+
Either role can be any model, at any reasoning effort, through any of six agent CLIs:
|
|
20
|
+
|
|
21
|
+
| | codex | claude | openclaude | opencode | antigravity | grok |
|
|
22
|
+
|---|---|---|---|---|---|---|
|
|
23
|
+
| binary | `codex` | `claude` | `openclaude` | `opencode` | `agy` | `grok` |
|
|
24
|
+
|
|
25
|
+
So `--dwarf sol:xhigh --qa opus` runs GPT through Codex and Claude Opus through Claude
|
|
26
|
+
Code, in one command, from whichever CLI you happen to be sitting in.
|
|
27
|
+
|
|
28
|
+
With `--decompose-level` it goes wider: the goal is split into tasks, several dwarves work
|
|
29
|
+
**concurrently in isolated git worktrees**, each task is reviewed on its own diff, and only
|
|
30
|
+
the tasks that pass get merged.
|
|
31
|
+
|
|
32
|
+
```mermaid
|
|
33
|
+
flowchart LR
|
|
34
|
+
goal(["your goal"]) --> plan["plan: tasks and models,<br/>then you read the table"] --> dwarf["each task: a dwarf<br/>implements it in<br/>its own worktree"] --> qa["qa reads<br/>the real diff"]
|
|
35
|
+
qa -. "FAIL: retry with<br/>the findings" .-> dwarf
|
|
36
|
+
qa -- PASS --> merge["merged into<br/>integration"] --> deliver(["verification, final review,<br/>integrate --approved"])
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Without `--decompose-level` the whole goal is one task: one dwarf, one review of its diff.
|
|
40
|
+
|
|
41
|
+
With optional [Fractal execution](#fractal-execution), an implementer can delegate bounded
|
|
42
|
+
child tasks inside its owned paths. Forge still chooses the models, reviews the combined
|
|
43
|
+
diff, verifies the result and controls integration. A local dashboard shows the task tree,
|
|
44
|
+
logs and separate execution/acceptance states; HTML reports capture them for later review.
|
|
45
|
+
|
|
46
|
+
[forge-ui](#forge-ui), its opt-in companion, is a read-only local page that watches any run
|
|
47
|
+
live, drawn as a 3D map in archivision's style: every run on one plate, tasks in their waves
|
|
48
|
+
and as they really ran, the final review as a node, and the prompt, model, timings and P0–P3
|
|
49
|
+
findings behind every step. It also floats every run in a full-screen 3D cloud, shows diffs
|
|
50
|
+
with each review finding under its line, and ranks the models on your own runs. forge offers
|
|
51
|
+
it at the start of every new run and never opens it without a yes.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Contents
|
|
56
|
+
|
|
57
|
+
- [Install](#install)
|
|
58
|
+
- [Quick start](#quick-start)
|
|
59
|
+
- [Fractal execution](#fractal-execution)
|
|
60
|
+
- [The spec: `alias:effort:harness`](#the-spec-aliaseffortharness)
|
|
61
|
+
- [Effort, ceilings and clamping](#effort-ceilings-and-clamping)
|
|
62
|
+
- [Yolo mode](#yolo-mode)
|
|
63
|
+
- [Decomposed runs](#decomposed-runs)
|
|
64
|
+
- [Planning](#planning)
|
|
65
|
+
- [Project memory](#project-memory)
|
|
66
|
+
- [Run dataset](#run-dataset)
|
|
67
|
+
- [forge-ui](#forge-ui)
|
|
68
|
+
- [Full command reference](#full-command-reference)
|
|
69
|
+
- [Model registry](#model-registry)
|
|
70
|
+
- [Run artifacts](#run-artifacts)
|
|
71
|
+
- [Safety guarantees](#safety-guarantees)
|
|
72
|
+
- [Troubleshooting](#troubleshooting)
|
|
73
|
+
- [Repository layout](#repository-layout)
|
|
74
|
+
- [License](#license)
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Install
|
|
79
|
+
|
|
80
|
+
forge runs on macOS and Linux. It needs bash, git and Python 3.9 or later, plus the agent
|
|
81
|
+
CLIs you route models through; the doctor below shows which ones it finds. Installing from
|
|
82
|
+
npm brings none of these with it.
|
|
83
|
+
|
|
84
|
+
### From npm
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install -g @giamme/forge
|
|
88
|
+
forge install
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`forge install` links the installed package into each harness, as the installer does for a
|
|
92
|
+
checkout below. It is a step of its own rather than an npm install script, so it works where
|
|
93
|
+
scripts are turned off (`--ignore-scripts`). To update:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm install -g @giamme/forge@latest && forge install
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The links point into npm's global package directory, so they keep working across updates;
|
|
100
|
+
`forge install` refreshes antigravity's copied plugin. Under nvm, that directory belongs to
|
|
101
|
+
one Node version: after switching Node, install forge on the new one and run `forge install`
|
|
102
|
+
again.
|
|
103
|
+
|
|
104
|
+
> **Foundry users:** Foundry's `forge` has the same name, and whichever directory comes first
|
|
105
|
+
> on your `PATH` wins; npm's global bin usually comes first. Runs start from your harness
|
|
106
|
+
> (`/forge`), so this command is only for `forge install` and the optional tools. If you keep
|
|
107
|
+
> Foundry's `forge` first, run ours as `"$(npm root -g)/@giamme/forge/forge"`.
|
|
108
|
+
|
|
109
|
+
### From a git checkout
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
git clone https://github.com/Giamme/forge.git ~/.claude/skills/forge
|
|
113
|
+
bash ~/.claude/skills/forge/scripts/forge-install.sh
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Clone anywhere you like — the installer works out its own location and links from there.
|
|
117
|
+
`~/.claude/skills/forge` is just the tidiest home: claude reads that path directly, and
|
|
118
|
+
opencode and grok scan it on their own.
|
|
119
|
+
|
|
120
|
+
The installer also offers optional Fractal support in an interactive terminal. Declining
|
|
121
|
+
or a Fractal installation failure does not prevent ordinary Forge setup. See
|
|
122
|
+
[Fractal installation](#install-the-optional-runtime) for prerequisites and a separate install.
|
|
123
|
+
|
|
124
|
+
Then check what's actually reachable on your machine:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
bash ~/.claude/skills/forge/scripts/forge-dispatch.sh doctor
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
HARNESS STATUS DETAIL
|
|
132
|
+
codex found /Users/you/.nvm/versions/node/v24.18.0/bin/codex
|
|
133
|
+
claude found /Users/you/.local/bin/claude
|
|
134
|
+
openclaude found /Users/you/.nvm/versions/node/v24.18.0/bin/openclaude
|
|
135
|
+
opencode found /Users/you/.opencode/bin/opencode
|
|
136
|
+
antigravity MISSING agy not on PATH
|
|
137
|
+
grok found /Users/you/.grok/bin/grok
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
A `MISSING` harness is fine — forge only needs the ones you actually route models through.
|
|
141
|
+
|
|
142
|
+
**Binary presence is not reachability.** A harness can be installed and still fail on auth or
|
|
143
|
+
billing (opencode in particular fails this way), and that failure only surfaces mid-dispatch.
|
|
144
|
+
To prove a pairing end to end before spending a real run:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
bash scripts/forge-dispatch.sh dwarf <spec> --prompt-file /dev/stdin --repo <scratch dir> \
|
|
148
|
+
<<< 'Reply with exactly: FORGE_OK'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### What the installer does per harness
|
|
152
|
+
|
|
153
|
+
Each CLI discovers skills differently, and the differences are not guessable — so verify
|
|
154
|
+
with the listed check rather than trusting file placement.
|
|
155
|
+
|
|
156
|
+
| Harness | Mechanism | Verify with |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| claude | `~/.claude/skills/forge/` | `/forge` appears in the skill list |
|
|
159
|
+
| openclaude | symlink into `~/.openclaude/skills/` | `openclaude skills list --json` |
|
|
160
|
+
| codex | symlink into **`~/.codex/skills/`** | `codex exec "do you have a skill named forge?"` |
|
|
161
|
+
| opencode | **nothing** — it scans `~/.claude/` itself | `opencode debug skill` |
|
|
162
|
+
| antigravity | `agy plugin install` of a generated wrapper | `agy --print='/forge'` |
|
|
163
|
+
|
|
164
|
+
> **The one ongoing caveat:** `agy plugin install` *copies* the files instead of referencing
|
|
165
|
+
> them. The other four track the source directory live, so an edit reaches them instantly —
|
|
166
|
+
> antigravity keeps running whatever it copied. **Re-run `forge-install.sh` after any edit**,
|
|
167
|
+
> or agy silently runs a stale version.
|
|
168
|
+
|
|
169
|
+
`forge-install.sh --dry-run` prints what it would do and touches nothing. It never clobbers
|
|
170
|
+
a real directory that isn't a symlink.
|
|
171
|
+
|
|
172
|
+
### Updating all harness installations
|
|
173
|
+
|
|
174
|
+
Run the updater from your Forge checkout (or through one of its skill symlinks):
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
./forge update # same as: bash ~/.claude/skills/forge/scripts/forge-update.sh
|
|
178
|
+
./forge version # VERSION, checkout commit, tracked upstream, last known release
|
|
179
|
+
./forge version --check # ask the upstream for its newest release now
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
It fetches the current branch's configured upstream, fast-forwards the checkout, and
|
|
183
|
+
runs Forge's installer to refresh Claude, OpenClaude and Codex links, OpenCode discovery,
|
|
184
|
+
and Antigravity's copied plugin. Harnesses that are not present are skipped. Restart
|
|
185
|
+
active harness sessions afterward to load the updated skill.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# Preview without fetching or changing files:
|
|
189
|
+
bash <forge-checkout>/scripts/forge-update.sh --dry-run
|
|
190
|
+
|
|
191
|
+
# Refresh harness installations after local edits, without fetching:
|
|
192
|
+
bash <forge-checkout>/scripts/forge-update.sh --local
|
|
193
|
+
|
|
194
|
+
# Point a copied installation's updater at the original checkout:
|
|
195
|
+
bash <path>/scripts/forge-update.sh --source <forge-checkout>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
When a solo run, `plan` or `run` finishes, forge checks the checkout's upstream for a newer
|
|
199
|
+
release tag at most once a day (`git ls-remote`, 5-second timeout, cached in
|
|
200
|
+
`${XDG_STATE_HOME:-~/.local/state}/forge/update-check.json`) and, if there is one, prints a
|
|
201
|
+
single line on stderr: `forge 0.8 is available (you have 0.7.1): forge update`. The check is
|
|
202
|
+
silent offline, in a copied installation without Git, in CI (`CI` set) and with
|
|
203
|
+
`FORGE_UPDATE_CHECK=off`, and it never changes a run's outcome or exit code.
|
|
204
|
+
|
|
205
|
+
The default update requires a clean Git checkout and a branch with an upstream. Local
|
|
206
|
+
changes, detached HEAD, fetch failures and diverged branches stop the update without
|
|
207
|
+
resetting or stashing your work. `--local` deliberately allows current local edits.
|
|
208
|
+
Real skill directories are preserved; a conflicting directory or plugin installation
|
|
209
|
+
failure produces a nonzero exit and an explanation. After fixing an installation
|
|
210
|
+
failure, use `--local` to retry from the updated source. Updating Forge does not prompt
|
|
211
|
+
for optional Ripwire installation.
|
|
212
|
+
|
|
213
|
+
An npm installation is not a Git checkout, so the updater stops there and prints the npm
|
|
214
|
+
command [above](#from-npm) instead. `--local` still refreshes its harness installations.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Quick start
|
|
219
|
+
|
|
220
|
+
Inside any installed harness, invoke the skill:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
/forge "add retry with exponential backoff to the HTTP client" --dwarf sol:high
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
That's the whole minimum. `sol` builds it at `high` effort on Codex, `opus` reviews the
|
|
227
|
+
resulting diff at `xhigh` (the default reviewer), and you get back a summary of the change
|
|
228
|
+
plus qa's findings.
|
|
229
|
+
Before an interactive run, Forge asks **“Use Fractal for this run? [y/N]”** once.
|
|
230
|
+
Pass `--fractal` or `--no-fractal` to answer explicitly; unattended runs default off.
|
|
231
|
+
It also asks **“Open forge-ui to watch this run? [y/N]”** (`--ui` / `--no-ui`); on a yes,
|
|
232
|
+
the [forge-ui](#forge-ui) page opens as soon as the run exists. Resume and retry never ask.
|
|
233
|
+
|
|
234
|
+
**Pick both sides explicitly:**
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
/forge "fix the off-by-one in the pagination cursor" --dwarf luna:max --qa sol:xhigh
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Send a model through a different CLI** — same model, different harness:
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
/forge "port the config loader to TOML" --dwarf sol:high:openclaude --qa opus
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Let the dwarf off the leash** so it can install deps and run the suite:
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
/forge "add integration tests against a real Postgres container" --dwarf sol:xhigh --yolo-dwarf
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Split it across several dwarves working in parallel:**
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
/forge "add auth: middleware, token model, login route, and docs" \
|
|
256
|
+
--decompose-level medium \
|
|
257
|
+
--dwarf-high sol:xhigh --dwarf-low gemini:low:antigravity --dwarf luna:high
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
forge decomposes the goal, rates each task's difficulty, routes it, then **shows you the
|
|
261
|
+
table and stops** before spending anything.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Fractal execution
|
|
266
|
+
|
|
267
|
+
Fractal is an optional implementation backend for both solo and decomposed runs. It uses
|
|
268
|
+
the existing Forge dispatcher for Codex, Claude, OpenClaude, OpenCode and Antigravity.
|
|
269
|
+
Each work step gets a fresh model invocation; preserved files and child results provide
|
|
270
|
+
continuity when a parent resumes. Independent QA reviews the complete Forge task diff.
|
|
271
|
+
|
|
272
|
+
`--decompose-level` plans top-level Forge tasks before execution. `--fractal` lets an
|
|
273
|
+
implementer request nested children during execution. They can be used together, and
|
|
274
|
+
solo runs can use Fractal without a top-level decomposition.
|
|
275
|
+
|
|
276
|
+
### Install the optional runtime
|
|
277
|
+
|
|
278
|
+
From the Forge checkout:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
./forge fractal install --dry-run
|
|
282
|
+
./forge fractal install --yes --with-prerequisites
|
|
283
|
+
./forge fractal doctor --spec sol --spec opus --json
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The managed runtime pins **Fractal 1.2.0** at revision
|
|
287
|
+
`18793200c0d7e8cdb2db369ea3abe5647a1e15e4`, checks the source archive checksum,
|
|
288
|
+
and includes the required `wiki` executable. It prefers an existing Python 3.12–3.14;
|
|
289
|
+
with prerequisite installation selected, it can provision uv, Python 3.13 and tmux.
|
|
290
|
+
tmux installation uses an existing Homebrew on macOS or apt on Debian/Ubuntu.
|
|
291
|
+
The [installation reference](references/fractal.md#installation-and-provenance) covers
|
|
292
|
+
consent, supported bootstrap platforms, provenance and failure diagnostics.
|
|
293
|
+
|
|
294
|
+
Installation never enables Fractal for a run. `./forge` is the repository-local launcher,
|
|
295
|
+
the same one npm puts on your `PATH` as `forge`; installing Fractal does not add a command
|
|
296
|
+
or change your shell startup files.
|
|
297
|
+
`./forge --help` lists its commands.
|
|
298
|
+
Doctor checks the runtime and advertised harness capabilities without calling a provider.
|
|
299
|
+
|
|
300
|
+
### Start and inspect a run
|
|
301
|
+
|
|
302
|
+
Through the skill:
|
|
303
|
+
|
|
304
|
+
```text
|
|
305
|
+
/forge "add retry with exponential backoff to the HTTP client" --dwarf sol:high --fractal
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Or prepare a solo run directory outside the product repository and invoke the runner:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
FORGE_RUN="$HOME/.local/state/forge/examples/http-client"
|
|
312
|
+
mkdir -p "$FORGE_RUN"
|
|
313
|
+
printf '%s\n' 'Add bounded exponential backoff to the HTTP client and run its tests.' > "$FORGE_RUN/prompt.md"
|
|
314
|
+
bash scripts/forge-solo.sh "$FORGE_RUN" --repo /absolute/path/to/product \
|
|
315
|
+
--dwarf sol:high --qa opus --fractal --fractal-concurrency 3
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Choose a new run directory for unrelated work. Backend selection, routing pools and limits
|
|
319
|
+
are stored with the run; resume and explicit retry retain them. For a prepared, approved
|
|
320
|
+
decomposed plan, select the backend on `run`:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
bash scripts/forge-parallel.sh run /absolute/path/to/plan --fractal --dry-run
|
|
324
|
+
bash scripts/forge-parallel.sh run /absolute/path/to/plan --fractal
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Dry runs show resolved model pools, limits and proposed workspaces without installation
|
|
328
|
+
or provider calls. When a selected runtime is unavailable, interactive runs offer
|
|
329
|
+
installation; unattended runs return an actionable error instead of changing backends.
|
|
330
|
+
|
|
331
|
+
Discover the managed run ID, then inspect it from another terminal:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
./forge fractal runs --repo /absolute/path/to/product --json
|
|
335
|
+
./forge fractal open # live runs index
|
|
336
|
+
./forge fractal status RUN_ID --json
|
|
337
|
+
./forge fractal tree RUN_ID --json
|
|
338
|
+
./forge fractal logs RUN_ID --follow
|
|
339
|
+
./forge fractal report RUN_ID --html /absolute/path/to/report.html
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
`RUN_ID` is the `run-…` identifier returned by `runs`, not the solo directory or plan name.
|
|
343
|
+
The [run inspector](references/fractal-dashboard.md) opens on progress and problems, then lets
|
|
344
|
+
readers inspect task QA/history or node decisions, logs and changes. It refreshes about every
|
|
345
|
+
two seconds without replacing the workspace; search, selection, focus and scrolling remain in
|
|
346
|
+
place. “Pause live updates” affects the browser only. Browser access is read-only, bound to
|
|
347
|
+
`127.0.0.1` with an ephemeral access token. Closing its server does not stop execution.
|
|
348
|
+
Portable HTML embeds complete captured history and logs, works offline and needs no Fractal
|
|
349
|
+
runtime to read.
|
|
350
|
+
|
|
351
|
+
### Automatic recursive decomposition
|
|
352
|
+
|
|
353
|
+
`--fractal` permits workers to request children. To require a planning decision before
|
|
354
|
+
every eligible node implements, use `--fractal-auto-decompose`:
|
|
355
|
+
|
|
356
|
+
```text
|
|
357
|
+
/forge "<goal>" --dwarf luna:high --qa sol:high --fractal-auto-decompose --fractal-planner sol:xhigh --fractal-depth 3 --fractal-nodes 24
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
After the top-level Forge approval, the planner automatically splits useful work or
|
|
361
|
+
records why it is atomic. Children repeat this decision within the approved ownership
|
|
362
|
+
and frozen limits. `--fractal-planner` selects a separate decision model; when omitted,
|
|
363
|
+
Forge reuses the configured top-level planner, then the task's initial root model.
|
|
364
|
+
Decisions use disposable snapshots, persist for resume, and appear in tree/status/HTML
|
|
365
|
+
reports. Ordinary `--fractal` and existing runs retain their current behavior. Start a
|
|
366
|
+
new run to change policy or planner. See the [full decision contract](references/fractal.md#automatic-decomposition-decisions).
|
|
367
|
+
|
|
368
|
+
### Bounds, recovery and acceptance
|
|
369
|
+
|
|
370
|
+
Defaults per Forge task are two nesting levels below implementation, three unsettled
|
|
371
|
+
direct children per node, twelve lifetime nodes, six iterations per node per attempt,
|
|
372
|
+
and a 45-minute attempt deadline excluding explicit pauses. Three model slots are shared
|
|
373
|
+
across the whole Forge run, including QA. `--max-parallel` bounds top-level task pipelines;
|
|
374
|
+
`--fractal-concurrency` bounds model invocations across their nested trees.
|
|
375
|
+
|
|
376
|
+
Child difficulty selects a frozen `--dwarf-low`, `--dwarf-medium` or `--dwarf-high` pool,
|
|
377
|
+
falling back to the configured general dwarf, then the parent. Dependencies and owned
|
|
378
|
+
paths constrain admission; overlapping ownership is serialized. Fractal never implies
|
|
379
|
+
`--yolo-dwarf`. Costs are observational; unavailable usage stays unknown and dollar-cap
|
|
380
|
+
requests are rejected.
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
./forge fractal pause RUN_ID
|
|
384
|
+
./forge fractal resume RUN_ID
|
|
385
|
+
./forge fractal stop RUN_ID
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Pause and stop preserve work and diagnostics. Run-level resume recovers recorded execution
|
|
389
|
+
and the Forge pipeline without repeating accepted stages. Use explicit runner retry for
|
|
390
|
+
a new attempt, retaining work and the original Forge QA baseline. Controls also accept
|
|
391
|
+
`--task TASK_ID` and `--task TASK_ID --node NODE_ID`; those IDs come from `tree` output.
|
|
392
|
+
|
|
393
|
+
Managed data lives in `${XDG_STATE_HOME:-$HOME/.local/state}/forge/fractal/`. Solo execution
|
|
394
|
+
uses an isolated snapshot and imports changes only after checking source drift, preserving
|
|
395
|
+
staging and leaving changes uncommitted. Decomposed candidates return to Forge's existing
|
|
396
|
+
review/integration pipeline. **Fractal completion is not Forge acceptance**: QA, repository
|
|
397
|
+
verification and authorization to update the user's branch remain distinct.
|
|
398
|
+
|
|
399
|
+
See the [full Fractal reference](references/fractal.md) for limits, child requests and
|
|
400
|
+
recovery details, and [verification evidence](tests/fractal-verification.md) for the tested
|
|
401
|
+
platforms and browser checks. No paid-provider or speed/cost claim follows from fake-CLI tests.
|
|
402
|
+
|
|
403
|
+
## The spec: `alias:effort:harness`
|
|
404
|
+
|
|
405
|
+
Everywhere forge takes a model, it takes the same three-field spec. Each field after the
|
|
406
|
+
first is optional:
|
|
407
|
+
|
|
408
|
+
| Spec | Resolves to |
|
|
409
|
+
|---|---|
|
|
410
|
+
| `sol` | alias `sol`, default effort for the role, its default harness (codex) |
|
|
411
|
+
| `sol:xhigh` | `sol` at `xhigh` effort on its default harness |
|
|
412
|
+
| `sol:xhigh:openclaude` | same model and effort, run through the `openclaude` CLI |
|
|
413
|
+
| `opus::claude` | explicit harness, effort left to the role default — the middle field may be empty |
|
|
414
|
+
| `gpt-5.6-sol::codex` | not in the registry? passed through as a literal model id — but only with an explicit harness |
|
|
415
|
+
|
|
416
|
+
An alias with no registry row still works, provided you name the harness. forge won't guess
|
|
417
|
+
a default harness for a model it has never seen; that guess would silently spend quota on
|
|
418
|
+
the wrong provider.
|
|
419
|
+
|
|
420
|
+
**Role defaults when you leave things out:**
|
|
421
|
+
|
|
422
|
+
- `--dwarf` missing → forge **asks**. Every dispatch spends real quota on your account, so
|
|
423
|
+
picking for you isn't its call.
|
|
424
|
+
- `--qa` missing → `opus`, which runs locally through `claude` and adds no cost surprise.
|
|
425
|
+
- effort missing → dwarf builds at `medium`, qa reviews at `xhigh`. A reviewer thinking
|
|
426
|
+
*less* than the implementer tends to wave things through, which wastes the stage entirely.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Effort, ceilings and clamping
|
|
431
|
+
|
|
432
|
+
Every harness has its own effort ladder:
|
|
433
|
+
|
|
434
|
+
| Harness | Ladder |
|
|
435
|
+
|---|---|
|
|
436
|
+
| codex | `low medium high xhigh max ultra` |
|
|
437
|
+
| claude | `low medium high xhigh max` |
|
|
438
|
+
| openclaude | `low medium high xhigh max ultracode` |
|
|
439
|
+
| antigravity | `low medium high` |
|
|
440
|
+
| grok | `low medium high xhigh max` (grok-4.7 offers up to `xhigh`) |
|
|
441
|
+
| opencode | provider-specific; forwarded verbatim |
|
|
442
|
+
|
|
443
|
+
And support is more irregular than one ceiling per backend:
|
|
444
|
+
|
|
445
|
+
- **Ceilings** — `luna` tops out at `max` on codex, so `luna:ultra` clamps down.
|
|
446
|
+
- **Gaps** — `gemini-pro` offers `low` and `high` but *not* `medium`, so `gemini-pro:medium`
|
|
447
|
+
clamps to `low`.
|
|
448
|
+
- **None at all** — Claude models on antigravity take no effort flag; passing one errors, so
|
|
449
|
+
forge reports it and drops it.
|
|
450
|
+
|
|
451
|
+
When forge clamps, it says so (`clamped=ultra -> max`) and the orchestrator passes that on
|
|
452
|
+
to you. Getting less thinking than you paid for, silently, is exactly the failure worth ten
|
|
453
|
+
lines of validation to prevent — and it's a real one: **codex accepts an invalid effort
|
|
454
|
+
without complaint and quietly runs at some other level.** forge validates first.
|
|
455
|
+
|
|
456
|
+
A word that isn't a recognized effort at all is an error, not a clamp.
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## Yolo mode
|
|
461
|
+
|
|
462
|
+
`--yolo-dwarf` and `--yolo-qa` drop that role's sandbox and approval gates:
|
|
463
|
+
|
|
464
|
+
| Harness | Flag used |
|
|
465
|
+
|---|---|
|
|
466
|
+
| codex | `--dangerously-bypass-approvals-and-sandbox` |
|
|
467
|
+
| claude / openclaude / antigravity | `--dangerously-skip-permissions` |
|
|
468
|
+
| opencode | `--auto` |
|
|
469
|
+
| grok | `--always-approve` |
|
|
470
|
+
|
|
471
|
+
Each flag applies to **one role only**, so `--yolo-dwarf` alone is the normal combination:
|
|
472
|
+
the implementer gets a free hand, the reviewer stays fenced in and still can't edit the code
|
|
473
|
+
it's judging.
|
|
474
|
+
|
|
475
|
+
You mostly don't need it. On codex, claude and openclaude the sandboxed roles are already
|
|
476
|
+
useful — the dwarf edits files and runs tests, qa reads and runs commands but cannot write.
|
|
477
|
+
Reach for yolo when a role genuinely must escape the sandbox: installing dependencies,
|
|
478
|
+
writing outside the repo, touching the network.
|
|
479
|
+
|
|
480
|
+
> **Antigravity is the exception worth knowing.** Without yolo it cannot run *any* shell
|
|
481
|
+
> command, and a single denied command **aborts the whole run** rather than degrading — a
|
|
482
|
+
> dwarf that tries to run the test suite ends with an error and an empty diff. forge detects
|
|
483
|
+
> this and appends a note telling that role not to attempt commands, which is why an
|
|
484
|
+
> antigravity dwarf reports its work as unverified by execution. If you want a Gemini dwarf
|
|
485
|
+
> that can actually run the tests it writes, it needs `--yolo-dwarf`.
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## Decomposed runs
|
|
490
|
+
|
|
491
|
+
`--decompose-level low|medium|high` turns one dwarf into many. Without the flag, nothing
|
|
492
|
+
changes — one dwarf, one QA pass.
|
|
493
|
+
|
|
494
|
+
### Two independent axes
|
|
495
|
+
|
|
496
|
+
Easy to conflate, so: **decompose-level** is how finely the *goal* is split. **difficulty**
|
|
497
|
+
is how hard each resulting *task* is.
|
|
498
|
+
|
|
499
|
+
| `--decompose-level` | Split |
|
|
500
|
+
|---|---|
|
|
501
|
+
| `low` | only where the goal has obviously independent pieces; 2–3 coarse tasks |
|
|
502
|
+
| `medium` | one task per coherent unit (module, feature slice); typically 4–6 |
|
|
503
|
+
| `high` | finest split where each task is still independently reviewable *and* independently correct; typically 7–12 |
|
|
504
|
+
|
|
505
|
+
| `difficulty` | Means |
|
|
506
|
+
|---|---|
|
|
507
|
+
| `low` | mechanical and local: rename, move, docs, a test for behaviour that already exists |
|
|
508
|
+
| `medium` | self-contained implementation against a clear spec; bounded blast radius |
|
|
509
|
+
| `high` | needs design judgment, cross-cutting behaviour, subtle edge cases, state or concurrency |
|
|
510
|
+
|
|
511
|
+
Difficulty is about **judgment required, not diff size**. A five-hundred-line mechanical
|
|
512
|
+
rename is `low`. A ten-line concurrency fix is `high`. Getting this backwards sends the cheap
|
|
513
|
+
model at the subtle problem and the expensive one at the boilerplate — worse than not routing
|
|
514
|
+
at all.
|
|
515
|
+
|
|
516
|
+
### Routing dwarves to tasks
|
|
517
|
+
|
|
518
|
+
Per-task assignment can't be expressed when you type the command, because the tasks don't
|
|
519
|
+
exist yet. Naming a dwarf per task would mean guessing how the goal will split. So you map
|
|
520
|
+
**model tiers to difficulty tiers**, once, in advance — three layers, each optional:
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
# 1. one dwarf for everything (today's syntax, unchanged)
|
|
524
|
+
--dwarf sol:xhigh
|
|
525
|
+
|
|
526
|
+
# 2. per-tier routing, with plain --dwarf as the fallback for any tier not named
|
|
527
|
+
--dwarf-high sol:xhigh --dwarf-medium luna:high --dwarf-low gemini:low:antigravity
|
|
528
|
+
--dwarf sonnet:medium
|
|
529
|
+
|
|
530
|
+
# 3. pool several models within one tier — round-robined across tasks
|
|
531
|
+
--dwarf-high sol:xhigh,terra:xhigh
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
`--qa`, `--qa-high`, `--qa-medium`, `--qa-low` mirror it exactly. QA has no tier rules by
|
|
535
|
+
default and falls back to `opus`.
|
|
536
|
+
|
|
537
|
+
Resolution order per task: **explicit column value → the tier's rule → plain `--dwarf` →
|
|
538
|
+
`UNASSIGNED`**. An `UNASSIGNED` task makes `plan` exit `2` and `run` refuse outright. forge
|
|
539
|
+
does not invent a model when you haven't said which one should spend your quota.
|
|
540
|
+
|
|
541
|
+
Layer three is **the approval gate**: after decomposition, before any dispatch, you get the
|
|
542
|
+
task table with each task's difficulty and resolved dwarf/qa. Retier a task ("2 is actually
|
|
543
|
+
medium") and it re-routes per your rules, or override a single row's dwarf outright. This is
|
|
544
|
+
also the cost gate — `high` on a large goal is 20+ dispatches, which should be visible before
|
|
545
|
+
it's spent.
|
|
546
|
+
|
|
547
|
+
**Exploration is on by default** (`--explore 0.2`):
|
|
548
|
+
- **Which rows:** only rows left to forge, never one whose dwarf column you filled.
|
|
549
|
+
- **What changes:** now and then such a row gets its *neighbouring* tier's spec from your own
|
|
550
|
+
pools: the next cheaper tier for medium and high rows, the next stronger for low rows.
|
|
551
|
+
Pools that differ only in effort (`--dwarf-low sol --dwarf-high sol:xhigh`) compare the
|
|
552
|
+
two efforts.
|
|
553
|
+
- **How often:** about one row in ten at the default.
|
|
554
|
+
- **What you see:** each explored row is marked `*` in the table, before anything is spent.
|
|
555
|
+
- **What it's for:** `forge dataset routing` reads the draws back as pass rates per model and
|
|
556
|
+
effort. Pass `--explore 0` to turn it off.
|
|
557
|
+
|
|
558
|
+
See [decompose.md](references/decompose.md#exploratory-routing-on-by-default).
|
|
559
|
+
|
|
560
|
+
### Waves, and what actually bounds parallelism
|
|
561
|
+
|
|
562
|
+
A **wave** is the largest set of tasks whose dependencies are all satisfied by earlier waves
|
|
563
|
+
**and** whose declared `files` sets are pairwise disjoint.
|
|
564
|
+
|
|
565
|
+
Disjointness — not the task count — is the real bound. Two dwarves editing one file produce a
|
|
566
|
+
conflict no reviewer can untangle and no merge can resolve honestly, so they land in different
|
|
567
|
+
waves however the goal was decomposed. When that happens forge says so:
|
|
568
|
+
|
|
569
|
+
```
|
|
570
|
+
deferrals (serialized to protect the merge):
|
|
571
|
+
wave 1: lint deferred (file overlap with a task already in this wave)
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
That line matters. A decomposition that looks parallel but serializes at run time should
|
|
575
|
+
announce it, rather than leaving you wondering why eight tasks took eight rounds. When most
|
|
576
|
+
tasks overlap, the fix is a better split — not more parallelism.
|
|
577
|
+
|
|
578
|
+
Waves are a planning preview. A Python 3 standard-library coordinator starts eligible tasks
|
|
579
|
+
as prerequisites merge and capacity becomes available, while excluding overlapping active
|
|
580
|
+
paths. Merges are serialized. Each task pins the integration commit at dispatch; retries keep
|
|
581
|
+
that baseline, and QA sees only baseline membership recorded for that task.
|
|
582
|
+
|
|
583
|
+
### Fresh session per task, plus a capsule
|
|
584
|
+
|
|
585
|
+
Every dispatch is a **clean slate** — forge never passes `--continue`, `--resume`, or
|
|
586
|
+
`codex exec resume`. One task's confused turn can't poison another, and each task's context
|
|
587
|
+
stays small.
|
|
588
|
+
|
|
589
|
+
Clean slates alone would leave every dwarf blind to the wider run, so a short `capsule.md` is
|
|
590
|
+
generated from the task's pinned baseline and preserved for both implementation and QA:
|
|
591
|
+
|
|
592
|
+
```
|
|
593
|
+
# forge run <run-id>
|
|
594
|
+
Goal: <overall goal>
|
|
595
|
+
You are doing ONE task of <N>. Do not do the others' work.
|
|
596
|
+
|
|
597
|
+
## Your task
|
|
598
|
+
tokens — Add the token model difficulty: medium
|
|
599
|
+
Files you own: src/auth/token.py
|
|
600
|
+
|
|
601
|
+
## Run status
|
|
602
|
+
id diff status files
|
|
603
|
+
middleware high MERGED src/auth/middleware.py
|
|
604
|
+
tokens medium THIS ONE src/auth/token.py
|
|
605
|
+
docs low NOT IN BASE README.md
|
|
606
|
+
|
|
607
|
+
## Ground rules
|
|
608
|
+
- Your branch already contains every MERGED task's work — do not reimplement it.
|
|
609
|
+
- Files owned by other tasks are off limits; another dwarf is editing them now.
|
|
610
|
+
- If your task truly needs a change in someone else's file, say so in your final
|
|
611
|
+
message instead of making it.
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
It's written **per task**, not once per run — tasks in a wave execute concurrently, so a
|
|
615
|
+
single shared capsule file is a race in which each task overwrites it and reads back whichever
|
|
616
|
+
sibling wrote last, handing a dwarf someone else's task description.
|
|
617
|
+
|
|
618
|
+
For QA the capsule additionally scopes the review: code from `MERGED` tasks is already in the
|
|
619
|
+
base and is not this task's bug. Without that, a wave-2 reviewer reports wave-1 code as
|
|
620
|
+
defects in the diff it was handed.
|
|
621
|
+
|
|
622
|
+
### Verdicts and failure
|
|
623
|
+
|
|
624
|
+
Decompose-mode QA must end with a sentinel line:
|
|
625
|
+
|
|
626
|
+
```
|
|
627
|
+
FORGE_VERDICT: PASS no confirmed correctness bug (style nits are not failures)
|
|
628
|
+
FORGE_VERDICT: FAIL at least one CONFIRMED correctness bug
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
Only a standalone final line counts, and the parser is shared by every runner (solo,
|
|
632
|
+
decomposed, Fractal's QA checkpoint). The prompt shows each verdict with a dash and its
|
|
633
|
+
meaning, and some reviewers copy that whole line, so `FORGE_VERDICT: PASS — meaning` is
|
|
634
|
+
accepted, as are trailing whitespace and CRLF line endings. Prose or a quoted marker earlier
|
|
635
|
+
in the reply is never a verdict. A missing verdict is `UNKNOWN` and is treated exactly like a
|
|
636
|
+
failure — excluded from integration and flagged. Merging a diff whose reviewer never reached
|
|
637
|
+
a conclusion would defeat the point of reviewing it, so the ambiguous case fails safe.
|
|
638
|
+
|
|
639
|
+
A failing task is excluded and **its branch and worktree are preserved**; the rest of the run
|
|
640
|
+
proceeds. forge never loops a dwarf against its reviewer on its own: a retry happens when you
|
|
641
|
+
type `retry`, or when you opt in for a run with `--retry-failed N` (next section).
|
|
642
|
+
|
|
643
|
+
An `UNKNOWN` task is different from a rejected one: the reviewer never judged the code, so
|
|
644
|
+
`retry` (or `--retry-failed`) asks **QA again on the unchanged work** instead of paying an
|
|
645
|
+
implementer to rewrite something nobody rejected. No attempt is spent and no dwarf runs. If
|
|
646
|
+
the worktree no longer matches what was reviewed, the full task runs instead.
|
|
647
|
+
|
|
648
|
+
#### Retrying inside a run: `--retry-failed N`
|
|
649
|
+
|
|
650
|
+
```bash
|
|
651
|
+
forge-parallel.sh run <plan-dir> --retry-failed 2
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
A task whose reviewer says `FAIL` (or `UNKNOWN`) is re-queued at once, **inside the same
|
|
655
|
+
run**, up to `N` more times — with the reviewer's findings carried into the next prompt exactly
|
|
656
|
+
as `retry` does. Other tasks keep running meanwhile; dependents still wait for the task to
|
|
657
|
+
*merge*, so nothing downstream starts early. Without this, a failed task waited for the whole
|
|
658
|
+
run (verification included) to finish before a human could retry it.
|
|
659
|
+
|
|
660
|
+
It is opt-in and per invocation: it is not remembered by the plan, and the orchestrator never
|
|
661
|
+
adds it unprompted. Only reviewer rejections are retried automatically — `TIMEOUT`, `ERROR`,
|
|
662
|
+
`INVALIDATED` and `CONFLICT` still need a human. A retry that changes nothing (empty diff, or
|
|
663
|
+
a diff byte-identical to the previous attempt) writes a `noretry` marker and is **not**
|
|
664
|
+
retried again, so a stuck task cannot burn its whole budget repeating itself.
|
|
665
|
+
`tasks/<id>/attempts.tsv` records every entry (epoch, attempt number, resulting status, infra
|
|
666
|
+
class).
|
|
667
|
+
|
|
668
|
+
#### Infrastructure failures are not task failures
|
|
669
|
+
|
|
670
|
+
A quota or usage limit, an auth problem, a rate limit, a network error, or a harness that
|
|
671
|
+
returns nothing at all says nothing about the work. forge classifies those from the dispatch
|
|
672
|
+
log (`forge-runtime.py classify`) and `forge-dispatch.sh` exits **8** instead of 4. A
|
|
673
|
+
decomposed task then ends as **`INFRA`**, not `ERROR`/`FAIL`, and:
|
|
674
|
+
|
|
675
|
+
- **no attempt is spent** — a dwarf-stage stop refunds the counter; a QA-stage stop keeps the
|
|
676
|
+
dwarf's committed work and re-runs **QA only** next time (`tasks/<id>/resume`, revalidated
|
|
677
|
+
against the branch tip and fingerprint before it is trusted);
|
|
678
|
+
- the scheduler stops dispatching, lets running tasks drain, leaves unstarted ones `PENDING`
|
|
679
|
+
(never `BLOCKED`) and `run` exits **8**: fix the cause, then run the same command again;
|
|
680
|
+
- `run --infra-retries M` waits instead — up to `M` times, using the provider's retry-after
|
|
681
|
+
hint when it gave one (capped at 6 hours) and otherwise 60 s doubling to 30 min
|
|
682
|
+
(`FORGE_INFRA_BACKOFF` sets the base) — and resumes in the same run;
|
|
683
|
+
- verification is skipped for a paused run (it is unfinished); the next `run` verifies at its end.
|
|
684
|
+
|
|
685
|
+
`.infra` (`dwarf.infra`/`qa.infra`) holds `class=`, `retry_after=`, `detail=`, `rc=`,
|
|
686
|
+
`role=`. Detection is deliberately strict: strong phrases only, and ordinary text that merely
|
|
687
|
+
mentions "rate limit" in a reply that otherwise succeeded is never classified. A false
|
|
688
|
+
positive costs a pause, not a spent attempt. Under Fractal a dwarf-side infrastructure stop
|
|
689
|
+
still collapses to Fractal's own "failed" handling; QA-side stops propagate as above.
|
|
690
|
+
|
|
691
|
+
#### Severity, thresholds and known issues
|
|
692
|
+
|
|
693
|
+
QA is asked to label every finding `- [P0..P3][CONFIRMED|PLAUSIBLE] file:line — what breaks`
|
|
694
|
+
(P0 data loss/security/crash on the main path, P1 wrong behaviour on realistic input, P2
|
|
695
|
+
edge-case bug, P3 minor). By default the verdict rule is unchanged — any CONFIRMED bug fails.
|
|
696
|
+
|
|
697
|
+
`--qa-threshold Pn` (on `run` or `retry`; remembered in the plan; `none` clears it) names the
|
|
698
|
+
**least severe level that still blocks**: `P2` means P0–P2 block and only P3 is tolerated.
|
|
699
|
+
A task-specific `tasks/<id>/qa_threshold` file beats the plan-level value, which is how a
|
|
700
|
+
dev-tooling or investigation task gets a looser bar than production code. With a threshold, a
|
|
701
|
+
reviewer `FAIL` made only of CONFIRMED findings that are all labelled and strictly milder than
|
|
702
|
+
the threshold is accepted as **PASS with known issues**: the task merges, `tasks/<id>/known_issues.md`
|
|
703
|
+
keeps the findings, `qa.gate` records the decision, and every run/retry/integrate summary
|
|
704
|
+
lists them. The literal status stays `PASS`, so nothing downstream needed a new vocabulary.
|
|
705
|
+
It fails safe: an unlabelled CONFIRMED finding, or a FAIL with no CONFIRMED finding at all,
|
|
706
|
+
is never tolerated, and without a threshold nothing changes.
|
|
707
|
+
|
|
708
|
+
#### Shadow reviews: how much of a verdict is the reviewer?
|
|
709
|
+
|
|
710
|
+
`plan --shadow-qa <spec>[,<spec>...] [--shadow-rate R]` (default R = 0.1) sends a sampled
|
|
711
|
+
share of reviewed attempts to a second reviewer, the first listed one of another model
|
|
712
|
+
family than the task's. Its verdict goes to the run dataset and nothing in the run reads it,
|
|
713
|
+
so it never changes a status, merge, memory or retry. `forge dataset status` then reports how
|
|
714
|
+
often the two reviewers agree, overall and per model pair. The table shows the extra reviews
|
|
715
|
+
before you approve. The shadow runs beside the main review and is stopped soon after that
|
|
716
|
+
finishes, so the most it costs is reviews and a little latency. See
|
|
717
|
+
[decomposition](references/decompose.md#shadow-reviews-opt-in).
|
|
718
|
+
|
|
719
|
+
#### Implementer guards
|
|
720
|
+
|
|
721
|
+
The dwarf is told not to commit, switch branches, start background processes, or claim checks
|
|
722
|
+
it did not run. forge then **observes** whether it did, rather than trusting its report:
|
|
723
|
+
|
|
724
|
+
- **self-commit / branch switch** — HEAD or the checked-out ref moved during the dispatch;
|
|
725
|
+
- **orphan processes** — the harness runs in its own session; anything still alive in it after
|
|
726
|
+
the harness exits is listed in `<role>.orphans` and stopped (`FORGE_ORPHAN_GUARD=off`
|
|
727
|
+
disables this; it is also skipped under Fractal);
|
|
728
|
+
- **promised later work** — "running in the background", "will report back once it
|
|
729
|
+
finishes" and similar in the final message.
|
|
730
|
+
|
|
731
|
+
These are warnings recorded in `tasks/<id>/guard.txt`, shown in the QA prompt ("Implementer
|
|
732
|
+
guard notes") and in the run summary. They never change a status: the diff and the reviewer
|
|
733
|
+
still decide. In solo mode a self-commit prints the `git reset --soft <old HEAD>` that would
|
|
734
|
+
undo it; forge never resets anything itself.
|
|
735
|
+
|
|
736
|
+
#### Verification coverage and flaky reruns
|
|
737
|
+
|
|
738
|
+
`--verify` is whatever you hand it, and a gate that skips a suite the project defines lets a
|
|
739
|
+
regression there reach your branch silently. `forge-verify-coverage.py` reads the project's own
|
|
740
|
+
test configuration (`package.json` scripts, Makefile targets, pytest/tox, Cargo, Go, Ruby,
|
|
741
|
+
Elixir, `scripts/test*.sh`), follows aggregate scripts, and **warns** — before any model is
|
|
742
|
+
paid for, again in the run summary and at integrate — about suites the verify command never
|
|
743
|
+
runs, e.g. `suite 'test:contract' (npm run test:contract) is not run by the verify command`.
|
|
744
|
+
It is advisory: it never fails a run, and `verification.coverage.txt` keeps the lines. It is a
|
|
745
|
+
text heuristic (root of the repo only; a mentioned command counts as run).
|
|
746
|
+
|
|
747
|
+
A failing verify command is rerun **once** (`--verify-retries N`; `0` restores the single strict
|
|
748
|
+
run) when the source fingerprint is unchanged. If the rerun passes the status is `PASS` but
|
|
749
|
+
`verification.flaky` is written, `verification.log` keeps the first (failing) run,
|
|
750
|
+
`verification.retry.log` the second, and `run`/`integrate` print a loud `FLAKY` line — an
|
|
751
|
+
intermittent failure can be a real bug, so it is never passed off as a clean pass. A command
|
|
752
|
+
that changes the tree is never retried. This deterministic rerun comes before Jev's optional
|
|
753
|
+
model-assisted triage, which then does not run.
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## Planning
|
|
758
|
+
|
|
759
|
+
A dwarf that is handed only an objective plans its own approach, silently, inside its own
|
|
760
|
+
run. Usually fine — but in a decomposed run the capsule prevents two dwarves editing one
|
|
761
|
+
*file* and does nothing about them inventing incompatible *interfaces* at a shared seam.
|
|
762
|
+
Both stay in their lane, both pass their own QA, and the mismatch surfaces at integration.
|
|
763
|
+
|
|
764
|
+
So forge plans before it dispatches. By default the orchestrator does it and writes the
|
|
765
|
+
approach into each task's prompt — no extra dispatch. `--planner <spec>` hands that stage to
|
|
766
|
+
a named model instead:
|
|
767
|
+
|
|
768
|
+
```bash
|
|
769
|
+
/forge "migrate the API layer to gRPC" --decompose-level medium \
|
|
770
|
+
--planner sol:xhigh --dwarf-high sol:high --dwarf luna:high
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
**One planner dispatch per run, never one per task.** That is the whole point: a single mind
|
|
774
|
+
designs both sides of every seam, where N independent planners would recreate the problem.
|
|
775
|
+
Effort defaults to `xhigh`, since a bad plan is executed at full price by every dwarf
|
|
776
|
+
downstream of it. The planner runs with QA's permission profile — it reads the repo and
|
|
777
|
+
writes nothing to it.
|
|
778
|
+
|
|
779
|
+
The approach shows up in the approval table under each task, which is the last moment it can
|
|
780
|
+
be changed for free, and it is handed to QA as the *intended* approach — so a review can
|
|
781
|
+
finally say "this works, but it abandons the planned shape" rather than only judging against
|
|
782
|
+
the objective.
|
|
783
|
+
|
|
784
|
+
## Project memory
|
|
785
|
+
|
|
786
|
+
Every forge dispatch is a clean slate, so without help the tenth run in a repo rediscovers
|
|
787
|
+
what the first one learned. forge keeps that in the repo, in two files:
|
|
788
|
+
|
|
789
|
+
```
|
|
790
|
+
.forge/memory.md small, capped, injected into every prompt
|
|
791
|
+
.forge/ledger.tsv append-only, one row per dispatch, never injected
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
The split is the design. Injected context is paid for on every dispatch of every future run,
|
|
795
|
+
so "remember everything" and "stay small" pull against each other — the ledger remembers at
|
|
796
|
+
zero context cost and is read only when asked, while `memory.md` is capped at 40 lines / 4 KB.
|
|
797
|
+
|
|
798
|
+
```markdown
|
|
799
|
+
## Verify
|
|
800
|
+
- pytest -q runs the suite; make test also lints and is 4x slower
|
|
801
|
+
|
|
802
|
+
## Known traps
|
|
803
|
+
- tests/test_api.py::test_timeout is flaky under -n auto [tests/test_api.py]
|
|
804
|
+
|
|
805
|
+
## Recurring QA findings
|
|
806
|
+
- 3x: route added without updating openapi.yaml [src/routes]
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### How a fact gets in
|
|
810
|
+
|
|
811
|
+
The role that just did the work writes it down, in its final message, next to the verdict:
|
|
812
|
+
|
|
813
|
+
```
|
|
814
|
+
FORGE_LEARNING: trap | src/proto/*_pb2.py is generated, never hand-edit [src/proto]
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
No extra dispatch and no separate model guessing what mattered — the one that hit the
|
|
818
|
+
landmine is the one that records it. Everything after that is deterministic bash.
|
|
819
|
+
|
|
820
|
+
**What stops it becoming a junk drawer:**
|
|
821
|
+
|
|
822
|
+
| Guard | Effect |
|
|
823
|
+
|---|---|
|
|
824
|
+
| recurrence threshold | a `finding` needs **two distinct runs** before it is injected. One occurrence is an incident, not a pattern |
|
|
825
|
+
| stale anchors | an entry ending in `[path]` is dropped once that path stops existing, so memory stays true as code moves |
|
|
826
|
+
| hard cap | over 40 lines / 4 KB, lowest-count and least-recent facts are trimmed; one entry is clamped to 180 chars |
|
|
827
|
+
| bias to silence | the prompt says most runs teach nothing durable and emitting nothing is correct. A false fact costs every future run; a missing one costs a single rediscovery |
|
|
828
|
+
|
|
829
|
+
Only `verify` and `trap` are kept on first sighting — those are facts, true the first time.
|
|
830
|
+
|
|
831
|
+
### Slices, and what forge writes
|
|
832
|
+
|
|
833
|
+
| Role | Gets |
|
|
834
|
+
|---|---|
|
|
835
|
+
| planner, dwarf | Verify + Known traps + Recurring findings |
|
|
836
|
+
| qa | Known traps + Recurring findings |
|
|
837
|
+
|
|
838
|
+
QA is not told the build command; it is not building anything. It *is* told the recurring
|
|
839
|
+
findings, so it can notice this is the fourth time the same bug shipped.
|
|
840
|
+
|
|
841
|
+
`.forge/` is the **only** thing forge writes into your working tree rather than onto a branch
|
|
842
|
+
of its own. It is excluded from every captured diff, so a reviewer never sees it as the
|
|
843
|
+
dwarf's work — but it *will* show up as untracked in `git status` after your first run.
|
|
844
|
+
|
|
845
|
+
### What to do with `.forge/` when it appears
|
|
846
|
+
|
|
847
|
+
forge never commits it. That decision is yours, and there are three sane answers:
|
|
848
|
+
|
|
849
|
+
| You want | Do this |
|
|
850
|
+
|---|---|
|
|
851
|
+
| memory shared with your team, and with future you | **commit `.forge/`** — the intended case; every teammate's runs then build on the same facts |
|
|
852
|
+
| memory kept to your machine | `echo '.forge/' >> .gitignore` |
|
|
853
|
+
| no memory at all | `--no-memory`, or `FORGE_MEMORY=off` in your environment |
|
|
854
|
+
|
|
855
|
+
Two things that are easy to get wrong:
|
|
856
|
+
|
|
857
|
+
**Gitignoring is not the same as turning it off.** An ignored `.forge/` is still written and
|
|
858
|
+
still injected into every prompt — it just stops travelling. Only `--no-memory` /
|
|
859
|
+
`FORGE_MEMORY=off` actually disables the feature.
|
|
860
|
+
|
|
861
|
+
**Never commit one file and ignore the other.** `memory.md` is *rebuilt from* `ledger.tsv` on
|
|
862
|
+
every write, so a clone that has the memory but not the ledger loses everything the moment
|
|
863
|
+
anyone runs forge there — the rebuild regenerates `memory.md` from that machine's own short
|
|
864
|
+
ledger and the shared facts are gone, silently. Verified behaviour, not a theoretical risk:
|
|
865
|
+
|
|
866
|
+
```
|
|
867
|
+
teammate A commits memory.md only, ledger.tsv gitignored
|
|
868
|
+
memory.md: - pytest -q runs the suite
|
|
869
|
+
- src/a.py is generated [src/a.py]
|
|
870
|
+
|
|
871
|
+
teammate B clones, runs forge once, learns one unrelated thing
|
|
872
|
+
memory.md: - b learned something new ← A's two facts destroyed
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
Commit both, or neither. If you commit them, expect `ledger.tsv` to gain a line per dispatch
|
|
876
|
+
— small, but it will appear in your diffs; that growth is the price of the recurrence
|
|
877
|
+
counting that keeps `memory.md` honest.
|
|
878
|
+
|
|
879
|
+
If you contribute to someone else's repo, gitignore it. A pull request containing forge's
|
|
880
|
+
notes to itself is noise to a reviewer who does not use forge.
|
|
881
|
+
|
|
882
|
+
## Run dataset
|
|
883
|
+
|
|
884
|
+
Run dirs get deleted, and the ledger keeps spec strings and verdicts but no inputs. So
|
|
885
|
+
every run also records its raw inputs and outcomes outside every repo, in
|
|
886
|
+
`~/.local/share/forge/dataset/` (`FORGE_DATA_HOME` moves it, `FORGE_DATASET=off` turns
|
|
887
|
+
it off). That covers resolved models, prompts, per-attempt diffs, verdicts and findings,
|
|
888
|
+
tokens, merge and verification outcomes, and the full request behind every Jev judgment.
|
|
889
|
+
It exists to train and calibrate decision models on what forge actually did.
|
|
890
|
+
|
|
891
|
+
```sh
|
|
892
|
+
./forge dataset status # size, counts, and anything that looks incomplete
|
|
893
|
+
./forge dataset import ~/.local/share/forge/legacy # runs that predate live capture
|
|
894
|
+
./forge dataset export # labeled decision cases for benchmarks and fine-tuning
|
|
895
|
+
./forge dataset outcomes # which integrated lines survived, read from git history
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
Capture is fail-open and never read by anything that builds a prompt. See
|
|
899
|
+
[references/dataset.md](references/dataset.md) for the event schema and its invariants.
|
|
900
|
+
|
|
901
|
+
## forge-ui
|
|
902
|
+
|
|
903
|
+
forge-ui is a small read-only local web page that shows runs live, as a 3D isometric map
|
|
904
|
+
ported from [archivision](https://github.com/Giamme/archivision). Its **run cloud** puts every
|
|
905
|
+
run in one full-screen view, grouped by project, in five layouts (helix, sphere, galaxy,
|
|
906
|
+
timeline, constellation), filtered down to exactly the runs you want; drag any run anywhere
|
|
907
|
+
and the cloud remembers it.
|
|
908
|
+
|
|
909
|
+
<p align="center">
|
|
910
|
+
<img src="ui/docs/media/cloud.webp" width="880" alt="The run cloud: about seventy runs as blocks in five project spirals, orbited, then laid out as galaxies and as a timeline with one lane per project.">
|
|
911
|
+
</p>
|
|
912
|
+
|
|
913
|
+
<table>
|
|
914
|
+
<tr>
|
|
915
|
+
<td width="50%" valign="top"><img src="ui/docs/media/run.png" alt="A run's eight tasks on a 3D plate in their planned waves, with dependency cables and the finish nodes, beside the run's inspector."><br><b>A run, as planned.</b> Tasks in their waves with dependency cables, ending in integration, verification and the final review. Click a task to open its attempts, dwarves and reviews.</td>
|
|
916
|
+
<td width="50%" valign="top"><img src="ui/docs/media/execution.png" alt="The same run on wall-clock time: one block per attempt, failed attempts in red, a plane marking now, and the inspector's timeline and critical path."><br><b>And as it ran.</b> One block per attempt in the lane it ran in, failed ones in red, against <code>--max-parallel</code>; the inspector names the critical path.</td>
|
|
917
|
+
</tr>
|
|
918
|
+
<tr>
|
|
919
|
+
<td valign="top"><img src="ui/docs/media/diff.png" alt="The diff a reviewer read, with a P1 finding pinned under the line comparing token digests and a P2 finding under the revoke function."><br><b>Findings on their lines.</b> Any diff of a task, or exactly the diff one reviewer read, with each P0–P3 finding under the line it names.</td>
|
|
920
|
+
<td valign="top"><img src="ui/docs/media/models.png" alt="The model leaderboard: per implementer, tasks, first-attempt pass rate with a 95% interval bar, retries, escalations, timeouts, accepts, median model time and tokens."><br><b>Models, measured on your runs.</b> First-attempt pass rate with a confidence interval, retries, escalations, time and tokens; reviewers' verdicts and shadow agreement.</td>
|
|
921
|
+
</tr>
|
|
922
|
+
<tr>
|
|
923
|
+
<td valign="top"><img src="ui/docs/media/files.png" alt="The files heatmap: files grouped by directory against tasks grouped by wave, with planned, touched, drift and same-wave overlap marks."><br><b>Who touched which file.</b> Planned, changed, drifted into or conflicted on, wave by wave; a cell opens that file's diff.</td>
|
|
924
|
+
<td valign="top"><img src="ui/docs/media/map.png" alt="The map of every run: one block per run in a district per repo, green when done, blue while running, red with failures."><br><b>Every run, by repo.</b> One block per run with a lamp per task, filtered by state, repo, model, age or review verdict.</td>
|
|
925
|
+
</tr>
|
|
926
|
+
</table>
|
|
927
|
+
|
|
928
|
+
Also: replay, critical path, Fractal child-task trees, quick jump (Ctrl K, ⌘K on a Mac, or
|
|
929
|
+
`/`), a bell for runs that finish or fail, failed tasks and final-review verdicts (with opt-in
|
|
930
|
+
browser notifications), and a run exported as one HTML file that opens without forge-ui, with
|
|
931
|
+
prompts, diffs and local paths left out unless you choose them.
|
|
932
|
+
|
|
933
|
+
Offline (it loads three.js from a CDN for the 3D view) the same page works in 2D. It is opt-in.
|
|
934
|
+
The `/forge` skill asks "Open forge-ui to watch this run? [y/N]" at the start of every new
|
|
935
|
+
interactive run, or you pass it yourself:
|
|
936
|
+
|
|
937
|
+
```sh
|
|
938
|
+
bash scripts/forge-solo.sh "$RUN" --repo "$REPO" --dwarf sol --ui
|
|
939
|
+
bash scripts/forge-parallel.sh plan "$PLAN" --repo "$REPO" --decompose-level medium --ui
|
|
940
|
+
./forge ui open [DIR] # an existing run dir, or the run list
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
All runs share one detached server on `127.0.0.1:8765` (`FORGE_UI_PORT`) that exits after
|
|
944
|
+
four idle hours (`FORGE_UI_IDLE_S`). A UI that cannot start never fails the run. It has no
|
|
945
|
+
token: any local process can read the prompts and diffs of the runs it shows. See
|
|
946
|
+
[references/ui.md](references/ui.md) and [ui/README.md](ui/README.md).
|
|
947
|
+
|
|
948
|
+
To look around before your first run, point it at made-up runs (the ones in these pictures):
|
|
949
|
+
|
|
950
|
+
```sh
|
|
951
|
+
python3 ui/tools/demo_runs.py /tmp/forge-demo
|
|
952
|
+
(cd ui && python3 -m forge_ui --no-registry --root /tmp/forge-demo --open)
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
## Full command reference
|
|
956
|
+
|
|
957
|
+
### The `/forge` slash command
|
|
958
|
+
|
|
959
|
+
```
|
|
960
|
+
/forge "<goal>" --dwarf <alias>[:<effort>[:<harness>]]
|
|
961
|
+
[--qa <alias>[:<effort>[:<harness>]]]
|
|
962
|
+
[--planner <alias>[:<effort>[:<harness>]]]
|
|
963
|
+
[--yolo-dwarf] [--yolo-qa] [--repo <dir>] [--native-review]
|
|
964
|
+
[--no-memory] [--timeout <seconds>] [--ui | --no-ui] [--fractal | --no-fractal]
|
|
965
|
+
[--fractal-auto-decompose] [--fractal-planner <spec>]
|
|
966
|
+
[--fractal-depth <n>] [--fractal-children <n>] [--fractal-nodes <n>]
|
|
967
|
+
[--fractal-iterations <n>] [--fractal-concurrency <n>] [--fractal-deadline <s>]
|
|
968
|
+
[--decompose-level low|medium|high] [--max-parallel <n>]
|
|
969
|
+
[--dwarf-high <spec>] [--dwarf-medium <spec>] [--dwarf-low <spec>]
|
|
970
|
+
[--qa-high <spec>] [--qa-medium <spec>] [--qa-low <spec>]
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
| Flag | Default | Meaning |
|
|
974
|
+
|---|---|---|
|
|
975
|
+
| `--dwarf <spec>` | *asks* | the model that implements the change |
|
|
976
|
+
| `--qa <spec>` | `opus` at `xhigh` | the model that reviews the dwarf's diff |
|
|
977
|
+
| `--planner <spec>` | orchestrator plans | dispatch planning to a model; one dispatch per run, effort defaults to `xhigh` |
|
|
978
|
+
| `--no-memory` | memory on | write and inject nothing in `.forge/` |
|
|
979
|
+
| `--timeout <s>` | `2700` (45m) | kill any single dispatch that runs longer. `0` disables |
|
|
980
|
+
| `--yolo-dwarf` | off | drop the dwarf's sandbox and approval gates |
|
|
981
|
+
| `--yolo-qa` | off | drop the reviewer's sandbox |
|
|
982
|
+
| `--repo <dir>` | cwd | repository to work in |
|
|
983
|
+
| `--native-review` | off | use native Codex review against the recorded baseline instead of an inlined diff |
|
|
984
|
+
| `--decompose-level <l>` | off | split the goal into parallel tasks |
|
|
985
|
+
| `--max-parallel <n>` | `3` | concurrent task pipelines |
|
|
986
|
+
| `--dwarf-high/-medium/-low` | — | per-difficulty routing; comma-list pools models |
|
|
987
|
+
| `--qa-high/-medium/-low` | — | same, for reviewers |
|
|
988
|
+
| `--fractal` / `--no-fractal` | ask interactively; off unattended | mutually exclusive, per-run backend choice |
|
|
989
|
+
| `--fractal-auto-decompose` | off | enables Fractal with automatic recursive split-or-atomic decisions |
|
|
990
|
+
| `--fractal-planner <spec>` | top-level planner, then task root spec | frozen decision model; requires automatic mode |
|
|
991
|
+
| `--fractal-depth <n>` | `2` | nested levels below each Forge implementation node |
|
|
992
|
+
| `--fractal-children <n>` | `3` | unsettled direct children per node |
|
|
993
|
+
| `--fractal-nodes <n>` | `12` | lifetime nodes per Forge task, including implementation |
|
|
994
|
+
| `--fractal-iterations <n>` | `6` | per node per execution attempt |
|
|
995
|
+
| `--fractal-concurrency <n>` | `3` | shared model slots across nested trees and QA |
|
|
996
|
+
| `--fractal-deadline <s>` | `2700` | task attempt deadline, excluding explicit pauses |
|
|
997
|
+
| `--fractal-max-cost <amount>` | unsupported | returns an error; the bridge cannot enforce dollar caps |
|
|
998
|
+
|
|
999
|
+
Fractal's task deadline still applies when `--timeout 0` disables the ordinary
|
|
1000
|
+
per-dispatch timeout. Child routing uses the dwarf tier flags; QA stays at the Forge
|
|
1001
|
+
task boundary. Pools and Fractal limits are frozen for an existing run.
|
|
1002
|
+
|
|
1003
|
+
**On `--native-review`:** for a diff too large to inline, this switches codex to its
|
|
1004
|
+
purpose-built reviewer. Know the trade — codex refuses a custom prompt alongside its scope
|
|
1005
|
+
flags, so the reviewer never learns what the dwarf was *asked* to do. It can still judge
|
|
1006
|
+
whether the code is correct, but not whether it's the right change.
|
|
1007
|
+
|
|
1008
|
+
#### Examples
|
|
1009
|
+
|
|
1010
|
+
```bash
|
|
1011
|
+
# minimum
|
|
1012
|
+
/forge "make the cache key include the tenant id" --dwarf sol:high
|
|
1013
|
+
|
|
1014
|
+
# both roles named, reviewer thinking harder than the implementer
|
|
1015
|
+
/forge "rewrite the rate limiter as a token bucket" --dwarf luna:max --qa sol:ultra
|
|
1016
|
+
|
|
1017
|
+
# same model, different CLI
|
|
1018
|
+
/forge "extract the retry helper into its own module" --dwarf sol:high:openclaude
|
|
1019
|
+
|
|
1020
|
+
# Gemini as the dwarf, unsandboxed so it can actually run the tests it writes
|
|
1021
|
+
/forge "add table-driven tests for the parser" --dwarf gemini:high:antigravity --yolo-dwarf
|
|
1022
|
+
|
|
1023
|
+
# a cheap dwarf with an expensive reviewer
|
|
1024
|
+
/forge "update all the docstrings to match the new signatures" --dwarf haiku --qa opus:max
|
|
1025
|
+
|
|
1026
|
+
# work in another repo
|
|
1027
|
+
/forge "bump the pinned deps and fix the fallout" --dwarf sol:xhigh --repo ~/dev/other-project
|
|
1028
|
+
|
|
1029
|
+
# large refactor, reviewed by codex's native reviewer
|
|
1030
|
+
/forge "split the god object into three services" --dwarf sol:ultra --qa sol:ultra --native-review
|
|
1031
|
+
|
|
1032
|
+
# parallel, one dwarf for every task
|
|
1033
|
+
/forge "add CRUD endpoints for projects, users and teams" \
|
|
1034
|
+
--decompose-level medium --dwarf sol:high
|
|
1035
|
+
|
|
1036
|
+
# parallel, routed by difficulty, wider fan-out
|
|
1037
|
+
/forge "migrate the whole API layer from REST to gRPC" \
|
|
1038
|
+
--decompose-level high --max-parallel 5 \
|
|
1039
|
+
--dwarf-high sol:ultra --dwarf-medium luna:high --dwarf-low haiku:medium \
|
|
1040
|
+
--qa-high opus:max --qa sonnet:high
|
|
1041
|
+
|
|
1042
|
+
# pool two providers on the hard tier so neither quota carries it alone
|
|
1043
|
+
/forge "implement the new billing rules across the codebase" \
|
|
1044
|
+
--decompose-level medium \
|
|
1045
|
+
--dwarf-high sol:xhigh,terra:xhigh --dwarf luna:high
|
|
1046
|
+
|
|
1047
|
+
# a model that thinks for a long time; raise the per-dispatch backstop
|
|
1048
|
+
/forge "prove the scheduler cannot deadlock, and fix it if it can" \
|
|
1049
|
+
--dwarf sol:ultra --timeout 7200
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
After a decomposed run, a task that failed review can be re-run on its own — with the
|
|
1053
|
+
findings that failed it, on a stronger model:
|
|
1054
|
+
|
|
1055
|
+
```bash
|
|
1056
|
+
bash scripts/forge-parallel.sh retry /tmp/forge-billing pricing --dwarf sol:ultra
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
### `forge fractal` command group
|
|
1060
|
+
|
|
1061
|
+
Use `./forge fractal` from this checkout, or `<forge-checkout>/forge fractal` elsewhere.
|
|
1062
|
+
This group manages Fractal runs; it does not replace the `/forge` skill or runner scripts.
|
|
1063
|
+
|
|
1064
|
+
| Command | Purpose |
|
|
1065
|
+
| --- | --- |
|
|
1066
|
+
| `install [--dry-run] [--yes] [--with-prerequisites]` | preview or install the isolated pinned runtime |
|
|
1067
|
+
| `doctor [--spec SPEC] [--json]` | check runtime/hooks/tmux and optional repeated harness specs without provider calls |
|
|
1068
|
+
| `runs [--repo DIR] [--json] [--html PATH]` | discover managed runs, optionally filtered by repository |
|
|
1069
|
+
| `status\|tree\|activity RUN_ID` | inspect progress, hierarchy and history |
|
|
1070
|
+
| `logs\|costs\|messages\|config RUN_ID` | inspect diagnostics, observed accounting, radio traffic and effective settings |
|
|
1071
|
+
| `pause\|resume\|stop RUN_ID` | request a control and record its observed state |
|
|
1072
|
+
| `open [RUN_ID] [--port PORT]` | open the read-only live dashboard or runs index |
|
|
1073
|
+
| `report RUN_ID [--html PATH]` | capture a self-contained HTML report; default filename is `RUN_ID.html` |
|
|
1074
|
+
|
|
1075
|
+
Inspection commands accept `--task TASK_ID`, `--node NODE_ID`, `--json`, `--html PATH`,
|
|
1076
|
+
`--offset N` and `--limit N` (1–1000). They currently return a common JSON run projection;
|
|
1077
|
+
the command names do not imply separate output schemas. `logs --follow` follows changes.
|
|
1078
|
+
HTML exports capture full history and logs. Controls require an explicit run ID, and a
|
|
1079
|
+
node control also requires its task ID; they never guess the latest run. Use each command's
|
|
1080
|
+
`--help` for its accepted options. Installation is also available through
|
|
1081
|
+
`bash scripts/forge-install-fractal.sh`.
|
|
1082
|
+
|
|
1083
|
+
### `forge ui` command group
|
|
1084
|
+
|
|
1085
|
+
| Command | Purpose |
|
|
1086
|
+
| --- | --- |
|
|
1087
|
+
| `open [DIR] [--no-browser] [--port N]` | start or reuse the shared forge-ui server; print `forge-ui: <url>` for the plan or solo run dir `DIR` (the run list without it) and open a browser the first time |
|
|
1088
|
+
| `status [--json]` | exit 0 and describe the server when it runs, 1 when not |
|
|
1089
|
+
| `stop` | stop the server |
|
|
1090
|
+
|
|
1091
|
+
`FORGE_UI=off` makes `--ui` on a runner start nothing (it does not stop a running server or block `forge ui open`; use `forge ui stop`), `FORGE_UI_BROWSER=off` never opens a browser,
|
|
1092
|
+
`FORGE_UI_PORT` (8765) and `FORGE_UI_IDLE_S` (14400, `0` = never) tune the server. State and
|
|
1093
|
+
`server.log` are in `${XDG_STATE_HOME:-~/.local/state}/forge/ui/`.
|
|
1094
|
+
|
|
1095
|
+
### `scripts/forge-dispatch.sh`
|
|
1096
|
+
|
|
1097
|
+
Resolves a spec into a real CLI invocation and runs it. Needs only bash and coreutils, which
|
|
1098
|
+
is the point: it behaves identically no matter which harness is orchestrating.
|
|
1099
|
+
|
|
1100
|
+
```
|
|
1101
|
+
forge-dispatch.sh doctor
|
|
1102
|
+
forge-dispatch.sh dwarf <spec> --prompt-file <f> [--repo <dir>] [--run-dir <d>] [--yolo] [--dry-run]
|
|
1103
|
+
forge-dispatch.sh qa <spec> --prompt-file <f> [--repo <dir>] [--run-dir <d>] [--yolo] [--dry-run]
|
|
1104
|
+
[--native-review [--review-base <ref>]]
|
|
1105
|
+
forge-dispatch.sh planner <spec> --prompt-file <f> [--repo <dir>] [--run-dir <d>] [--dry-run]
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
| Option | Meaning |
|
|
1109
|
+
|---|---|
|
|
1110
|
+
| `--prompt-file <f>` | file holding the prompt (required for `dwarf`/`qa`) |
|
|
1111
|
+
| `--repo <dir>` | repository the role operates in |
|
|
1112
|
+
| `--run-dir <d>` | where prompts, logs and results are written |
|
|
1113
|
+
| `--yolo` | bypass sandbox and approvals for this role |
|
|
1114
|
+
| `--dry-run` | resolve and print the command; run nothing |
|
|
1115
|
+
| `--native-review` | qa only; use codex's built-in reviewer |
|
|
1116
|
+
| `--review-base <ref>` | qa only; base ref for the native review |
|
|
1117
|
+
| `--agy-timeout <s>` | antigravity `--print-timeout` (long runs need this raised) |
|
|
1118
|
+
| `--timeout <s>` | kill this dispatch after `<s>` seconds; `0` disables. See the precedence below |
|
|
1119
|
+
|
|
1120
|
+
**Exit codes:** `0` ok · `2` spec or usage error · `3` harness missing or unusable ·
|
|
1121
|
+
`4` the backend ran and failed · `7` the backend hit its timeout and was killed ·
|
|
1122
|
+
`8` an **infrastructure failure** (quota or usage limit, auth, rate limit, network, or an
|
|
1123
|
+
empty answer) — nothing about the work was judged, so callers pause instead of counting a
|
|
1124
|
+
failure. On `8`, `<run-dir>/<role>.infra` holds `class=`, `retry_after=`, `detail=`, `rc=`,
|
|
1125
|
+
`role=`.
|
|
1126
|
+
|
|
1127
|
+
**Timeout precedence.** An explicit `--timeout`, then `FORGE_TIMEOUT` (an explicit `0` wins
|
|
1128
|
+
and disables the watchdog), then the optional fifth `timeout` column of the model's
|
|
1129
|
+
[registry](#model-registry) row, then the built-in `2700` (45 min). A value that was *not*
|
|
1130
|
+
explicit is scaled by the resolved (post-clamp) effort: up to `high` ×1, `xhigh` ×4/3,
|
|
1131
|
+
`max`/`ultra`/`ultracode` ×2. QA and planner default to `xhigh`, so their built-in limit is
|
|
1132
|
+
3600 s; dwarves default to `medium` and keep 2700 s. `<role>.resolved` records `timeout=` and
|
|
1133
|
+
`timeout_source=` (`explicit`, `env`, `registry` or `default`, plus `+effort-scaled`).
|
|
1134
|
+
`forge-parallel.sh run`/`retry --timeout S` sets it for a decomposed run (kept in the plan).
|
|
1135
|
+
`FORGE_REGISTRY` points the dispatcher at an alternative registry file.
|
|
1136
|
+
|
|
1137
|
+
Only antigravity bounds its own runtime, and stock macOS has no `timeout` binary to wrap the
|
|
1138
|
+
others in — so a hung backend used to block forever, and under a decomposed run one hung task
|
|
1139
|
+
silently stalled the others. The default is deliberately generous: this is a backstop against
|
|
1140
|
+
a wedged process, not a budget. A role killed by it has usually left a partial edit behind, so
|
|
1141
|
+
`7` is worth reporting as such rather than as a clean failure.
|
|
1142
|
+
|
|
1143
|
+
On `3` or `4`, forge shows you the actual error rather than substituting a different model —
|
|
1144
|
+
quietly swapping backends hides the fact that the one you picked is broken.
|
|
1145
|
+
|
|
1146
|
+
**Process hygiene.** Unless `FORGE_ORPHAN_GUARD=off` (or the run is a Fractal one), the harness
|
|
1147
|
+
is started in its own session, and after it exits any process still alive in that session is
|
|
1148
|
+
listed in `<role>.orphans` and stopped, so a backgrounded measurement cannot outlive the
|
|
1149
|
+
dispatch. The timeout watchdog and the exit trap signal the whole session too.
|
|
1150
|
+
|
|
1151
|
+
```bash
|
|
1152
|
+
# what would this actually run?
|
|
1153
|
+
bash scripts/forge-dispatch.sh dwarf sol:ultra --prompt-file /tmp/p --dry-run
|
|
1154
|
+
|
|
1155
|
+
# check a clamp before committing to a long run
|
|
1156
|
+
bash scripts/forge-dispatch.sh qa gemini-pro:medium --prompt-file /tmp/p --dry-run
|
|
1157
|
+
# effort=low
|
|
1158
|
+
# clamped=medium -> low (gemini-pro offers low and high, but no medium)
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
### `scripts/forge-solo.sh`
|
|
1162
|
+
|
|
1163
|
+
The whole single-task run: memory injection, prompt assembly, the dwarf dispatch, the diff
|
|
1164
|
+
capture, the QA dispatch, and the ledger writes.
|
|
1165
|
+
|
|
1166
|
+
```
|
|
1167
|
+
forge-solo.sh <run-dir> --repo <dir> --dwarf <spec> [--qa <spec>]
|
|
1168
|
+
[--approach <file>] [--yolo-dwarf] [--yolo-qa]
|
|
1169
|
+
[--native-review] [--no-memory] [--timeout <s>] [--dry-run] [--ui | --no-ui]
|
|
1170
|
+
[--fail-first]
|
|
1171
|
+
[--fractal | --no-fractal] [--fractal-<limit> <n>]
|
|
1172
|
+
[--dwarf-low <pool>] [--dwarf-medium <pool>] [--dwarf-high <pool>] [--retry]
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
You write two files into the run directory; forge does the rest:
|
|
1176
|
+
|
|
1177
|
+
| You write | Is |
|
|
1178
|
+
|---|---|
|
|
1179
|
+
| `prompt.md` | the implementation instruction (required) |
|
|
1180
|
+
| `goal.txt` | one line, what was asked — given to the reviewer as intent (optional) |
|
|
1181
|
+
|
|
1182
|
+
| It writes | Is |
|
|
1183
|
+
|---|---|
|
|
1184
|
+
| `dwarf.last` / `qa.last` | each role's final message |
|
|
1185
|
+
| `changes.diff` | exactly what the reviewer read |
|
|
1186
|
+
| `verdict` | `PASS`, `FAIL`, `UNKNOWN` or `NOCHANGES` |
|
|
1187
|
+
| `<role>.{input,log,resolved,cmd}` | the prompt, the transcript, the resolution, the command |
|
|
1188
|
+
| `fractal-selection.json` | persisted backend choice; enabled runs include their managed run ID |
|
|
1189
|
+
|
|
1190
|
+
With Fractal, per-step dispatch artifacts live in the managed run while the solo directory
|
|
1191
|
+
retains the aggregate diff and Forge QA result. `--retry` starts a new Fractal attempt;
|
|
1192
|
+
`./forge fractal resume RUN_ID` continues preserved execution and pipeline checkpoints.
|
|
1193
|
+
|
|
1194
|
+
```bash
|
|
1195
|
+
RUN="$(mktemp -d /tmp/forge-XXXXXX)"
|
|
1196
|
+
echo "add retry with backoff to the HTTP client" > "$RUN/goal.txt"
|
|
1197
|
+
echo "Add exponential backoff to src/http.py, capped at 5 attempts." > "$RUN/prompt.md"
|
|
1198
|
+
|
|
1199
|
+
bash scripts/forge-solo.sh "$RUN" --repo ~/dev/api --dwarf sol:high --qa opus
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
**Exit codes:** `0` reviewed · `2` usage · `3` not a git repository · `4` a dispatch failed ·
|
|
1203
|
+
`5` **no changes or review invalidated** · `7` a dispatch timed out · `8` a dispatch hit an
|
|
1204
|
+
**infrastructure failure** (quota, auth, rate limit, network, empty output): nothing was
|
|
1205
|
+
judged, fix the cause and run again. A dwarf that exits 0 with an empty answer *and* changes
|
|
1206
|
+
nothing is reported as `8`, not `5` — it never really ran.
|
|
1207
|
+
|
|
1208
|
+
The dwarf is given the same implementer rules as in a decomposed run (no commits, no
|
|
1209
|
+
background processes, finish measurements before the last message). If it commits anyway, the
|
|
1210
|
+
run still reviews its changes, says so, and prints the `git reset --soft <old HEAD>` that would
|
|
1211
|
+
turn them back into uncommitted changes; forge never resets anything itself. Orphan processes
|
|
1212
|
+
and promised-later-work notes appear under "implementer guard notes".
|
|
1213
|
+
|
|
1214
|
+
`5` is the one worth recognising on sight. Headless dwarves genuinely stop to ask a
|
|
1215
|
+
clarifying question that nothing can answer; the run ends having spent the quota and changed
|
|
1216
|
+
nothing, with the question sitting in `dwarf.last`.
|
|
1217
|
+
|
|
1218
|
+
Two things it gets right that are easy to get wrong by hand, and that fail *silently* rather
|
|
1219
|
+
than loudly:
|
|
1220
|
+
|
|
1221
|
+
- **`.forge/` is excluded from the capture.** Forge rewrites its own memory in the working
|
|
1222
|
+
tree at the end of every run, so left in, it reaches the reviewer as a file the dwarf
|
|
1223
|
+
appears to have touched — which a good reviewer correctly flags as scope creep.
|
|
1224
|
+
- **New files are diffed against `/dev/null`.** A `git status` line says only `?? calc.py`.
|
|
1225
|
+
A dwarf whose entire task was to add a file would have had its actual code reviewed by
|
|
1226
|
+
nobody, while the run still reported a clean QA pass.
|
|
1227
|
+
|
|
1228
|
+
The run directory must live outside the repository — anything forge writes inside the working
|
|
1229
|
+
tree shows up in the dwarf's own diff. `forge-solo.sh` refuses rather than letting that happen
|
|
1230
|
+
quietly.
|
|
1231
|
+
|
|
1232
|
+
### `scripts/forge-parallel.sh`
|
|
1233
|
+
|
|
1234
|
+
Decomposed runs: many dwarves in isolated worktrees, per-task QA, only passing work merged.
|
|
1235
|
+
Bash 3.2 entrypoints use a Python standard-library coordinator to schedule eligible tasks.
|
|
1236
|
+
|
|
1237
|
+
```
|
|
1238
|
+
forge-parallel.sh plan <plan-dir> --repo <dir> [routing flags] [--planner <spec>] [--no-memory]
|
|
1239
|
+
[--decompose-level L] [--shadow-qa <spec>[,..]|none] [--shadow-rate R]
|
|
1240
|
+
[--explore E] [--ui | --no-ui] [--fail-first | --no-fail-first]
|
|
1241
|
+
forge-parallel.sh run <plan-dir> [--max-parallel N] [--yolo-dwarf] [--yolo-qa] [--dry-run]
|
|
1242
|
+
[--verify <cmd>] [--setup <cmd>] [--timeout S]
|
|
1243
|
+
[--retry-failed N] [--infra-retries M]
|
|
1244
|
+
[--qa-threshold P0|P1|P2|P3|none] [--verify-retries N]
|
|
1245
|
+
[--fractal | --no-fractal] [--fractal-<limit> <n>]
|
|
1246
|
+
forge-parallel.sh retry <plan-dir> <task-id> [--dwarf <spec>] [--timeout S]
|
|
1247
|
+
[--qa-threshold Pn|none] [--verify-retries N]
|
|
1248
|
+
forge-parallel.sh accept <plan-dir> <task-id> --reason "<why>" --approved
|
|
1249
|
+
forge-parallel.sh review <plan-dir> [--qa <spec>] [--yolo-qa] [--qa-threshold Pn]
|
|
1250
|
+
forge-parallel.sh integrate <plan-dir> --approved [--final-review <qa-spec>] [--verify-retries N]
|
|
1251
|
+
forge-parallel.sh split <plan-dir> <new-plan-dir> --tasks <id,id> [--dry-run]
|
|
1252
|
+
forge-parallel.sh combine <new-plan-dir> <plan-dir> <plan-dir>... [--verify <cmd>] [--dry-run]
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
| Subcommand | Does |
|
|
1256
|
+
|---|---|
|
|
1257
|
+
| `plan` | validate `tasks.tsv`, resolve difficulty → concrete dwarf/qa specs, compute waves, render the approval table |
|
|
1258
|
+
| `run` | schedule ready tasks; per task: worktree → dwarf → diff → QA → status; merge passing tasks onto the integration branch |
|
|
1259
|
+
| `retry` | re-run **one** failed task in the worktree it already has, with its reviewer's findings in the prompt |
|
|
1260
|
+
| `integrate` | **never automatic** — merge the integration branch into your branch |
|
|
1261
|
+
| `accept` | **only on your explicit instruction** — merge a `FAIL`/`UNKNOWN` task's reviewed work anyway, recording why |
|
|
1262
|
+
| `review` | **opt-in, spends QA quota** — one reviewer reads the whole combined result; read-only |
|
|
1263
|
+
| `split` / `combine` | move tasks into a plan of their own / join fully merged plans into one |
|
|
1264
|
+
|
|
1265
|
+
| Flag | Subcommands | Remembered in the plan | Means |
|
|
1266
|
+
|---|---|---|---|
|
|
1267
|
+
| `--timeout S` | run, retry | `timeout` | per-dispatch timeout in seconds (`0` disables); wins over `FORGE_TIMEOUT` |
|
|
1268
|
+
| `--retry-failed N` | run | no — this run only | re-queue a task the reviewer rejected up to `N` more times inside the run |
|
|
1269
|
+
| `--infra-retries M` | run | no — this run only | wait out up to `M` infrastructure stops instead of pausing |
|
|
1270
|
+
| `--qa-threshold Pn` | run, retry | `qa_threshold` | least severe finding that still blocks; milder ones become known issues; `none` clears |
|
|
1271
|
+
| `--verify-retries N` | run, retry, integrate | `verify_retries` | reruns of a failed verify command (default `1`, `0` = strict single run) |
|
|
1272
|
+
|
|
1273
|
+
**Exit codes:** `0` ok · `2` usage or validation · `3` precondition (not a git repo, …) ·
|
|
1274
|
+
`5` some task failed QA, or the combined verification failed · `6` integration conflict ·
|
|
1275
|
+
`8` **paused on an infrastructure failure** (quota, auth, rate limit, network): no attempt was
|
|
1276
|
+
spent and nothing was judged — fix the cause and run the same command again.
|
|
1277
|
+
|
|
1278
|
+
```bash
|
|
1279
|
+
PLAN=/tmp/forge-auth
|
|
1280
|
+
|
|
1281
|
+
# 1. plan and inspect the routing
|
|
1282
|
+
bash scripts/forge-parallel.sh plan "$PLAN" --repo ~/dev/api \
|
|
1283
|
+
--dwarf-high sol:xhigh --dwarf-low gemini:low:antigravity --dwarf luna:high
|
|
1284
|
+
|
|
1285
|
+
# 2. see exactly what will be dispatched, without dispatching
|
|
1286
|
+
bash scripts/forge-parallel.sh run "$PLAN" --dry-run
|
|
1287
|
+
|
|
1288
|
+
# 3. run it, four dwarves at a time, implementers unsandboxed
|
|
1289
|
+
bash scripts/forge-parallel.sh run "$PLAN" --max-parallel 4 --yolo-dwarf
|
|
1290
|
+
|
|
1291
|
+
# 4. after reading the results, deliver the passing work
|
|
1292
|
+
bash scripts/forge-parallel.sh integrate "$PLAN" --approved
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
`integrate` refuses without `--approved`, and refuses on a dirty working tree.
|
|
1296
|
+
For Fractal execution, pass tier pools to `plan` and backend/limit flags to `run`.
|
|
1297
|
+
`--max-parallel` limits task pipelines; the shared `--fractal-concurrency` limit includes
|
|
1298
|
+
all their child invocations and QA. See [decomposed Fractal execution](references/decompose.md#fractal-within-decomposed-tasks).
|
|
1299
|
+
|
|
1300
|
+
#### Retrying a failed task
|
|
1301
|
+
|
|
1302
|
+
```bash
|
|
1303
|
+
# re-run one task with the findings that failed it, on a stronger model
|
|
1304
|
+
bash scripts/forge-parallel.sh retry "$PLAN" middleware --dwarf sol:xhigh
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
The findings are the point. `retry` prepends the previous reviewer's report to the dwarf's
|
|
1308
|
+
prompt — "your last attempt was reviewed and these were the findings; fix them" — and reuses
|
|
1309
|
+
the worktree, so the dwarf continues from its own code rather than starting over. If the
|
|
1310
|
+
verdict comes back `PASS` the task merges like any other.
|
|
1311
|
+
|
|
1312
|
+
`--dwarf` is written back into `tasks.tsv`, so the table shows the model that will actually
|
|
1313
|
+
be spent and the escalation survives a re-plan.
|
|
1314
|
+
|
|
1315
|
+
Two things it deliberately does:
|
|
1316
|
+
|
|
1317
|
+
- **Reviews the task's cumulative diff**, against the commit the task was originally branched
|
|
1318
|
+
from — not just the fix. Reviewing the fix alone would let the first attempt's code through
|
|
1319
|
+
unread.
|
|
1320
|
+
- **Refuses to re-review identical code.** If the retry produces a byte-identical diff, the
|
|
1321
|
+
task fails again without spending a QA dispatch.
|
|
1322
|
+
|
|
1323
|
+
`retry` is something you run. Forge never loops a dwarf against its own reviewer on its own
|
|
1324
|
+
initiative — unless you ask a *run* to with `--retry-failed N`, see
|
|
1325
|
+
[retrying inside a run](#retrying-inside-a-run---retry-failed-n). A task whose reviewer
|
|
1326
|
+
never gave a verdict (`UNKNOWN`) or whose run stopped on an infrastructure failure (`INFRA`)
|
|
1327
|
+
resumes at the review stage instead: QA runs again on the unchanged work, no dwarf runs, and
|
|
1328
|
+
no attempt is spent.
|
|
1329
|
+
|
|
1330
|
+
#### Resuming an interrupted run
|
|
1331
|
+
|
|
1332
|
+
Run `run` again to skip MERGED tasks, merge saved PASS results after fingerprint checks,
|
|
1333
|
+
and dispatch eligible pending tasks. Tasks paused on an infrastructure failure (`INFRA`) are
|
|
1334
|
+
dispatched again without any flag, since nothing was wrong with the work. Failed and
|
|
1335
|
+
interrupted tasks need an explicit `retry` (or `run --retry-failed N` for rejected ones).
|
|
1336
|
+
Their worktrees and pinned baselines remain available. Concurrent run/retry/integrate
|
|
1337
|
+
operations for the same plan are excluded by an advisory lock.
|
|
1338
|
+
|
|
1339
|
+
For a Fractal-backed run, use `./forge fractal resume RUN_ID` to recover recorded execution
|
|
1340
|
+
and pipeline checkpoints. A task/subtree selector resumes only that execution scope.
|
|
1341
|
+
Explicit `retry` retains the backend and frozen pools; a replacement dwarf must already
|
|
1342
|
+
belong to that pool. See [recovery](references/fractal.md#limits-and-lifecycle).
|
|
1343
|
+
|
|
1344
|
+
#### Accepting a task despite its reviewer
|
|
1345
|
+
|
|
1346
|
+
```bash
|
|
1347
|
+
bash scripts/forge-parallel.sh accept "$PLAN" tooling \
|
|
1348
|
+
--reason "only an unreachable option left; tracked in the backlog" --approved
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
When a reviewer keeps finding new corners of something that should ship (the report's
|
|
1352
|
+
investigation harness got 12 findings over five rounds, and only a human decision ended it),
|
|
1353
|
+
`accept` is that decision made explicit instead of done with git plumbing. It needs **both**
|
|
1354
|
+
`--reason` and `--approved`, because it overrides a reviewer; an orchestrating model never runs
|
|
1355
|
+
it on its own. It accepts only a `FAIL` or `UNKNOWN` task whose reviewed work is **intact** —
|
|
1356
|
+
the branch is still at the reviewed commit and the worktree still has the saved fingerprint —
|
|
1357
|
+
otherwise it exits `3` and says to `retry` for a fresh review (accepting would sign off on code
|
|
1358
|
+
nobody reviewed). It writes `tasks/<id>/known_issues.md` (your reason, who and when, and the
|
|
1359
|
+
reviewer's reply copied verbatim) and `accepted.by`, merges through the same checks as any other
|
|
1360
|
+
task, and puts everything back if the merge conflicts (exit `6`). Dependents are not touched;
|
|
1361
|
+
`run` continues them.
|
|
1362
|
+
|
|
1363
|
+
#### Whole-run review (opt-in)
|
|
1364
|
+
|
|
1365
|
+
Per-task QA only sees one slice. In the report, a holistic review of the combined result still
|
|
1366
|
+
found two confirmed P2 defects in code every per-task review had passed. It is opt-in because it
|
|
1367
|
+
spends QA quota and `integrate` made no model calls before:
|
|
1368
|
+
|
|
1369
|
+
```bash
|
|
1370
|
+
bash scripts/forge-parallel.sh review "$PLAN" --qa opus # read-only look at the combined result
|
|
1371
|
+
bash scripts/forge-parallel.sh integrate "$PLAN" --approved --final-review opus # and gate the merge on it
|
|
1372
|
+
```
|
|
1373
|
+
|
|
1374
|
+
The reviewer gets the overall goal, every task's complete requirements (bounded to about 80 KB,
|
|
1375
|
+
truncated visibly), what forge observed that no per-task reviewer saw (accepted known issues,
|
|
1376
|
+
scope drift, guard notes, the verification result including a `FLAKY` pass and coverage
|
|
1377
|
+
warnings), the combined diff (inline up to 400 KB), and the same labelled-findings contract —
|
|
1378
|
+
with an emphasis on cross-task interactions, interface mismatches and ordering races. `review`
|
|
1379
|
+
reads `integration.base..integration tip` from a disposable checkout, writes
|
|
1380
|
+
`<plan>/final-review-<epoch>-<pid>/` (`qa.input`, `qa.last`, `verdict`, `qa.gate`, …), keeps
|
|
1381
|
+
`<plan>/final-review` and `final-review.verdict` pointing at the newest, and never touches your
|
|
1382
|
+
branch or the integration branch. With `integrate --final-review`, it runs after verification
|
|
1383
|
+
and before your branch can move: `PASS` (or a `FAIL` accepted by `--qa-threshold`) continues,
|
|
1384
|
+
anything else stops with exit `5`; an infrastructure stop exits `8`. The QA spec is `--qa`, else
|
|
1385
|
+
the one remembered in `final_qa`, else the single spec every task shares (otherwise it asks).
|
|
1386
|
+
Tasks that are not merged are not in the result, and it says so. `integrate` also names unmerged
|
|
1387
|
+
tasks even without a review.
|
|
1388
|
+
|
|
1389
|
+
#### Splitting and combining plans
|
|
1390
|
+
|
|
1391
|
+
`run --retry-failed N` removes most of the reason to split a plan (a failed task no longer holds
|
|
1392
|
+
the rest of a run). When you still want independent work in separate plans — separate locks,
|
|
1393
|
+
separate integration branches, one verified at a time — these two make it a supported operation
|
|
1394
|
+
instead of hand-built git plumbing:
|
|
1395
|
+
|
|
1396
|
+
- `split <plan> <new-plan> --tasks a,b` moves tasks **with their saved state** (attempts,
|
|
1397
|
+
findings, reviewed commit, status) into a new plan: their branches and worktrees are renamed to
|
|
1398
|
+
the new run (`forge/<new>/<id>`), the new integration branch starts at the source's accepted
|
|
1399
|
+
tip, dependencies on already-merged tasks are dropped, and the source forgets the moved rows
|
|
1400
|
+
(`split-<new>.txt` records them). It refuses — changing nothing — for a Fractal-backed plan, a
|
|
1401
|
+
MERGED or RUNNING task, a selection that leaves a dependent behind, or a dependency that is
|
|
1402
|
+
neither moving nor merged. Copy first, delete last; an interrupted split rolls back.
|
|
1403
|
+
- `combine <new-plan> <plan-a> <plan-b>…` joins plans whose tasks are **all `MERGED`** into one
|
|
1404
|
+
plan: a new integration branch merges each source's, the per-task records are copied, and
|
|
1405
|
+
`run <new-plan>` then only verifies (no model call) so `integrate` works unchanged. It refuses
|
|
1406
|
+
different repositories, locked or dirty sources, duplicate task ids, and any task left behind;
|
|
1407
|
+
a merge conflict exits `6` after removing everything it created. `--verify` sets the combined
|
|
1408
|
+
plan's check; `--dry-run` previews and predicts conflicts. Task branches keep their original
|
|
1409
|
+
names.
|
|
1410
|
+
|
|
1411
|
+
#### Scope drift
|
|
1412
|
+
|
|
1413
|
+
After each dwarf, the runner compares what the task **declared** in `files` against what it
|
|
1414
|
+
actually touched (a declared directory covers the paths beneath it). Undeclared paths appear
|
|
1415
|
+
in the summary and in the QA prompt:
|
|
1416
|
+
|
|
1417
|
+
```
|
|
1418
|
+
scope drift (touched files the task did not declare):
|
|
1419
|
+
auth src/db.py
|
|
1420
|
+
```
|
|
1421
|
+
|
|
1422
|
+
Never a failure — a dwarf that genuinely needed one more file did the right thing. What it
|
|
1423
|
+
buys is that a merge `CONFLICT` two waves later arrives with its cause already named.
|
|
1424
|
+
|
|
1425
|
+
#### `tasks.tsv`
|
|
1426
|
+
|
|
1427
|
+
Tab-separated, one row per task, written during decomposition:
|
|
1428
|
+
|
|
1429
|
+
```
|
|
1430
|
+
# id deps difficulty files dwarf qa title
|
|
1431
|
+
middleware - high src/auth/middleware.py - - Auth middleware
|
|
1432
|
+
tokens - medium src/auth/token.py - - Token model
|
|
1433
|
+
route middleware,tokens medium src/routes/login.py - - Login route
|
|
1434
|
+
docs - low README.md - - Document the auth flow
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1437
|
+
| Column | Meaning |
|
|
1438
|
+
|---|---|
|
|
1439
|
+
| `id` | short unique slug; also the worktree and branch name |
|
|
1440
|
+
| `deps` | comma-list of task ids that must land first, or `-` |
|
|
1441
|
+
| `difficulty` | `low` \| `medium` \| `high` |
|
|
1442
|
+
| `files` | expected touch set, comma-separated — **drives disjointness** |
|
|
1443
|
+
| `dwarf` / `qa` | `-` to resolve from the routing rules; an explicit spec always wins |
|
|
1444
|
+
| `title` | one line, shown in the approval table and the capsule |
|
|
1445
|
+
|
|
1446
|
+
Each task also needs `tasks/<id>/prompt.md`, written the same way a single-task forge goal is.
|
|
1447
|
+
|
|
1448
|
+
`files` is a **promise, not a prediction**: it's what the wave planner trusts when deciding
|
|
1449
|
+
what may run concurrently, and what the capsule tells other dwarves not to touch. An
|
|
1450
|
+
understated `files` is how two dwarves end up in the same file.
|
|
1451
|
+
|
|
1452
|
+
`plan` writes resolved specs back into the `dwarf`/`qa` columns, so `run` never consults the
|
|
1453
|
+
routing rules — and an explicit value survives a re-plan, which is what makes a gate override
|
|
1454
|
+
durable. (`UNASSIGNED` is treated as a leftover marker and re-resolved.)
|
|
1455
|
+
|
|
1456
|
+
### `scripts/forge-memory.sh`
|
|
1457
|
+
|
|
1458
|
+
```
|
|
1459
|
+
forge-memory.sh inject <repo> <planner|dwarf|qa> # the block to prepend to a prompt
|
|
1460
|
+
forge-memory.sh note <planner|dwarf|qa> # the instruction to append
|
|
1461
|
+
forge-memory.sh record <repo> --last <file> --role <r> [--run-id X] [--task T]
|
|
1462
|
+
[--model M] [--verdict V] [--duration S]
|
|
1463
|
+
forge-memory.sh show <repo>
|
|
1464
|
+
forge-memory.sh prune <repo> # re-apply staleness and the cap now
|
|
1465
|
+
forge-memory.sh spend <repo> [--run <run-id>] # what forge has cost this repo
|
|
1466
|
+
```
|
|
1467
|
+
|
|
1468
|
+
`record` appends to the ledger and then rebuilds `memory.md` from the whole ledger — the
|
|
1469
|
+
rebuild is idempotent, so a crashed run can leave the ledger a row short but can never leave
|
|
1470
|
+
memory half-written. Editing `memory.md` by hand therefore does not stick; remove the ledger
|
|
1471
|
+
rows instead.
|
|
1472
|
+
|
|
1473
|
+
```bash
|
|
1474
|
+
# what would a dwarf actually be told about this repo?
|
|
1475
|
+
bash scripts/forge-memory.sh inject ~/dev/api dwarf
|
|
1476
|
+
|
|
1477
|
+
# start over
|
|
1478
|
+
rm -rf ~/dev/api/.forge
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1481
|
+
#### Spend
|
|
1482
|
+
|
|
1483
|
+
The ledger already holds a row per dispatch and is never injected into a prompt, so
|
|
1484
|
+
accounting from it costs nothing:
|
|
1485
|
+
|
|
1486
|
+
```bash
|
|
1487
|
+
bash scripts/forge-memory.sh spend ~/dev/api
|
|
1488
|
+
```
|
|
1489
|
+
```
|
|
1490
|
+
14 dispatch(es) across 3 run(s), 1h12m of model time
|
|
1491
|
+
Durations sum concurrent work, so they exceed the wall-clock of a parallel run.
|
|
1492
|
+
|
|
1493
|
+
by model disp total mean
|
|
1494
|
+
sol:xhigh 4 38m20s 9m35s
|
|
1495
|
+
opus 7 22m10s 3m10s
|
|
1496
|
+
gemini:low:antigravity 3 11m30s 3m50s
|
|
1497
|
+
|
|
1498
|
+
by role disp total
|
|
1499
|
+
dwarf 7 49m50s
|
|
1500
|
+
qa 7 22m10s
|
|
1501
|
+
```
|
|
1502
|
+
|
|
1503
|
+
**Time and dispatch counts, never tokens or money.** The six CLIs expose usage differently
|
|
1504
|
+
or not at all, and a number forge cannot actually measure would be worse than no number.
|
|
1505
|
+
|
|
1506
|
+
`duration_s` is the ledger's tenth column, appended at the end on purpose: a ledger written
|
|
1507
|
+
by an older forge — possibly already committed and shared — has nine fields, and awk yields
|
|
1508
|
+
an empty string for the missing one instead of shifting every column. Those rows are counted
|
|
1509
|
+
but not timed, and the output says so.
|
|
1510
|
+
|
|
1511
|
+
### `scripts/forge-install.sh`
|
|
1512
|
+
|
|
1513
|
+
```
|
|
1514
|
+
forge-install.sh [--dry-run]
|
|
1515
|
+
```
|
|
1516
|
+
|
|
1517
|
+
Links this directory into every installed harness. Skips harnesses that aren't present,
|
|
1518
|
+
skips a destination that exists and isn't a symlink (it will never clobber a directory you
|
|
1519
|
+
may have edited in place), and reports opencode as auto-detected.
|
|
1520
|
+
|
|
1521
|
+
---
|
|
1522
|
+
|
|
1523
|
+
## Model registry
|
|
1524
|
+
|
|
1525
|
+
`registry.tsv` is the single source of truth for alias resolution. Four tab-separated
|
|
1526
|
+
columns, plus an optional fifth:
|
|
1527
|
+
|
|
1528
|
+
```
|
|
1529
|
+
# alias harness model effort [timeout]
|
|
1530
|
+
sol codex gpt-6.1-sol <=ultra
|
|
1531
|
+
sol openclaude gpt-6.1-sol <=max
|
|
1532
|
+
gemini-pro antigravity gemini-3.1-pro low,high
|
|
1533
|
+
opus antigravity claude-opus-4-6-thinking none
|
|
1534
|
+
grok grok grok-4.7 <=xhigh
|
|
1535
|
+
grok opencode github-copilot/grok-4.6 -
|
|
1536
|
+
```
|
|
1537
|
+
|
|
1538
|
+
| `effort` form | Means |
|
|
1539
|
+
|---|---|
|
|
1540
|
+
| `<=X` | any effort on that harness's ladder up to and including `X` |
|
|
1541
|
+
| `a,b,c` | exactly these efforts — gaps are real, and this is how they're expressed |
|
|
1542
|
+
| `none` | takes no effort flag at all; passing one errors |
|
|
1543
|
+
| `-` | no validated ladder; forwarded verbatim |
|
|
1544
|
+
|
|
1545
|
+
The optional fifth column, `timeout`, is a per-model default dispatch limit in seconds (`-`
|
|
1546
|
+
or absent = the built-in default). It applies only when neither `--timeout` nor
|
|
1547
|
+
`FORGE_TIMEOUT` was given and is scaled by the resolved effort (see
|
|
1548
|
+
[timeout precedence](#scriptsforge-dispatchsh)). Rows without it behave exactly as before.
|
|
1549
|
+
|
|
1550
|
+
**Row order is meaningful:** the *first* row for an alias is that alias's default harness.
|
|
1551
|
+
`--dwarf sol` means sol on codex; `--dwarf sol:high:openclaude` moves the same model
|
|
1552
|
+
elsewhere.
|
|
1553
|
+
|
|
1554
|
+
Adding a model is one line in this file. Nothing else needs to change.
|
|
1555
|
+
|
|
1556
|
+
---
|
|
1557
|
+
|
|
1558
|
+
## Run artifacts
|
|
1559
|
+
|
|
1560
|
+
Fractal adds a managed workspace alongside the runner artifacts described here:
|
|
1561
|
+
|
|
1562
|
+
```text
|
|
1563
|
+
${XDG_STATE_HOME:-$HOME/.local/state}/forge/fractal/
|
|
1564
|
+
runtime/ isolated pinned Fractal and wiki environment
|
|
1565
|
+
provenance.json installed versions and source checksum
|
|
1566
|
+
installs/ installation attempts and diagnostics
|
|
1567
|
+
runs/<run-id>/run.json effective routing, limits and runner binding
|
|
1568
|
+
runs/<run-id>/controls/ requested controls and observed results
|
|
1569
|
+
runs/<run-id>/tasks/<task-id>/
|
|
1570
|
+
request.json / state.json task request and execution checkpoint
|
|
1571
|
+
initialization.json control repository and Fractal ledger provenance
|
|
1572
|
+
control/ Fractal metadata, separate from product changes
|
|
1573
|
+
nodes/<node-id>/product/ isolated product candidate
|
|
1574
|
+
nodes/<node-id>/steps/ fresh dispatcher attempts and raw logs
|
|
1575
|
+
outcome.json captured Forge acceptance and verification
|
|
1576
|
+
```
|
|
1577
|
+
|
|
1578
|
+
The original solo/plan directory stores `fractal-selection.json`; decomposed plans also
|
|
1579
|
+
store `fractal-routing.json`. Prefer `./forge fractal runs`, `tree`, `logs` and `report`
|
|
1580
|
+
to direct ledger access. Inspection and stop never delete managed work.
|
|
1581
|
+
|
|
1582
|
+
Each stage writes into the run directory, which lives **outside** the repo — anything forge
|
|
1583
|
+
writes inside the working tree would show up in the dwarf's own diff and land in front of qa
|
|
1584
|
+
as if the dwarf had written it.
|
|
1585
|
+
|
|
1586
|
+
```
|
|
1587
|
+
<run-dir>/
|
|
1588
|
+
<role>.resolved what the spec resolved to (model, effort, harness, any clamp)
|
|
1589
|
+
<role>.prompt the exact prompt sent
|
|
1590
|
+
<role>.cmd the exact command line executed
|
|
1591
|
+
<role>.log full transcript
|
|
1592
|
+
<role>.last the final message — read this first
|
|
1593
|
+
<role>.infra only after an infrastructure failure: class, retry_after, detail
|
|
1594
|
+
<role>.orphans only when processes were left running after the harness exited
|
|
1595
|
+
changes.diff the dwarf's real diff, which is qa's input
|
|
1596
|
+
```
|
|
1597
|
+
|
|
1598
|
+
A solo run also records, for forge-ui and for you:
|
|
1599
|
+
|
|
1600
|
+
```
|
|
1601
|
+
<run-dir>/
|
|
1602
|
+
status RUNNING, then PASS|FAIL|UNKNOWN|NOCHANGES|INVALIDATED|TIMEOUT|INFRA|INTERRUPTED|ERROR
|
|
1603
|
+
dwarf.spec, qa.spec the specs the run was given
|
|
1604
|
+
attempts.tsv epoch, attempt number, status, infrastructure class, dwarf spec; one row per invocation
|
|
1605
|
+
```
|
|
1606
|
+
|
|
1607
|
+
Plus, in the repo itself and surviving the run:
|
|
1608
|
+
|
|
1609
|
+
```
|
|
1610
|
+
.forge/memory.md what forge learned here, injected into future prompts
|
|
1611
|
+
.forge/ledger.tsv one row per dispatch, ever; never injected
|
|
1612
|
+
```
|
|
1613
|
+
|
|
1614
|
+
A decomposed run adds:
|
|
1615
|
+
|
|
1616
|
+
```
|
|
1617
|
+
<plan-dir>/
|
|
1618
|
+
goal.txt
|
|
1619
|
+
tasks.tsv
|
|
1620
|
+
waves.tsv, deferrals.txt
|
|
1621
|
+
tasks/<id>/{capsule.md,prompt.md,dwarf.*,qa.*,changes.diff,status}
|
|
1622
|
+
tasks/<id>/attempt, attempts.tsv attempts spent; one audit row per entry (incl. refunded stops)
|
|
1623
|
+
tasks/<id>/known_issues.md, qa.gate findings tolerated by --qa-threshold, and the gate's decision
|
|
1624
|
+
tasks/<id>/guard.txt what forge observed about the implementer (self-commit, orphans, promises)
|
|
1625
|
+
tasks/<id>/infra.txt, resume, noretry infrastructure stop record; QA-only resume marker; no-progress marker
|
|
1626
|
+
tasks/<id>/qa_threshold optional per-task blocking threshold
|
|
1627
|
+
results.tsv id status branch
|
|
1628
|
+
timeout, qa_threshold, verify_retries options remembered from run/retry flags
|
|
1629
|
+
integration.base commit the integration branch started from
|
|
1630
|
+
verification.{status,exit,log,command,fingerprint} the combined verification
|
|
1631
|
+
verification.{flaky,retry.log} only when a failing command passed on rerun
|
|
1632
|
+
verification.coverage.txt suites the verify command does not run (advisory)
|
|
1633
|
+
```
|
|
1634
|
+
|
|
1635
|
+
with worktrees and branches at:
|
|
1636
|
+
|
|
1637
|
+
```
|
|
1638
|
+
<repo>/../.forge-worktrees/<run-id>/<task-id> worktree
|
|
1639
|
+
forge/<run-id>/<task-id> task branch
|
|
1640
|
+
forge/<run-id>-integration integration branch
|
|
1641
|
+
```
|
|
1642
|
+
|
|
1643
|
+
The worktree root is a **sibling of the repo, never `TMPDIR`**. Isolated worktrees are never
|
|
1644
|
+
pushed, so a temp root that gets cleaned takes the only copy of that work with it.
|
|
1645
|
+
|
|
1646
|
+
---
|
|
1647
|
+
|
|
1648
|
+
## Safety guarantees
|
|
1649
|
+
|
|
1650
|
+
- Solo runs leave source changes uncommitted. Forge creates private review commits, but
|
|
1651
|
+
does not commit on the solo source branch, push, or reset the user tree.
|
|
1652
|
+
- In a decomposed run, commits and merges happen **only** on the `forge/<run-id>/*` task
|
|
1653
|
+
branches and the `forge/<run-id>-integration` branch — branches forge created for itself.
|
|
1654
|
+
Never your branch, never a push. Your branch and working tree are untouched for the whole
|
|
1655
|
+
run.
|
|
1656
|
+
- Bypass flags are never added on forge's own initiative. `--yolo-dwarf` / `--yolo-qa` are the
|
|
1657
|
+
only route to an unsandboxed run, so that choice is always explicit and visible.
|
|
1658
|
+
- **One dwarf per working tree, always.** Two agents editing one tree interleave their edits
|
|
1659
|
+
into a diff neither of them wrote and qa cannot untangle. Parallelism comes from separate
|
|
1660
|
+
worktrees, which is why worktrees are mandatory there rather than an optimization.
|
|
1661
|
+
- A failed task's branch and worktree are **never** deleted — they're the only record of what
|
|
1662
|
+
went wrong and the only thing to retry from. A successful task's worktree is removed only
|
|
1663
|
+
after `results.tsv` is written and read back.
|
|
1664
|
+
- QA runs in a disposable repository. Mutations to the source or review snapshot invalidate
|
|
1665
|
+
acceptance. Disabled editing tools alone do not prevent writes through Bash.
|
|
1666
|
+
|
|
1667
|
+
### Preconditions
|
|
1668
|
+
|
|
1669
|
+
- Not a git repo → decomposed runs refuse. Worktrees are impossible and there's no honest
|
|
1670
|
+
fallback.
|
|
1671
|
+
- Uncommitted changes → **warned prominently.** Worktrees branch from a commit, so your local
|
|
1672
|
+
edits are invisible to every dwarf. This is the likeliest route to a confusingly wrong
|
|
1673
|
+
result.
|
|
1674
|
+
|
|
1675
|
+
---
|
|
1676
|
+
|
|
1677
|
+
## Troubleshooting
|
|
1678
|
+
|
|
1679
|
+
| Symptom | Cause / fix |
|
|
1680
|
+
|---|---|
|
|
1681
|
+
| skill missing in **codex** only | frontmatter isn't strict YAML. Codex refuses to load and logs `invalid YAML`; the other four are lenient and load it fine. Note codex's line numbers exclude the opening `---`, so its "line 3" is the file's line 4 |
|
|
1682
|
+
| skill missing in **antigravity** | `agy` ignores `~/.gemini/skills` entirely — skills load only inside a plugin. Re-run `forge-install.sh` |
|
|
1683
|
+
| antigravity runs an **old version** | `agy plugin install` copies. Re-run `forge-install.sh` after every edit |
|
|
1684
|
+
| duplicate entry in **opencode** | opencode auto-scans `~/.claude/`; a `~/.config/opencode/command/forge.md` on top is redundant |
|
|
1685
|
+
| agy: `Find command timed out` | missing `--add-dir <repo>` — forge passes it; a hand-built command must too |
|
|
1686
|
+
| agy dwarf ends with an error and an empty diff | it tried to run a shell command without yolo. A single denial aborts the run — use `--yolo-dwarf` |
|
|
1687
|
+
| dwarf produced no changes | usually an ambiguous prompt it couldn't resolve headlessly. Nobody can answer a question mid-run |
|
|
1688
|
+
| task status `ERROR` | worktree creation or a dispatch failed — read `tasks/<id>/dwarf.out` / `qa.out` |
|
|
1689
|
+
| task status `UNKNOWN` | QA never emitted a verdict line; read `tasks/<id>/qa.last`. `retry` (or `run --retry-failed N`) asks QA again on the unchanged work — no dwarf runs, no attempt is spent |
|
|
1690
|
+
| task status `INFRA`, `run` exits `8` | a quota/usage limit, auth problem, rate limit, network error or empty answer stopped a dispatch — not a task failure, and no attempt was spent. Read `tasks/<id>/infra.txt` (`class=`, `retry_after=`), fix the cause, run the same command again. `run --infra-retries M` waits it out instead. Dependents stay `PENDING`, never `BLOCKED` |
|
|
1691
|
+
| a task merged although its reviewer said FAIL | `--qa-threshold` accepted it: every CONFIRMED finding was labelled and milder than the threshold. See `tasks/<id>/known_issues.md` and `qa.gate`; nothing is accepted without an explicit threshold |
|
|
1692
|
+
| `accept` exits `3` "changed since it was reviewed" | the branch or worktree moved after QA, so accepting would sign off on unreviewed code. `retry` for a fresh review |
|
|
1693
|
+
| `combine` / `split` refuses | the message names why: unmerged task left behind, duplicate ids, locked or dirty plan, a Fractal-backed plan, or a selection that strands a dependent. Nothing was changed |
|
|
1694
|
+
| `integrate --final-review` stops with exit `5` | the whole-run reviewer found a blocking defect (or never reached a verdict); findings are in `<plan>/…/final-review/qa.last`. Your branch is untouched. Exit `8` means the reviewer hit a quota/auth stop: run it again |
|
|
1695
|
+
| `FLAKY` in the summary, verification `PASS` | the verify command failed once and passed on rerun. Read `verification.log` (the first run) — an intermittent failure can be a real bug. `--verify-retries 0` restores the strict single run |
|
|
1696
|
+
| `verify coverage: suite '…' is not run by the verify command` | the project defines a suite your `--verify` skips. Advisory only; add it to the command (or `.forge/verify`) if it should gate the merge |
|
|
1697
|
+
| implementer guard notes in the summary | the dwarf committed, switched branch, left processes running, or promised later work. Recorded in `tasks/<id>/guard.txt`; the diff and the reviewer still decide. Leftover processes were stopped |
|
|
1698
|
+
| task status `CONFLICT` | QA passed but the merge conflicted — `files` was understated somewhere. Check the run's scope-drift list: it usually names the file |
|
|
1699
|
+
| task status `TIMEOUT` | that role exceeded the dispatch timeout and was killed. Raise `--timeout`, or `retry` — the partial edit is still in the worktree |
|
|
1700
|
+
| a dispatch never returns | it can't any more: every backend is killed at its timeout (`--timeout`, else `FORGE_TIMEOUT`, else the registry's `timeout` column, else 45 min — 60 min for the default `xhigh` QA/planner effort) |
|
|
1701
|
+
| a task won't re-run: "already exists" | a worktree directory was deleted by hand. `run` and `retry` prune stale registrations first, so re-run rather than deleting the branch |
|
|
1702
|
+
| a failed task is expensive to redo | it isn't — `forge-parallel.sh retry <dir> <id> [--dwarf <spec>]` re-runs that one task with its reviewer's findings, in the worktree it already has |
|
|
1703
|
+
| an interrupted run seems lost | run `run` again; merged tasks are skipped, not re-dispatched |
|
|
1704
|
+
| every wave has exactly one task | `files` sets overlap across most tasks; the decomposition isn't actually parallel |
|
|
1705
|
+
| dwarves reimplement already-merged work | `goal.txt` or `files` missing, so the capsule carries no useful status |
|
|
1706
|
+
| several tasks report "produced no changes" | the decomposition made tasks that weren't independently actionable — use a coarser `--decompose-level`, not more retries |
|
|
1707
|
+
| `retry` says "produced no new changes" | the dwarf returned a byte-identical diff, so no reviewer was paid to read the same code twice. Escalate with `--dwarf`, or fix the prompt |
|
|
1708
|
+
| a new file's code never reached QA | fixed: untracked files are diffed against `/dev/null`. A hand-built capture using only `git status --porcelain` shows the reviewer a filename and no content |
|
|
1709
|
+
| claude dwarf ends "I need your permission to edit" | a permission mode that prompts for Edit. forge uses `acceptEdits` plus an explicit Bash allowance, because `auto` allows shell but prompts for Edit, and `acceptEdits` alone allows Edit but prompts for real shell commands |
|
|
1710
|
+
| `.forge/` turns up in a review | a diff capture is missing `':(exclude).forge'` |
|
|
1711
|
+
| memory stays empty | nothing durable was learned — the common, correct case. The ledger still has a row per dispatch |
|
|
1712
|
+
| a wrong fact keeps reappearing | `memory.md` is rebuilt from the ledger; delete the ledger rows, not the memory line |
|
|
1713
|
+
| dwarves build incompatible interfaces | the seam was never agreed — plan the approach, or use `--planner` |
|
|
1714
|
+
| Fractal was installed but a new unattended run uses ordinary execution | installation never activates it; pass `--fractal` explicitly |
|
|
1715
|
+
| `Fractal selected but unavailable` | run `./forge fractal doctor`, then the suggested managed install; resume retains the selected backend |
|
|
1716
|
+
| existing run rejects a new backend, pool or limit | selection and bounds are recorded; create a new run directory for changed settings |
|
|
1717
|
+
| Fractal reports completion but Forge has not accepted the work | inspect QA, verdict and verification; execution completion does not establish acceptance |
|
|
1718
|
+
| Fractal import stops with `source_drift` | the source changed after its snapshot; preserve both versions and inspect the candidate instead of forcing import |
|
|
1719
|
+
| Fractal work was paused or its coordinator exited | inspect `status` and `logs`, then use explicit `resume RUN_ID`; retry starts a new attempt |
|
|
1720
|
+
| browser dashboard closed while work is running | execution is independent; reopen with `./forge fractal open RUN_ID` |
|
|
1721
|
+
| costs are unknown or a dollar cap is rejected | the bridge only reports available usage; v1 cannot enforce a spending cap |
|
|
1722
|
+
|
|
1723
|
+
For per-harness invocation details, effort ladders and known CLI failure modes, see
|
|
1724
|
+
[`references/harnesses.md`](references/harnesses.md). For decomposition mechanics, see
|
|
1725
|
+
[`references/decompose.md`](references/decompose.md). For memory internals, see
|
|
1726
|
+
[`references/memory.md`](references/memory.md).
|
|
1727
|
+
|
|
1728
|
+
---
|
|
1729
|
+
|
|
1730
|
+
## Repository layout
|
|
1731
|
+
|
|
1732
|
+
```
|
|
1733
|
+
forge/
|
|
1734
|
+
├── SKILL.md the skill itself — what the orchestrating model reads
|
|
1735
|
+
├── registry.tsv alias → (harness, model, effort). Add a row to teach forge a model
|
|
1736
|
+
├── README.md this file
|
|
1737
|
+
├── LICENSE MIT
|
|
1738
|
+
├── package.json the npm package: its name, version and the files it ships
|
|
1739
|
+
├── forge launcher, on PATH from npm: `forge install`, `fractal`, `jev`, `dataset` and `ui`
|
|
1740
|
+
├── scripts/
|
|
1741
|
+
│ ├── forge-dispatch.sh resolve a spec → run one role on one harness
|
|
1742
|
+
│ ├── forge-solo.sh the whole single-task run: dwarf → diff → qa
|
|
1743
|
+
│ ├── forge-parallel.sh plan / run / retry / integrate for decomposed runs
|
|
1744
|
+
│ ├── forge-parallel-options.sh flags shared by several forge-parallel.sh subcommands
|
|
1745
|
+
│ ├── forge-lib-task.sh attempt log, infra stops, QA-only resume, guards, severity gate, report blocks
|
|
1746
|
+
│ ├── forge-lib-verify.sh verify command lookup and the coverage report
|
|
1747
|
+
│ ├── forge-lib-plans.sh accept / split / combine
|
|
1748
|
+
│ ├── forge-lib-review.sh the opt-in whole-run review (review, integrate --final-review)
|
|
1749
|
+
│ ├── forge-schedule.py readiness scheduler: in-run retries, infrastructure pauses
|
|
1750
|
+
│ ├── forge-contract.py the shared QA/dwarf prompt contract and the severity gate
|
|
1751
|
+
│ ├── forge-guard.py implementer guards: session wrapper, orphan reaping, promise scan
|
|
1752
|
+
│ ├── forge-verify-coverage.py suites the project defines that the verify command skips
|
|
1753
|
+
│ ├── forge-memory.sh inject / note / record / spend project memory
|
|
1754
|
+
│ ├── forge-fractal.py Fractal CLI and internal adapter entrypoint
|
|
1755
|
+
│ ├── forge-fractal-options.sh runner selection, limit flags and help
|
|
1756
|
+
│ ├── forge-fractal-dispatch.sh bridge entrypoint for existing pipelines
|
|
1757
|
+
│ ├── forge-install-fractal.sh isolated optional runtime installer
|
|
1758
|
+
│ ├── forge_fractal/ nested execution, recovery, inspection and dashboard
|
|
1759
|
+
│ ├── forge-ui.py `forge ui open|status|stop`: the one shared forge-ui server
|
|
1760
|
+
│ ├── forge-ui-options.sh `--ui|--no-ui` and the fail-open launch, sourced by the runners
|
|
1761
|
+
│ ├── forge_registry.py the run registry: where run dirs are noted, read by forge-ui
|
|
1762
|
+
│ ├── forge-dataset.py `forge dataset` entry point
|
|
1763
|
+
│ ├── forge_dataset/ the run dataset: capture hooks, readers, importer, status checks
|
|
1764
|
+
│ └── forge-install.sh install into every harness that needs it (opencode and grok find it themselves)
|
|
1765
|
+
├── ui/ forge-ui: read-only local run viewer (`forge_ui` package, tests, API contract)
|
|
1766
|
+
└── references/
|
|
1767
|
+
├── ui.md forge-ui: commands, env vars, shared server, state, privacy
|
|
1768
|
+
├── harnesses.md per-harness invocation, ladders, failure modes
|
|
1769
|
+
├── decompose.md tasks.tsv schema, difficulty criteria, wave algorithm
|
|
1770
|
+
├── fractal.md opt-in backend, limits, recovery and observability
|
|
1771
|
+
├── memory.md FORGE_LEARNING grammar, promotion and pruning rules
|
|
1772
|
+
└── dataset.md the run dataset: what is captured, where, and its invariants
|
|
1773
|
+
```
|
|
1774
|
+
|
|
1775
|
+
## Execution integrity and offline checks
|
|
1776
|
+
|
|
1777
|
+
Forge now captures solo changes against a recorded starting snapshot, including staged edits,
|
|
1778
|
+
without altering the user's index. Existing work is recorded separately in `existing.diff`.
|
|
1779
|
+
QA receives the full implementation requirements and reviews a disposable repository; source
|
|
1780
|
+
or snapshot changes invalidate the result. Only the recorded reviewed task commit can merge.
|
|
1781
|
+
|
|
1782
|
+
Parallel execution rechecks dependencies before each task and retry. Failed prerequisites leave
|
|
1783
|
+
consumers BLOCKED. Directory and child file declarations are serialized.
|
|
1784
|
+
|
|
1785
|
+
`forge-dispatch.sh doctor --spec sol --role dwarf` validates a selected model recipe using local
|
|
1786
|
+
CLI help. Solo and parallel runners preflight the whole selected pipeline before implementation.
|
|
1787
|
+
These checks do not establish authentication, billing or live model availability.
|
|
1788
|
+
|
|
1789
|
+
Use `forge-parallel.sh run <plan> --verify 'your check command'` or put the command in
|
|
1790
|
+
`.forge/verify` to verify the combined result. Output and exit status are saved with the plan.
|
|
1791
|
+
Integration checks a candidate containing the current user branch before updating it. A missing
|
|
1792
|
+
command is reported as UNVERIFIED; a failing configured check blocks integration. The project's
|
|
1793
|
+
own test suites that the command does not run are named as warnings (advisory), and a failing
|
|
1794
|
+
command is rerun once so a flaky lane is reported rather than failing the run — see
|
|
1795
|
+
[verification coverage and flaky reruns](#verification-coverage-and-flaky-reruns).
|
|
1796
|
+
`integrate` also names any task that is not merged and therefore not part of what it delivers.
|
|
1797
|
+
|
|
1798
|
+
Run `bash tests/check.sh` for offline regression checks. See [tests](tests/README.md) for skill
|
|
1799
|
+
invocation scenarios and verification limits. There is currently no CI workflow.
|
|
1800
|
+
|
|
1801
|
+
|
|
1802
|
+
## Runtime efficiency and measurement
|
|
1803
|
+
|
|
1804
|
+
Shell entrypoints require Python 3 (standard library only) in addition to Bash and Git.
|
|
1805
|
+
The [runtime helper decision](references/runtime.md) records the module boundaries and tradeoffs.
|
|
1806
|
+
Solo and parallel runners default to `--output summary`; use `--output full` to include raw
|
|
1807
|
+
backend output. Direct dispatch keeps its full-output default. Read each `dwarf.last` and
|
|
1808
|
+
`qa.last` once, and retrieve `.log` or `.out` only when investigating a problem.
|
|
1809
|
+
|
|
1810
|
+
QA receives complete requirements, approach, ownership, retry findings and distinct memory
|
|
1811
|
+
facts once, with review instructions separate from implementation instructions. Prompts and
|
|
1812
|
+
full binary diffs remain on disk. Review repositories receive self-contained tree object packs,
|
|
1813
|
+
create baseline/final commits, and check out the final tree once. Exact tree comparisons and
|
|
1814
|
+
all source, review and integration fingerprint checks remain acceptance boundaries.
|
|
1815
|
+
|
|
1816
|
+
`FORGE_CAPABILITY_CACHE` optionally selects an offline help cache. Runners share a run-local
|
|
1817
|
+
`capabilities/` cache across preflights and dispatches. Keys cover the executable's resolved
|
|
1818
|
+
path, device/inode, size and modification/change timestamps, help command mode, registry
|
|
1819
|
+
contents and dispatch recipe. Every selected spec, effort and permission recipe is still
|
|
1820
|
+
validated; cached help never proves authentication or live model availability.
|
|
1821
|
+
|
|
1822
|
+
Run `bash scripts/forge-dispatch.sh report <run-dir>` for JSON attempt records, including
|
|
1823
|
+
failures and timeouts, with project memory enabled or disabled. `attempts/<role>-<id>/`
|
|
1824
|
+
retains prompts, final responses, raw logs, resolution, command and `metrics.json` per dispatch.
|
|
1825
|
+
Pipeline records capture preparation, preflight, snapshot, verification and total time;
|
|
1826
|
+
dispatch records capture preparation, preflight, model and total time. Nested pipeline
|
|
1827
|
+
`dispatch` time includes dispatch overhead: do not add nested totals together. Timing uses
|
|
1828
|
+
monotonic seconds. A still-running or forcibly killed record has a null exit code and may
|
|
1829
|
+
have incomplete timings. A dispatch that stopped on an infrastructure failure records its class
|
|
1830
|
+
(`quota`, `auth`, `rate_limit`, `network`, `empty`) as `infra_class`, and keeps the `.infra` and
|
|
1831
|
+
`.orphans` files beside the other attempt artifacts.
|
|
1832
|
+
|
|
1833
|
+
Prompt bytes are separate from native input, cached-input and output token counts. Codex
|
|
1834
|
+
uses `turn.completed` usage; Claude uses result usage (input includes uncached, cache-read
|
|
1835
|
+
and cache-creation tokens). Unsupported or absent usage stays null, never estimated from
|
|
1836
|
+
bytes. See [measurement and live evaluation](tests/README.md).
|
|
1837
|
+
|
|
1838
|
+
Measured results, including the live comparison's higher total token use and latency, are
|
|
1839
|
+
recorded in [the September 7 evaluation](tests/benchmark-2026-09-07.md).
|
|
1840
|
+
|
|
1841
|
+
## Ripwire context
|
|
1842
|
+
|
|
1843
|
+
Forge automatically adds optional Ripwire 0.4.0 repository evidence to planner, dwarf,
|
|
1844
|
+
and QA prompts across all six harnesses. Setup and interactive runners offer a pinned,
|
|
1845
|
+
verified installation; headless runs continue when it is missing. Use `--no-ripwire`
|
|
1846
|
+
or `FORGE_RIPWIRE=off` to disable it. Decomposed plans preserve opt-out across resumes
|
|
1847
|
+
and retries. See [installation, controls and attempt artifacts](references/ripwire.md).
|
|
1848
|
+
|
|
1849
|
+
---
|
|
1850
|
+
|
|
1851
|
+
## License
|
|
1852
|
+
|
|
1853
|
+
forge is released under the [MIT License](LICENSE). forge-ui's 3D view ports code from
|
|
1854
|
+
archivision, iso-glow and iso-figure, all MIT-licensed; their notices are in
|
|
1855
|
+
[ui/THIRD_PARTY_NOTICES.md](ui/THIRD_PARTY_NOTICES.md).
|