Skip to content

feat(replay): re-query action-match mode — the honest metric for the one nondeterministic stage (#9028) - #9586

Merged
loopover-orb[bot] merged 1 commit into
mainfrom
feat/replay-requery-9028
Jul 28, 2026
Merged

feat(replay): re-query action-match mode — the honest metric for the one nondeterministic stage (#9028)#9586
loopover-orb[bot] merged 1 commit into
mainfrom
feat/replay-requery-9028

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Closes #9028

Summary

Completes Replay v2. Requirement 2 (wall-clock capture) was verified already landedstaleness-clock.ts cites #9028 in its own header, the captured instant rides decision_replay_inputs.replay_json, and the seeded per-rule tests plus the CLI's clock-divergence exit are all in place. This PR is requirement 1, whose "blocked on" was a persistence decision — made here rather than deferred again.

What it is

Everything downstream of the model is a pure function the replay harness pins bit-exactly, including time. The model call is the one stage that cannot be: hosted inference is not bit-deterministic even at temperature 0 (batching, kernel scheduling, silent model revisions).

So replay-decision.ts --requery <n> re-runs the model n times against the exact persisted prompt and reports an action-match rate: across fresh runs, how often the verdict lands in the same class the recorded decision acted on — defect vs clean, through the same parseModelReview the live pipeline uses, on the same ai_consensus_defect/ai_review_split boundary the gate acts on. Two runs that phrase the same blocker differently are the same action; a defect ⇄ clean flip is a different action regardless of phrasing.

Per the issue's hard requirement, it is never labeled reproducibility — the metric's own label is "action-match-rate (NOT reproducibility)", so a pasted report cannot shed the caveat, and a test pins that the word appears nowhere else in the artifact.

The persistence decision

decision_replay_prompts (migration 0200): a private sibling of decision_replay_inputs with the identical posture and a deliberately shorter 30-day retention — the prompt embeds the full diff plus contributor content (the largest, most sensitive artifact in the replay family), requery is a debugging tool for recent decisions, and the public promptDigest commitment outlives the text forever. Keyed by the base record id, so a supersession's :rev<N> row still finds its head's prompt (the CLI header documents the extract join).

Oversize prompts are skipped, never truncated: a truncated prompt re-queried would report a rate for a prompt that was never sent.

Both turns — the flaw my own first test caught

The first end-to-end assertion failed in the right way: the persisted system prompt did not contain the PR's diff, because the system prompt carries the rubric + config suffixes (what promptDigest commits to) while the diff travels in the user turn. Re-querying with the system prompt alone would ask the model to review nothing, and the rate would have been meaningless.

Both turns are now captured — at the orchestration site, never through findings (findings reach public render surfaces) — and the CLI replays the live call's own shape: system turn + user turn at temperature 0. That is what makes the rate a statement about the decision, not about a different way of asking.

Denominator honesty

A transport failure is an unusable run counted in the denominator, never a silent skip — shrinking it would inflate the rate exactly when the provider is flakiest. unusable matches neither class, mirroring the live pipeline's fail-closed inconclusive routing.

Provider config is explicit env only (REPLAY_AI_BASE_URL+REPLAY_AI_MODEL, or ANTHROPIC_API_KEY+REPLAY_AI_MODEL): a tool that spends tokens never guesses which model to spend against. Exit 0 on a produced report (a low rate is a finding, not a failure); 2 on unusable input or missing config. Pure replay mode remains network-free by construction, untouched.

Testing

21 tests across four suites: class boundaries through the live parser, both recorded-class codes, the denominator invariants, the zero-run arm, verbatim two-turn passthrough, bundle/env parsing with every named error (including the 30-day retention hint on the missing-prompt path), base-id upsert semantics, the oversize skip, the swallowed persist failure, the retention rule pruning at its own window, and an end-to-end pass proving a real AI review persists both turns with the diff in the user one.

New module: 100% statements, branches, functions. All changed lines in existing files: 0 uncovered. Full suite: 24,045 green. db:migrations:check, db:schema-drift:check, and every drift checker pass.

…one nondeterministic stage (#9028)

Completes #9028. Requirement 2 (wall-clock capture) was verified already landed:
staleness-clock.ts cites this issue in its header, decision_replay_inputs.replay_json
carries the captured instant, and decision-replay.test.ts pins both arms per rule plus
the CLI's clock-divergence exit. This PR is requirement 1, previously deferred on the
prompt-persistence decision -- which is made here, not deferred again.

WHAT IT IS. Everything downstream of the model is a pure function the replay harness
pins bit-exactly, including time. The model call is the one stage that cannot be:
hosted inference is not bit-deterministic even at temperature 0 (batching, kernel
scheduling, silent model revisions). So `replay-decision.ts --requery <n>` re-runs the
model n times against the EXACT persisted prompt and reports an ACTION-MATCH RATE --
across fresh runs, how often the verdict lands in the same CLASS the recorded decision
acted on (defect vs clean, through the same parseModelReview the live pipeline uses,
with the same ai_consensus_defect/ai_review_split boundary the gate acts on). The
word "reproducibility" appears only inside the metric's own label as a negation --
"action-match-rate (NOT reproducibility)" -- so a pasted report cannot shed the caveat.

THE PERSISTENCE DECISION. decision_replay_prompts (migration 0200): a private sibling
of decision_replay_inputs with the identical posture and a deliberately SHORTER 30-day
retention -- the prompt embeds the full diff plus contributor content (the largest,
most sensitive artifact in the replay family), requery is a debugging tool for recent
decisions, and the public promptDigest commitment outlives the text forever. Keyed by
the BASE record id so a supersession's :rev<N> row still finds its head's prompt.
Oversize prompts are SKIPPED, never truncated: a truncated prompt re-queried would
report a rate for a prompt that was never sent.

BOTH TURNS, because the first honest test caught the design flaw: the system prompt
carries the rubric + config suffixes (what promptDigest commits to), but the DIFF
travels in the USER turn -- re-querying with the system prompt alone would ask the
model to review nothing. Captured at the orchestration site, never through findings
(findings reach public render surfaces). The CLI replays the live call's own shape: a
system turn plus a user turn at temperature 0, so the rate is a statement about the
decision, not about a different way of asking.

Denominator honesty: a transport failure is an UNUSABLE run counted in the
denominator, never a silent skip -- shrinking it would inflate the rate exactly when
the provider is flakiest. Unusable matches neither class, mirroring the live
pipeline's fail-closed inconclusive routing.

Provider config is explicit env only (REPLAY_AI_BASE_URL/REPLAY_AI_MODEL or
ANTHROPIC_API_KEY/REPLAY_AI_MODEL) -- a tool that spends tokens never guesses which
model to spend against. Exit 0 on a produced report (a low rate is a FINDING); 2 on
unusable input or missing config. Pure replay mode remains network-free by
construction, untouched.

Tests: 21 across four suites -- the class boundaries through the live parser, both
recorded-class codes, denominator invariants, zero-run degenerate arm, verbatim
two-turn passthrough, bundle/env parsing with every named error (including the 30-day
retention hint), base-id upsert semantics, oversize skip, swallowed persist failure,
the retention rule at its own window, and an end-to-end pass proving a real AI review
persists both turns with the diff in the user one. New module: 100% statements,
branches, and functions. Full suite: 24,045 green.
@JSONbored JSONbored self-assigned this Jul 28, 2026
@loopover-orb

loopover-orb Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-28 20:05:16 UTC

11 files · 1 AI reviewer · no blockers · readiness 88/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
Adds the model-requery action-match mode from #9028: a new decision_replay_prompts table (migration 0200) captures the exact system/user prompts sent to the model, keyed by the base record id so a supersession can still find its head's prompt, with a private/30-day-retention posture distinct from decision_replay_inputs' 180 days. The CLI's --requery flag re-runs the model n times against the persisted prompt and reports an action-match rate through the same parseModelReview/finding-code boundary the live gate acts on, with careful handling of transport failures (counted as 'unusable', never silently dropped from the denominator) and oversized prompts (skipped, never truncated). Wiring is consistent end-to-end: retention policy, PK map, schema-drift allowlist, and the orchestration call site all agree on the record-id format used by the documented extract SQL, and the test suite exercises both classification arms, the zero-runs degenerate case, and the retention prune window.

Nits — 6 non-blocking
  • migrations/0200_decision_replay_prompts.sql:19 comments `prompt_json` as `{ systemPrompt }` but the actual persisted JSON (src/review/decision-replay.ts persistDecisionReplayPrompt) also includes `userPrompt` — update the column comment so it doesn't mislead an operator writing the extract SQL.
  • Several new numeric literals ship without named constants (scripts/replay-decision.ts:133 run-count cap of 25, src/db/retention.ts's 30-day window, decision-replay.ts's 250-char id truncation and 120/160-char log truncations) — low-risk but worth a constant for the run-count cap at least since it's a user-facing validation boundary.
  • The console.warn/error calls in decision-replay.ts and replay-decision.ts are appropriate for a CLI/best-effort-persist context, not real debug leftovers, but flagged by static scan — no action needed.
  • Consider naming DECISION_REPLAY_PROMPT_MAX_CHARS-style constants for the --requery run-count bound (25) and the record-id slice length (250) the same way DECISION_REPLAY_PROMPT_MAX_CHARS is already named, for consistency.
  • Fix the migration's `prompt_json` column comment to list both `systemPrompt` and `userPrompt` so it stays accurate for operators writing the extract SQL by hand.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #9028
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ❌ 8/20 High review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 300 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 300 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: significant
Linked issue satisfaction

Addressed
The PR delivers the persisted-prompt-plus-requery mechanism (decision_replay_prompts table, --requery CLI mode, action-match-rate metric explicitly never labeled reproducibility) that requirement 1 asks for, and requirement 2's wall-clock capture is asserted as already landed with tests cited (staleness-clock.ts, per-rule seeded tests, clock-divergence exit).

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is registered but has no active allocation in the current snapshot.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 14 PR(s), 300 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 3 steps in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@github-actions

Copy link
Copy Markdown
Contributor

Logic backtest

Replayed 0 historical case(s) for linked_issue_scope_mismatch through the base (643383c) and head (e87c70d) versions of its detection logic (corpus checksum 4f53cda18c2b).

Backtest comparison: linked_issue_scope_mismatch

Verdict: unchanged — no comparable axis moved.

Advisory only — this check never blocks merge (#8105).

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.84%. Comparing base (643383c) to head (e87c70d).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #9586   +/-   ##
=======================================
  Coverage   89.84%   89.84%           
=======================================
  Files         878      879    +1     
  Lines      111036   111060   +24     
  Branches    26434    26438    +4     
=======================================
+ Hits        99756    99780   +24     
  Misses       9992     9992           
  Partials     1288     1288           
Flag Coverage Δ
backend 95.62% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/db/retention.ts 100.00% <ø> (ø)
src/queue/ai-review-orchestration.ts 98.79% <ø> (ø)
src/review/decision-replay.ts 100.00% <100.00%> (ø)
src/review/decision-requery.ts 100.00% <100.00%> (ø)
src/services/ai-review.ts 96.79% <ø> (ø)

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit b1b7643 into main Jul 28, 2026
9 checks passed
@loopover-orb
loopover-orb Bot deleted the feat/replay-requery-9028 branch July 28, 2026 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replay v2: re-query action-match mode + wall-clock capture for staleness rules

1 participant