Report what an accepted outcome costs, and refuse to guess

8703101286a8 · AtlantisPleb · · parent f0c36de6a0e0

Report what an accepted outcome costs, and refuse to guess

The suite grades a completed Harbor job of `openagents coder` runs, counts
the outcomes a verifier accepted, and divides the run's whole cost by that
count — failures included, which is the only version of the number that
moves when the coder gets worse.

The execution half already existed: bench/run-suite.sh packs the CLI, runs a
suite through Harbor against the #35 adapter, and leaves a job directory.
This reads that directory and answers what the run was for. Nothing new has
to be produced by the coder; it reads the trial results, the ATIF export,
and the coder.txt already on disk.

The lane the graded runs use most cannot be priced. The forge model catalog
gives gpt-5.6-luna no `pricing` key on purpose — "so a grant pinned to it
records no estimated cost rather than a made-up zero" — and the worker table
leaves it out for the same reason. So the suite reports unknown. It does not
fall back to UNKNOWN_MODEL_COST, which exists so an un-tabled model is not
under-charged at the till and is marked in its own source as not a measured
rate; charging conservatively and measuring honestly are opposite jobs.

Three refusals, each a finding rather than a zero: nothing accepted, so the
denominator is undefined; nothing priced, so the total is unknown; and the
one a careless aggregate gets wrong — some trials priced and others not,
where summing the priced ones over every accepted outcome yields a
real-looking number that is too low by whatever the rest cost. That case
prints its coverage instead.

The two ids that do carry rates carry rates the config calls placeholders,
so a number built from them is labelled provisional wherever it travels, and
a dollar ceiling scored against them needs an explicit opt-in.

Hence the third gate verdict. A cost floor that could not be measured is
unverifiable, not passed, and the CLI exits 2 — so a scheduled job checking
only for a zero exit cannot read silence as green. A measured breach still
outranks an unmeasurable criterion.

A trial whose verifier never ran is ungraded: neither pass nor failure, kept
out of the success-rate denominator, counted, and capped by its own floor.
Terminal-Bench images are amd64 and their verifier segfaults under qemu on
Apple Silicon, so a crashed grader is routine, and folding it into either
bucket would move the headline number for a reason unrelated to the coder.

Five fixture Harbor jobs carry the tests: fully priced, luna, mixed, crashed
verifier, and a regression that raises the cost per accepted outcome and
trips the gate. No model is called and no clock is read, so CI and a laptop
agree.

Refs #34.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnhfrafYx5ZGaMbzZEJQ2d
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified docs/assure-repo/false-green-candidates.v1.json
  • modified docs/assure-repo/surface-inventory.v1.json
  • modified package.json
  • added packages/coder-effectiveness/README.md
  • added packages/coder-effectiveness/fixtures/crashed-verifier/build-cmake__60606060/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/crashed-verifier/build-cmake__60606060/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/crashed-verifier/build-cmake__60606060/result.json
  • added packages/coder-effectiveness/fixtures/crashed-verifier/config.json
  • added packages/coder-effectiveness/fixtures/crashed-verifier/fix-git__50505050/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/crashed-verifier/fix-git__50505050/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/crashed-verifier/fix-git__50505050/result.json
  • added packages/coder-effectiveness/fixtures/crashed-verifier/parse-log__70707070/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/crashed-verifier/parse-log__70707070/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/crashed-verifier/parse-log__70707070/result.json
  • added packages/coder-effectiveness/fixtures/crashed-verifier/result.json
  • added packages/coder-effectiveness/fixtures/floors-fixture-scale-placeholder-ok.json
  • added packages/coder-effectiveness/fixtures/floors-fixture-scale.json
  • added packages/coder-effectiveness/fixtures/mixed-lane/build-cmake__30303030/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/mixed-lane/build-cmake__30303030/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/mixed-lane/build-cmake__30303030/result.json
  • added packages/coder-effectiveness/fixtures/mixed-lane/config.json
  • added packages/coder-effectiveness/fixtures/mixed-lane/fix-git__20202020/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/mixed-lane/fix-git__20202020/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/mixed-lane/fix-git__20202020/result.json
  • added packages/coder-effectiveness/fixtures/mixed-lane/parse-log__40404040/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/mixed-lane/parse-log__40404040/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/mixed-lane/parse-log__40404040/result.json
  • added packages/coder-effectiveness/fixtures/mixed-lane/result.json
  • added packages/coder-effectiveness/fixtures/priced-lane/build-cmake__bbbbbbbb/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/priced-lane/build-cmake__bbbbbbbb/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/priced-lane/build-cmake__bbbbbbbb/result.json
  • added packages/coder-effectiveness/fixtures/priced-lane/config.json
  • added packages/coder-effectiveness/fixtures/priced-lane/fix-git__aaaaaaaa/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/priced-lane/fix-git__aaaaaaaa/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/priced-lane/fix-git__aaaaaaaa/result.json
  • added packages/coder-effectiveness/fixtures/priced-lane/parse-log__cccccccc/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/priced-lane/parse-log__cccccccc/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/priced-lane/parse-log__cccccccc/result.json
  • added packages/coder-effectiveness/fixtures/priced-lane/port-forward__dddddddd/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/priced-lane/port-forward__dddddddd/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/priced-lane/port-forward__dddddddd/result.json
  • added packages/coder-effectiveness/fixtures/priced-lane/result.json
  • added packages/coder-effectiveness/fixtures/regressed-lane/build-cmake__90909090/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/regressed-lane/build-cmake__90909090/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/regressed-lane/build-cmake__90909090/result.json
  • added packages/coder-effectiveness/fixtures/regressed-lane/config.json
  • added packages/coder-effectiveness/fixtures/regressed-lane/fix-git__80808080/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/regressed-lane/fix-git__80808080/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/regressed-lane/fix-git__80808080/result.json
  • added packages/coder-effectiveness/fixtures/regressed-lane/parse-log__a0a0a0a0/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/regressed-lane/parse-log__a0a0a0a0/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/regressed-lane/parse-log__a0a0a0a0/result.json
  • added packages/coder-effectiveness/fixtures/regressed-lane/port-forward__b0b0b0b0/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/regressed-lane/port-forward__b0b0b0b0/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/regressed-lane/port-forward__b0b0b0b0/result.json
  • added packages/coder-effectiveness/fixtures/regressed-lane/result.json
  • added packages/coder-effectiveness/fixtures/served-models.json
  • added packages/coder-effectiveness/fixtures/unpriced-lane/build-cmake__ffffffff/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/unpriced-lane/build-cmake__ffffffff/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/unpriced-lane/build-cmake__ffffffff/result.json
  • added packages/coder-effectiveness/fixtures/unpriced-lane/config.json
  • added packages/coder-effectiveness/fixtures/unpriced-lane/fix-git__eeeeeeee/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/unpriced-lane/fix-git__eeeeeeee/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/unpriced-lane/fix-git__eeeeeeee/result.json
  • added packages/coder-effectiveness/fixtures/unpriced-lane/parse-log__10101010/agent/coder.txt
  • added packages/coder-effectiveness/fixtures/unpriced-lane/parse-log__10101010/agent/trajectory.json
  • added packages/coder-effectiveness/fixtures/unpriced-lane/parse-log__10101010/result.json
  • added packages/coder-effectiveness/fixtures/unpriced-lane/result.json
  • added packages/coder-effectiveness/package.json
  • added packages/coder-effectiveness/src/cli.test.ts
  • added packages/coder-effectiveness/src/cli.ts
  • added packages/coder-effectiveness/src/effectiveness.test.ts
  • added packages/coder-effectiveness/src/effectiveness.ts
  • added packages/coder-effectiveness/src/harbor-job.ts
  • added packages/coder-effectiveness/src/index.ts
  • added packages/coder-effectiveness/src/pricing.test.ts
  • added packages/coder-effectiveness/src/pricing.ts
  • added packages/coder-effectiveness/src/render.ts
  • added packages/coder-effectiveness/src/thresholds.test.ts
  • added packages/coder-effectiveness/src/thresholds.ts
  • added packages/coder-effectiveness/thresholds/tb2-cross-section.json
  • added packages/coder-effectiveness/tsconfig.json
  • modified pnpm-lock.yaml

Diff

83 files changed, +3390 -6

docs/assure-repo/false-green-candidates.v1.json modified +1 -1

@@ -4,7 +4,7 @@

4 4
  "note": "Heuristic false-green LEADS, not findings. A finding requires a demonstrated reproduction (surviving mutation via mutation-runner). Do not treat a candidate as a confirmed false green. Coverage-theater leads may include tests that delegate their assertion to a custom helper the classifier does not recognise; verify before acting.",
5 5
  "sourceDigest": "sha256:dd810dd48c5bdbc9becd7fcc01dd41a4ca2abf0b2d6f6a545907247f6e3e8361",
6 6
  "summary": {
7
    "filesScanned": 2478,
7
    "filesScanned": 2482,
8 8
    "candidateCount": 16,
9 9
    "byMode": {
10 10
      "false_green_coverage_theater": 15,
docs/assure-repo/surface-inventory.v1.json modified +22 -5

@@ -1,7 +1,7 @@

1 1
{
2 2
  "schemaVersion": "1",
3 3
  "repository": "OpenAgentsInc/openagents",
4
  "sourceDigest": "sha256:d8cd295762f49628cb5b8f0b739a7c2f863cfa6b021515152743307a3736f13c",
4
  "sourceDigest": "sha256:393cbfb62fb12f46fc1d8141ce09db98fe2310378007b0e6b4672fa0b033e9e3",
5 5
  "surfaces": [
6 6
    {
7 7
      "id": "app:@openagentsinc/acceptance-runner",

@@ -1926,6 +1926,23 @@

1926 1926
        "note": "executable oracle(s) authored: test; observation pending AR-3 sweep"
1927 1927
      }
1928 1928
    },
1929
    {
1930
      "id": "package:@openagentsinc/coder-effectiveness",
1931
      "kind": "package",
1932
      "owningPath": "packages/coder-effectiveness",
1933
      "title": "@openagentsinc/coder-effectiveness",
1934
      "derivation": "derived",
1935
      "oracles": [
1936
        {
1937
          "type": "test",
1938
          "ref": "packages/coder-effectiveness (4 tracked test files)"
1939
        }
1940
      ],
1941
      "obligation": {
1942
        "state": "designed",
1943
        "note": "executable oracle(s) authored: test; observation pending AR-3 sweep"
1944
      }
1945
    },
1929 1946
    {
1930 1947
      "id": "package:@openagentsinc/codex-app-server-protocol",
1931 1948
      "kind": "package",

@@ -3385,17 +3402,17 @@

3385 3402
    }
3386 3403
  ],
3387 3404
  "summary": {
3388
    "totalSurfaces": 180,
3405
    "totalSurfaces": 181,
3389 3406
    "byKind": {
3390 3407
      "app": 17,
3391 3408
      "crate": 7,
3392 3409
      "document": 52,
3393
      "package": 97,
3410
      "package": 98,
3394 3411
      "public-endpoint": 2,
3395 3412
      "release-pipeline": 4,
3396 3413
      "worker": 1
3397 3414
    },
3398
    "withOracle": 172,
3415
    "withOracle": 173,
3399 3416
    "unverified": 8,
3400 3417
    "byUnverifiedReason": {
3401 3418
      "config-only": 2,

@@ -3403,7 +3420,7 @@

3403 3420
      "reference-only": 1
3404 3421
    },
3405 3422
    "byObligationState": {
3406
      "designed": 136,
3423
      "designed": 137,
3407 3424
      "inconclusive": 4,
3408 3425
      "mapped": 36,
3409 3426
      "out-of-scope": 4
package.json modified +2

@@ -172,6 +172,8 @@

172 172
    "test:assure-repo": "vp test --run packages/assure-repo/test",
173 173
    "audit:assure-repo": "node --import tsx packages/assure-repo/src/cli.ts audit-generate",
174 174
    "check:assure-repo-audit": "node --import tsx packages/assure-repo/src/cli.ts audit-check",
175
    "effectiveness:report": "node --import tsx packages/coder-effectiveness/src/cli.ts",
176
    "test:coder-effectiveness": "vp test --run packages/coder-effectiveness/src",
175 177
    "test:product-spec": "vp test --run packages/product-spec",
176 178
    "check:assure-repo-drift": "node --import tsx packages/assure-repo/src/cli.ts drift-check",
177 179
    "sweep:assure-repo": "node --import tsx packages/assure-repo/src/cli.ts sweep",
packages/coder-effectiveness/README.md added +171

@@ -0,0 +1,171 @@

1
# @openagentsinc/coder-effectiveness
2
3
The effectiveness suite for `openagents coder`
4
([#34](https://openagents.com/OpenAgentsInc/openagents/issues/34)). It grades a
5
completed Harbor job of coder runs, counts the outcomes a verifier accepted,
6
and reports **cost per accepted outcome** — with unpriced lanes marked unknown
7
rather than zero.
8
9
## Where it sits
10
11
The execution half already exists. `bench/run-suite.sh` packs the working-tree
12
CLI, runs a suite through Harbor against the installed-agent adapter from
13
[#35](https://openagents.com/OpenAgentsInc/openagents/issues/35), and leaves a
14
job directory on disk. This package reads that directory and answers the
15
question the run was for.
16
17
```sh
18
# 1. Run the suite. Harbor grades the trials.
19
bench/run-suite.sh bench/suites/tb2-cross-section.txt \
20
  --model openai/gpt-5.6-luna --jobs-dir /tmp/gym-jobs-run
21
22
# 2. Score it.
23
pnpm run effectiveness:report -- /tmp/gym-jobs-run/<job-dir> \
24
  --suite tb2-cross-section --lane proxy \
25
  --thresholds packages/coder-effectiveness/thresholds/tb2-cross-section.json
26
```
27
28
Nothing new has to be produced by the coder for this to work. The suite reads
29
the artifacts the run already leaves: each trial's `result.json` for the
30
verifier's decision, the coder's own ATIF `trajectory.json` for tokens and tool
31
calls, and `coder.txt` for the thread the trial ran in.
32
33
## The metric
34
35
**Cost per accepted outcome is the run's total cost divided by the outcomes a
36
verifier accepted, failures included.** You pay for the trials that fail, so an
37
agent whose success rate halves doubles what an accepted outcome costs you even
38
if every individual attempt got cheaper. That is the same definition the
39
benchmark report already uses
40
(`apps/openagents.com/workers/api/src/inference/benchmark/report.ts`,
41
`costPerAcceptedOutcomeMsat`).
42
43
Reported alongside it, from the same run: success rate, ungraded count, prompt
44
and output tokens, cached reads, tool calls, and wall clock.
45
46
## Unpriced lanes
47
48
**A lane with no published rate reports `unknown`. It is never priced at zero,
49
and it never borrows a fallback rate.**
50
51
This is not a hypothetical. `gpt-5.6-luna` is the lane the graded runs use
52
most, and the forge model catalog
53
(`OpenAgentsInc/openagents.com` `config/config.exs`) deliberately gives it no
54
`pricing` key. The config says why: _"This entry deliberately omits `pricing`,
55
so a grant pinned to it records no estimated cost rather than a made-up zero."_
56
The worker pricing table leaves it out for the same reason.
57
58
There is a tempting fallback nearby and it is the wrong tool. The worker's
59
`UNKNOWN_MODEL_COST` exists so an un-tabled model is not _under-charged at the
60
till_, and the source marks it "not a measured rate". Charging conservatively
61
and measuring honestly are opposite jobs. Borrowing that number here would
62
fabricate the exact figure this suite exists to report, so this package does
63
not import it.
64
65
Four dispositions, each `usd: null`:
66
67
| Disposition            | Meaning                                                                                        |
68
| ---------------------- | ---------------------------------------------------------------------------------------------- |
69
| `unpriced_model`       | The catalog carries this id and deliberately declines to price it (`gpt-5.6-luna`).            |
70
| `unknown_model`        | The catalog has never heard of this id. A weaker finding than the one above.                   |
71
| `unmetered_local_lane` | An `ollama:` lane bills no metered tokens, so no per-token rate applies. Not free — unmetered. |
72
| `unknown_usage`        | A rate exists but the trial reported no token counts, or counts that contradict each other.    |
73
74
And the case a careless aggregate gets wrong: when **some** trials price and
75
others do not, the run reports `cost_partial` and withholds the number.
76
Summing the priced trials and dividing by every accepted outcome yields a
77
real-looking figure that is too low by whatever the unpriced trials cost. The
78
coverage (`2 of 3 trials priced`) is printed instead.
79
80
### Placeholder rates
81
82
The two ids that _do_ carry rates — `gemini-3.7-flash` and `ox-alpha` — carry
83
rates the forge config marks itself: _"Placeholder: the operator must set real
84
provider rates before accepting any spend."_ A number derived from them is
85
arithmetically sound and economically provisional. It is reported, and it
86
carries `rateBasis: "operator_placeholder"` everywhere it travels, so a
87
threshold can refuse to score against it.
88
89
## The gate
90
91
A thresholds file declares the floors. `thresholds/tb2-cross-section.json` is
92
the checked-in set for the cross-section suite.
93
94
A criterion is `passed`, `failed`, or `unverifiable`, and the third one is the
95
point. If a thresholds file declares a cost ceiling and the run happened on an
96
unpriced lane, the honest answer is not "under budget" — nothing was measured.
97
So the criterion is `unverifiable`, the gate is `unverifiable`, and the CLI
98
exits **2**. A scheduled job that only checks for a zero exit cannot read
99
silence as green.
100
101
The same rule covers placeholder rates: scoring a dollar ceiling against rates
102
the config calls provisional is scoring against a guess, so it is
103
`unverifiable` unless the thresholds file opts in with
104
`acceptPlaceholderRates: true`. That is a reasonable thing to do for a relative
105
regression check, and an unreasonable thing to do quietly.
106
107
A measured breach outranks an unmeasurable criterion: a run that fails the
108
success floor and cannot be priced is `failed`, because something _was_
109
measured and it broke.
110
111
| Exit | Meaning                                     |
112
| ---- | ------------------------------------------- |
113
| 0    | Every applicable floor passed.              |
114
| 1    | A floor was breached.                       |
115
| 2    | The gate could not be verified. Not a pass. |
116
117
## Grading
118
119
A trial is `accepted` only when a verifier **ran** and returned a positive
120
reward. A trial whose verifier never ran is `ungraded` — neither a pass nor a
121
failure, kept out of the success-rate denominator, counted and reported, and
122
capped by its own floor.
123
124
This is not pedantry. Terminal-Bench images are amd64 and their verifier
125
segfaults under qemu on Apple Silicon, so a crashed grader is a routine local
126
outcome. Folding those trials into either bucket would move the headline number
127
for a reason that has nothing to do with the coder.
128
129
## Tests
130
131
Every case reads a checked-in fixture Harbor job under `fixtures/`. No model is
132
called, no Docker image runs, and no clock is read.
133
134
| Fixture            | What it proves                                                    |
135
| ------------------ | ----------------------------------------------------------------- |
136
| `priced-lane`      | A fully priced run produces a real number.                        |
137
| `unpriced-lane`    | A `gpt-5.6-luna` run reports unknown, never zero.                 |
138
| `mixed-lane`       | A partly priced run withholds the number.                         |
139
| `crashed-verifier` | Ungraded trials stay out of both buckets.                         |
140
| `regressed-lane`   | A regression raises cost per accepted outcome and trips the gate. |
141
142
```sh
143
pnpm --dir packages/coder-effectiveness test
144
```
145
146
## Rate catalog
147
148
`src/pricing.ts` pins a snapshot of the forge catalog rates, following the
149
precedent in `packages/rlm-recall-eval/src/price-catalog.ts`: the forge config
150
is not a module this package can import, a pinned version keeps a graded run
151
reproducible from a clean checkout, and cost provenance has to be auditable
152
rather than implied. Bump `CODER_RATE_CATALOG_VERSION` when the rows change.
153
154
To score against the catalog a deployment actually serves, capture
155
`GET /api/v1/models` and pass `--models <file>`. A model the served catalog
156
leaves unpriced stays unpriced here — that omission is the signal.
157
158
## Not yet done
159
160
- **Per-model cost from the coder's own trajectory.** The ATIF exporter writes
161
  `total_prompt_tokens` and `total_completion_tokens` and no cost, and cached
162
  reads survive only per step as `metrics.extra.cache_read_input_tokens`, which
163
  this package sums. Once
164
  [#36](https://openagents.com/OpenAgentsInc/openagents/issues/36) lands the
165
  per-model-family seam, the token-economy delta it measures is readable from
166
  this suite's rows without a change here.
167
- **Two consecutive scheduled runs and a caught live regression.** Issue #34's
168
  acceptance needs real Harbor runs on amd64 hardware; the fixture runs prove
169
  the grading and the arithmetic, not the schedule.
170
- **Appending results to `bench-results` with receipts.** The report is
171
  `--json`-shaped and ready for it; the store is not wired.
packages/coder-effectiveness/fixtures/crashed-verifier/build-cmake__60606060/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread cccccccc-cccc-4ccc-8ccc-cccccccccccc]
2
working on build-cmake
packages/coder-effectiveness/fixtures/crashed-verifier/build-cmake__60606060/agent/trajectory.json added +40

@@ -0,0 +1,40 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "make the build pass"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "ok",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 11000,
25
        "completion_tokens": 450,
26
        "extra": {
27
          "cache_read_input_tokens": 0
28
        }
29
      }
30
    }
31
  ],
32
  "final_metrics": {
33
    "total_prompt_tokens": 11000,
34
    "total_completion_tokens": 450,
35
    "total_steps": 2
36
  },
37
  "extra": {
38
    "exporter": "openagents.coder.atif_export.v1"
39
  }
40
}
packages/coder-effectiveness/fixtures/crashed-verifier/build-cmake__60606060/result.json added +12

@@ -0,0 +1,12 @@

1
{
2
  "trial_name": "build-cmake__60606060",
3
  "task_name": "build-cmake",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T12:01:00Z",
6
    "finished_at": "2026-08-25T12:02:00Z"
7
  },
8
  "verifier_result": null,
9
  "exception_info": {
10
    "exception_type": "VerifierCrashedError"
11
  }
12
}
packages/coder-effectiveness/fixtures/crashed-verifier/config.json added +3

@@ -0,0 +1,3 @@

1
{
2
  "dataset": "terminal-bench@2.0"
3
}
packages/coder-effectiveness/fixtures/crashed-verifier/fix-git__50505050/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb]
2
working on fix-git
packages/coder-effectiveness/fixtures/crashed-verifier/fix-git__50505050/agent/trajectory.json added +40

@@ -0,0 +1,40 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "fix the git history"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "ok",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 10000,
25
        "completion_tokens": 400,
26
        "extra": {
27
          "cache_read_input_tokens": 0
28
        }
29
      }
30
    }
31
  ],
32
  "final_metrics": {
33
    "total_prompt_tokens": 10000,
34
    "total_completion_tokens": 400,
35
    "total_steps": 2
36
  },
37
  "extra": {
38
    "exporter": "openagents.coder.atif_export.v1"
39
  }
40
}
packages/coder-effectiveness/fixtures/crashed-verifier/fix-git__50505050/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "fix-git__50505050",
3
  "task_name": "fix-git",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T12:00:00Z",
6
    "finished_at": "2026-08-25T12:01:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 1
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/crashed-verifier/parse-log__70707070/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread dddddddd-dddd-4ddd-8ddd-dddddddddddd]
2
working on parse-log
packages/coder-effectiveness/fixtures/crashed-verifier/parse-log__70707070/agent/trajectory.json added +40

@@ -0,0 +1,40 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "extract the error counts"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "ok",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 9000,
25
        "completion_tokens": 300,
26
        "extra": {
27
          "cache_read_input_tokens": 0
28
        }
29
      }
30
    }
31
  ],
32
  "final_metrics": {
33
    "total_prompt_tokens": 9000,
34
    "total_completion_tokens": 300,
35
    "total_steps": 2
36
  },
37
  "extra": {
38
    "exporter": "openagents.coder.atif_export.v1"
39
  }
40
}
packages/coder-effectiveness/fixtures/crashed-verifier/parse-log__70707070/result.json added +12

@@ -0,0 +1,12 @@

1
{
2
  "trial_name": "parse-log__70707070",
3
  "task_name": "parse-log",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T12:02:00Z",
6
    "finished_at": "2026-08-25T12:03:00Z"
7
  },
8
  "verifier_result": null,
9
  "exception_info": {
10
    "exception_type": "VerifierCrashedError"
11
  }
12
}
packages/coder-effectiveness/fixtures/crashed-verifier/result.json added +3

@@ -0,0 +1,3 @@

1
{
2
  "id": "job-crashed-verifier-0001"
3
}
packages/coder-effectiveness/fixtures/floors-fixture-scale-placeholder-ok.json added +14

@@ -0,0 +1,14 @@

1
{
2
  "$comment": [
3
    "The same fixture-scale floors, with the explicit opt-in to score a dollar",
4
    "ceiling against rates the forge config marks provisional. This is what a",
5
    "relative regression check looks like when the operator has decided the",
6
    "placeholder rates are good enough to compare two runs against each other."
7
  ],
8
  "id": "fixture-scale-placeholder-ok",
9
  "minGradedTrials": 2,
10
  "minSuccessRate": 0.4,
11
  "maxUngradedRatio": 0.25,
12
  "maxCostPerAcceptedOutcomeUsd": 2.0,
13
  "acceptPlaceholderRates": true
14
}
packages/coder-effectiveness/fixtures/floors-fixture-scale.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "$comment": [
3
    "Floors sized for the fixture jobs in this directory, which hold three or",
4
    "four trials rather than the twelve a real cross-section run does. The",
5
    "shipped suite floors live in thresholds/, and this file exists so the CLI",
6
    "cases can reach the cost criterion instead of stopping at the trial count."
7
  ],
8
  "id": "fixture-scale",
9
  "minGradedTrials": 2,
10
  "minSuccessRate": 0.4,
11
  "maxUngradedRatio": 0.25,
12
  "maxCostPerAcceptedOutcomeUsd": 2.0
13
}
packages/coder-effectiveness/fixtures/mixed-lane/build-cmake__30303030/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread 99999999-9999-4999-8999-999999999999]
2
working on build-cmake
packages/coder-effectiveness/fixtures/mixed-lane/build-cmake__30303030/agent/trajectory.json added +40

@@ -0,0 +1,40 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "make the build pass"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "ok",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 12000,
25
        "completion_tokens": 500,
26
        "extra": {
27
          "cache_read_input_tokens": 0
28
        }
29
      }
30
    }
31
  ],
32
  "final_metrics": {
33
    "total_prompt_tokens": 12000,
34
    "total_completion_tokens": 500,
35
    "total_steps": 2
36
  },
37
  "extra": {
38
    "exporter": "openagents.coder.atif_export.v1"
39
  }
40
}
packages/coder-effectiveness/fixtures/mixed-lane/build-cmake__30303030/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "build-cmake__30303030",
3
  "task_name": "build-cmake",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T11:01:00Z",
6
    "finished_at": "2026-08-25T11:02:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 1
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/mixed-lane/config.json added +3

@@ -0,0 +1,3 @@

1
{
2
  "dataset": "terminal-bench@2.0"
3
}
packages/coder-effectiveness/fixtures/mixed-lane/fix-git__20202020/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread 88888888-8888-4888-8888-888888888888]
2
working on fix-git
packages/coder-effectiveness/fixtures/mixed-lane/fix-git__20202020/agent/trajectory.json added +40

@@ -0,0 +1,40 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "fix the git history"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "ok",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 10000,
25
        "completion_tokens": 400,
26
        "extra": {
27
          "cache_read_input_tokens": 0
28
        }
29
      }
30
    }
31
  ],
32
  "final_metrics": {
33
    "total_prompt_tokens": 10000,
34
    "total_completion_tokens": 400,
35
    "total_steps": 2
36
  },
37
  "extra": {
38
    "exporter": "openagents.coder.atif_export.v1"
39
  }
40
}
packages/coder-effectiveness/fixtures/mixed-lane/fix-git__20202020/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "fix-git__20202020",
3
  "task_name": "fix-git",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T11:00:00Z",
6
    "finished_at": "2026-08-25T11:01:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 1
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/mixed-lane/parse-log__40404040/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa]
2
working on parse-log
packages/coder-effectiveness/fixtures/mixed-lane/parse-log__40404040/agent/trajectory.json added +40

@@ -0,0 +1,40 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gpt-5.6-luna",
4
  "trajectory_id": "fixture-gpt-5.6-luna",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gpt-5.6-luna"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "extract the error counts"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "ok",
22
      "model_name": "gpt-5.6-luna",
23
      "metrics": {
24
        "prompt_tokens": 40000,
25
        "completion_tokens": 2000,
26
        "extra": {
27
          "cache_read_input_tokens": 0
28
        }
29
      }
30
    }
31
  ],
32
  "final_metrics": {
33
    "total_prompt_tokens": 40000,
34
    "total_completion_tokens": 2000,
35
    "total_steps": 2
36
  },
37
  "extra": {
38
    "exporter": "openagents.coder.atif_export.v1"
39
  }
40
}
packages/coder-effectiveness/fixtures/mixed-lane/parse-log__40404040/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "parse-log__40404040",
3
  "task_name": "parse-log",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T11:02:00Z",
6
    "finished_at": "2026-08-25T11:03:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 1
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/mixed-lane/result.json added +3

@@ -0,0 +1,3 @@

1
{
2
  "id": "job-mixed-lane-0001"
3
}
packages/coder-effectiveness/fixtures/priced-lane/build-cmake__bbbbbbbb/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread 22222222-2222-4222-8222-222222222222]
2
working on build-cmake
packages/coder-effectiveness/fixtures/priced-lane/build-cmake__bbbbbbbb/agent/trajectory.json added +56

@@ -0,0 +1,56 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "make the build pass"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "configuring",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 30000,
25
        "completion_tokens": 1200,
26
        "extra": {
27
          "cache_read_input_tokens": 20000
28
        }
29
      },
30
      "tool_calls": [
31
        {
32
          "tool_call_id": "c1",
33
          "function_name": "shell",
34
          "arguments": {
35
            "command": "cmake -B build"
36
          }
37
        },
38
        {
39
          "tool_call_id": "c2",
40
          "function_name": "shell",
41
          "arguments": {
42
            "command": "make"
43
          }
44
        }
45
      ]
46
    }
47
  ],
48
  "final_metrics": {
49
    "total_prompt_tokens": 30000,
50
    "total_completion_tokens": 1200,
51
    "total_steps": 2
52
  },
53
  "extra": {
54
    "exporter": "openagents.coder.atif_export.v1"
55
  }
56
}
packages/coder-effectiveness/fixtures/priced-lane/build-cmake__bbbbbbbb/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "build-cmake__bbbbbbbb",
3
  "task_name": "build-cmake",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T09:02:00Z",
6
    "finished_at": "2026-08-25T09:05:30Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 1
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/priced-lane/config.json added +5

@@ -0,0 +1,5 @@

1
{
2
  "dataset": "terminal-bench@2.0",
3
  "model": "google/gemini-3.7-flash",
4
  "agent_import_path": "adapters.openagents_coder:OpenAgentsCoder"
5
}
packages/coder-effectiveness/fixtures/priced-lane/fix-git__aaaaaaaa/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread 11111111-1111-4111-8111-111111111111]
2
working on fix-git
packages/coder-effectiveness/fixtures/priced-lane/fix-git__aaaaaaaa/agent/trajectory.json added +63

@@ -0,0 +1,63 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "fix the git history"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "reading the reflog",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 12000,
25
        "completion_tokens": 400,
26
        "extra": {
27
          "cache_read_input_tokens": 8000
28
        }
29
      },
30
      "tool_calls": [
31
        {
32
          "tool_call_id": "c1",
33
          "function_name": "shell",
34
          "arguments": {
35
            "command": "git reflog"
36
          }
37
        }
38
      ]
39
    },
40
    {
41
      "step_id": 3,
42
      "timestamp": "2026-08-25T09:00:00Z",
43
      "source": "agent",
44
      "message": "done",
45
      "model_name": "gemini-3.7-flash",
46
      "metrics": {
47
        "prompt_tokens": 8000,
48
        "completion_tokens": 200,
49
        "extra": {
50
          "cache_read_input_tokens": 6000
51
        }
52
      }
53
    }
54
  ],
55
  "final_metrics": {
56
    "total_prompt_tokens": 20000,
57
    "total_completion_tokens": 600,
58
    "total_steps": 3
59
  },
60
  "extra": {
61
    "exporter": "openagents.coder.atif_export.v1"
62
  }
63
}
packages/coder-effectiveness/fixtures/priced-lane/fix-git__aaaaaaaa/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "fix-git__aaaaaaaa",
3
  "task_name": "fix-git",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T09:00:00Z",
6
    "finished_at": "2026-08-25T09:02:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 1
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/priced-lane/parse-log__cccccccc/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread 33333333-3333-4333-8333-333333333333]
2
working on parse-log
packages/coder-effectiveness/fixtures/priced-lane/parse-log__cccccccc/agent/trajectory.json added +49

@@ -0,0 +1,49 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "extract the error counts"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "guessing at a schema",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 15000,
25
        "completion_tokens": 800,
26
        "extra": {
27
          "cache_read_input_tokens": 4000
28
        }
29
      },
30
      "tool_calls": [
31
        {
32
          "tool_call_id": "c1",
33
          "function_name": "read",
34
          "arguments": {
35
            "path": "app.log"
36
          }
37
        }
38
      ]
39
    }
40
  ],
41
  "final_metrics": {
42
    "total_prompt_tokens": 15000,
43
    "total_completion_tokens": 800,
44
    "total_steps": 2
45
  },
46
  "extra": {
47
    "exporter": "openagents.coder.atif_export.v1"
48
  }
49
}
packages/coder-effectiveness/fixtures/priced-lane/parse-log__cccccccc/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "parse-log__cccccccc",
3
  "task_name": "parse-log",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T09:05:30Z",
6
    "finished_at": "2026-08-25T09:07:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 0
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/priced-lane/port-forward__dddddddd/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread 44444444-4444-4444-8444-444444444444]
2
working on port-forward
packages/coder-effectiveness/fixtures/priced-lane/port-forward__dddddddd/agent/trajectory.json added +49

@@ -0,0 +1,49 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "open the tunnel"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "wrong interface",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 10000,
25
        "completion_tokens": 500,
26
        "extra": {
27
          "cache_read_input_tokens": 2000
28
        }
29
      },
30
      "tool_calls": [
31
        {
32
          "tool_call_id": "c1",
33
          "function_name": "shell",
34
          "arguments": {
35
            "command": "ss -tlnp"
36
          }
37
        }
38
      ]
39
    }
40
  ],
41
  "final_metrics": {
42
    "total_prompt_tokens": 10000,
43
    "total_completion_tokens": 500,
44
    "total_steps": 2
45
  },
46
  "extra": {
47
    "exporter": "openagents.coder.atif_export.v1"
48
  }
49
}
packages/coder-effectiveness/fixtures/priced-lane/port-forward__dddddddd/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "port-forward__dddddddd",
3
  "task_name": "port-forward",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T09:07:00Z",
6
    "finished_at": "2026-08-25T09:09:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 0
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/priced-lane/result.json added +3

@@ -0,0 +1,3 @@

1
{
2
  "id": "job-priced-lane-0001"
3
}
packages/coder-effectiveness/fixtures/regressed-lane/build-cmake__90909090/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread ffffffff-ffff-4fff-8fff-ffffffffffff]
2
working on build-cmake
packages/coder-effectiveness/fixtures/regressed-lane/build-cmake__90909090/agent/trajectory.json added +49

@@ -0,0 +1,49 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.1",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "make the build pass"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "lost the thread",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 70000,
25
        "completion_tokens": 2000,
26
        "extra": {
27
          "cache_read_input_tokens": 5000
28
        }
29
      },
30
      "tool_calls": [
31
        {
32
          "tool_call_id": "c1",
33
          "function_name": "shell",
34
          "arguments": {
35
            "command": "make"
36
          }
37
        }
38
      ]
39
    }
40
  ],
41
  "final_metrics": {
42
    "total_prompt_tokens": 70000,
43
    "total_completion_tokens": 2000,
44
    "total_steps": 2
45
  },
46
  "extra": {
47
    "exporter": "openagents.coder.atif_export.v1"
48
  }
49
}
packages/coder-effectiveness/fixtures/regressed-lane/build-cmake__90909090/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "build-cmake__90909090",
3
  "task_name": "build-cmake",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T13:04:00Z",
6
    "finished_at": "2026-08-25T13:09:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 0
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/regressed-lane/config.json added +4

@@ -0,0 +1,4 @@

1
{
2
  "dataset": "terminal-bench@2.0",
3
  "model": "google/gemini-3.7-flash"
4
}
packages/coder-effectiveness/fixtures/regressed-lane/fix-git__80808080/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee]
2
working on fix-git
packages/coder-effectiveness/fixtures/regressed-lane/fix-git__80808080/agent/trajectory.json added +49

@@ -0,0 +1,49 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.1",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "fix the git history"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "one command per round",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 60000,
25
        "completion_tokens": 1500,
26
        "extra": {
27
          "cache_read_input_tokens": 10000
28
        }
29
      },
30
      "tool_calls": [
31
        {
32
          "tool_call_id": "c1",
33
          "function_name": "shell",
34
          "arguments": {
35
            "command": "git log -p"
36
          }
37
        }
38
      ]
39
    }
40
  ],
41
  "final_metrics": {
42
    "total_prompt_tokens": 60000,
43
    "total_completion_tokens": 1500,
44
    "total_steps": 2
45
  },
46
  "extra": {
47
    "exporter": "openagents.coder.atif_export.v1"
48
  }
49
}
packages/coder-effectiveness/fixtures/regressed-lane/fix-git__80808080/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "fix-git__80808080",
3
  "task_name": "fix-git",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T13:00:00Z",
6
    "finished_at": "2026-08-25T13:04:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 1
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/regressed-lane/parse-log__a0a0a0a0/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread 12121212-1212-4212-8212-121212121212]
2
working on parse-log
packages/coder-effectiveness/fixtures/regressed-lane/parse-log__a0a0a0a0/agent/trajectory.json added +40

@@ -0,0 +1,40 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.1",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "extract the error counts"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "wrong again",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 40000,
25
        "completion_tokens": 1000,
26
        "extra": {
27
          "cache_read_input_tokens": 0
28
        }
29
      }
30
    }
31
  ],
32
  "final_metrics": {
33
    "total_prompt_tokens": 40000,
34
    "total_completion_tokens": 1000,
35
    "total_steps": 2
36
  },
37
  "extra": {
38
    "exporter": "openagents.coder.atif_export.v1"
39
  }
40
}
packages/coder-effectiveness/fixtures/regressed-lane/parse-log__a0a0a0a0/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "parse-log__a0a0a0a0",
3
  "task_name": "parse-log",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T13:09:00Z",
6
    "finished_at": "2026-08-25T13:12:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 0
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/regressed-lane/port-forward__b0b0b0b0/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread 13131313-1313-4313-8313-131313131313]
2
working on port-forward
packages/coder-effectiveness/fixtures/regressed-lane/port-forward__b0b0b0b0/agent/trajectory.json added +40

@@ -0,0 +1,40 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gemini-3.7-flash",
4
  "trajectory_id": "fixture-gemini-3.7-flash",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.1",
8
    "model_name": "gemini-3.7-flash"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "open the tunnel"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "wrong interface",
22
      "model_name": "gemini-3.7-flash",
23
      "metrics": {
24
        "prompt_tokens": 35000,
25
        "completion_tokens": 900,
26
        "extra": {
27
          "cache_read_input_tokens": 0
28
        }
29
      }
30
    }
31
  ],
32
  "final_metrics": {
33
    "total_prompt_tokens": 35000,
34
    "total_completion_tokens": 900,
35
    "total_steps": 2
36
  },
37
  "extra": {
38
    "exporter": "openagents.coder.atif_export.v1"
39
  }
40
}
packages/coder-effectiveness/fixtures/regressed-lane/port-forward__b0b0b0b0/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "port-forward__b0b0b0b0",
3
  "task_name": "port-forward",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T13:12:00Z",
6
    "finished_at": "2026-08-25T13:15:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 0
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/regressed-lane/result.json added +3

@@ -0,0 +1,3 @@

1
{
2
  "id": "job-regressed-lane-0001"
3
}
packages/coder-effectiveness/fixtures/served-models.json added +34

@@ -0,0 +1,34 @@

1
{
2
  "$comment": [
3
    "A captured GET /api/v1/models body, the shape the forge serves from its",
4
    "model_catalog config. gpt-5.6-luna appears with no `pricing` key, which is",
5
    "the served catalog's way of saying it has no rate. That omission is the",
6
    "signal --models carries through: an unpriced model stays unpriced."
7
  ],
8
  "models": [
9
    {
10
      "id": "gemini-3.7-flash",
11
      "context_window": 1048576,
12
      "max_output": 65536,
13
      "pricing": {
14
        "input_per_million_tokens": 1250000,
15
        "output_per_million_tokens": 10000000,
16
        "cached_input_per_million_tokens": 100000
17
      }
18
    },
19
    {
20
      "id": "ox-alpha",
21
      "context_window": 1000000,
22
      "max_output": 64000,
23
      "pricing": {
24
        "input_per_million_tokens": 500000,
25
        "output_per_million_tokens": 2000000
26
      }
27
    },
28
    {
29
      "id": "gpt-5.6-luna",
30
      "context_window": 272000,
31
      "max_output": 4096
32
    }
33
  ]
34
}
packages/coder-effectiveness/fixtures/unpriced-lane/build-cmake__ffffffff/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread 66666666-6666-4666-8666-666666666666]
2
working on build-cmake
packages/coder-effectiveness/fixtures/unpriced-lane/build-cmake__ffffffff/agent/trajectory.json added +49

@@ -0,0 +1,49 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gpt-5.6-luna",
4
  "trajectory_id": "fixture-gpt-5.6-luna",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gpt-5.6-luna"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "make the build pass"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "configuring",
22
      "model_name": "gpt-5.6-luna",
23
      "metrics": {
24
        "prompt_tokens": 14000,
25
        "completion_tokens": 700,
26
        "extra": {
27
          "cache_read_input_tokens": 9000
28
        }
29
      },
30
      "tool_calls": [
31
        {
32
          "tool_call_id": "c1",
33
          "function_name": "shell",
34
          "arguments": {
35
            "command": "cmake -B build && make"
36
          }
37
        }
38
      ]
39
    }
40
  ],
41
  "final_metrics": {
42
    "total_prompt_tokens": 14000,
43
    "total_completion_tokens": 700,
44
    "total_steps": 2
45
  },
46
  "extra": {
47
    "exporter": "openagents.coder.atif_export.v1"
48
  }
49
}
packages/coder-effectiveness/fixtures/unpriced-lane/build-cmake__ffffffff/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "build-cmake__ffffffff",
3
  "task_name": "build-cmake",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T10:01:00Z",
6
    "finished_at": "2026-08-25T10:03:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 1
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/unpriced-lane/config.json added +5

@@ -0,0 +1,5 @@

1
{
2
  "dataset": "terminal-bench@2.0",
3
  "model": "openai/gpt-5.6-luna",
4
  "agent_import_path": "adapters.openagents_coder:OpenAgentsCoder"
5
}
packages/coder-effectiveness/fixtures/unpriced-lane/fix-git__eeeeeeee/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread 55555555-5555-4555-8555-555555555555]
2
working on fix-git
packages/coder-effectiveness/fixtures/unpriced-lane/fix-git__eeeeeeee/agent/trajectory.json added +49

@@ -0,0 +1,49 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gpt-5.6-luna",
4
  "trajectory_id": "fixture-gpt-5.6-luna",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gpt-5.6-luna"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "fix the git history"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "batching the recon",
22
      "model_name": "gpt-5.6-luna",
23
      "metrics": {
24
        "prompt_tokens": 9000,
25
        "completion_tokens": 350,
26
        "extra": {
27
          "cache_read_input_tokens": 5000
28
        }
29
      },
30
      "tool_calls": [
31
        {
32
          "tool_call_id": "c1",
33
          "function_name": "shell",
34
          "arguments": {
35
            "command": "git reflog --stat"
36
          }
37
        }
38
      ]
39
    }
40
  ],
41
  "final_metrics": {
42
    "total_prompt_tokens": 9000,
43
    "total_completion_tokens": 350,
44
    "total_steps": 2
45
  },
46
  "extra": {
47
    "exporter": "openagents.coder.atif_export.v1"
48
  }
49
}
packages/coder-effectiveness/fixtures/unpriced-lane/fix-git__eeeeeeee/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "fix-git__eeeeeeee",
3
  "task_name": "fix-git",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T10:00:00Z",
6
    "finished_at": "2026-08-25T10:01:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 1
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/unpriced-lane/parse-log__10101010/agent/coder.txt added +2

@@ -0,0 +1,2 @@

1
[oa:thread 77777777-7777-4777-8777-777777777777]
2
working on parse-log
packages/coder-effectiveness/fixtures/unpriced-lane/parse-log__10101010/agent/trajectory.json added +49

@@ -0,0 +1,49 @@

1
{
2
  "schema_version": "ATIF-v1.7",
3
  "session_id": "fixture-gpt-5.6-luna",
4
  "trajectory_id": "fixture-gpt-5.6-luna",
5
  "agent": {
6
    "name": "openagents-coder",
7
    "version": "0.4.0",
8
    "model_name": "gpt-5.6-luna"
9
  },
10
  "steps": [
11
    {
12
      "step_id": 1,
13
      "timestamp": "2026-08-25T09:00:00Z",
14
      "source": "user",
15
      "message": "extract the error counts"
16
    },
17
    {
18
      "step_id": 2,
19
      "timestamp": "2026-08-25T09:00:00Z",
20
      "source": "agent",
21
      "message": "wrong field",
22
      "model_name": "gpt-5.6-luna",
23
      "metrics": {
24
        "prompt_tokens": 7000,
25
        "completion_tokens": 300,
26
        "extra": {
27
          "cache_read_input_tokens": 3000
28
        }
29
      },
30
      "tool_calls": [
31
        {
32
          "tool_call_id": "c1",
33
          "function_name": "read",
34
          "arguments": {
35
            "path": "app.log"
36
          }
37
        }
38
      ]
39
    }
40
  ],
41
  "final_metrics": {
42
    "total_prompt_tokens": 7000,
43
    "total_completion_tokens": 300,
44
    "total_steps": 2
45
  },
46
  "extra": {
47
    "exporter": "openagents.coder.atif_export.v1"
48
  }
49
}
packages/coder-effectiveness/fixtures/unpriced-lane/parse-log__10101010/result.json added +13

@@ -0,0 +1,13 @@

1
{
2
  "trial_name": "parse-log__10101010",
3
  "task_name": "parse-log",
4
  "agent_execution": {
5
    "started_at": "2026-08-25T10:03:00Z",
6
    "finished_at": "2026-08-25T10:04:00Z"
7
  },
8
  "verifier_result": {
9
    "rewards": {
10
      "reward": 0
11
    }
12
  }
13
}
packages/coder-effectiveness/fixtures/unpriced-lane/result.json added +3

@@ -0,0 +1,3 @@

1
{
2
  "id": "job-unpriced-lane-0001"
3
}
packages/coder-effectiveness/package.json added +30

@@ -0,0 +1,30 @@

1
{
2
  "name": "@openagentsinc/coder-effectiveness",
3
  "version": "0.1.0",
4
  "private": true,
5
  "description": "The coder effectiveness suite: grade a Harbor job of openagents coder runs, count accepted outcomes, and report cost per accepted outcome with unpriced lanes marked unknown rather than zero.",
6
  "files": [
7
    "src",
8
    "thresholds",
9
    "README.md"
10
  ],
11
  "type": "module",
12
  "sideEffects": false,
13
  "exports": {
14
    ".": "./src/index.ts"
15
  },
16
  "scripts": {
17
    "report": "node --import tsx src/cli.ts",
18
    "test": "vp test --run",
19
    "typecheck": "tsc -p tsconfig.json --noEmit"
20
  },
21
  "dependencies": {
22
    "effect": "catalog:"
23
  },
24
  "devDependencies": {
25
    "@types/node": "catalog:",
26
    "@types/vite-plus-matchers": "workspace:*",
27
    "tsx": "4.20.6",
28
    "typescript": "catalog:"
29
  }
30
}
packages/coder-effectiveness/src/cli.test.ts added +141

@@ -0,0 +1,141 @@

1
/**
2
 * The CLI, run the way a scheduled job runs it.
3
 *
4
 * These cases spawn the entry point rather than importing it, so they prove
5
 * the command is invocable and that its exit codes mean what the runbook says
6
 * they mean. A gate that could not be measured must not exit 0.
7
 */
8
9
import { spawnSync } from "node:child_process";
10
import { fileURLToPath } from "node:url";
11
import { describe, expect, test } from "vite-plus/test";
12
13
const cliPath = fileURLToPath(new URL("./cli.ts", import.meta.url));
14
const fixture = (name: string): string =>
15
  fileURLToPath(new URL(`../fixtures/${name}`, import.meta.url));
16
/**
17
 * Floors sized for the fixture jobs, which hold three or four trials rather
18
 * than the twelve a real cross-section run does. The shipped suite floors
19
 * would stop at the trial count before reaching the cost criterion these
20
 * cases are about; `thresholds.test.ts` covers the shipped file itself.
21
 */
22
const fixtureFloors = fileURLToPath(
23
  new URL("../fixtures/floors-fixture-scale.json", import.meta.url),
24
);
25
const placeholderOkFloors = fileURLToPath(
26
  new URL("../fixtures/floors-fixture-scale-placeholder-ok.json", import.meta.url),
27
);
28
29
const run = (args: ReadonlyArray<string>): { status: number; stdout: string; stderr: string } => {
30
  const result = spawnSync(process.execPath, ["--import", "tsx", cliPath, ...args], {
31
    encoding: "utf8",
32
  });
33
  return { status: result.status ?? -1, stdout: result.stdout, stderr: result.stderr };
34
};
35
36
describe("coder-effectiveness report", () => {
37
  test("prints the report and exits 0 without a thresholds file", () => {
38
    const result = run([fixture("priced-lane"), "--suite", "tb2-cross-section"]);
39
40
    expect(result.status).toBe(0);
41
    expect(result.stdout).toContain("Cost per accepted outcome");
42
    expect(result.stdout).toContain("gemini-3.7-flash");
43
  });
44
45
  test("skips the `--` pnpm forwards", () => {
46
    const result = run(["--", fixture("priced-lane")]);
47
48
    expect(result.status).toBe(0);
49
  });
50
51
  test("prints unknown, not $0.0000, for the unpriced luna lane", () => {
52
    const result = run([fixture("unpriced-lane")]);
53
54
    expect(result.stdout).toContain("unknown");
55
    expect(result.stdout).toContain("cost_unknown");
56
    expect(result.stdout).toContain("gpt-5.6-luna");
57
    // The line under the cost heading must never read as a free run.
58
    expect(result.stdout).not.toMatch(/\$0\.0000 per accepted outcome/u);
59
  });
60
61
  test("exits 0 once the floors opt into the provisional rates", () => {
62
    const result = run([fixture("priced-lane"), "--thresholds", placeholderOkFloors]);
63
64
    expect(result.status).toBe(0);
65
    expect(result.stdout).toContain("PASSED");
66
  });
67
68
  test("exits 2 on the same priced run when the floors do not opt in", () => {
69
    // Same run, same ceiling, one field different. The number exists; scoring
70
    // a dollar floor against provisional rates is what needs consent.
71
    const result = run([fixture("priced-lane"), "--thresholds", fixtureFloors]);
72
73
    expect(result.status).toBe(2);
74
    expect(result.stdout).toContain("placeholder");
75
  });
76
77
  test("exits 1 when a measured floor is breached", () => {
78
    // The regressed lane accepts 1 of 4 graded trials against a 0.4 floor.
79
    const result = run([fixture("regressed-lane"), "--thresholds", fixtureFloors]);
80
81
    expect(result.status).toBe(1);
82
    expect(result.stdout).toContain("FAILED");
83
    expect(result.stdout).toContain("success_rate");
84
  });
85
86
  test("exits 2, not 0, when the gate cannot be verified", () => {
87
    // The floors declare a dollar ceiling; the luna lane carries no rate. A CI
88
    // step that only checks for a zero exit must not read this as green.
89
    const result = run([fixture("unpriced-lane"), "--thresholds", fixtureFloors]);
90
91
    expect(result.status).toBe(2);
92
    expect(result.status).not.toBe(0);
93
    expect(result.stdout).toContain("UNVERIFIABLE");
94
    expect(result.stdout).toContain("is not a pass");
95
  });
96
97
  test("emits a machine-readable report under --json", () => {
98
    const result = run([fixture("mixed-lane"), "--json"]);
99
    const parsed = JSON.parse(result.stdout) as {
100
      report: { costPerAcceptedOutcome: { usd: number | null; disposition: string } };
101
      gate: null;
102
    };
103
104
    expect(result.status).toBe(0);
105
    expect(parsed.gate).toBeNull();
106
    expect(parsed.report.costPerAcceptedOutcome.usd).toBeNull();
107
    expect(parsed.report.costPerAcceptedOutcome.disposition).toBe("cost_partial");
108
  });
109
110
  test("prices from a served catalog when given one", () => {
111
    const models = fileURLToPath(new URL("../fixtures/served-models.json", import.meta.url));
112
    const result = run([fixture("priced-lane"), "--models", models, "--json"]);
113
    const parsed = JSON.parse(result.stdout) as {
114
      report: { costPerAcceptedOutcome: { usd: number | null } };
115
    };
116
117
    expect(result.status).toBe(0);
118
    expect(parsed.report.costPerAcceptedOutcome.usd).not.toBeNull();
119
  });
120
121
  test("refuses a directory that is not a Harbor job", () => {
122
    const result = run([fixture("nope")]);
123
124
    expect(result.status).toBe(1);
125
    expect(result.stderr).toContain("not a Harbor job directory");
126
  });
127
128
  test("rejects an unknown option instead of ignoring it", () => {
129
    const result = run([fixture("priced-lane"), "--lane", "moon"]);
130
131
    expect(result.status).toBe(1);
132
    expect(result.stderr).toContain("--lane must be proxy or local");
133
  });
134
135
  test("prints usage on --help", () => {
136
    const result = run(["--help"]);
137
138
    expect(result.status).toBe(0);
139
    expect(result.stdout).toContain("Exit codes");
140
  });
141
});
packages/coder-effectiveness/src/cli.ts added +174

@@ -0,0 +1,174 @@

1
/**
2
 * `coder-effectiveness report` — score one graded Harbor job.
3
 *
4
 * The execution half of the suite already exists: `bench/run-suite.sh` packs
5
 * the working-tree CLI, runs a suite through Harbor, and leaves a job
6
 * directory. This command reads that directory and answers the question the
7
 * run was for.
8
 *
9
 *     bench/run-suite.sh bench/suites/tb2-cross-section.txt \
10
 *       --model openai/gpt-5.6-luna --jobs-dir /tmp/gym-jobs-run
11
 *
12
 *     pnpm run effectiveness:report -- /tmp/gym-jobs-run/<job> \
13
 *       --suite tb2-cross-section --lane proxy \
14
 *       --thresholds packages/coder-effectiveness/thresholds/tb2-cross-section.json
15
 *
16
 * EXIT CODES. 0 the gate passed, 1 a floor was breached, 2 the gate could not
17
 * be verified. The third code exists so a scheduled run on an unpriced lane
18
 * cannot be mistaken for a clean one by a CI step that only checks for zero.
19
 */
20
21
import { readFileSync } from "node:fs";
22
23
import { summarizeRun } from "./effectiveness.ts";
24
import { readHarborJob } from "./harbor-job.ts";
25
import {
26
  CODER_RATE_CATALOG,
27
  CODER_RATE_CATALOG_VERSION,
28
  type ModelRateRow,
29
  pricingFromModelsPayload,
30
} from "./pricing.ts";
31
import { renderReport } from "./render.ts";
32
import { evaluateThresholds, parseThresholds, type ThresholdGate } from "./thresholds.ts";
33
34
const USAGE = `Usage: coder-effectiveness report <job-dir> [options]
35
36
Arguments:
37
  <job-dir>              A completed Harbor job directory: the one holding
38
                         result.json, config.json, and one directory per trial.
39
40
Options:
41
  --suite <name>         Suite name recorded in the report. Default: terminal-bench@2.0
42
  --lane <proxy|local>   Lane the run used. Default: proxy
43
  --thresholds <file>    JSON floors to score the run against. Without it the
44
                         report is printed and no gate runs.
45
  --models <file>        A captured GET /api/v1/models body to price from,
46
                         instead of the pinned rate catalog. A model the served
47
                         catalog leaves unpriced stays unpriced here.
48
  --json                 Emit the report as JSON instead of text.
49
  -h, --help             Show this help.
50
51
Exit codes: 0 gate passed, 1 a floor was breached, 2 the gate was unverifiable.
52
An unverifiable gate is not a pass: a criterion could not be measured, most
53
often because the lane carries no published rate.`;
54
55
interface Arguments {
56
  readonly jobDir: string;
57
  readonly suite: string;
58
  readonly lane: string;
59
  readonly thresholdsPath: string | null;
60
  readonly modelsPath: string | null;
61
  readonly json: boolean;
62
}
63
64
/** `--` is what pnpm forwards, never an argument. */
65
const parseArguments = (argv: ReadonlyArray<string>): Arguments | "help" => {
66
  let jobDir: string | null = null;
67
  let suite = "terminal-bench@2.0";
68
  let lane = "proxy";
69
  let thresholdsPath: string | null = null;
70
  let modelsPath: string | null = null;
71
  let json = false;
72
73
  for (let index = 0; index < argv.length; index += 1) {
74
    const argument = argv[index]!;
75
    if (argument === "--") continue;
76
    if (argument === "-h" || argument === "--help") return "help";
77
    if (argument === "--json") {
78
      json = true;
79
      continue;
80
    }
81
    if (argument === "--suite") {
82
      suite = expectValue(argv, (index += 1), "--suite");
83
      continue;
84
    }
85
    if (argument === "--lane") {
86
      lane = expectValue(argv, (index += 1), "--lane");
87
      continue;
88
    }
89
    if (argument === "--thresholds") {
90
      thresholdsPath = expectValue(argv, (index += 1), "--thresholds");
91
      continue;
92
    }
93
    if (argument === "--models") {
94
      modelsPath = expectValue(argv, (index += 1), "--models");
95
      continue;
96
    }
97
    if (argument.startsWith("-")) {
98
      throw new Error(`unknown option: ${argument}`);
99
    }
100
    if (jobDir !== null) {
101
      throw new Error(`unexpected extra argument: ${argument}`);
102
    }
103
    jobDir = argument;
104
  }
105
106
  if (jobDir === null) throw new Error("missing required <job-dir> argument");
107
  if (lane !== "proxy" && lane !== "local") {
108
    throw new Error(`--lane must be proxy or local, got: ${lane}`);
109
  }
110
  return { jobDir, suite, lane, thresholdsPath, modelsPath, json };
111
};
112
113
const expectValue = (argv: ReadonlyArray<string>, index: number, option: string): string => {
114
  const value = argv[index];
115
  if (value === undefined || value.startsWith("-")) {
116
    throw new Error(`${option} needs a value`);
117
  }
118
  return value;
119
};
120
121
const exitCodeFor = (gate: ThresholdGate | null): number => {
122
  if (gate === null) return 0;
123
  if (gate.status === "failed") return 1;
124
  if (gate.status === "unverifiable") return 2;
125
  return 0;
126
};
127
128
const main = (argv: ReadonlyArray<string>): number => {
129
  let parsed: Arguments | "help";
130
  try {
131
    parsed = parseArguments(argv);
132
  } catch (error) {
133
    process.stderr.write(`${(error as Error).message}\n\n${USAGE}\n`);
134
    return 1;
135
  }
136
  if (parsed === "help") {
137
    process.stdout.write(`${USAGE}\n`);
138
    return 0;
139
  }
140
141
  try {
142
    let catalog: Readonly<Record<string, ModelRateRow>> = CODER_RATE_CATALOG;
143
    let catalogVersion: string = CODER_RATE_CATALOG_VERSION;
144
    if (parsed.modelsPath !== null) {
145
      catalog = pricingFromModelsPayload(JSON.parse(readFileSync(parsed.modelsPath, "utf8")));
146
      catalogVersion = `served:${parsed.modelsPath}`;
147
    }
148
149
    const run = readHarborJob(parsed.jobDir, {
150
      suite: parsed.suite,
151
      lane: parsed.lane,
152
      rateCatalogVersion: catalogVersion,
153
    });
154
    const report = summarizeRun(run, catalog, catalogVersion);
155
156
    const gate =
157
      parsed.thresholdsPath === null
158
        ? null
159
        : evaluateThresholds(
160
            report,
161
            parseThresholds(JSON.parse(readFileSync(parsed.thresholdsPath, "utf8"))),
162
          );
163
164
    process.stdout.write(
165
      parsed.json ? `${JSON.stringify({ report, gate }, null, 2)}\n` : renderReport(report, gate),
166
    );
167
    return exitCodeFor(gate);
168
  } catch (error) {
169
    process.stderr.write(`coder-effectiveness: ${(error as Error).message}\n`);
170
    return 1;
171
  }
172
};
173
174
process.exitCode = main(process.argv.slice(2));
packages/coder-effectiveness/src/effectiveness.test.ts added +212

@@ -0,0 +1,212 @@

1
/**
2
 * Grading and aggregation over fixture Harbor jobs.
3
 *
4
 * Every case reads a checked-in job directory under `fixtures/`. No model is
5
 * called, no Docker image runs, and no clock is read, so the suite produces the
6
 * same numbers in CI that it produces on a laptop.
7
 */
8
9
import { fileURLToPath } from "node:url";
10
import { describe, expect, test } from "vite-plus/test";
11
12
import { summarizeRun } from "./effectiveness.ts";
13
import { readHarborJob } from "./harbor-job.ts";
14
import { CODER_RATE_CATALOG_VERSION } from "./pricing.ts";
15
16
const fixture = (name: string): string =>
17
  fileURLToPath(new URL(`../fixtures/${name}`, import.meta.url));
18
19
const report = (name: string, lane = "proxy") =>
20
  summarizeRun(
21
    readHarborJob(fixture(name), {
22
      suite: "tb2-cross-section",
23
      lane,
24
      rateCatalogVersion: CODER_RATE_CATALOG_VERSION,
25
    }),
26
  );
27
28
describe("grading a Harbor job", () => {
29
  test("counts an accepted outcome only where a verifier returned a positive reward", () => {
30
    const result = report("priced-lane");
31
32
    expect(result.trialsTotal).toBe(4);
33
    expect(result.accepted).toBe(2);
34
    expect(result.rejected).toBe(2);
35
    expect(result.ungraded).toBe(0);
36
    expect(result.successRate).toBe(0.5);
37
  });
38
39
  test("keeps a trial whose verifier never ran out of both buckets", () => {
40
    const result = report("crashed-verifier");
41
42
    expect(result.trialsTotal).toBe(3);
43
    expect(result.accepted).toBe(1);
44
    expect(result.rejected).toBe(0);
45
    expect(result.ungraded).toBe(2);
46
    // One accepted over one graded trial. The two ungraded trials do not
47
    // deflate this into 33%, and they do not silently vanish either.
48
    expect(result.successRate).toBe(1);
49
    expect(result.ungradedRatio).toBeCloseTo(2 / 3, 10);
50
  });
51
52
  test("reads the task name, model, cli version, and thread from the trial", () => {
53
    const result = report("priced-lane");
54
55
    expect(result.perTrial.map((trial) => trial.task).toSorted()).toEqual([
56
      "build-cmake",
57
      "fix-git",
58
      "parse-log",
59
      "port-forward",
60
    ]);
61
    expect(result.models).toEqual(["gemini-3.7-flash"]);
62
    expect(result.agentVersions).toEqual(["0.4.0"]);
63
  });
64
65
  test("sums cached reads from the ATIF steps, which carry no total", () => {
66
    // The coder's exporter writes cache_read_input_tokens per step and never
67
    // totals it, so the reader has to add the steps up itself.
68
    const result = report("priced-lane");
69
70
    expect(result.cachedInputTokens).toBe(8000 + 6000 + 20000 + 4000 + 2000);
71
    expect(result.promptTokens).toBe(20000 + 30000 + 15000 + 10000);
72
    expect(result.completionTokens).toBe(600 + 1200 + 800 + 500);
73
  });
74
75
  test("counts tool calls and wall clock", () => {
76
    const result = report("priced-lane");
77
78
    expect(result.toolCalls).toBe(1 + 2 + 1 + 1);
79
    expect(result.wallClockSeconds).toBe(120 + 210 + 90 + 120);
80
  });
81
82
  test("refuses a directory that holds no Harbor result", () => {
83
    expect(() => report("does-not-exist")).toThrow(/not a Harbor job directory/u);
84
  });
85
86
  test("pins a digest that changes when the run changes", () => {
87
    expect(report("priced-lane").runDigest).toBe(report("priced-lane").runDigest);
88
    expect(report("priced-lane").runDigest).not.toBe(report("regressed-lane").runDigest);
89
    // The lane is part of what makes two rows comparable.
90
    expect(report("priced-lane").runDigest).not.toBe(report("priced-lane", "local").runDigest);
91
  });
92
});
93
94
/** The gemini-3.7-flash rates, spelled out rather than read from the catalog. */
95
const perMtok = (uncached: number, cached: number, output: number): number =>
96
  (uncached / 1e6) * 1.25 + (cached / 1e6) * 0.1 + (output / 1e6) * 10;
97
98
describe("cost per accepted outcome", () => {
99
  test("divides the whole run's cost by the accepted outcomes, failures included", () => {
100
    const result = report("priced-lane");
101
102
    const expectedTotal =
103
      perMtok(20000 - 14000, 14000, 600) +
104
      perMtok(30000 - 20000, 20000, 1200) +
105
      perMtok(15000 - 4000, 4000, 800) +
106
      perMtok(10000 - 2000, 2000, 500);
107
108
    expect(result.cost.totalUsd).toBeCloseTo(expectedTotal, 10);
109
    expect(result.cost.coverage).toBe("known");
110
    // Two accepted outcomes carry the cost of all four trials.
111
    expect(result.costPerAcceptedOutcome.usd).toBeCloseTo(expectedTotal / 2, 10);
112
    expect(result.costPerAcceptedOutcome.disposition).toBe("known");
113
  });
114
115
  test("labels a number derived from placeholder rates as provisional", () => {
116
    const result = report("priced-lane");
117
118
    expect(result.costPerAcceptedOutcome.rateBasis).toBe("operator_placeholder");
119
    expect(result.costPerAcceptedOutcome.reason).toContain("placeholder");
120
  });
121
122
  // The headline constraint.
123
  test("reports unknown, not zero, for a run entirely on the unpriced luna lane", () => {
124
    const result = report("unpriced-lane");
125
126
    expect(result.accepted).toBe(2);
127
    expect(result.cost.coverage).toBe("unknown");
128
    expect(result.cost.totalUsd).toBeNull();
129
    expect(result.costPerAcceptedOutcome.usd).toBeNull();
130
    expect(result.costPerAcceptedOutcome.usd).not.toBe(0);
131
    expect(result.costPerAcceptedOutcome.disposition).toBe("cost_unknown");
132
    expect(result.costPerAcceptedOutcome.reason).toContain("gpt-5.6-luna");
133
  });
134
135
  test("still reports the volume it does know on an unpriced lane", () => {
136
    // Refusing a cost is not refusing to measure. Tokens, tool calls, and wall
137
    // clock are counted the same way on every lane.
138
    const result = report("unpriced-lane");
139
140
    expect(result.promptTokens).toBe(9000 + 14000 + 7000);
141
    expect(result.completionTokens).toBe(350 + 700 + 300);
142
    expect(result.successRate).toBeCloseTo(2 / 3, 10);
143
  });
144
145
  test("withholds a number when only some trials could be priced", () => {
146
    // Two priced trials and one unpriced. Summing the two and dividing by all
147
    // three accepted outcomes would produce a real-looking number that is too
148
    // low by whatever the third trial cost.
149
    const result = report("mixed-lane");
150
151
    expect(result.accepted).toBe(3);
152
    expect(result.cost.coverage).toBe("partial");
153
    expect(result.cost.pricedTrials).toBe(2);
154
    expect(result.cost.unpricedTrials).toBe(1);
155
    expect(result.costPerAcceptedOutcome.usd).toBeNull();
156
    expect(result.costPerAcceptedOutcome.disposition).toBe("cost_partial");
157
  });
158
159
  test("names why each unpriced trial went unpriced", () => {
160
    const result = report("mixed-lane");
161
162
    expect(result.cost.unpricedReasons.join(" ")).toContain("gpt-5.6-luna");
163
    expect(result.perTrial.find((trial) => trial.task === "parse-log")?.disposition).toBe(
164
      "unpriced_model",
165
    );
166
  });
167
168
  test("reports an undefined cost per outcome when nothing was accepted", () => {
169
    const zeroAccepted = summarizeRun({
170
      jobId: "job-none",
171
      suite: "tb2-cross-section",
172
      lane: "proxy",
173
      runDigest: "effectiveness:test",
174
      trials: [
175
        {
176
          task: "fix-git",
177
          outcome: "rejected",
178
          modelId: "gemini-3.7-flash",
179
          agentVersion: "0.4.0",
180
          promptTokens: 10_000,
181
          completionTokens: 500,
182
          cachedInputTokens: 0,
183
          toolCalls: 1,
184
          wallClockSeconds: 60,
185
          threadId: null,
186
          exception: null,
187
        },
188
      ],
189
    });
190
191
    // The run cost real money and bought nothing, so the cost of an accepted
192
    // outcome is undefined — not zero, and not infinity.
193
    expect(zeroAccepted.cost.totalUsd).toBeGreaterThan(0);
194
    expect(zeroAccepted.costPerAcceptedOutcome.usd).toBeNull();
195
    expect(zeroAccepted.costPerAcceptedOutcome.disposition).toBe("no_accepted_outcomes");
196
    expect(zeroAccepted.successRate).toBe(0);
197
  });
198
199
  test("a regression raises the cost per accepted outcome", () => {
200
    // Same suite, same model, one accepted outcome instead of two, and more
201
    // tokens spent getting there. This is the number the gate watches.
202
    const before = report("priced-lane");
203
    const after = report("regressed-lane");
204
205
    expect(before.costPerAcceptedOutcome.usd).not.toBeNull();
206
    expect(after.costPerAcceptedOutcome.usd).not.toBeNull();
207
    expect(after.costPerAcceptedOutcome.usd!).toBeGreaterThan(
208
      before.costPerAcceptedOutcome.usd! * 3,
209
    );
210
    expect(after.successRate!).toBeLessThan(before.successRate!);
211
  });
212
});
packages/coder-effectiveness/src/effectiveness.ts added +262

@@ -0,0 +1,262 @@

1
/**
2
 * The metric: cost per accepted outcome, and the conditions under which this
3
 * suite will refuse to state one.
4
 *
5
 * DEFINITION. Cost per accepted outcome is the run's TOTAL cost divided by the
6
 * number of outcomes a verifier accepted — the cost of the failures included.
7
 * That is the same definition the benchmark report already uses
8
 * (`apps/openagents.com/workers/api/src/inference/benchmark/report.ts`,
9
 * `costPerAcceptedOutcomeMsat`), and it is the one worth reporting: you pay
10
 * for the trials that failed, so an agent that halves its success rate doubles
11
 * what an accepted outcome costs you even if each individual attempt got
12
 * cheaper. Dividing accepted-run cost by accepted runs would hide exactly the
13
 * regression the gate exists to catch.
14
 *
15
 * THREE WAYS THE NUMBER IS REFUSED, each a finding rather than a zero:
16
 *
17
 * - `no_accepted_outcomes`: nothing was accepted. The denominator is zero and
18
 *   the cost of an accepted outcome is undefined, not infinite and not free.
19
 * - `cost_unknown`: no trial could be priced at all — the whole run was on an
20
 *   unpriced lane such as `gpt-5.6-luna`, or on the local lane, or the coder
21
 *   reported no token counts.
22
 * - `cost_partial`: SOME trials priced and others did not. This is the case a
23
 *   careless implementation gets wrong: summing the priced trials and dividing
24
 *   by every accepted outcome yields a real-looking number that understates
25
 *   the truth by however much the unpriced trials cost. A partial numerator
26
 *   over a full denominator is not a cheaper run, so the number is withheld
27
 *   and the coverage is printed instead.
28
 *
29
 * When a number IS produced it still carries {@link RateBasis}. Today every
30
 * catalog rate is an operator placeholder, so every number this suite can
31
 * currently produce is arithmetically sound and economically provisional, and
32
 * it says so on every surface it reaches.
33
 */
34
35
import type { GradedRun, TrialRecord } from "./harbor-job.ts";
36
import {
37
  CODER_RATE_CATALOG,
38
  CODER_RATE_CATALOG_VERSION,
39
  type CostDisposition,
40
  type ModelRateRow,
41
  priceUsage,
42
  type RateBasis,
43
} from "./pricing.ts";
44
45
/** One trial with the cost the catalog could or could not put on it. */
46
export interface TrialCost {
47
  readonly task: string;
48
  readonly outcome: TrialRecord["outcome"];
49
  readonly usd: number | null;
50
  readonly disposition: CostDisposition;
51
  readonly reason: string;
52
}
53
54
/** How completely a run's cost is known. */
55
export type CostCoverage = "known" | "partial" | "unknown";
56
57
export interface CostAggregate {
58
  /** The sum over trials that could be priced, or `null` when none could. */
59
  readonly totalUsd: number | null;
60
  readonly coverage: CostCoverage;
61
  readonly pricedTrials: number;
62
  readonly unpricedTrials: number;
63
  /**
64
   * The weakest basis among the rates used. A run mixing confirmed and
65
   * placeholder rates is a placeholder-priced run.
66
   */
67
  readonly rateBasis: RateBasis | null;
68
  /** Distinct reasons trials went unpriced, for the report to print. */
69
  readonly unpricedReasons: ReadonlyArray<string>;
70
}
71
72
export type CostPerAcceptedOutcomeDisposition =
73
  | "known"
74
  | "no_accepted_outcomes"
75
  | "cost_unknown"
76
  | "cost_partial";
77
78
export interface CostPerAcceptedOutcome {
79
  readonly usd: number | null;
80
  readonly disposition: CostPerAcceptedOutcomeDisposition;
81
  readonly rateBasis: RateBasis | null;
82
  readonly reason: string;
83
}
84
85
export interface EffectivenessReport {
86
  readonly suite: string;
87
  readonly lane: string;
88
  readonly runDigest: string;
89
  readonly jobId: string | null;
90
  readonly models: ReadonlyArray<string>;
91
  readonly agentVersions: ReadonlyArray<string>;
92
  readonly rateCatalogVersion: string;
93
94
  readonly trialsTotal: number;
95
  readonly accepted: number;
96
  readonly rejected: number;
97
  readonly ungraded: number;
98
  /** Trials a verifier actually judged: the success-rate denominator. */
99
  readonly graded: number;
100
  /** Accepted over graded. `null` when no verifier ran, which is not a 0% run. */
101
  readonly successRate: number | null;
102
  readonly ungradedRatio: number;
103
104
  readonly cost: CostAggregate;
105
  readonly costPerAcceptedOutcome: CostPerAcceptedOutcome;
106
107
  readonly promptTokens: number | null;
108
  readonly completionTokens: number | null;
109
  readonly cachedInputTokens: number;
110
  readonly toolCalls: number | null;
111
  readonly wallClockSeconds: number | null;
112
113
  readonly perTrial: ReadonlyArray<TrialCost>;
114
}
115
116
/** Aggregate a graded run into the effectiveness report. Pure. */
117
export const summarizeRun = (
118
  run: GradedRun,
119
  catalog: Readonly<Record<string, ModelRateRow>> = CODER_RATE_CATALOG,
120
  rateCatalogVersion: string = CODER_RATE_CATALOG_VERSION,
121
): EffectivenessReport => {
122
  const perTrial: Array<TrialCost> = [];
123
  let totalUsd = 0;
124
  let pricedTrials = 0;
125
  let unpricedTrials = 0;
126
  const unpricedReasons = new Set<string>();
127
  const rateBases = new Set<RateBasis>();
128
129
  for (const trial of run.trials) {
130
    const cost = priceUsage(
131
      trial.modelId,
132
      {
133
        promptTokens: trial.promptTokens,
134
        completionTokens: trial.completionTokens,
135
        cachedInputTokens: trial.cachedInputTokens,
136
      },
137
      catalog,
138
    );
139
    if (cost.usd === null) {
140
      unpricedTrials += 1;
141
      unpricedReasons.add(cost.reason);
142
    } else {
143
      pricedTrials += 1;
144
      totalUsd += cost.usd;
145
      if (cost.rateBasis !== null) rateBases.add(cost.rateBasis);
146
    }
147
    perTrial.push({
148
      task: trial.task,
149
      outcome: trial.outcome,
150
      usd: cost.usd,
151
      disposition: cost.disposition,
152
      reason: cost.reason,
153
    });
154
  }
155
156
  const accepted = run.trials.filter((trial) => trial.outcome === "accepted").length;
157
  const rejected = run.trials.filter((trial) => trial.outcome === "rejected").length;
158
  const ungraded = run.trials.filter((trial) => trial.outcome === "ungraded").length;
159
  const graded = accepted + rejected;
160
161
  const cost: CostAggregate = {
162
    totalUsd: pricedTrials === 0 ? null : totalUsd,
163
    coverage: coverageOf(pricedTrials, unpricedTrials),
164
    pricedTrials,
165
    unpricedTrials,
166
    rateBasis: weakestBasis(rateBases),
167
    unpricedReasons: [...unpricedReasons].toSorted(),
168
  };
169
170
  return {
171
    suite: run.suite,
172
    lane: run.lane,
173
    runDigest: run.runDigest,
174
    jobId: run.jobId,
175
    models: distinct(run.trials.map((trial) => trial.modelId)),
176
    agentVersions: distinct(run.trials.map((trial) => trial.agentVersion)),
177
    rateCatalogVersion,
178
179
    trialsTotal: run.trials.length,
180
    accepted,
181
    rejected,
182
    ungraded,
183
    graded,
184
    successRate: graded === 0 ? null : accepted / graded,
185
    ungradedRatio: run.trials.length === 0 ? 0 : ungraded / run.trials.length,
186
187
    cost,
188
    costPerAcceptedOutcome: perAcceptedOutcome(cost, accepted),
189
190
    promptTokens: sumOrNull(run.trials.map((trial) => trial.promptTokens)),
191
    completionTokens: sumOrNull(run.trials.map((trial) => trial.completionTokens)),
192
    cachedInputTokens: run.trials.reduce((sum, trial) => sum + trial.cachedInputTokens, 0),
193
    toolCalls: sumOrNull(run.trials.map((trial) => trial.toolCalls)),
194
    wallClockSeconds: sumOrNull(run.trials.map((trial) => trial.wallClockSeconds)),
195
196
    perTrial,
197
  };
198
};
199
200
const coverageOf = (priced: number, unpriced: number): CostCoverage => {
201
  if (priced === 0) return "unknown";
202
  if (unpriced === 0) return "known";
203
  return "partial";
204
};
205
206
/** A placeholder anywhere makes the whole aggregate a placeholder. */
207
const weakestBasis = (bases: ReadonlySet<RateBasis>): RateBasis | null => {
208
  if (bases.has("operator_placeholder")) return "operator_placeholder";
209
  if (bases.has("operator_confirmed")) return "operator_confirmed";
210
  return null;
211
};
212
213
const perAcceptedOutcome = (cost: CostAggregate, accepted: number): CostPerAcceptedOutcome => {
214
  if (accepted === 0) {
215
    return {
216
      usd: null,
217
      disposition: "no_accepted_outcomes",
218
      rateBasis: cost.rateBasis,
219
      reason:
220
        "no outcome was accepted, so the cost of an accepted outcome is undefined rather than zero or infinite",
221
    };
222
  }
223
  if (cost.coverage === "unknown" || cost.totalUsd === null) {
224
    return {
225
      usd: null,
226
      disposition: "cost_unknown",
227
      rateBasis: cost.rateBasis,
228
      reason: `no trial in this run could be priced (${cost.unpricedReasons.join("; ")})`,
229
    };
230
  }
231
  if (cost.coverage === "partial") {
232
    return {
233
      usd: null,
234
      disposition: "cost_partial",
235
      rateBasis: cost.rateBasis,
236
      reason: `only ${String(cost.pricedTrials)} of ${String(cost.pricedTrials + cost.unpricedTrials)} trials could be priced, and a partial total over every accepted outcome would understate the cost`,
237
    };
238
  }
239
  return {
240
    usd: cost.totalUsd / accepted,
241
    disposition: "known",
242
    rateBasis: cost.rateBasis,
243
    reason:
244
      cost.rateBasis === "operator_placeholder"
245
        ? "priced entirely from operator placeholder rates, so the figure is provisional"
246
        : "priced entirely from confirmed rates",
247
  };
248
};
249
250
/** A sum is only known when every term is. One unknown makes the total unknown. */
251
const sumOrNull = (values: ReadonlyArray<number | null>): number | null => {
252
  if (values.length === 0) return null;
253
  let sum = 0;
254
  for (const value of values) {
255
    if (value === null) return null;
256
    sum += value;
257
  }
258
  return sum;
259
};
260
261
const distinct = (values: ReadonlyArray<string | null>): ReadonlyArray<string> =>
262
  [...new Set(values.filter((value): value is string => value !== null))].toSorted();
packages/coder-effectiveness/src/harbor-job.ts added +244

@@ -0,0 +1,244 @@

1
/**
2
 * Read one completed Harbor job directory into typed, graded trials.
3
 *
4
 * The input is exactly what `bench/run-suite.sh` already leaves on disk after
5
 * `harbor run --dataset terminal-bench@2.0 --agent-import-path
6
 * adapters.openagents_coder:OpenAgentsCoder`:
7
 *
8
 *     <job-dir>/result.json                  the job envelope
9
 *     <job-dir>/config.json                  the pinned recipe
10
 *     <job-dir>/<task>__<uuid>/result.json   the trial and its verifier result
11
 *     <job-dir>/<task>__<uuid>/agent/trajectory.json   the coder's ATIF export
12
 *     <job-dir>/<task>__<uuid>/agent/coder.txt         the captured session
13
 *
14
 * Nothing new is produced by the coder for this suite to work. The field names
15
 * read here are the same ones `bench/post_gym_run.py` reads, so the two agree
16
 * about what a pass is.
17
 *
18
 * THE GRADING RULE. A trial is `accepted` only when a verifier RAN and
19
 * returned a positive reward. A trial whose verifier never ran is `ungraded` —
20
 * not a failure, and not a pass. The Terminal-Bench images are amd64 and their
21
 * verifier segfaults under qemu on Apple Silicon, so a crashed grader is a
22
 * routine local outcome rather than a rare one, and folding it into either
23
 * bucket would move the headline number for a reason that has nothing to do
24
 * with the coder. `ungraded` trials are counted, reported, and kept out of the
25
 * success-rate denominator, and a threshold can cap how many of them a run may
26
 * contain before the run stops being worth reading.
27
 */
28
29
import { createHash } from "node:crypto";
30
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
31
import { join } from "node:path";
32
33
/** The coder's `--plain` thread announcement, the same contract bench parses. */
34
const THREAD_LINE = /\[oa:thread ([0-9a-fA-F-]{36})\]/u;
35
36
/** What a verifier decided about one trial. */
37
export type TrialOutcome = "accepted" | "rejected" | "ungraded";
38
39
export interface TrialRecord {
40
  /** The task half of Harbor's `<task>__<shortuuid>` directory name. */
41
  readonly task: string;
42
  readonly outcome: TrialOutcome;
43
  readonly modelId: string | null;
44
  readonly agentVersion: string | null;
45
  readonly promptTokens: number | null;
46
  readonly completionTokens: number | null;
47
  /** Cached-read input tokens, summed from the ATIF steps. */
48
  readonly cachedInputTokens: number;
49
  /** Tool calls the trajectory records, or `null` when it records no steps. */
50
  readonly toolCalls: number | null;
51
  readonly wallClockSeconds: number | null;
52
  /** The forge thread the trial ran in, when the coder announced one. */
53
  readonly threadId: string | null;
54
  /** The typed error Harbor classified, when the trial raised one. */
55
  readonly exception: string | null;
56
}
57
58
export interface GradedRun {
59
  readonly jobId: string | null;
60
  readonly suite: string;
61
  readonly lane: string;
62
  /**
63
   * A digest over what was actually run: the suite, the lane, the sorted task
64
   * list, the CLI version, the model, and the rate catalog version. Two runs
65
   * that share a digest are comparable rows; two that do not are not, and the
66
   * report says so rather than letting a reader assume.
67
   */
68
  readonly runDigest: string;
69
  readonly trials: ReadonlyArray<TrialRecord>;
70
}
71
72
export interface ReadHarborJobOptions {
73
  readonly suite: string;
74
  readonly lane: string;
75
  /** Mixed into the run digest so a re-scored run is not mistaken for a re-run. */
76
  readonly rateCatalogVersion: string;
77
}
78
79
/**
80
 * Read a Harbor job directory. Throws when the directory is not a Harbor job,
81
 * because scoring a directory that holds no result is not a zero-score run.
82
 */
83
export const readHarborJob = (jobDir: string, options: ReadHarborJobOptions): GradedRun => {
84
  const jobResult = readJson(join(jobDir, "result.json"));
85
  if (jobResult === undefined) {
86
    throw new Error(
87
      `not a Harbor job directory (no result.json): ${jobDir}. Point --job-dir at the directory harbor run created under its --jobs-dir.`,
88
    );
89
  }
90
91
  const trials: Array<TrialRecord> = [];
92
  for (const entry of readdirSync(jobDir).toSorted()) {
93
    const trialDir = join(jobDir, entry);
94
    if (!statSync(trialDir).isDirectory()) continue;
95
    const trialResult = readJson(join(trialDir, "result.json"));
96
    if (trialResult === undefined) continue;
97
    trials.push(readTrial(entry, trialDir, trialResult));
98
  }
99
100
  return {
101
    jobId: readString(readField(jobResult, "id")),
102
    suite: options.suite,
103
    lane: options.lane,
104
    runDigest: runDigestOf(trials, options),
105
    trials,
106
  };
107
};
108
109
const readTrial = (dirName: string, trialDir: string, trialResult: unknown): TrialRecord => {
110
  const trajectory = readJson(join(trialDir, "agent", "trajectory.json"));
111
  const usage = readTrajectoryUsage(trajectory);
112
  const agent = readField(trajectory, "agent");
113
114
  return {
115
    task: dirName.includes("__") ? dirName.slice(0, dirName.lastIndexOf("__")) : dirName,
116
    outcome: outcomeOf(trialResult),
117
    modelId: readString(readField(agent, "model_name")),
118
    agentVersion: readString(readField(agent, "version")),
119
    promptTokens: usage.promptTokens,
120
    completionTokens: usage.completionTokens,
121
    cachedInputTokens: usage.cachedInputTokens,
122
    toolCalls: usage.toolCalls,
123
    wallClockSeconds: wallClockOf(trialResult),
124
    threadId: threadIdOf(trialDir),
125
    exception: readString(readField(readField(trialResult, "exception_info"), "exception_type")),
126
  };
127
};
128
129
/**
130
 * A verifier that never ran leaves no `verifier_result`. That is `ungraded`.
131
 * Where one ran, any positive reward is an accepted outcome; Harbor writes the
132
 * reward either as `rewards.reward` or as the sole entry of a `rewards` map.
133
 */
134
const outcomeOf = (trialResult: unknown): TrialOutcome => {
135
  const verifier = readField(trialResult, "verifier_result");
136
  if (verifier === undefined || verifier === null) return "ungraded";
137
  const rewards = readField(verifier, "rewards") ?? readField(trialResult, "rewards");
138
  const reward = rewardValue(rewards);
139
  return reward !== null && reward > 0 ? "accepted" : "rejected";
140
};
141
142
const rewardValue = (rewards: unknown): number | null => {
143
  const direct = readNumber(readField(rewards, "reward"));
144
  if (direct !== null) return direct;
145
  if (typeof rewards !== "object" || rewards === null) return null;
146
  const values = Object.values(rewards as Record<string, unknown>);
147
  const first = values.length === 1 ? readNumber(values[0]) : null;
148
  return first;
149
};
150
151
const wallClockOf = (trialResult: unknown): number | null => {
152
  const execution = readField(trialResult, "agent_execution");
153
  const started = readString(readField(execution, "started_at"));
154
  const finished = readString(readField(execution, "finished_at"));
155
  if (started === null || finished === null) return null;
156
  const span = Date.parse(finished) - Date.parse(started);
157
  return Number.isFinite(span) ? span / 1000 : null;
158
};
159
160
interface TrajectoryUsage {
161
  readonly promptTokens: number | null;
162
  readonly completionTokens: number | null;
163
  readonly cachedInputTokens: number;
164
  readonly toolCalls: number | null;
165
}
166
167
/**
168
 * Pull usage out of the coder's ATIF export.
169
 *
170
 * Totals come from `final_metrics`, which the coder writes only when at least
171
 * one turn reported usage — an absent total is unknown, so it stays `null`.
172
 * Cached reads are NOT in `final_metrics`: the exporter carries them per step
173
 * as `metrics.extra.cache_read_input_tokens` and never totals them, so this
174
 * sums the steps. A trajectory with no cached figure anywhere reports 0 cached
175
 * tokens, which is the honest reading — the coder omits the field when the
176
 * turn reported no cache reads, not when it failed to measure them.
177
 */
178
const readTrajectoryUsage = (trajectory: unknown): TrajectoryUsage => {
179
  const finalMetrics = readField(trajectory, "final_metrics");
180
  const steps = readArray(readField(trajectory, "steps"));
181
182
  let cachedInputTokens = 0;
183
  let toolCalls = 0;
184
  for (const step of steps) {
185
    const extra = readField(readField(step, "metrics"), "extra");
186
    cachedInputTokens += readNumber(readField(extra, "cache_read_input_tokens")) ?? 0;
187
    toolCalls += readArray(readField(step, "tool_calls")).length;
188
  }
189
190
  return {
191
    promptTokens: readNumber(readField(finalMetrics, "total_prompt_tokens")),
192
    completionTokens: readNumber(readField(finalMetrics, "total_completion_tokens")),
193
    cachedInputTokens,
194
    toolCalls: steps.length === 0 ? null : toolCalls,
195
  };
196
};
197
198
const threadIdOf = (trialDir: string): string | null => {
199
  const path = join(trialDir, "agent", "coder.txt");
200
  if (!existsSync(path)) return null;
201
  const match = THREAD_LINE.exec(readFileSync(path, "utf8"));
202
  return match?.[1] ?? null;
203
};
204
205
/**
206
 * The recipe pin. Deliberately independent of the `harbor:` digest
207
 * `bench/post_gym_run.py` computes: that one hashes a Python-serialised config
208
 * and this one hashes an explicit list of the facts that make two runs
209
 * comparable, so claiming they agree would be a claim neither can keep.
210
 */
211
const runDigestOf = (trials: ReadonlyArray<TrialRecord>, options: ReadHarborJobOptions): string => {
212
  const source = JSON.stringify({
213
    suite: options.suite,
214
    lane: options.lane,
215
    rateCatalogVersion: options.rateCatalogVersion,
216
    tasks: trials.map((trial) => trial.task).toSorted(),
217
    agentVersions: distinct(trials.map((trial) => trial.agentVersion)),
218
    models: distinct(trials.map((trial) => trial.modelId)),
219
  });
220
  return `effectiveness:${createHash("sha256").update(source).digest("hex")}`;
221
};
222
223
const distinct = (values: ReadonlyArray<string | null>): ReadonlyArray<string> =>
224
  [...new Set(values.filter((value): value is string => value !== null))].toSorted();
225
226
const readJson = (path: string): unknown => {
227
  if (!existsSync(path)) return undefined;
228
  try {
229
    return JSON.parse(readFileSync(path, "utf8")) as unknown;
230
  } catch {
231
    return undefined;
232
  }
233
};
234
235
const readField = (value: unknown, key: string): unknown =>
236
  typeof value === "object" && value !== null ? (value as Record<string, unknown>)[key] : undefined;
237
238
const readArray = (value: unknown): ReadonlyArray<unknown> => (Array.isArray(value) ? value : []);
239
240
const readString = (value: unknown): string | null =>
241
  typeof value === "string" && value !== "" ? value : null;
242
243
const readNumber = (value: unknown): number | null =>
244
  typeof value === "number" && Number.isFinite(value) ? value : null;
packages/coder-effectiveness/src/index.ts added +29

@@ -0,0 +1,29 @@

1
export {
2
  type CostAggregate,
3
  type CostPerAcceptedOutcome,
4
  type EffectivenessReport,
5
  summarizeRun,
6
  type TrialCost,
7
} from "./effectiveness.ts";
8
export {
9
  type GradedRun,
10
  readHarborJob,
11
  type TrialOutcome,
12
  type TrialRecord,
13
} from "./harbor-job.ts";
14
export {
15
  CODER_RATE_CATALOG,
16
  CODER_RATE_CATALOG_VERSION,
17
  type CostDisposition,
18
  type ModelRateRow,
19
  priceUsage,
20
  pricingFromModelsPayload,
21
  type RateBasis,
22
} from "./pricing.ts";
23
export { renderReport } from "./render.ts";
24
export {
25
  type EffectivenessThresholds,
26
  evaluateThresholds,
27
  parseThresholds,
28
  type ThresholdGate,
29
} from "./thresholds.ts";
packages/coder-effectiveness/src/pricing.test.ts added +164

@@ -0,0 +1,164 @@

1
import { describe, expect, test } from "vite-plus/test";
2
3
import {
4
  CODER_RATE_CATALOG,
5
  DELIBERATELY_UNPRICED_MODELS,
6
  priceUsage,
7
  pricingFromModelsPayload,
8
} from "./pricing.ts";
9
10
const complete = (promptTokens: number, completionTokens: number, cachedInputTokens = 0) => ({
11
  promptTokens,
12
  completionTokens,
13
  cachedInputTokens,
14
});
15
16
describe("priceUsage", () => {
17
  test("prices a catalogued model from its input, cached, and output rates", () => {
18
    // gemini-3.7-flash: $1.25 in / $0.10 cached / $10.00 out per Mtok.
19
    // 1M uncached input + 1M cached input + 1M output.
20
    const result = priceUsage("gemini-3.7-flash", complete(2_000_000, 1_000_000, 1_000_000));
21
22
    expect(result.disposition).toBe("known");
23
    expect(result.usd).toBeCloseTo(1.25 + 0.1 + 10.0, 10);
24
  });
25
26
  test("bills cached reads at the cached rate, not the input rate", () => {
27
    const allUncached = priceUsage("gemini-3.7-flash", complete(1_000_000, 0, 0));
28
    const allCached = priceUsage("gemini-3.7-flash", complete(1_000_000, 0, 1_000_000));
29
30
    expect(allUncached.usd).toBeCloseTo(1.25, 10);
31
    expect(allCached.usd).toBeCloseTo(0.1, 10);
32
  });
33
34
  test("falls back to the input rate where the catalog declares no cached rate", () => {
35
    // ox-alpha declares no cached rate, so a cached read costs full input.
36
    const row = CODER_RATE_CATALOG["ox-alpha"]!;
37
    expect(row.cachedInputUsdPerMtok).toBe(row.inputUsdPerMtok);
38
39
    const cached = priceUsage("ox-alpha", complete(1_000_000, 0, 1_000_000));
40
    expect(cached.usd).toBeCloseTo(0.5, 10);
41
  });
42
43
  test("marks every catalogued rate as an operator placeholder", () => {
44
    for (const row of Object.values(CODER_RATE_CATALOG)) {
45
      expect(row.rateBasis).toBe("operator_placeholder");
46
    }
47
  });
48
49
  // The constraint this whole package exists for.
50
  test("refuses to price gpt-5.6-luna, and never prices it at zero", () => {
51
    const result = priceUsage("gpt-5.6-luna", complete(500_000, 50_000));
52
53
    expect(result.usd).toBeNull();
54
    expect(result.usd).not.toBe(0);
55
    expect(result.disposition).toBe("unpriced_model");
56
    expect(result.reason).toContain("gpt-5.6-luna");
57
  });
58
59
  test("names gpt-5.6-luna as deliberately unpriced, not merely unrecognised", () => {
60
    expect(Object.keys(DELIBERATELY_UNPRICED_MODELS)).toContain("gpt-5.6-luna");
61
    expect(priceUsage("gpt-5.6-luna", complete(1, 1)).disposition).toBe("unpriced_model");
62
    expect(priceUsage("some-model-nobody-shipped", complete(1, 1)).disposition).toBe(
63
      "unknown_model",
64
    );
65
  });
66
67
  test("reports the local lane as unmetered rather than free", () => {
68
    for (const id of ["ollama:qwen3.8:27b-mtp-q8_0", "ollama/qwen3.8"]) {
69
      const result = priceUsage(id, complete(100_000, 10_000));
70
      expect(result.usd).toBeNull();
71
      expect(result.disposition).toBe("unmetered_local_lane");
72
    }
73
  });
74
75
  test("reports unknown usage rather than pricing a missing count as zero", () => {
76
    const noPrompt = priceUsage("gemini-3.7-flash", {
77
      promptTokens: null,
78
      completionTokens: 100,
79
      cachedInputTokens: 0,
80
    });
81
    const noCompletion = priceUsage("gemini-3.7-flash", {
82
      promptTokens: 100,
83
      completionTokens: null,
84
      cachedInputTokens: 0,
85
    });
86
87
    expect(noPrompt.usd).toBeNull();
88
    expect(noPrompt.disposition).toBe("unknown_usage");
89
    expect(noCompletion.usd).toBeNull();
90
    expect(noCompletion.disposition).toBe("unknown_usage");
91
  });
92
93
  test("refuses usage claiming more cached reads than prompt tokens", () => {
94
    const result = priceUsage("gemini-3.7-flash", complete(1_000, 100, 5_000));
95
96
    expect(result.usd).toBeNull();
97
    expect(result.disposition).toBe("unknown_usage");
98
  });
99
100
  test("reports a trial with no model id as unknown", () => {
101
    expect(priceUsage(null, complete(1_000, 100)).disposition).toBe("unknown_model");
102
    expect(priceUsage("", complete(1_000, 100)).disposition).toBe("unknown_model");
103
  });
104
});
105
106
describe("pricingFromModelsPayload", () => {
107
  // The forge omits `pricing` entirely for an unpriced model, and that absence
108
  // is the whole signal.
109
  const payload = {
110
    models: [
111
      {
112
        id: "gemini-3.7-flash",
113
        pricing: {
114
          input_per_million_tokens: 1_250_000,
115
          output_per_million_tokens: 10_000_000,
116
          cached_input_per_million_tokens: 100_000,
117
        },
118
      },
119
      {
120
        id: "ox-alpha",
121
        pricing: {
122
          input_per_million_tokens: 500_000,
123
          output_per_million_tokens: 2_000_000,
124
        },
125
      },
126
      { id: "gpt-5.6-luna" },
127
    ],
128
  };
129
130
  test("converts micro-USD per million tokens into USD per million tokens", () => {
131
    const catalog = pricingFromModelsPayload(payload);
132
133
    expect(catalog["gemini-3.7-flash"]).toMatchObject({
134
      inputUsdPerMtok: 1.25,
135
      cachedInputUsdPerMtok: 0.1,
136
      outputUsdPerMtok: 10,
137
    });
138
  });
139
140
  test("resolves a missing cached rate to the input rate", () => {
141
    const catalog = pricingFromModelsPayload(payload);
142
143
    expect(catalog["ox-alpha"]?.cachedInputUsdPerMtok).toBe(0.5);
144
  });
145
146
  test("carries the served catalog's omission through as unpriced", () => {
147
    const catalog = pricingFromModelsPayload(payload);
148
149
    expect(catalog["gpt-5.6-luna"]).toBeUndefined();
150
    expect(priceUsage("gpt-5.6-luna", complete(1_000, 100), catalog).usd).toBeNull();
151
  });
152
153
  test("never claims a served rate was operator-confirmed", () => {
154
    for (const row of Object.values(pricingFromModelsPayload(payload))) {
155
      expect(row.rateBasis).toBe("operator_placeholder");
156
    }
157
  });
158
159
  test("survives a payload that is not a model list", () => {
160
    expect(pricingFromModelsPayload(null)).toEqual({});
161
    expect(pricingFromModelsPayload({ models: "nope" })).toEqual({});
162
    expect(pricingFromModelsPayload({ models: [{ id: 1 }, {}] })).toEqual({});
163
  });
164
});
packages/coder-effectiveness/src/pricing.ts added +260

@@ -0,0 +1,260 @@

1
/**
2
 * Pinned rate catalog for the coder lanes the effectiveness suite grades, and
3
 * the one rule that makes the suite worth running: an unpriced lane reports
4
 * `unknown`, never zero.
5
 *
6
 * SOURCE OF TRUTH: the forge model catalog at `OpenAgentsInc/openagents.com`
7
 * `config/config.exs` under `config :openagents, model_catalog:`, projected
8
 * publicly by `lib/openagents/inference/models.ex` onto `GET /api/v1/models`.
9
 * That catalog — not the Cloudflare worker table in
10
 * `apps/openagents.com/workers/api/src/inference/pricing.ts` — is the one that
11
 * prices the ids the coder actually runs (`gemini-3.7-flash`, `ox-alpha`,
12
 * `gpt-5.6-luna`). The worker table prices a different model generation
13
 * (`sonnet`, `gemini-3.6-flash`, ...) and shares no id with the coder catalog.
14
 *
15
 * The rows below are a PINNED snapshot, following the precedent set by
16
 * `packages/rlm-recall-eval/src/price-catalog.ts`: the forge config is not a
17
 * TypeScript module this package can import, a pinned version keeps a graded
18
 * run reproducible from a clean checkout, and cost provenance has to be
19
 * auditable rather than implied. {@link pricingFromModelsPayload} re-derives
20
 * the same rows from a live `GET /api/v1/models` body when an operator wants
21
 * to score against the deployed catalog instead of the snapshot.
22
 *
23
 * TWO SEPARATE HONESTY PROBLEMS LIVE HERE, AND THEY ARE NOT THE SAME PROBLEM.
24
 *
25
 * 1. UNPRICED. `gpt-5.6-luna` — the lane the graded runs actually use most —
26
 *    has no `pricing` key in the forge catalog at all. The config says why:
27
 *    "This entry deliberately omits `pricing`, so a grant pinned to it records
28
 *    no estimated cost rather than a made-up zero." A local `ollama:` lane is
29
 *    unpriced for a different reason: it burns wall clock and electricity, not
30
 *    metered tokens, so no per-token rate exists to apply. Both return
31
 *    `usd: null`. Neither is ever priced at 0, and neither falls back to the
32
 *    worker's `UNKNOWN_MODEL_COST` — that constant exists so an un-tabled
33
 *    model is not UNDER-charged at the till, and it is explicitly "not a
34
 *    measured rate". Charging conservatively and measuring honestly are
35
 *    opposite jobs; borrowing that number here would fabricate the very
36
 *    figure this suite exists to report.
37
 *
38
 * 2. PLACEHOLDER. The two ids that DO carry rates carry rates the forge config
39
 *    marks itself: "Placeholder: the operator must set real provider rates
40
 *    before accepting any spend." A number derived from those rates is
41
 *    arithmetically correct and economically provisional. It is reported, and
42
 *    it is labelled {@link RateBasis} `operator_placeholder` everywhere it
43
 *    travels, so a threshold can refuse to score against it.
44
 */
45
46
/** Pinned catalog version. Bump when the rows below change. */
47
export const CODER_RATE_CATALOG_VERSION = "openagents.coder-rate-catalog.2026-08-25" as const;
48
49
/** Human-auditable pointer back to the forge source of truth. */
50
export const CODER_RATE_CATALOG_SOURCE_REF =
51
  "OpenAgentsInc/openagents.com config/config.exs#model_catalog" as const;
52
53
/**
54
 * Where a rate came from. `operator_placeholder` is a rate the forge config
55
 * marks as provisional; `operator_confirmed` is a rate an operator has
56
 * reconciled against a provider invoice. Every row is a placeholder today.
57
 */
58
export type RateBasis = "operator_placeholder" | "operator_confirmed";
59
60
/** A per-model rate row, in USD per 1,000,000 tokens. */
61
export interface ModelRateRow {
62
  readonly modelId: string;
63
  readonly inputUsdPerMtok: number;
64
  /**
65
   * The rate for cached-read input tokens. The forge prices cached reads at
66
   * this rate and charges cache WRITES as ordinary input, because a write is
67
   * not a read. Where the catalog declares no cached rate, it falls back to
68
   * the full input rate, and this field records that resolved fallback.
69
   */
70
  readonly cachedInputUsdPerMtok: number;
71
  readonly outputUsdPerMtok: number;
72
  readonly rateBasis: RateBasis;
73
}
74
75
/**
76
 * The pinned rows. Values are the forge catalog's integer micro-USD per
77
 * million tokens converted to USD per million tokens.
78
 */
79
export const CODER_RATE_CATALOG: Readonly<Record<string, ModelRateRow>> = {
80
  // pricing: input 1_250_000 / output 10_000_000 / cached 100_000 micro-USD per Mtok.
81
  "gemini-3.7-flash": {
82
    modelId: "gemini-3.7-flash",
83
    inputUsdPerMtok: 1.25,
84
    cachedInputUsdPerMtok: 0.1,
85
    outputUsdPerMtok: 10.0,
86
    rateBasis: "operator_placeholder",
87
  },
88
  // pricing: input 500_000 / output 2_000_000 micro-USD per Mtok, no cached
89
  // rate declared, so cached reads resolve to the full input rate.
90
  "ox-alpha": {
91
    modelId: "ox-alpha",
92
    inputUsdPerMtok: 0.5,
93
    cachedInputUsdPerMtok: 0.5,
94
    outputUsdPerMtok: 2.0,
95
    rateBasis: "operator_placeholder",
96
  },
97
};
98
99
/**
100
 * Model ids the coder can be pinned to that the catalog deliberately leaves
101
 * unpriced, with the catalog's own stated reason. Listing them separates "we
102
 * looked and the catalog refuses to price this" from "we have never heard of
103
 * this id", which is a different and less confident finding.
104
 */
105
export const DELIBERATELY_UNPRICED_MODELS: Readonly<Record<string, string>> = {
106
  "gpt-5.6-luna":
107
    "the forge model catalog omits `pricing` for this id so a grant pinned to it records no estimated cost rather than a made-up zero",
108
};
109
110
/** Token usage offered for pricing. A `null` count is unknown, not zero. */
111
export interface UsageForCost {
112
  readonly promptTokens: number | null;
113
  readonly completionTokens: number | null;
114
  /** Cached-read input tokens, already included in `promptTokens`. */
115
  readonly cachedInputTokens: number;
116
}
117
118
/**
119
 * Why a cost figure is or is not known. Every disposition other than `known`
120
 * carries `usd: null` so an aggregate can exclude or flag it rather than
121
 * silently adding zero to a total.
122
 */
123
export type CostDisposition =
124
  | "known"
125
  | "unpriced_model"
126
  | "unmetered_local_lane"
127
  | "unknown_model"
128
  | "unknown_usage";
129
130
export interface CostResult {
131
  readonly usd: number | null;
132
  readonly disposition: CostDisposition;
133
  readonly rateBasis: RateBasis | null;
134
  /** One sentence a report can print next to an unknown. */
135
  readonly reason: string;
136
}
137
138
/** The local lane runs on hardware you own; no per-token rate applies. */
139
const isLocalLaneModel = (modelId: string): boolean =>
140
  modelId.startsWith("ollama:") || modelId.startsWith("ollama/");
141
142
/**
143
 * Price one trial's usage against a rate catalog.
144
 *
145
 * Unknown stays unknown. A model with no rate, a local lane with no per-token
146
 * rate at all, or usage missing either token dimension all return `usd: null`
147
 * with a disposition that names the reason. Nothing here falls back to a
148
 * conservative default rate: this function measures, it does not charge.
149
 */
150
export const priceUsage = (
151
  modelId: string | null,
152
  usage: UsageForCost,
153
  catalog: Readonly<Record<string, ModelRateRow>> = CODER_RATE_CATALOG,
154
): CostResult => {
155
  if (modelId === null || modelId === "") {
156
    return {
157
      usd: null,
158
      disposition: "unknown_model",
159
      rateBasis: null,
160
      reason: "the trial records no model id, so no rate can be selected",
161
    };
162
  }
163
  if (isLocalLaneModel(modelId)) {
164
    return {
165
      usd: null,
166
      disposition: "unmetered_local_lane",
167
      rateBasis: null,
168
      reason: `${modelId} runs on the local lane, which bills no metered tokens, so it has no per-token cost`,
169
    };
170
  }
171
  const row = catalog[modelId];
172
  if (row === undefined) {
173
    const declared = DELIBERATELY_UNPRICED_MODELS[modelId];
174
    return {
175
      usd: null,
176
      disposition: declared === undefined ? "unknown_model" : "unpriced_model",
177
      rateBasis: null,
178
      reason:
179
        declared === undefined
180
          ? `${modelId} is absent from the rate catalog, so its cost is unknown`
181
          : `${modelId} is unpriced: ${declared}`,
182
    };
183
  }
184
  if (usage.promptTokens === null || usage.completionTokens === null) {
185
    return {
186
      usd: null,
187
      disposition: "unknown_usage",
188
      rateBasis: row.rateBasis,
189
      reason: `${modelId} has a rate but the trial reports no token counts, so its cost is unknown`,
190
    };
191
  }
192
  if (usage.cachedInputTokens > usage.promptTokens) {
193
    return {
194
      usd: null,
195
      disposition: "unknown_usage",
196
      rateBasis: row.rateBasis,
197
      reason: `${modelId} reports ${String(usage.cachedInputTokens)} cached-read tokens inside ${String(usage.promptTokens)} prompt tokens, which cannot both be true`,
198
    };
199
  }
200
  const uncachedInput = usage.promptTokens - usage.cachedInputTokens;
201
  const usd =
202
    (uncachedInput / 1_000_000) * row.inputUsdPerMtok +
203
    (usage.cachedInputTokens / 1_000_000) * row.cachedInputUsdPerMtok +
204
    (usage.completionTokens / 1_000_000) * row.outputUsdPerMtok;
205
  return {
206
    usd,
207
    disposition: "known",
208
    rateBasis: row.rateBasis,
209
    reason: `${modelId} priced from the ${row.rateBasis === "operator_placeholder" ? "placeholder" : "confirmed"} catalog rate`,
210
  };
211
};
212
213
/**
214
 * Re-derive rate rows from a live `GET /api/v1/models` body, so a run can be
215
 * scored against the catalog a deployment actually serves rather than the
216
 * pinned snapshot.
217
 *
218
 * The forge omits the `pricing` key entirely for an unpriced model. That
219
 * absence is the signal, and it is preserved: such an id yields no row, so
220
 * {@link priceUsage} reports it unknown rather than zero. Rates arrive as
221
 * integer micro-USD per million tokens and are converted to USD per million.
222
 */
223
export const pricingFromModelsPayload = (
224
  payload: unknown,
225
): Readonly<Record<string, ModelRateRow>> => {
226
  const rows: Record<string, ModelRateRow> = {};
227
  const models = readArray(readField(payload, "models") ?? readField(payload, "data"));
228
  for (const model of models) {
229
    const id = readString(readField(model, "id"));
230
    if (id === null) continue;
231
    const pricing = readField(model, "pricing");
232
    if (pricing === undefined || pricing === null) continue;
233
    const input = readNumber(readField(pricing, "input_per_million_tokens"));
234
    const output = readNumber(readField(pricing, "output_per_million_tokens"));
235
    if (input === null || output === null) continue;
236
    const cached = readNumber(readField(pricing, "cached_input_per_million_tokens"));
237
    rows[id] = {
238
      modelId: id,
239
      inputUsdPerMtok: input / 1_000_000,
240
      // No declared cached rate means cached reads bill at the input rate.
241
      cachedInputUsdPerMtok: (cached ?? input) / 1_000_000,
242
      outputUsdPerMtok: output / 1_000_000,
243
      // A served catalog states rates, never their provenance. It cannot
244
      // promise an operator reconciled them, so the basis stays placeholder.
245
      rateBasis: "operator_placeholder",
246
    };
247
  }
248
  return rows;
249
};
250
251
const readField = (value: unknown, key: string): unknown =>
252
  typeof value === "object" && value !== null ? (value as Record<string, unknown>)[key] : undefined;
253
254
const readArray = (value: unknown): ReadonlyArray<unknown> => (Array.isArray(value) ? value : []);
255
256
const readString = (value: unknown): string | null =>
257
  typeof value === "string" && value !== "" ? value : null;
258
259
const readNumber = (value: unknown): number | null =>
260
  typeof value === "number" && Number.isFinite(value) ? value : null;
packages/coder-effectiveness/src/render.ts added +105

@@ -0,0 +1,105 @@

1
/**
2
 * Render the effectiveness report as text.
3
 *
4
 * The rendering rule matches the measuring rule: an unknown prints as
5
 * `unknown` with the reason beside it, never as `$0.0000` and never as a
6
 * blank. A reader who skims one line of this output should not be able to
7
 * come away believing a lane was measured when it was not.
8
 */
9
10
import type { EffectivenessReport } from "./effectiveness.ts";
11
import { CODER_RATE_CATALOG_SOURCE_REF } from "./pricing.ts";
12
import type { ThresholdGate } from "./thresholds.ts";
13
14
const usd = (value: number): string => `$${value.toFixed(4)}`;
15
16
const rate = (value: number | null): string =>
17
  value === null ? "unknown" : `${(value * 100).toFixed(1)}%`;
18
19
const count = (value: number | null): string =>
20
  value === null ? "unknown" : value.toLocaleString("en-US");
21
22
export const renderReport = (report: EffectivenessReport, gate: ThresholdGate | null): string => {
23
  const lines: Array<string> = [];
24
25
  lines.push(`Coder effectiveness — ${report.suite} on the ${report.lane} lane`);
26
  lines.push(`  run digest      ${report.runDigest}`);
27
  if (report.jobId !== null) lines.push(`  harbor job      ${report.jobId}`);
28
  lines.push(`  model           ${report.models.join(", ") || "unknown"}`);
29
  lines.push(`  cli version     ${report.agentVersions.join(", ") || "unknown"}`);
30
  lines.push(`  rate catalog    ${report.rateCatalogVersion}`);
31
  lines.push(`  rate source     ${CODER_RATE_CATALOG_SOURCE_REF}`);
32
  lines.push("");
33
34
  lines.push("Outcomes");
35
  lines.push(
36
    `  accepted ${String(report.accepted)} · rejected ${String(report.rejected)} · ungraded ${String(report.ungraded)} of ${String(report.trialsTotal)}`,
37
  );
38
  lines.push(
39
    `  success rate    ${rate(report.successRate)} over ${String(report.graded)} graded trials`,
40
  );
41
  if (report.ungraded > 0) {
42
    lines.push(
43
      `  ungraded        ${report.ungradedRatio.toFixed(3)} of trials had no verifier result, so they are neither pass nor fail`,
44
    );
45
  }
46
  lines.push("");
47
48
  lines.push("Cost per accepted outcome");
49
  const cost = report.costPerAcceptedOutcome;
50
  if (cost.usd === null) {
51
    lines.push(`  ${"unknown".padEnd(15)} (${cost.disposition})`);
52
    lines.push(`  ${" ".repeat(15)} ${cost.reason}`);
53
  } else {
54
    const provisional = cost.rateBasis === "operator_placeholder" ? "  [placeholder rates]" : "";
55
    lines.push(`  ${usd(cost.usd).padEnd(15)} per accepted outcome${provisional}`);
56
    lines.push(
57
      `  ${" ".repeat(15)} ${report.cost.totalUsd === null ? "unknown" : usd(report.cost.totalUsd)} total over ${String(report.accepted)} accepted`,
58
    );
59
    if (cost.rateBasis === "operator_placeholder") {
60
      lines.push(`  ${" ".repeat(15)} ${cost.reason}`);
61
    }
62
  }
63
  lines.push(
64
    `  cost coverage   ${report.cost.pricedTrials} of ${String(report.cost.pricedTrials + report.cost.unpricedTrials)} trials priced (${report.cost.coverage})`,
65
  );
66
  for (const reason of report.cost.unpricedReasons) {
67
    lines.push(`    unpriced      ${reason}`);
68
  }
69
  lines.push("");
70
71
  lines.push("Volume");
72
  lines.push(`  prompt tokens   ${count(report.promptTokens)}`);
73
  lines.push(`  output tokens   ${count(report.completionTokens)}`);
74
  lines.push(`  cached reads    ${count(report.cachedInputTokens)}`);
75
  lines.push(`  tool calls      ${count(report.toolCalls)}`);
76
  lines.push(
77
    `  wall clock      ${report.wallClockSeconds === null ? "unknown" : `${report.wallClockSeconds.toFixed(1)}s`}`,
78
  );
79
  lines.push("");
80
81
  lines.push("Trials");
82
  for (const trial of report.perTrial) {
83
    const money = trial.usd === null ? `unknown (${trial.disposition})` : usd(trial.usd);
84
    lines.push(`  ${trial.outcome.padEnd(9)} ${trial.task.padEnd(34)} ${money}`);
85
  }
86
87
  if (gate !== null) {
88
    lines.push("");
89
    lines.push(`Gate ${gate.thresholdsId}: ${gate.status.toUpperCase()}`);
90
    for (const criterion of gate.criteria) {
91
      const mark =
92
        criterion.verdict === "passed" ? "pass" : criterion.verdict === "failed" ? "FAIL" : "n/a ";
93
      lines.push(`  [${mark}] ${criterion.name}`);
94
      lines.push(`         ${criterion.detail}`);
95
    }
96
    if (gate.status === "unverifiable") {
97
      lines.push("");
98
      lines.push("  This gate is unverifiable, which is not a pass. A criterion could not be");
99
      lines.push("  measured on this run — most often because the lane carries no published");
100
      lines.push("  rate — so the run proves nothing about that floor.");
101
    }
102
  }
103
104
  return `${lines.join("\n")}\n`;
105
};
packages/coder-effectiveness/src/thresholds.test.ts added +205

@@ -0,0 +1,205 @@

1
/**
2
 * The gate, including the case that matters most: a cost floor that cannot be
3
 * measured must not read as a pass.
4
 */
5
6
import { readFileSync } from "node:fs";
7
import { fileURLToPath } from "node:url";
8
import { describe, expect, test } from "vite-plus/test";
9
10
import { summarizeRun } from "./effectiveness.ts";
11
import { readHarborJob } from "./harbor-job.ts";
12
import { CODER_RATE_CATALOG_VERSION } from "./pricing.ts";
13
import { type EffectivenessThresholds, evaluateThresholds, parseThresholds } from "./thresholds.ts";
14
15
const fixture = (name: string): string =>
16
  fileURLToPath(new URL(`../fixtures/${name}`, import.meta.url));
17
18
const report = (name: string) =>
19
  summarizeRun(
20
    readHarborJob(fixture(name), {
21
      suite: "tb2-cross-section",
22
      lane: "proxy",
23
      rateCatalogVersion: CODER_RATE_CATALOG_VERSION,
24
    }),
25
  );
26
27
const floors = (overrides: Partial<EffectivenessThresholds> = {}): EffectivenessThresholds =>
28
  parseThresholds({
29
    id: "test-floors",
30
    minGradedTrials: 2,
31
    minSuccessRate: 0.4,
32
    maxUngradedRatio: 0.25,
33
    ...overrides,
34
  });
35
36
const criterion = (
37
  gate: ReturnType<typeof evaluateThresholds>,
38
  fragment: string,
39
): { name: string; verdict: string; detail: string } => {
40
  const found = gate.criteria.find((entry) => entry.name.includes(fragment));
41
  if (found === undefined) throw new Error(`no criterion matching ${fragment}`);
42
  return found;
43
};
44
45
describe("parseThresholds", () => {
46
  test("reads the checked-in suite floors", () => {
47
    const path = fileURLToPath(new URL("../thresholds/tb2-cross-section.json", import.meta.url));
48
    const thresholds = parseThresholds(JSON.parse(readFileSync(path, "utf8")));
49
50
    expect(thresholds.id).toBe("tb2-cross-section");
51
    expect(thresholds.maxCostPerAcceptedOutcomeUsd).toBeGreaterThan(0);
52
    // Left off deliberately: every catalog rate is provisional today.
53
    expect(thresholds.acceptPlaceholderRates).toBeUndefined();
54
  });
55
56
  test("rejects a rate outside 0..1", () => {
57
    expect(() => floors({ minSuccessRate: 1.5 })).toThrow(/minSuccessRate/u);
58
    expect(() => floors({ maxUngradedRatio: -0.1 })).toThrow(/maxUngradedRatio/u);
59
  });
60
61
  test("rejects a fractional or negative trial count", () => {
62
    expect(() => floors({ minGradedTrials: 2.5 })).toThrow(/minGradedTrials/u);
63
    expect(() => floors({ minGradedTrials: -1 })).toThrow(/minGradedTrials/u);
64
  });
65
66
  test("rejects a cost ceiling of zero", () => {
67
    expect(() => floors({ maxCostPerAcceptedOutcomeUsd: 0 })).toThrow(
68
      /maxCostPerAcceptedOutcomeUsd/u,
69
    );
70
  });
71
});
72
73
describe("evaluateThresholds", () => {
74
  test("passes a run that clears every floor", () => {
75
    const gate = evaluateThresholds(report("priced-lane"), floors());
76
77
    expect(gate.status).toBe("passed");
78
    expect(gate.criteria.every((entry) => entry.verdict === "passed")).toBe(true);
79
  });
80
81
  test("fails a run below the success floor", () => {
82
    // The regressed lane accepts 1 of 4 graded trials.
83
    const gate = evaluateThresholds(report("regressed-lane"), floors({ minSuccessRate: 0.4 }));
84
85
    expect(gate.status).toBe("failed");
86
    expect(criterion(gate, "success_rate").verdict).toBe("failed");
87
  });
88
89
  test("fails a run with too few graded trials", () => {
90
    const gate = evaluateThresholds(report("crashed-verifier"), floors({ minGradedTrials: 3 }));
91
92
    expect(gate.status).toBe("failed");
93
    expect(criterion(gate, "graded_trials").verdict).toBe("failed");
94
  });
95
96
  test("fails a run where too many verifiers crashed", () => {
97
    const gate = evaluateThresholds(
98
      report("crashed-verifier"),
99
      floors({ minGradedTrials: 1, maxUngradedRatio: 0.25 }),
100
    );
101
102
    expect(gate.status).toBe("failed");
103
    expect(criterion(gate, "ungraded_ratio").verdict).toBe("failed");
104
  });
105
106
  // The rule that keeps an unpriced lane accountable.
107
  test("leaves a cost floor unverifiable on the unpriced luna lane, never passed", () => {
108
    const gate = evaluateThresholds(
109
      report("unpriced-lane"),
110
      floors({ minSuccessRate: 0.5, maxCostPerAcceptedOutcomeUsd: 2 }),
111
    );
112
113
    const cost = criterion(gate, "cost_per_accepted_outcome");
114
    expect(cost.verdict).toBe("unverifiable");
115
    expect(cost.verdict).not.toBe("passed");
116
    expect(cost.detail).toContain("gpt-5.6-luna");
117
    expect(gate.status).toBe("unverifiable");
118
  });
119
120
  test("leaves a cost floor unverifiable when only some trials were priced", () => {
121
    const gate = evaluateThresholds(
122
      report("mixed-lane"),
123
      floors({ maxCostPerAcceptedOutcomeUsd: 2 }),
124
    );
125
126
    expect(criterion(gate, "cost_per_accepted_outcome").verdict).toBe("unverifiable");
127
    expect(gate.status).toBe("unverifiable");
128
  });
129
130
  test("will not score a dollar ceiling against placeholder rates by default", () => {
131
    // The priced lane has a real number. It is still built from rates the
132
    // forge config marks provisional, so scoring against it needs consent.
133
    const gate = evaluateThresholds(
134
      report("priced-lane"),
135
      floors({ maxCostPerAcceptedOutcomeUsd: 100 }),
136
    );
137
138
    expect(criterion(gate, "cost_per_accepted_outcome").verdict).toBe("unverifiable");
139
    expect(gate.status).toBe("unverifiable");
140
  });
141
142
  test("scores the ceiling once the thresholds file opts into placeholder rates", () => {
143
    const under = evaluateThresholds(
144
      report("priced-lane"),
145
      floors({ maxCostPerAcceptedOutcomeUsd: 100, acceptPlaceholderRates: true }),
146
    );
147
    const over = evaluateThresholds(
148
      report("priced-lane"),
149
      floors({ maxCostPerAcceptedOutcomeUsd: 0.0001, acceptPlaceholderRates: true }),
150
    );
151
152
    expect(criterion(under, "cost_per_accepted_outcome").verdict).toBe("passed");
153
    expect(under.status).toBe("passed");
154
    expect(criterion(over, "cost_per_accepted_outcome").verdict).toBe("failed");
155
    expect(over.status).toBe("failed");
156
  });
157
158
  test("omits the cost criterion when the thresholds file declares no ceiling", () => {
159
    const gate = evaluateThresholds(report("unpriced-lane"), floors({ minSuccessRate: 0.5 }));
160
161
    expect(gate.criteria.some((entry) => entry.name.includes("cost"))).toBe(false);
162
    expect(gate.status).toBe("passed");
163
  });
164
165
  test("a measured breach outranks an unmeasurable criterion", () => {
166
    // The regressed lane breaches the success floor and cannot be scored on
167
    // cost. `failed` is the honest status: something was measured and broke.
168
    const gate = evaluateThresholds(
169
      report("regressed-lane"),
170
      floors({ minSuccessRate: 0.5, maxCostPerAcceptedOutcomeUsd: 2 }),
171
    );
172
173
    expect(criterion(gate, "success_rate").verdict).toBe("failed");
174
    expect(criterion(gate, "cost_per_accepted_outcome").verdict).toBe("unverifiable");
175
    expect(gate.status).toBe("failed");
176
  });
177
178
  test("reports no success rate rather than a zero one when no verifier ran", () => {
179
    const ungradedOnly = summarizeRun({
180
      jobId: "job-none",
181
      suite: "tb2-cross-section",
182
      lane: "proxy",
183
      runDigest: "effectiveness:test",
184
      trials: [
185
        {
186
          task: "fix-git",
187
          outcome: "ungraded",
188
          modelId: "gemini-3.7-flash",
189
          agentVersion: "0.4.0",
190
          promptTokens: 1000,
191
          completionTokens: 100,
192
          cachedInputTokens: 0,
193
          toolCalls: 1,
194
          wallClockSeconds: 10,
195
          threadId: null,
196
          exception: "VerifierCrashedError",
197
        },
198
      ],
199
    });
200
    const gate = evaluateThresholds(ungradedOnly, floors({ minGradedTrials: 0 }));
201
202
    expect(criterion(gate, "success_rate").verdict).toBe("unverifiable");
203
    expect(criterion(gate, "success_rate").detail).toContain("no verifier ran");
204
  });
205
});
packages/coder-effectiveness/src/thresholds.ts added +181

@@ -0,0 +1,181 @@

1
/**
2
 * The floors a graded run is scored against, and the third verdict that keeps
3
 * an unpriced lane from passing a cost floor by default.
4
 *
5
 * A criterion is `passed`, `failed`, or `unverifiable`. The third one is the
6
 * point. If a thresholds file declares a cost ceiling and the run happened on
7
 * `gpt-5.6-luna`, the honest answer is not "under budget" — nothing was
8
 * measured. Reporting that as a pass would make the gate quietest exactly when
9
 * the lane is least accountable, which is the failure mode the suite is
10
 * supposed to remove. So an unmeasurable criterion makes the whole gate
11
 * `unverifiable`, and the CLI exits 2 rather than 0, so no scheduled job can
12
 * read silence as green.
13
 *
14
 * The same rule covers placeholder rates. The catalog rates are marked
15
 * provisional by the config that holds them, so scoring a dollar ceiling
16
 * against them is scoring against a guess. That is `unverifiable` too, unless
17
 * the thresholds file opts in with `acceptPlaceholderRates: true` — which is
18
 * a reasonable thing to do for a relative regression check, and an unreasonable
19
 * thing to do quietly.
20
 */
21
22
import { Schema as S } from "effect";
23
24
import type { EffectivenessReport } from "./effectiveness.ts";
25
26
export const EffectivenessThresholdsSchema = S.Struct({
27
  /** Names this floor set, so a report can say what it was scored against. */
28
  id: S.String,
29
  /** Fewest verifier-judged trials for the run to be worth scoring at all. */
30
  minGradedTrials: S.Number,
31
  /** Lowest acceptable accepted-over-graded rate, 0..1. */
32
  minSuccessRate: S.Number,
33
  /** Most of the run that may go ungraded before the run is unreadable, 0..1. */
34
  maxUngradedRatio: S.Number,
35
  /** Optional dollar ceiling on cost per accepted outcome. */
36
  maxCostPerAcceptedOutcomeUsd: S.optional(S.Number),
37
  /**
38
   * Score the cost ceiling against rates the catalog marks provisional.
39
   * Absent or false, a placeholder-priced run leaves the cost criterion
40
   * unverifiable rather than passing it.
41
   */
42
  acceptPlaceholderRates: S.optional(S.Boolean),
43
});
44
45
export type EffectivenessThresholds = typeof EffectivenessThresholdsSchema.Type;
46
47
const decodeThresholds = S.decodeUnknownSync(EffectivenessThresholdsSchema);
48
49
/** Parse a thresholds document, rejecting values outside their ranges. */
50
export const parseThresholds = (value: unknown): EffectivenessThresholds => {
51
  const thresholds = decodeThresholds(value);
52
  assertFraction("minSuccessRate", thresholds.minSuccessRate);
53
  assertFraction("maxUngradedRatio", thresholds.maxUngradedRatio);
54
  if (thresholds.minGradedTrials < 0 || !Number.isInteger(thresholds.minGradedTrials)) {
55
    throw new Error("minGradedTrials must be a non-negative integer");
56
  }
57
  if (
58
    thresholds.maxCostPerAcceptedOutcomeUsd !== undefined &&
59
    !(thresholds.maxCostPerAcceptedOutcomeUsd > 0)
60
  ) {
61
    throw new Error("maxCostPerAcceptedOutcomeUsd must be greater than zero");
62
  }
63
  return thresholds;
64
};
65
66
const assertFraction = (name: string, value: number): void => {
67
  if (!(value >= 0 && value <= 1)) {
68
    throw new Error(`${name} must be between 0 and 1, got ${String(value)}`);
69
  }
70
};
71
72
export type CriterionVerdict = "passed" | "failed" | "unverifiable";
73
74
export interface ThresholdCriterion {
75
  readonly name: string;
76
  readonly verdict: CriterionVerdict;
77
  readonly detail: string;
78
}
79
80
export interface ThresholdGate {
81
  readonly thresholdsId: string;
82
  /**
83
   * `failed` beats `unverifiable` beats `passed`: a measured breach is a
84
   * breach whatever else could not be measured.
85
   */
86
  readonly status: CriterionVerdict;
87
  readonly criteria: ReadonlyArray<ThresholdCriterion>;
88
}
89
90
/** Score a report against its floors. Pure. */
91
export const evaluateThresholds = (
92
  report: EffectivenessReport,
93
  thresholds: EffectivenessThresholds,
94
): ThresholdGate => {
95
  const criteria: Array<ThresholdCriterion> = [
96
    gradedTrialsCriterion(report, thresholds),
97
    successRateCriterion(report, thresholds),
98
    ungradedRatioCriterion(report, thresholds),
99
  ];
100
  const cost = costCriterion(report, thresholds);
101
  if (cost !== null) criteria.push(cost);
102
103
  return {
104
    thresholdsId: thresholds.id,
105
    status: criteria.some((criterion) => criterion.verdict === "failed")
106
      ? "failed"
107
      : criteria.some((criterion) => criterion.verdict === "unverifiable")
108
        ? "unverifiable"
109
        : "passed",
110
    criteria,
111
  };
112
};
113
114
const gradedTrialsCriterion = (
115
  report: EffectivenessReport,
116
  thresholds: EffectivenessThresholds,
117
): ThresholdCriterion => ({
118
  name: `graded_trials>=${String(thresholds.minGradedTrials)}`,
119
  verdict: report.graded >= thresholds.minGradedTrials ? "passed" : "failed",
120
  detail: `${String(report.graded)} of ${String(report.trialsTotal)} trials were graded`,
121
});
122
123
const successRateCriterion = (
124
  report: EffectivenessReport,
125
  thresholds: EffectivenessThresholds,
126
): ThresholdCriterion => {
127
  const name = `success_rate>=${thresholds.minSuccessRate.toFixed(3)}`;
128
  if (report.successRate === null) {
129
    return {
130
      name,
131
      verdict: "unverifiable",
132
      detail: "no verifier ran, so the run has no success rate rather than a zero one",
133
    };
134
  }
135
  return {
136
    name,
137
    verdict: report.successRate >= thresholds.minSuccessRate ? "passed" : "failed",
138
    detail: `success rate ${report.successRate.toFixed(3)} over ${String(report.graded)} graded trials`,
139
  };
140
};
141
142
const ungradedRatioCriterion = (
143
  report: EffectivenessReport,
144
  thresholds: EffectivenessThresholds,
145
): ThresholdCriterion => ({
146
  name: `ungraded_ratio<=${thresholds.maxUngradedRatio.toFixed(3)}`,
147
  verdict: report.ungradedRatio <= thresholds.maxUngradedRatio ? "passed" : "failed",
148
  detail: `${String(report.ungraded)} of ${String(report.trialsTotal)} trials went ungraded (${report.ungradedRatio.toFixed(3)})`,
149
});
150
151
/** `null` when the thresholds file declares no cost ceiling. */
152
const costCriterion = (
153
  report: EffectivenessReport,
154
  thresholds: EffectivenessThresholds,
155
): ThresholdCriterion | null => {
156
  const ceiling = thresholds.maxCostPerAcceptedOutcomeUsd;
157
  if (ceiling === undefined) return null;
158
159
  const name = `cost_per_accepted_outcome<=$${ceiling.toFixed(4)}`;
160
  const cost = report.costPerAcceptedOutcome;
161
162
  if (cost.usd === null) {
163
    return {
164
      name,
165
      verdict: "unverifiable",
166
      detail: `cost per accepted outcome is unknown (${cost.disposition}): ${cost.reason}`,
167
    };
168
  }
169
  if (cost.rateBasis === "operator_placeholder" && thresholds.acceptPlaceholderRates !== true) {
170
    return {
171
      name,
172
      verdict: "unverifiable",
173
      detail: `cost per accepted outcome is $${cost.usd.toFixed(4)} but every rate behind it is an operator placeholder; set acceptPlaceholderRates to score against provisional rates`,
174
    };
175
  }
176
  return {
177
    name,
178
    verdict: cost.usd <= ceiling ? "passed" : "failed",
179
    detail: `cost per accepted outcome $${cost.usd.toFixed(4)} against a $${ceiling.toFixed(4)} ceiling`,
180
  };
181
};
packages/coder-effectiveness/thresholds/tb2-cross-section.json added +19

@@ -0,0 +1,19 @@

1
{
2
  "$comment": [
3
    "Floors for the tb2-cross-section suite (bench/suites/tb2-cross-section.txt).",
4
    "A run below a floor fails the gate. A floor that cannot be measured on the",
5
    "run leaves the gate unverifiable, which exits 2 and is not a pass.",
6
    "acceptPlaceholderRates is deliberately absent: every rate in the forge",
7
    "model catalog is marked provisional today, so a dollar ceiling scored",
8
    "against them would be scored against a guess. Set it to true only for a",
9
    "relative regression check, and say so when you do.",
10
    "minSuccessRate starts low on purpose. It is a floor that catches a",
11
    "regression, not a target that flatters a number, and it should be raised",
12
    "as scheduled runs establish what the lane actually holds."
13
  ],
14
  "id": "tb2-cross-section",
15
  "minGradedTrials": 8,
16
  "minSuccessRate": 0.25,
17
  "maxUngradedRatio": 0.2,
18
  "maxCostPerAcceptedOutcomeUsd": 2.0
19
}
packages/coder-effectiveness/tsconfig.json added +15

@@ -0,0 +1,15 @@

1
{
2
  "compilerOptions": {
3
    "target": "ES2022",
4
    "lib": ["ES2023"],
5
    "module": "ESNext",
6
    "moduleResolution": "Bundler",
7
    "strict": true,
8
    "skipLibCheck": true,
9
    "noEmit": true,
10
    "allowImportingTsExtensions": true,
11
    "types": ["node", "vite-plus-matchers"],
12
    "exactOptionalPropertyTypes": true
13
  },
14
  "include": ["src/**/*.ts"]
15
}
pnpm-lock.yaml modified +22

@@ -1527,6 +1527,25 @@ importers:

1527 1527
        specifier: 'catalog:'
1528 1528
        version: 6.0.3
1529 1529
1530
  packages/coder-effectiveness:
1531
    dependencies:
1532
      effect:
1533
        specifier: 4.0.0-beta.94
1534
        version: 4.0.0-beta.94
1535
    devDependencies:
1536
      '@types/node':
1537
        specifier: 24.13.1
1538
        version: 24.13.1
1539
      '@types/vite-plus-matchers':
1540
        specifier: workspace:*
1541
        version: link:../../types/vite-plus-matchers
1542
      tsx:
1543
        specifier: 4.20.6
1544
        version: 4.20.6
1545
      typescript:
1546
        specifier: 'catalog:'
1547
        version: 6.0.3
1548
1530 1549
  packages/codex-app-server-protocol:
1531 1550
    dependencies:
1532 1551
      ajv:

@@ -8303,6 +8322,7 @@ packages:

8303 8322
8304 8323
  msgpackr-extract@3.0.4:
8305 8324
    resolution: {integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==}
8325
    hasBin: true
8306 8326
8307 8327
  msgpackr@2.0.4:
8308 8328
    resolution: {integrity: sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA==}

@@ -8377,6 +8397,7 @@ packages:

8377 8397
8378 8398
  node-gyp-build-optional-packages@5.2.2:
8379 8399
    resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
8400
    hasBin: true
8380 8401
8381 8402
  node-gyp-build@4.8.4:
8382 8403
    resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}

@@ -9617,6 +9638,7 @@ packages:

9617 9638
9618 9639
  uuid@14.0.1:
9619 9640
    resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==}
9641
    hasBin: true
9620 9642
9621 9643
  uuid@7.0.3:
9622 9644
    resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==}

This page updates live while a promote is in flight · changelog