feat(retrieval): a review pass judges whether injected lines related — menus_to_review, judge_menu and a judged readout (#4772)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m1s
CI & Build / Python tests (push) Successful in 1m55s
CI & Build / Build & push image (push) Successful in 44s

An open rate cannot say whether a menu line related: every line carries its
matched passage (#4364), so "not opened" covers unrelated, enough as shown,
and already in context. #4772 "Injected notes are never judged".

- retrieval_judgments (0115): a reviewer verdict per line of a logged call,
  on_point / adjacent / unrelated, with its reason, rank, budget side and
  whether the agent opened it within the hour.
- menus_to_review re-runs a random sample of unjudged auto_inject calls with
  the arm's own parameters, past its budget, passage on every line.
  judge_menu records verdicts, re-deriving rank from a fresh re-run.
- retrieval_telemetry gains a judged block (by rank, within/beyond budget,
  on_point_unopened). surfaced_never_pulled stops blaming titles.
- missed-retrieval guidance names the review before a budget move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 15:34:14 -04:00
co-authored by Claude Opus 5.5
parent 0a1bb68808
commit 6598c7fa85
13 changed files with 879 additions and 11 deletions
+24 -6
View File
@@ -40,6 +40,7 @@ from scribe.models.system_usage import SURFACED as SYSTEM_SURFACED
from scribe.models.system_usage import SystemUsageEvent
from scribe.services.rule_usage import is_ambient
from scribe.models.retrieval_log import RetrievalLog
from scribe.services.retrieval_review import judged_block
from scribe.services.retrieval_registry import (
POINTS, UNBIDDEN, get_point, is_registered, sources_expected_to_emit,
)
@@ -745,9 +746,11 @@ def _compute_warnings(sources: dict, usage: dict, rule_usage: dict,
# ── Surfaced and never pulled, for each corpus ───────────────────────
#
# The one corpus-level check, and the only number here that judges the
# RECORDS rather than the arms. A record shown repeatedly and never opened
# is either badly titled or genuinely irrelevant, and both are actionable
# in a way "pull-through is 0.15" is not.
# RECORDS rather than the arms. It used to say a record shown repeatedly
# and never opened is "badly titled or genuinely irrelevant" — true when a
# line was a title, and not since it carries the passage (#4364): the
# record may have done its job unopened. So it now points at the judged
# sample rather than at a cause (#4772).
#
# Distinct records, not events: a note surfaced forty times and never
# opened is one problem, not forty.
@@ -764,9 +767,11 @@ def _compute_warnings(sources: dict, usage: dict, rule_usage: dict,
out.append(_warn(
"surfaced_never_pulled",
f"{never} of {shown} distinct {label} were surfaced in this "
f"window and never opened. Read the titles before the "
f"threshold: a record nobody opens is usually one whose title "
f"does not say when it matters.",
f"window and never opened. That is not a verdict on them: a "
f"line carries its matched passage, so an unopened record may "
f"have been unrelated, enough as shown, or already in context. "
f"Before acting on it — a title, a floor, a budget — judge a "
f"sample with `menus_to_review` and read the `judged` block.",
source=None, corpus=label,
surfaced=int(shown), pulled=int(pulled or 0), never_pulled=never,
))
@@ -995,6 +1000,7 @@ async def retrieval_summary(
"usage": {},
"rule_usage": {},
"system_usage": {},
"judged": {},
"read_failed": False,
}
@@ -1554,6 +1560,18 @@ async def retrieval_summary(
out["rule_usage"] = rule_usage
out["system_usage"] = await _system_usage(user_id, since)
# ── Judged lines (#4772) ─────────────────────────────────────────────
#
# The relevance readout the usage blocks cannot be: a reviewer's verdict
# on each line of a sample of logged menus, by rank. Its own guard, for
# `_system_usage`'s reason — a failure flags itself rather than printing
# an empty block, which reads as "nothing judged" (#2663).
try:
out["judged"] = await judged_block(user_id, since)
except Exception:
logger.warning("judged block read failed", exc_info=True)
out["judged"] = {"judged_failed": True}
# ── What is wrong (#3431) ────────────────────────────────────────────
#
# Computed LAST, over the blocks above rather than over the database, so