@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.
Files changed (83) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +106 -1766
  3. package/SKILL.md +22 -3
  4. package/VERSION +1 -1
  5. package/docs/README.md +26 -0
  6. package/docs/add-ons.md +33 -0
  7. package/docs/artifacts.md +84 -0
  8. package/docs/commands.md +365 -0
  9. package/docs/dataset.md +29 -0
  10. package/docs/decomposed-runs.md +310 -0
  11. package/docs/development.md +95 -0
  12. package/docs/forge-ui.md +128 -0
  13. package/docs/fractal.md +181 -0
  14. package/docs/install.md +160 -0
  15. package/docs/memory.md +156 -0
  16. package/docs/models.md +118 -0
  17. package/docs/reviews.md +245 -0
  18. package/docs/safety.md +55 -0
  19. package/docs/troubleshooting.md +54 -0
  20. package/forge +20 -0
  21. package/package.json +3 -2
  22. package/references/dataset.md +18 -1
  23. package/references/decompose.md +20 -2
  24. package/references/fractal.md +1 -1
  25. package/references/harnesses.md +18 -6
  26. package/references/solo.md +1 -1
  27. package/references/ui.md +27 -5
  28. package/scripts/forge-config.py +347 -0
  29. package/scripts/forge-control.py +645 -0
  30. package/scripts/forge-dispatch.sh +7 -1
  31. package/scripts/forge-issues.py +806 -0
  32. package/scripts/forge-lib-task.sh +234 -35
  33. package/scripts/forge-parallel-options.sh +3 -1
  34. package/scripts/forge-parallel.sh +3 -2
  35. package/scripts/forge-route.py +184 -27
  36. package/scripts/forge-runs-rm.py +840 -0
  37. package/scripts/forge-runs.py +139 -0
  38. package/scripts/forge-runtime.py +59 -6
  39. package/scripts/forge-schedule.py +227 -11
  40. package/scripts/forge-ui.py +85 -19
  41. package/scripts/forge-version.py +54 -12
  42. package/scripts/forge_dataset/__init__.py +77 -6
  43. package/scripts/forge_dataset/capture.py +5 -2
  44. package/scripts/forge_dataset/harvest.py +23 -1
  45. package/scripts/forge_dataset/importer.py +420 -18
  46. package/scripts/forge_fractal/__init__.py +2 -1
  47. package/scripts/forge_registry.py +168 -16
  48. package/ui/README.md +116 -23
  49. package/ui/THIRD_PARTY_NOTICES.md +108 -0
  50. package/ui/docs/api.md +283 -23
  51. package/ui/forge_ui/__main__.py +10 -2
  52. package/ui/forge_ui/control.py +575 -0
  53. package/ui/forge_ui/export.py +33 -9
  54. package/ui/forge_ui/fractal.py +1 -2
  55. package/ui/forge_ui/reader.py +121 -8
  56. package/ui/forge_ui/server.py +135 -5
  57. package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
  58. package/ui/forge_ui/static/app.js +358 -131
  59. package/ui/forge_ui/static/cloud3d.js +3 -2
  60. package/ui/forge_ui/static/cloudui.js +8 -5
  61. package/ui/forge_ui/static/controls.js +619 -0
  62. package/ui/forge_ui/static/convo.js +295 -0
  63. package/ui/forge_ui/static/datatable.js +232 -0
  64. package/ui/forge_ui/static/diffview.js +29 -10
  65. package/ui/forge_ui/static/findings3d.js +134 -0
  66. package/ui/forge_ui/static/findingsview.js +412 -0
  67. package/ui/forge_ui/static/graph.js +3 -3
  68. package/ui/forge_ui/static/icons.js +94 -0
  69. package/ui/forge_ui/static/index.html +31 -13
  70. package/ui/forge_ui/static/iso.js +70 -19
  71. package/ui/forge_ui/static/leaderboard.js +44 -48
  72. package/ui/forge_ui/static/panel.js +92 -36
  73. package/ui/forge_ui/static/rundelete.js +388 -0
  74. package/ui/forge_ui/static/runmodel.js +17 -1
  75. package/ui/forge_ui/static/scene3d.js +518 -43
  76. package/ui/forge_ui/static/search.js +2 -1
  77. package/ui/forge_ui/static/settings.js +399 -0
  78. package/ui/forge_ui/static/shell.js +137 -0
  79. package/ui/forge_ui/static/stagectl.js +150 -0
  80. package/ui/forge_ui/static/style.css +1099 -469
  81. package/ui/forge_ui/static/theme.js +25 -0
  82. package/ui/forge_ui/static/uikit.js +161 -0
  83. package/ui/forge_ui/transcripts.py +608 -0
@@ -0,0 +1,181 @@
1
+ # Fractal execution
2
+
3
+ [← README](../README.md) · [All docs](README.md)
4
+
5
+ Fractal is an optional implementation backend for both solo and decomposed runs. It uses
6
+ the existing Forge dispatcher for Codex, Claude, OpenClaude, OpenCode and Antigravity.
7
+ Each work step gets a fresh model invocation; preserved files and child results provide
8
+ continuity when a parent resumes. Independent QA reviews the complete Forge task diff.
9
+
10
+ `--decompose-level` plans top-level Forge tasks before execution. `--fractal` lets an
11
+ implementer request nested children during execution. They can be used together, and
12
+ solo runs can use Fractal without a top-level decomposition.
13
+
14
+ ## Install the optional runtime
15
+
16
+ ```bash
17
+ forge fractal install --dry-run
18
+ forge fractal install --yes --with-prerequisites
19
+ forge fractal doctor --spec sol --spec opus --json
20
+ ```
21
+
22
+ The managed runtime pins **Fractal 1.2.0** at revision
23
+ `18793200c0d7e8cdb2db369ea3abe5647a1e15e4`, checks the source archive checksum,
24
+ and includes the required `wiki` executable. It prefers an existing Python 3.12–3.14;
25
+ with prerequisite installation selected, it can provision uv, Python 3.13 and tmux.
26
+ tmux installation uses an existing Homebrew on macOS or apt on Debian/Ubuntu.
27
+ The [installation reference](../references/fractal.md#installation-and-provenance) covers
28
+ consent, supported bootstrap platforms, provenance and failure diagnostics.
29
+
30
+ Installation never enables Fractal for a run, adds no command and leaves your shell startup
31
+ files alone.
32
+ Doctor checks the runtime and advertised harness capabilities without calling a provider.
33
+
34
+ ## Start and inspect a run
35
+
36
+ Through the skill:
37
+
38
+ ```text
39
+ /forge "add retry with exponential backoff to the HTTP client" --dwarf sol:high --fractal
40
+ ```
41
+
42
+ Or prepare a solo run directory outside the product repository and invoke the runner:
43
+
44
+ ```bash
45
+ FORGE_RUN="$HOME/.local/state/forge/examples/http-client"
46
+ mkdir -p "$FORGE_RUN"
47
+ printf '%s\n' 'Add bounded exponential backoff to the HTTP client and run its tests.' > "$FORGE_RUN/prompt.md"
48
+ bash scripts/forge-solo.sh "$FORGE_RUN" --repo /absolute/path/to/product \
49
+ --dwarf sol:high --qa opus --fractal --fractal-concurrency 3
50
+ ```
51
+
52
+ Choose a new run directory for unrelated work. Backend selection, routing pools and limits
53
+ are stored with the run; resume and explicit retry retain them. For a prepared, approved
54
+ decomposed plan, select the backend on `run`:
55
+
56
+ ```bash
57
+ bash scripts/forge-parallel.sh run /absolute/path/to/plan --fractal --dry-run
58
+ bash scripts/forge-parallel.sh run /absolute/path/to/plan --fractal
59
+ ```
60
+
61
+ Dry runs show resolved model pools, limits and proposed workspaces without installation
62
+ or provider calls. When a selected runtime is unavailable, interactive runs offer
63
+ installation; unattended runs return an actionable error instead of changing backends.
64
+
65
+ Discover the managed run ID, then inspect it from another terminal:
66
+
67
+ ```bash
68
+ forge fractal runs --repo /absolute/path/to/product --json
69
+ forge fractal open # live runs index
70
+ forge fractal status RUN_ID --json
71
+ forge fractal tree RUN_ID --json
72
+ forge fractal logs RUN_ID --follow
73
+ forge fractal report RUN_ID --html /absolute/path/to/report.html
74
+ ```
75
+
76
+ `RUN_ID` is the `run-…` identifier returned by `runs`, not the solo directory or plan name.
77
+ The [run inspector](../references/fractal-dashboard.md) opens on progress and problems, then lets
78
+ readers inspect task QA/history or node decisions, logs and changes. It refreshes about every
79
+ two seconds without replacing the workspace; search, selection, focus and scrolling remain in
80
+ place. “Pause live updates” affects the browser only. Browser access is read-only, bound to
81
+ `127.0.0.1` with an ephemeral access token. Closing its server does not stop execution.
82
+ Portable HTML embeds complete captured history and logs, works offline and needs no Fractal
83
+ runtime to read.
84
+
85
+ ## Automatic recursive decomposition
86
+
87
+ `--fractal` permits workers to request children. To require a planning decision before
88
+ every eligible node implements, use `--fractal-auto-decompose`:
89
+
90
+ ```text
91
+ /forge "<goal>" --dwarf luna:high --qa sol:high --fractal-auto-decompose --fractal-planner sol:xhigh --fractal-depth 3 --fractal-nodes 24
92
+ ```
93
+
94
+ After the top-level Forge approval, the planner automatically splits useful work or
95
+ records why it is atomic. Children repeat this decision within the approved ownership
96
+ and frozen limits. `--fractal-planner` selects a separate decision model; when omitted,
97
+ Forge reuses the configured top-level planner, then the task's initial root model.
98
+ Decisions use disposable snapshots, persist for resume, and appear in tree/status/HTML
99
+ reports. Ordinary `--fractal` and existing runs retain their current behavior. Start a
100
+ new run to change policy or planner. See the [full decision contract](../references/fractal.md#automatic-decomposition-decisions).
101
+
102
+ ## Bounds, recovery and acceptance
103
+
104
+ Defaults per Forge task are two nesting levels below implementation, three unsettled
105
+ direct children per node, twelve lifetime nodes, six iterations per node per attempt,
106
+ and a 45-minute attempt deadline excluding explicit pauses. Three model slots are shared
107
+ across the whole Forge run, including QA. `--max-parallel` bounds top-level task pipelines;
108
+ `--fractal-concurrency` bounds model invocations across their nested trees.
109
+
110
+ Child difficulty selects a frozen `--dwarf-low`, `--dwarf-medium` or `--dwarf-high` pool,
111
+ falling back to the configured general dwarf, then the parent. Dependencies and owned
112
+ paths constrain admission; overlapping ownership is serialized. Fractal never implies
113
+ `--yolo-dwarf`. Costs are observational; unavailable usage stays unknown and dollar-cap
114
+ requests are rejected.
115
+
116
+ ```bash
117
+ forge fractal pause RUN_ID
118
+ forge fractal resume RUN_ID
119
+ forge fractal stop RUN_ID
120
+ ```
121
+
122
+ Pause and stop preserve work and diagnostics. Run-level resume recovers recorded execution
123
+ and the Forge pipeline without repeating accepted stages. Use explicit runner retry for
124
+ a new attempt, retaining work and the original Forge QA baseline. Controls also accept
125
+ `--task TASK_ID` and `--task TASK_ID --node NODE_ID`; those IDs come from `tree` output.
126
+
127
+ Managed data lives in `${XDG_STATE_HOME:-$HOME/.local/state}/forge/fractal/`. Solo execution
128
+ uses an isolated snapshot and imports changes only after checking source drift, preserving
129
+ staging and leaving changes uncommitted. Decomposed candidates return to Forge's existing
130
+ review/integration pipeline. **Fractal completion is not Forge acceptance**: QA, repository
131
+ verification and authorization to update the user's branch remain distinct.
132
+
133
+ See the [full Fractal reference](../references/fractal.md) for limits, child requests and
134
+ recovery details, and [verification evidence](../tests/fractal-verification.md) for the tested
135
+ platforms and browser checks. No paid-provider or speed/cost claim follows from fake-CLI tests.
136
+
137
+ ## `forge fractal` command group
138
+
139
+ Run it as `forge fractal`, or `forge fractal` from a git checkout.
140
+ This group manages Fractal runs; it does not replace the `/forge` skill or runner scripts.
141
+
142
+ | Command | Purpose |
143
+ | --- | --- |
144
+ | `install [--dry-run] [--yes] [--with-prerequisites]` | preview or install the isolated pinned runtime |
145
+ | `doctor [--spec SPEC] [--json]` | check runtime/hooks/tmux and optional repeated harness specs without provider calls |
146
+ | `runs [--repo DIR] [--json] [--html PATH]` | discover managed runs, optionally filtered by repository |
147
+ | `status\|tree\|activity RUN_ID` | inspect progress, hierarchy and history |
148
+ | `logs\|costs\|messages\|config RUN_ID` | inspect diagnostics, observed accounting, radio traffic and effective settings |
149
+ | `pause\|resume\|stop RUN_ID` | request a control and record its observed state |
150
+ | `open [RUN_ID] [--port PORT]` | open the read-only live dashboard or runs index |
151
+ | `report RUN_ID [--html PATH]` | capture a self-contained HTML report; default filename is `RUN_ID.html` |
152
+
153
+ Inspection commands accept `--task TASK_ID`, `--node NODE_ID`, `--json`, `--html PATH`,
154
+ `--offset N` and `--limit N` (1–1000). They currently return a common JSON run projection;
155
+ the command names do not imply separate output schemas. `logs --follow` follows changes.
156
+ HTML exports capture full history and logs. Controls require an explicit run ID, and a
157
+ node control also requires its task ID; they never guess the latest run. Use each command's
158
+ `--help` for its accepted options. Installation is also available through
159
+ `bash scripts/forge-install-fractal.sh`.
160
+
161
+ Fractal adds a managed workspace alongside the runner artifacts described here:
162
+
163
+ ```text
164
+ ${XDG_STATE_HOME:-$HOME/.local/state}/forge/fractal/
165
+ runtime/ isolated pinned Fractal and wiki environment
166
+ provenance.json installed versions and source checksum
167
+ installs/ installation attempts and diagnostics
168
+ runs/<run-id>/run.json effective routing, limits and runner binding
169
+ runs/<run-id>/controls/ requested controls and observed results
170
+ runs/<run-id>/tasks/<task-id>/
171
+ request.json / state.json task request and execution checkpoint
172
+ initialization.json control repository and Fractal ledger provenance
173
+ control/ Fractal metadata, separate from product changes
174
+ nodes/<node-id>/product/ isolated product candidate
175
+ nodes/<node-id>/steps/ fresh dispatcher attempts and raw logs
176
+ outcome.json captured Forge acceptance and verification
177
+ ```
178
+
179
+ The original solo/plan directory stores `fractal-selection.json`; decomposed plans also
180
+ store `fractal-routing.json`. Prefer `forge fractal runs`, `tree`, `logs` and `report`
181
+ to direct ledger access. Inspection and stop never delete managed work.
@@ -0,0 +1,160 @@
1
+ # Install and update
2
+
3
+ [← README](../README.md) · [All docs](README.md)
4
+
5
+ Install forge from npm or a git checkout, check which agent CLIs it can reach, and keep every harness installation current.
6
+
7
+ ## Requirements
8
+
9
+ forge runs on macOS and Linux. It needs bash, git and Python 3.9 or later, plus the agent
10
+ CLIs you route models through; the doctor below shows which ones it finds. Installing from
11
+ npm brings none of these with it.
12
+
13
+ ## From npm
14
+
15
+ ```bash
16
+ npm install -g @giamme/forge
17
+ forge install
18
+ ```
19
+
20
+ `forge install` links the installed package into each harness, as the installer does for a
21
+ checkout below. It is a step of its own rather than an npm install script, so it works where
22
+ scripts are turned off (`--ignore-scripts`). To update:
23
+
24
+ ```bash
25
+ npm install -g @giamme/forge@latest && forge install
26
+ ```
27
+
28
+ The links point into npm's global package directory, so they keep working across updates;
29
+ `forge install` refreshes antigravity's copied plugin. Under nvm, that directory belongs to
30
+ one Node version: after switching Node, install forge on the new one and run `forge install`
31
+ again.
32
+
33
+ > **Foundry users:** Foundry's `forge` has the same name, and whichever directory comes first
34
+ > on your `PATH` wins; npm's global bin usually comes first. Runs start from your harness
35
+ > (`/forge`), so this command is only for `forge install` and the optional tools. If you keep
36
+ > Foundry's `forge` first, run ours as `"$(npm root -g)/@giamme/forge/forge"`.
37
+
38
+ ## From a git checkout
39
+
40
+ ```bash
41
+ git clone https://github.com/Giamme/forge.git ~/.claude/skills/forge
42
+ bash ~/.claude/skills/forge/scripts/forge-install.sh
43
+ ```
44
+
45
+ Clone anywhere you like — the installer works out its own location and links from there.
46
+ `~/.claude/skills/forge` is just the tidiest home: claude reads that path directly, and
47
+ opencode and grok scan it on their own.
48
+
49
+ The installer also offers optional Fractal support in an interactive terminal. Declining
50
+ or a Fractal installation failure does not prevent ordinary Forge setup. See
51
+ [Fractal installation](fractal.md#install-the-optional-runtime) for prerequisites and a separate install.
52
+
53
+ ## Check what forge can reach
54
+
55
+ `<forge>` is your checkout, or `"$(npm root -g)/@giamme/forge"` for an npm install:
56
+
57
+ ```bash
58
+ bash <forge>/scripts/forge-dispatch.sh doctor
59
+ ```
60
+
61
+ ```
62
+ HARNESS STATUS DETAIL
63
+ codex found /Users/you/.nvm/versions/node/v24.18.0/bin/codex
64
+ claude found /Users/you/.local/bin/claude
65
+ openclaude found /Users/you/.nvm/versions/node/v24.18.0/bin/openclaude
66
+ opencode found /Users/you/.opencode/bin/opencode
67
+ antigravity MISSING agy not on PATH
68
+ grok found /Users/you/.grok/bin/grok
69
+ ```
70
+
71
+ A `MISSING` harness is fine — forge only needs the ones you actually route models through.
72
+
73
+ **Binary presence is not reachability.** A harness can be installed and still fail on auth or
74
+ billing (opencode in particular fails this way), and that failure only surfaces mid-dispatch.
75
+ To prove a pairing end to end before spending a real run:
76
+
77
+ ```bash
78
+ bash <forge>/scripts/forge-dispatch.sh dwarf <spec> --prompt-file /dev/stdin --repo <scratch dir> \
79
+ <<< 'Reply with exactly: FORGE_OK'
80
+ ```
81
+
82
+ ## What the installer does per harness
83
+
84
+ Each CLI discovers skills differently, and the differences are not guessable — so verify
85
+ with the listed check rather than trusting file placement.
86
+
87
+ | Harness | Mechanism | Verify with |
88
+ |---|---|---|
89
+ | claude | `~/.claude/skills/forge/` | `/forge` appears in the skill list |
90
+ | openclaude | symlink into `~/.openclaude/skills/` | `openclaude skills list --json` |
91
+ | codex | symlink into **`~/.codex/skills/`** | `codex exec "do you have a skill named forge?"` |
92
+ | opencode | **nothing** — it scans `~/.claude/` itself | `opencode debug skill` |
93
+ | antigravity | `agy plugin install` of a generated wrapper | `agy --print='/forge'` |
94
+
95
+ > **The one ongoing caveat:** `agy plugin install` *copies* the files instead of referencing
96
+ > them. The other four track the source directory live, so an edit reaches them instantly —
97
+ > antigravity keeps running whatever it copied. **Re-run `forge-install.sh` after any edit**,
98
+ > or agy silently runs a stale version.
99
+
100
+ `forge-install.sh --dry-run` prints what it would do and touches nothing. It never clobbers
101
+ a real directory that isn't a symlink.
102
+
103
+ ## Updating all harness installations
104
+
105
+ Run the updater from your Forge checkout (or through one of its skill symlinks):
106
+
107
+ ```bash
108
+ ./forge update # same as: bash ~/.claude/skills/forge/scripts/forge-update.sh
109
+ ./forge version # VERSION, checkout commit, tracked upstream, last known release
110
+ ./forge version --check # ask the upstream for its newest release now
111
+ ```
112
+
113
+ It fetches the current branch's configured upstream, fast-forwards the checkout, and
114
+ runs Forge's installer to refresh Claude, OpenClaude and Codex links, OpenCode discovery,
115
+ and Antigravity's copied plugin. Harnesses that are not present are skipped. Restart
116
+ active harness sessions afterward to load the updated skill.
117
+
118
+ ```bash
119
+ # Preview without fetching or changing files:
120
+ bash <forge-checkout>/scripts/forge-update.sh --dry-run
121
+
122
+ # Refresh harness installations after local edits, without fetching:
123
+ bash <forge-checkout>/scripts/forge-update.sh --local
124
+
125
+ # Point a copied installation's updater at the original checkout:
126
+ bash <path>/scripts/forge-update.sh --source <forge-checkout>
127
+ ```
128
+
129
+ When a solo run, `plan` or `run` finishes, forge checks the checkout's upstream for a newer
130
+ release tag at most once a day (`git ls-remote`, 5-second timeout, cached in
131
+ `${XDG_STATE_HOME:-~/.local/state}/forge/update-check.json`) and, if there is one, prints a
132
+ single line on stderr: `forge 0.8 is available (you have 0.7.1): forge update`. The check is
133
+ silent offline, in a copied installation without Git, in CI (`CI` set) and with
134
+ `FORGE_UPDATE_CHECK=off`, and it never changes a run's outcome or exit code.
135
+
136
+ An npm installation has no Git upstream, so there the same daily check asks the npm registry
137
+ for the package's `latest` (`npm_config_registry` when you set one, as npm does), and the line
138
+ names npm's command instead: `forge 0.9.0 is available (you have 0.8): npm install -g
139
+ @giamme/forge@latest && forge install`. `forge version` shows the same command.
140
+
141
+ The default update requires a clean Git checkout and a branch with an upstream. Local
142
+ changes, detached HEAD, fetch failures and diverged branches stop the update without
143
+ resetting or stashing your work. `--local` deliberately allows current local edits.
144
+ Real skill directories are preserved; a conflicting directory or plugin installation
145
+ failure produces a nonzero exit and an explanation. After fixing an installation
146
+ failure, use `--local` to retry from the updated source. Updating Forge does not prompt
147
+ for optional Ripwire installation.
148
+
149
+ An npm installation is not a Git checkout, so the updater stops there and prints the npm
150
+ command [above](#from-npm) instead. `--local` still refreshes its harness installations.
151
+
152
+ ## `scripts/forge-install.sh`
153
+
154
+ ```
155
+ forge-install.sh [--dry-run]
156
+ ```
157
+
158
+ Links this directory into every installed harness. Skips harnesses that aren't present,
159
+ skips a destination that exists and isn't a symlink (it will never clobber a directory you
160
+ may have edited in place), and reports opencode as auto-detected.
package/docs/memory.md ADDED
@@ -0,0 +1,156 @@
1
+ # Project memory
2
+
3
+ [← README](../README.md) · [All docs](README.md)
4
+
5
+ What forge learns about a repository, where it keeps it, what each role is told, and what forge has spent there.
6
+
7
+ Every forge dispatch is a clean slate, so without help the tenth run in a repo rediscovers
8
+ what the first one learned. forge keeps that in the repo, in two files:
9
+
10
+ ```
11
+ .forge/memory.md small, capped, injected into every prompt
12
+ .forge/ledger.tsv append-only, one row per dispatch, never injected
13
+ ```
14
+
15
+ The split is the design. Injected context is paid for on every dispatch of every future run,
16
+ so "remember everything" and "stay small" pull against each other — the ledger remembers at
17
+ zero context cost and is read only when asked, while `memory.md` is capped at 40 lines / 4 KB.
18
+
19
+ ```markdown
20
+ ## Verify
21
+ - pytest -q runs the suite; make test also lints and is 4x slower
22
+
23
+ ## Known traps
24
+ - tests/test_api.py::test_timeout is flaky under -n auto [tests/test_api.py]
25
+
26
+ ## Recurring QA findings
27
+ - 3x: route added without updating openapi.yaml [src/routes]
28
+ ```
29
+
30
+ ## How a fact gets in
31
+
32
+ The role that just did the work writes it down, in its final message, next to the verdict:
33
+
34
+ ```
35
+ FORGE_LEARNING: trap | src/proto/*_pb2.py is generated, never hand-edit [src/proto]
36
+ ```
37
+
38
+ No extra dispatch and no separate model guessing what mattered — the one that hit the
39
+ landmine is the one that records it. Everything after that is deterministic bash.
40
+
41
+ **What stops it becoming a junk drawer:**
42
+
43
+ | Guard | Effect |
44
+ |---|---|
45
+ | recurrence threshold | a `finding` needs **two distinct runs** before it is injected. One occurrence is an incident, not a pattern |
46
+ | stale anchors | an entry ending in `[path]` is dropped once that path stops existing, so memory stays true as code moves |
47
+ | hard cap | over 40 lines / 4 KB, lowest-count and least-recent facts are trimmed; one entry is clamped to 180 chars |
48
+ | bias to silence | the prompt says most runs teach nothing durable and emitting nothing is correct. A false fact costs every future run; a missing one costs a single rediscovery |
49
+
50
+ Only `verify` and `trap` are kept on first sighting — those are facts, true the first time.
51
+
52
+ ## Slices, and what forge writes
53
+
54
+ | Role | Gets |
55
+ |---|---|
56
+ | planner, dwarf | Verify + Known traps + Recurring findings |
57
+ | qa | Known traps + Recurring findings |
58
+
59
+ QA is not told the build command; it is not building anything. It *is* told the recurring
60
+ findings, so it can notice this is the fourth time the same bug shipped.
61
+
62
+ `.forge/` is the **only** thing forge writes into your working tree rather than onto a branch
63
+ of its own. It is excluded from every captured diff, so a reviewer never sees it as the
64
+ dwarf's work — but it *will* show up as untracked in `git status` after your first run.
65
+
66
+ ## What to do with `.forge/` when it appears
67
+
68
+ forge never commits it. That decision is yours, and there are three sane answers:
69
+
70
+ | You want | Do this |
71
+ |---|---|
72
+ | memory shared with your team, and with future you | **commit `.forge/`** — the intended case; every teammate's runs then build on the same facts |
73
+ | memory kept to your machine | `echo '.forge/' >> .gitignore` |
74
+ | no memory at all | `--no-memory`, or `FORGE_MEMORY=off` in your environment |
75
+
76
+ Two things that are easy to get wrong:
77
+
78
+ **Gitignoring is not the same as turning it off.** An ignored `.forge/` is still written and
79
+ still injected into every prompt — it just stops travelling. Only `--no-memory` /
80
+ `FORGE_MEMORY=off` actually disables the feature.
81
+
82
+ **Never commit one file and ignore the other.** `memory.md` is *rebuilt from* `ledger.tsv` on
83
+ every write, so a clone that has the memory but not the ledger loses everything the moment
84
+ anyone runs forge there — the rebuild regenerates `memory.md` from that machine's own short
85
+ ledger and the shared facts are gone, silently. Verified behaviour, not a theoretical risk:
86
+
87
+ ```
88
+ teammate A commits memory.md only, ledger.tsv gitignored
89
+ memory.md: - pytest -q runs the suite
90
+ - src/a.py is generated [src/a.py]
91
+
92
+ teammate B clones, runs forge once, learns one unrelated thing
93
+ memory.md: - b learned something new ← A's two facts destroyed
94
+ ```
95
+
96
+ Commit both, or neither. If you commit them, expect `ledger.tsv` to gain a line per dispatch
97
+ — small, but it will appear in your diffs; that growth is the price of the recurrence
98
+ counting that keeps `memory.md` honest.
99
+
100
+ If you contribute to someone else's repo, gitignore it. A pull request containing forge's
101
+ notes to itself is noise to a reviewer who does not use forge.
102
+
103
+ ## `scripts/forge-memory.sh`
104
+
105
+ ```
106
+ forge-memory.sh inject <repo> <planner|dwarf|qa> # the block to prepend to a prompt
107
+ forge-memory.sh note <planner|dwarf|qa> # the instruction to append
108
+ forge-memory.sh record <repo> --last <file> --role <r> [--run-id X] [--task T]
109
+ [--model M] [--verdict V] [--duration S]
110
+ forge-memory.sh show <repo>
111
+ forge-memory.sh prune <repo> # re-apply staleness and the cap now
112
+ forge-memory.sh spend <repo> [--run <run-id>] # what forge has cost this repo
113
+ ```
114
+
115
+ `record` appends to the ledger and then rebuilds `memory.md` from the whole ledger — the
116
+ rebuild is idempotent, so a crashed run can leave the ledger a row short but can never leave
117
+ memory half-written. Editing `memory.md` by hand therefore does not stick; remove the ledger
118
+ rows instead.
119
+
120
+ ```bash
121
+ # what would a dwarf actually be told about this repo?
122
+ bash scripts/forge-memory.sh inject ~/dev/api dwarf
123
+
124
+ # start over
125
+ rm -rf ~/dev/api/.forge
126
+ ```
127
+
128
+ ## Spend
129
+
130
+ The ledger already holds a row per dispatch and is never injected into a prompt, so
131
+ accounting from it costs nothing:
132
+
133
+ ```bash
134
+ bash scripts/forge-memory.sh spend ~/dev/api
135
+ ```
136
+ ```
137
+ 14 dispatch(es) across 3 run(s), 1h12m of model time
138
+ Durations sum concurrent work, so they exceed the wall-clock of a parallel run.
139
+
140
+ by model disp total mean
141
+ sol:xhigh 4 38m20s 9m35s
142
+ opus 7 22m10s 3m10s
143
+ gemini:low:antigravity 3 11m30s 3m50s
144
+
145
+ by role disp total
146
+ dwarf 7 49m50s
147
+ qa 7 22m10s
148
+ ```
149
+
150
+ **Time and dispatch counts, never tokens or money.** The six CLIs expose usage differently
151
+ or not at all, and a number forge cannot actually measure would be worse than no number.
152
+
153
+ `duration_s` is the ledger's tenth column, appended at the end on purpose: a ledger written
154
+ by an older forge — possibly already committed and shared — has nine fields, and awk yields
155
+ an empty string for the missing one instead of shifting every column. Those rows are counted
156
+ but not timed, and the output says so.
package/docs/models.md ADDED
@@ -0,0 +1,118 @@
1
+ # Models, efforts and harnesses
2
+
3
+ [← README](../README.md) · [All docs](README.md)
4
+
5
+ How forge names a model (`alias:effort:harness`), what each harness offers, how effort is validated and clamped, when to drop the sandbox, and how to teach forge a new model.
6
+
7
+ ## The spec: `alias:effort:harness`
8
+
9
+ Everywhere forge takes a model, it takes the same three-field spec. Each field after the
10
+ first is optional:
11
+
12
+ | Spec | Resolves to |
13
+ |---|---|
14
+ | `sol` | alias `sol`, default effort for the role, its default harness (codex) |
15
+ | `sol:xhigh` | `sol` at `xhigh` effort on its default harness |
16
+ | `sol:xhigh:openclaude` | same model and effort, run through the `openclaude` CLI |
17
+ | `opus::claude` | explicit harness, effort left to the role default — the middle field may be empty |
18
+ | `gpt-5.6-sol::codex` | not in the registry? passed through as a literal model id — but only with an explicit harness |
19
+
20
+ An alias with no registry row still works, provided you name the harness. forge won't guess
21
+ a default harness for a model it has never seen; that guess would silently spend quota on
22
+ the wrong provider.
23
+
24
+ **Role defaults when you leave things out:**
25
+
26
+ - `--dwarf` missing → forge **asks**. Every dispatch spends real quota on your account, so
27
+ picking for you isn't its call.
28
+ - `--qa` missing → `opus`, which runs locally through `claude` and adds no cost surprise.
29
+ - effort missing → dwarf builds at `medium`, qa reviews at `xhigh`. A reviewer thinking
30
+ *less* than the implementer tends to wave things through, which wastes the stage entirely.
31
+
32
+ ## Effort, ceilings and clamping
33
+
34
+ Every harness has its own effort ladder:
35
+
36
+ | Harness | Ladder |
37
+ |---|---|
38
+ | codex | `low medium high xhigh max ultra` |
39
+ | claude | `low medium high xhigh max` |
40
+ | openclaude | `low medium high xhigh max ultracode` |
41
+ | antigravity | `low medium high` |
42
+ | grok | `low medium high xhigh max` (grok-4.7 offers up to `xhigh`) |
43
+ | opencode | provider-specific; forwarded verbatim |
44
+
45
+ And support is more irregular than one ceiling per backend:
46
+
47
+ - **Ceilings** — `luna` tops out at `max` on codex, so `luna:ultra` clamps down.
48
+ - **Gaps** — `gemini-pro` offers `low` and `high` but *not* `medium`, so `gemini-pro:medium`
49
+ clamps to `low`.
50
+ - **None at all** — Claude models on antigravity take no effort flag; passing one errors, so
51
+ forge reports it and drops it.
52
+
53
+ When forge clamps, it says so (`clamped=ultra -> max`) and the orchestrator passes that on
54
+ to you. Getting less thinking than you paid for, silently, is exactly the failure worth ten
55
+ lines of validation to prevent — and it's a real one: **codex accepts an invalid effort
56
+ without complaint and quietly runs at some other level.** forge validates first.
57
+
58
+ A word that isn't a recognized effort at all is an error, not a clamp.
59
+
60
+ ## Yolo mode
61
+
62
+ `--yolo-dwarf` and `--yolo-qa` drop that role's sandbox and approval gates:
63
+
64
+ | Harness | Flag used |
65
+ |---|---|
66
+ | codex | `--dangerously-bypass-approvals-and-sandbox` |
67
+ | claude / openclaude / antigravity | `--dangerously-skip-permissions` |
68
+ | opencode | `--auto` |
69
+ | grok | `--always-approve` |
70
+
71
+ Each flag applies to **one role only**, so `--yolo-dwarf` alone is the normal combination:
72
+ the implementer gets a free hand, the reviewer stays fenced in and still can't edit the code
73
+ it's judging.
74
+
75
+ You mostly don't need it. On codex, claude and openclaude the sandboxed roles are already
76
+ useful — the dwarf edits files and runs tests, qa reads and runs commands but cannot write.
77
+ Reach for yolo when a role genuinely must escape the sandbox: installing dependencies,
78
+ writing outside the repo, touching the network.
79
+
80
+ > **Antigravity is the exception worth knowing.** Without yolo it cannot run *any* shell
81
+ > command, and a single denied command **aborts the whole run** rather than degrading — a
82
+ > dwarf that tries to run the test suite ends with an error and an empty diff. forge detects
83
+ > this and appends a note telling that role not to attempt commands, which is why an
84
+ > antigravity dwarf reports its work as unverified by execution. If you want a Gemini dwarf
85
+ > that can actually run the tests it writes, it needs `--yolo-dwarf`.
86
+
87
+ ## Model registry
88
+
89
+ `registry.tsv` is the single source of truth for alias resolution. Four tab-separated
90
+ columns, plus an optional fifth:
91
+
92
+ ```
93
+ # alias harness model effort [timeout]
94
+ sol codex gpt-6.1-sol <=ultra
95
+ sol openclaude gpt-6.1-sol <=max
96
+ gemini-pro antigravity gemini-3.1-pro low,high
97
+ opus antigravity claude-opus-4-6-thinking none
98
+ grok grok grok-4.7 <=xhigh
99
+ grok opencode github-copilot/grok-4.6 -
100
+ ```
101
+
102
+ | `effort` form | Means |
103
+ |---|---|
104
+ | `<=X` | any effort on that harness's ladder up to and including `X` |
105
+ | `a,b,c` | exactly these efforts — gaps are real, and this is how they're expressed |
106
+ | `none` | takes no effort flag at all; passing one errors |
107
+ | `-` | no validated ladder; forwarded verbatim |
108
+
109
+ The optional fifth column, `timeout`, is a per-model default dispatch limit in seconds (`-`
110
+ or absent = the built-in default). It applies only when neither `--timeout` nor
111
+ `FORGE_TIMEOUT` was given and is scaled by the resolved effort (see
112
+ [timeout precedence](commands.md#scriptsforge-dispatchsh)). Rows without it behave exactly as before.
113
+
114
+ **Row order is meaningful:** the *first* row for an alias is that alias's default harness.
115
+ `--dwarf sol` means sol on codex; `--dwarf sol:high:openclaude` moves the same model
116
+ elsewhere.
117
+
118
+ Adding a model is one line in this file. Nothing else needs to change.