"""The operator's own preferences for a completion report, at the moment it is written. WHY THIS EXISTS (milestone 409 step 4) The reporting-back skill ships DEFAULT shapes. An operator will want some of them different ("my completion reports also say how it was tested", "decisions as a numbered list"), and those adjustments are `preference` records. The gap is the query: prompt-time retrieval matches the OPERATOR'S MESSAGE, and a shape preference is about the REPLY. "Fix the flaky test" never retrieves "completion reports should say how it was tested", so the preference is on file and never arrives. THE DECISION (operator, 2026-09-14, logged on the step): two deliveries, split by reply kind. - A COMPLETION REPORT has a moment the server can see — a task closing — so the server retrieves for it and hands the matches back beside `report_back`. That is this module. - EVERY OTHER REPLY KIND (a finding, a decision, a handoff…) has no tool call in front of it, so the reporting-back skill asks: it tells the agent to `search(content_type="rule")` for that kind before writing. Loading reply-shape preferences at session start was the rejected third option: it is a small copy of the preloading milestone 394 retired, and just as unmeasurable. HOW A PREFERENCE SAYS IT IS ABOUT COMPLETION REPORTS By its trigger, which is what it already has — no tag, no new column. A preference's `when_to_apply` dominates its embedded document, so one written for this moment ("writing the report after finishing a task") resembles COMPLETION_QUERY below, and one about anything else does not. That keeps delivery entirely in retrieval, as 394 decided, and leaves an operator nothing new to learn: a preference reaches the completion report the same way every other record reaches its moment. THE BAR IS ITS OWN, AND THE FIRST READING EARNED IT (#3860) This borrowed the prompt arm's key when it shipped, on the argument that a fixed query against triggers is a different score distribution from an operator's message against the same documents — true, and the reason the two could not stay one dial. Five days of traffic settled it. What the readout said: 69 calls, 69 declines, every one naming the SAME record at the SAME score (rule 77 at 0.7194 against a 0.72 bar). That constancy is the signature of this arm — `COMPLETION_QUERY` never varies, so for a given corpus its best score is a constant, and a constant sitting under the bar is a dead arm rather than a quiet one. The record it kept declining was about reading a REQUEST, not about the shape of a report, so the decline was right and the arm is healthy: this install simply has no completion-report preference on file. The bar stayed at 0.72, and the key moved out (REPORTPREF_THRESHOLD_KEY) so that staying is a decision rather than a side effect of what the prose arm is set to. A surface whose score cannot vary is the one surface where a borrowed bar can be wrong forever without a single call looking unusual. """ from __future__ import annotations import logging import time from scribe.services.embeddings import semantic_search_rules from scribe.services.retrieval_surfaces import SURFACES, budget_for, floor_for from scribe.services.retrieval_telemetry import record_retrieval from scribe.services.rule_usage import record_rule_surfaced logger = logging.getLogger(__name__) SOURCE = "report_preference" # Written in the vocabulary of the MOMENT, because that is what a trigger is # written in and what this query is scored against. Domain-neutral on purpose # (rule #115): a writing project or a home-infrastructure project closes tasks # too, and its operator's preferences must match as well as a developer's. COMPLETION_QUERY = ( "writing the completion report to the operator after finishing a task — " "how that reply should be laid out and what it should include" ) # A handful, not a menu. More than a few shape preferences for ONE kind of # reply would contradict each other before they helped; the limit is here to # keep one noisy corpus from turning a status change into a wall of text. # The STARTING budget, not the budget (#4102). Both numbers this arm runs on # now come from the surface registry, so the model that reads this arm's # telemetry can move either — which matters more here than anywhere else, # because a fixed query makes this arm's score a constant and a floor a hair # above it produces a dead arm no amount of traffic will ever reveal. LIMIT = SURFACES["report_preference"].budget_default async def _threshold(user_id: int) -> float: return await floor_for(user_id, SOURCE) async def _limit(user_id: int) -> int: return await budget_for(user_id, SOURCE) async def completion_preferences(user_id: int, *, project_id: int | None = None) -> list[dict]: """The operator's preferences for a completion report, best match first. KIND-FILTERED, for the reason `_reserve_slot_for_preference` gives: a binding rule that happened to resemble the query would otherwise ride out under a key that says "how the operator likes this written", which is a claim about force the record does not make. Every call is logged, the empty ones included: a surface that records only the calls it liked reports a flawless clear-rate however badly its bar is set. An install with no preferences at all logs nothing, because no search ran (`searched` stays False) — that is not a decline. Fails open to an empty list: this decorates a write that has already happened, and a lookup that errors must not turn it into a failure. """ try: threshold = await _threshold(user_id) limit = await _limit(user_id) report: dict = {} t0 = time.perf_counter() hits = await semantic_search_rules( user_id, COMPLETION_QUERY, limit=limit, threshold=threshold, kind="preference", report=report, project_id=project_id, ) hits = [(score, rule) for score, rule in hits if rule.kind == "preference"] record_retrieval( user_id=user_id, source=SOURCE, query=COMPLETION_QUERY, threshold=threshold, limit=limit, project_id=project_id, is_task=None, results=hits, best_available=report.get("best_available_score"), best_available_id=report.get("best_available_id"), searched=bool(report.get("searched", True)), duration_ms=(time.perf_counter() - t0) * 1000.0, ) if not hits: return [] # RANKED: this surface chose what it showed, so the name is in # rule_usage.RANKED_SOURCES and its hits count toward pull-through. record_rule_surfaced( user_id=user_id, rule_ids=[rule.id for _s, rule in hits], source=SOURCE, ) return [ {"id": rule.id, "title": rule.title, "statement": rule.statement, "kind": "preference"} for _score, rule in hits ] except Exception: # noqa: BLE001 - a decoration never breaks its payload logger.warning("completion preference lookup failed", exc_info=True) return []