CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / integration (push) Successful in 50s
CI & Build / TypeScript typecheck (push) Successful in 57s
CI & Build / Python tests (push) Failing after 1m3s
CI & Build / Build & push image (push) Skipped
Groundwork for the step's real change. The operator's decision is that the floor is chosen and adjusted by the model using it, not shipped as a value somebody has to defend: "we need a model consistent surface for the adjustment of these floor values. the user should be able to touch it but the model should be the thing handling it 9 times out of 10." A tuning surface cannot be consistent across six arms that each spell their configuration differently, so the arms stop owning their numbers. `retrieval_surfaces.SURFACES` names each one, its floor key and default, its budget key and default, and — because they are rendered by the tuning tool and the Settings UI — what it asks, over what corpus, and how often it fires. A floor cannot be moved responsibly by anyone who does not know those three. Three things fall out: - **`k` becomes a real budget everywhere.** Only auto-inject had a configurable one; `RULEHINT_LIMIT`, `PROMPTRULE_LIMIT` and `reply_preferences.LIMIT` were constants. `k` is what binds under a low floor, so it has to be settable per surface — and per surface is the point, since `pre_tool_rule` fires before every Bash call while `prompt_rule` fires once a turn. - **`write_path` gets its own budget, inherited not reset.** It shared auto-inject's outright on the argument that "how many titles at once" means the same thing on both. It does not, for the same reason. Unset, it still reads auto-inject's key, so an install that tuned the shared knob does not silently drop to a new default. - **The duplicated read-and-clamp goes.** That shape is canon #2860 across 295 of 372 judged siblings. Survivable while the numbers were constants; not once they are meant to move. The long measurement comments stay exactly where they are — #2223's noise-floor probe, #3853's command-vs-code split, #3851's band measurement. The constants they annotate now alias the registry, so there is one value and the reasoning still sits beside it. Tests build the write-path config from the registry (`helpers.writepath_cfg`) instead of from hand-written dicts. That is not tidiness: the rule arms read their numbers inside a fail-open `except`, so a dict missing one key does not raise where a reader would see it — the arm silently becomes a no-op that reads exactly like "fired and found nothing". Ten hand-written dicts each looked complete on the day they were typed. tests/test_retrieval_surfaces.py pins the identity everything rests on: a surface's name IS its telemetry source. Nothing in the type system says so — `record_retrieval(source="pre_tool_rule")` is a literal in another file — and renaming one without the other yields an arm that can be tuned and not measured, or measured and not tuned, with no symptom either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
149 lines
7.0 KiB
Python
149 lines
7.0 KiB
Python
"""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 []
|