Skip to content

feat(eval): Move 1 — trajectory capture + TraceProbe INSIGHT scoring on real SWE-bench tasks#289

Open
theagenticguy wants to merge 3 commits into
mainfrom
feat/move1-trajectory-insight
Open

feat(eval): Move 1 — trajectory capture + TraceProbe INSIGHT scoring on real SWE-bench tasks#289
theagenticguy wants to merge 3 commits into
mainfrom
feat/move1-trajectory-insight

Conversation

@theagenticguy

Copy link
Copy Markdown
Owner

What & why

Roadmap Move 1publish the search-loop reduction number. The code-graph is commodity now; OCH's moat only pays if the agent consumes the pack with fewer wasted steps. This extends the variance-probe (@opencodehub/eval, spec 010) to score each run's tool-call trajectory against TraceProbe's (arXiv:2607.06184) structural anti-pattern detectors, and adds a SWE-bench Verified/Pro task generator so it runs on real, graded tasks.

Finding 0001 gave the resource number (pack cuts tokens 2–4×). This gives the behavioral one underneath it: does the pack cut search loops — TraceProbe's most stable failure-associated clue?

The build (all additive; --insight is opt-in, report shape unchanged without it)

  • Capture — Claude Code switched to --output-format stream-json --verbose; Codex --json already streams. New RunOutcome.trajectory.
  • Normalize (trajectory.ts) — both harnesses → TraceProbe's nine-type action taxonomy; unwraps Codex's /bin/zsh -lc '…' wrapper so shell greps classify.
  • Detect (insight.ts) — four structural, no-oracle detectors: Search Loop, Re-read Churn, Redundant Search, Shell-over-Tool. Predicates quoted from the paper's frozen-detector table.
  • Report — per-arm ArmInsight + per-harness insightDelta (per-run without − with; positive = pack suppressed the anti-pattern). --insight CLI flag.
  • Real tasks (swebench.ts + scripts/) — instance → assertion task that applies test_patch and runs FAIL_TO_PASS/PASS_TO_PASS, so correctness is graded (upgrades Finding 0001's eyeball caveat). TraceProbe ran on SWE-bench Verified, so per-detector numbers are comparable to the paper.

Deliberate scope

  • Skip the four semantic detectors (Phase Oscillation, …) — they need an LLM labeler, the same judge-oracle dependency that kept Finding 0001's headline on the directly-measured number. v2.
  • The number can come back null — if the pack cuts tokens but not loops, we publish that.

Fidelity limits (documented in docs/findings/0002)

v1 grades on a /tmp clone with deps installed; the probe runner does not reset the checkout between the N runs, so the token + trajectory deltas are the trustworthy headline and the assertion pass-rate is indicative. Per-run isolation / SWE-bench's official Docker images are a v2 upgrade.

Tests

pnpm check green: 118 eval tests (new: insight, trajectory, swebench; updated cli-runner for stream-json), 335 cli tests, lint, typecheck, banned-strings.

Grounding

CLI event schemas (Claude Code 2.1.x stream-json, Codex 0.143.x exec --json) were live-captured, not recalled. TraceProbe's taxonomy + frozen predicates were pulled from the paper.

Gated on maintainer

  • Live Bedrock measurement run (fills docs/findings/0002 results table, flips status Preliminary).
  • External publication of Finding 0002.

🤖 Generated with Claude Code

…on real SWE-bench tasks

Extends the variance-probe (@opencodehub/eval, spec 010) to score each run's
tool-call trajectory against TraceProbe's (arXiv:2607.06184) structural
anti-pattern detectors, and adds a SWE-bench Verified/Pro task generator so the
probe runs on real, graded tasks.

Roadmap Move 1 ("publish the search-loop reduction number"): the code-graph is
commodity; the moat only pays if the agent consumes it with fewer wasted steps.
This turns that into a measurable per-detector without−with delta.

- Capture: Claude Code switched to --output-format stream-json --verbose; Codex
  --json already streams. New RunOutcome.trajectory.
- Normalize (trajectory.ts): both harnesses → TraceProbe's nine-type action
  taxonomy; shell-wrapper unwrap for Codex's /bin/zsh -lc form.
- Detect (insight.ts): four STRUCTURAL no-oracle detectors — Search Loop,
  Re-read Churn, Redundant Search, Shell-over-Tool. The four semantic detectors
  (need an LLM labeler = judge-oracle caveat) are deferred to v2.
- Report: per-arm ArmInsight + per-harness insightDelta; --insight CLI flag.
- Real tasks (swebench.ts + scripts): instance → assertion task applying
  test_patch and running FAIL_TO_PASS/PASS_TO_PASS, so correctness is graded
  (upgrades Finding 0001's eyeball caveat). TraceProbe ran on SWE-bench Verified,
  so per-detector numbers are comparable to the paper.

Fidelity (documented in Finding 0002): v1 grades on a /tmp clone without per-run
checkout isolation — token + trajectory deltas are the trustworthy headline,
assertion pass-rate is indicative; official Docker images are a v2 upgrade.

Off by default: report shape is unchanged unless --insight is passed.
118 eval tests + 335 cli tests green; pnpm check passes.

Live Bedrock measurement run and external publication of Finding 0002 are gated
on Laith.
Comment thread packages/eval/src/swebench.ts Fixed
Comment thread packages/eval/src/trajectory.ts Fixed
…al-redos)

CodeQL flagged 2 high-severity polynomial-backtracking regexes in the Move 1
code:
- trajectory.ts SHELL_WRAPPER (`^\s*…sh\s+-…c\s+(.*)$`) — ambiguous \s* / \s+
  backtracks on 'sh\t-c\t…' with many tab repetitions. Replaced with a
  linear tokenized unwrap (indexOfWhitespace / trimStart / isDashCFlag char
  scans); no regex over the command string.
- swebench.ts trailing-slash strip (`/\/+$/`) — backtracks on an all-slash
  path. Replaced with a linear stripTrailingSlashes scan.

Behavior preserved (existing shellFirstWord / instanceToTask tests green) plus
regression tests for the tab-separated wrapper, a non-shell `-c` arg (gcc -c),
and an all-slash cloneRoot. 121 eval tests pass; pnpm check green.
…MAX_ARG_STRLEN)

The live N=2 pilot on SWE-bench flask exposed a real bug: every WITH-pack run
errored (0 tokens, no trajectory), while without-pack ran fine. Root cause: the
runner passed the composed prompt as a single argv argument, and an OCH pack
context is ~1 MB. A single argv arg is capped at MAX_ARG_STRLEN (128 KB on
Linux) — independent of the larger total ARG_MAX — so the with-pack spawn failed
with "argument list too long" (E2BIG), silently, on every real repo.

Fix: feed the prompt on stdin. `claude -p` with no positional reads the prompt
from stdin; `codex exec -` reads it from stdin. argv stays tiny regardless of
pack size. The runner already had an unused `stdin` seam — now it carries the
prompt.

After the fix, both arms populate on flask (with-pack 7.8M cache tokens vs
without 0.6M — the pack IS in context). Tests updated: buildArgv drops the
prompt positional; the injection test asserts the pack is on stdin and NOT on
argv. 121 eval tests + pnpm check green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants