pasr-bench 0.2.0__tar.gz

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.
@@ -0,0 +1,226 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
219
+
220
+ # PASR local artifacts — receipts are ignored, Context Packs are committable
221
+ .pasr/*
222
+ !.pasr/packs/
223
+ eval/runs/
224
+ eval/pasr_eval/runs/
225
+ pasr-bench-runs/
226
+ .eval-checkouts/
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.5
2
+ Name: pasr-bench
3
+ Version: 0.2.0
4
+ Summary: PASR-Bench: a pre-registered protocol for measuring a code-context retriever against grep, a repo-map, semantic search, and a whole-repo dump.
5
+ Project-URL: Homepage, https://github.com/Apheironn/pasr
6
+ Project-URL: Source, https://github.com/Apheironn/pasr/tree/main/eval
7
+ Author: Apheironn
8
+ License-Expression: Apache-2.0
9
+ Keywords: benchmark,coding-agent,context,evaluation,non-inferiority,retrieval
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: pasr-mcp>=0.2
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest<9,>=8; extra == 'dev'
19
+ Requires-Dist: ruff>=0.6; extra == 'dev'
20
+ Provides-Extra: llm
21
+ Requires-Dist: anthropic>=0.40; extra == 'llm'
22
+ Provides-Extra: plots
23
+ Requires-Dist: matplotlib>=3.7; extra == 'plots'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # PASR-Bench
27
+
28
+ A small, **pre-registered** protocol for measuring a code-context retriever, and the
29
+ harness that runs it. Packaged separately from `pasr-mcp` (`pip install
30
+ ./eval`, distribution name `pasr-bench`). Results for PASR itself:
31
+ [`RESULTS.md`](RESULTS.md) and [`../docs/competitors-benchmark.md`](../docs/competitors-benchmark.md).
32
+
33
+ ## What it measures
34
+
35
+ **Real-agent run** — four arms per task, each producing the same `ArmResult`:
36
+
37
+ 1. **native_search** — deterministic grep + read-files (stand-in for an agent's own tools)
38
+ 2. **broad** — the whole repo, source-first, truncated to a cap
39
+ 3. **pasr** — `select_context` at a fixed budget
40
+ 4. **pasr_fallback** — `pasr`, then one budget widening if confidence is low
41
+
42
+ A model answers each task from **only** that arm's context (`UNKNOWN` if absent); a
43
+ second model judges the answer against the task's expected identifiers;
44
+ `critical_source_hit` is still required. Metrics: task success, model input tokens
45
+ (incl. a flat per-tool-call overhead), round trips, critical-source miss rate, fallback
46
+ rate. Then a **paired non-inferiority** test of `pasr` / `pasr_fallback` vs the
47
+ `baseline_arm` at the plan's `margin_task_success` (point estimate + 10k-resample
48
+ bootstrap CI).
49
+
50
+ **Bake-off** — an offline, no-API retrieval comparison at a shared budget: `grep`,
51
+ `repomap` (aider-style signatures), `embed_lex` (a no-setup semantic floor), `pasr`,
52
+ `pasr_hash`, `pasr_map`. Scored on critical-file hit ∧ keyword coverage, split by task
53
+ kind.
54
+
55
+ **No GPU.** Arms run on CPU; the only model use is one answer + one judge call per
56
+ (task, arm) — ~400 Anthropic calls for the 50-task plan. Full Sonnet ≈ $12–15; a cheap
57
+ `--model` with a strong `--judge-model` ≈ $4–6.
58
+
59
+ ## Install & run
60
+
61
+ ```bash
62
+ pip install ./eval # the pasr-bench distribution (deps: pasr-mcp)
63
+ pip install "./eval[llm,plots]" # + anthropic (real agent) + matplotlib (report.png)
64
+
65
+ pasr-bench plans # the packaged plan(s)
66
+ pasr-bench run --agent keyword # offline smoke: full matrix + report + validation
67
+ pasr-bench bakeoff --budget 6000 # offline retrieval bake-off
68
+
69
+ export ANTHROPIC_API_KEY=sk-ant-...
70
+ pasr-bench run --agent claude --max-tasks 4 # cheap trial (~$0.4)
71
+ pasr-bench run --agent claude \
72
+ --model claude-haiku-4-5 --judge-model claude-sonnet-5 \
73
+ --checkout-dir .eval-checkouts # budget-safe full run (~$4–6)
74
+ ```
75
+
76
+ Each finished `(task, arm)` row is appended to `matrix.jsonl` and flushed, so a crash
77
+ keeps every completed row; `--resume <run_dir>` reloads the partial matrix and finishes
78
+ into the same delivery. `--checkout-dir DIR` reuses clones. `PASR_EVAL_BROAD_CAP=30000`
79
+ shrinks the `broad` arm. A run writes `<out>/<plan>_<utc>/` with `matrix.jsonl`,
80
+ `report.{json,md,png}`, `validation.json`, `resolved_commits.json`.
81
+
82
+ `python eval/run_eval.py …` and `python eval/bakeoff.py …` still work as thin shims for
83
+ the two sub-commands.
84
+
85
+ ## Bring your own retriever
86
+
87
+ The protocol is retriever-agnostic. To measure a different context tool against the
88
+ same 50 tasks and the same baselines:
89
+
90
+ 1. Add an arm in [`pasr_eval/arms.py`](pasr_eval/arms.py): extend `ARMS` and add a
91
+ branch in `_build(...)` that returns `(context, sources, tool_calls, round_trips,
92
+ fallback)` for your retriever. Everything downstream — grading, metrics,
93
+ non-inferiority, the validator — is arm-agnostic.
94
+ 2. For a bake-off arm, add a function in [`pasr_eval/bakeoff.py`](pasr_eval/bakeoff.py)
95
+ and list it in that file's `ARMS`.
96
+ 3. Keep the plan (`pasr_eval/plans/pilot.json`) fixed so numbers stay comparable, or
97
+ register a new plan and cite it.
98
+
99
+ The validator (`validate_matrix`) rejects synthetic rows, query→answer leaks, and an
100
+ unmatched task×arm matrix, so a submitted result is checkable.
101
+
102
+ ## Files
103
+
104
+ | Path | What |
105
+ |---|---|
106
+ | `pasr_eval/spec.py` | `RepoSpec` / `TaskSpec` / `EvalPlan`, `load_plan`; the leak + kind guards |
107
+ | `pasr_eval/arms.py` | the four real-agent arms → `ArmResult` |
108
+ | `pasr_eval/bakeoff.py` | the six offline bake-off arms |
109
+ | `pasr_eval/agents.py` | `AgentRunner` protocol, `KeywordAgent` (offline proxy) |
110
+ | `pasr_eval/llm_agent.py` | `LlmAgent` — answer + judge via the Anthropic API (`[llm]` extra) |
111
+ | `pasr_eval/metrics.py` | grade, aggregate, paired bootstrap CI, non-inferiority, `full_report` |
112
+ | `pasr_eval/runner.py` | `resolve_repos`, `run_plan` (`skip=` / `on_row=`), `write_matrix` |
113
+ | `pasr_eval/validate.py` | `validate_matrix` — no synthetic rows, no leaks, matched matrix |
114
+ | `pasr_eval/run.py` | end-to-end orchestrator: clone → streamed matrix → report → validate |
115
+ | `pasr_eval/plans/pilot.json` | the registered plan — 10 pinned repos, 50 tasks |
116
+ | `RESULTS.md` | pre-registration + n=15 pilot + keyword-50 + the n=50 real-agent headline |
@@ -0,0 +1,91 @@
1
+ # PASR-Bench
2
+
3
+ A small, **pre-registered** protocol for measuring a code-context retriever, and the
4
+ harness that runs it. Packaged separately from `pasr-mcp` (`pip install
5
+ ./eval`, distribution name `pasr-bench`). Results for PASR itself:
6
+ [`RESULTS.md`](RESULTS.md) and [`../docs/competitors-benchmark.md`](../docs/competitors-benchmark.md).
7
+
8
+ ## What it measures
9
+
10
+ **Real-agent run** — four arms per task, each producing the same `ArmResult`:
11
+
12
+ 1. **native_search** — deterministic grep + read-files (stand-in for an agent's own tools)
13
+ 2. **broad** — the whole repo, source-first, truncated to a cap
14
+ 3. **pasr** — `select_context` at a fixed budget
15
+ 4. **pasr_fallback** — `pasr`, then one budget widening if confidence is low
16
+
17
+ A model answers each task from **only** that arm's context (`UNKNOWN` if absent); a
18
+ second model judges the answer against the task's expected identifiers;
19
+ `critical_source_hit` is still required. Metrics: task success, model input tokens
20
+ (incl. a flat per-tool-call overhead), round trips, critical-source miss rate, fallback
21
+ rate. Then a **paired non-inferiority** test of `pasr` / `pasr_fallback` vs the
22
+ `baseline_arm` at the plan's `margin_task_success` (point estimate + 10k-resample
23
+ bootstrap CI).
24
+
25
+ **Bake-off** — an offline, no-API retrieval comparison at a shared budget: `grep`,
26
+ `repomap` (aider-style signatures), `embed_lex` (a no-setup semantic floor), `pasr`,
27
+ `pasr_hash`, `pasr_map`. Scored on critical-file hit ∧ keyword coverage, split by task
28
+ kind.
29
+
30
+ **No GPU.** Arms run on CPU; the only model use is one answer + one judge call per
31
+ (task, arm) — ~400 Anthropic calls for the 50-task plan. Full Sonnet ≈ $12–15; a cheap
32
+ `--model` with a strong `--judge-model` ≈ $4–6.
33
+
34
+ ## Install & run
35
+
36
+ ```bash
37
+ pip install ./eval # the pasr-bench distribution (deps: pasr-mcp)
38
+ pip install "./eval[llm,plots]" # + anthropic (real agent) + matplotlib (report.png)
39
+
40
+ pasr-bench plans # the packaged plan(s)
41
+ pasr-bench run --agent keyword # offline smoke: full matrix + report + validation
42
+ pasr-bench bakeoff --budget 6000 # offline retrieval bake-off
43
+
44
+ export ANTHROPIC_API_KEY=sk-ant-...
45
+ pasr-bench run --agent claude --max-tasks 4 # cheap trial (~$0.4)
46
+ pasr-bench run --agent claude \
47
+ --model claude-haiku-4-5 --judge-model claude-sonnet-5 \
48
+ --checkout-dir .eval-checkouts # budget-safe full run (~$4–6)
49
+ ```
50
+
51
+ Each finished `(task, arm)` row is appended to `matrix.jsonl` and flushed, so a crash
52
+ keeps every completed row; `--resume <run_dir>` reloads the partial matrix and finishes
53
+ into the same delivery. `--checkout-dir DIR` reuses clones. `PASR_EVAL_BROAD_CAP=30000`
54
+ shrinks the `broad` arm. A run writes `<out>/<plan>_<utc>/` with `matrix.jsonl`,
55
+ `report.{json,md,png}`, `validation.json`, `resolved_commits.json`.
56
+
57
+ `python eval/run_eval.py …` and `python eval/bakeoff.py …` still work as thin shims for
58
+ the two sub-commands.
59
+
60
+ ## Bring your own retriever
61
+
62
+ The protocol is retriever-agnostic. To measure a different context tool against the
63
+ same 50 tasks and the same baselines:
64
+
65
+ 1. Add an arm in [`pasr_eval/arms.py`](pasr_eval/arms.py): extend `ARMS` and add a
66
+ branch in `_build(...)` that returns `(context, sources, tool_calls, round_trips,
67
+ fallback)` for your retriever. Everything downstream — grading, metrics,
68
+ non-inferiority, the validator — is arm-agnostic.
69
+ 2. For a bake-off arm, add a function in [`pasr_eval/bakeoff.py`](pasr_eval/bakeoff.py)
70
+ and list it in that file's `ARMS`.
71
+ 3. Keep the plan (`pasr_eval/plans/pilot.json`) fixed so numbers stay comparable, or
72
+ register a new plan and cite it.
73
+
74
+ The validator (`validate_matrix`) rejects synthetic rows, query→answer leaks, and an
75
+ unmatched task×arm matrix, so a submitted result is checkable.
76
+
77
+ ## Files
78
+
79
+ | Path | What |
80
+ |---|---|
81
+ | `pasr_eval/spec.py` | `RepoSpec` / `TaskSpec` / `EvalPlan`, `load_plan`; the leak + kind guards |
82
+ | `pasr_eval/arms.py` | the four real-agent arms → `ArmResult` |
83
+ | `pasr_eval/bakeoff.py` | the six offline bake-off arms |
84
+ | `pasr_eval/agents.py` | `AgentRunner` protocol, `KeywordAgent` (offline proxy) |
85
+ | `pasr_eval/llm_agent.py` | `LlmAgent` — answer + judge via the Anthropic API (`[llm]` extra) |
86
+ | `pasr_eval/metrics.py` | grade, aggregate, paired bootstrap CI, non-inferiority, `full_report` |
87
+ | `pasr_eval/runner.py` | `resolve_repos`, `run_plan` (`skip=` / `on_row=`), `write_matrix` |
88
+ | `pasr_eval/validate.py` | `validate_matrix` — no synthetic rows, no leaks, matched matrix |
89
+ | `pasr_eval/run.py` | end-to-end orchestrator: clone → streamed matrix → report → validate |
90
+ | `pasr_eval/plans/pilot.json` | the registered plan — 10 pinned repos, 50 tasks |
91
+ | `RESULTS.md` | pre-registration + n=15 pilot + keyword-50 + the n=50 real-agent headline |
@@ -0,0 +1,151 @@
1
+ # PASR real-agent evaluation — results
2
+
3
+ > **50-task real-agent run done** (`eval/deliveries/realagent50_20260902T221528Z/`,
4
+ > 10 pinned repos, Claude answer + judge). PASR gives the model **5.8k targeted
5
+ > tokens in one tool call** and it answers **48%** of the tasks; the **59k-token
6
+ > source-first repo dump answers 38%** — PASR is **+0.10** on paired task success
7
+ > (`pasr_fallback` +0.12), and the **point estimate clears the -0.05
8
+ > non-inferiority margin** (95% CI still crosses it: `pasr` [-0.08, +0.30]).
9
+ > PASR puts the critical file in context **46/50** times vs the dump's **33/50**.
10
+ >
11
+ > Reading: on localized code questions PASR matches — slightly beats — a 10×-larger
12
+ > whole-repo dump, at **~90% fewer input tokens and one round trip**. `native_search`
13
+ > (grep + read 6 files, 23k tokens, 6 round trips) is the raw-success leader at 0.52
14
+ > but misses the critical file 30% of the time. This is a **bounded efficiency
15
+ > result**, not a superiority claim — one more batch of 50 would settle the interval.
16
+
17
+ ## Pre-registration
18
+
19
+ - **Plan:** `pasr_eval/plans/pilot.json` — 10 pinned public Python repos, 50 source-grounded
20
+ `locate` / `trace` / `explain` tasks (5/repo). `pasr-bench run` records each repo's
21
+ resolved commit SHA and the answer/judge model IDs into `matrix.jsonl`'s `_meta` line.
22
+ - **Arms:** `native_search`, `broad`, `pasr`, `pasr_fallback`.
23
+ - **Baseline:** `broad`.
24
+ - **Primary claim (non-inferiority):** on paired tasks, `pasr_fallback` task success
25
+ is non-inferior to `broad` within a **-0.05** margin (both the point estimate and
26
+ the lower bound of a 10k-resample paired bootstrap 95% CI ≥ margin).
27
+ - **Secondary:** `pasr` / `pasr_fallback` reduce mean `tokens_in` and `round_trips`
28
+ vs `broad`; `critical_source_miss_rate` reported per arm; `fallback_rate` reported.
29
+ - **No synthetic rows, no reference leak:** enforced by `validate_matrix` (every row
30
+ traces to a registered task + repo; the query never contains the full answer;
31
+ exactly one row per task×arm).
32
+
33
+ ## The two graders
34
+
35
+ - **Keyword proxy** (`--agent keyword`, offline, no API): task success = every expected
36
+ identifier appears verbatim in the arm's context **and** the critical file is present.
37
+ A harsh literal proxy for retrieval quality — machinery + token-story check only.
38
+ - **Real agent** (`--agent claude`): a model answers from **only** the arm's context
39
+ (`UNKNOWN` if not present), then a second model judges the answer against the task's
40
+ expected identifiers; `critical_source_hit` is still required for a pass.
41
+
42
+ The n=15 pilot and the 50-task keyword re-run below are the supporting runs; the
43
+ [50-task real-agent run](#50-task-real-agent-run--claude-answer--judge-10-repos---headline)
44
+ is the headline.
45
+
46
+ ## Pilot run — `claude-sonnet-5`, 15 tasks, 5 repos
47
+
48
+ | arm | task success | context tokens | round trips | crit. miss | fallback rate |
49
+ |---|---:|---:|---:|---:|---:|
50
+ | broad (58k source-first repo dump) | 0.467 | 58 146 | 1 | 0.067 | – |
51
+ | native_search (grep + 6 files, 4k each) | 0.400 | 22 771 | 6 | 0.267 | – |
52
+ | **pasr** | **0.533** | **5 764** | **1** | 0.133 | – |
53
+ | pasr_fallback | 0.600 | 5 764 | 1 | 0.133 | 0.000 |
54
+
55
+ - **Token savings vs `broad`: pasr / pasr_fallback +90.0%**; native_search +60.4%.
56
+ - **Non-inferiority (`pasr_fallback` vs `broad`, task success, margin -0.05):**
57
+ delta **+0.133**, 95% CI **[-0.20, +0.47]** — **point estimate PASSES**, the CI
58
+ lower bound (-0.20) does **not** clear the margin. `pasr` vs `broad`: delta +0.067,
59
+ CI [-0.33, +0.47], same picture.
60
+ - `pasr` critical-source miss: **2/15** (`req-02`, `star-03`; `star-03` was also
61
+ missed by `broad` → likely a task-spec problem, not retrieval).
62
+ - The `pasr_fallback` threshold (`confidence < 0.5`) **never triggered** — all 15
63
+ PASR selections were confident. `pasr_fallback` ran on the same contexts as `pasr`;
64
+ its +0.067 over `pasr` is answer/judge sampling noise, not a fallback effect.
65
+
66
+ ### Reading it
67
+
68
+ - `broad` puts the critical file in context 14/15 times (58k tokens) yet the model
69
+ answers only 7/15 — a large-haystack utilisation effect. `pasr` gives the model
70
+ 5.8k targeted tokens and it answers 8/15. **Same answer quality band, one order of
71
+ magnitude fewer tokens, one tool call instead of the model chewing 58k.**
72
+ - `native_search` is worst: brittle grep (27% critical miss) plus six partial files.
73
+ - **What this does not show:** inferential non-inferiority (n=15 CI is ±0.3). The
74
+ bounded positive claim is a cross-repo efficiency direction, mirroring `researchv2`'s
75
+ LongBench Pro finding.
76
+
77
+ Delivery: `eval/deliveries/pilot_20260902T212108Z/`.
78
+
79
+ ## 50-task keyword-proxy re-run (no API — `pasr-bench run --agent keyword`)
80
+
81
+ `eval/deliveries/keyword50_20260902T213744Z/`. 10 repos, 50 source-grounded tasks;
82
+ `_FALLBACK_CONFIDENCE` raised to 0.65.
83
+
84
+ | arm | task success | context tokens | round trips | crit. miss | fallback rate |
85
+ |---|---:|---:|---:|---:|---:|
86
+ | broad (58k source-first) | 0.660 | 59 075 | 1 | **0.340** | – |
87
+ | native_search (6 files, 4k) | 0.640 | 22 901 | 6 | 0.300 | – |
88
+ | **pasr** | **0.700** | **5 777** | 1 | **0.080** | – |
89
+ | pasr_fallback | 0.700 | 5 854 | 1.02 | 0.060 | 0.020 |
90
+
91
+ - **PASR beats both baselines on the literal grader** (0.70 vs 0.66 / 0.64) at **+90%
92
+ tokens**, and its critical-source miss (0.08 = 4/50) is **4× lower than broad's
93
+ 0.34** — a 60k source-first dump still fails to include the right file for 17/50
94
+ real-repo tasks; PASR's targeted retrieval gets it in 92% of the time.
95
+ - Non-inferiority (`pasr` vs `broad`, keyword grader): delta **+0.04**, 95% CI
96
+ **[-0.14, +0.22]** — point PASSES, CI is now ±0.18 (was ±0.30 at n=15) and just
97
+ misses clearing the -0.05 margin.
98
+ - Fallback engaged on 1/50 (crit-miss 0.08 → 0.06).
99
+
100
+ ## 50-task real-agent run — Claude answer + judge, 10 repos ★ headline
101
+
102
+ `eval/deliveries/realagent50_20260902T221528Z/`. Models recorded in `matrix.jsonl`'s
103
+ `_meta`. `validate_matrix` clean (no synthetic rows, no leak, matched 4×50 matrix).
104
+
105
+ | arm | task success | context tokens | tokens_in | round trips | crit. miss | fallback |
106
+ |---|---:|---:|---:|---:|---:|---:|
107
+ | broad (59k source-first repo dump) | 0.380 | 59 075 | 59 115 | 1 | **0.340** | – |
108
+ | native_search (grep + 6 files, 4k each) | **0.520** | 22 901 | 23 181 | 6 | 0.300 | – |
109
+ | **pasr** | 0.480 | **5 777** | **5 817** | **1** | **0.080** | – |
110
+ | pasr_fallback | 0.500 | 5 854 | 5 895 | 1.02 | 0.060 | 0.020 |
111
+
112
+ - **Token savings vs `broad`: pasr +90.2%, pasr_fallback +90.0%**; native_search +60.8%.
113
+ - **Non-inferiority (task success, margin -0.05):** `pasr` delta **+0.100**, 95% CI
114
+ **[-0.08, +0.30]**; `pasr_fallback` delta **+0.120**, CI **[-0.06, +0.30]**. Both
115
+ **point estimates PASS**; both CI lower bounds miss the margin by ≈0.03 (half-width
116
+ ±0.18–0.19 at n=50, down from ±0.30 at n=15).
117
+ - **Critical-source hit: `pasr` 46/50 (92%), `pasr_fallback` 47/50** vs **`broad`
118
+ 33/50 (66%)**. A 59k source-first dump *still* omits the answer's file for 17/50
119
+ real-repo tasks — concentrated in the large repos (`typer` 5/5 missed, `jinja` 4/5,
120
+ `packaging` 4/5, `anyio` 3/5); those are exactly the repos where `broad` scores
121
+ 0–1/5. Where the file *does* fit (`requests`, `pluggy`, `httpx`) `broad` reaches
122
+ 4/5. **`broad`'s 0.38 is a truncation + large-haystack failure, not a grading one.**
123
+ - Head-to-head: `pasr` wins **15** tasks `broad` loses, loses **10** `broad` wins
124
+ (net +5 of 50 = the +0.10). `pasr_fallback` widened once (`pkg`-family), turning one
125
+ loss into a win.
126
+ - **PASR's weak spot — `typer` (0/5, both PASR arms).** The slice reaches the right
127
+ file 3/5 but the answering lines aren't in the selected window: `typer` leans on
128
+ re-exports and decorator plumbing that the current chunker + 6k budget don't
129
+ resolve. Drop `typer` and `pasr` is 24/45 = **0.53**. Logged for a chunker follow-up.
130
+ - `native_search` is the raw-success leader (0.52) but pays **4× the tokens, 6 round
131
+ trips**, and a **30% critical-source miss** — brittle when the query terms don't
132
+ literally appear near the answer.
133
+
134
+ ### Reading it
135
+
136
+ The claim PASR makes is a **profile trade**, and the run supports it: at parity-ish
137
+ answer quality with a whole-repo dump (−0 to +0.12 depending on arm/margin), PASR
138
+ costs **one order of magnitude fewer input tokens, one tool call instead of the model
139
+ chewing 59k, and a 92% critical-file hit rate with per-line provenance**. It is *not*
140
+ an inferential non-inferiority pass (CI lower bound −0.08) and *not* a raw-accuracy
141
+ win over an agent's own grep. It mirrors `researchv2`'s LongBench Pro finding: a
142
+ bounded cross-repo efficiency direction.
143
+
144
+ ### Next
145
+
146
+ 1. **Second batch of 50 tasks** to close the CI (projected half-width ≈±0.13 at
147
+ n=100 would clear the −0.05 margin if the point estimate holds).
148
+ 2. **Chunker follow-up for re-export/decorator-heavy repos** (`typer`): the pass that
149
+ turns a critical-file hit into an answerable window.
150
+ 3. Consider a relevance-ranked `broad` truncation so it isn't a strawman past ~40k —
151
+ though the point of this arm is precisely "what a naive big-context dump gets you".
@@ -0,0 +1,34 @@
1
+ """Real-agent evaluation harness for the PASR MCP (roadmap M11 / research Track 2).
2
+
3
+ Not shipped with ``pasr-mcp``. Compares four arms per task — the agent's native
4
+ search, full/broad context, PASR selected context, PASR + one controlled fallback —
5
+ and reports paired metrics with a pre-registered non-inferiority margin.
6
+
7
+ The dry-run path (``KeywordAgent`` + local-mode plan) exercises the whole matrix
8
+ offline; the real run swaps in ``LlmAgent`` (answer + judge over the Anthropic API).
9
+ """
10
+
11
+ from pasr_eval.arms import ARMS, ArmResult, run_arm
12
+ from pasr_eval.metrics import aggregate, bootstrap_ci, grade, non_inferiority, paired_delta
13
+ from pasr_eval.runner import run_plan, write_matrix
14
+ from pasr_eval.spec import EvalPlan, RepoSpec, TaskSpec, load_plan, plan_from_dict
15
+ from pasr_eval.validate import validate_matrix
16
+
17
+ __all__ = [
18
+ "ARMS",
19
+ "ArmResult",
20
+ "run_arm",
21
+ "run_plan",
22
+ "write_matrix",
23
+ "EvalPlan",
24
+ "RepoSpec",
25
+ "TaskSpec",
26
+ "load_plan",
27
+ "plan_from_dict",
28
+ "aggregate",
29
+ "paired_delta",
30
+ "bootstrap_ci",
31
+ "non_inferiority",
32
+ "grade",
33
+ "validate_matrix",
34
+ ]
@@ -0,0 +1,47 @@
1
+ """``pasr-bench`` console entry: a thin dispatcher over the two orchestrators.
2
+
3
+ pasr-bench run [args...] # the 4-arm real-agent / keyword evaluation
4
+ pasr-bench bakeoff [args...] # the offline retrieval bake-off
5
+ pasr-bench plans # list the packaged plans
6
+
7
+ Everything after the sub-command is forwarded verbatim, so
8
+ ``pasr-bench run --agent claude --resume ...`` works exactly like the old
9
+ ``python eval/run_eval.py``.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ _USAGE = "usage: pasr-bench {run|bakeoff|plans} [args...]"
18
+
19
+
20
+ def main(argv: list[str] | None = None) -> int:
21
+ args = list(sys.argv[1:] if argv is None else argv)
22
+ if not args or args[0] in ("-h", "--help"):
23
+ print(_USAGE)
24
+ print("\n run end-to-end evaluation -> a timestamped delivery")
25
+ print(" bakeoff offline retrieval bake-off (grep / repo-map / semantic / PASR)")
26
+ print(" plans list the plan files bundled with this package")
27
+ return 0 if args else 2
28
+
29
+ cmd, rest = args[0], args[1:]
30
+ if cmd == "run":
31
+ from pasr_eval.run import main as run_main
32
+
33
+ return run_main(rest)
34
+ if cmd == "bakeoff":
35
+ from pasr_eval.bakeoff import main as bakeoff_main
36
+
37
+ return bakeoff_main(rest)
38
+ if cmd == "plans":
39
+ for path in sorted((Path(__file__).parent / "plans").glob("*.json")):
40
+ print(path)
41
+ return 0
42
+ print(f"unknown sub-command {cmd!r}\n{_USAGE}", file=sys.stderr)
43
+ return 2
44
+
45
+
46
+ if __name__ == "__main__":
47
+ raise SystemExit(main())