Repository navigation
fix(insights): the narrative names the budget that failed the run - #51
Merged
Merged
Conversation
…eason Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The prompt listed each budget's limit with no result, so a FAIL on the latency ceiling alone was explained as 'failed to meet the budget for Trace-rule violations' — a budget that held. Each budget line now ends with its status, the breached ones ride on failed_budgets, the decision's reason travels with them, and the instruction allows only a breached budget to be named as the cause. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ve copies The decision's reason travels into a FACTS block the model copies verbatim, but the policy words it with evalshift.yaml keys: a trace-rule FAIL quoted migration_policy.max_invariant_violations, a banned token that cost a faithful generation its retry, and a dropped-params FAIL quoted fail_on_dropped_params and drop_params, which would ship to readers. The reason now reaches the facts in plain words, figures intact. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On a FAIL the verdict summary and recommendation are scanned for a 'within' budget sharing a clause with a non-negated blame word. A hit is rejected like an invented number, the retry names the labels, and two rejections ship the templated prose, which already names the breached budget. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
validate_blame took held labels only and matched them by substring, so a
breached slice label such as "'checkout' slice cost increase" accused
its held overall row, and a breached label carrying a blame word
("Trace-rule violations") accused a held neighbour. Labels from both
sides are now located longest first, non-overlapping, and masked before
the scan; _build_insight passes the non-held labels as others.
', with', ', however,' and a dash now end a clause too, so a retry phrased
the way the hint invites ('..., with Cost increase within policy') is
accepted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A slice label reaches the model as "'checkout' slice cost increase" and may come back bare or curly-quoted; the held overall "cost increase" inside it then claimed the span. _normalise now drops straight and curly quotes from labels and prose alike. The neighbour test's sentence split at ', with' and passed without the masking; it now keeps both labels in one clause and asserts the accusation that appears when others is omitted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…erdict reason The policy words a slice-only trace-rule fail as "in the 'security' slice, over the 0 allowed by migration_policy.max_invariant_violations". The plain rewrite turned the key into "the trace-rule violations budget", pinning the slice's limit on the overall budget the FACTS block marks within; a summary that copied the reason, as instructed, was rejected as a misblame every time. A key quoted beside a slice's limit is now named with the same scoped label the FACTS block lists the slice row under, so the blame scan's masking claims it. The overall case is unchanged. The Facts.verdict_reason comment now says what the field actually is. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…failure
The blame scan rejected faithful FAIL narratives, and two rejections ship the
templated prose with no findings:
- "over the" / "above the" matched "+102% over the source". Over, above and
beyond now accuse only with a limit noun (limit, budget, ceiling,
threshold, maximum, allowance), one word or figure allowed between.
- A run-level "failed" accused every held budget in its clause, as in
"the run failed on Latency increase, as Cost increase and Trace-rule
violations all stayed within budget". Each mention of a held label is now
judged on its own. It is excused by a negation right before it ("not Cost
increase or Trace-rule violations"), or by an un-negated holding word after
it (within, held, stayed, remains, passed, inside, under the/its, ...) with
no blame word between and no other label between unless joined by
and/or/nor/a comma. A label is blamed only when a mention is not excused
and the clause holds an un-negated blame word.
The shipped sentence, "address the Trace-rule violations", and "exceeded its
limit and stayed within budget" are still caught. So is a holding word that
belongs to another label ("failed on Cost increase as Latency increase stayed
within its ceiling").
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A FAIL from comparison severity or dropped parameters breaches no budget, and its FACTS block has no failed_budgets line, yet the retry hint sent the model to "the budgets under failed_budgets". With failed_budgets empty the hint now reads "No budget was breached; ..."; its first sentence is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tch costs DOCS.md and llms-full.txt said a false match "costs one retry". It costs the retry, which is shared with invented-number rejections, and a repeated one ships the templated prose, which has no findings. Both now say so, list the limit noun "over/above/beyond" needs, describe the negated-mention and holding-word excuses and the "No budget was breached" retry hint, and name a slice's trace-rule limit for its slice in the plain-worded reason. The DOCS.md Run-insights paragraph is split at "On a `fail`", and the touched llms-full.txt bullet is rewrapped to its width. The changelog entry notes that naming a held budget to clear it is not blame. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The two excuses added to the blame scan were loose enough to wave through clear accusations that were caught before: - Any holding verb excused the label before it, so "Cost increase remained above the +200% limit", "held at +250%, exceeding its limit" and "under the target model breached its ceiling" shipped. A holding phrase now counts only when it is measured against the limit: "within", "inside/under/below" the same limit phrase the blame words use (one shared constant), or a bare "held"/"holds"/"passed" that ends its clause. - A negation before a label excused it even when the label modified a noun, so "no Cost increase headroom left" shipped. The negation now excuses only a name that stands alone: the label, and any labels chained to it, followed by a comma, the clause end or or/and/nor/but. Two misses are accepted and pinned: "stayed within budget at first, then exceeded it" and "Address Cost increase so it stays within budget". Also read a slice's scope up to "' slice, over" rather than the first quote, so a slice named "o'brien" keeps its scoped budget in the verdict reason. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ases DOCS.md and llms-full.txt now say a holding phrase must be measured against the limit, that a negated label must stand alone, and which two misses are accepted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A bare "within" still excused any held label before it, so "Cost increase within the checkout flow exceeded its limit", "Cost increase, within hours, broke its ceiling" and "Address Cost increase within the next sprint" shipped. All four holding prepositions (within, inside, under, below) now need a limit phrase after them. The shared limit phrase widens so correct phrasings stop being flagged: the determiner is optional and may be "their", up to two comma-free words or figures may precede the noun, and "policy" joins the limit nouns, so "within budget", "within policy", "under their limits" and "under the +200% cost limit" hold. The blame side widens with it: "went over budget" and "went over their limits" now accuse. "below the target's ceiling" and "below the ceiling on 3 prompts" are limit phrases, so they still excuse the label before them; both are pinned as accepted gaps. DOCS.md, llms-full.txt and the docstrings list the accepted words and the four accepted misses. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
The run-insights narrative (the AI-written summary at the top of
report.html) is now told which budget failed the run. On a FAIL, a summary that blames a budget that held is rejected.budget "<label>": limit <x>, <status>. The status is one ofbreached,within,measured nothing, orover its limit but not confirmed at this sample size.failed_budgetsandverdict_reason. These are new FACTS lines: the breached rows, and the decision's ownreason. The reason is reworded so it never quotes config keys (migration_policy.max_invariant_violations→ "the trace-rule violations budget", scoped to the slice when the reason names one). Its figures join the number allow-list. Both keys are banned from prose, like the other FACTS keys.breachedbudget may be named as the cause of a FAIL or as something to fix. The verdict summary must agree withverdict_reason.validate_blame. This runs on a FAIL only, oververdict_summaryandrecommendation.withinbudget shares space with a blame word that isn't negated: breach/exceed/fail/violate/address/miss, or over/above/beyond plus a limit word.llms-full.txt,docs/faq.mdand CHANGELOG are updated, including the cache caveat: an existing run keeps its old narrative until itsinsights.jsonis deleted.No change to the config, suite,
state.json,scores.jsonl,insights.jsonor bundle schemas.Why
Run
r_20261012_converter_d418e5failed on the latency ceiling alone (+175.3% against +30%), and every other budget held. Its narrative said the target model "failed to meet the budget for Trace-rule violations" and recommended "address the Trace-rule violations". That budget had passed at 0 against a limit of 1. The prompt had listed each budget's limit without its result, so the model guessed the cause. The existing number and identifier checks could not catch an accusation written in plain words.Verified locally (CLI from this branch)
r_20261012_planner_d51f9cr_20261012_converter_c80f30model: none): "the latency increase budget was breached". Nothing blames Trace-rule violations. Both AI attempts were rejected, and why is not yet known. The reason is printed incompare's warnings and was not captured. It is either a blame-check false match or the older invented-number check. Follow-up: rerunreportand capture the warning.r_20261012_triage_697d54nonewith the tolerance note;agent_contractnot attempted).Known limits of the blame check
The blame check is a heuristic safety net; the prompt rule is the primary fix. The reviewer measured it on probe sets:
The accepted gaps are pinned in tests and listed in DOCS.md.
Follow-ups (not in this PR)
Test plan
pytestpasses: 3093 passed, 95% coverage (make ci)ruff check,ruff format --checkcleanmypy --strict src/evalshift_cliclean## [Unreleased]Release: 1.4.2, together with #50, via a separate
chore(release)PR after merge.🤖 Generated with Claude Code