@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
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Project memory
|
|
2
|
+
|
|
3
|
+
How forge remembers what it learned about a repository across runs: the `FORGE_LEARNING`
|
|
4
|
+
grammar, what gets promoted into a prompt and what only reaches the ledger, and how memory
|
|
5
|
+
stays small and true as the code moves under it.
|
|
6
|
+
|
|
7
|
+
Read this when memory looks empty, looks wrong, or has grown past its budget.
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
- [Why two files](#why-two-files)
|
|
12
|
+
- [Committing, ignoring, disabling](#committing-ignoring-disabling)
|
|
13
|
+
- [The sentinel](#the-sentinel)
|
|
14
|
+
- [Promotion](#promotion)
|
|
15
|
+
- [Dedup, and the limits of it](#dedup-and-the-limits-of-it)
|
|
16
|
+
- [Staleness and the cap](#staleness-and-the-cap)
|
|
17
|
+
- [Injection slices](#injection-slices)
|
|
18
|
+
- [The diff exclusion](#the-diff-exclusion)
|
|
19
|
+
- [Spend accounting](#spend-accounting)
|
|
20
|
+
- [Commands](#commands)
|
|
21
|
+
- [Failure modes](#failure-modes)
|
|
22
|
+
|
|
23
|
+
## Why two files
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
.forge/memory.md curated, hard-capped, injected into every prompt
|
|
27
|
+
.forge/ledger.tsv append-only, one row per dispatch, NEVER injected
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Injected context is paid for on every dispatch of every future run — with a decomposed run
|
|
31
|
+
that is 2N prompts, forever. So the two things a memory has to do pull in opposite
|
|
32
|
+
directions: remembering everything makes it big, and staying small means forgetting.
|
|
33
|
+
|
|
34
|
+
Splitting them resolves that. The ledger is unbounded and costs nothing, because nothing
|
|
35
|
+
reads it unless asked; it is what recurrence is counted from and what answers "has this been
|
|
36
|
+
tried here before". Only `memory.md` is in prompts, and it is capped hard.
|
|
37
|
+
|
|
38
|
+
Never inline the ledger into a prompt. That would undo the whole arrangement.
|
|
39
|
+
|
|
40
|
+
Both live in the repo so the memory travels with it, and both are written by forge but
|
|
41
|
+
committed by the user — forge does not commit on anyone's behalf.
|
|
42
|
+
|
|
43
|
+
## Committing, ignoring, disabling
|
|
44
|
+
|
|
45
|
+
`.forge/` appears untracked after the first run. Three coherent choices:
|
|
46
|
+
|
|
47
|
+
| Intent | Action |
|
|
48
|
+
|---|---|
|
|
49
|
+
| share memory with the team | commit `.forge/` |
|
|
50
|
+
| keep it to this machine | `echo '.forge/' >> .gitignore` |
|
|
51
|
+
| no memory at all | `--no-memory` or `FORGE_MEMORY=off` |
|
|
52
|
+
|
|
53
|
+
Gitignoring does **not** disable anything: an ignored `.forge/` is still written and still
|
|
54
|
+
injected into every prompt. Only the third row turns the feature off.
|
|
55
|
+
|
|
56
|
+
**The two files must share a fate.** Because `memory.md` is rebuilt from `ledger.tsv` on
|
|
57
|
+
every write, committing the memory while ignoring the ledger destroys the shared facts the
|
|
58
|
+
first time anyone runs forge on a fresh clone — the rebuild regenerates `memory.md` from that
|
|
59
|
+
machine's own short ledger, and nothing warns:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
A commits memory.md only: - pytest -q runs the suite
|
|
63
|
+
- src/a.py is generated [src/a.py]
|
|
64
|
+
B clones, runs forge once: - b learned something new <- A's facts gone
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Committing both means `ledger.tsv` grows a line per dispatch and shows up in diffs. That is
|
|
68
|
+
the price of the recurrence counting; the alternative is memory that cannot tell a one-off
|
|
69
|
+
from a pattern.
|
|
70
|
+
|
|
71
|
+
For a repo you contribute to but do not own, ignore it — a PR carrying forge's notes to
|
|
72
|
+
itself is noise to a reviewer who does not use forge.
|
|
73
|
+
|
|
74
|
+
## The sentinel
|
|
75
|
+
|
|
76
|
+
Extraction happens in-band, in the final message of the role that just did the work:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
FORGE_LEARNING: verify | pytest -q runs the suite; make test also lints and is 4x slower
|
|
80
|
+
FORGE_LEARNING: trap | test_api.py::test_timeout is flaky under -n auto [tests/test_api.py]
|
|
81
|
+
FORGE_LEARNING: finding | route added without updating openapi.yaml [src/routes]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`<category> | <text> [optional anchor]`. Categories:
|
|
85
|
+
|
|
86
|
+
| Category | Is |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `verify` | how this project is actually built, tested or checked |
|
|
89
|
+
| `trap` | a landmine: a flaky test, a generated file, a sharp edge |
|
|
90
|
+
| `finding` | a *mistake pattern* likely to recur — emitted by qa, not a description of one bug |
|
|
91
|
+
|
|
92
|
+
This costs no extra dispatch, which is the reason it works at all: a separate "librarian"
|
|
93
|
+
model would have to re-derive from a transcript what the working model already knew. The
|
|
94
|
+
role that hit the landmine is the one that writes it down.
|
|
95
|
+
|
|
96
|
+
`forge-memory.sh note <role>` produces the instruction. It is deliberately biased toward
|
|
97
|
+
silence — "most runs teach nothing durable; emitting no line is the normal, correct
|
|
98
|
+
outcome" — because a false fact is worse than a missing one. A missing fact costs one
|
|
99
|
+
rediscovery; a false one is injected into every future prompt until someone notices.
|
|
100
|
+
Measured behaviour matches: on a trivial task with nothing to learn, models emit nothing;
|
|
101
|
+
on a task with a real trap, they emit it.
|
|
102
|
+
|
|
103
|
+
## Promotion
|
|
104
|
+
|
|
105
|
+
| Category | Enters `memory.md` |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `verify`, `trap` | on first sighting — these are facts, true the first time |
|
|
108
|
+
| `finding` | only after **two distinct runs** |
|
|
109
|
+
|
|
110
|
+
Everything reaches the ledger immediately regardless; promotion governs only what gets
|
|
111
|
+
injected. The threshold is the main guard against a junk drawer: one QA noticing one bug is
|
|
112
|
+
an incident, and putting it in every future prompt would be noise. Two separate runs finding
|
|
113
|
+
the same thing is a pattern worth telling the next dwarf about.
|
|
114
|
+
|
|
115
|
+
Recurrence counts **distinct runs**, not lines — one run emitting the same learning twice is
|
|
116
|
+
still one occurrence. Promoted findings display their count (`3x:`), which is also how a
|
|
117
|
+
human spots a systemic problem worth fixing properly.
|
|
118
|
+
|
|
119
|
+
## Dedup, and the limits of it
|
|
120
|
+
|
|
121
|
+
The key is the text lowercased with every run of non-alphanumerics collapsed to one space.
|
|
122
|
+
So `Route added without updating openapi.yaml` and `route added, without updating
|
|
123
|
+
openapi.yaml!` are the same key; a genuinely different phrasing is not.
|
|
124
|
+
|
|
125
|
+
This is exact matching, not fuzzy, and pretending otherwise would be worse than stating it.
|
|
126
|
+
What makes it work in practice is a feedback loop rather than cleverness: memory is *in the
|
|
127
|
+
prompt*, and `note` asks explicitly to reuse the wording of anything already listed. A
|
|
128
|
+
reviewer that can see `2x: route added without updating openapi.yaml` tends to re-emit that
|
|
129
|
+
line rather than invent a synonym, so repeats converge on one phrasing.
|
|
130
|
+
|
|
131
|
+
`memory.md` is rebuilt from the whole ledger on every write rather than edited in place.
|
|
132
|
+
Rebuilding is idempotent and order-independent, so a crashed or concurrent run cannot leave
|
|
133
|
+
memory half-updated — at worst the ledger is one row short.
|
|
134
|
+
|
|
135
|
+
## Staleness and the cap
|
|
136
|
+
|
|
137
|
+
An entry ending in `[path]` is anchored. When that path stops existing the entry is dropped:
|
|
138
|
+
the code it describes is gone, so the fact is at best useless and at worst actively
|
|
139
|
+
misleading. This is what keeps memory true as the codebase moves, and it is why `note` asks
|
|
140
|
+
for an anchor wherever one makes sense.
|
|
141
|
+
|
|
142
|
+
Caps are 40 lines and 4 KB (`FORGE_MEMORY_MAX_LINES`, `FORGE_MEMORY_MAX_BYTES`). Over cap,
|
|
143
|
+
entries are trimmed from the bottom — lowest recurrence count first, then least recent.
|
|
144
|
+
A single entry is clamped to 180 characters (`FORGE_MEMORY_MAX_FACT_CHARS`), truncated at a
|
|
145
|
+
word boundary with its anchor preserved, so one discursive model cannot spend the whole
|
|
146
|
+
budget on one line.
|
|
147
|
+
|
|
148
|
+
## Injection slices
|
|
149
|
+
|
|
150
|
+
| Role | Gets |
|
|
151
|
+
|---|---|
|
|
152
|
+
| planner | Verify + Known traps + Recurring findings |
|
|
153
|
+
| dwarf | Verify + Known traps + Recurring findings |
|
|
154
|
+
| qa | Known traps + Recurring findings |
|
|
155
|
+
|
|
156
|
+
QA is not told the build command because it is not building anything. It *is* told the
|
|
157
|
+
recurring findings, so it can notice this is the fourth time the same bug shipped, and the
|
|
158
|
+
traps, so it does not report a known-generated file as a defect.
|
|
159
|
+
|
|
160
|
+
## The diff exclusion
|
|
161
|
+
|
|
162
|
+
`.forge/` must be excluded from every diff capture:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
git diff -- . ':(exclude).forge'
|
|
166
|
+
git add -A -- . ':(exclude).forge' && git diff --cached -- . ':(exclude).forge'
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Two distinct failures otherwise, both quiet:
|
|
170
|
+
|
|
171
|
+
- memory is rewritten in the working tree at the end of every run, so the *next* run's
|
|
172
|
+
capture hands it to qa as if a dwarf had written it — an unrelated file in the diff, which
|
|
173
|
+
a good reviewer will correctly flag as scope creep;
|
|
174
|
+
- if `.forge/` ever entered a task's `files`, every task would overlap every other task and
|
|
175
|
+
the wave planner would serialize the entire run while reporting it as deferrals.
|
|
176
|
+
|
|
177
|
+
## Spend accounting
|
|
178
|
+
|
|
179
|
+
`forge-dispatch.sh` times every dispatch and `record` stores it, so the ledger answers what
|
|
180
|
+
forge has cost this repo without any new bookkeeping and without a byte of prompt context:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
forge-memory.sh spend <repo> [--run <run-id>]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Wall-clock and dispatch counts grouped by model and by role — **not** tokens and not money.
|
|
187
|
+
The five CLIs expose usage differently or not at all, so a token number would be a number
|
|
188
|
+
forge cannot actually measure, which is worse than none.
|
|
189
|
+
|
|
190
|
+
Two things the output states rather than leaves the reader to work out: durations *sum*
|
|
191
|
+
concurrent work, so a `--max-parallel 3` run reports more model time than it took in
|
|
192
|
+
wall-clock; and dispatches recorded before `duration_s` existed are counted but not timed.
|
|
193
|
+
|
|
194
|
+
`duration_s` is the ledger's tenth column, appended at the end on purpose. A ledger written
|
|
195
|
+
by an older forge — and possibly already committed and shared — has nine fields, and awk
|
|
196
|
+
yields `""` for the missing one instead of shifting every column, so old rows stay readable.
|
|
197
|
+
|
|
198
|
+
Counting is by dispatch, not by row: one dispatch that emits two learnings writes two rows,
|
|
199
|
+
and every row from a single dispatch shares its timestamp, which is what the counter keys on.
|
|
200
|
+
|
|
201
|
+
## Commands
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
forge-memory.sh inject <repo> <planner|dwarf|qa> # block to prepend; empty if no memory
|
|
205
|
+
forge-memory.sh note <planner|dwarf|qa> # the instruction to append
|
|
206
|
+
forge-memory.sh record <repo> --last <file> --role <r> [--run-id X] [--task T]
|
|
207
|
+
[--model M] [--verdict V] [--duration S]
|
|
208
|
+
forge-memory.sh show <repo>
|
|
209
|
+
forge-memory.sh prune <repo> # re-apply staleness and cap now
|
|
210
|
+
forge-memory.sh spend <repo> [--run <run-id>] # time and dispatch counts
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`--no-memory`, or `FORGE_MEMORY=off` in the environment, disables all of it: nothing is
|
|
214
|
+
injected, nothing is recorded, and no `.forge/` is created.
|
|
215
|
+
|
|
216
|
+
## Failure modes
|
|
217
|
+
|
|
218
|
+
| Symptom | Cause |
|
|
219
|
+
|---|---|
|
|
220
|
+
| memory stays empty | nothing durable was learned — the common and correct case. Check the ledger: rows with `-` categories mean dispatches ran and emitted nothing |
|
|
221
|
+
| a fact you wanted is missing | it was a `finding` seen once; it is in the ledger and will promote on its second run |
|
|
222
|
+
| the same fact appears twice, worded differently | dedup is exact-after-normalization; delete the weaker line from `memory.md`, or let the cap evict it |
|
|
223
|
+
| a fact is wrong and keeps coming back | it is in the ledger. Editing `memory.md` alone will not hold — the next rebuild regenerates from the ledger; remove the ledger rows |
|
|
224
|
+
| memory vanished after a refactor | its entries were anchored to paths that no longer exist and were dropped as stale. Working as intended |
|
|
225
|
+
| `.forge/` shows up in a review | a diff capture is missing the exclude pathspec |
|
|
226
|
+
| memory in a worktree is stale | inject reads the **main repo**, not the worktree; on a repo's first run `.forge/` is not committed, so a worktree branched from the base commit does not have it |
|
|
227
|
+
| a teammate's clone lost every shared fact | `memory.md` was committed but `ledger.tsv` was not; the first `record` there rebuilt memory from an empty ledger. Commit both, or neither |
|
|
228
|
+
| `.forge/` keeps appearing in `git status` | expected — forge never commits it. Commit it to share, or gitignore it to keep it local |
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
## Planning
|
|
2
|
+
|
|
3
|
+
Fractal selection applies to the later implementation stage. It does not turn a planner
|
|
4
|
+
dispatch or `forge-parallel.sh plan` into nested execution, install a runtime during
|
|
5
|
+
planning, or bypass the task/model approval table. Pass `--fractal` or `--no-fractal` to
|
|
6
|
+
the eventual solo runner or decomposed `run`. See [Fractal](fractal.md).
|
|
7
|
+
|
|
8
|
+
For automatic recursive decisions during execution, use `--fractal-auto-decompose`.
|
|
9
|
+
Its `--fractal-planner` selects the per-node decision model, independently of the
|
|
10
|
+
top-level `--planner` that designs the whole run. Without an override it reuses that
|
|
11
|
+
planner when configured, then each task's initial root model. Child planning must
|
|
12
|
+
preserve the approved interfaces and ownership established here.
|
|
13
|
+
|
|
14
|
+
By default **you** plan: before dispatching, decide the approach and put it in the prompt,
|
|
15
|
+
so the dwarf builds the agreed thing rather than inventing a shape nobody has seen. In a
|
|
16
|
+
decomposed run that matters more, because the capsule prevents two dwarves from touching
|
|
17
|
+
one *file* but nothing stops them inventing incompatible *interfaces* at a seam they share.
|
|
18
|
+
|
|
19
|
+
`--planner <spec>` hands that stage to a named model instead — one dispatch for the whole
|
|
20
|
+
run, never one per task. A single planner is the point: one mind designs both sides of every
|
|
21
|
+
seam, where N independent planners would recreate the problem. Effort defaults to `xhigh`,
|
|
22
|
+
since a bad plan is executed at full price by every dwarf downstream of it.
|
|
23
|
+
|
|
24
|
+
Offer optional installation once before a direct planner dispatch with
|
|
25
|
+
`bash <skill_dir>/scripts/forge-install-ripwire.sh`. Propagate `FORGE_RIPWIRE=off`
|
|
26
|
+
when the user opted out. Supply `--ripwire-query-file` when requirements are separate
|
|
27
|
+
from the assembled planner prompt; see [Ripwire context](ripwire.md).
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bash <skill_dir>/scripts/forge-dispatch.sh planner <spec> \
|
|
31
|
+
--repo "$REPO" --run-dir "$FORGE_RUN" --prompt-file "$FORGE_RUN/planner.prompt"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The planner is instructed to read only and uses QA permission flags. These flags alone do not enforce filesystem immutability.
|
|
35
|
+
Ask it for the approach only: in a decomposed run, `tasks.tsv` rows plus a few lines of
|
|
36
|
+
approach per task; in a single-task run, just the approach. Ask too for tasks small enough to
|
|
37
|
+
pass on a first attempt, each ending its prompt with an `## Acceptance criteria` section of
|
|
38
|
+
checks the reviewer can verify; see [task size](decompose.md#task-size-and-acceptance-criteria). Write each task's approach to
|
|
39
|
+
`tasks/<id>/approach.md`, where the runner picks it up for both the dwarf and qa.
|
|
40
|
+
|
|
41
|
+
Show the approach before dispatching and let the user change it. That gate is the last
|
|
42
|
+
moment the plan is free; after it, changing the plan costs a whole run.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
## Optional Ripwire context
|
|
2
|
+
|
|
3
|
+
Forge automatically uses Ripwire **0.4.0** when found on `PATH`, then in
|
|
4
|
+
`~/.local/bin`. Other versions are preserved but skipped until compatibility is verified.
|
|
5
|
+
All six harnesses use the same preparation step. No provider calls are needed for it.
|
|
6
|
+
|
|
7
|
+
Use `--no-ripwire` on dispatch, solo, or parallel `plan`, `run`, and `retry`, or set
|
|
8
|
+
`export FORGE_RIPWIRE=off` in your shell configuration. Opt-out is resolved before
|
|
9
|
+
probing or offering installation. Parallel plans remember opt-out in `no_ripwire`
|
|
10
|
+
for later resumes and retries; remove that marker to enable it again.
|
|
11
|
+
|
|
12
|
+
Forge setup and solo/parallel run entrypoints offer installation once before agents
|
|
13
|
+
start. The helper shows v0.4.0 and `~/.local/bin/ripwire`, then asks
|
|
14
|
+
“Ripwire is missing. Install it now? [Y/n]”. Enter accepts; no or EOF continues.
|
|
15
|
+
It reads the controlling terminal separately from task stdin. Without a terminal it
|
|
16
|
+
prints the helper command and continues. Dispatch itself never prompts, so background
|
|
17
|
+
workers cannot wait for installation input.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bash <skill_dir>/scripts/forge-install-ripwire.sh # offer installation
|
|
21
|
+
bash <skill_dir>/scripts/forge-install-ripwire.sh --dry-run # preview, no downloads
|
|
22
|
+
bash <skill_dir>/scripts/forge-install-ripwire.sh --yes # explicit unattended consent
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The helper pins the [upstream v0.4.0 installer](https://github.com/redhat-et/ripwire/blob/v0.4.0/scripts/install.sh)
|
|
26
|
+
and its SHA-256, retains upstream archive checksum and binary version checks, and sets
|
|
27
|
+
`RIPWIRE_NO_ACTIVATE=1`. Existing installations are preserved. Failed installation
|
|
28
|
+
reports its cause; Forge runners continue without requiring Ripwire.
|
|
29
|
+
|
|
30
|
+
Planner/dwarf use `--for` in their own repository or worktree. Runners pass the
|
|
31
|
+
requirements via `--ripwire-query-file`; direct dispatch defaults to the original
|
|
32
|
+
prompt. Only this search excerpt is limited to 2,048 characters. QA uses
|
|
33
|
+
`--pr-context=<review-base>` against the disposable review snapshot; direct QA without
|
|
34
|
+
a baseline uses task context. See the [pinned command reference](https://github.com/redhat-et/ripwire/blob/v0.4.0/docs/COMMANDS.md).
|
|
35
|
+
|
|
36
|
+
The full prompt remains intact, followed by one marked advisory section. Requirements,
|
|
37
|
+
ownership, retry findings and the actual diff remain authoritative; suggested tests
|
|
38
|
+
supplement required verification. Each attempt prepares fresh context with `--no-cache`,
|
|
39
|
+
excluding `.forge` and Git internal paths. Temporary preparation files live with the
|
|
40
|
+
external attempt artifacts. Preparation has a shared 30-second deadline, a 4,096 estimated
|
|
41
|
+
token budget and a 16 KiB subprocess output ceiling. Failed, empty, oversized, incompatible
|
|
42
|
+
or timed-out results are skipped with a reason; timeout cleanup kills the process group.
|
|
43
|
+
|
|
44
|
+
Each `attempts/<role>-*/ripwire.json` records the command, binary version, repository,
|
|
45
|
+
baseline, duration, status and delivered section (or skip reason). Dry runs and preflight
|
|
46
|
+
skip preparation. Doctor reports availability without installation. Native Codex review
|
|
47
|
+
skips context because its dispatch cannot accept an additional prompt.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Runtime helper decision
|
|
2
|
+
|
|
3
|
+
The approved optimization plan permits Python 3 standard-library helpers while retaining
|
|
4
|
+
shell entrypoints and all existing model, retry and integration boundaries.
|
|
5
|
+
|
|
6
|
+
A complete Python runner would simplify process ownership, but would also rewrite artifact
|
|
7
|
+
capture, retry, merge, permission and setup behavior. The chosen design keeps those shell
|
|
8
|
+
contracts and adds focused helpers: `forge-prompt.py` assembles context without summarizing
|
|
9
|
+
requirements, `forge-schedule.py` coordinates eligible tasks (including opt-in in-run retries and
|
|
10
|
+
infrastructure pauses), `forge-runtime.py` handles capability caching, attempt telemetry and
|
|
11
|
+
infrastructure-failure classification, `forge-contract.py` owns the shared QA/dwarf prompt
|
|
12
|
+
contract and the severity gate, `forge-guard.py` the implementer guards, and
|
|
13
|
+
`forge-verify-coverage.py` the verify-coverage warnings. `forge-metrics.sh` connects pipeline timing to
|
|
14
|
+
existing shell stages. Python unit tests retain the repository's standard-library unittest
|
|
15
|
+
convention; no new dependency manager or test framework is required.
|
|
16
|
+
|
|
17
|
+
Tasks execute through `_task` and merge through `_merge`. The coordinator pins baselines
|
|
18
|
+
before launching tasks, excludes overlapping active ownership paths, and serializes merges.
|
|
19
|
+
Each merge still checks dependency status, reviewed commit, source fingerprint and accepted
|
|
20
|
+
integration revision. A plan lock spans run/retry/integrate operations. Saved passing work
|
|
21
|
+
can resume without a new dispatch; ordinary failed and interrupted work requires explicit
|
|
22
|
+
retry (or an opt-in `run --retry-failed N`), while tasks paused on an infrastructure failure
|
|
23
|
+
resume on the next `run` and an unjudged review re-runs QA only. Fractal has the additional
|
|
24
|
+
recovery checkpoints described below.
|
|
25
|
+
|
|
26
|
+
Review repositories import self-contained tree object packs and create two commits before
|
|
27
|
+
checking out the final tree. No alternates or source repository access is required afterward.
|
|
28
|
+
Both exact tree comparisons and the existing fingerprint checks remain mandatory.
|
|
29
|
+
|
|
30
|
+
Telemetry is independent of project memory. Dispatch and pipeline attempt records are nested;
|
|
31
|
+
their total durations must not be added together. Unsupported usage remains unknown. See
|
|
32
|
+
[the evaluation](../tests/benchmark-2026-09-07.md) for observed improvements and regressions.
|
|
33
|
+
|
|
34
|
+
## Optional Fractal adapter decision
|
|
35
|
+
|
|
36
|
+
Fractal adds a Python adapter around the existing shell pipeline instead of replacing
|
|
37
|
+
Forge's dispatcher, model registry, QA or integration rules. This keeps provider effort,
|
|
38
|
+
permission and attempt-record behavior in one place. The isolated runtime pins Fractal
|
|
39
|
+
1.2.0 and registers `ForgeAgent` through upstream `agents.py`; no Fractal fork is required.
|
|
40
|
+
Ordinary execution and inspection do not require that runtime to be installed.
|
|
41
|
+
|
|
42
|
+
`forge-fractal-options.sh` records the per-run choice and selects the bridge only when
|
|
43
|
+
enabled. `forge_fractal/selection.py` freezes routing and limits; `execution.py` manages
|
|
44
|
+
bounded children, shared model slots, process ownership and candidate import checks;
|
|
45
|
+
`pipeline.py` preserves Forge checkpoints for resume and explicit retry. Work steps use
|
|
46
|
+
fresh dispatcher invocations rather than provider conversation continuation.
|
|
47
|
+
|
|
48
|
+
Each task separates a Fractal control repository from product snapshot repositories.
|
|
49
|
+
That separation prevents wiki/configuration/ledger initialization from entering the product
|
|
50
|
+
diff. Solo imports check source fingerprints and preserve the user's index. Decomposed
|
|
51
|
+
candidates return to the task's existing pinned review and integration boundary. Explicit
|
|
52
|
+
retry retains prior work and the original Forge QA baseline while recording a new attempt.
|
|
53
|
+
|
|
54
|
+
Runtime and durable run records live under
|
|
55
|
+
`${XDG_STATE_HOME:-$HOME/.local/state}/forge/fractal/`, outside the product repository.
|
|
56
|
+
A single task coordinator and a run-wide invocation limiter bound nested work and QA;
|
|
57
|
+
setup and shutdown count toward the task deadline, explicit pauses do not. Fractal
|
|
58
|
+
execution status, Forge acceptance and repository verification remain separate records.
|
|
59
|
+
|
|
60
|
+
`inspection.py` reads curated managed artifacts and the pinned SQLite ledger without
|
|
61
|
+
constructing lifecycle-reconciling Fractal nodes or marking messages read. The browser
|
|
62
|
+
server is read-only and localhost-bound; portable HTML embeds captured data and assets.
|
|
63
|
+
Unsupported tokens/cost/tool details stay unknown, and the bridge rejects dollar caps.
|
|
64
|
+
See [operating instructions](fractal.md) and [verification evidence](../tests/fractal-verification.md).
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Solo runs
|
|
2
|
+
|
|
3
|
+
Create a run directory outside the repository, write the full implementation requirements to
|
|
4
|
+
`prompt.md`, and optionally add `goal.txt` and an approach file. State the selected models,
|
|
5
|
+
efforts, harnesses and any explicit bypass flags before dispatch.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bash <skill_dir>/scripts/forge-solo.sh "$FORGE_RUN" --repo "$REPO" \
|
|
9
|
+
--dwarf <spec> [--qa <spec>] [--approach <file>] \
|
|
10
|
+
[--yolo-dwarf] [--yolo-qa] [--timeout <seconds>] [--no-memory] [--dry-run] \
|
|
11
|
+
[--fractal | --no-fractal] [--fractal-concurrency <n>] [--fractal-deadline <seconds>] \
|
|
12
|
+
[--ui | --no-ui]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`--ui` opens the run in [forge-ui](ui.md) before the first dispatch and prints a
|
|
16
|
+
`forge-ui: <url>` line to relay. It is fail-open (a UI that cannot start never changes the
|
|
17
|
+
exit code), is not remembered, and is ignored with `--dry-run`. Ask once for a new run; do
|
|
18
|
+
not ask on a re-run or `--retry`.
|
|
19
|
+
|
|
20
|
+
Besides the artifacts above, the runner writes these for forge-ui and for you:
|
|
21
|
+
|
|
22
|
+
| File | Meaning |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `status` | `RUNNING` while it works, then this invocation's outcome: a verdict word (`PASS`, `FAIL`, `UNKNOWN`, `NOCHANGES`, `INVALIDATED`) or `TIMEOUT`, `INFRA`, `INTERRUPTED`, `ERROR` |
|
|
25
|
+
| `dwarf.spec`, `qa.spec` | the specs this run was given, one line each |
|
|
26
|
+
| `attempts.tsv` | one row per invocation: epoch, attempt number, status, infrastructure class, dwarf spec |
|
|
27
|
+
|
|
28
|
+
Both roles receive offline preflight before implementation starts. The runner records a
|
|
29
|
+
starting tree with a private index, then captures the net filesystem changes since that
|
|
30
|
+
snapshot, including staged, unstaged, binary and untracked files. It does not alter the
|
|
31
|
+
user's staging choices. `existing.diff` distinguishes pre-existing work from `changes.diff`.
|
|
32
|
+
Ignored files and Forge memory are outside the implementation diff. Concurrent writers
|
|
33
|
+
cannot be attributed reliably: keep one implementer per source tree.
|
|
34
|
+
|
|
35
|
+
QA receives the full implementation input, approach and actual diff in a disposable Git
|
|
36
|
+
repository. Source and review fingerprints are checked afterwards; mutations invalidate QA.
|
|
37
|
+
This detects mutations; it is not an operating-system security sandbox for unrestricted Bash.
|
|
38
|
+
|
|
39
|
+
Read `dwarf.last`, `qa.last`, `changes.diff` and `verdict`. Preserve CONFIRMED/PLAUSIBLE labels
|
|
40
|
+
when relaying findings. Report scope drift and unsupported implementation claims. Do not
|
|
41
|
+
automatically retry. In ordinary execution the run is one implementation followed by
|
|
42
|
+
one review; Fractal may use multiple bounded work steps before that independent review.
|
|
43
|
+
|
|
44
|
+
Statuses: PASS, FAIL, UNKNOWN, NOCHANGES, INVALIDATED. Exit codes: 0 completed review
|
|
45
|
+
(inspect verdict), 2 usage, 3 precondition, 4 dispatch failure, 5 no changes or invalidated
|
|
46
|
+
review, 7 timeout, 8 infrastructure failure (quota, auth, rate limit, network, empty output:
|
|
47
|
+
nothing was judged — fix the cause and run again; a dwarf that returns nothing *and* changes
|
|
48
|
+
nothing is reported as 8, not 5). The default dispatch timeout is 2700 seconds, scaled up for
|
|
49
|
+
`xhigh`/`max` effort (so the default QA/planner effort gets 3600), overridable per model in the
|
|
50
|
+
registry; 0 disables that dispatch timeout, but an enabled Fractal task still has its own attempt
|
|
51
|
+
deadline. See [dispatch timeouts](../README.md#scriptsforge-dispatchsh).
|
|
52
|
+
|
|
53
|
+
The dwarf prompt carries implementer rules (no commits, no background processes, finish every
|
|
54
|
+
measurement before the last message). forge records what it observes: if the dwarf committed
|
|
55
|
+
anyway the run still reviews its changes, says so, and prints `git reset --soft <old HEAD>` —
|
|
56
|
+
it never resets anything itself; leftover processes (stopped) and promises of later work appear
|
|
57
|
+
under "implementer guard notes". The QA prompt asks for `P0..P3`-labelled findings; solo has no
|
|
58
|
+
threshold gate, so any CONFIRMED finding still fails.
|
|
59
|
+
|
|
60
|
+
`--native-review` is an explicit Codex-only alternative: native review cannot accept the
|
|
61
|
+
custom requirements prompt or verdict instruction. Its result is informational and UNKNOWN;
|
|
62
|
+
it cannot establish requirements coverage. The disposable repository supplies the recorded
|
|
63
|
+
baseline via `--review-base`.
|
|
64
|
+
|
|
65
|
+
`--fail-first` runs the change's new tests against the code without its non-test changes
|
|
66
|
+
first and gives the reviewer the result (`failfirst.json`); see
|
|
67
|
+
[fail-first evidence](decompose.md#fail-first-evidence-opt-in).
|
|
68
|
+
|
|
69
|
+
Changes remain in the source working tree. Forge does not commit or integrate solo work.
|
|
70
|
+
|
|
71
|
+
Solo defaults to `--output summary`; `--output full` includes raw CLI output. Read the two
|
|
72
|
+
`.last` files once, then inspect logs only as needed. `forge-dispatch.sh report <run-dir>`
|
|
73
|
+
reports immutable attempt metrics and native usage independently of project memory. Shared
|
|
74
|
+
context is assembled from original requirements and approach; QA does not embed dwarf
|
|
75
|
+
execution instructions. Review snapshots use self-contained object packs and one checkout.
|
|
76
|
+
|
|
77
|
+
Optional [Ripwire context](ripwire.md) is enabled by default. Use `--no-ripwire`
|
|
78
|
+
or `FORGE_RIPWIRE=off` to disable preparation and installation offers.
|
|
79
|
+
|
|
80
|
+
## Fractal execution and recovery
|
|
81
|
+
|
|
82
|
+
[Fractal](fractal.md) is separately opt-in. It starts from the complete snapshot
|
|
83
|
+
above, runs in a managed isolated repository, and imports the candidate only after
|
|
84
|
+
checking source drift. Resume preserves that snapshot. `--retry` starts an explicit
|
|
85
|
+
new Fractal attempt using preserved work and the original QA baseline. A saved PASS
|
|
86
|
+
checkpoint is reused only while its source fingerprint remains unchanged.
|
|
87
|
+
|
|
88
|
+
Ask `Use Fractal for this run? [y/N]` once in the conversational workflow and pass
|
|
89
|
+
`--fractal` or `--no-fractal` explicitly to the runner. A direct interactive runner
|
|
90
|
+
asks if neither flag is given; unattended execution defaults off. Installation is a
|
|
91
|
+
separate choice. Do not ask again when resuming or retrying a recorded run.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# First run, with optional child routing; the run directory already contains prompt.md.
|
|
95
|
+
bash <skill_dir>/scripts/forge-solo.sh "$FORGE_RUN" --repo "$REPO" \
|
|
96
|
+
--dwarf sol:high --qa opus --dwarf-low luna:medium --fractal
|
|
97
|
+
|
|
98
|
+
# Discover the managed run ID, then inspect or resume from another terminal.
|
|
99
|
+
<skill_dir>/forge fractal runs --repo "$REPO" --json
|
|
100
|
+
<skill_dir>/forge fractal tree RUN_ID --json
|
|
101
|
+
<skill_dir>/forge fractal resume RUN_ID
|
|
102
|
+
|
|
103
|
+
# Start a new attempt only when explicitly requested, using the same frozen settings.
|
|
104
|
+
bash <skill_dir>/scripts/forge-solo.sh "$FORGE_RUN" --repo "$REPO" \
|
|
105
|
+
--dwarf sol:high --qa opus --dwarf-low luna:medium --fractal --retry
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The solo directory's `fractal-selection.json` also records the managed run ID.
|
|
109
|
+
Per-node workspaces, preserved responses and raw attempts are under the managed run;
|
|
110
|
+
the solo directory still supplies `changes.diff`, `qa.last` and `verdict` for delivery.
|
|
111
|
+
Parent invocations yield while their children run. QA sees their combined task diff.
|
|
112
|
+
Use [Fractal controls and reports](fractal.md#inspection-and-controls) to inspect partial
|
|
113
|
+
work; a completed execution node does not imply a PASS verdict or repository verification.
|
package/references/ui.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# forge-ui
|
|
2
|
+
|
|
3
|
+
forge-ui is a read-only local web page that shows a forge run live: a decomposed plan or a
|
|
4
|
+
solo run. It is opt-in per run. The orchestrator asks "Open forge-ui to watch this run?
|
|
5
|
+
[y/N]" once at the start of every new interactive run and passes the answer as `--ui` or
|
|
6
|
+
`--no-ui`. Nothing about the run depends on it: a UI that cannot start prints a note and the
|
|
7
|
+
run goes on, with the same exit code.
|
|
8
|
+
|
|
9
|
+
## What it shows
|
|
10
|
+
|
|
11
|
+
- **Map of every run:** every run forge has registered, one district per repo and one block per
|
|
12
|
+
run with a lamp per task, filterable by state, repo, kind, model, age and review verdict.
|
|
13
|
+
- **Run cloud:** every run at once, full screen in 3D, grouped by project, in one of five layouts
|
|
14
|
+
(helix, sphere, galaxy, timeline, constellation) as blocks or orbs. Filters narrow it to exactly
|
|
15
|
+
the runs wanted (project, state, difficulty, tasks per run, models and more), and runs can be
|
|
16
|
+
dragged anywhere; where they were dropped is remembered in that browser.
|
|
17
|
+
- **Files heatmap:** which tasks planned, changed, drifted into or conflicted on which files,
|
|
18
|
+
with same-wave overlaps marked, and each file's diff a click away.
|
|
19
|
+
- **Model leaderboard:** first-attempt pass rates with confidence intervals, retries, escalations,
|
|
20
|
+
timeouts, time and tokens per implementer, and verdicts, findings and shadow agreement per
|
|
21
|
+
reviewer, filterable by project, difficulty and period.
|
|
22
|
+
- **Diffs with findings in place:** any task diff, the exact diff each reviewer read, or a final
|
|
23
|
+
review's combined diff, with every P0–P3 finding shown under the line it names.
|
|
24
|
+
- **Fractal child tasks:** the tree a Fractal task was split into, in the inspector and above
|
|
25
|
+
the opened task in 3D, each node with its decision, cost, output and candidate diff.
|
|
26
|
+
- **Export a run:** one HTML file to share that opens without forge-ui; prompts, diffs, reports
|
|
27
|
+
and local paths stay out unless chosen.
|
|
28
|
+
- **Quick jump and notifications:** Ctrl K (⌘K) or `/` searches every run, task, project and
|
|
29
|
+
model; the bell tells you when a run finishes or fails, a task fails or a final review gives its
|
|
30
|
+
verdict, and can raise browser notifications while the tab is in the background.
|
|
31
|
+
- **Plan run:** tasks in their planned waves with dependency cables, then the finish line:
|
|
32
|
+
integration, verification and the whole-run final review, each a node with its status. The
|
|
33
|
+
*Execution* view shows the same run on wall-clock time, one block per attempt in the lane it
|
|
34
|
+
ran in, with projected blocks for what has not started and a strip of busy slots against
|
|
35
|
+
`--max-parallel`.
|
|
36
|
+
- **Opening a node** shows its attempts, dwarves and reviews, and an inspector with the prompt
|
|
37
|
+
that launched each one, the model, timings by phase, tokens, verdicts and the P0–P3 findings
|
|
38
|
+
of every review (the final review's included).
|
|
39
|
+
- **Solo run:** one task: its attempts, specs, prompts and the final messages.
|
|
40
|
+
- It refreshes every two seconds while something runs (five otherwise) while the tab is
|
|
41
|
+
visible, so a run can be watched as it goes. A run can also be replayed from its record.
|
|
42
|
+
|
|
43
|
+
The 3D view loads three.js from cdn.jsdelivr.net when first used; offline, the page shows the
|
|
44
|
+
same run in 2D. The full page description is in [ui/README.md](../ui/README.md).
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bash <skill_dir>/scripts/forge-parallel.sh plan "$PLAN" --repo "$REPO" ... --ui
|
|
50
|
+
bash <skill_dir>/scripts/forge-solo.sh "$FORGE_RUN" --repo "$REPO" --dwarf <spec> --ui
|
|
51
|
+
<skill_dir>/forge ui open [DIR] [--no-browser] [--port N]
|
|
52
|
+
<skill_dir>/forge ui status [--json]
|
|
53
|
+
<skill_dir>/forge ui stop
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `--ui` starts (or reuses) the server for this run and prints a `forge-ui: <url>` line;
|
|
57
|
+
relay that URL to the user. `--no-ui` is the explicit "no". `plan --ui` prints the link
|
|
58
|
+
under the task table. `forge-solo.sh --ui` is ignored with `--dry-run`.
|
|
59
|
+
- `forge ui open DIR` does the same for an existing plan or solo run dir; without `DIR` it
|
|
60
|
+
opens the run list. `--no-browser` prints the link only. The browser opens the first time a
|
|
61
|
+
run is opened, not on every `open`.
|
|
62
|
+
- `forge ui status` exits 0 when the server runs and 1 when not; `--json` prints
|
|
63
|
+
`{"running": ...}` with pid, port, url, version, build, started and log.
|
|
64
|
+
- `forge ui stop` stops it; it exits 0 when nothing was running.
|
|
65
|
+
|
|
66
|
+
| Variable | Meaning |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `FORGE_UI=off` | `--ui` on a runner starts nothing (a note is printed); `forge ui open` still starts the server |
|
|
69
|
+
| `FORGE_UI_BROWSER=off` | print the link, never open a browser |
|
|
70
|
+
| `FORGE_UI_PORT` | preferred port, default `8765`; `--port` wins; a taken port falls back to a free one |
|
|
71
|
+
| `FORGE_UI_IDLE_S` | idle seconds before the server exits, default `14400`; `0` never |
|
|
72
|
+
| `FORGE_RUN_REGISTRY=off` | runs stop registering themselves (`--ui` and `forge ui open DIR` still register the run they open) |
|
|
73
|
+
|
|
74
|
+
## One shared server
|
|
75
|
+
|
|
76
|
+
All runs share one detached server on `127.0.0.1`. A second `forge ui open` reuses it.
|
|
77
|
+
It is replaced when it runs old code: `open` compares the server's build (a hash of the
|
|
78
|
+
`forge_ui` Python files, reported by `/api/health`) with the checkout's, and when they differ,
|
|
79
|
+
or the registry directory changed, stops the old server and starts a new one. It exits by
|
|
80
|
+
itself after `FORGE_UI_IDLE_S` seconds without a request; the next `open` starts it again.
|
|
81
|
+
Concurrent launchers are serialised by a lock, so they never start two servers.
|
|
82
|
+
|
|
83
|
+
## State and logs
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
${XDG_STATE_HOME:-~/.local/state}/forge/ui/
|
|
87
|
+
server.json pid, port, build, registry and the runs a browser was opened for
|
|
88
|
+
server.log the server's output; read it when the server did not start
|
|
89
|
+
lock serialises launchers
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Every plan, run and solo run registers its run dir in forge's run registry
|
|
93
|
+
(`${XDG_CONFIG_HOME:-~/.config}/forge/runs/`), whether or not the UI was asked for: that is
|
|
94
|
+
how the map shows all of a repo's runs. `forge-parallel.sh run` also records the
|
|
95
|
+
`--max-parallel` it was given in `$PLAN/max_parallel`.
|
|
96
|
+
|
|
97
|
+
## Privacy
|
|
98
|
+
|
|
99
|
+
The server binds only `127.0.0.1` and serves read-only. It has **no token**: any process on
|
|
100
|
+
the machine, under any user who can reach loopback, can read every run it knows, including
|
|
101
|
+
prompts, diffs and review text from private repos. A running server keeps serving them,
|
|
102
|
+
including runs that were later rerun with `--no-ui`.
|
|
103
|
+
|
|
104
|
+
Every run is registered, so a running server shows every run on the machine, not only those
|
|
105
|
+
opened with `--ui`. The 3D view fetches three.js from cdn.jsdelivr.net (no run data is sent;
|
|
106
|
+
choose 2D in the page to never contact it).
|
|
107
|
+
|
|
108
|
+
`--no-ui` and `FORGE_UI=off` only stop a *runner* from launching the server. They do not stop a
|
|
109
|
+
server that is already running, do not unregister runs, and do not block `forge ui open`. To
|
|
110
|
+
close the exposure, run `forge ui stop`. Do not forward the server's port.
|
|
111
|
+
|
|
112
|
+
The API contract is [ui/docs/api.md](../ui/docs/api.md).
|