feat(retrieval): hot-path touch-state storage substrate (#816, closes #748) - #821
Conversation
There was a problem hiding this comment.
Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR-size soft capThis PR is over the advisory size threshold:
Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the |
|
[claim:review:feynman:2026-05-14T21:21:25Z] |
|
[claim:review:chomsky:2026-05-14T21:23:45Z] |
|
[release:review:chomsky:2026-05-14T21:23:50Z] |
|
[claim:review:oppenheimer:2026-05-14T21:24:20Z] |
|
[release:review:oppenheimer:2026-05-14T21:24:25Z] |
robotrocketscience
left a comment
There was a problem hiding this comment.
Reviewed against DESIGN.md v1 scope as summarized in the PR body. Substrate axis is sound — schema, predicate, store APIs, doctor surface, hook wiring, and locked-decision honoring (#605 monotonic counter, #661 per-session PK, no ML) all match the v1 ship list. CI clean (incl. CodeQL python). Approving v1.
Two concerns flagged in inline comments on the migration loop in hook._record_touches; both are post-merge follow-ups for the consumer-flip PR, not blockers for v1 substrate ship.
Migration loop is not "one-shot" and not no-op-idempotent.
The hook helper docstring (src/aelfrice/hook.py:1181-1208 in the diff) claims a "one-shot per-session migration" using "INSERT-OR-IGNORE". Neither is accurate:
- The SQL is INSERT … ON CONFLICT DO UPDATE (store.py:3215-3225), not INSERT OR IGNORE. Subsequent calls do not no-op —
touch_count = belief_touches.touch_count + 1on every conflict. - The migration loop runs on every UPS turn with at least one injection. There's no first-call guard.
Functional impact in v1: none. touch_count is unread by any consumer (count_touches_for_session returns COUNT(*) not SUM(touch_count); read_touch_set_in_window and is_hot only look at last_fire_idx). last_fire_idx ends up correct because the migration walks ring entries in ascending fire_idx order and the current-turn loop runs last with the highest fire_idx.
Latent impact for v2: touch_count accumulates +1 per UPS turn for every belief in the ring (whether or not it was injected this turn), plus an additional +1 for beliefs in the current injection set. After N UPS turns, an always-injected belief has touch_count = 2N. Any v2 consumer that tries to use this field for rerank weighting will need a redesign or a count_only_current_touches write path.
Performance: 200 fsync commits per UPS turn from migration.
MemoryStore.record_touch commits per call (store.py:3243). With DEFAULT_RING_MAX = 200 (session_ring.py:49), a fully-populated ring causes 200 commits on every UPS hook fire just for the migration loop. Each commit is a SQLite fsync. The substrate isn't read in v1, so the write cost is dead weight on the UPS hot path.
Recommended fix: gate the migration on first-call-per-session. Either
if store.count_touches_for_session(session_id) == 0:
# walk ring_list, do migration(one extra SELECT per UPS turn, amortizes the migration cost to once) or drop the migration loop entirely and add an explicit one-shot aelf doctor --hot-path --migrate-from-ring for operators who want pre-PR ring entries reflected.
Either fix would also remove the touch_count drift concern by collapsing migration writes to one per (belief, session) pair forever.
Other notes (non-blocking):
_record_touchesis invoked even when_ring_append_idsreturned-1via theexcept Exception: _next_fire = -1path; the_next_fire >= 1gate at line 962 catches this correctly, but the dependency on the gate is subtle. A comment on why-1is the sentinel would be useful.- DESIGN.md v1 lives in the lab and is not reviewable from this side. The PR body's quotes and the divergence-from-issue-body section together carry enough context for a public reviewer to evaluate scope. Good call.
- The R7c open-question carry-forward to the operator is appropriate given the gate is genuinely structurally unrunnable until production
injection_eventsaccumulates.
LGTM to merge once the consumer-flip follow-up PR is on the radar with the migration redesign captured. Tagging operator on the migration-loop concern for v2 planning.
|
[release:review:feynman:2026-05-14T21:26:08Z] |
|
[claim:review:prince:2026-05-14T21:36:20Z] |
|
[claim:review:clarke:2026-05-14T21:37:09Z] |
|
[release:review:clarke:2026-05-14T21:37:14Z] |
Review verdict: REQUEST CHANGESSubstrate work is high quality — sidecar schema, FK CASCADE, composite PK, One material concern below, plus two smaller notes. 1.
|
|
[release:review:prince:2026-05-14T21:42:14Z] |
|
[claim:review:feynman:2026-05-14T21:45:22Z] |
|
[claim:review:oppenheimer:2026-05-14T21:47:31Z] |
|
[release:review:oppenheimer:2026-05-14T21:47:36Z] |
|
[release:review:feynman:2026-05-14T21:49:23Z] |
|
[claim:review:prince:2026-05-14T21:50:57Z] |
|
[release:review:prince:2026-05-14T21:52:23Z] |
|
[claim:review:clarke:2026-05-14T21:54:18Z] |
Disposition — third reviewer concurs; no new review neededReviewers above are consilient on the single blocking issue: Both reviews proposed compatible fixes (sentinel / drop the ring Dropping |
|
[release:review:clarke:2026-05-14T21:55:50Z] |
review) PR #821 review (chomsky, feynman, prince, oppenheimer, clarke, 2026-05-14) flagged that _record_touches re-walks the entire #744 JSON ring on every UPS fire and calls record_touch for every entry. Because record_touch upserts with `touch_count = touch_count + 1` on conflict, the docstring's "one-shot INSERT-OR-IGNORE migration" claim was false in two ways: not one-shot, and not idempotent. Net effect on the example [b1],[b2],[b1]: b1.touch_count=5, b2.touch_count=3 (reviewers' repro), vs expected 2 / 1. Functional impact in v1: none — no consumer reads touch_count. Correctness impact for the H3 fidelity bench / any v2 rerank consumer: real, since touch_count drift would mis-rank. Fix: drop the ring replay entirely. The hook is now forward-only: record_touch is called only for the current turn's belief_ids. Pre- substrate ring entries (rows in the #744 JSON ring that predate the sidecar) are NOT backfilled. The ring is bounded (ring_max=200), no v1 consumer reads belief_touches, and the v2 rerank consumer will only care about touches recorded after v1 ships — so the value of backfill was already low. A sentinel-based "true one-shot" alternative would have required DB-level state for ~zero payoff. Test plan: - test_record_touches_writes_current_turn_only_no_ring_backfill — asserts ring-pre-pop is NOT migrated; current turn lands at touch_count=1. - test_record_touches_count_matches_actual_inject_count_across_fires — regression for the [b1],[b2],[b1] pattern; asserts touch_count={2,1} and last_fire_idx monotonicity (b1 > b2). Mimics the production caller sequence (append_ids then _record_touches fire_idx=next_fire-1). 22 tests in tests/test_hot_path_touch_state.py pass (was 21; migration test replaced by the two above). Docstring, docs/feature-hot-path.md, and the v3.1 CHANGELOG entry updated to describe forward-only semantics.
|
[release:review:oppenheimer:2026-05-14T23:07:58Z] |
v1 of the #748 hot-path campaign per `experiments/hot-path/DESIGN.md` (lab `6b40538`). Adds the storage substrate for per-(belief, session) touch state; no rerank consumer wired (v1 DESIGN ship list item 7). - `belief_touches(belief_id, session_id, last_fire_idx, touch_count, event_kinds_bitmask)` with composite PK and FK CASCADE to beliefs. Index `(session_id, last_fire_idx DESC)` for the rerank-stage window query. - Store APIs: `record_touch` (INSERT ... ON CONFLICT DO UPDATE), `read_touch_set_in_window`, `count_touches_for_session`, `list_touch_sessions`. - `src/aelfrice/hot_path.py`: pure `is_hot` predicate + `DEFAULT_TOUCH_WINDOW_K = 50` (R2c canonical cell) + `TOUCH_EVENT_KIND_*` bitmask constants. Only `INJECTION` (bit 0) is populated by callers in v1; bits 1-3 reserved (H4 refuted at R4/R4e/R5). Determinism (#605): fire_idx is a monotonic counter, never wall-clock. Federation (#661): per-session PK keeps foreign federated beliefs cold every read by construction.
After `_ring_append_ids` returns the next fire_idx for this UPS turn, call the new `_record_touches` helper to write the same injected ids into `belief_touches` with INJECTION bit set. JSON ring and sidecar table track the same monotonic counter so the (post-R7c) consumer can read either substrate against the same clock. `_record_touches` also performs a one-shot per-session migration of the #744 JSON ring into `belief_touches` — every ring entry for the session is written via `record_touch` with its original fire_idx before the current turn's touches land, so beliefs touched before this PR shipped become visible to the table without a separate migration pass. Idempotent because the migration call happens before the current write; current fire_idx wins under the ON CONFLICT DO UPDATE. Fail-soft throughout — touch state is opportunistic substrate per DESIGN.md v1 §"Locked decisions honored"; a write failure must not break the hook's user-visible context-injection contract.
…816) 21 tests across the v1 DESIGN test plan: - is_hot boundary cases incl. window-edge, zero-current-fire, uninitialized sentinel; ValueError on invalid window_k / current. - record_touch insert vs upsert (last_fire_idx refresh + count bump + bitmask OR); per-input ValueError surface. - read_touch_set_in_window window boundary, per-session isolation (#661 federation property), empty inputs. - count_touches_for_session + list_touch_sessions ordering. - Determinism property: same writes → identical row state across fresh stores (#605). - FK CASCADE on belief delete (using insert_belief). - Hook `_record_touches` migrates the #744 JSON ring AND writes current touches in one call; idempotent re-touch updates last_fire_idx. - Fail-soft on missing-DB path. Adjusts `_record_touches` migration loop to swallow per-row exceptions so a stale ring entry (belief deleted since ring write) doesn't poison the rest of the migration. Outer try/except still catches catastrophic store-open failures.
DESIGN.md v1 ship list item 6 — operator-side inspection of the belief_touches substrate. Lists every session_id with at least one touch row, plus row count and most-recent fire_idx. Read-only, always exits 0; v1 is observational. The post-R7c consumer flip will add gate semantics here.
`docs/feature-hot-path.md` covers schema, write path, decisions honored, R4 vs H3 distinction, inspection, and file map. Frames v1 explicitly as substrate-only — consumer flip post-R7c. CHANGELOG entry under Unreleased / Added.
review) PR #821 review feedback (2026-05-14) flagged that _record_touches re-walks the entire #744 JSON ring on every UPS fire and calls record_touch for every entry. Because record_touch upserts with `touch_count = touch_count + 1` on conflict, the docstring's "one-shot INSERT-OR-IGNORE migration" claim was false in two ways: not one-shot, and not idempotent. Net effect on the example [b1],[b2],[b1]: b1.touch_count=5, b2.touch_count=3 (reviewers' repro), vs expected 2 / 1. Functional impact in v1: none — no consumer reads touch_count. Correctness impact for the H3 fidelity bench / any v2 rerank consumer: real, since touch_count drift would mis-rank. Fix: drop the ring replay entirely. The hook is now forward-only: record_touch is called only for the current turn's belief_ids. Pre- substrate ring entries (rows in the #744 JSON ring that predate the sidecar) are NOT backfilled. The ring is bounded (ring_max=200), no v1 consumer reads belief_touches, and the v2 rerank consumer will only care about touches recorded after v1 ships — so the value of backfill was already low. A sentinel-based "true one-shot" alternative would have required DB-level state for ~zero payoff. Test plan: - test_record_touches_writes_current_turn_only_no_ring_backfill — asserts ring-pre-pop is NOT migrated; current turn lands at touch_count=1. - test_record_touches_count_matches_actual_inject_count_across_fires — regression for the [b1],[b2],[b1] pattern; asserts touch_count={2,1} and last_fire_idx monotonicity (b1 > b2). Mimics the production caller sequence (append_ids then _record_touches fire_idx=next_fire-1). 22 tests in tests/test_hot_path_touch_state.py pass (was 21; migration test replaced by the two above). Docstring, docs/feature-hot-path.md, and the v3.1 CHANGELOG entry updated to describe forward-only semantics.
f8208df to
2dd86df
Compare
Rebased on current main; ready to re-mergeRebased onto CHANGELOG conflict resolved: top-level One commit-message scrub in the same pass: the Pre-push:
Removing |
|
merge-train: merged 2dd86df → |
) PR #821 commit b030a4f demonstrated that the pre-push hook's diff-content scan left a gap: banned vocabulary and CLAUDE.md-derived phrases carried in commit subject lines or bodies were never inspected, because git diff shows file deltas, not log messages. Session-routing names shipped in a commit body and landed permanently in refs/pull/821/head. This adds scripts/install-discretion-hook.sh, a tracked installer whose heredoc contains the canonical .git/hooks/pre-push content. The canonical hook extends the existing three checks with two new ones: Check 4 — BANNED_VOCAB on git log --format=%B for the pushed range Check 5 — BANNED_PHRASES on the same Both fire inside the existing while-read ref loop. When no common ancestor with main exists, the full reachable history from local_sha is walked. Error shape and ALLOW_DISCRETION_OVERRIDE=1 bypass mirror Checks 2/3. Path-excludes are not applied to Checks 4/5 (commit messages have no paths). Checks 2 and 3 (diff-content scans) gain ':(exclude)scripts/install-discretion-hook.sh' so that the installer's own heredoc — which necessarily contains the literal BANNED_VOCAB and BANNED_PHRASES regexes — does not trigger a self-block on every push that touches this file. Installer semantics: - Idempotent: exits 0 with "already up to date" when content matches byte-for-byte. - Refuses divergent overwrites without --force (prints diff, exits 1). - Marks result executable; resolves repo root via git rev-parse --show-toplevel. - Exits 1 if not inside a git working tree.
Hot-path consumer-flip decision (status update)Follow-up on this PR's deferred-consumer note. The touch-state substrate shipped here (the sidecar That measurement has now been taken against two real
Both above the pre-committed 0.60 crossover (R7b synthetic sweep). Implication: the synthetic-baseline top-K shift signal that originally motivated a touch-temperature multiplier rerank consumer mostly evaporates at production correlation levels. Cross-corpus agreement (two independent project shapes) strengthens the read. Decision: the consumer flip is not scheduled. No follow-up PR will introduce a posterior-rerank multiplier that reads Caveat: each corpus is a single-session measurement; N (touched-belief count) is modest. The verdict is suggestive-not-decisive at this dispatch budget. An extended sweep (multiple sessions per corpus, additional corpora) could tighten the call but is not required to land the deferral — both measurements landed cleanly above the crossover band. Re-opening conditions: an extended sweep yielding ρ_mixed < 0.60 on a meaningful fraction of cells, or a different consumer mechanism that isn't a posterior-uncorrelated temperature multiplier. No code change attached to this comment. Documentation amendment to |
Public diagnostic for the hot-path belief_touches substrate (#748 / #816 / PR #821). Measures Spearman rho between posterior_mean = alpha/(alpha+beta) (from beliefs) and per-session touch_count (from injection_events) on a chosen session. Two correlation shapes reported: 1. Touched-only: rho restricted to beliefs touched in the session. Whether posterior tracks *frequency* among already-touched beliefs. 2. Touched + sampled-untouched: rho over touched beliefs plus a random sample of untouched beliefs of comparable size. Closest match to the original R4-family comparison shape; this is the load-bearing number for the #848 H3-defer call. Decision framework (carried from R7b): rho < 0.30 -> BUILD_PIPELINE (signal robust) rho < 0.60 -> PARTIAL (signal partially survives) rho >= 0.60 -> SHIP_H4_ONLY (signal mostly artifact) Usage: python3 scripts/probe_posterior_touch_correlation.py \ --db <project-root>/.git/aelfrice/memory.db If --session-id is omitted, picks the most-recent session with >=5 injection_events. Pre-#779 schemas (no injection_events table) exit 2 with a clear message. Privacy: reads only schema columns (alpha, beta, belief_id, session_id, injected_at). Never touches text or document content. Output is purely statistical. Tests: tests/test_probe_posterior_touch_correlation.py covers the Spearman helper (perfect-positive, perfect-negative, tie-handling, small-N edge cases) and the verdict-band mapping at anchor + boundary values. 11 tests, all passing. Full script run is operator-time (needs a real DB) and is not bench-gated. Closes #850.
Public diagnostic for the hot-path belief_touches substrate (#748 / #816 / PR #821). Measures Spearman rho between posterior_mean = alpha/(alpha+beta) (from beliefs) and per-session touch_count (from injection_events) on a chosen session. Two correlation shapes reported: 1. Touched-only: rho restricted to beliefs touched in the session. Whether posterior tracks *frequency* among already-touched beliefs. 2. Touched + sampled-untouched: rho over touched beliefs plus a random sample of untouched beliefs of comparable size. Closest match to the original R4-family comparison shape; this is the load-bearing number for the #848 H3-defer call. Decision framework (carried from R7b): rho < 0.30 -> BUILD_PIPELINE (signal robust) rho < 0.60 -> PARTIAL (signal partially survives) rho >= 0.60 -> SHIP_H4_ONLY (signal mostly artifact) Usage: python3 scripts/probe_posterior_touch_correlation.py \ --db <project-root>/.git/aelfrice/memory.db If --session-id is omitted, picks the most-recent session with >=5 injection_events. Pre-#779 schemas (no injection_events table) exit 2 with a clear message. Privacy: reads only schema columns (alpha, beta, belief_id, session_id, injected_at). Never touches text or document content. Output is purely statistical. Tests: tests/test_probe_posterior_touch_correlation.py covers the Spearman helper (perfect-positive, perfect-negative, tie-handling, small-N edge cases) and the verdict-band mapping at anchor + boundary values. 11 tests, all passing. Full script run is operator-time (needs a real DB) and is not bench-gated. Closes #850.
Summary
Lands v1 of the #748 hot-path campaign per
experiments/hot-path/DESIGN.md(lab6b40538). v1 is the storagesubstrate only —
belief_touchesaccumulates per-(belief, session)touch state; the rerank consumer is parked on R7c per DESIGN.md v1
ship list item 7.
Closes the storage axis of #748. Issue #816 stays in the loop for the
post-R7c consumer-flip PR.
Scope (DESIGN.md v1)
belief_touchestableevent_kinds_bitmaskbits 1–3 for v2aelf doctor --hot-pathinspection surfaceDivergence from #816's issue body
The issue body asked for a default-OFF rerank consumer wired into
retrieve_v2(resolve_use_hot_path_touch_state,hot_path_multiplier, federation property-test formultiplier=1.0).DESIGN.md v1 is explicit: "No retrieval consumer. Touch state is
written but the rerank multiplier is dark — gated behind
meta:retrieval.touch_temperature_enabled = false. Flipping the gateis a separate post-H3 PR." The DESIGN was authored on the same day
as the issue and reflects the post-R7c verdict; the issue body
predates it. This PR follows DESIGN.md.
Concrete consequences: no
resolve_use_hot_path_touch_stateresolver,no
hot_path_multiplierhelper, noretrieve_v2wiring changes. Thefederation property is preserved structurally by the composite PK
(belief_id, session_id)— foreign federated beliefs come in cold byconstruction rather than via a
multiplier=1.0short-circuit. Doctorsurface, schema, migration, and lab evidence all match DESIGN.md
literally.
Lab evidence reference (R0..R7c)
reads at N=50k / 5k, no regression on unrelated reads; result holds
at small-N (1k–5k) per R1b. H1 PASS.
"touched-in-last-K" indistinguishable at the rerank gate
(ρ ≥ 0.85 across all well-powered cells; adversarial steel-man
reinforces). H2 REFUTED. v1 ships boolean.
retrieve_hitvs INJECTION-only at top-5 Jaccard = 1.000 across 6 orthogonal
axes / ~43 cells. H4 REFUTED. v1 ships INJECTION-only;
retrieve_hitbit reserved but unwritten.0.6757) with R7b crossover at r=0.6 between synthetic and
production translation.
injection_eventsaccumulates ≥50 rows. H3 verdict parked;consumer flip is a separate PR.
Commits
fd0e7708b6176b769d4b20134d51c3dc4f6fa755Tests
uv run pytest tests/test_hot_path_touch_state.py tests/test_slash_commands.py→ 138 passed, 1 skipped. Run again as part of the full suite via the
PR gate.
Acceptance vs. issue body
The issue body's acceptance list assumed a wired consumer. This PR
recasts the acceptance to match DESIGN.md v1:
belief_touchesschema lands (CREATE TABLE IF NOT EXISTS—forward-compat on existing stores).
isolation, fire_idx monotonicity, ON CONFLICT semantics).
retrieve_v2— noretrieve_v2changes at all in this PR; trivially preserved.
state across fresh stores.
multiplier=1.0— replaced by thestructural PK guarantee + per-session isolation test.
what makes the bench possible).
Open question (R7c)
DESIGN.md's only open operator question is when to dispatch R7c.
Direct quote: "Once
injection_eventsaccumulates ~50+ rows on areal DB, run
experiments/hot-path/run_R7c_operator_probe.py --db <real-brain-db>. Result r against R7b's crossover decides whether tobuild the κ pipeline (r < 0.6) or ship H4-only at v1 with H3
deferred indefinitely (r ≥ 0.6)." Surface for the operator —
nothing in this PR depends on the answer.