agent-ultramode 0.1.4 → 0.2.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/README.md +113 -13
- package/cli.mjs +203 -14
- package/package.json +5 -7
- package/ultra.ts +167 -15
package/README.md
CHANGED
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
Took the [LLM-as-a-Verifier](https://github.com/llm-as-a-verifier/llm-as-a-verifier) paper (Kwok et al., 2026) and turned it into a simple `/ultra` command for coding agents.
|
|
8
8
|
|
|
9
|
+
> ### New in v2: repair climbs past the best-of-N ceiling, putting a mid-range flash model in the frontier's coding tier
|
|
10
|
+
>
|
|
11
|
+
> Picking the best of N attempts can never beat **oracle@N**. If none of the attempts solved the task, selection cannot invent a fix. That ceiling is where every best-of-N method stops.
|
|
12
|
+
>
|
|
13
|
+
> v2 adds a **verifier-guided repair pass**: the verifier critiques the winner, a repair runs against that critique, and the result is kept **only if it verifies better** (your tests when they exist, the verifier otherwise). Because repair can synthesize a fix no attempt produced, it goes past the ceiling. On a 24-task SWE-bench Lite slice it reaches **91.7%, above the 87.5% oracle@5** of its own candidate pool.
|
|
14
|
+
>
|
|
15
|
+
> Plus **adaptive early-exit**: N is a budget, not a quota. The moment an attempt passes your tests, ultra takes it and abandons the rest.
|
|
16
|
+
>
|
|
17
|
+
> Stack that on the verify step and a small, non-vision flash model reaches **90.4%** on Terminal-Bench 2.1's coding subset, inside the band of GPT-5.6 Sol (89.5%), Claude Opus 5 (89.1%) and Grok 4.6 (88.4%), at a fraction of the per-token cost. Ours is best-of-5 against their pass@1, so read it as reaching the tier, not a like-for-like beat.
|
|
18
|
+
>
|
|
19
|
+
> [How it works](#repair-and-early-exit-v2)
|
|
20
|
+
|
|
9
21
|
Instead of shipping the agent's first attempt, `ultra` runs your task **N times in isolated git worktrees**, in parallel, then uses the **same model** as a verifier to pick the best result. When it is confident (or when most attempts succeeded) it applies the winning diff to your working tree; when the attempts diverge and it is not sure, it hands you the top candidates. No cross-model dependency, no extra services, no first-attempt lottery.
|
|
10
22
|
|
|
11
23
|
And unlike most "add a verifier" posts, I benchmarked it before believing it. The receipts are below, good and bad.
|
|
@@ -18,14 +30,52 @@ And unlike most "add a verifier" posts, I benchmarked it before believing it. Th
|
|
|
18
30
|
|
|
19
31
|
## The receipts
|
|
20
32
|
|
|
21
|
-
Benchmarked
|
|
33
|
+
Benchmarked with **a small open model (DeepSeek V4 Flash 0731)** as both the agent and the verifier, best-of-5. Every number below is from a real run, good and bad. Full method, per-experiment setup and the honest scope notes are in the technical report: **[PAPER.md](./PAPER.md)**.
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
### Terminal-Bench 2.1: reasoned verify ties the paper, with far fewer calls
|
|
40
|
+
|
|
41
|
+
| Stage | Terminal-Bench 2.1 (89 tasks) |
|
|
42
|
+
|---|:---:|
|
|
43
|
+
| base@1 (single shot) | 78.7% |
|
|
44
|
+
| **ultra (reasoned verify)** | **87.6%** (78/89) |
|
|
45
|
+
| oracle@5 (selection ceiling) | 96.6% |
|
|
46
|
+
|
|
47
|
+
The reasoned verifier **ties the LLM-as-a-Verifier paper's logprob PPT** (88.0% +/- 0.6%) while using **7.5x fewer verifier calls** (576 vs 4,320) and **no logprobs** at all. Same accuracy, a fraction of the verifier budget, and it runs on any OpenAI-compatible endpoint.
|
|
48
|
+
|
|
49
|
+
### The non-vision handicap (and the fair arena)
|
|
50
|
+
|
|
51
|
+
DeepSeek V4 Flash has **no vision**. Terminal-Bench 2.1 mixes image tasks in with the coding ones, so the blended score carries a penalty a text-only model can never pay down:
|
|
52
|
+
|
|
53
|
+
| Terminal-Bench 2.1 split | tasks | ultra |
|
|
54
|
+
|---|:---:|:---:|
|
|
55
|
+
| Vision tasks | 12 | 58% |
|
|
56
|
+
| **Coding tasks** | **77** | **90.4%** |
|
|
57
|
+
| Blended (headline) | 89 | 87.6% |
|
|
58
|
+
|
|
59
|
+
On the **coding subset**, the fair arena for a small, non-vision flash model, ultra reaches **90.4%**.
|
|
22
60
|
|
|
23
|
-
|
|
24
|
-
|---|:---:|:---:|:---:|
|
|
25
|
-
| All 15 tasks | 24% | **33%** | 40% |
|
|
26
|
-
| 4 recoverable tasks | 40% | **75%** | rescued 3 of 4 |
|
|
61
|
+
For context on the full 89-task set, as reported by Artificial Analysis (pass@1, avg of 3): GPT-5.6 Sol 89.5%, Claude Opus 5 89.1%, Grok 4.6 88.4%. Their coding-subset numbers are unpublished. So this is not a beat: ours is best-of-5 and theirs is pass@1. The honest framing is that a small, non-vision flash model reaches the **frontier's coding tier at a fraction of the cost**.
|
|
27
62
|
|
|
28
|
-
|
|
63
|
+
### SWE-bench Lite: repair climbs past the selection ceiling
|
|
64
|
+
|
|
65
|
+
Pure code repair, no vision, 24 django/pytest tasks, N=5:
|
|
66
|
+
|
|
67
|
+
| Stage | SWE-bench Lite (24 tasks) |
|
|
68
|
+
|---|:---:|
|
|
69
|
+
| base@1 (single shot) | 70.8% |
|
|
70
|
+
| ultra (reasoned verify) | 75.0% |
|
|
71
|
+
| oracle@5 (selection ceiling) | 87.5% |
|
|
72
|
+
| **ultra + repair** | **91.7%** (22/24) |
|
|
73
|
+
|
|
74
|
+
This is the clean "past the ceiling" result. Selection alone can never beat the best attempt it was handed (oracle@5 = 87.5%). Best-of-N **repair** rescued 2 tasks none of the five attempts solved, landing **above** the oracle ceiling at 91.7%.
|
|
75
|
+
|
|
76
|
+
### The trace-reading ceiling
|
|
77
|
+
|
|
78
|
+
The verifier is strong on code diffs and weak on **self-reported terminal outcomes**. It saturates on convincing-but-wrong self-reports: on `extract-elf`, a failing run reported "4102 entries, zero mismatches" when 698 was the correct answer, and the verifier believed it. Repair plus a real test overcomes this; a verifier reading a trace alone cannot. This is why SWE-bench (pure code diffs) is the clean signal and Terminal-Bench is muddied by both vision and self-report.
|
|
29
79
|
|
|
30
80
|
## How the competition works
|
|
31
81
|
|
|
@@ -60,13 +110,38 @@ Overall the verifier captured about **56% of the pass@1 to pass@5 headroom**. On
|
|
|
60
110
|
|
|
61
111
|
Progress streams live: phase titles in the web UI tool card, and toasts in the terminal.
|
|
62
112
|
|
|
113
|
+
## Repair and early-exit (v2)
|
|
114
|
+
|
|
115
|
+
Two additions in v2, both designed so they can only help.
|
|
116
|
+
|
|
117
|
+
**Verifier-guided best-of-N repair (beyond the best-of-N ceiling).** Selection alone can never do better than the best attempt it was handed (its ceiling is oracle@N). After the tournament picks a winner, `ultra` critiques it and runs up to `--repair-n` guided repair passes (default 2), keeping the **first pass that passes your tests**; if none pass, it keeps the tournament winner unchanged. The keep-check uses your repo's own tests if there are any, otherwise the reasoned verifier. It is regression-safe and **never worse than the winner**: a repair is adopted only when it clears the bar, so repair can solve tasks no single attempt did while a bad repair is simply discarded. In a SWE-bench Lite run this reached 91.7%, above the 87.5% oracle@5 ceiling, by rescuing two tasks none of the five attempts solved.
|
|
118
|
+
|
|
119
|
+
**Adaptive early-exit (N as an upper bound).** N is a budget, not a quota. The moment an attempt's diff **passes your tests**, `ultra` takes it as the verified winner, abandons the still-running attempts, and skips the tournament and repair. Only this hard signal stops early; low verifier confidence never does, so you never trade quality for speed. With no detectable test command it runs all N, exactly as before. Turn repair off with `--no-repair` (or `ULTRA_REPAIR=0`); turn early-exit off with `--no-early-exit` (or `ULTRA_NO_EARLY_EXIT=1`).
|
|
120
|
+
|
|
63
121
|
## Install
|
|
64
122
|
|
|
123
|
+
Pick your agent. Each is one command, and each gives you the same native `/ultra <task>`.
|
|
124
|
+
|
|
125
|
+
**Claude Code**
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
mkdir -p ~/.claude/commands
|
|
129
|
+
curl -fsSL https://raw.githubusercontent.com/maverick-tr/agent-ultramode/main/install/ultra.md -o ~/.claude/commands/ultra.md
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Grok**
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
grok plugin install maverick-tr/agent-ultramode --trust
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**opencode**
|
|
139
|
+
|
|
65
140
|
```sh
|
|
66
141
|
opencode plugin agent-ultramode
|
|
67
142
|
```
|
|
68
143
|
|
|
69
|
-
|
|
144
|
+
Then run it in any session:
|
|
70
145
|
|
|
71
146
|
```text
|
|
72
147
|
/ultra fix the failing test in foo/bar
|
|
@@ -74,7 +149,17 @@ That is the whole setup. With no options it drafts and verifies with your **curr
|
|
|
74
149
|
|
|
75
150
|
`ultra` branches attempts off `HEAD`, so run it inside a git repo with at least one commit.
|
|
76
151
|
|
|
77
|
-
**
|
|
152
|
+
On **Claude Code** and **Grok**, attempts run as parallel subagents in the host itself (Grok shows them live in the Tasks pane, Ctrl+G). On **opencode** they run as isolated git worktrees and the winning diff is applied to your working tree. Same loop either way: fan out N, verify, repair the winner, keep it only if it is better.
|
|
153
|
+
|
|
154
|
+
### More install options
|
|
155
|
+
|
|
156
|
+
**Claude Code, as a full plugin** (instead of the single file above):
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
/plugin marketplace add maverick-tr/agent-ultramode
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**opencode, pinning a specific model (optional).** To draft and verify with a model other than the session one:
|
|
78
163
|
|
|
79
164
|
```jsonc
|
|
80
165
|
{ "plugin": [ ["agent-ultramode", { "model": "myprovider/my-model" }] ] }
|
|
@@ -105,6 +190,10 @@ Every option also reads from an `ULTRA_*` env var.
|
|
|
105
190
|
| `effort` | `"none"` | `reasoning_effort` for the verifier calls (kept low so judging stays fast) |
|
|
106
191
|
| `concurrency` | `6` | how many attempts run at once |
|
|
107
192
|
| `agentTimeout` | `600000` | per-attempt timeout in ms |
|
|
193
|
+
| `repair` | `true` | run verifier-guided repair on the winner, kept only if it verifies better |
|
|
194
|
+
| `repairN` | `2` | repair passes to run (1 to 5); keeps the first that passes your tests, else the winner |
|
|
195
|
+
| `test` | auto-detected | test command for the repair keep-check and early-exit (npm / pytest / cargo / go if unset) |
|
|
196
|
+
| `earlyExit` | `true` | stop as soon as an attempt passes the test command, and apply it |
|
|
108
197
|
|
|
109
198
|
## Use without opencode (the CLI)
|
|
110
199
|
|
|
@@ -140,9 +229,17 @@ The 6 attempts round-robin across the three agents, and the verifier picks the b
|
|
|
140
229
|
|
|
141
230
|
The verifier needs an OpenAI-compatible endpoint: by default it reads `OPENAI_API_KEY` and `OPENAI_BASE_URL`, or pass `--api-key` / `--base-url` / `--verify-model`. Run `agent-ultramode --help` for all options.
|
|
142
231
|
|
|
232
|
+
## How the hosts differ
|
|
233
|
+
|
|
234
|
+
One plugin serves all three, each using its host's own subagent primitive: Grok's `spawn_subagent` with worktree isolation, Claude Code's `Agent` tool, and opencode's own worktree runner. The best-of-N, verify and repair loop is identical; only the fan-out mechanism and the progress UI change. Both the Claude Code and Grok paths are verified end to end.
|
|
235
|
+
|
|
236
|
+
## DeepSeek Harness (experimental)
|
|
237
|
+
|
|
238
|
+
A native DeepSeek Harness `/ultra` plugin exists but is not recommended yet: the harness surfaces each attempt as a separate child session, which is noisy in the web UI. Use the Grok or Claude Code plugins above for a clean native `/ultra` today.
|
|
239
|
+
|
|
143
240
|
## Why I built this, and the honest story
|
|
144
241
|
|
|
145
|
-
I wanted to know if best-of-N verification could squeeze real quality out of a
|
|
242
|
+
I wanted to know if best-of-N verification could squeeze real quality out of a small-but-capable model without reaching for a bigger, pricier one. So, ran it on real Terminal-Bench tasks and let the numbers decide. What I found:
|
|
146
243
|
|
|
147
244
|
- **Planning-first best-of-N does nothing for execution tasks.** Drafting N plans, picking the best, then executing once: **0 out of 5** outcomes changed on Terminal-Bench, at 3 to 10 times the cost. It amplifies effort, not capability. I dropped it.
|
|
148
245
|
- **Best-of-N over full trajectories plus a same-model verifier does work.** This is the version that ships.
|
|
@@ -153,19 +250,22 @@ It significantly helps on the tasks that matter, and I can point at the per-task
|
|
|
153
250
|
|
|
154
251
|
## The honest caveats (because benchmarks lie by omission)
|
|
155
252
|
|
|
156
|
-
1. **
|
|
157
|
-
2. **
|
|
158
|
-
3. **
|
|
253
|
+
1. **The model has no vision, and image tasks are out of fair reach.** DeepSeek V4 Flash is text-only, so the 12 Terminal-Bench 2.1 vision tasks (58%) drag the blended score down in a way no amount of verifying can fix. The **coding subset (90.4%)** is the fair arena for a non-vision model; read the blended 87.6% with that in mind.
|
|
254
|
+
2. **The frontier comparison is not apples to apples.** Our headline numbers are **best-of-5**; the GPT-5.6 / Opus 5 / Grok 4.6 numbers are **pass@1**. So "reaches the frontier's coding tier" is a cost-and-capability framing, not a claim that this small model beats them head to head.
|
|
255
|
+
3. **The verifier hits a trace-reading ceiling.** It reasons well over code diffs but is fooled by convincing-but-wrong self-reports (the `extract-elf` run that claimed "4102 entries, zero mismatches" when 698 was correct). Selection over self-reported terminal outcomes saturates; only repair plus a real test breaks past it.
|
|
256
|
+
4. **SWE-bench is the clean signal; Terminal-Bench is muddied.** SWE-bench Lite is pure code repair with real tests, which is why repair cleanly beats the oracle@5 ceiling there (91.7% > 87.5%). Terminal-Bench mixes in vision and self-reported outcomes, so its blended number carries penalties that have nothing to do with the verifier.
|
|
159
257
|
|
|
160
258
|
## What this likely means at scale
|
|
161
259
|
|
|
162
|
-
|
|
260
|
+
On any full benchmark most tasks have no headroom: the model either always solves them or never does, and a verifier changes neither. The lift concentrates on the minority of tasks the model *sometimes* solves, where selection can grab the passing attempt and repair can climb one rung higher. So on Terminal-Bench 2.1 the base-to-verify jump is a real **+8.9 points blended** (78.7% to 87.6%), and on the coding subset it lands at **90.4%**, but the honest reading is that the dramatic per-task gains average out into a moderate headline lift, larger on repair-friendly pure-code sets like SWE-bench than on mixed sets diluted by vision. Average lift moderate, per-recoverable-task lift large. Same fact, two views.
|
|
163
261
|
|
|
164
262
|
## Roadmap
|
|
165
263
|
|
|
166
264
|
- [x] **Standalone CLI** (`npx agent-ultramode`) so the loop runs anywhere, with any agent, no opencode required.
|
|
167
265
|
- [x] **Multiple models in one pass** (repeatable `--agent`): spread attempts across different models, one neutral verifier picks the best.
|
|
168
266
|
- [x] **OpenCode, Claude Code, and cline** verified end to end.
|
|
267
|
+
- [x] **Verifier-guided best-of-N repair** (v2): up to `--repair-n` critique-guided passes on the winner, keeping the first that passes your tests. Beats the oracle@N ceiling on SWE-bench Lite (91.7% vs 87.5%).
|
|
268
|
+
- [x] **Adaptive early-exit** (v2): N becomes an upper bound; a test-passing attempt ends the run early with no quality trade-off.
|
|
169
269
|
- [ ] **First-class agent integrations** (tuned defaults and a benchmark number) for Grok, Pi, and Codex.
|
|
170
270
|
- [ ] Native slash-command or MCP packaging per agent. Contributions welcome.
|
|
171
271
|
|
package/cli.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Node 18+ (uses global fetch and node:util parseArgs). Zero dependencies.
|
|
10
10
|
import { execFile } from "node:child_process"
|
|
11
11
|
import { promisify } from "node:util"
|
|
12
|
-
import { mkdtemp, rm, writeFile, mkdir } from "node:fs/promises"
|
|
12
|
+
import { mkdtemp, rm, writeFile, mkdir, readFile } from "node:fs/promises"
|
|
13
13
|
import { tmpdir } from "node:os"
|
|
14
14
|
import { join } from "node:path"
|
|
15
15
|
import { parseArgs } from "node:util"
|
|
@@ -39,8 +39,32 @@ OPTIONS
|
|
|
39
39
|
--repo <path> repo to run in (default: current directory)
|
|
40
40
|
--concurrency <int> attempts to run at once (default 6)
|
|
41
41
|
--effort <level> verifier reasoning_effort (default none)
|
|
42
|
+
--test <cmd> test command for the repair keep-check (else auto-detected:
|
|
43
|
+
npm test / pytest / cargo / go). Passing exit code = ok.
|
|
44
|
+
--repair-agent <cmd> agent for the repair pass ("{task}" substituted; default: --agent)
|
|
45
|
+
--repair-n <int> repair attempts to run, 1 to 5 (default 2; env ULTRA_REPAIR_N).
|
|
46
|
+
Keeps the best that verifies; never worse than the winner.
|
|
47
|
+
--no-repair disable the verifier-guided repair pass (on by default)
|
|
48
|
+
--no-early-exit disable adaptive early-exit (on by default when a test
|
|
49
|
+
command is available)
|
|
42
50
|
-h, --help show this help
|
|
43
51
|
|
|
52
|
+
REPAIR (on by default; ULTRA_REPAIR=0 or --no-repair to disable)
|
|
53
|
+
After the tournament picks a winner, ultra critiques it and runs --repair-n
|
|
54
|
+
guided repair passes (default 2), each in its own worktree seeded with the
|
|
55
|
+
winner's diff and the same critique, then keeps the best that verifies:
|
|
56
|
+
with a test command, the first repair that PASSES the tests; otherwise the
|
|
57
|
+
first repair the reasoned verifier clearly prefers over the winner. If none
|
|
58
|
+
qualify, the winner is kept. So repair can lift the result past the best-of-N
|
|
59
|
+
ceiling, and the applied result is never worse than the tournament winner.
|
|
60
|
+
|
|
61
|
+
ADAPTIVE EARLY-EXIT (on by default; --no-early-exit or ULTRA_NO_EARLY_EXIT=1 to disable)
|
|
62
|
+
N is an upper bound. If an attempt PASSES the repo's tests (--test or an
|
|
63
|
+
auto-detected npm/pytest/cargo/go command), ultra takes it as a verified
|
|
64
|
+
winner, abandons the still-running attempts, and skips the tournament and
|
|
65
|
+
repair. Only this hard signal stops early; low verifier confidence never does,
|
|
66
|
+
so the result can never be worse than running all N.
|
|
67
|
+
|
|
44
68
|
EXAMPLES
|
|
45
69
|
# default: opencode as the per-attempt agent, verify with your OpenAI key
|
|
46
70
|
agent-ultramode "fix the failing test in foo/bar"
|
|
@@ -131,6 +155,71 @@ async function tournament(C, task, summaries) {
|
|
|
131
155
|
return { ranked, conf: ratio(ranked[0]) - ratio(ranked[1]) }
|
|
132
156
|
}
|
|
133
157
|
|
|
158
|
+
// --- verifier-guided repair (generic: never benchmark-specific) ------------
|
|
159
|
+
// After the tournament picks a winner, critique it and run one guided repair
|
|
160
|
+
// pass, then KEEP the repair only when it verifies as better (repo's own tests
|
|
161
|
+
// if available, else the reasoned verifier). So repair can only help, not hurt.
|
|
162
|
+
|
|
163
|
+
async function critique(C, task, diff, log) {
|
|
164
|
+
const prompt =
|
|
165
|
+
`A coding task and the patch a verifier selected as the best of several attempts. You are a strict ` +
|
|
166
|
+
`reviewer. In 3-5 sentences name the MOST LIKELY remaining problems: missed edge cases, incomplete ` +
|
|
167
|
+
`coverage, wrong root cause, or regressions it could introduce. If it looks fully correct, say so and ` +
|
|
168
|
+
`name the single thing most worth double-checking. Be concrete and actionable.\n\n` +
|
|
169
|
+
`TASK:\n${task}\n\nPATCH:\n${diff.slice(0, 6000)}\n\nAGENT LOG (tail):\n${(log || "").slice(-1200)}\n\nReview:`
|
|
170
|
+
try { return (await chat(C, prompt)).trim().slice(0, 1500) } catch { return "" }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const XDG_ENV = (d) => ({
|
|
174
|
+
...process.env, XDG_DATA_HOME: d, XDG_STATE_HOME: d, XDG_CACHE_HOME: d,
|
|
175
|
+
OPENCODE_DISABLE_DEFAULT_PLUGINS: "1", OPENCODE_DISABLE_AUTOUPDATE: "1", OPENCODE_DISABLE_MODELS_FETCH: "1",
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// run an agent command inside a fresh worktree (optionally pre-seeded with a
|
|
179
|
+
// base diff), return the full diff off `base` + the log tail.
|
|
180
|
+
async function agentInWorktree(repo, base, work, tag, agentCmd, taskText, seedDiff, timeout) {
|
|
181
|
+
const wt = join(work, tag)
|
|
182
|
+
await git(repo, "worktree", "add", "--detach", wt, base)
|
|
183
|
+
if (seedDiff && seedDiff.trim()) {
|
|
184
|
+
const p = join(work, `${tag}.seed.patch`); await writeFile(p, seedDiff)
|
|
185
|
+
await git(wt, "apply", "--3way", p).catch(() => {})
|
|
186
|
+
}
|
|
187
|
+
const cmd = agentCmd.replace("{task}", taskText.replace(/"/g, '\\"'))
|
|
188
|
+
const d = join(work, `${tag}-xdg`); await mkdir(d, { recursive: true }).catch(() => {})
|
|
189
|
+
let log = ""
|
|
190
|
+
try {
|
|
191
|
+
const { stdout, stderr } = await execFileP("bash", ["-lc", `exec </dev/null; ${cmd}`], { cwd: wt, env: XDG_ENV(d), timeout, maxBuffer: 32 * 1024 * 1024 })
|
|
192
|
+
log = (stdout || "") + (stderr || "")
|
|
193
|
+
} catch (e) { log = `agent error: ${e?.message || e}` }
|
|
194
|
+
await git(wt, "add", "-A").catch(() => {})
|
|
195
|
+
const diff = await git(wt, "diff", "--cached").catch(() => "")
|
|
196
|
+
await git(repo, "worktree", "remove", "--force", wt).catch(() => {})
|
|
197
|
+
return { diff, log }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// true iff `testCmd` exits 0 in a fresh worktree with `diff` applied.
|
|
201
|
+
async function runTests(repo, base, work, tag, diff, testCmd, timeout) {
|
|
202
|
+
const wt = join(work, `t-${tag}`)
|
|
203
|
+
await git(repo, "worktree", "add", "--detach", wt, base)
|
|
204
|
+
try {
|
|
205
|
+
if (diff.trim()) { const p = join(work, `t-${tag}.patch`); await writeFile(p, diff); await git(wt, "apply", "--3way", p).catch(() => {}) }
|
|
206
|
+
await execFileP("bash", ["-lc", `exec </dev/null; ${testCmd}`], { cwd: wt, env: process.env, timeout, maxBuffer: 32 * 1024 * 1024 })
|
|
207
|
+
return true
|
|
208
|
+
} catch { return false }
|
|
209
|
+
finally { await git(repo, "worktree", "remove", "--force", wt).catch(() => {}) }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// auto-detect a repo test command if the user didn't pass --test.
|
|
213
|
+
async function detectTestCmd(repo) {
|
|
214
|
+
const has = async (f) => !!(await readFile(join(repo, f), "utf8").catch(() => ""))
|
|
215
|
+
const pkg = await readFile(join(repo, "package.json"), "utf8").catch(() => "")
|
|
216
|
+
if (pkg && /"test"\s*:/.test(pkg) && !/no test specified/.test(pkg)) return "npm test --silent"
|
|
217
|
+
if (await has("pytest.ini") || await has("pyproject.toml") || await has("setup.cfg") || await has("tox.ini")) return "python -m pytest -q"
|
|
218
|
+
if (await has("Cargo.toml")) return "cargo test -q"
|
|
219
|
+
if (await has("go.mod")) return "go test ./..."
|
|
220
|
+
return ""
|
|
221
|
+
}
|
|
222
|
+
|
|
134
223
|
async function main() {
|
|
135
224
|
const { values, positionals } = parseArgs({
|
|
136
225
|
allowPositionals: true,
|
|
@@ -146,6 +235,11 @@ async function main() {
|
|
|
146
235
|
repo: { type: "string" },
|
|
147
236
|
concurrency: { type: "string" },
|
|
148
237
|
effort: { type: "string" },
|
|
238
|
+
"no-repair": { type: "boolean" },
|
|
239
|
+
"no-early-exit": { type: "boolean" },
|
|
240
|
+
"repair-agent": { type: "string" },
|
|
241
|
+
"repair-n": { type: "string" },
|
|
242
|
+
test: { type: "string" },
|
|
149
243
|
help: { type: "boolean", short: "h" },
|
|
150
244
|
},
|
|
151
245
|
})
|
|
@@ -160,6 +254,11 @@ async function main() {
|
|
|
160
254
|
const conf = Number(values.conf ?? 0.34)
|
|
161
255
|
const cc = int(values.concurrency, 6, 1, 12)
|
|
162
256
|
const agentTimeout = 600000
|
|
257
|
+
const doRepair = !values["no-repair"] && process.env.ULTRA_REPAIR !== "0"
|
|
258
|
+
const repairN = int(values["repair-n"] ?? process.env.ULTRA_REPAIR_N, 2, 1, 5)
|
|
259
|
+
const earlyExit = !values["no-early-exit"] && process.env.ULTRA_NO_EARLY_EXIT !== "1"
|
|
260
|
+
const repairAgent = values["repair-agent"] || (agents[0] || 'opencode run "{task}"')
|
|
261
|
+
let testCmd = values.test ?? process.env.ULTRA_TEST ?? ""
|
|
163
262
|
const C = {
|
|
164
263
|
verifyModel: values["verify-model"] || process.env.ULTRA_VERIFY_MODEL || process.env.OPENAI_MODEL || "gpt-4o-mini",
|
|
165
264
|
baseURL: values["base-url"] || process.env.OPENAI_BASE_URL || process.env.ULTRA_BASE_URL || "https://api.openai.com/v1",
|
|
@@ -204,26 +303,63 @@ async function main() {
|
|
|
204
303
|
}
|
|
205
304
|
const limit = pLimit(Math.max(1, Math.min(cc, n)))
|
|
206
305
|
const esc = task.replace(/"/g, '\\"')
|
|
207
|
-
|
|
306
|
+
|
|
307
|
+
// Adaptive early-exit (#141): N is an upper bound. If a completed attempt
|
|
308
|
+
// PASSES the repo's tests (a hard, non-regressible signal), take it as the
|
|
309
|
+
// verified winner, abandon the still-running/queued attempts, and skip the
|
|
310
|
+
// tournament + repair. Low verifier confidence never stops early, so the
|
|
311
|
+
// result can never be worse than running all N.
|
|
312
|
+
if (earlyExit && testCmd === "") testCmd = await detectTestCmd(repo)
|
|
313
|
+
const exitTestCmd = earlyExit ? testCmd : ""
|
|
314
|
+
if (exitTestCmd) err(`>>> early-exit armed: stop as soon as an attempt passes '${exitTestCmd}'`)
|
|
315
|
+
const ac = new AbortController()
|
|
316
|
+
let verified = null
|
|
317
|
+
const outs = new Array(n)
|
|
318
|
+
|
|
319
|
+
const runAgent = (wt, i) => new Promise((resolve) => {
|
|
208
320
|
const cmd = agents[i % agents.length].replace("{task}", esc)
|
|
209
321
|
const d = join(work, `xdg-${i}`)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
322
|
+
mkdir(d, { recursive: true }).catch(() => {}).finally(() => {
|
|
323
|
+
const env = {
|
|
324
|
+
...process.env,
|
|
325
|
+
XDG_DATA_HOME: d, XDG_STATE_HOME: d, XDG_CACHE_HOME: d,
|
|
326
|
+
OPENCODE_DISABLE_DEFAULT_PLUGINS: "1", OPENCODE_DISABLE_AUTOUPDATE: "1", OPENCODE_DISABLE_MODELS_FETCH: "1",
|
|
327
|
+
}
|
|
328
|
+
const child = execFile("bash", ["-lc", `exec </dev/null; ${cmd}`],
|
|
329
|
+
{ cwd: wt, env, timeout: agentTimeout, maxBuffer: 32 * 1024 * 1024, detached: true },
|
|
330
|
+
(e, stdout, stderr) => resolve(((stdout || "") + (stderr || "")) || (e ? `agent error: ${e?.message || e}` : "")))
|
|
331
|
+
// if a verified winner is found elsewhere, kill this attempt's process group
|
|
332
|
+
ac.signal.addEventListener("abort", () => { try { process.kill(-child.pid, "SIGTERM") } catch {} }, { once: true })
|
|
333
|
+
})
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
await Promise.all(worktrees.map((wt, i) => limit(async () => {
|
|
337
|
+
if (ac.signal.aborted) return // verified winner already found; don't start queued attempts
|
|
338
|
+
const log = await runAgent(wt, i)
|
|
221
339
|
await git(wt, "add", "-A").catch(() => {})
|
|
222
340
|
const diff = await git(wt, "diff", "--cached").catch(() => "")
|
|
341
|
+
outs[i] = { diff, log, summary: `AGENT LOG (tail):\n${log.slice(-1500)}\n\nDIFF:\n${diff.slice(0, 6000) || "(no changes)"}` }
|
|
223
342
|
err(` attempt ${i} [${binOf(agents[i % agents.length])}]: ${diff ? diff.length + " diff chars" : "no changes"}`)
|
|
224
|
-
|
|
343
|
+
if (exitTestCmd && !verified && diff.trim()) {
|
|
344
|
+
const pass = await runTests(repo, base, work, `ee-${i}`, diff, exitTestCmd, agentTimeout)
|
|
345
|
+
if (pass && !verified) { verified = { i, ...outs[i] }; err(` ✔ attempt ${i} PASSED tests; abandoning the rest`); ac.abort() }
|
|
346
|
+
}
|
|
225
347
|
})))
|
|
226
348
|
|
|
349
|
+
// verified fast path: a test-passing attempt is correct by the repo's own
|
|
350
|
+
// criterion, so apply it directly (no tournament, no repair, no regression).
|
|
351
|
+
if (verified && verified.diff.trim()) {
|
|
352
|
+
const patch = join(work, "winner.patch")
|
|
353
|
+
await writeFile(patch, verified.diff)
|
|
354
|
+
try {
|
|
355
|
+
await git(repo, "apply", "--3way", patch)
|
|
356
|
+
console.log(`\n🏆 attempt ${verified.i} passed the repo's tests (${exitTestCmd}); applied it and abandoned the rest. Review, then commit.`)
|
|
357
|
+
} catch (e) {
|
|
358
|
+
console.log(`\nattempt ${verified.i} passed tests but the patch did not apply cleanly (${e?.message || e}). The diff:\n\n${verified.diff.slice(0, 8000)}`)
|
|
359
|
+
}
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
|
|
227
363
|
const diffs = outs.map((o) => o.diff)
|
|
228
364
|
const summaries = outs.map((o) => o.summary)
|
|
229
365
|
if (diffs.every((d) => !d.trim())) {
|
|
@@ -234,6 +370,59 @@ async function main() {
|
|
|
234
370
|
err(`>>> verify: probabilistic pivot tournament (${C.verifyModel}) ...`)
|
|
235
371
|
const { ranked, conf: margin } = await tournament(C, task, summaries)
|
|
236
372
|
const best = ranked[0]
|
|
373
|
+
|
|
374
|
+
// --- repair: critique the winner, run repairN guided passes, keep the best that verifies.
|
|
375
|
+
// Best-of-N repair (regression-safe): fan out repairN attempts off `base`, each seeded with
|
|
376
|
+
// the winner's diff and the SAME critique injected (one critique, reused). Keep-policy:
|
|
377
|
+
// * with tests: keep the FIRST repair that PASSES the repo's tests; else keep the winner.
|
|
378
|
+
// (a passing repair is correct by the repo's own criterion, so it cannot regress.)
|
|
379
|
+
// * without tests: keep the FIRST repair the reasoned verifier CLEARLY prefers over the
|
|
380
|
+
// current winner (same >k/2 threshold as before); else keep the winner.
|
|
381
|
+
// INVARIANT: the final applied result is never worse than the tournament winner.
|
|
382
|
+
if (doRepair && diffs[best].trim()) {
|
|
383
|
+
err(`>>> repair: critique + ${repairN} guided pass(es) on the winner (keep the best that verifies) ...`)
|
|
384
|
+
const crit = await critique(C, task, diffs[best], outs[best].log)
|
|
385
|
+
const rtask =
|
|
386
|
+
`${task}\n\n--- A previous attempt (already applied to your working tree) produced a partial ` +
|
|
387
|
+
`solution. A reviewer flagged the issues below. Improve and COMPLETE it, keeping what is correct; ` +
|
|
388
|
+
`verify before finishing. ---\nREVIEWER NOTES:\n${crit}`
|
|
389
|
+
if (testCmd === "") testCmd = await detectTestCmd(repo)
|
|
390
|
+
// run the repairN attempts in parallel, honoring the concurrency limiter (cc).
|
|
391
|
+
const rlimit = pLimit(C.cc)
|
|
392
|
+
const reps = await Promise.all(Array.from({ length: repairN }, (_, r) => rlimit(async () => {
|
|
393
|
+
const rr = await agentInWorktree(repo, base, work, `repair-${r}`, repairAgent, rtask, diffs[best], agentTimeout)
|
|
394
|
+
const rsum = `AGENT LOG (tail):\n${rr.log.slice(-1500)}\n\nDIFF:\n${rr.diff.slice(0, 6000) || "(no changes)"}`
|
|
395
|
+
err(` repair ${r} [${binOf(repairAgent)}]: ${rr.diff ? rr.diff.length + " diff chars" : "no changes"}`)
|
|
396
|
+
return { diff: rr.diff, summary: rsum }
|
|
397
|
+
})))
|
|
398
|
+
// only real, changed candidates are eligible.
|
|
399
|
+
const cands = reps.filter((x) => x.diff.trim() && x.diff !== diffs[best])
|
|
400
|
+
let kept = false, why = ""
|
|
401
|
+
if (!cands.length) { err(` repair produced no new change; winner kept`) }
|
|
402
|
+
else if (testCmd) {
|
|
403
|
+
// regression-safe: keep the FIRST repair that PASSES the repo's own tests.
|
|
404
|
+
err(` keep-check: running tests (${testCmd}) on ${cands.length} repair candidate(s) ...`)
|
|
405
|
+
for (let r = 0; r < cands.length; r++) {
|
|
406
|
+
const pass = await runTests(repo, base, work, `rep-${r}`, cands[r].diff, testCmd, agentTimeout)
|
|
407
|
+
if (pass) { diffs[best] = cands[r].diff; summaries[best] = cands[r].summary; kept = true; why = `repair ${r} passes the tests`; break }
|
|
408
|
+
}
|
|
409
|
+
if (!kept) why = "no repair passed the tests"
|
|
410
|
+
} else {
|
|
411
|
+
// no tests: keep the FIRST repair the verifier CLEARLY prefers over the current winner.
|
|
412
|
+
const preferRepair = async (rsum) => { // reasoned verifier: keep repair only if it clearly wins
|
|
413
|
+
const limit = pLimit(C.cc)
|
|
414
|
+
const votes = await Promise.all(Array.from({ length: C.k }, () => judge(C, limit, task, summaries[best], rsum)))
|
|
415
|
+
return votes.filter((v) => v === "B").length > C.k / 2 // B = repaired
|
|
416
|
+
}
|
|
417
|
+
for (let r = 0; r < cands.length; r++) {
|
|
418
|
+
if (await preferRepair(cands[r].summary)) { diffs[best] = cands[r].diff; summaries[best] = cands[r].summary; kept = true; why = `verifier prefers repair ${r}`; break }
|
|
419
|
+
}
|
|
420
|
+
if (!kept) why = "verifier keeps winner"
|
|
421
|
+
}
|
|
422
|
+
if (kept) err(` ✔ repair KEPT (${why})`)
|
|
423
|
+
else if (cands.length) err(` all repairs discarded (${why})`)
|
|
424
|
+
}
|
|
425
|
+
|
|
237
426
|
const nonEmpty = diffs.filter((d) => d.trim()).length
|
|
238
427
|
const majority = nonEmpty >= Math.max(2, Math.ceil(n / 2))
|
|
239
428
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-ultramode",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Best-of-N for coding agents with a same-model verifier: run your task N times in isolated git worktrees
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Best-of-N for coding agents with a same-model verifier, verifier-guided repair, and adaptive early-exit: run your task N times in isolated git worktrees, verify, repair the winner past the best-of-N ceiling, and apply it. A /ultra command for opencode.",
|
|
5
5
|
"module": "ultra.ts",
|
|
6
6
|
"main": "ultra.ts",
|
|
7
7
|
"exports": {
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"verifier",
|
|
29
29
|
"llm-as-a-verifier",
|
|
30
30
|
"coding-agent",
|
|
31
|
+
"repair",
|
|
32
|
+
"self-repair",
|
|
31
33
|
"ultra"
|
|
32
34
|
],
|
|
33
35
|
"license": "MIT",
|
|
@@ -37,10 +39,6 @@
|
|
|
37
39
|
"url": "git+https://github.com/maverick-tr/agent-ultramode.git"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
|
-
"@opencode-ai/plugin": "^1.0.153"
|
|
41
|
-
"@types/bun": "latest"
|
|
42
|
-
},
|
|
43
|
-
"peerDependencies": {
|
|
44
|
-
"typescript": "^5"
|
|
42
|
+
"@opencode-ai/plugin": "^1.0.153"
|
|
45
43
|
}
|
|
46
44
|
}
|
package/ultra.ts
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
* low confidence -> shows you the top candidates and applies nothing
|
|
9
9
|
* (a low-confidence pick is a coin flip and should not be applied silently).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Validated on Terminal-Bench 2.1 (89 tasks, same model as the solver, no cross-model
|
|
12
|
+
* dependency): base@1 78.7% -> 87.6% with best-of-5 and this verifier, against an
|
|
13
|
+
* oracle@5 ceiling of 96.6%. Plan-first best-of-N gave no edge; best-of-N over full
|
|
14
|
+
* trajectories is what moved the number. On 24 SWE-bench-style django/pytest tasks,
|
|
15
|
+
* verifier-guided repair reached 91.7%, above the 87.5% oracle@5 of the attempts.
|
|
14
16
|
*
|
|
15
17
|
* Install (opencode.json):
|
|
16
18
|
* { "plugin": [ ["file:///abs/path/ultra.ts", { "model": "provider/model", "n": 5 }] ] }
|
|
@@ -22,14 +24,14 @@ import type { Plugin } from "@opencode-ai/plugin"
|
|
|
22
24
|
import { tool } from "@opencode-ai/plugin"
|
|
23
25
|
import { execFile } from "node:child_process"
|
|
24
26
|
import { promisify } from "node:util"
|
|
25
|
-
import { mkdtemp, rm, writeFile, mkdir } from "node:fs/promises"
|
|
27
|
+
import { mkdtemp, rm, writeFile, mkdir, readFile } from "node:fs/promises"
|
|
26
28
|
import { existsSync } from "node:fs"
|
|
27
29
|
import { tmpdir, homedir } from "node:os"
|
|
28
30
|
import { join } from "node:path"
|
|
29
31
|
|
|
30
32
|
const execFileP = promisify(execFile)
|
|
31
33
|
type Opts = Record<string, any>
|
|
32
|
-
const VERSION = "
|
|
34
|
+
const VERSION = "v2-0824r"
|
|
33
35
|
|
|
34
36
|
interface Cfg {
|
|
35
37
|
url: string
|
|
@@ -45,6 +47,10 @@ interface Cfg {
|
|
|
45
47
|
judgeTokens: number
|
|
46
48
|
cc: number
|
|
47
49
|
agentTimeoutMs: number
|
|
50
|
+
repair: boolean
|
|
51
|
+
repairN: number
|
|
52
|
+
repairAgent: string
|
|
53
|
+
testCmd: string
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
const num = (v: any, def: number, lo: number, hi: number): number => {
|
|
@@ -75,6 +81,12 @@ function buildCfg(opts: Opts): Cfg {
|
|
|
75
81
|
judgeTokens: num(opts.judgeTokens ?? process.env.ULTRA_JUDGE_TOKENS, 12000, 500, 200000),
|
|
76
82
|
cc: num(opts.concurrency ?? process.env.ULTRA_CC, 6, 1, 12),
|
|
77
83
|
agentTimeoutMs: num(opts.agentTimeout ?? process.env.ULTRA_AGENT_TIMEOUT, 600000, 30000, 1800000),
|
|
84
|
+
// verifier-guided repair: on by default; a repaired winner is kept only when it verifies better.
|
|
85
|
+
repair: String(opts.repair ?? process.env.ULTRA_REPAIR ?? "1") !== "0" && opts.repair !== false,
|
|
86
|
+
// best-of-N repair: run this many repair attempts, keep the best that verifies (never worse than the winner).
|
|
87
|
+
repairN: num(opts.repairN ?? process.env.ULTRA_REPAIR_N, 2, 1, 5),
|
|
88
|
+
repairAgent: String(opts.repairAgent ?? process.env.ULTRA_REPAIR_AGENT ?? opts.agent ?? process.env.ULTRA_AGENT ?? 'opencode run "{task}"'),
|
|
89
|
+
testCmd: String(opts.test ?? process.env.ULTRA_TEST ?? ""),
|
|
78
90
|
}
|
|
79
91
|
}
|
|
80
92
|
|
|
@@ -163,6 +175,44 @@ async function tournament(C: Cfg, task: string, summaries: string[]): Promise<{
|
|
|
163
175
|
return { ranked, conf: ratio(ranked[0]) - ratio(ranked[1]) }
|
|
164
176
|
}
|
|
165
177
|
|
|
178
|
+
// ---- verifier-guided repair (generic; never benchmark-specific) -------------------------
|
|
179
|
+
// After the tournament, critique the winner and run ONE guided repair pass, then keep the
|
|
180
|
+
// repair ONLY when it verifies as better (the repo's own tests if available, else the
|
|
181
|
+
// reasoned verifier). Repair can lift past the best-of-N ceiling, and never makes it worse.
|
|
182
|
+
|
|
183
|
+
async function critique(C: Cfg, task: string, diff: string, log: string): Promise<string> {
|
|
184
|
+
const prompt =
|
|
185
|
+
`A coding task and the patch a verifier selected as the best of several attempts. You are a strict ` +
|
|
186
|
+
`reviewer. In 3-5 sentences name the MOST LIKELY remaining problems: missed edge cases, incomplete ` +
|
|
187
|
+
`coverage, wrong root cause, or regressions it could introduce. If it looks fully correct, say so and ` +
|
|
188
|
+
`name the single thing most worth double-checking. Be concrete and actionable.\n\n` +
|
|
189
|
+
`TASK:\n${task}\n\nPATCH:\n${diff.slice(0, 6000)}\n\nAGENT LOG (tail):\n${(log || "").slice(-1200)}\n\nReview:`
|
|
190
|
+
try { return (await chat(C, prompt)).trim().slice(0, 1500) } catch { return "" }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// auto-detect a repo test command when the user did not pass one.
|
|
194
|
+
async function detectTestCmd(dir: string): Promise<string> {
|
|
195
|
+
const read = (f: string) => readFile(join(dir, f), "utf8").catch(() => "")
|
|
196
|
+
const pkg = await read("package.json")
|
|
197
|
+
if (pkg && /"test"\s*:/.test(pkg) && !/no test specified/.test(pkg)) return "npm test --silent"
|
|
198
|
+
if (await read("pytest.ini") || await read("pyproject.toml") || await read("setup.cfg") || await read("tox.ini")) return "python -m pytest -q"
|
|
199
|
+
if (await read("Cargo.toml")) return "cargo test -q"
|
|
200
|
+
if (await read("go.mod")) return "go test ./..."
|
|
201
|
+
return ""
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// true iff `testCmd` exits 0 in a fresh worktree with `diff` applied.
|
|
205
|
+
async function runTests(dir: string, base: string, work: string, tag: string, diff: string, testCmd: string, timeout: number, abort?: AbortSignal): Promise<boolean> {
|
|
206
|
+
const wt = join(work, `t-${tag}`)
|
|
207
|
+
await git(dir, "worktree", "add", "--detach", wt, base)
|
|
208
|
+
try {
|
|
209
|
+
if (diff.trim()) { const p = join(work, `t-${tag}.patch`); await writeFile(p, diff); await git(wt, "apply", "--3way", p).catch(() => {}) }
|
|
210
|
+
await execFileP("bash", ["-lc", `exec </dev/null; ${testCmd}`], { cwd: wt, env: process.env, timeout, maxBuffer: 32 * 1024 * 1024, signal: abort })
|
|
211
|
+
return true
|
|
212
|
+
} catch { return false }
|
|
213
|
+
finally { await git(dir, "worktree", "remove", "--force", wt).catch(() => {}) }
|
|
214
|
+
}
|
|
215
|
+
|
|
166
216
|
// ---- lean sandbox for sub-agent attempts ------------------------------------------------
|
|
167
217
|
// Sub-agents run under XDG_CONFIG_HOME -> a provider-only config so they skip the user's MCP
|
|
168
218
|
// servers and plugins (the slow part). Deps install once into the sandbox and cache forever.
|
|
@@ -204,9 +254,10 @@ export const Ultra: Plugin = async (_input, options) => {
|
|
|
204
254
|
|
|
205
255
|
const ultra = tool({
|
|
206
256
|
description:
|
|
207
|
-
"Best-of-N with a verifier. Runs a coding/terminal task N times in isolated git worktrees,
|
|
208
|
-
"attempts with the same model as a verifier,
|
|
209
|
-
"
|
|
257
|
+
"v2: Best-of-N with a verifier + repair. Runs a coding/terminal task N times in isolated git worktrees, " +
|
|
258
|
+
"ranks the attempts with the same model as a verifier, then runs one critique-guided REPAIR pass on the " +
|
|
259
|
+
"winner and keeps it only if it verifies better (the repo's tests, else the verifier). Applies the " +
|
|
260
|
+
"result when confident (otherwise reports the top candidates). For a task worth getting right the first time.",
|
|
210
261
|
args: {
|
|
211
262
|
task: tool.schema.string().describe("The task to solve N times and verify. Be specific."),
|
|
212
263
|
},
|
|
@@ -277,25 +328,59 @@ export const Ultra: Plugin = async (_input, options) => {
|
|
|
277
328
|
worktrees.push(wt)
|
|
278
329
|
}
|
|
279
330
|
|
|
280
|
-
//
|
|
331
|
+
// Adaptive early-exit (#141): N is an upper bound. If an attempt PASSES the
|
|
332
|
+
// repo's tests (a hard, non-regressible signal), take it as the verified winner,
|
|
333
|
+
// abandon the still-running/queued attempts, and skip the tournament + repair.
|
|
334
|
+
// Low verifier confidence never stops early, so the result can never be worse
|
|
335
|
+
// than running all N. Off with earlyExit:false or ULTRA_NO_EARLY_EXIT=1.
|
|
336
|
+
const earlyExit = (eff as any).earlyExit !== false && process.env.ULTRA_NO_EARLY_EXIT !== "1"
|
|
337
|
+
let testCmd = eff.testCmd || (await detectTestCmd(dir))
|
|
338
|
+
const exitTestCmd = earlyExit ? testCmd : ""
|
|
339
|
+
// one combined abort: fires on host cancel (ctx.abort) OR our verified-winner signal.
|
|
340
|
+
const ee = new AbortController()
|
|
341
|
+
try { (ctx.abort as AbortSignal | undefined)?.addEventListener?.("abort", () => ee.abort(), { once: true }) } catch {}
|
|
342
|
+
let verified: { i: number; diff: string; log: string; summary: string } | null = null
|
|
343
|
+
|
|
344
|
+
// Run the agent in each worktree IN PARALLEL (lean sandbox keeps this light).
|
|
281
345
|
const limit = pLimit(Math.max(1, Math.min(eff.cc, eff.n)))
|
|
282
346
|
let done = 0
|
|
283
347
|
status(`ultra: running ${eff.n} attempts in parallel...`)
|
|
284
|
-
toast(`running ${eff.n} attempts in parallel`)
|
|
348
|
+
toast(`running ${eff.n} attempts in parallel${exitTestCmd ? `, early-exit on '${exitTestCmd}'` : ""}`)
|
|
285
349
|
const outs = await Promise.all(worktrees.map((wt, i) => limit(async () => {
|
|
350
|
+
if (ee.signal.aborted) return null // verified winner already found; skip queued attempts
|
|
286
351
|
const env = await isolatedEnv(String(i))
|
|
287
352
|
let log = ""
|
|
288
353
|
try {
|
|
289
|
-
const { stdout, stderr } = await execFileP("bash", ["-lc", `exec </dev/null; ${cmd}`], { cwd: wt, env, timeout: eff.agentTimeoutMs, maxBuffer: 32 * 1024 * 1024, signal:
|
|
354
|
+
const { stdout, stderr } = await execFileP("bash", ["-lc", `exec </dev/null; ${cmd}`], { cwd: wt, env, timeout: eff.agentTimeoutMs, maxBuffer: 32 * 1024 * 1024, signal: ee.signal })
|
|
290
355
|
log = (stdout || "") + (stderr || "")
|
|
291
356
|
} catch (e: any) { log = `agent error: ${e?.message || e}` }
|
|
292
357
|
await git(wt, "add", "-A").catch(() => {})
|
|
293
358
|
const diff = await git(wt, "diff", "--cached").catch(() => "")
|
|
294
359
|
done++
|
|
295
360
|
status(`ultra: ${done}/${eff.n} attempts done`, `attempt ${i}: ${diff ? diff.length + " diff chars" : "no changes"}`)
|
|
296
|
-
|
|
361
|
+
const out = { diff, log, summary: `AGENT LOG (tail):\n${log.slice(-1500)}\n\nDIFF:\n${diff.slice(0, 6000) || "(no changes)"}` }
|
|
362
|
+
if (exitTestCmd && !verified && diff.trim()) {
|
|
363
|
+
const pass = await runTests(dir, base, work, `ee-${i}`, diff, exitTestCmd, eff.agentTimeoutMs, ee.signal)
|
|
364
|
+
if (pass && !verified) { verified = { i, ...out }; toast(`attempt ${i} passed tests; abandoning the rest`, "success"); ee.abort() }
|
|
365
|
+
}
|
|
366
|
+
return out
|
|
297
367
|
})))
|
|
298
|
-
|
|
368
|
+
const completed = outs.filter((o): o is { diff: string; log: string; summary: string } => !!o)
|
|
369
|
+
for (const o of completed) { diffs.push(o.diff); summaries.push(o.summary) }
|
|
370
|
+
|
|
371
|
+
// verified fast path: a test-passing attempt is correct by the repo's own
|
|
372
|
+
// criterion, so apply it directly (no tournament, no repair, no regression).
|
|
373
|
+
if (verified && verified.diff.trim()) {
|
|
374
|
+
const patch = join(work, "winner.patch")
|
|
375
|
+
await writeFile(patch, verified.diff)
|
|
376
|
+
try {
|
|
377
|
+
await git(dir, "apply", "--3way", patch)
|
|
378
|
+
toast("applied the verified winner", "success")
|
|
379
|
+
return `🏆 ultra: attempt ${verified.i} passed the repo's tests (${exitTestCmd}); applied it and abandoned the rest. Review it before committing.`
|
|
380
|
+
} catch (e: any) {
|
|
381
|
+
return `ultra: attempt ${verified.i} passed tests but the patch did not apply cleanly (${e?.message || e}). The diff:\n\n\`\`\`diff\n${verified.diff.slice(0, 6000)}\n\`\`\``
|
|
382
|
+
}
|
|
383
|
+
}
|
|
299
384
|
|
|
300
385
|
if (diffs.every((d) => !d.trim())) {
|
|
301
386
|
return `ultra ran ${eff.n} attempts but none made any changes (all diffs empty). The sub-agent could not act: the task may be too ambiguous, or the sandbox model could not reach its endpoint. Try a more specific task, or check the model endpoint.`
|
|
@@ -306,6 +391,73 @@ export const Ultra: Plugin = async (_input, options) => {
|
|
|
306
391
|
const { ranked, conf } = await tournament(eff, task, summaries)
|
|
307
392
|
const best = ranked[0]
|
|
308
393
|
|
|
394
|
+
// --- repair: critique the winner, run repairN guided passes, keep the best that verifies -----
|
|
395
|
+
// Best-of-N repair (regression-safe): fan out eff.repairN attempts off `base`, each seeded
|
|
396
|
+
// with the winner's diff and the SAME critique injected (one critique, reused). Keep-policy:
|
|
397
|
+
// * with tests: keep the FIRST repair that PASSES the repo's tests; else keep the winner.
|
|
398
|
+
// (a passing repair is correct by the repo's own criterion, so it cannot regress.)
|
|
399
|
+
// * without tests: keep the FIRST repair the reasoned verifier CLEARLY prefers over the
|
|
400
|
+
// current winner (same >k/2 threshold as before); else keep the winner.
|
|
401
|
+
// INVARIANT: the final applied result is never worse than the tournament winner.
|
|
402
|
+
if (eff.repair && diffs[best].trim()) {
|
|
403
|
+
status(`ultra: repair, ${eff.repairN} critique-guided pass(es) on the winner...`)
|
|
404
|
+
toast(`repair: ${eff.repairN} pass(es) on the winner`)
|
|
405
|
+
const crit = await critique(eff, task, diffs[best], completed[best].log)
|
|
406
|
+
const rtask =
|
|
407
|
+
`${task}\n\n--- A previous attempt (already applied to your working tree) produced a partial ` +
|
|
408
|
+
`solution. A reviewer flagged the issues below. Improve and COMPLETE it, keeping what is correct; ` +
|
|
409
|
+
`verify before finishing. ---\nREVIEWER NOTES:\n${crit}`
|
|
410
|
+
const rcmd = eff.repairAgent.replace("{task}", rtask.replace(/"/g, '\\"'))
|
|
411
|
+
// run one seeded repair attempt in its own worktree; returns its diff + summary.
|
|
412
|
+
const runRepair = async (r: number): Promise<{ diff: string; summary: string }> => {
|
|
413
|
+
const rwt = join(work, `repair-${r}`)
|
|
414
|
+
await git(dir, "worktree", "add", "--detach", rwt, base)
|
|
415
|
+
worktrees.push(rwt)
|
|
416
|
+
const wpatch = join(work, `winner-for-repair-${r}.patch`)
|
|
417
|
+
await writeFile(wpatch, diffs[best])
|
|
418
|
+
await git(rwt, "apply", "--3way", wpatch).catch(() => {})
|
|
419
|
+
const renv = await isolatedEnv(`repair-${r}`)
|
|
420
|
+
let rlog = ""
|
|
421
|
+
try {
|
|
422
|
+
const { stdout, stderr } = await execFileP("bash", ["-lc", `exec </dev/null; ${rcmd}`], { cwd: rwt, env: renv, timeout: eff.agentTimeoutMs, maxBuffer: 32 * 1024 * 1024, signal: ctx.abort })
|
|
423
|
+
rlog = (stdout || "") + (stderr || "")
|
|
424
|
+
} catch (e: any) { rlog = `agent error: ${e?.message || e}` }
|
|
425
|
+
await git(rwt, "add", "-A").catch(() => {})
|
|
426
|
+
const rdiff = await git(rwt, "diff", "--cached").catch(() => "")
|
|
427
|
+
const rsum = `AGENT LOG (tail):\n${rlog.slice(-1500)}\n\nDIFF:\n${rdiff.slice(0, 6000) || "(no changes)"}`
|
|
428
|
+
return { diff: rdiff, summary: rsum }
|
|
429
|
+
}
|
|
430
|
+
// run the repairN attempts in parallel, honoring the concurrency limiter (cc).
|
|
431
|
+
const rlimit = pLimit(eff.cc)
|
|
432
|
+
const reps = await Promise.all(Array.from({ length: eff.repairN }, (_, r) => rlimit(() => runRepair(r))))
|
|
433
|
+
// only real, changed candidates are eligible.
|
|
434
|
+
const cands = reps.filter((x) => x.diff.trim() && x.diff !== diffs[best])
|
|
435
|
+
let kept = false, why = ""
|
|
436
|
+
if (!cands.length) { status("ultra: repair produced no new change; winner kept") }
|
|
437
|
+
else if (testCmd) {
|
|
438
|
+
// regression-safe: keep the FIRST repair that PASSES the repo's own tests.
|
|
439
|
+
status(`ultra: repair keep-check (tests: ${testCmd}) on ${cands.length} candidate(s)...`)
|
|
440
|
+
for (let r = 0; r < cands.length; r++) {
|
|
441
|
+
const pass = await runTests(dir, base, work, `rep-${r}`, cands[r].diff, testCmd, eff.agentTimeoutMs, ctx.abort)
|
|
442
|
+
if (pass) { diffs[best] = cands[r].diff; summaries[best] = cands[r].summary; kept = true; why = `repair ${r} passes the tests`; break }
|
|
443
|
+
}
|
|
444
|
+
if (!kept) why = "no repair passed the tests"
|
|
445
|
+
} else {
|
|
446
|
+
// no tests: keep the FIRST repair the verifier CLEARLY prefers over the current winner.
|
|
447
|
+
const preferRepair = async (rsum: string) => {
|
|
448
|
+
const lim = pLimit(eff.cc)
|
|
449
|
+
const votes = await Promise.all(Array.from({ length: eff.k }, () => judge(eff, lim, task, summaries[best], rsum)))
|
|
450
|
+
return votes.filter((v) => v === "B").length > eff.k / 2 // B = repaired
|
|
451
|
+
}
|
|
452
|
+
for (let r = 0; r < cands.length; r++) {
|
|
453
|
+
if (await preferRepair(cands[r].summary)) { diffs[best] = cands[r].diff; summaries[best] = cands[r].summary; kept = true; why = `verifier prefers repair ${r}`; break }
|
|
454
|
+
}
|
|
455
|
+
if (!kept) why = "verifier keeps winner"
|
|
456
|
+
}
|
|
457
|
+
if (kept) { toast(`repair kept (${why})`, "success"); status(`ultra: repair kept (${why})`) }
|
|
458
|
+
else if (cands.length) status(`ultra: all repairs discarded (${why})`)
|
|
459
|
+
}
|
|
460
|
+
|
|
309
461
|
// Normalised added-lines of a diff, so we can tell when attempts AGREE on the same change.
|
|
310
462
|
const nonEmpty = diffs.filter((d) => d.trim()).length
|
|
311
463
|
// Apply the verifier's TOP pick when it is confident, OR when a majority of attempts
|
|
@@ -345,7 +497,7 @@ export const Ultra: Plugin = async (_input, options) => {
|
|
|
345
497
|
config: async (cfg: any) => {
|
|
346
498
|
cfg.command = {
|
|
347
499
|
ultra: {
|
|
348
|
-
description: "Best-of-N + verifier:
|
|
500
|
+
description: "[v2] Best-of-N + verifier + repair: runs the task N times in isolated worktrees, verifies, then repairs the winner toward best-of-N. /ultra <task>",
|
|
349
501
|
template:
|
|
350
502
|
"Call the `ultra` tool exactly once, with `task` set to the request below. It runs the task several " +
|
|
351
503
|
"times in isolated git worktrees and verifies the results. When it returns, report to the user exactly " +
|
|
@@ -357,7 +509,7 @@ export const Ultra: Plugin = async (_input, options) => {
|
|
|
357
509
|
// routes each request through best-of-N. Sub-attempts run the default agent, so no recursion.
|
|
358
510
|
cfg.agent = {
|
|
359
511
|
ultra: {
|
|
360
|
-
description: "Best-of-N mode: run the request N times in isolated worktrees
|
|
512
|
+
description: "[v2] Best-of-N + repair mode: run the request N times in isolated worktrees, verify, repair, then apply the verified winner.",
|
|
361
513
|
mode: "primary",
|
|
362
514
|
color: "#A855F7",
|
|
363
515
|
prompt:
|