@giamme/forge 0.0.0-stage → 0.9.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 +205 -0
- package/LICENSE +21 -0
- package/README.md +194 -2
- package/SKILL.md +123 -0
- package/VERSION +1 -0
- package/docs/README.md +26 -0
- package/docs/add-ons.md +33 -0
- package/docs/artifacts.md +69 -0
- package/docs/commands.md +315 -0
- package/docs/dataset.md +20 -0
- package/docs/decomposed-runs.md +268 -0
- package/docs/development.md +95 -0
- package/docs/forge-ui.md +92 -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 +231 -0
- package/docs/safety.md +55 -0
- package/docs/troubleshooting.md +54 -0
- package/forge +79 -0
- package/package.json +27 -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 +365 -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 +671 -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-runs.py +121 -0
- package/scripts/forge-runtime.py +586 -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 +240 -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 +84 -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 +347 -0
- package/ui/THIRD_PARTY_NOTICES.md +177 -0
- package/ui/docs/api.md +970 -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 +214 -0
- package/ui/forge_ui/findings.py +132 -0
- package/ui/forge_ui/fractal.py +582 -0
- package/ui/forge_ui/models.py +216 -0
- package/ui/forge_ui/reader.py +1424 -0
- package/ui/forge_ui/server.py +312 -0
- package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
- package/ui/forge_ui/static/app.js +1079 -0
- package/ui/forge_ui/static/cloud3d.js +647 -0
- package/ui/forge_ui/static/cloudmodel.js +375 -0
- package/ui/forge_ui/static/cloudui.js +397 -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 +235 -0
- package/ui/forge_ui/static/favicon.svg +1 -0
- package/ui/forge_ui/static/files.js +776 -0
- package/ui/forge_ui/static/findings3d.js +134 -0
- package/ui/forge_ui/static/gantt.js +97 -0
- package/ui/forge_ui/static/graph.js +266 -0
- package/ui/forge_ui/static/icons.js +94 -0
- package/ui/forge_ui/static/index.html +67 -0
- package/ui/forge_ui/static/iso.js +816 -0
- package/ui/forge_ui/static/leaderboard.js +311 -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 +562 -0
- package/ui/forge_ui/static/runmodel.js +667 -0
- package/ui/forge_ui/static/scene3d.js +1471 -0
- package/ui/forge_ui/static/search.js +120 -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 +1073 -0
- package/ui/forge_ui/static/theme.js +25 -0
- package/ui/forge_ui/transcripts.py +608 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# Harness reference
|
|
2
|
+
|
|
3
|
+
Per-CLI invocation details behind `scripts/forge-dispatch.sh`. Read this when a dispatch
|
|
4
|
+
fails, when adding a backend, or when you need to know what a role can actually do.
|
|
5
|
+
|
|
6
|
+
Everything below was verified by running the CLIs on this machine, not read from docs —
|
|
7
|
+
several of these behaviours are undocumented and a few contradict what the `--help` text
|
|
8
|
+
implies.
|
|
9
|
+
|
|
10
|
+
Fractal uses this same dispatcher for all six harnesses through its `agents.py` bridge.
|
|
11
|
+
It is an optional execution backend, not a seventh model harness or a replacement for
|
|
12
|
+
`alias:effort:harness`. The same effort and permission rules apply to each fresh nested
|
|
13
|
+
work step. Use `./forge fractal doctor --spec SPEC` for runtime and advertised capability
|
|
14
|
+
checks; authentication and live provider availability still require an explicitly requested
|
|
15
|
+
provider test. See [Fractal execution](fractal.md).
|
|
16
|
+
|
|
17
|
+
## Contents
|
|
18
|
+
|
|
19
|
+
- [At a glance](#at-a-glance)
|
|
20
|
+
- [codex](#codex)
|
|
21
|
+
- [claude / openclaude](#claude--openclaude)
|
|
22
|
+
- [opencode](#opencode)
|
|
23
|
+
- [antigravity (agy)](#antigravity-agy)
|
|
24
|
+
- [grok](#grok)
|
|
25
|
+
- [Adding a harness](#adding-a-harness)
|
|
26
|
+
|
|
27
|
+
## At a glance
|
|
28
|
+
|
|
29
|
+
| Harness | Non-interactive form | Model flag | Effort flag | Full bypass |
|
|
30
|
+
|---------|----------------------|------------|-------------|-------------|
|
|
31
|
+
| `codex` | `codex exec` | `-m` | `-c model_reasoning_effort=<e>` | `--dangerously-bypass-approvals-and-sandbox` |
|
|
32
|
+
| `claude` | `claude -p` | `--model` | `--effort` | `--dangerously-skip-permissions` |
|
|
33
|
+
| `openclaude` | `openclaude -p` | `--model` | `--effort` | `--dangerously-skip-permissions` |
|
|
34
|
+
| `opencode` | `opencode run` | `-m provider/model` | `--variant` | `--auto` |
|
|
35
|
+
| `antigravity` | `agy --print=...` | `--model` | `--effort` | `--dangerously-skip-permissions` |
|
|
36
|
+
| `grok` | `grok --prompt-file` | `--model` | `--reasoning-effort` | `--always-approve` |
|
|
37
|
+
|
|
38
|
+
**Only antigravity bounds its own runtime.** The other five will sit on a hung request
|
|
39
|
+
indefinitely, and stock macOS ships no `timeout` binary to wrap them in — so a hung dwarf
|
|
40
|
+
used to stall its whole wave under `forge-parallel.sh`'s `xargs -P` with no output and no
|
|
41
|
+
way to distinguish stuck from slow. `forge-dispatch.sh` therefore runs every backend in the
|
|
42
|
+
background and kills it after its timeout: `--timeout`, else `FORGE_TIMEOUT` (an explicit `0`
|
|
43
|
+
disables), else the optional `timeout` column of the model's registry row, else 2700 s — scaled
|
|
44
|
+
×4/3 for `xhigh` and ×2 for `max`/`ultra` effort when not explicit (`<role>.resolved` records
|
|
45
|
+
`timeout=` and `timeout_source=`). A killed dispatch exits `7`.
|
|
46
|
+
|
|
47
|
+
**Infrastructure failures exit `8`.** After the backend exits, `forge-runtime.py classify` reads
|
|
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` 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
|
+
looking for strong phrases (usage/session limit, quota, insufficient balance/credit, rate limit,
|
|
52
|
+
429/529, unauthorized/401/403, invalid API key, not logged in, ECONNRESET/ENOTFOUND/…). An
|
|
53
|
+
`empty` answer (exit 0, nothing said) is the fifth class. codex's non-fatal reconnect notices
|
|
54
|
+
followed by a normal reply never classify. `<role>.infra` holds `class=`, `retry_after=` (parsed
|
|
55
|
+
from "resets 3pm", "try again in 2h", `|<epoch>`, capped at 24 h), `detail=`, `rc=`, `role=`.
|
|
56
|
+
A dwarf that exits 0 with nothing said writes the file but still exits 0; callers consult it
|
|
57
|
+
only when the diff is empty.
|
|
58
|
+
|
|
59
|
+
Implementation notes worth keeping, because they were arrived at the hard way: the backend is
|
|
60
|
+
started with `exec` inside its subshell, so the pid forge holds is the backend itself rather than
|
|
61
|
+
a wrapper it would be useless to kill. macOS has no `setsid` binary, so the harness is launched
|
|
62
|
+
through `forge-guard.py session` (python's `os.setsid()` then `exec`, same pid, pgid == pid)
|
|
63
|
+
unless `FORGE_ORPHAN_GUARD=off` or the run is Fractal. That gives a process group to signal as a
|
|
64
|
+
unit: the watchdog (TERM, then KILL for a CLI that traps TERM) and the exit trap signal the whole
|
|
65
|
+
group in addition to `pkill -P`, and after a normal exit `forge-guard.py reap` lists and stops
|
|
66
|
+
anything still alive in it (`<role>.orphans`). This is proven offline only; verify a new harness
|
|
67
|
+
with one live dispatch before relying on it, and use the kill switch if a harness deliberately
|
|
68
|
+
leaves a helper running.
|
|
69
|
+
|
|
70
|
+
Effort ladders, low to high:
|
|
71
|
+
|
|
72
|
+
- codex — `low medium high xhigh max ultra`
|
|
73
|
+
- claude — `low medium high xhigh max`
|
|
74
|
+
- openclaude — `low medium high xhigh max ultracode`
|
|
75
|
+
- antigravity — `low medium high`, and **per-model**: see below
|
|
76
|
+
- opencode — no fixed ladder; `--variant` is provider-specific and is forwarded verbatim
|
|
77
|
+
- grok — `low medium high xhigh max`; each model offers a subset (grok-4.7: up to `xhigh`)
|
|
78
|
+
|
|
79
|
+
## codex
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
codex exec -m <model> -C <repo> --skip-git-repo-check -o <last-message-file> \
|
|
83
|
+
-c model_reasoning_effort=<effort> (--approve-for-me | --dangerously-bypass-...) "<prompt>"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**It does not validate the effort.** `-c model_reasoning_effort=bogus` starts a run and
|
|
87
|
+
prints `reasoning effort: bogus` in its own header without erroring. A typo therefore costs
|
|
88
|
+
a full run at some unintended level, with nothing in the output saying so. This is the main
|
|
89
|
+
reason forge validates efforts itself before dispatch.
|
|
90
|
+
|
|
91
|
+
**Prompt delivery uses stdin.** Forge copies the input once (including FIFOs), checks it
|
|
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, Claude and openclaude
|
|
94
|
+
`--output-format stream-json` and opencode `--format json` provide native usage; final responses
|
|
95
|
+
remain in `<role>.last`.
|
|
96
|
+
See the official [Codex non-interactive guide](https://learn.chatgpt.com/docs/non-interactive-mode)
|
|
97
|
+
and [Claude headless guide](https://code.claude.com/docs/en/headless).
|
|
98
|
+
|
|
99
|
+
`--approve-for-me` and `--dangerously-bypass-approvals-and-sandbox` are mutually exclusive,
|
|
100
|
+
and `--approve-for-me` additionally conflicts with an explicit `-s/--sandbox`.
|
|
101
|
+
|
|
102
|
+
**Native review** (`codex exec review`) has two constraints worth knowing:
|
|
103
|
+
|
|
104
|
+
- Its scope flags (`--uncommitted`, `--base`, `--commit`) are **mutually exclusive with a
|
|
105
|
+
custom PROMPT** — `error: the argument '--uncommitted' cannot be used with '[PROMPT]'`.
|
|
106
|
+
So native review cannot be told what the dwarf was asked to do. It judges whether the
|
|
107
|
+
code is correct, never whether it is the right change.
|
|
108
|
+
- It has **no `-C/--cd`**, unlike `codex exec`. It must be launched from inside the repo.
|
|
109
|
+
|
|
110
|
+
## claude / openclaude
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
<harness> -p --model <model> --add-dir <run-dir> --output-format stream-json --verbose --effort <effort> \
|
|
114
|
+
(--permission-mode acceptEdits --allowedTools Bash | --dangerously-skip-permissions)
|
|
115
|
+
[--disallowed-tools "Edit,Write,NotebookEdit"]
|
|
116
|
+
```
|
|
117
|
+
with the prompt delivered on **stdin**.
|
|
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
|
+
|
|
125
|
+
**The prompt must not go on argv when a variadic flag precedes it.** `--disallowed-tools`,
|
|
126
|
+
`--allowed-tools` and friends are variadic, so a following positional is consumed as
|
|
127
|
+
another value. In practice the prompt is parsed word by word into tool names — you get a
|
|
128
|
+
pile of `Permission deny rule "the" matches no known tool` warnings and then
|
|
129
|
+
`Error: Input must be provided either through stdin or as a prompt argument`. Passing it on
|
|
130
|
+
stdin sidesteps this entirely and also lifts the argv length limit, which matters once a
|
|
131
|
+
diff is inlined into a review prompt.
|
|
132
|
+
|
|
133
|
+
**Permission modes are not interchangeable, and no single mode is enough.** Re-measured on
|
|
134
|
+
this machine with a prompt that asks the agent to edit a file *and* run a real command:
|
|
135
|
+
|
|
136
|
+
| Mode | Can edit files | Can run shell |
|
|
137
|
+
|------|----------------|---------------|
|
|
138
|
+
| `acceptEdits` | yes | trivial commands only — prompts for the rest |
|
|
139
|
+
| `dontAsk` | — | **no** |
|
|
140
|
+
| `auto` | **no** — prompts for Edit | yes |
|
|
141
|
+
| `bypassPermissions` | yes | yes |
|
|
142
|
+
|
|
143
|
+
Both single-mode choices fail, and both fail *silently*:
|
|
144
|
+
|
|
145
|
+
- under `auto`, a dwarf writes nothing and ends with "I need your permission to edit f.py".
|
|
146
|
+
The harness still exits 0, so forge sees a successful run that produced an empty diff;
|
|
147
|
+
- under `acceptEdits` alone, a dwarf writes the code and then stalls on "awaiting approval
|
|
148
|
+
to run pytest", shipping unverified work while reporting success.
|
|
149
|
+
|
|
150
|
+
Beware that `acceptEdits` auto-approves *some* shell — `echo` goes through — so a smoke test
|
|
151
|
+
using a trivial command will wrongly suggest shell access works.
|
|
152
|
+
|
|
153
|
+
Forge therefore uses **`--permission-mode acceptEdits` plus `--allowedTools "Bash"`** for
|
|
154
|
+
non-yolo roles, which gives both. QA additionally gets
|
|
155
|
+
`--disallowed-tools "Edit,Write,NotebookEdit"`, which still overrides the mode's edit
|
|
156
|
+
permission for those tools. Bash can still write files. The solo and parallel runners use
|
|
157
|
+
disposable review repositories and fingerprint checks to invalidate changed artifacts.
|
|
158
|
+
|
|
159
|
+
`--add-dir <run-dir>` is required because the run directory deliberately sits outside the
|
|
160
|
+
repo; without it the agent refuses to open the artifacts forge just wrote for it
|
|
161
|
+
("outside the allowed working directory").
|
|
162
|
+
|
|
163
|
+
openclaude is a Claude Code fork and takes the same flags, plus `--provider` and an extra
|
|
164
|
+
`ultracode` effort tier. It routes to whatever provider it is authenticated against —
|
|
165
|
+
check with `openclaude auth status`, which on this machine reports
|
|
166
|
+
`{"authMethod": "third_party", "apiProvider": "codex"}`, meaning GPT models run through a
|
|
167
|
+
Claude-Code-shaped harness. That is the point of `sol:xhigh:openclaude`.
|
|
168
|
+
|
|
169
|
+
## opencode
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
opencode run -m <provider>/<model> --dir <repo> --variant <effort> --auto [--agent plan] --format json "<prompt>"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Models are always `provider/model`; `opencode models` lists what is configured.
|
|
176
|
+
|
|
177
|
+
`opencode run` exposes **no sandbox tier** — there is only `--auto`. Without it the run
|
|
178
|
+
blocks on a permission prompt that nothing can answer headlessly, so forge always passes
|
|
179
|
+
it. This means opencode's non-yolo mode is weaker than the other three harnesses': the
|
|
180
|
+
containment for a QA role comes from `--agent plan`, its built-in read-only agent, rather
|
|
181
|
+
than from a permission boundary.
|
|
182
|
+
|
|
183
|
+
**Credentials are the usual failure.** Being installed proves nothing. On this machine all
|
|
184
|
+
three configured routes currently fail, each differently:
|
|
185
|
+
|
|
186
|
+
- `github-copilot/*` → `Unauthorized: unauthorized: AuthenticateToken authentication failed`
|
|
187
|
+
- `opencode-go/*` → `Insufficient balance.`
|
|
188
|
+
- `opencode/*-free` → `UnknownError ... Unexpected server error`
|
|
189
|
+
|
|
190
|
+
`forge-dispatch.sh doctor` reports binaries on PATH; it cannot detect any of these, which
|
|
191
|
+
surface only mid-dispatch as an exit 4. When an opencode dispatch fails, show the user the
|
|
192
|
+
actual error — it is almost always auth or billing, and swapping to another backend would
|
|
193
|
+
hide a problem they need to fix.
|
|
194
|
+
|
|
195
|
+
## antigravity (agy)
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
agy --model <model> --print-timeout <t> [--effort <e>] --add-dir <repo> --add-dir <run-dir> \
|
|
199
|
+
(--mode accept-edits | --mode plan | --dangerously-skip-permissions) --print='<prompt>'
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The harness is named `antigravity` in forge; the binary is `agy`. This is how Gemini is
|
|
203
|
+
reached.
|
|
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
|
+
|
|
210
|
+
**`--print` must use the attached `=` form.** It is a Go flag with an optional value, so
|
|
211
|
+
written detached it consumes whatever token follows as its prompt. `agy --print
|
|
212
|
+
--print-timeout 120s --model X "real prompt"` fails with *"--print took --print-timeout as
|
|
213
|
+
its prompt, so the intended prompt was left as an argument and ignored"*. Forge puts
|
|
214
|
+
`--print=...` last so nothing can be captured after it.
|
|
215
|
+
|
|
216
|
+
**`--add-dir <repo>` is mandatory.** Antigravity is workspace-scoped, not cwd-scoped.
|
|
217
|
+
Without it the agent searches the wrong tree and the run dies on *"Find command timed out.
|
|
218
|
+
Use a more targeted search directory or pattern.: context deadline exceeded"* — which reads
|
|
219
|
+
like a performance problem and is actually a missing workspace.
|
|
220
|
+
|
|
221
|
+
**It cannot run shell commands without yolo.** Measured: `--mode accept-edits` and
|
|
222
|
+
`--sandbox` both deny every command, and the denial is *fatal* — the run aborts with
|
|
223
|
+
`permission check failed for command ... user denied permission`, leaving an empty diff
|
|
224
|
+
after consuming the quota. Only `--dangerously-skip-permissions` allows execution. Because
|
|
225
|
+
the standard dwarf prompt asks the model to run the project's tests, forge appends an
|
|
226
|
+
explicit "do not attempt commands" note to the prompt whenever antigravity runs without
|
|
227
|
+
yolo. This is the one harness where the non-yolo dwarf genuinely cannot verify its own work.
|
|
228
|
+
|
|
229
|
+
**Effort is validated, and varies per model** — unlike codex, which accepts anything:
|
|
230
|
+
|
|
231
|
+
| Model | Accepts |
|
|
232
|
+
|-------|---------|
|
|
233
|
+
| `gemini-3.7-flash`, `-3.6-flash`, `-3.5-flash` | `low`, `medium`, `high` |
|
|
234
|
+
| `gemini-3.1-pro` | `low`, `high` — **no medium** |
|
|
235
|
+
| `gpt-oss-120b` | `medium` only |
|
|
236
|
+
| `claude-sonnet-4-6`, `claude-opus-4-6-thinking` | none; passing `--effort` errors |
|
|
237
|
+
|
|
238
|
+
Two traps here. `agy models` lists effort-suffixed ids (`gemini-3.7-flash-low`), but a
|
|
239
|
+
suffixed id **conflicts** with `--effort`: *"--model gemini-3.7-flash-low conflicts with
|
|
240
|
+
--effort=high"*. Forge therefore registers the unsuffixed ids and sets effort via the flag,
|
|
241
|
+
which is what makes `gemini:high` mean the same thing here as on every other harness. And
|
|
242
|
+
`gemini-3.1-pro` has a hole in the middle rather than a low ceiling, which is why the
|
|
243
|
+
registry's effort column supports explicit sets and not just a `<=ceiling`.
|
|
244
|
+
|
|
245
|
+
QA runs under `--mode plan`, antigravity's read-only mode.
|
|
246
|
+
|
|
247
|
+
**Installing forge *into* antigravity works differently from the others.** agy ignores a
|
|
248
|
+
loose skill directory under `~/.gemini/skills` — it cannot see skills placed there at all,
|
|
249
|
+
including ones that were already sitting in that folder. Skills are only loaded as part of
|
|
250
|
+
a plugin: a directory containing `plugin.json` **at its root** (not in `.claude-plugin/`,
|
|
251
|
+
which is why `agy plugin validate` rejects a Claude plugin directly) plus a `skills/`
|
|
252
|
+
subdirectory. `forge-install.sh` generates that wrapper at `~/.gemini/forge-plugin` and runs
|
|
253
|
+
`agy plugin install`.
|
|
254
|
+
|
|
255
|
+
The catch: `agy plugin install` **copies** the files into `~/.gemini/config/plugins/forge/`
|
|
256
|
+
rather than referencing them. Every other harness tracks this directory live through a
|
|
257
|
+
symlink or a shim, so editing `SKILL.md` updates them all at once — antigravity is the
|
|
258
|
+
exception and keeps running whatever it copied. Re-run `forge-install.sh` after changing
|
|
259
|
+
the skill, or agy will silently run a stale version.
|
|
260
|
+
|
|
261
|
+
`--print-timeout` defaults to 5m in agy, which is short for real implementation work; forge
|
|
262
|
+
passes 30m and exposes `--agy-timeout` to change it.
|
|
263
|
+
|
|
264
|
+
## grok
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
grok --model <model> --prompt-file <prompt> --output-format streaming-messages-json \
|
|
268
|
+
--reasoning-effort <effort> (--permission-mode acceptEdits --allow Bash [--deny Edit --deny Write] | --always-approve)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The xAI Grok Build CLI, signed in with `grok login` (or `XAI_API_KEY`); `grok models` lists
|
|
272
|
+
what the account can use and each model's efforts live in `~/.grok/models_cache.json`. The
|
|
273
|
+
`grok` alias runs `grok-4.7` here and keeps its opencode route (`grok:high:opencode`, 4.6 via
|
|
274
|
+
GitHub Copilot) as a second row.
|
|
275
|
+
|
|
276
|
+
- **A headless run cannot ask, so it refuses and exits 0.** In the default mode an edit that
|
|
277
|
+
needs approval is skipped with `permission required: Edit calc.py was not run` on stderr:
|
|
278
|
+
the same silent empty diff claude gives. The dwarf therefore runs in `acceptEdits` with
|
|
279
|
+
`--allow Bash` (shell for tests). A reviewer gets the same plus `--deny Edit --deny Write`;
|
|
280
|
+
deny beats the mode and every allow, and Bash can still write files, as with claude.
|
|
281
|
+
- **Output is the claude stream.** `streaming-messages-json` ends in a `result` event with
|
|
282
|
+
`result`, `is_error` and the three disjoint token buckets, so `forge-runtime.py` reads the
|
|
283
|
+
final message, the tokens and an `is_error` failure exactly as it does for claude. An error
|
|
284
|
+
subtype can leave `result` empty and put the reason in `errors[]`, which is read instead.
|
|
285
|
+
- **A tool call that still needs approval cancels the whole session.** `--allow Bash` does not
|
|
286
|
+
cover every command. Seen in a real run: a `mkdir -p /tmp/… && cp …` into a directory outside
|
|
287
|
+
the worktree, and a multi-line `cd …/ui && \` command. Each printed `permission required: …
|
|
288
|
+
was not run`, and the session then ended with an `error_during_execution` result (`stop_reason`
|
|
289
|
+
`cancelled`), still exiting 0.
|
|
290
|
+
- **What forge records:** class `cancelled` in `<role>.infra`, with the refused command, and
|
|
291
|
+
a note in the run output.
|
|
292
|
+
- **It is not an infrastructure stop:** the dwarf's diff so far still goes to review, and a
|
|
293
|
+
reviewer cut off before its verdict is UNKNOWN, which re-runs only the review.
|
|
294
|
+
- **Untested:** whether `--permission-mode dontAsk` (deny without cancelling) together with
|
|
295
|
+
explicit Edit/Write/Bash allows would avoid the cancel. Nothing changes until that is
|
|
296
|
+
proven live.
|
|
297
|
+
- The prompt goes by `--prompt-file`, never argv, and stdin is `/dev/null`.
|
|
298
|
+
|
|
299
|
+
Verified live on 2026-10-19 with a `FORGE_OK` dispatch: exit 0, `permissionMode` acceptEdits,
|
|
300
|
+
the answer extracted from the `result` line, tokens recorded, nothing left running. The same
|
|
301
|
+
day, grok-4.7 dwarfs implemented and passed review on real tasks (the forge-ui integration run).
|
|
302
|
+
A grok reviewer has not been proven live yet.
|
|
303
|
+
|
|
304
|
+
## Installing forge into a harness
|
|
305
|
+
|
|
306
|
+
Each harness discovers skills differently, and the differences are not guessable — verify
|
|
307
|
+
with the listed check rather than trusting file placement.
|
|
308
|
+
|
|
309
|
+
| Harness | Mechanism | Verify with |
|
|
310
|
+
|---------|-----------|-------------|
|
|
311
|
+
| claude | `~/.claude/skills/forge/` (the source) | — |
|
|
312
|
+
| openclaude | symlink in `~/.openclaude/skills/` | `openclaude skills list --json` |
|
|
313
|
+
| codex | symlink in **`~/.codex/skills/`** | `codex exec "do you have a skill named forge?"` |
|
|
314
|
+
| opencode | **nothing** — it scans `~/.claude/` itself | `opencode debug skill` |
|
|
315
|
+
| antigravity | `agy plugin install` of a wrapper | `agy --print='/forge'` |
|
|
316
|
+
| grok | **nothing** — it lists `~/.claude/skills` itself | `skills` in a headless `init` line |
|
|
317
|
+
|
|
318
|
+
Three traps here:
|
|
319
|
+
|
|
320
|
+
- **Codex reads `~/.codex/skills`, not `~/.codex/prompts`.** A file in `prompts/` gives you
|
|
321
|
+
a slash command that expands as text; it does not register a skill.
|
|
322
|
+
- **opencode needs no install.** It scans `~/.claude/` and `~/.agents/` for skills unless
|
|
323
|
+
`OPENCODE_DISABLE_CLAUDE_CODE_SKILLS=1` is set, so it finds this directory on its own.
|
|
324
|
+
Adding a `~/.config/opencode/command/forge.md` on top produces a duplicate entry.
|
|
325
|
+
- **Antigravity ignores `~/.gemini/skills` entirely** — it cannot see skills placed there,
|
|
326
|
+
including any already in that folder. Skills load only inside a plugin: a directory with
|
|
327
|
+
`plugin.json` **at its root** (not in `.claude-plugin/`, which is why `agy plugin validate`
|
|
328
|
+
rejects a Claude plugin as-is) plus a `skills/` subdirectory. `forge-install.sh` generates
|
|
329
|
+
that wrapper at `~/.gemini/forge-plugin`.
|
|
330
|
+
|
|
331
|
+
And one ongoing caveat: `agy plugin install` **copies** into `~/.gemini/config/plugins/forge/`
|
|
332
|
+
rather than referencing the source. The other four track this directory live, so an edit to
|
|
333
|
+
`SKILL.md` reaches them immediately — antigravity keeps running whatever it copied. Re-run
|
|
334
|
+
`forge-install.sh` after any change, or agy silently runs a stale version.
|
|
335
|
+
|
|
336
|
+
### Frontmatter must be strict YAML
|
|
337
|
+
|
|
338
|
+
Codex parses skill frontmatter strictly and **refuses to load the skill** on any error,
|
|
339
|
+
logging `failed to load skill ...: invalid YAML` and then behaving as if it does not exist.
|
|
340
|
+
Claude Code and OpenClaude are lenient and load the same file fine, so a frontmatter bug can
|
|
341
|
+
sit unnoticed in four harnesses and break the fifth.
|
|
342
|
+
|
|
343
|
+
The one that bit this skill: a value that *starts* with a quoted scalar and then continues
|
|
344
|
+
unquoted is invalid YAML —
|
|
345
|
+
|
|
346
|
+
```yaml
|
|
347
|
+
argument-hint: "<goal>" --dwarf <alias> # invalid: text after the closing quote
|
|
348
|
+
argument-hint: '"<goal>" --dwarf <alias>' # valid: whole value quoted
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Note codex reports line numbers relative to the frontmatter body, i.e. excluding the opening
|
|
352
|
+
`---`, so its "line 3" is the file's line 4.
|
|
353
|
+
|
|
354
|
+
## Adding a harness
|
|
355
|
+
|
|
356
|
+
1. Add rows to `registry.tsv` mapping aliases to that harness's model ids.
|
|
357
|
+
2. Add its effort ladder to `ladder_for()` in `scripts/forge-dispatch.sh`, or leave it
|
|
358
|
+
empty for verbatim forwarding.
|
|
359
|
+
3. Add a `case` branch building its argv, covering: non-interactive flag, model, effort,
|
|
360
|
+
working directory, the non-yolo containment for each role, the yolo bypass, and how the
|
|
361
|
+
prompt is delivered.
|
|
362
|
+
4. Add it to `known_harness()` and to `doctor()`.
|
|
363
|
+
5. Prove it end to end before trusting it — a dwarf that really edits a scratch repo and a
|
|
364
|
+
QA that really finds a planted bug. Every trap documented above was found this way and
|
|
365
|
+
none of them were visible from `--help`.
|