Repository navigation
feat(telemetry): carry a per-stage latency breakdown in every telemetry sample (#722, slice 1) - #878
Conversation
…#722, slice 1) Each recall telemetry sample carries stage_ms: wall-clock milliseconds spent in query embedding, fused search, familiarity triage, recollection, cross-encoder, rerank nudges, final stages, enrichments, replay tracking, response budget, injection receipt and response encoding. Stages do not overlap. Measurement only: ranking and responses are unchanged. Refs #722 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_018kyZYtgaxDxPVHz4MGz7wA
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Claude finished @cdeust's task in 2m 14s —— View job PR #878 review (1 of 3)
No Blockers. The review comment has three notes that need no action before merge: Verdict: approve |
…timing # Conflicts: # CHANGELOG.md
|
Claude finished @cdeust's task in 2m 18s —— View job Review of PR #878 (review 1 of 3)
None of the five reviews found a Blocker. Wrapping the stages in
Not done: I couldn't run the test suite or the craftsmanship gate here; the reviews come from reading the code. Verdict: approve |
Review 1 of 3: PR #878 at
|
Review 1 of 3: PR #878, per-stage recall latency breakdown (#722, slice 1)Five reviews ran: code quality, documentation accuracy, performance, security and test coverage. None of them found a Blocker. What was checked and holds:
Notes, no action before merge
Verdict: approve |
|
Claude finished @cdeust's task in 1m 21s —— View job Review 3 of 3: PR #878 at
|
|
CI autopilot : mise en hold. Le gate de mutation ADR-1122 ne peut pas passer sur cette PR sans une décision du propriétaire. Run 42137028447, head Ce que le gate demande à cette PR. Il prend chaque fonction dont la PR touche une ligne. La PR ne fait qu'entourer des étapes existantes d'un
Les mêmes chiffres sortent sur l'ancien head Ce que l'autopilot ne fera pas seul. Il ne retire pas ces fonctions du gate par un détour comme un décorateur, qui rendrait la fonction non mutable : ce serait contourner le gate. Il n'ajoute pas non plus à une tranche de télémétrie environ 280 tests sur du code qu'elle ne change pas. Et même avec ces tests, le run sur À trancher par un humain, au choix :
Les revues Claude (1 à 3) et Codex (code et sécurité) approuvent sans Blocker. Rien n'a été poussé dans cette tentative. Aucun re-run n'a été lancé : un re-run donnerait le même dépassement de temps. Generated by Claude Code |
…ess (ADR-1122) (#906) mutmut's default fork isolation forks every mutant from the process that already ran the suite. On the Linux runner the child then hangs in torch autograd (titans_memory.py loss.backward) until mutmut times it out, and a timeout fails the gate: #877's 26 _resolve_domain mutants all timed out, and #878 was cancelled at the six-hour job limit. Reproduced in an Ubuntu 26.04 container set up as ci.json "unit" on 11 mutants of #878: fork, 5 of the first 7 timed out (py-spy: child idle in backward()); forkserver, 0 of 11 timed out in 71 s. mutmut 3.8.1 documents forkserver for test setups that are not fork-safe (gevent, grpc, torch).
|
Hold Generated by Claude Code |
|
CI autopilot : remise en hold. Avec #906, le gate de mutation va maintenant au bout, et il confirme que cette PR ne peut pas passer sans une décision du propriétaire. Run 42188232491, job 127793667449, head
Le total est de 273. Presque tous existaient avant la PR, qui n'ajoute qu'un La conclusion de la tentative 1 tient, avec des chiffres complets cette fois : régler cela relève d'un chantier de tests sur le chemin
Si tu choisis l'option 3, ou l'option 2 limitée à une fonction, réponds ici : l'autopilot reprend la PR (tentative 3, la dernière avant qu'il cède la main à un humain). Rien n'a été poussé, et aucun re-run n'a été lancé puisque le résultat est déterministe. Generated by Claude Code |
|
Décision : option 3. Cette tranche se limite à
Pas de rebase ni de force push : continue avec des commits |
|
Hold Generated by Claude Code |
… recall (#722, slice 1)
|
J'ai appliqué l'option 3 dans le commit
Ce que je n'ai pas pu vérifier : le chemin PostgreSQL, faute de PostgreSQL dans ce conteneur. Generated by Claude Code |
#906 set process_isolation = "forkserver" for every platform. On macOS that aborts before any mutant runs: the stats run, now in a forked child, imports sentence_transformers, whose torch.backends.mps.is_available() ends in SIGABRT (Child exited with code -6). Local runs on macOS, the autopilot's included, could no longer run the gate. mutation_check.sh now rewrites process_isolation from ci_mutation_scope.py: forkserver on Linux (fork hangs in torch autograd there), fork on macOS (measured again: 11 mutants of #878, 76 s, 0 timeouts). Any other platform stops the gate instead of guessing.
|
CI autopilot, passage du 2026-11-06 13:25 Z. Le run 42222649108 ( La branche est en retard de 2 commits sur Generated by Claude Code |
…on gate (ADR-1122) (#912) * ci: an added blank or comment line selects no function for the mutation gate (ADR-1122) A function owns the blank lines after it (_span), so the blank lines a PR adds above a new function fell in the span of the function before it, and the gate judged that unchanged function: #878 added timed_stage below count_reranked and was asked to kill count_reranked's mutants. changed_lines_ex now collects the added lines that are blank or only a comment in hunks that removed no code, and plan() drops them unless they sit inside a multiline string, whose value they change. Deletion anchors are untouched. * fixup! ci: an added blank or comment line selects no function for the mutation gate (ADR-1122) Review 1 blocker: a blank line inside a multiline bytes literal changes the value, but _string_spans only covered str and f-strings, so drop_inert dropped it. Bytes count now. changed_lines_ex splits the diff on \n only: str.splitlines also cut an added line at a form feed or U+2028 and shifted the inert positions. Tests for both (each fails on the previous code), for the f-string side and for a comment replaced by code plus a blank; the CHANGELOG names both exceptions.
|
CI autopilot, passage du 2026-11-06 ~14:40 Z. #912 est maintenant sur La branche est en retard de 5 commits sur Il faut qu'un humain rebase la branche sur Generated by Claude Code |
…timing # Conflicts: # CHANGELOG.md
…timing # Conflicts: # CHANGELOG.md
|
CI autopilot : branche en retard de 18 commits sur J'ai rebasé la branche sur Il faut qu'un humain rebase la branche sur Generated by Claude Code |
…122 item 6) (#917) Owner decision 2026-11-06. mutmut names mutants by function, so the gate ran and judged every mutant of each changed function: a one-line change owned the whole function's survivors (#865: 102 in _construct_store; #878: 273 on the recall path). scripts/ci_mutation_lines.py places each mutant on the source by diffing it against mutmut's unmutated copy of the function, both indexed in mutants/<path>.spans, and keeps it when it alters a line the PR changed (or the anchor of a deletion and the next line). plan() gives each job its lines; run_job passes them as MUTATION_ONLY_LINES; the --untested check and mutation_equivalents.py judge only the kept mutants, so registry entries on other lines are not stale. A mutant that cannot be placed stops the gate. Checked against mutmut show on all 245 mutants of core/telemetry.py and all 35 of core/wiki_identity.py (methods included). End to end on a one-line change to publish_record (PostgreSQL 18.6): 38 mutants ran, 20 survived on untouched lines, the gate passes and reports them as not judged.
…timing # Conflicts: # CHANGELOG.md
|
CI autopilot : branche en retard de 2 commits sur Note : la branche contient des commits Generated by Claude Code |
…timing # Conflicts: # CHANGELOG.md
|
Claude finished @cdeust's task in 1m 46s —— View job Review 4 of 3: PR #878 at
|
Review 4 of 3: PR #878 at
|
Summary
First slice of #722, reduced to option 3 by the owner (comment of 2026-11-06 12:40 Z). Telemetry can now carry a per-stage latency breakdown:
RetrievalMetrics(ADR-0668) gainsstage_ms, filled by a newtimed_stage(name)context manager, andtelemetry.record/publish_recordput it in every sample and JSONL line, each value rounded to 3 decimals likelatency_ms. No stage is timed yet, so every sample carriesstage_ms: {}for now. Instrumenting therecallstages (_handler_impl,pg_recall_stages,pg_recall_context) is the next slice: it waits for the owner's decision on the scope of the ADR-1122 mutation gate (whole function or changed lines).Refs #722
Type of change
Test plan
tests_py/core/test_retrieval_telemetry.py:timed_stageaccumulates a repeated stage and counts a stage that raises (fake clock). Outside an operation it runs the block and records nothing. Each new operation starts with an emptystage_ms.tests_py/handlers/test_telemetry_response.py: an instrumented handler publishes exactly the stages of its own call, rounded to 3 decimals (0.1234567 ms → 0.123); a sample with no timed stage publishes{}.tests_py/core/test_telemetry_record_contract.py(new) pins what the mutation gate found untested inrecord/publish_record: default counts, thetskey, the 3-decimallatency_ms, the UTF-8 size handed toopen_rotating_log, and the message andexc_infoof both logged failure paths.record+publish_record: 87/87 killed. Localscripts/ci_mutation_diff.py(GITHUB_BASE_REF=main):telemetry.pypasses. The one remaining survivor is intelemetry_context.count_reranked, an unchanged function the gate selects only because blank lines were added abovetimed_stage. It goes away with ci: an added blank or comment line selects no function for the mutation gate (ADR-1122) #912 (per the owner, no test for it).timed_stagecarries@contextmanager, so mutmut does not mutate it (notice); its behaviour tests cover it.pytest tests_py/core tests_py/handlers tests_py/benchmarks tests_py/shared tests_py/test_telemetry_middleware.py tests_py/hooks/test_hook_telemetry.py: 6718 passed, 264 skipped, 1 failed. The failure istest_wiki_coverage_dashboard.py::test_unreadable_page_is_skipped_not_fatal, which fails identically onmainin this container (runs as root).ruff check/ruff format --check: clean.python scripts/check_craftsmanship.py: OK.Not verified here: PostgreSQL path (no PostgreSQL in this container).
Breaking changes
None. Samples gain one key,
stage_ms(empty until stages are timed); exporters receive it.Reviewer checklist
https://claude.ai/code/session_018kyZYtgaxDxPVHz4MGz7wA
Generated by Claude Code