Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(retrieval): query-time temporal resolver (Issue 2, slim)#287

Merged
jamby77 merged 2 commits into
masterfrom
feature/longmemeval-temporal-resolver
Jul 6, 2026
Merged

feat(retrieval): query-time temporal resolver (Issue 2, slim)#287
jamby77 merged 2 commits into
masterfrom
feature/longmemeval-temporal-resolver

Conversation

@jamby77

@jamby77 jamby77 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Part of the LongMemEval recall→QA epic (item 205255004). Stack 4/4 — base feature/longmemeval-fact-consolidation.

resolveTemporal: as-of/'before X' filter (with empty-result safety) + newest-first recency ordering, behind the temporal lever in the reader-context path; recall unchanged. Scoped down because Issue 3's reconcile already supersedes and the assembler already orders chronologically. 4 tests.

QA lift target (+8pt temporal-reasoning) pending a real Tier-2 eval.


Note

Low Risk
Changes are confined to the LongMemEval eval harness behind an optional lever; production retrieval is unaffected unless separately wired.

Overview
Adds an opt-in temporal lever on the LongMemEval eval runner that filters the rerank pool before reader contexts are built, without changing recall (still scored on the unmodified top-k).

resolveTemporal drops chunks whose date tag is after record.question_date, keeps undated chunks, preserves incoming rerank order (no recency re-sort), and falls back to the full pool if the filter would remove everything. When the lever is off, behavior is unchanged.

The QA path now builds contexts from a readerPool (temporal-filtered when enabled) for both assemble and non-assemble modes; the non-assemble branch takes the first k from that pool instead of the recall hits slice. Temporal work is recorded in the cost report as latency-only (no embed/LLM calls). Unit and integration tests cover filter semantics and lever wiring.

Reviewed by Cursor Bugbot for commit 7b7fe6e. Bugbot is set up for automated code reviews on this repo. Configure here.

@jamby77 jamby77 force-pushed the feature/longmemeval-fact-consolidation branch from 7373465 to 3347356 Compare June 30, 2026 08:12
@jamby77 jamby77 force-pushed the feature/longmemeval-temporal-resolver branch 4 times, most recently from 407df8b to d456cd4 Compare June 30, 2026 10:22
@KIvanow

KIvanow commented Jun 30, 2026

Copy link
Copy Markdown
Member

LongMemEval _m ablation result: assemble+temporal regress QA hard.

Full 500, k=10, pool=30, same harness as the baseline. Levers on (assemble+temporal, no facts): recall@10 92.8% (unchanged), QA 28.2% vs the 63.8% baseline (-35.6 pts). Per-type QA collapses everywhere, worst on multi-session (13.5%) and temporal-reasoning (21.1%).

Recall is identical because it is measured on the untouched rerank top-k, which proves the gold evidence is retrieved into the pool. The assemble/temporal stage then loses it before the reader. Root causes:

  1. Ordering contract conflict: the runner pipes pool through resolveTemporal before assembleContexts. resolveTemporal re-sorts the whole pool by date desc, but assembleContexts derives MMR relevance from list position (assumes rerank order). So MMR ends up ranking by recency, not relevance.
  2. MMR (lambda 0.5) drops corroborating evidence as near-duplicates. For multi-evidence questions this removes the gold chunk. MMR diversification suits result presentation, not QA evidence assembly.
  3. Dedup at 0.9 containment (smaller-set denominator) discards short fact chunks subsumed by longer ones.
  4. Final chronological re-sort feeds the reader date-ordered rather than relevance-first.

Suggestions: run each lever in isolation on _m to attribute the loss (the all-on number conflates them); if keeping assemble, derive relevance from the rerank score rather than post-temporal list position, drop or soften MMR/dedup for evidence selection, and preserve relevance-first ordering into the reader. The temporal as-of filter (drop future-dated chunks) is fine and cheap; the harm is its full re-sort feeding assemble.

Separately, the facts lever is impractical on _m: it fires one sequential gpt-5.4 chat call per haystack session, and _m averages ~475 sessions per record (~237k sequential calls for the full run). It was likely validated on oracle, where haystacks are only the evidence sessions. It needs bounded-concurrency extraction (the per-session calls are independent; only reconcile needs ordering) before it can run on _m.

@jamby77 jamby77 force-pushed the feature/longmemeval-fact-consolidation branch from 54d7bd3 to e9f84af Compare July 1, 2026 07:20
@jamby77 jamby77 force-pushed the feature/longmemeval-temporal-resolver branch from d456cd4 to 12121bf Compare July 1, 2026 07:20
@jamby77

jamby77 commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Reworked to address the regression (pushed):

  • temporal (12121bf): resolveTemporal is now an as-of filter only — dropped the newest-first re-sort that fed a recency-ordered pool into the assembler.
  • assemble (8265156): default is now relevance-first and non-destructive (rerank-ordered top-k + date prefix). Dedup, MMR, and chronological grouping are behind opt-in flags, out of the default evidence path.

Net: assemble+temporal on should now sit at ≈ baseline rather than dropping/reordering evidence. Could you re-run _m to confirm? And agreed on isolating each lever — the flags let us A/B dedup/MMR/grouping individually.

Separately, the facts lever's per-session sequential extraction is untouched here; it still needs bounded-concurrency before it's practical on _m (tracked separately).

@jamby77

jamby77 commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up on the facts scalability note: bounded-concurrency extraction is now up in #291 (stacked on this chain). Per-session extraction runs via an order-preserving mapWithConcurrency (default 8, LONGMEMEVAL_FACTS_CONCURRENCY); reconcile stays sequential and curated output is unchanged — so facts is now runnable on _m.

@KIvanow KIvanow left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the full PR and re-ran LongMemEval-M (full 500, k=10, pool=30, same reader/judge/embedder + live valkey-search).

Code is clean and safe to merge. The as-of filter is correct: it drops chunks dated after question_date, keeps undated chunks, falls back to the original pool if the filter would empty it, and preserves rerank order. Recall stays measured on the unmodified top-k (pool.slice(0, k)), so recall is invariant to the lever. With the lever off, readerPool.slice(0, k) is identical to the previous hits path, so there is no baseline regression. Tests cover the edge cases plus zero-cost and no-recall-regression, which is the right contract.

It also resolves the earlier regression. The prior version re-sorted survivors newest-first, which fed a recency-ordered pool into the assembler's position-based relevance and collapsed QA (we saw 28.2% on the combined levers). Scoping this down to an as-of filter only, with ordering left to rerank/assemble, brings QA back to baseline: 62.4% vs 63.8% (within noise), recall unchanged at 92.8%.

One honest caveat: it does not improve QA on this benchmark. LongMemEval questions are asked at the end of each timeline, so almost nothing is dated after question_date and the filter has little to remove (measured cost was +12ms with effectively no pool change). The +8pt temporal-reasoning target in the description is unmet in practice. That is a property of the benchmark structure, not a code defect. The real QA bottleneck is downstream reasoning (multi-session and temporal-reasoning lag despite 90%+ recall), which a pool filter cannot address.

Net: this is defensible, well-tested, flag-gated scaffolding that fixes the regression and adds no risk, so merging is fine. Just flagging that the temporal QA lift will need to come from the evidence/reasoning layer, not the as-of filter.

@jamby77 jamby77 force-pushed the feature/longmemeval-temporal-resolver branch from 12121bf to 41e7e8e Compare July 3, 2026 06:37
@jamby77 jamby77 force-pushed the feature/longmemeval-fact-consolidation branch from e9f84af to 952a0b1 Compare July 3, 2026 06:37
@jamby77 jamby77 force-pushed the feature/longmemeval-temporal-resolver branch from 41e7e8e to 50b5a67 Compare July 3, 2026 07:10
@jamby77 jamby77 force-pushed the feature/longmemeval-fact-consolidation branch from 952a0b1 to 235ec1c Compare July 3, 2026 07:10
jamby77 added 2 commits July 6, 2026 20:49
- add eval/longmemeval/temporal.ts: resolveTemporal applies an as-of
  ("before X") filter with empty-result safety, then orders survivors
  newest-date-first for recency-sensitive questions
- wire behind the temporal lever in the reader-context path; recall stays
  measured on the unmodified rerank top-k (no regression)
Drop the newest-first recency re-sort from resolveTemporal. Re-sorting the pool
by date fed a recency-ordered list into the assembler's position-based
relevance and regressed QA on LongMemEval-M. The lever now only filters out
chunks dated after the question, preserving rerank order.
@jamby77 jamby77 force-pushed the feature/longmemeval-temporal-resolver branch from 50b5a67 to 7b7fe6e Compare July 6, 2026 17:51
@jamby77 jamby77 changed the base branch from feature/longmemeval-fact-consolidation to master July 6, 2026 17:51
@jamby77 jamby77 merged commit 9bd38e5 into master Jul 6, 2026
2 checks passed
@jamby77 jamby77 deleted the feature/longmemeval-temporal-resolver branch July 6, 2026 17:52
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants