@giamme/forge 0.8.0 → 0.10.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 +50 -0
- package/README.md +106 -1766
- package/SKILL.md +22 -3
- package/VERSION +1 -1
- package/docs/README.md +26 -0
- package/docs/add-ons.md +33 -0
- package/docs/artifacts.md +84 -0
- package/docs/commands.md +365 -0
- package/docs/dataset.md +29 -0
- package/docs/decomposed-runs.md +310 -0
- package/docs/development.md +95 -0
- package/docs/forge-ui.md +128 -0
- package/docs/fractal.md +181 -0
- package/docs/install.md +160 -0
- package/docs/memory.md +156 -0
- package/docs/models.md +118 -0
- package/docs/reviews.md +245 -0
- package/docs/safety.md +55 -0
- package/docs/troubleshooting.md +54 -0
- package/forge +20 -0
- package/package.json +3 -2
- package/references/dataset.md +18 -1
- package/references/decompose.md +20 -2
- package/references/fractal.md +1 -1
- package/references/harnesses.md +18 -6
- package/references/solo.md +1 -1
- package/references/ui.md +27 -5
- package/scripts/forge-config.py +347 -0
- package/scripts/forge-control.py +645 -0
- package/scripts/forge-dispatch.sh +7 -1
- package/scripts/forge-issues.py +806 -0
- package/scripts/forge-lib-task.sh +234 -35
- package/scripts/forge-parallel-options.sh +3 -1
- package/scripts/forge-parallel.sh +3 -2
- package/scripts/forge-route.py +184 -27
- package/scripts/forge-runs-rm.py +840 -0
- package/scripts/forge-runs.py +139 -0
- package/scripts/forge-runtime.py +59 -6
- package/scripts/forge-schedule.py +227 -11
- package/scripts/forge-ui.py +85 -19
- package/scripts/forge-version.py +54 -12
- package/scripts/forge_dataset/__init__.py +77 -6
- package/scripts/forge_dataset/capture.py +5 -2
- package/scripts/forge_dataset/harvest.py +23 -1
- package/scripts/forge_dataset/importer.py +420 -18
- package/scripts/forge_fractal/__init__.py +2 -1
- package/scripts/forge_registry.py +168 -16
- package/ui/README.md +116 -23
- package/ui/THIRD_PARTY_NOTICES.md +108 -0
- package/ui/docs/api.md +283 -23
- package/ui/forge_ui/__main__.py +10 -2
- package/ui/forge_ui/control.py +575 -0
- package/ui/forge_ui/export.py +33 -9
- package/ui/forge_ui/fractal.py +1 -2
- package/ui/forge_ui/reader.py +121 -8
- package/ui/forge_ui/server.py +135 -5
- package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
- package/ui/forge_ui/static/app.js +358 -131
- package/ui/forge_ui/static/cloud3d.js +3 -2
- package/ui/forge_ui/static/cloudui.js +8 -5
- package/ui/forge_ui/static/controls.js +619 -0
- package/ui/forge_ui/static/convo.js +295 -0
- package/ui/forge_ui/static/datatable.js +232 -0
- package/ui/forge_ui/static/diffview.js +29 -10
- package/ui/forge_ui/static/findings3d.js +134 -0
- package/ui/forge_ui/static/findingsview.js +412 -0
- package/ui/forge_ui/static/graph.js +3 -3
- package/ui/forge_ui/static/icons.js +94 -0
- package/ui/forge_ui/static/index.html +31 -13
- package/ui/forge_ui/static/iso.js +70 -19
- package/ui/forge_ui/static/leaderboard.js +44 -48
- package/ui/forge_ui/static/panel.js +92 -36
- package/ui/forge_ui/static/rundelete.js +388 -0
- package/ui/forge_ui/static/runmodel.js +17 -1
- package/ui/forge_ui/static/scene3d.js +518 -43
- package/ui/forge_ui/static/search.js +2 -1
- package/ui/forge_ui/static/settings.js +399 -0
- package/ui/forge_ui/static/shell.js +137 -0
- package/ui/forge_ui/static/stagectl.js +150 -0
- package/ui/forge_ui/static/style.css +1099 -469
- package/ui/forge_ui/static/theme.js +25 -0
- package/ui/forge_ui/static/uikit.js +161 -0
- package/ui/forge_ui/transcripts.py +608 -0
package/docs/reviews.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Reviews, retries and recovery
|
|
2
|
+
|
|
3
|
+
[← README](../README.md) · [All docs](README.md)
|
|
4
|
+
|
|
5
|
+
What a reviewer must answer, what forge does when it fails a task, how to retry, tolerate or accept findings, and the checks forge runs around the reviewer.
|
|
6
|
+
|
|
7
|
+
## Verdicts and failure
|
|
8
|
+
|
|
9
|
+
Decompose-mode QA must end with a sentinel line:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
FORGE_VERDICT: PASS no confirmed correctness bug (style nits are not failures)
|
|
13
|
+
FORGE_VERDICT: FAIL at least one CONFIRMED correctness bug
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Only a standalone final line counts, and the parser is shared by every runner (solo,
|
|
17
|
+
decomposed, Fractal's QA checkpoint). The prompt shows each verdict with a dash and its
|
|
18
|
+
meaning, and some reviewers copy that whole line, so `FORGE_VERDICT: PASS — meaning` is
|
|
19
|
+
accepted, as are trailing whitespace and CRLF line endings. Prose or a quoted marker earlier
|
|
20
|
+
in the reply is never a verdict. A missing verdict is `UNKNOWN` and is treated exactly like a
|
|
21
|
+
failure — excluded from integration and flagged. Merging a diff whose reviewer never reached
|
|
22
|
+
a conclusion would defeat the point of reviewing it, so the ambiguous case fails safe.
|
|
23
|
+
|
|
24
|
+
A failing task is excluded and **its branch and worktree are preserved**; the rest of the run
|
|
25
|
+
proceeds. forge never loops a dwarf against its reviewer on its own: a retry happens when you
|
|
26
|
+
type `retry`, or when you opt in for a run with `--retry-failed N` ([below](#retrying-inside-a-run---retry-failed-n)).
|
|
27
|
+
|
|
28
|
+
An `UNKNOWN` task is different from a rejected one: the reviewer never judged the code, so
|
|
29
|
+
`retry` (or `--retry-failed`) asks **QA again on the unchanged work** instead of paying an
|
|
30
|
+
implementer to rewrite something nobody rejected. No attempt is spent and no dwarf runs. If
|
|
31
|
+
the worktree no longer matches what was reviewed, the full task runs instead.
|
|
32
|
+
|
|
33
|
+
## Retrying a failed task
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# re-run one task with the findings that failed it, on a stronger model
|
|
37
|
+
bash scripts/forge-parallel.sh retry "$PLAN" middleware --dwarf sol:xhigh
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The findings are the point. `retry` prepends the previous reviewer's report to the dwarf's
|
|
41
|
+
prompt — "your last attempt was reviewed and these were the findings; fix them" — and reuses
|
|
42
|
+
the worktree, so the dwarf continues from its own code rather than starting over. If the
|
|
43
|
+
verdict comes back `PASS` the task merges like any other.
|
|
44
|
+
|
|
45
|
+
`--dwarf` is written back into `tasks.tsv`, so the table shows the model that will actually
|
|
46
|
+
be spent and the escalation survives a re-plan.
|
|
47
|
+
|
|
48
|
+
Two things it deliberately does:
|
|
49
|
+
|
|
50
|
+
- **Reviews the task's cumulative diff**, against the commit the task was originally branched
|
|
51
|
+
from — not just the fix. Reviewing the fix alone would let the first attempt's code through
|
|
52
|
+
unread.
|
|
53
|
+
- **Refuses to re-review identical code.** If the retry produces a byte-identical diff, the
|
|
54
|
+
task fails again without spending a QA dispatch.
|
|
55
|
+
|
|
56
|
+
`retry` is something you run. Forge never loops a dwarf against its own reviewer on its own
|
|
57
|
+
initiative — unless you ask a *run* to with `--retry-failed N`, see
|
|
58
|
+
[retrying inside a run](#retrying-inside-a-run---retry-failed-n). A task whose reviewer
|
|
59
|
+
never gave a verdict (`UNKNOWN`) or whose run stopped on an infrastructure failure (`INFRA`)
|
|
60
|
+
resumes at the review stage instead: QA runs again on the unchanged work, no dwarf runs, and
|
|
61
|
+
no attempt is spent.
|
|
62
|
+
|
|
63
|
+
## Retrying inside a run: `--retry-failed N`
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
forge-parallel.sh run <plan-dir> --retry-failed 2
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
A task whose reviewer says `FAIL` (or `UNKNOWN`) is re-queued at once, **inside the same
|
|
70
|
+
run**, up to `N` more times — with the reviewer's findings carried into the next prompt exactly
|
|
71
|
+
as `retry` does. Other tasks keep running meanwhile; dependents still wait for the task to
|
|
72
|
+
*merge*, so nothing downstream starts early. Without this, a failed task waited for the whole
|
|
73
|
+
run (verification included) to finish before a human could retry it.
|
|
74
|
+
|
|
75
|
+
It is opt-in and per invocation: it is not remembered by the plan, and the orchestrator never
|
|
76
|
+
adds it unprompted. Only reviewer rejections are retried automatically — `TIMEOUT`, `ERROR`,
|
|
77
|
+
`INVALIDATED` and `CONFLICT` still need a human. A retry that changes nothing (empty diff, or
|
|
78
|
+
a diff byte-identical to the previous attempt) writes a `noretry` marker and is **not**
|
|
79
|
+
retried again, so a stuck task cannot burn its whole budget repeating itself.
|
|
80
|
+
`tasks/<id>/attempts.tsv` records every entry (epoch, attempt number, resulting status, infra
|
|
81
|
+
class).
|
|
82
|
+
|
|
83
|
+
## Infrastructure failures are not task failures
|
|
84
|
+
|
|
85
|
+
A quota or usage limit, an auth problem, a rate limit, a network error, or a harness that
|
|
86
|
+
returns nothing at all says nothing about the work. forge classifies those from the dispatch
|
|
87
|
+
log (`forge-runtime.py classify`) and `forge-dispatch.sh` exits **8** instead of 4. A
|
|
88
|
+
decomposed task then ends as **`INFRA`**, not `ERROR`/`FAIL`, and:
|
|
89
|
+
|
|
90
|
+
- **no attempt is spent** — a dwarf-stage stop refunds the counter; a QA-stage stop keeps the
|
|
91
|
+
dwarf's committed work and re-runs **QA only** next time (`tasks/<id>/resume`, revalidated
|
|
92
|
+
against the branch tip and fingerprint before it is trusted);
|
|
93
|
+
- the scheduler stops dispatching, lets running tasks drain, leaves unstarted ones `PENDING`
|
|
94
|
+
(never `BLOCKED`) and `run` exits **8**: fix the cause, then run the same command again;
|
|
95
|
+
- `run --infra-retries M` waits instead — up to `M` times, using the provider's retry-after
|
|
96
|
+
hint when it gave one (capped at 6 hours) and otherwise 60 s doubling to 30 min
|
|
97
|
+
(`FORGE_INFRA_BACKOFF` sets the base) — and resumes in the same run;
|
|
98
|
+
- verification is skipped for a paused run (it is unfinished); the next `run` verifies at its end.
|
|
99
|
+
|
|
100
|
+
`.infra` (`dwarf.infra`/`qa.infra`) holds `class=`, `retry_after=`, `detail=`, `rc=`,
|
|
101
|
+
`role=`. Detection is deliberately strict: strong phrases only, and ordinary text that merely
|
|
102
|
+
mentions "rate limit" in a reply that otherwise succeeded is never classified. A false
|
|
103
|
+
positive costs a pause, not a spent attempt. Under Fractal a dwarf-side infrastructure stop
|
|
104
|
+
still collapses to Fractal's own "failed" handling; QA-side stops propagate as above.
|
|
105
|
+
|
|
106
|
+
## Severity, thresholds and known issues
|
|
107
|
+
|
|
108
|
+
QA is asked to label every finding `- [P0..P3][CONFIRMED|PLAUSIBLE] file:line — what breaks`
|
|
109
|
+
(P0 data loss/security/crash on the main path, P1 wrong behaviour on realistic input, P2
|
|
110
|
+
edge-case bug, P3 minor). By default the verdict rule is unchanged — any CONFIRMED bug fails.
|
|
111
|
+
|
|
112
|
+
`--qa-threshold Pn` (on `run` or `retry`; remembered in the plan; `none` clears it) names the
|
|
113
|
+
**least severe level that still blocks**: `P2` means P0–P2 block and only P3 is tolerated.
|
|
114
|
+
A task-specific `tasks/<id>/qa_threshold` file beats the plan-level value, which is how a
|
|
115
|
+
dev-tooling or investigation task gets a looser bar than production code. With a threshold, a
|
|
116
|
+
reviewer `FAIL` made only of CONFIRMED findings that are all labelled and strictly milder than
|
|
117
|
+
the threshold is accepted as **PASS with known issues**: the task merges, `tasks/<id>/known_issues.md`
|
|
118
|
+
keeps the findings, `qa.gate` records the decision, and every run/retry/integrate summary
|
|
119
|
+
lists them. The literal status stays `PASS`, so nothing downstream needed a new vocabulary.
|
|
120
|
+
It fails safe: an unlabelled CONFIRMED finding, or a FAIL with no CONFIRMED finding at all,
|
|
121
|
+
is never tolerated, and without a threshold nothing changes.
|
|
122
|
+
|
|
123
|
+
Tolerated findings are recorded, not fixed. After a run, the skill offers once to file them as
|
|
124
|
+
GitHub issues, together with PLAUSIBLE findings of passed tasks and whatever a whole-run review
|
|
125
|
+
left open (`--file-issues ask|auto|off`, `ask` by default). Where the
|
|
126
|
+
[gh-proman](https://github.com/Giamme/gh-proman) skill is set up for the repository, it files
|
|
127
|
+
them on the team's project board; anywhere else forge files them itself with plain `gh`:
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
./forge issues <plan-dir> # drafts only: titles, severity, already filed
|
|
131
|
+
./forge issues <plan-dir> --include-plausible --apply --label bug
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Each issue gets the finding, its severity, the task, reviewer and a code link at the merged
|
|
135
|
+
commit. A hidden fingerprint, shared with gh-proman, keeps a finding from being filed twice.
|
|
136
|
+
|
|
137
|
+
## Accepting a task despite its reviewer
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
bash scripts/forge-parallel.sh accept "$PLAN" tooling \
|
|
141
|
+
--reason "only an unreachable option left; tracked in the backlog" --approved
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
When a reviewer keeps finding new corners of something that should ship (one real
|
|
145
|
+
investigation harness got 12 findings over five rounds, and only a human decision ended it),
|
|
146
|
+
`accept` is that decision made explicit instead of done with git plumbing. It needs **both**
|
|
147
|
+
`--reason` and `--approved`, because it overrides a reviewer; an orchestrating model never runs
|
|
148
|
+
it on its own. It accepts only a `FAIL` or `UNKNOWN` task whose reviewed work is **intact** —
|
|
149
|
+
the branch is still at the reviewed commit and the worktree still has the saved fingerprint —
|
|
150
|
+
otherwise it exits `3` and says to `retry` for a fresh review (accepting would sign off on code
|
|
151
|
+
nobody reviewed). It writes `tasks/<id>/known_issues.md` (your reason, who and when, and the
|
|
152
|
+
reviewer's reply copied verbatim) and `accepted.by`, merges through the same checks as any other
|
|
153
|
+
task, and puts everything back if the merge conflicts (exit `6`). Dependents are not touched;
|
|
154
|
+
`run` continues them.
|
|
155
|
+
|
|
156
|
+
## Whole-run review (opt-in)
|
|
157
|
+
|
|
158
|
+
Per-task QA only sees one slice. In one real run, a holistic review of the combined result still
|
|
159
|
+
found two confirmed P2 defects in code every per-task review had passed. It is opt-in because it
|
|
160
|
+
spends QA quota and `integrate` made no model calls before:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
bash scripts/forge-parallel.sh review "$PLAN" --qa opus # read-only look at the combined result
|
|
164
|
+
bash scripts/forge-parallel.sh integrate "$PLAN" --approved --final-review opus # and gate the merge on it
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The reviewer gets the overall goal, every task's complete requirements (bounded to about 80 KB,
|
|
168
|
+
truncated visibly), what forge observed that no per-task reviewer saw (accepted known issues,
|
|
169
|
+
scope drift, guard notes, the verification result including a `FLAKY` pass and coverage
|
|
170
|
+
warnings), the combined diff (inline up to 400 KB), and the same labelled-findings contract —
|
|
171
|
+
with an emphasis on cross-task interactions, interface mismatches and ordering races. `review`
|
|
172
|
+
reads `integration.base..integration tip` from a disposable checkout, writes
|
|
173
|
+
`<plan>/final-review-<epoch>-<pid>/` (`qa.input`, `qa.last`, `verdict`, `qa.gate`, …), keeps
|
|
174
|
+
`<plan>/final-review` and `final-review.verdict` pointing at the newest, and never touches your
|
|
175
|
+
branch or the integration branch. With `integrate --final-review`, it runs after verification
|
|
176
|
+
and before your branch can move: `PASS` (or a `FAIL` accepted by `--qa-threshold`) continues,
|
|
177
|
+
anything else stops with exit `5`; an infrastructure stop exits `8`. The QA spec is `--qa`, else
|
|
178
|
+
the one remembered in `final_qa`, else the single spec every task shares (otherwise it asks).
|
|
179
|
+
Tasks that are not merged are not in the result, and it says so. `integrate` also names unmerged
|
|
180
|
+
tasks even without a review.
|
|
181
|
+
|
|
182
|
+
## Shadow reviews: how much of a verdict is the reviewer?
|
|
183
|
+
|
|
184
|
+
`plan --shadow-qa <spec>[,<spec>...] [--shadow-rate R]` (default R = 0.1) sends a sampled
|
|
185
|
+
share of reviewed attempts to a second reviewer, the first listed one of another model
|
|
186
|
+
family than the task's. Its verdict goes to the run dataset and nothing in the run reads it,
|
|
187
|
+
so it never changes a status, merge, memory or retry. `forge dataset status` then reports how
|
|
188
|
+
often the two reviewers agree, overall and per model pair. The table shows the extra reviews
|
|
189
|
+
before you approve. The shadow runs beside the main review and is stopped soon after that
|
|
190
|
+
finishes, so the most it costs is reviews and a little latency. See
|
|
191
|
+
[decomposition](../references/decompose.md#shadow-reviews-opt-in).
|
|
192
|
+
|
|
193
|
+
## Implementer guards
|
|
194
|
+
|
|
195
|
+
The dwarf is told not to commit, switch branches, start background processes, or claim checks
|
|
196
|
+
it did not run. forge then **observes** whether it did, rather than trusting its report:
|
|
197
|
+
|
|
198
|
+
- **self-commit / branch switch** — HEAD or the checked-out ref moved during the dispatch;
|
|
199
|
+
- **orphan processes** — the harness runs in its own session; anything still alive in it after
|
|
200
|
+
the harness exits is listed in `<role>.orphans` and stopped (`FORGE_ORPHAN_GUARD=off`
|
|
201
|
+
disables this; it is also skipped under Fractal);
|
|
202
|
+
- **promised later work** — "running in the background", "will report back once it
|
|
203
|
+
finishes" and similar in the final message.
|
|
204
|
+
|
|
205
|
+
These are warnings recorded in `tasks/<id>/guard.txt`, shown in the QA prompt ("Implementer
|
|
206
|
+
guard notes") and in the run summary. They never change a status: the diff and the reviewer
|
|
207
|
+
still decide. In solo mode a self-commit prints the `git reset --soft <old HEAD>` that would
|
|
208
|
+
undo it; forge never resets anything itself.
|
|
209
|
+
|
|
210
|
+
## Verification coverage and flaky reruns
|
|
211
|
+
|
|
212
|
+
`--verify` is whatever you hand it, and a gate that skips a suite the project defines lets a
|
|
213
|
+
regression there reach your branch silently. `forge-verify-coverage.py` reads the project's own
|
|
214
|
+
test configuration (`package.json` scripts, Makefile targets, pytest/tox, Cargo, Go, Ruby,
|
|
215
|
+
Elixir, `scripts/test*.sh`), follows aggregate scripts, and **warns** — before any model is
|
|
216
|
+
paid for, again in the run summary and at integrate — about suites the verify command never
|
|
217
|
+
runs, e.g. `suite 'test:contract' (npm run test:contract) is not run by the verify command`.
|
|
218
|
+
It is advisory: it never fails a run, and `verification.coverage.txt` keeps the lines. It is a
|
|
219
|
+
text heuristic (root of the repo only; a mentioned command counts as run).
|
|
220
|
+
|
|
221
|
+
A failing verify command is rerun **once** (`--verify-retries N`; `0` restores the single strict
|
|
222
|
+
run) when the source fingerprint is unchanged. If the rerun passes the status is `PASS` but
|
|
223
|
+
`verification.flaky` is written, `verification.log` keeps the first (failing) run,
|
|
224
|
+
`verification.retry.log` the second, and `run`/`integrate` print a loud `FLAKY` line — an
|
|
225
|
+
intermittent failure can be a real bug, so it is never passed off as a clean pass. A command
|
|
226
|
+
that changes the tree is never retried. This deterministic rerun comes before Jev's optional
|
|
227
|
+
model-assisted triage, which then does not run.
|
|
228
|
+
|
|
229
|
+
## Native review
|
|
230
|
+
|
|
231
|
+
`--native-review` switches a codex reviewer to its purpose-built reviewer, for a diff too large
|
|
232
|
+
to inline. Know the trade: codex refuses a custom prompt alongside its scope flags, so the
|
|
233
|
+
reviewer never learns what the dwarf was *asked* to do. It can still judge whether the code is
|
|
234
|
+
correct, but not whether it's the right change, so its result is informational and the verdict
|
|
235
|
+
is `UNKNOWN`. See [solo runs](../references/solo.md) for how the baseline is passed.
|
|
236
|
+
|
|
237
|
+
## Fail-first evidence (opt-in)
|
|
238
|
+
|
|
239
|
+
`--fail-first` (on a solo run or `plan`) checks that the tests a change adds or edits can
|
|
240
|
+
actually fail. Before each review, forge runs each new or edited test file against the change
|
|
241
|
+
and against the base without it, using the per-file command in `.forge/verify-subset`, and hands
|
|
242
|
+
the reviewer the result: `proven`, `passes_without_change`, `inconclusive` or `error`. The
|
|
243
|
+
reviewer is asked for a P2 finding for every test that passes without the change. It is advice
|
|
244
|
+
only: forge never changes a status or verdict from it. forge-ui shows the evidence under the
|
|
245
|
+
review findings. See [fail-first evidence](../references/decompose.md#fail-first-evidence-opt-in).
|
package/docs/safety.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Safety
|
|
2
|
+
|
|
3
|
+
[← README](../README.md) · [All docs](README.md)
|
|
4
|
+
|
|
5
|
+
What forge never does to your branch and working tree, what it refuses to run on, and how it keeps reviewed work and merged work the same.
|
|
6
|
+
|
|
7
|
+
## Safety guarantees
|
|
8
|
+
|
|
9
|
+
- Solo runs leave source changes uncommitted. Forge creates private review commits, but
|
|
10
|
+
does not commit on the solo source branch, push, or reset the user tree.
|
|
11
|
+
- In a decomposed run, commits and merges happen **only** on the `forge/<run-id>/*` task
|
|
12
|
+
branches and the `forge/<run-id>-integration` branch — branches forge created for itself.
|
|
13
|
+
Never your branch, never a push. Your branch and working tree are untouched for the whole
|
|
14
|
+
run.
|
|
15
|
+
- Bypass flags are never added on forge's own initiative. `--yolo-dwarf` / `--yolo-qa` are the
|
|
16
|
+
only route to an unsandboxed run, so that choice is always explicit and visible.
|
|
17
|
+
- **One dwarf per working tree, always.** Two agents editing one tree interleave their edits
|
|
18
|
+
into a diff neither of them wrote and qa cannot untangle. Parallelism comes from separate
|
|
19
|
+
worktrees, which is why worktrees are mandatory there rather than an optimization.
|
|
20
|
+
- A failed task's branch and worktree are **never** deleted — they're the only record of what
|
|
21
|
+
went wrong and the only thing to retry from. A successful task's worktree is removed only
|
|
22
|
+
after `results.tsv` is written and read back.
|
|
23
|
+
- QA runs in a disposable repository. Mutations to the source or review snapshot invalidate
|
|
24
|
+
acceptance. Disabled editing tools alone do not prevent writes through Bash.
|
|
25
|
+
|
|
26
|
+
## Preconditions
|
|
27
|
+
|
|
28
|
+
- Not a git repo → decomposed runs refuse. Worktrees are impossible and there's no honest
|
|
29
|
+
fallback.
|
|
30
|
+
- Uncommitted changes → **warned prominently.** Worktrees branch from a commit, so your local
|
|
31
|
+
edits are invisible to every dwarf. This is the likeliest route to a confusingly wrong
|
|
32
|
+
result.
|
|
33
|
+
|
|
34
|
+
## Execution integrity
|
|
35
|
+
|
|
36
|
+
Forge now captures solo changes against a recorded starting snapshot, including staged edits,
|
|
37
|
+
without altering the user's index. Existing work is recorded separately in `existing.diff`.
|
|
38
|
+
QA receives the full implementation requirements and reviews a disposable repository; source
|
|
39
|
+
or snapshot changes invalidate the result. Only the recorded reviewed task commit can merge.
|
|
40
|
+
|
|
41
|
+
Parallel execution rechecks dependencies before each task and retry. Failed prerequisites leave
|
|
42
|
+
consumers BLOCKED. Directory and child file declarations are serialized.
|
|
43
|
+
|
|
44
|
+
`forge-dispatch.sh doctor --spec sol --role dwarf` validates a selected model recipe using local
|
|
45
|
+
CLI help. Solo and parallel runners preflight the whole selected pipeline before implementation.
|
|
46
|
+
These checks do not establish authentication, billing or live model availability.
|
|
47
|
+
|
|
48
|
+
Use `forge-parallel.sh run <plan> --verify 'your check command'` or put the command in
|
|
49
|
+
`.forge/verify` to verify the combined result. Output and exit status are saved with the plan.
|
|
50
|
+
Integration checks a candidate containing the current user branch before updating it. A missing
|
|
51
|
+
command is reported as UNVERIFIED; a failing configured check blocks integration. The project's
|
|
52
|
+
own test suites that the command does not run are named as warnings (advisory), and a failing
|
|
53
|
+
command is rerun once so a flaky lane is reported rather than failing the run — see
|
|
54
|
+
[verification coverage and flaky reruns](reviews.md#verification-coverage-and-flaky-reruns).
|
|
55
|
+
`integrate` also names any task that is not merged and therefore not part of what it delivers.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
[← README](../README.md) · [All docs](README.md)
|
|
4
|
+
|
|
5
|
+
Symptoms, causes and fixes, from a skill a harness cannot see to a task that will not merge.
|
|
6
|
+
|
|
7
|
+
| Symptom | Cause / fix |
|
|
8
|
+
|---|---|
|
|
9
|
+
| 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 |
|
|
10
|
+
| skill missing in **antigravity** | `agy` ignores `~/.gemini/skills` entirely — skills load only inside a plugin. Re-run `forge-install.sh` |
|
|
11
|
+
| antigravity runs an **old version** | `agy plugin install` copies. Re-run `forge-install.sh` after every edit |
|
|
12
|
+
| duplicate entry in **opencode** | opencode auto-scans `~/.claude/`; a `~/.config/opencode/command/forge.md` on top is redundant |
|
|
13
|
+
| agy: `Find command timed out` | missing `--add-dir <repo>` — forge passes it; a hand-built command must too |
|
|
14
|
+
| 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` |
|
|
15
|
+
| dwarf produced no changes | usually an ambiguous prompt it couldn't resolve headlessly. Nobody can answer a question mid-run |
|
|
16
|
+
| task status `ERROR` | worktree creation or a dispatch failed — read `tasks/<id>/dwarf.out` / `qa.out` |
|
|
17
|
+
| 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 |
|
|
18
|
+
| 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` |
|
|
19
|
+
| 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 |
|
|
20
|
+
| `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 |
|
|
21
|
+
| `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 |
|
|
22
|
+
| `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 |
|
|
23
|
+
| `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 |
|
|
24
|
+
| `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 |
|
|
25
|
+
| 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 |
|
|
26
|
+
| 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 |
|
|
27
|
+
| task status `TIMEOUT` | that role exceeded the dispatch timeout and was killed. Raise `--timeout`, or `retry` — the partial edit is still in the worktree |
|
|
28
|
+
| 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) |
|
|
29
|
+
| 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 |
|
|
30
|
+
| 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 |
|
|
31
|
+
| an interrupted run seems lost | run `run` again; merged tasks are skipped, not re-dispatched |
|
|
32
|
+
| every wave has exactly one task | `files` sets overlap across most tasks; the decomposition isn't actually parallel |
|
|
33
|
+
| dwarves reimplement already-merged work | `goal.txt` or `files` missing, so the capsule carries no useful status |
|
|
34
|
+
| several tasks report "produced no changes" | the decomposition made tasks that weren't independently actionable — use a coarser `--decompose-level`, not more retries |
|
|
35
|
+
| `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 |
|
|
36
|
+
| 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 |
|
|
37
|
+
| 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 |
|
|
38
|
+
| `.forge/` turns up in a review | a diff capture is missing `':(exclude).forge'` |
|
|
39
|
+
| memory stays empty | nothing durable was learned — the common, correct case. The ledger still has a row per dispatch |
|
|
40
|
+
| a wrong fact keeps reappearing | `memory.md` is rebuilt from the ledger; delete the ledger rows, not the memory line |
|
|
41
|
+
| dwarves build incompatible interfaces | the seam was never agreed — plan the approach, or use `--planner` |
|
|
42
|
+
| Fractal was installed but a new unattended run uses ordinary execution | installation never activates it; pass `--fractal` explicitly |
|
|
43
|
+
| `Fractal selected but unavailable` | run `forge fractal doctor`, then the suggested managed install; resume retains the selected backend |
|
|
44
|
+
| existing run rejects a new backend, pool or limit | selection and bounds are recorded; create a new run directory for changed settings |
|
|
45
|
+
| Fractal reports completion but Forge has not accepted the work | inspect QA, verdict and verification; execution completion does not establish acceptance |
|
|
46
|
+
| Fractal import stops with `source_drift` | the source changed after its snapshot; preserve both versions and inspect the candidate instead of forcing import |
|
|
47
|
+
| Fractal work was paused or its coordinator exited | inspect `status` and `logs`, then use explicit `resume RUN_ID`; retry starts a new attempt |
|
|
48
|
+
| browser dashboard closed while work is running | execution is independent; reopen with `forge fractal open RUN_ID` |
|
|
49
|
+
| costs are unknown or a dollar cap is rejected | the bridge only reports available usage; v1 cannot enforce a spending cap |
|
|
50
|
+
|
|
51
|
+
For per-harness invocation details, effort ladders and known CLI failure modes, see
|
|
52
|
+
[`references/harnesses.md`](../references/harnesses.md). For decomposition mechanics, see
|
|
53
|
+
[`references/decompose.md`](../references/decompose.md). For memory internals, see
|
|
54
|
+
[`references/memory.md`](../references/memory.md).
|
package/forge
CHANGED
|
@@ -21,7 +21,11 @@ usage() {
|
|
|
21
21
|
' fractal opt-in Fractal execution: install, doctor, runs, status, tree, logs, open, report, ...' \
|
|
22
22
|
' jev optional Jev advice: setup, status, doctor, enable, disable, backtest' \
|
|
23
23
|
' dataset every run'"'"'s inputs and outcomes: status, dump, import, export, outcomes, ...' \
|
|
24
|
+
' issues file the review findings a run left open as GitHub issues (plain gh)' \
|
|
24
25
|
' ui forge-ui, the run viewer: open [DIR], status, stop' \
|
|
26
|
+
' runs this repo'"'"'s recent runs in the terminal: state, verdicts and run dirs' \
|
|
27
|
+
' control a plan'"'"'s live controls: show, set (QA threshold, retries, timeouts), pin, unpin' \
|
|
28
|
+
' config every config source: show, and set/unset the per-repo .forge files and Jev on/off' \
|
|
25
29
|
' update fast-forward this checkout and refresh every harness installation' \
|
|
26
30
|
' version the installed version, and whether a newer release is out' \
|
|
27
31
|
'' \
|
|
@@ -46,10 +50,26 @@ case "${1:-}" in
|
|
|
46
50
|
shift
|
|
47
51
|
exec python3 "$ROOT/scripts/forge-dataset.py" "$@"
|
|
48
52
|
;;
|
|
53
|
+
issues)
|
|
54
|
+
shift
|
|
55
|
+
exec python3 "$ROOT/scripts/forge-issues.py" "$@"
|
|
56
|
+
;;
|
|
49
57
|
ui)
|
|
50
58
|
shift
|
|
51
59
|
exec python3 "$ROOT/scripts/forge-ui.py" "$@"
|
|
52
60
|
;;
|
|
61
|
+
runs)
|
|
62
|
+
shift
|
|
63
|
+
exec python3 "$ROOT/scripts/forge-runs.py" "$@"
|
|
64
|
+
;;
|
|
65
|
+
control)
|
|
66
|
+
shift
|
|
67
|
+
exec python3 "$ROOT/scripts/forge-control.py" "$@"
|
|
68
|
+
;;
|
|
69
|
+
config)
|
|
70
|
+
shift
|
|
71
|
+
exec python3 "$ROOT/scripts/forge-config.py" "$@"
|
|
72
|
+
;;
|
|
53
73
|
update)
|
|
54
74
|
shift
|
|
55
75
|
exec /bin/bash "$ROOT/scripts/forge-update.sh" "$@"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giamme/forge",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Multi-model coding with a reviewer that reads the code: plan, build in parallel worktrees, review every real diff, merge only what passes.",
|
|
5
5
|
"keywords": ["claude-code", "codex", "opencode", "agent-skill", "code-review", "multi-model"],
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Giamme",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"VERSION",
|
|
15
15
|
"CHANGELOG.md",
|
|
16
16
|
"README.md",
|
|
17
|
+
"docs/",
|
|
17
18
|
"LICENSE",
|
|
18
19
|
"registry.tsv",
|
|
19
20
|
"references/",
|
package/references/dataset.md
CHANGED
|
@@ -35,7 +35,9 @@ time order: an attempt that PASSed, then a merge that CONFLICTed, then a human `
|
|
|
35
35
|
| `run_end` | `run` | results per task, the accepted integration commit, `memory.md` |
|
|
36
36
|
| `invocation` | user-facing `forge-parallel.sh` subcommand | argv, exit, duration, plus final review, accept, integrate and split/combine payloads |
|
|
37
37
|
| `event` | point event | `merge` (MERGED, CONFLICT with the files, INVALIDATED and why), `integrate_advanced`, `integrate_conflict`, `combine_conflict`, `sched_terminal`, `subset_run` (every outcome), `memory_record`, `memory_prune` |
|
|
38
|
-
| `event` `escalation` |
|
|
38
|
+
| `event` `escalation` | retry that changes the model | the model a retry escalated from and to, `after_attempt`, and `source` (`policy`, `retry` for an explicit `--dwarf`, or `pin`), recorded at the rewrite, before the retried attempt begins |
|
|
39
|
+
| `event` `control` | `forge control` change | a human's live change: `at`, `actor` (`cli`/`ui`), `action` (`set`, `pin`, `unpin`), `key`, `value`, `previous`; task events carry the task id. Also read back from `control.log` by `import` |
|
|
40
|
+
| `event` `run_delete_started` / `run_deleted` | `forge runs rm` | written strictly around a run's deletion: `path`, `run_id`, `archive`, and for `run_deleted` the bytes freed |
|
|
39
41
|
| `event` `shadow_review` | shadow review | how an opt-in second review ended (`done`, `invalid`, `overtime`, `orphaned`, ...), with the reviewed commit and both specs. Its dispatch is recorded with role `qa_shadow` |
|
|
40
42
|
| `jev` | Jev request | site, answers, usage, latency, mode. Blob: the **full request** (state and questions). Its original sha is `jev.jsonl`'s `key` |
|
|
41
43
|
| `fractal_step` / `fractal_node` | Fractal step, node state | decomposition decision, iteration, resolved model, cost, tokens, candidate diff |
|
|
@@ -297,6 +299,21 @@ so the importer will not guess which execution wrote a mutable file:
|
|
|
297
299
|
- Archives are extracted to a temp dir and mapped back to their original path. The legacy
|
|
298
300
|
run id hashes that original path, so the archive and the original dir agree on it.
|
|
299
301
|
|
|
302
|
+
- **Import is strict when a caller must know.** `importer.import_run` is strict by default and
|
|
303
|
+
`forge runs rm` relies on it: it stores through `record_strict`, which raises (and the deletion
|
|
304
|
+
stops) if any event, blob included, was not stored, or any part of the run could not be read,
|
|
305
|
+
instead of treating an unreadable file as absent. Live capture stays fail-open. Interventions import by content, not by time of capture:
|
|
306
|
+
`tasks/<id>/escalations.tsv` (four or five columns; a missing `source` reads as `policy`) and
|
|
307
|
+
`control.log` produce the same event ids as live capture, so a re-import fills gaps without
|
|
308
|
+
duplicating. A line that does not parse is reported as failed.
|
|
309
|
+
- **Archives describe themselves.** `archive_run` writes `FORGE_ARCHIVE.json` first
|
|
310
|
+
(`format`, `original_path`, `run_id`, `dataset_uid`, `created`), then `FORGE_ARCHIVE_EXCLUDED.txt`, then
|
|
311
|
+
the run dir under its basename. Symlinks, files over 50 MB, `review-*` snapshots and what the
|
|
312
|
+
importer never reads are left out and listed in the excluded file. Importing such an archive maps
|
|
313
|
+
it back to `original_path`, so its run id and uid match the original dir's; an unreadable path
|
|
314
|
+
during archiving is an error, not a silent omission. The archive is written to a temp name, fsynced
|
|
315
|
+
and renamed.
|
|
316
|
+
|
|
300
317
|
`~/.local/share/forge/legacy/` holds archives of run dirs taken before live capture, with a
|
|
301
318
|
`MANIFEST.tsv`. Passing that dir to `import` reads every archive, plus the ledger copies
|
|
302
319
|
listed in `ledgers/REPOS.tsv`.
|
package/references/decompose.md
CHANGED
|
@@ -390,7 +390,7 @@ realistic input or a broken existing contract; P2 edge-case bug, unusual-timing
|
|
|
390
390
|
hardening; P3 minor. Severity is impact; CONFIRMED/PLAUSIBLE is confidence.
|
|
391
391
|
|
|
392
392
|
`run`/`retry --qa-threshold Pn` (remembered in `<plan>/qa_threshold`; `none` clears it) sets
|
|
393
|
-
the **least severe level that still blocks**. `tasks/<id>/qa_threshold` overrides it per task —
|
|
393
|
+
the **least severe level that still blocks**. `tasks/<id>/qa_threshold` overrides it per task (the value `none` exempts the task from a plan threshold; `forge control set ... qa-threshold` writes both and takes effect at the next attempt) —
|
|
394
394
|
the way to give a dev-tooling or investigation task a looser bar than production code,
|
|
395
395
|
which otherwise gets a new finding in a new corner of a non-default option every round and
|
|
396
396
|
only a human decision ends it. The orchestrator decides *deterministically* (`forge-contract.py gate`),
|
|
@@ -470,6 +470,24 @@ turns it off):
|
|
|
470
470
|
History cannot yet say whether escalating rescues a task (31% of the 29 later attempts on a
|
|
471
471
|
changed spec passed, mostly effort bumps); the attempts this policy makes are what will.
|
|
472
472
|
|
|
473
|
+
**Live controls.** `forge control show|set|pin|unpin <plan>` changes a run while it works:
|
|
474
|
+
`qa-threshold` (read once per attempt, so an attempt keeps the bar it started with; per task, `none`
|
|
475
|
+
exempts it and `inherit` drops the override), `timeout`, `escalate-after`, `verify-retries`, and,
|
|
476
|
+
only while a scheduler is live, `retry-budget` (plan or `--task`) and `infra-retries`. The budgets
|
|
477
|
+
keep the **per invocation** rule: the flags of `run` are not remembered, and a live scheduler only
|
|
478
|
+
mirrors them into `retry_budget`, `infra_budget` and `tasks/<id>/retry_budget` (re-read at every
|
|
479
|
+
decision, removed when it stops; `tasks/<id>/retries.used` stays), so a change dies with the
|
|
480
|
+
run. `pin <task> <spec>` sets the model the task's next full retry uses, once (checked with
|
|
481
|
+
`forge-dispatch.sh doctor`, no quota): `prepare_retry` claims `escalation.pin` as
|
|
482
|
+
`escalation.pin.applying`, rewrites `tasks.tsv`, appends the `escalations.tsv` row with
|
|
483
|
+
`source=pin`, clears `escalation.stop`/`split.suggested`, then consumes the pin. A QA-only resume
|
|
484
|
+
leaves it for the next full retry; Fractal plans never apply pins; a MERGED task refuses one.
|
|
485
|
+
The last column of `escalations.tsv` says who decided: `policy`, `retry` (an explicit
|
|
486
|
+
`--dwarf`) or `pin`; four-column rows read as `policy`. Every change goes to `control.log` (JSON lines:
|
|
487
|
+
`at`, `actor` `cli|ui`, `action`, `task`, `key`, `value`, `previous`) and to the dataset as a
|
|
488
|
+
`control` event. Exit 2 invalid input, 3 refused (not live, MERGED, Fractal, failing doctor,
|
|
489
|
+
lock busy over 5 s). Report them; never set one yourself unless asked.
|
|
490
|
+
|
|
473
491
|
**Ordinary execution resume:** run `run` again. Tasks already `MERGED` are skipped. A saved PASS is fingerprint-checked and merged without
|
|
474
492
|
redispatch. PENDING, `INFRA` (paused by an infrastructure failure) and newly unblocked tasks run
|
|
475
493
|
when eligible. Failed or interrupted tasks
|
|
@@ -521,7 +539,7 @@ its last message, and to say plainly what it could not run.
|
|
|
521
539
|
## Accepting, splitting, combining and the whole-run review
|
|
522
540
|
|
|
523
541
|
Four subcommands act on plans after tasks have run. Full usage and refusal conditions are in the
|
|
524
|
-
[README](../
|
|
542
|
+
[README](../docs/reviews.md#accepting-a-task-despite-its-reviewer); the mechanics:
|
|
525
543
|
|
|
526
544
|
- **`accept <plan> <task> --reason … --approved`** overrides a `FAIL`/`UNKNOWN` reviewer only
|
|
527
545
|
when the reviewed commit is still the branch tip and the worktree still matches
|
package/references/fractal.md
CHANGED
|
@@ -5,7 +5,7 @@ model routing, independent QA of the cumulative diff, verification and integrati
|
|
|
5
5
|
Fractal execution completion and Forge acceptance are separate outcomes. Costs are
|
|
6
6
|
observational, and this integration makes no speed or cost-saving claim.
|
|
7
7
|
|
|
8
|
-
For a first run, follow the [
|
|
8
|
+
For a first run, follow the [Fractal guide](../docs/fractal.md).
|
|
9
9
|
This reference covers the exact selection, routing, lifecycle and inspection contracts.
|
|
10
10
|
|
|
11
11
|
## Contents
|
package/references/harnesses.md
CHANGED
|
@@ -46,8 +46,8 @@ disables), else the optional `timeout` column of the model's registry row, else
|
|
|
46
46
|
|
|
47
47
|
**Infrastructure failures exit `8`.** After the backend exits, `forge-runtime.py classify` reads
|
|
48
48
|
the dispatch log (never `<role>.last`, which is only created afterwards): claude/openclaude/grok
|
|
49
|
-
`result` events with `is_error`, codex `turn.failed`/`error` events that
|
|
50
|
-
and for the raw-log
|
|
49
|
+
`result` events with `is_error`, codex `turn.failed`/`error` and opencode `error` events that no
|
|
50
|
+
answer followed, and for the raw-log harness (antigravity) the last 2 KB when the exit was nonzero —
|
|
51
51
|
looking for strong phrases (usage/session limit, quota, insufficient balance/credit, rate limit,
|
|
52
52
|
429/529, unauthorized/401/403, invalid API key, not logged in, ECONNRESET/ENOTFOUND/…). An
|
|
53
53
|
`empty` answer (exit 0, nothing said) is the fifth class. codex's non-fatal reconnect notices
|
|
@@ -90,8 +90,9 @@ reason forge validates efforts itself before dispatch.
|
|
|
90
90
|
|
|
91
91
|
**Prompt delivery uses stdin.** Forge copies the input once (including FIFOs), checks it
|
|
92
92
|
with a streaming whitespace character class, and runs `codex exec ... - < role.prompt`.
|
|
93
|
-
Native review has no custom prompt and uses `/dev/null`. Codex JSONL events and
|
|
94
|
-
`--output-format json` provide native usage; final responses
|
|
93
|
+
Native review has no custom prompt and uses `/dev/null`. Codex JSONL events, Claude and openclaude
|
|
94
|
+
`--output-format stream-json` and opencode `--format json` provide native usage; final responses
|
|
95
|
+
remain in `<role>.last`.
|
|
95
96
|
See the official [Codex non-interactive guide](https://learn.chatgpt.com/docs/non-interactive-mode)
|
|
96
97
|
and [Claude headless guide](https://code.claude.com/docs/en/headless).
|
|
97
98
|
|
|
@@ -109,12 +110,18 @@ and `--approve-for-me` additionally conflicts with an explicit `-s/--sandbox`.
|
|
|
109
110
|
## claude / openclaude
|
|
110
111
|
|
|
111
112
|
```
|
|
112
|
-
<harness> -p --model <model> --add-dir <run-dir> --effort <effort> \
|
|
113
|
+
<harness> -p --model <model> --add-dir <run-dir> --output-format stream-json --verbose --effort <effort> \
|
|
113
114
|
(--permission-mode acceptEdits --allowedTools Bash | --dangerously-skip-permissions)
|
|
114
115
|
[--disallowed-tools "Edit,Write,NotebookEdit"]
|
|
115
116
|
```
|
|
116
117
|
with the prompt delivered on **stdin**.
|
|
117
118
|
|
|
119
|
+
**The log is a stream.** `--output-format stream-json` (which `-p` accepts only with `--verbose`)
|
|
120
|
+
writes one JSON message per line: the session, every assistant message and tool call, every tool
|
|
121
|
+
result, and a closing `result` line with the final answer and the token counts, which is what
|
|
122
|
+
forge reads into `<role>.last` and the metrics. The rest is the run's conversation, which forge-ui
|
|
123
|
+
shows. Hook and rate-limit events in the stream are ignored.
|
|
124
|
+
|
|
118
125
|
**The prompt must not go on argv when a variadic flag precedes it.** `--disallowed-tools`,
|
|
119
126
|
`--allowed-tools` and friends are variadic, so a following positional is consumed as
|
|
120
127
|
another value. In practice the prompt is parsed word by word into tool names — you get a
|
|
@@ -162,7 +169,7 @@ Claude-Code-shaped harness. That is the point of `sol:xhigh:openclaude`.
|
|
|
162
169
|
## opencode
|
|
163
170
|
|
|
164
171
|
```
|
|
165
|
-
opencode run -m <provider>/<model> --dir <repo> --variant <effort> --auto [--agent plan] "<prompt>"
|
|
172
|
+
opencode run -m <provider>/<model> --dir <repo> --variant <effort> --auto [--agent plan] --format json "<prompt>"
|
|
166
173
|
```
|
|
167
174
|
|
|
168
175
|
Models are always `provider/model`; `opencode models` lists what is configured.
|
|
@@ -195,6 +202,11 @@ agy --model <model> --print-timeout <t> [--effort <e>] --add-dir <repo> --add-di
|
|
|
195
202
|
The harness is named `antigravity` in forge; the binary is `agy`. This is how Gemini is
|
|
196
203
|
reached.
|
|
197
204
|
|
|
205
|
+
**Its log stays plain text.** agy has `--output-format stream-json`, but only its closing event
|
|
206
|
+
has been seen (`{"event": "result", "result": {"status", "response", "error", "usage", ...}}`, on a
|
|
207
|
+
lapsed login), not the stream of a working run, and the final answer would come from it. Until
|
|
208
|
+
then forge keeps agy's printed output, which is both its log and its answer.
|
|
209
|
+
|
|
198
210
|
**`--print` must use the attached `=` form.** It is a Go flag with an optional value, so
|
|
199
211
|
written detached it consumes whatever token follows as its prompt. `agy --print
|
|
200
212
|
--print-timeout 120s --model X "real prompt"` fails with *"--print took --print-timeout as
|
package/references/solo.md
CHANGED
|
@@ -48,7 +48,7 @@ nothing was judged — fix the cause and run again; a dwarf that returns nothing
|
|
|
48
48
|
nothing is reported as 8, not 5). The default dispatch timeout is 2700 seconds, scaled up for
|
|
49
49
|
`xhigh`/`max` effort (so the default QA/planner effort gets 3600), overridable per model in the
|
|
50
50
|
registry; 0 disables that dispatch timeout, but an enabled Fractal task still has its own attempt
|
|
51
|
-
deadline. See [dispatch timeouts](../
|
|
51
|
+
deadline. See [dispatch timeouts](../docs/commands.md#scriptsforge-dispatchsh).
|
|
52
52
|
|
|
53
53
|
The dwarf prompt carries implementer rules (no commits, no background processes, finish every
|
|
54
54
|
measurement before the last message). forge records what it observes: if the dwarf committed
|
package/references/ui.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# forge-ui
|
|
2
2
|
|
|
3
|
-
forge-ui is a
|
|
3
|
+
forge-ui is a local web page that shows a forge run live and can change it: a decomposed plan or a
|
|
4
4
|
solo run. It is opt-in per run. The orchestrator asks "Open forge-ui to watch this run?
|
|
5
5
|
[y/N]" once at the start of every new interactive run and passes the answer as `--ui` or
|
|
6
6
|
`--no-ui`. Nothing about the run depends on it: a UI that cannot start prints a note and the
|
|
@@ -23,6 +23,15 @@ run goes on, with the same exit code.
|
|
|
23
23
|
review's combined diff, with every P0–P3 finding shown under the line it names.
|
|
24
24
|
- **Fractal child tasks:** the tree a Fractal task was split into, in the inspector and above
|
|
25
25
|
the opened task in 3D, each node with its decision, cost, output and candidate diff.
|
|
26
|
+
- **Controls:** change a live plan from its inspector: QA threshold, timeout, escalate-after,
|
|
27
|
+
verify retries, and (while a scheduler is live) the retry and infra-retry budgets; per task, a
|
|
28
|
+
threshold override (`None` exempts it), a retry budget and a one-shot model pin. Each is one
|
|
29
|
+
`forge control` call; the change is confirmed first and logged in `control.log`.
|
|
30
|
+
- **Open findings and issues:** the findings a run (or all of its repository's runs) left open,
|
|
31
|
+
filed as GitHub issues or dismissed through `forge issues`.
|
|
32
|
+
- **Delete a run:** a dry run, then `forge runs rm`: the dataset keeps the traces, an archive is
|
|
33
|
+
written unless declined, and clean worktrees and unchecked-out branches can go with it.
|
|
34
|
+
- **Settings:** `forge config show` in one view, with edits for the per-repo files and Jev.
|
|
26
35
|
- **Export a run:** one HTML file to share that opens without forge-ui; prompts, diffs, reports
|
|
27
36
|
and local paths stay out unless chosen.
|
|
28
37
|
- **Quick jump and notifications:** Ctrl K (⌘K) or `/` searches every run, task, project and
|
|
@@ -96,10 +105,23 @@ how the map shows all of a repo's runs. `forge-parallel.sh run` also records the
|
|
|
96
105
|
|
|
97
106
|
## Privacy
|
|
98
107
|
|
|
99
|
-
The server binds only `127.0.0.1
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
The server binds only `127.0.0.1`. **Reading needs no token**: any process on the machine,
|
|
109
|
+
under any user who can reach loopback, can read every run it knows, including prompts, diffs and
|
|
110
|
+
review text from private repos. A running server keeps serving them, including runs that were
|
|
111
|
+
later rerun with `--no-ui`.
|
|
112
|
+
|
|
113
|
+
**Changing needs one.** The server never writes a run file, a repo or a config itself: every
|
|
114
|
+
change is a call of forge's own CLI (`forge control`, `forge issues`, `forge runs rm`,
|
|
115
|
+
`forge config`), so validation, locks and the audit log are the CLI's. Each server start makes a
|
|
116
|
+
random token and puts it in the page it serves; every change must send it back, from a `Host` of
|
|
117
|
+
`127.0.0.1` or `localhost` and with an `Origin` equal to that host when the browser sends one, or
|
|
118
|
+
it is refused before any CLI runs. There are no CORS headers, so another site in your browser
|
|
119
|
+
can neither send nor read a change. Any process that can fetch the page can read the token, as
|
|
120
|
+
it can read the runs, so on a shared machine start the server **read-only**: `--read-only`
|
|
121
|
+
(`python3 -m forge_ui`) or `FORGE_UI_READONLY=1` (also for `forge ui open`). A read-only server
|
|
122
|
+
issues no token and answers every change with 405; its `/api/health` says `"controls": false`.
|
|
123
|
+
`forge ui open` never reuses a server in the other mode: it replaces it, as it does one running
|
|
124
|
+
old code, and `forge ui status` shows the mode.
|
|
103
125
|
|
|
104
126
|
Every run is registered, so a running server shows every run on the machine, not only those
|
|
105
127
|
opened with `--ui`. The 3D view fetches three.js from cdn.jsdelivr.net (no run data is sent;
|