Files
FabledGrimoire/src/grimoire/mcp/tools/retrieval_review.py
T
bvandeusenandClaude Opus 5.5 7c1d98dd2a
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m2s
CI & Build / Python tests (push) Failing after 1m26s
CI & Build / Build & push image (push) Skipped
feat(6251): the rule arms can be reviewed — menus_to_review re-runs prompt_rule, pre_tool_rule and write_path_rule
A rule arm logs its query, floor, budget and project, which is everything
semantic_search_rules takes, so a logged call is re-run to a depth past its
budget and judged like an auto_inject menu. within_budget is the arm's own
cut: inside the budget and, for the banded command and write arms, inside the
band of the top hit. "Opened after" reads rule pulls, and rules created
after the call are dropped as notes are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:56:17 -04:00

86 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Judging what a retrieval arm offered.
`retrieval_telemetry` says what the reader did with each line — opened it or
not. These two say whether the line RELATED, which is a reading only someone
looking at the query beside the line can do.
"""
from grimoire.mcp._context import current_user_id
from grimoire.services import retrieval_review as review_svc
async def menus_to_review(
source: str = "auto_inject", n: int = 5, days: int = 14, depth: int = 6,
) -> dict:
"""A sample of logged injection menus, re-run so you can judge each line.
Reach for this before moving a budget or a floor, and whenever an open rate
is about to be read as a verdict on relevance. It is not one: every menu
line carries the passage that matched, so a record left unopened may have
been unrelated, enough as shown, or already in context, and the usage
counters cannot tell those apart. A judged sample can.
Each menu is one logged call: the query the arm searched (enriched, as it
was asked), its `budget`, and `lines` — the query re-run with the arm's own
parameters to `depth`, each line with `rank`, `score`, `kind`, `name`, the
`passage` that matched, `within_budget` (was it inside the cut) and
`opened_after` (did your agent open it within an hour). Lines past the
budget are there on purpose: they are what a larger budget would add.
`missing` names ids the call logged that the re-run no longer finds — the
corpus has moved since. Judge what is in front of you.
The re-run is against today's corpus, so it guards what has been written
since. Records created after the call are left out and named in
`postdated`: the call could not have offered them, and the session that
made it often wrote them, quoting it. A line marked `changed_since_call`
was written to afterwards; if its passage is plainly the later text, leave
it unjudged. `logged` says the line was shown fresh at the time.
JUDGE FROM THE LINE; DO NOT OPEN THE RECORD. Opening counts as a pull and
would credit the arm with an open it never earned. Then record verdicts
with `judge_menu`. `how_to_judge` in the response repeats the vocabulary.
Args:
source: the arm to review. Only arms whose search can be re-run
exactly are offered: `auto_inject`, and the rule arms
`prompt_rule`, `pre_tool_rule` and `write_path_rule`. A rule
line's passage is the trigger the arm matched, and for the
command and write arms `within_budget` also needs the line to
sit inside the band of the top hit, as it did when shown.
n: how many calls to sample, 1–20. Random among the calls you have not
judged, so a review does not describe one afternoon.
days: how far back to sample from.
depth: how many candidates to re-run per call, 1–10. Past the budget,
so the cut itself can be judged.
"""
return await review_svc.menus_to_review(
current_user_id(), source=source, n=n, days=days, depth=depth,
)
async def judge_menu(log_id: int, verdicts: list[dict]) -> dict:
"""Record your verdict on lines of one menu from `menus_to_review`.
`verdicts` is a list of `{record_id, verdict, reason}`:
"on_point" — someone in the query's situation should read this.
"adjacent" — the same area, but it would not change what they do.
"unrelated" — close in words only.
`reason` is REQUIRED for every verdict: what in the query and the passage
decided it. A verdict nobody can re-read is a threshold, and this exists to
replace one. Rank, score and which side of the budget the line sat are
taken from a fresh re-run, not from you. Judging a line again replaces your
earlier verdict on it.
The verdicts are read back as `retrieval_telemetry`'s `judged` block.
"""
return await review_svc.judge_menu(
current_user_id(), log_id=log_id, verdicts=verdicts,
)
def register(mcp) -> None:
mcp.tool(name="menus_to_review")(menus_to_review)
mcp.tool(name="judge_menu")(judge_menu)