Skip to content

fix(eng-l1-a): distinguish exact and approximate issue matches - #12

Draft
mrojas54 wants to merge 10 commits into
devrev:mainfrom
mrojas54:eng-l1-a-related-issue-spec
Draft

fix(eng-l1-a): distinguish exact and approximate issue matches#12
mrojas54 wants to merge 10 commits into
devrev:mainfrom
mrojas54:eng-l1-a-related-issue-spec

Conversation

@mrojas54

@mrojas54 mrojas54 commented Jul 31, 2026

Copy link
Copy Markdown

Scope

This PR now contains only docs/eng-l1-a-criterion-6.md, the diagnosis of the eng-l1-a Criterion 6 failure.

The original instruction, rubric, and reference-trajectory edits were removed per maintainer direction. The structural wording/rubric fix for L1 will land separately; the exact / approximate / none contract is retained here as the foundation for a follow-up L2 audit task, not applied to L1.

Diagnosis

Across 10 valid trials on two model generations, every run failed only Criterion 6. Agents converged on the same semantic counterparts, but six were marked done; three tickets had no same-defect issue in any status. The document separates those spec/data findings from timeout and judgeability accounting and records the reproduction evidence.

Verification

  • make validate: passed (14 tasks and 14 dataset entries validated)
  • Diagnosis and run evidence are documented in docs/eng-l1-a-criterion-6.md

This write-up is AI-assisted; the runs, diagnosis, and trace verification were performed against the raw Harbor artifacts.

mrojas54 and others added 9 commits July 27, 2026 03:37
…atches

Criterion 6 fails on every trial because "related engineering issue" is
undefined. It admits a structural reading (shares the component field) and a
semantic one (describes the same defect). The rubric scores the first; the
prompt's wording invites the second.

An agent taking the semantic reading finds the exactly-matching issue for nine
of the 31 tickets, discovers it is marked status=done, and has no choice but to
report "none" -- which Criterion 6 treats as an automatic fail. Six of those
nine (ISS-012, ISS-015, ISS-021, ISS-024, ISS-027, ISS-030) are purpose-built
same-component, same-defect counterparts sitting at status=done.

The fix is at the instruction level rather than in the data: an enterprise
controls its prompts, not its issue-tracker hygiene, so a benchmark solvable
only against tidy data measures something the customer cannot act on.

- instruction.md: define "open", require ranking by defect similarity, and add
  exact/approximate/none labels. Every affected component carries 69-100 open
  issues, so "none" becomes unreachable while semantic ranking is still tested.
- criteria.yaml: drop Criterion 6's premise that all 31 tickets have matching
  open issues, which is false under the semantic reading; bound the "lists all
  of them" weighted criterion to DISTINCT issues so it stops rewarding
  enumeration of ~69 duplicate backlog chores; add a weighted criterion for
  honest exact/approximate labelling.
- trajectory.json: update the reference response, which the judge receives
  verbatim, so it grades the new spec rather than the old one.

No data rows changed. make validate passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two documents from running the suite with claude-code / claude-opus-4-8.

field-report-opus-4-8.md -- operational findings:
- 30% of recorded trials were not attempts. They cost $0, submitted nothing, and
  scored reward 0.0, which is indistinguishable from a wrong answer. Four tasks
  looked like failures on that basis; re-run properly, all four passed. A run
  that dies on billing publishes a silently depressed score.
- Job-level cost_usd excludes errored-attempt spend, so a budget guard built on
  it does not fire. Per-trial agent_result.cost_usd is accurate.
- Per-task costs vary 7x ($0.30-$2.22/trial), so a blended average is not a
  usable budget input.
- Reproduction section: versions, host, Docker allocation, exact commands.

eng-l1-a-criterion-6.md -- the task analysis:
- 8 valid trials, 0 passed, every one failing only Criterion 6.
- Root cause traced through the trajectories to an undefined term intersecting
  a status filter, with the six done-status counterpart issues identified.
- Leaderboard evidence: computer clears 14/14 at pass@10 while both claude-code
  entries sit at exactly 12/14 across two model generations -- the signature of
  a spec defect rather than a capability limit.
- Proposed refinement to the SKILL.md heuristic, which currently cannot
  distinguish task difficulty from a spec or data defect.

WIP: 7 TODO markers remain, all pending run data (k=10 tables and the
patched-task verification line).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Corrects a claim made in de58efd and documents two further harness issues found
while trying to reproduce it.

field-report-opus-4-8.md:

- Section 2 rewritten. The previous claim -- that job-level cost_usd excludes
  errored-attempt spend -- does not reproduce and is withdrawn. In job
  2026-07-27__00-43-07 the job figure equals the sum of its per-trial costs to
  the cent, and both of its errored trials genuinely cost $0.00. A later Opus 5
  run settles the other direction: an errored trial that burned $0.9956 was
  included in its job's cost_usd. The ~$21 account draw was a cross-job total
  ($1.87 + $15.93 + $3.69 = $21.48) compared against a single job's figure.
  What survives is narrower but real: cost_usd is per-job and harbor emits no
  run-level total, so with no harbor resume every interrupted suite spans
  several job directories; and it reports null rather than 0 when no trial
  completes, which breaks a guard doing arithmetic on it.

- New section 3: a run that never starts still reports a score. With the base
  image absent, five trials died at container build in ten seconds. The job
  still counted the same five as both completed and errored, and emitted
  pass_at_k values of 0.0 from n_trials: 0. A total infrastructure failure is
  therefore indistinguishable, in the scored fields, from an agent that
  attempted every trial and got every one wrong. This also reproduces section 1
  deterministically -- no credit exhaustion and no API spend required.

- New section 4: make run-task omits -k, so ATTEMPTS is silently ignored and a
  single-task invocation always runs at harbor's default attempt count. Per-task
  top-up is the only recovery path for a halted run, so anyone repairing a
  partial suite gets a k that does not match the suite being repaired.

- Reproduction: the harbor commands were missing --ae, without which the agent
  starts inside Docker with no credentials (README.md:229, Makefile:28-30). Also
  notes the base image must still be present -- a pruned image falls through to
  a registry pull and kills every trial in seconds.

Summary block now lists six findings; intro corrected to four harness issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Section 3 cited a local job directory as its evidence, which a reviewer cannot
inspect. Replaced with the commands that produce the failure, so the defect can
be verified on any machine in about ten seconds with no API spend:

    docker rmi enterprise-bench/conversational-base:latest
    harbor run -p tasks/eng-l1-a ... -k 5
    make build-image   # restore

Every trial then dies at container build -- Docker falls through to a registry
pull for an image that only ever existed locally -- and the resulting
result.json still counts the same trials as both completed and errored while
emitting pass_at_k values of 0.0 from n_trials: 0.

The eval key in the sample output is genericised to <model>, since the recipe is
model-independent.

Also adds jobs-*/ to .gitignore. The existing entry covers the default jobs/
directory but not the output of a custom --jobs-dir, which left run artifacts
untracked but committable by accident.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…irement

Ran the section 3 reproduction recipe end to end against claude-opus-4-8. It
reproduces the counters exactly as recorded for the earlier claude-opus-5 job --
n_completed_trials and n_errored_trials both 5 against n_total_trials 5,
n_trials 0 with pass_at_k populated as 0.0, cost_usd null -- in 8 seconds with
no API spend. Section 3 now notes both verifications; it previously rested on a
single accidental observation.

Reproduction: both API keys are needed in two places, and supplying only one
fails in a way the previous text did not predict. Harbor's [verifier.env] phase
reads them from the shell environment, while --ae is what forwards them into the
agent container. Passing only --ae aborts the run before any trial starts with
"Missing Environment Variables: OPENAI_API_KEY [verifier.env]". The Makefile
covers both via the ifndef guards (Makefile:109-115) and AGENT_ENV_ARGS
(Makefile:28-30); a hand-written harbor invocation must do the same.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The field report told readers its figures were reproducible with
scripts/verify_estimate.py "included so you can audit these numbers". The
script did not exist. This adds it.

It derives every figure in the report from harbor's own result.json files:
trial counts, per-task cost, and pass rate, separating valid attempts from
trials that died at $0 (the distinction section 1 is about). Standard library
only, no third-party dependencies.

Output on the run behind the report reproduces its section 1 block verbatim --
37 recorded, 26 valid, 11 at $0, 14/26 = 54% -- and all 14 per-task costs.

jobs/ is 384 MB and gitignored, so the script's output is committed alongside
it at docs/verify-estimate-output.txt. That keeps the numbers checkable
against the method that produced them without shipping the raw data.

Co-Authored-By: Claude <noreply@anthropic.com>
…table

Three things, all in the two field docs. No task or data files change.

1. Remove the seven <!-- TODO --> markers. They were invisible in rendered
   markdown but fully visible in the diff, and several stood in for numbers a
   planned k=5 suite run was meant to supply. That run did not happen, so each
   is resolved by stating what is actually measured rather than by deferring:
   coverage is 37 recorded / 26 valid across four jobs, sample sizes are 1-8
   per task, and the k=10 figure is labelled as the extrapolation it is.

2. Correct section 2's job table. It omitted jobs/2026-07-27__00-02-22 -- the
   23-trial suite sweep whose results populate the appendix -- and therefore
   reported "Total drawn $21.48" when the four jobs behind the 37 trials drew
   $34.05. The corrected figures strengthen the section's own point: a guard
   watching one job's $15.93 was blind to $14.44 already spent, not the $5.55
   previously claimed. Also splits the withdrawn cost_usd claim into the two
   checks that settle it, and notes that run used -r 0, so retry spend remains
   an open question this report does not answer.

3. Add the Opus 5 replication to the criterion-6 analysis. The task was re-run
   unmodified on claude-opus-5: 2 valid trials, both 0.0, both failing only
   Criterion 6, criteria 1-5/7/8 passing on each. Combined with the eight Opus
   4.8 trials that is 10 valid trials across two model generations with one
   shared failure. The judge's own explanation is quoted, because it states the
   mechanism directly -- the agent "asserts those related issues are Done".

Also states plainly that the diagnosis is measured but the proposed fix is
not: no trial has yet been run against the patched spec, and the analysis
makes no claim about its pass rate.

Co-Authored-By: Claude <noreply@anthropic.com>
@sumisunz

sumisunz commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

@mrojas54 thank you for this. Very good observations. We are reviewing this and will have a full reply later today.

@sumisunz

sumisunz commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Michelle — this is an excellent piece of work, and thank you for the rigor behind it. In particular, thank you for tracing why it fails, separating the two distinct causes, and backing it with cross-model trials and a trace. That's exactly the kind of contribution that makes a benchmark better. TL;DR: your diagnosis is correct.

We reproduced and verified your findings:

  • The ambiguity does exist. "Related open engineering issue" admits a structural reading (any open issue sharing the ticket's component) and a semantic reading (an open issue describing the ticket's actual defect). Criterion 6 grades the structural reading; the prompt's wording invites the semantic one which some models are choosing.
  • We confirmed your two categories in the data: 6 tickets whose exact same-defect issue exists but is done (the closed-counterpart cases — ISS-012, ISS-015, ISS-021, ISS-024, ISS-027, ISS-030), and 3 tickets with no same-defect issue in any status (the refund-permissions, bulk-refund, and Google-Pay cases). For those 9, an agent that reasons carefully and honestly reports "none"/"approximate" is doing the right thing and is penalized for it.
  • We saw the inversion from both directions in our own runs: an agent doing the shallow structural read passes; agents that found the closed counterparts and reported them honestly failed only Criterion 6. Two model generations, one shared failure — the signature of a spec issue, not a capability limit. You called that correctly.

What we're going to do — and why it's a little different from the PR as written.

We want to keep eng-l1-a as a level-1 task.L1 tasks are deterministic retrieval: traverse the structured link (the shared component / product-part field) and report it. The exact-vs-approximate-vs-none distinction your PR introduces is genuinely valuable — but it's a level-2 competency (auditing whether the structural link matches the real defect), and folding it into L1 would blur the level taxonomy and change what a passing L1 answer means (it would also invert which agents currently clear the task). So rather than merge the semantic rubric into L1, we're going to:

  1. Fix the L1 ambiguity directly. Reword the task so the structural reading is unambiguous — the relationship is the shared component/product-part link, phrased as "mapped to the same component" rather than the semantically-loaded "related," and the grading criteria carry that intent explicitly. This removes the exact defect you identified: a careful agent will no longer be punished, because the intended method is stated.
  2. Promote your exact/approximate/none contract into a new L2 "audit" task — find and classify the discrepancies between the structural links and the actual defect/status picture (the 6 closed-counterpart + 3 untracked cases you surfaced). Your PR is the foundation for this task.
  3. Add an L3 "repair" task as well — act on the audit (reopen the closed counterparts, file issues for the untracked ones), which exercises safe, permission-scoped write-back. This will take more work as the MCP servers will need additional functions and more fine-grained authentication will need to be supported.

In other words: you didn't just find a bug — you surfaced the difference between two levels of the benchmark for this type of query, and that is now the basis for two new tasks. That's a bigger contribution than the original fix.

We'll credit you for the finding and for the L2/L3 foundation. I'll follow up directly about collaborating on authoring these. I think you're the right person to introduce the L2 task given you've already done the hard analytical part.

One small mechanical note for anyone reading later: the ID stability point in the PR is right — object display IDs are handled ID-independently in grading, and the discrepancy set is defined on stable attributes (subject → counterpart title → status), so it holds at 256x (our primary scale).

Thank you again — this is a real improvement to Enterprise-Bench.

@mrojas54

mrojas54 commented Aug 5, 2026

Copy link
Copy Markdown
Author

Michelle — this is an excellent piece of work, and thank you for the rigor behind it. In particular, thank you for tracing why it fails, separating the two distinct causes, and backing it with cross-model trials and a trace. That's exactly the kind of contribution that makes a benchmark better. TL;DR: your diagnosis is correct.

We reproduced and verified your findings:

  • The ambiguity does exist. "Related open engineering issue" admits a structural reading (any open issue sharing the ticket's component) and a semantic reading (an open issue describing the ticket's actual defect). Criterion 6 grades the structural reading; the prompt's wording invites the semantic one which some models are choosing.
  • We confirmed your two categories in the data: 6 tickets whose exact same-defect issue exists but is done (the closed-counterpart cases — ISS-012, ISS-015, ISS-021, ISS-024, ISS-027, ISS-030), and 3 tickets with no same-defect issue in any status (the refund-permissions, bulk-refund, and Google-Pay cases). For those 9, an agent that reasons carefully and honestly reports "none"/"approximate" is doing the right thing and is penalized for it.
  • We saw the inversion from both directions in our own runs: an agent doing the shallow structural read passes; agents that found the closed counterparts and reported them honestly failed only Criterion 6. Two model generations, one shared failure — the signature of a spec issue, not a capability limit. You called that correctly.

What we're going to do — and why it's a little different from the PR as written.

We want to keep eng-l1-a as a level-1 task.L1 tasks are deterministic retrieval: traverse the structured link (the shared component / product-part field) and report it. The exact-vs-approximate-vs-none distinction your PR introduces is genuinely valuable — but it's a level-2 competency (auditing whether the structural link matches the real defect), and folding it into L1 would blur the level taxonomy and change what a passing L1 answer means (it would also invert which agents currently clear the task). So rather than merge the semantic rubric into L1, we're going to:

  1. Fix the L1 ambiguity directly. Reword the task so the structural reading is unambiguous — the relationship is the shared component/product-part link, phrased as "mapped to the same component" rather than the semantically-loaded "related," and the grading criteria carry that intent explicitly. This removes the exact defect you identified: a careful agent will no longer be punished, because the intended method is stated.
  2. Promote your exact/approximate/none contract into a new L2 "audit" task — find and classify the discrepancies between the structural links and the actual defect/status picture (the 6 closed-counterpart + 3 untracked cases you surfaced). Your PR is the foundation for this task.
  3. Add an L3 "repair" task as well — act on the audit (reopen the closed counterparts, file issues for the untracked ones), which exercises safe, permission-scoped write-back. This will take more work as the MCP servers will need additional functions and more fine-grained authentication will need to be supported.

In other words: you didn't just find a bug — you surfaced the difference between two levels of the benchmark for this type of query, and that is now the basis for two new tasks. That's a bigger contribution than the original fix.

We'll credit you for the finding and for the L2/L3 foundation. I'll follow up directly about collaborating on authoring these. I think you're the right person to introduce the L2 task given you've already done the hard analytical part.

One small mechanical note for anyone reading later: the ID stability point in the PR is right — object display IDs are handled ID-independently in grading, and the discrepancy set is defined on stable attributes (subject → counterpart title → status), so it holds at 256x (our primary scale).

Thank you again — this is a real improvement to Enterprise-Bench.

Having your tests match mine exactly is extremely validating — thank you! I like the idea of different levels increasing the cognitive difficulty, and potentially the verifier being refined at that level too. It's a layered complexity approach that I think gets to the heart of agent debugging quickly and efficiently. I'm happy to author the L2 audit task!

One ask regardless of what happens to this PR

SKILL.md:247-250 currently tells anyone debugging a failed run:

Interpreting a fail: check which required criterion failed in explanation. If the same criterion fails across different agents/models, that's a task-difficulty signal, not an agent bug (e.g. eng-l1-a consistently fails Criterion 6 — the ticket→component→open-issue join — across claude-code and goose).

First, what I'm not arguing: the audit work is harder than the retrieval work, and that's why it deserves its own level. No argument with the L1/L2 split.

Two separate questions get blurred here:

  1. Is auditing structural links harder than following them? Yes — which is why it becomes L2.
  2. Did agents score 0/10 because that work was too hard for them? No. They did the work. Every trial found the closed counterparts, weighed semantic against structural match, and named ISS-021 and ISS-012 correctly. Criterion 6 then marked them wrong for it.

The agents did it right and got marked down for it. That isn't a difficulty signal, but the heuristic answers question 2 with question 1's answer, so it reads like one.

The tell is the shape of the failure. Three shapes:

  1. Task difficulty — scatters. Agents name different wrong objects and disagree with each other and with the reference. Genuinely hard; leave it alone.
  2. Spec ambiguity — converges on an alternative. Agents independently land on the same other reading, because that reading is reasonable. Check whether the criterion's stated premise is true of the data: Criterion 6 asserted no row should say "none," which holds structurally and fails semantically.
  3. Data defect — converges on the correct object. Agents identify the right thing, but it fails a filter (status, date, ownership).

eng-l1-a is 2 and 3 at once, converging across claude-code on two model generations plus goose. Every agent independently finding the same right answer is not what "too hard" looks like.

The practical fix: read the trajectory, not just the score. All three shapes are identical in reward.txt — what separates them is the objects agents name. The current wording says the score is enough, which is why this went unexamined.

Could the three-branch version land separately? Once eng-l1-a is reworded, the existing example is stale either way.

On this PR

Four files, and your plan supersedes three of them:

File Proposal Why
docs/eng-l1-a-criterion-6.md Keep The diagnosis, the two categories, and the repro
tasks/eng-l1-a/instruction.md Drop Your rewording replaces it
tasks/eng-l1-a/tests/criteria.yaml Drop Same
tasks/eng-l1-a/tests/trajectory.json Drop Same

Unless you'd rather I close it outright, I'll retarget this PR to the diagnosis doc alone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants