@ssheleg/agent-stack 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,51 @@
1
+ ## v0.18.0 — how many runs before a difference is real, and the trajectory rule between its two measured edges
2
+
3
+ `agent-evals` shipped 315 lines of doctrine about **what** to assert with **no
4
+ `references/` directory at all**, and said nothing anywhere about how many times to run
5
+ anything. A skill whose whole job is answering *did it get better* had no way to say
6
+ whether a number was a result or noise.
7
+
8
+ **`references/statistics.md`** is that layer, and every figure in it was recomputed rather
9
+ than quoted:
10
+
11
+ - `SE(p) = √(p(1−p)/n)` — at n=100, p=0.70 the 95% band is **±8.98 pp**, so a 73%-vs-70%
12
+ comparison on a hundred cases is a number inside its own noise. Error falls as `1/√n`,
13
+ which makes the remedy *more tasks*, not more argument.
14
+ - **`pass@k` and `pass^k` differ by 91 points on the same agent** — at p=0.6, k=5 they are
15
+ 99.0% and 7.8%. The first is a capability ceiling a human picks from; the second is what
16
+ a payment or a permission change needs. An operation with side effects may not "retry
17
+ until it works", so `pass@k` is not available to it as a metric at all.
18
+ - **Trials are not independent, and the published data proves it.** τ-bench's airline
19
+ Pass^k for claude-3-5-sonnet runs 0.460 → 0.326 → 0.263 → 0.225, where independence from
20
+ Pass^1 would predict 0.460 → 0.212 → 0.097 → 0.045. Successes cluster by task, not by
21
+ trial. So `pass^k` cannot be computed from `pass^1`, and Anthropic's `0.75³ ≈ 42%` is the
22
+ right shape for an argument and the wrong number for a gate. **This one is ours** — it
23
+ came out of recomputing the table rather than restating it.
24
+ - Pairing on the same tasks and the same 3–5 seeds with McNemar or a paired bootstrap,
25
+ because the task-difficulty variance the previous point measures is exactly what pairing
26
+ removes.
27
+ - **The harness is a variable**: 6 pp between the most- and least-resourced setups on
28
+ Terminal-Bench 2.0 (p<0.01), moving within noise from 1× to 3× (p=0.40) and lifting ~4 pp
29
+ from 3× to uncapped — because generous headroom lets the agent attempt strategies a tight
30
+ cap forbids. Two caps measure two agents. The remedy is a floor *and* a ceiling.
31
+ - A ladder for what a given piece of evidence authorises next, ending on the rule people
32
+ skip: **4/4 on a slice is not 100% system-wide.**
33
+
34
+ **§5's trajectory rule moved between two measurements rather than being deleted.** It read
35
+ *"Judge the trajectory, not just the answer. Right tools, right order, right arguments."*
36
+ Anthropic calls exact tool-order assertions *"too rigid … agents regularly find valid
37
+ approaches that eval designers didn't anticipate"*, with a worked case of an agent that
38
+ solved a τ²-bench booking task through a policy loophole and failed the eval while serving
39
+ the user better. But the opposite edge is measured too: a grader blind to the trajectory
40
+ misses **44% of safety violations and 13% of robustness failures**. So the rule now reads
41
+ *read the trajectory; do not match it* — assert what was produced and what changed, and use
42
+ the trajectory as a **set and a forbidden list** for the claims an outcome cannot carry.
43
+ §2's axis table lost its `→` sequence example for the same reason.
44
+
45
+ The single-step example keeps its *"must call `find_meeting_times` first"*, with a sentence
46
+ saying why: at that granularity the fixture **is** one decision, so ordering is the subject
47
+ rather than a proxy for it. Across a trajectory it stops being one.
48
+
1
49
  # Changelog
2
50
 
3
51
  ## v0.17.1 — the evals run for the first time, and the tails go to zero
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssheleg/agent-stack",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "scripts": {
5
5
  "test": "python3 test/validate.py && python3 test/plant_guard_test.py && node test/installer_test.js"
6
6
  },
@@ -3,7 +3,7 @@
3
3
  "name": "agent-stack",
4
4
  "displayName": "Agent Stack",
5
5
  "description": "Four skills: agent-orchestrator — tool-calling loops, pipelines with checkpoints, provider routing with fallback, memory architecture, plus the wallet side of reselling LLM access; agent-evals — run/trace/thread evals, LLM judges, and fixtures grown from production; agent-interop — MCP servers and clients, A2A agent cards, the MCP Registry, and gateways; agent-harness — system prompts, tool shaping, workflow-vs-agent, and auditing an agent system.",
6
- "version": "0.17.1",
6
+ "version": "0.18.0",
7
7
  "author": {
8
8
  "name": "ssheleg",
9
9
  "url": "https://x.com/sshlg93"
@@ -63,6 +63,10 @@ point — tool name, argument shape.
63
63
  > "Schedule a meeting with Harrison tomorrow morning", with `find_meeting_times`,
64
64
  > `schedule_meeting` and `send_email` available, must call `find_meeting_times` first.
65
65
 
66
+ This is the one granularity where *first* is a legitimate assertion: the fixture is a
67
+ single decision, so the ordering claim is the subject rather than a proxy for it. Across a
68
+ whole trajectory it stops being one — see §5.
69
+
66
70
  Cheap, deterministic, CI-blocking. **Precondition: a stable agent architecture.** These
67
71
  break on a graph refactor, and a suite that fails on every refactor gets deleted.
68
72
 
@@ -72,7 +76,7 @@ Assert on three axes at once, with three different mechanisms:
72
76
 
73
77
  | Axis | Assert | With |
74
78
  |---|---|---|
75
- | Trajectory | tool-call sequence `read_file` `edit_file` `run_tests` | set/subset/order matchers |
79
+ | Trajectory | what the run **must not** do, and what it must have touched — never the order | set/subset matchers, forbidden-call lists |
76
80
  | Final response | quality, tone, policy compliance | rubric or judge |
77
81
  | **State change** | the memory row exists, the file was written, the artifact is there | direct inspection of the side effect |
78
82
 
@@ -147,8 +151,20 @@ That is what makes a judge reproducible and a disagreement resolvable.
147
151
  assertions, tool-call correctness — all deterministic, all faster and cheaper than a model
148
152
  call. Send to a judge only what cannot be decided by code.
149
153
 
150
- **Judge the trajectory, not just the answer.** Right tools, right order, right arguments.
151
- An agent that reaches a correct answer through three wrong tool calls is a latent outage.
154
+ **Read the trajectory; do not match it.** An agent that reaches a correct answer through
155
+ three wrong tool calls is a latent outage and asserting the *sequence* to catch that is
156
+ measurably the wrong instrument. Anthropic names the instinct and rejects its strict form:
157
+ exact tool-order assertions are *"too rigid and results in overly brittle tests, as agents
158
+ regularly find valid approaches that eval designers didn't anticipate"*, and the worked
159
+ case is an agent that solved a τ²-bench booking task through a policy loophole, failing
160
+ the eval as written while serving the user better. Grade **what was produced and what
161
+ changed**, and let the path vary.
162
+
163
+ The opposite edge is measured too, so this is not "grade the final answer": a grader blind
164
+ to the trajectory misses **44% of safety violations and 13% of robustness failures**,
165
+ because a policy breach on the way to a correct result leaves no trace in the outcome. Use
166
+ the trajectory for the claims the outcome cannot carry — a forbidden call, a missing
167
+ confirmation, a secret read — as a **set and a forbidden list**, never as an order.
152
168
 
153
169
  **Calibrate the judge before trusting it.** Collect human labels on the same traces,
154
170
  measure agreement, iterate the judge prompt until agreement is high — *then* let it score
@@ -304,6 +320,16 @@ None of the above runs without these, and they are the part people skip:
304
320
  - [ ] Every production failure minimised into a permanent fixture
305
321
  - [ ] Annotation queue with filters, and the two reviewer roles kept separate
306
322
  - [ ] Simulated users trained on real transcripts, with adversarial personas
323
+ - [ ] Every comparison that changes a decision states `n`, `k`, the reducer and the noise
324
+ band — `references/statistics.md`; a three-point gap on a hundred cases is noise
325
+
326
+ ---
327
+
328
+ ## References
329
+
330
+ | Load | When |
331
+ |---|---|
332
+ | [`references/statistics.md`](references/statistics.md) | a number is about to change a decision — how many runs before a difference is real, `pass@k` vs `pass^k` and why trials are not independent, paired comparison, the harness as a variable, and what a given piece of evidence authorises next |
307
333
 
308
334
  ---
309
335
 
@@ -0,0 +1,201 @@
1
+ # Statistics for agent evals — how many runs before a difference is real
2
+
3
+ **Load this when a number is about to change a decision:** picking between two models or
4
+ two harness configurations, setting a release threshold, deciding whether a regression is
5
+ real, or writing the sentence "X is better than Y" in a document somebody will act on.
6
+
7
+ Everything here is arithmetic over binary outcomes. It needs no library and no
8
+ statistician, and it is the layer most eval advice skips — including this skill's own
9
+ first eight sections, which say what to assert and never say how many times.
10
+
11
+ ## Contents
12
+
13
+ - [The band around a pass rate](#the-band-around-a-pass-rate)
14
+ - [pass@k and pass^k are different questions](#passk-and-passk-are-different-questions)
15
+ - [Trials are not independent, and the published data says so](#trials-are-not-independent-and-the-published-data-says-so)
16
+ - [Pairing: same tasks, same seeds, per-task deltas](#pairing-same-tasks-same-seeds-per-task-deltas)
17
+ - [The harness is a variable, so pin it](#the-harness-is-a-variable-so-pin-it)
18
+ - [What a number authorises](#what-a-number-authorises)
19
+ - [The report contract](#the-report-contract)
20
+
21
+ ---
22
+
23
+ ## The band around a pass rate
24
+
25
+ A pass rate over `n` cases is an estimate, and its standard error is
26
+
27
+ ```
28
+ SE(p) = sqrt( p * (1 - p) / n )
29
+ ```
30
+
31
+ The 95% band is roughly `±1.96 · SE`. Computed, not quoted:
32
+
33
+ | n | p | 95% band |
34
+ |---|---|---|
35
+ | 100 | 0.70 | **±8.98 pp** |
36
+ | 400 | 0.70 | ±4.49 pp |
37
+ | 1000 | 0.70 | ±2.84 pp |
38
+
39
+ ```python
40
+ import math
41
+ def band(p, n): return 1.96 * math.sqrt(p * (1 - p) / n) * 100 # percentage points
42
+ ```
43
+
44
+ **So "the new one gets 73% where the old one got 70%, on a hundred cases" is not a
45
+ result.** It is a number inside its own noise. The error shrinks as `1/√n`, which is the
46
+ whole practical consequence: **the fix for a 2–3 pp expected gain is more tasks, not more
47
+ argument.** Quadrupling the set halves the band.
48
+
49
+ A corollary worth stating because leaderboards invite the opposite: **differences below
50
+ about 3 pp deserve scepticism until both configurations are documented and matched.**
51
+
52
+ > The formula assumes independent cases. A benchmark whose tasks share a fixture, an
53
+ > environment or a generator violates that, and the true band is wider than this. Wider,
54
+ > never narrower — so the table is a floor on your uncertainty, not a ceiling.
55
+
56
+ ## pass@k and pass^k are different questions
57
+
58
+ Two metrics, one letter apart, measuring opposite things.
59
+
60
+ | Metric | Formula | Asks | Who it is for |
61
+ |---|---|---|---|
62
+ | `pass@k` | `1 − (1 − p)^k` | did **at least one** of k attempts succeed | a **capability ceiling** — a human picks the best of k |
63
+ | `pass^k` | `p^k` (if independent) | did **every** one of k succeed | a **reliability floor** — nobody is picking |
64
+
65
+ At `p = 0.6`, `k = 5`:
66
+
67
+ - `pass@5 = 1 − 0.4⁵ = 0.98976` → **99.0%**
68
+ - `pass^5 = 0.6⁵ = 0.07776` → **7.8%**
69
+
70
+ **A 91-point gap between two numbers describing the same agent.** The first makes a demo;
71
+ the second is what a payment, a refund or a permission change actually needs.
72
+
73
+ Anthropic states the same arithmetic for the everyday case: at a 75% per-trial rate, three
74
+ trials all passing is `0.75³ ≈ 42%` — verified, `0.421875`.
75
+
76
+ **The rule that follows: an operation with side effects may not "retry until it works."**
77
+ If a failed attempt leaves a charge, a message or a mutated row behind, `pass@k` is not
78
+ available to you as a metric — you cannot pick the best of five refunds. Sample in a
79
+ sandbox or a rollback-capable environment, and count **every** failure.
80
+
81
+ **A report that gives k without saying which k it means is unreadable.** *k independent
82
+ samples of one task* and *k consecutive tasks on one live pipeline* are different claims.
83
+
84
+ ## Trials are not independent, and the published data says so
85
+
86
+ `pass^k = p^k` assumes each trial is a fresh coin flip. Real benchmarks do not behave that
87
+ way, and the τ-bench leaderboard is the cleanest demonstration — claude-3-5-sonnet on the
88
+ airline domain, published Pass^k beside what independence would predict from Pass^1:
89
+
90
+ | k | published Pass^k | `0.460^k` if independent |
91
+ |---|---|---|
92
+ | 1 | 0.460 | 0.460 |
93
+ | 2 | **0.326** | 0.212 |
94
+ | 3 | **0.263** | 0.097 |
95
+ | 4 | **0.225** | 0.045 |
96
+
97
+ The observed curve falls far slower than independence predicts. The reason is not
98
+ mysterious: **some tasks are reliably easy and some reliably hard**, so successes cluster
99
+ by task rather than scattering by trial. Positive correlation across trials of the same
100
+ task.
101
+
102
+ Two consequences, and they cut in opposite directions:
103
+
104
+ - **You cannot compute `pass^k` from `pass^1`.** Exponentiating a headline rate gives a
105
+ number far below the truth. Measure `pass^k` directly, at the k you care about.
106
+ - **Anthropic's `0.75³ ≈ 42%` is a worst case, not a forecast.** It is the right shape for
107
+ an argument — *consistency is a much harder bar* — and the wrong number to put in a
108
+ release gate.
109
+
110
+ The other half of independence is the harness, not the task: Anthropic requires each trial
111
+ start from a clean environment, because *"unnecessary shared state between runs (leftover
112
+ files, cached data, resource exhaustion) can cause correlated failures."* Correlated
113
+ failures break the arithmetic above — so isolation is not hygiene, it is what makes the
114
+ metric mean anything.
115
+
116
+ ## Pairing: same tasks, same seeds, per-task deltas
117
+
118
+ **Never subtract two independent averages.** Run both configurations over the *same* task
119
+ list with the *same* fixed seeds, record a per-task win/loss/tie, and test the deltas.
120
+
121
+ ```
122
+ for task in tasks: # identical list
123
+ for seed in seeds: # identical seeds, 3-5 of them
124
+ a = run(config_A, task, seed)
125
+ b = run(config_B, task, seed)
126
+ delta[task, seed] = a - b
127
+ ```
128
+
129
+ - **3–5 seeds per configuration**, reporting mean and spread. A single run screens a
130
+ direction; it does not establish one.
131
+ - Test the paired deltas with **McNemar** (binary outcomes) or a **paired bootstrap**.
132
+ Pairing removes the task-difficulty variance that dominates the unpaired comparison —
133
+ which is exactly the correlation the previous section measured.
134
+ - Testing several hypotheses at once needs a **multiple-comparisons correction**, or an
135
+ independent re-run of whichever ones came out positive. Five hypotheses at p<0.05 gives
136
+ you roughly a one-in-four chance of a false positive somewhere.
137
+
138
+ **Ship on three conditions, not one:** the difference exceeds the noise band, it survives
139
+ the paired analysis, and it reproduces on a rerun.
140
+
141
+ ## The harness is a variable, so pin it
142
+
143
+ The container spec is part of the measurement. On Terminal-Bench 2.0 the gap between the
144
+ most- and least-resourced setups was **6 percentage points (p < 0.01)** — larger than most
145
+ model differences anyone argues about.
146
+
147
+ The shape of the effect matters more than the number:
148
+
149
+ - Between **1× and 3×** the task's specified resources, scores move **within noise
150
+ (p = 0.40)**.
151
+ - From 3× to uncapped, infrastructure errors drop a further **1.6 pp** and success jumps
152
+ nearly **4 pp** — because the extra headroom lets the agent attempt strategies that only
153
+ work with it: pulling large dependencies, spawning expensive subprocesses, running
154
+ memory-hungry suites.
155
+ - Infrastructure errors alone: **5.8% of tasks at 1×**, cut to **2.1% at a 3× ceiling
156
+ (p < 0.001)**.
157
+
158
+ So a tight cap and a generous cap **measure different agent strategies**, not the same
159
+ agent more or less precisely. The remedy is a **floor and a ceiling**, calibrated so that
160
+ scores at both fall within noise of each other — not a single pinned value.
161
+
162
+ The effect is task-distribution dependent and does not transfer: SWE-bench moves only
163
+ **1.54 pp from 1× to 5×**. Measure it for your own suite rather than importing a multiplier.
164
+
165
+ ## What a number authorises
166
+
167
+ Evidence licenses the next action its scope supports, and nothing further. A worked
168
+ three-round loop on a deliberately narrow slice — four tasks, one run each, model, seed,
169
+ step limit and environment fixed, arm order alternated, **one variable changed per round**:
170
+
171
+ | Round | The only change | Success | Tokens vs control | What it authorised |
172
+ |---|---|---|---|---|
173
+ | H1 | added navigation and final-check instructions | 25% → 25% | 0.47× | the prompt is not the bottleneck — stop tuning it |
174
+ | H5 | accessibility feed → UIAutomator tree | 25% → **100%** | **2.498×** | right mechanism, too expensive — try to cheapen it |
175
+ | H5C | prune invisible/textless/non-actionable nodes | 100% → 100% | **0.506×** | qualifies for a full rerun |
176
+
177
+ Two rules fall out, and the second is the one people skip:
178
+
179
+ - **Observation before prompt.** More detailed instructions cannot restore information the
180
+ agent never received. When a score will not move, ask what the agent could see before
181
+ asking how it was asked.
182
+ - **4/4 on a slice is not 100% system-wide, and must not be reported as one.** With four
183
+ tasks per arm these numbers can decide whether a larger rerun is worth paying for. They
184
+ cannot estimate success across the benchmark. The ladder's output is *the next
185
+ experiment*, not a result.
186
+
187
+ ## The report contract
188
+
189
+ A comparison that will change a decision states all of these, or it is a claim rather than
190
+ a measurement:
191
+
192
+ - [ ] `n` — how many tasks, and `k` — how many runs each
193
+ - [ ] which reducer: `pass@k`, `pass^k`, `mean`, and **which k means what**
194
+ - [ ] the noise band for that `n`, computed
195
+ - [ ] paired or unpaired; if paired, the seed set
196
+ - [ ] the harness configuration: resource floor and ceiling, isolation between trials
197
+ - [ ] whether the difference reproduced on a rerun
198
+ - [ ] what the scope of the evidence authorises next — not what it suggests
199
+
200
+ **A green suite with none of these is a number, not a verdict.** That distinction is the
201
+ reason this file exists.