agent-trials 1.0.0 → 1.0.1
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 +19 -9
- package/dist/cli.js +2 -2
- package/dist/trials.js +26 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Interview your coding agent before you ship with it.
|
|
4
4
|
|
|
5
|
-
`agent-trials` runs the same task suite through every coding agent CLI you have installed and prints a scoreboard: who passed, how long they took, what they spent.
|
|
5
|
+
`agent-trials` runs the same task suite through every coding agent CLI you have installed and prints a scoreboard: who passed, how long they took, what they spent. Each task gets a temporary working copy. Commands still run on your host.
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
@@ -10,6 +10,10 @@ Interview your coding agent before you ship with it.
|
|
|
10
10
|
|
|
11
11
|
Public benchmarks score agents on tasks someone else picked. Your repo is not that benchmark. The question that matters before you let an agent loose on a codebase: does it fix YOUR bugs, in YOUR stack, at a sane price? Write the tasks once, run every candidate, keep the receipts.
|
|
12
12
|
|
|
13
|
+
## execution safety
|
|
14
|
+
|
|
15
|
+
A temporary working copy is not a security sandbox. Agents and suite commands run with your host permissions and environment; built-in Claude and Gemini commands disable approval prompts. Repository dotfiles and secrets may be copied. Use only trusted suites in a disposable VM with no sensitive files or credentials. Do not run this on a workstation containing private data.
|
|
16
|
+
|
|
13
17
|
## install
|
|
14
18
|
|
|
15
19
|
```sh
|
|
@@ -31,6 +35,7 @@ agent-trials run # run the suite, print the board, write TRIALS.md
|
|
|
31
35
|
```json
|
|
32
36
|
{
|
|
33
37
|
"verify": "npm test",
|
|
38
|
+
"verifyPaths": ["verify"],
|
|
34
39
|
"timeoutSec": 600,
|
|
35
40
|
"tasks": [
|
|
36
41
|
{
|
|
@@ -47,10 +52,13 @@ agent-trials run # run the suite, print the board, write TRIALS.md
|
|
|
47
52
|
|
|
48
53
|
Each cell (agent x task) works like this:
|
|
49
54
|
|
|
50
|
-
1. your repo, minus `node_modules` and `.git`, is copied to a
|
|
55
|
+
1. your repo, minus `node_modules` and `.git`, is copied to a temporary working copy
|
|
51
56
|
2. optional `setup` command runs once (`npm ci` and friends)
|
|
52
|
-
3. the agent runs headless on the task prompt,
|
|
53
|
-
4.
|
|
57
|
+
3. the agent runs headless on the task prompt, using the selected CLI flags, timeout-guarded
|
|
58
|
+
4. checker paths are restored from your repo, so the agent cannot rewrite its own test
|
|
59
|
+
5. your `verify` command runs in the working copy; exit 0 means pass
|
|
60
|
+
|
|
61
|
+
Step 4 restores `verifyPaths` from the suite, or a top-level `verify/` dir when `verifyPaths` is not set. A verify command that reads mutable state outside those paths can still be gamed; put your checkers in `verify/` or list them.
|
|
54
62
|
|
|
55
63
|
Builtin agents: `claude`, `codex`, `gemini`. Custom agents are one argv array away: anything installed, any flags, `{prompt}` gets replaced with the task prompt.
|
|
56
64
|
|
|
@@ -73,11 +81,13 @@ cost $1.11 $1.10 $2.06
|
|
|
73
81
|
winner: haiku (3/3, median 60.9s)
|
|
74
82
|
```
|
|
75
83
|
|
|
84
|
+
Read this board with caveats. `opus`, `sonnet`, and `haiku` are requested CLI aliases; that run predates model-identity capture (`--json` now records `modelUsed` per cell when the agent CLI reports it). One run of three toy tasks is a smoke test, not a comparative benchmark. Costs are CLI-reported, not invoice costs.
|
|
85
|
+
|
|
76
86
|
What the board caught:
|
|
77
87
|
|
|
78
|
-
- haiku
|
|
79
|
-
-
|
|
80
|
-
- sonnet
|
|
88
|
+
- haiku went 3/3 with the fastest median while spending the most: $2.06. Pass rate and price did not move together.
|
|
89
|
+
- in one earlier run, the agent asked a clarifying question ("should -0 count as zero?") into an empty terminal, then exited 0 having changed nothing. One observed example, not a general claim. This showed why exit code alone cannot be the verdict; the verify command is what counts.
|
|
90
|
+
- sonnet failed slug-test.
|
|
81
91
|
|
|
82
92
|
Full results with per-cell costs: [LEADERBOARD.md](LEADERBOARD.md)
|
|
83
93
|
|
|
@@ -86,7 +96,7 @@ Full results with per-cell costs: [LEADERBOARD.md](LEADERBOARD.md)
|
|
|
86
96
|
- pass/fail is your verify command's exit code. The tool cannot judge taste, only outcomes.
|
|
87
97
|
- runs spend your agent quota and your auth; costs are reported when the agent's JSON output includes them (claude does)
|
|
88
98
|
- agent CLIs must be installed and logged in
|
|
89
|
-
- macOS/Linux; the
|
|
99
|
+
- macOS/Linux; the working copy and verify assume `sh`. Windows untested
|
|
90
100
|
- scoring is binary per task. No partial credit for "almost fixed it"
|
|
91
101
|
|
|
92
102
|
## flags
|
|
@@ -99,7 +109,7 @@ Full results with per-cell costs: [LEADERBOARD.md](LEADERBOARD.md)
|
|
|
99
109
|
| `--jobs <n>` | `2` | parallel agent x task cells |
|
|
100
110
|
| `--out <file>` | `TRIALS.md` | markdown report path, `none` skips |
|
|
101
111
|
| `--json <file>` | - | raw per-cell results |
|
|
102
|
-
| `--keep` | off | keep
|
|
112
|
+
| `--keep` | off | keep working copies under /tmp for debugging |
|
|
103
113
|
|
|
104
114
|
## why the suite format is the point
|
|
105
115
|
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { existsSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { agentInstalled, BUILTIN_AGENTS, loadSuite, renderBoard, renderMarkdown, resolveAgents, runSuite, writeMarkdown, verdictOf, } from "./trials.js";
|
|
5
|
-
const VERSION = "1.0.
|
|
5
|
+
const VERSION = "1.0.1";
|
|
6
6
|
const HELP = `agent-trials ${VERSION}
|
|
7
7
|
Interview your coding agent before you ship with it.
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@ run flags:
|
|
|
18
18
|
--jobs <n> parallel cells (default: 2)
|
|
19
19
|
--out <file> markdown report path (default: TRIALS.md, "none" skips)
|
|
20
20
|
--json <file> also write raw results JSON
|
|
21
|
-
--keep keep
|
|
21
|
+
--keep keep working copies for debugging
|
|
22
22
|
`;
|
|
23
23
|
const STARTER_SUITE = {
|
|
24
24
|
name: "my repo trials",
|
package/dist/trials.js
CHANGED
|
@@ -22,6 +22,9 @@ export function loadSuite(file) {
|
|
|
22
22
|
if (!t.verify && !raw.verify)
|
|
23
23
|
throw new Error(`${file}: task "${t.id}" has no verify and suite has no default`);
|
|
24
24
|
}
|
|
25
|
+
if (raw.verifyPaths !== undefined && !Array.isArray(raw.verifyPaths)) {
|
|
26
|
+
throw new Error(`${file}: "verifyPaths" must be an array of repo-relative paths`);
|
|
27
|
+
}
|
|
25
28
|
return raw;
|
|
26
29
|
}
|
|
27
30
|
export function resolveAgents(names, suite) {
|
|
@@ -83,12 +86,28 @@ function parseAgentJson(stdout) {
|
|
|
83
86
|
try {
|
|
84
87
|
const j = JSON.parse(stdout.trim());
|
|
85
88
|
const cost = j.total_cost_usd ?? j.cost_usd ?? j.stats?.total_cost_usd;
|
|
86
|
-
|
|
89
|
+
const model = j.model ?? Object.keys(j.modelUsage ?? {})[0];
|
|
90
|
+
const out = {};
|
|
91
|
+
if (typeof cost === "number")
|
|
92
|
+
out.costUsd = cost;
|
|
93
|
+
if (typeof model === "string" && model)
|
|
94
|
+
out.modelUsed = model;
|
|
95
|
+
return out;
|
|
87
96
|
}
|
|
88
97
|
catch {
|
|
89
98
|
return {};
|
|
90
99
|
}
|
|
91
100
|
}
|
|
101
|
+
// The checker files are restored from the source repo after the agent runs,
|
|
102
|
+
// so an agent cannot rewrite its own test. The suite marks them with
|
|
103
|
+
// verifyPaths; a top-level verify/ dir is the convention fallback.
|
|
104
|
+
function protectedPaths(root, suite) {
|
|
105
|
+
const rels = suite.verifyPaths ?? (existsSync(path.join(root, "verify")) ? ["verify"] : []);
|
|
106
|
+
return rels.filter((rel) => !path.isAbsolute(rel) &&
|
|
107
|
+
!rel.split(/[\\/]+/).includes("..") &&
|
|
108
|
+
!SKIP_DIRS.has(rel.split(/[\\/]+/)[0]) &&
|
|
109
|
+
existsSync(path.join(root, rel)));
|
|
110
|
+
}
|
|
92
111
|
export async function runSuite(opts) {
|
|
93
112
|
const { root, suite, agents, jobs, keep, onCell } = opts;
|
|
94
113
|
const timeoutMs = (suite.timeoutSec ?? 600) * 1000;
|
|
@@ -131,6 +150,12 @@ export async function runSuite(opts) {
|
|
|
131
150
|
out.error = agentRes.timedOut ? "timeout" : `spawn failed: ${agentRes.stderr.slice(-160)}`;
|
|
132
151
|
}
|
|
133
152
|
const verifyCmd = task.verify ?? suite.verify;
|
|
153
|
+
const restored = protectedPaths(root, suite);
|
|
154
|
+
for (const rel of restored) {
|
|
155
|
+
cpSync(path.join(root, rel), path.join(sandbox, rel), { recursive: true });
|
|
156
|
+
}
|
|
157
|
+
if (restored.length)
|
|
158
|
+
out.verifyRestored = restored;
|
|
134
159
|
const v = await runCmd(["sh", "-c", verifyCmd], sandbox, timeoutMs);
|
|
135
160
|
out.verifyExit = v.code;
|
|
136
161
|
out.pass = out.agentExit === 0 && v.code === 0;
|
package/package.json
CHANGED