feat(telemetry): the preload emits, and the always-on set stops being unfalsifiable (#3473)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 23s
CI & Build / integration (push) Successful in 31s
CI & Build / Python tests (push) Successful in 1m6s
CI & Build / Build & push image (push) Successful in 27s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 23s
CI & Build / integration (push) Successful in 31s
CI & Build / Python tests (push) Successful in 1m6s
CI & Build / Build & push image (push) Successful in 27s
The ranked rule arm became measurable in M333. The preload did not — and that is the surface whose value is actually in question. `list_always_on_rules`, the SessionStart block and every `rules_payload` caller handed rules over wholesale and emitted nothing, so the resident set's token cost was certain and its usefulness could not be tested even in principle. Bulk deliveries now record as AMBIENT, beside the ranked count and never inside pull-through. Folding them in would mean growing the always-on set depressed the arm's measured precision and trimming it flattered the arm, neither for any reason to do with the arm. `RANKED_SOURCES` inverts the note twin's `AMBIENT_SOURCES` deliberately: there is one ranked rule source and this change adds seven bulk ones, so naming the rare half makes a forgotten surface default to ambient — under-counting it — rather than padding the denominator with surfacings nobody chose. Two lookalike call sites are deliberately left silent, with a test to keep them that way: the write-path etag arm and `rules_etag_for` read the rules to build or compare a MARKER and show nobody anything. No migration — `event` and `source` are plain Text with no CHECK (rule 36 does not apply). Snippet #2858 updated to the new `rules_payload` contract. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011cPyzNnegXHr5iRMzzy5KJ
This commit is contained in:
@@ -30,6 +30,7 @@ from scribe.models.note_usage import PULLED, SURFACED, NoteUsageEvent
|
||||
from scribe.models.rule_usage import PULLED as RULE_PULLED
|
||||
from scribe.models.rule_usage import SURFACED as RULE_SURFACED
|
||||
from scribe.models.rule_usage import RuleUsageEvent
|
||||
from scribe.services.rule_usage import is_ambient
|
||||
from scribe.models.retrieval_log import RetrievalLog
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -428,10 +429,15 @@ async def retrieval_summary(user_id: int | None, *, days: int = 30) -> dict:
|
||||
.group_by(RuleUsageEvent.event, RuleUsageEvent.source)
|
||||
)
|
||||
).all()
|
||||
# No AMBIENT exclusion here, unlike the note twin: nothing
|
||||
# surfaces a rule un-ranked yet. `list_always_on_rules` and
|
||||
# `enter_project` deliver rules wholesale but emit no event, so
|
||||
# there is no ambient class to subtract (milestone 333 step 1).
|
||||
# The rows carry `source`, so the ranked/ambient split is done
|
||||
# below rather than in SQL — the bulk surfaces started emitting
|
||||
# on 2026-09-03 (#3473), so there IS an ambient class now.
|
||||
#
|
||||
# `distinct_rules_surfaced` deliberately counts BOTH classes. It
|
||||
# answers "how many distinct rules did this install put in front
|
||||
# of an agent at all", which is the denominator for dead weight
|
||||
# — and a rule delivered by the preload a hundred times and
|
||||
# never opened is the most important case that question has.
|
||||
distinct_rules_surfaced = (
|
||||
await session.execute(
|
||||
select(func.count(func.distinct(RuleUsageEvent.rule_id)))
|
||||
@@ -545,11 +551,18 @@ async def retrieval_summary(user_id: int | None, *, days: int = 30) -> dict:
|
||||
# been comparing across windows, without telling them it now measures
|
||||
# something else.
|
||||
#
|
||||
# No `ambient` key, unlike its twin. Nothing surfaces a rule un-ranked yet;
|
||||
# the absence is a fact about the data rather than an oversight, and it
|
||||
# returns the moment a bulk loader starts emitting.
|
||||
# `ambient` now carries the bulk deliveries — the SessionStart preload,
|
||||
# `list_always_on_rules`, and every `rules_payload` surface (#3473). Before
|
||||
# they emitted, this block had no ambient key and said the absence was a
|
||||
# fact about the data. It was, and it was also the thing that made the
|
||||
# always-on set impossible to judge: the largest rule surface in the
|
||||
# product was the one surface its own scoreboard could not see.
|
||||
#
|
||||
# READ THE TWO SEPARATELY, ALWAYS. `surfaced` is a claim a ranker made and
|
||||
# a pull can settle. `ambient` is a delivery nobody chose, so a high count
|
||||
# says the set is large and resident, never that it is useful.
|
||||
rule_usage = {
|
||||
"surfaced": 0,
|
||||
"surfaced": 0, "ambient": 0,
|
||||
"pulled": 0, "pulled_by_agent": 0, "pulled_by_human": 0,
|
||||
"distinct_rules_surfaced": int(distinct_rules_surfaced or 0),
|
||||
"distinct_rules_pulled": int(distinct_rules_pulled or 0),
|
||||
@@ -565,7 +578,14 @@ async def retrieval_summary(user_id: int | None, *, days: int = 30) -> dict:
|
||||
for event, source, n in rule_rows:
|
||||
n = int(n)
|
||||
if event == RULE_SURFACED:
|
||||
rule_usage["surfaced"] += n
|
||||
# One definition of ranked-vs-ambient, imported rather than
|
||||
# restated — the per-rule badge readout reads the same
|
||||
# predicate, and two spellings of "what counts as surfaced" is
|
||||
# precisely the uneven wiring #3246 found across this system.
|
||||
if is_ambient(source):
|
||||
rule_usage["ambient"] += n
|
||||
else:
|
||||
rule_usage["surfaced"] += n
|
||||
elif event == RULE_PULLED:
|
||||
rule_usage["pulled"] += n
|
||||
# Same split, and it carries MORE weight here than for notes.
|
||||
@@ -582,6 +602,15 @@ async def retrieval_summary(user_id: int | None, *, days: int = 30) -> dict:
|
||||
# empty numerator AND denominator that is a claim the data does not
|
||||
# support, and it is the reading that would make a brand-new install look
|
||||
# like a broken one.
|
||||
#
|
||||
# RANKED SURFACINGS ONLY in the denominator, and this is the load-bearing
|
||||
# line of the whole change. Pull-through asks "was that hint any use", and
|
||||
# only a surface that CHOSE what it showed can be judged by it. Folding the
|
||||
# preload in would divide the same pulls by a number that grows with every
|
||||
# session and every rule added to the resident set — so enlarging the
|
||||
# always-on set would DEPRESS the arm's measured precision, and trimming it
|
||||
# would flatter it, neither for any reason to do with the arm. The ambient
|
||||
# count sits beside it, unaveraged, and is read as size rather than skill.
|
||||
rule_usage["pull_through"] = (
|
||||
round(rule_usage["pulled_by_agent"] / rule_usage["surfaced"], 4)
|
||||
if rule_usage["surfaced"] else None
|
||||
|
||||
Reference in New Issue
Block a user