Skip to content

feat: retrieval-time TTL filter + three-number bench summary - #155

Merged
jaylfc merged 1 commit into
masterfrom
feat/ttl-filter-and-memscore
Jun 11, 2026
Merged

feat: retrieval-time TTL filter + three-number bench summary#155
jaylfc merged 1 commit into
masterfrom
feat/ttl-filter-and-memscore

Conversation

@jaylfc

@jaylfc jaylfc commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • Feature 1: retrieval-time TTL filter. VectorMemory.add now accepts forget_after (unix float) and forget_reason (str) in user metadata. _load_active_rows excludes rows whose forget_after has passed, exactly like superseded rows. Zero-loss: the raw row is never deleted. Non-numeric values are silently ignored. POST /ingest and POST /ingest/batch docstrings document the new fields. Inspired by supermemory's forgetAfter concept.

  • Feature 2: three-number bench summary. _summary, the overall JSON block, and _print_summary in locomo_runner.py now emit mean_latency_ms, p95_latency_ms, and mean_context_tokens (context chars / 4 per row). _process_qa stores context_chars per row. Existing accuracy columns (F1, BLEU-1, Judge, R@K) are untouched. Inspired by supermemory's MemScore philosophy of not collapsing three independent dimensions.

Files changed

  • taosmd/vector_memory.py -- _load_active_rows gains TTL filter + optional now param
  • taosmd/http_server.py -- endpoint docstring updated with forget_after/forget_reason note
  • benchmarks/locomo_runner.py -- _summary, _print_summary, _process_qa updated
  • CHANGELOG.md -- two new Unreleased entries
  • tests/test_ttl_filter.py -- 8 tests for the TTL filter
  • tests/test_locomo_memscore.py -- 11 tests for the bench summary

Test plan

  • python3 -m pytest tests/test_ttl_filter.py tests/test_locomo_memscore.py -v -- 19 passed
  • Full suite python3 -m pytest --timeout=60 -q -- 778 passed, 0 failures

Summary by CodeRabbit

  • New Features

    • Added memory expiration control: records can now specify when they should no longer appear in search results while preserving raw data.
    • Enhanced benchmark metrics: performance reports now include latency percentiles and estimated context token usage.
  • Documentation

    • Updated REST API documentation to reflect new optional metadata fields for memory ingestion.
  • Tests

    • Added comprehensive test coverage for memory expiration behavior and benchmark metric calculations.

Feature 1: forget_after / forget_reason in VectorMemory metadata.
_load_active_rows gains an optional `now` param and excludes rows whose
metadata forget_after has passed. Zero-loss: raw row stays in the DB,
only recall filters it. Non-numeric values are ignored with a debug log.
HTTP endpoint docstrings updated to document the two new metadata fields.
8 tests in tests/test_ttl_filter.py cover: expired invisible, future
visible, non-numeric ignored, zero-loss, batch round-trip, clock override.

Feature 2: mean_latency_ms / p95_latency_ms / mean_context_tokens added
to _summary, the overall JSON block, and _print_summary in locomo_runner.
_process_qa stores context_chars (len of context string) per row so the
token estimate (chars / 4) is exact. Accuracy columns untouched.
11 tests in tests/test_locomo_memscore.py cover aggregate math and print.

CHANGELOG updated under Unreleased.
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces two independent features: a retrieval-time TTL filter for vector-memory rows controlled by forget_after metadata that hides expired rows without deleting them, and an enhancement to the LoCoMo benchmark runner that adds latency percentiles and context-size metrics to benchmark summaries.

Changes

Retrieval-time TTL filter

Layer / File(s) Summary
TTL filter implementation and API contract
taosmd/vector_memory.py, taosmd/http_server.py, CHANGELOG.md
VectorMemory._load_active_rows now accepts an optional now parameter and filters out rows where metadata_json.forget_after is numeric and less than the current time; non-numeric or missing values are ignored. HTTP endpoint documentation and changelog describe the hide-from-search semantics.
TTL filter tests
tests/test_ttl_filter.py
Comprehensive test suite validates TTL visibility across semantic and BM25 search, covers expired, future-dated, missing, and non-numeric forget_after cases, verifies zero-loss row persistence in SQLite, confirms metadata round-tripping, and tests the now override parameter.

LoCoMo benchmark metrics enhancement

Layer / File(s) Summary
Benchmark metrics computation and output
benchmarks/locomo_runner.py, CHANGELOG.md
Per-QA results now track context_chars and evidence coverage. The _summary function computes mean latency (retrieval + generation), p95 latency percentile, and mean context-token estimate derived from average context characters, with safe defaults for missing fields. The printed output includes a latency line with p50/p95 percentiles and context-token metrics.
Benchmark metrics test suite
tests/test_locomo_memscore.py
Tests validate _summary computation of latency and context metrics across empty/single/multiple-row datasets, confirm p95 percentile indexing, and verify _print_summary outputs latency lines, context tokens, preserves accuracy table headers, and handles zero-valued metrics gracefully.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A filter for forgotten things,
With TTL wings and benchmark rings,
Hidden rows that never fade,
While metrics bloom in bright cascade—
Time and context, side by side,
In memory's tide we gently glide! 🌊

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely summarizes both main features: retrieval-time TTL filtering and a three-number benchmark summary, matching the primary changes across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ttl-filter-and-memscore

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
benchmarks/locomo_runner.py (1)

1108-1124: 💤 Low value

Computation logic is correct, but minor cleanup opportunity.

The latency and context-token calculations are sound. However, line 1118 has a redundant int() call: math.floor() already returns an int in Python 3.

♻️ Optional simplification
-    p95_idx = min(int(math.floor(0.95 * n)), n - 1)
+    p95_idx = min(math.floor(0.95 * n), n - 1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/locomo_runner.py` around lines 1108 - 1124, The p95 index
computation uses an unnecessary int() around math.floor in the expression
computing p95_idx; update the p95_idx calculation (the line computing p95_idx
using math.floor) to remove the redundant int() so it simply uses
math.floor(...) (or better, compute p95_idx = min(math.floor(0.95 * n), n - 1))
while keeping the use of latencies, p95_lat, and mean_lat unchanged.

Source: Linters/SAST tools

taosmd/http_server.py (1)

54-57: ⚡ Quick win

Clarify the request structure for single ingest.

The documentation mentions "user metadata dict" but doesn't show where this goes in the request body. Consider making it explicit like the batch endpoint does, e.g., {"text", "agent", "project"?, "metadata"?: {"forget_after"?: float, "forget_reason"?: str}}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@taosmd/http_server.py` around lines 54 - 57, The documentation for the
single-ingest request is ambiguous about where the "user metadata dict" belongs;
update the single-ingest endpoint docs in taosmd/http_server.py to explicitly
show the request JSON schema (mirroring the batch endpoint) — i.e. the body
should be {"text": str, "agent": str, "project"?: str, "metadata"?:
{"forget_after"?: float, "forget_reason"?: str}} — and ensure the single-ingest
handler (the function that parses the single-item POST) expects and validates a
top-level "metadata" object with optional forget_after and forget_reason fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@benchmarks/locomo_runner.py`:
- Around line 1198-1205: The printed label "p50" is incorrect because the code
is using overall.get('mean_latency_ms', ...) which is the arithmetic mean;
update the print f-string in benchmarks/locomo_runner.py (the block that prints
latency using overall.get('mean_latency_ms', ...) and
overall.get('p95_latency_ms', ...)) to relabel "p50=" as "mean=" (or
"mean_latency=") so it accurately reflects the metric, leaving the p95 line
unchanged.

In `@tests/test_locomo_memscore.py`:
- Around line 176-184: The test test_print_summary_includes_latency_line should
also assert that the printed latency line uses the correct label for the mean
latency instead of a p50 label; update the test to call
runner._print_summary(meta, {}, overall) (as it does) and assert that the output
contains the expected label for mean latency (e.g., "mean" or "mean latency")
and that the p95 value is labeled appropriately (e.g., "p95"), referencing the
test name test_print_summary_includes_latency_line and the runner._print_summary
invocation and the overall fields mean_latency_ms and p95_latency_ms so the test
fails if labels are swapped or incorrect.

---

Nitpick comments:
In `@benchmarks/locomo_runner.py`:
- Around line 1108-1124: The p95 index computation uses an unnecessary int()
around math.floor in the expression computing p95_idx; update the p95_idx
calculation (the line computing p95_idx using math.floor) to remove the
redundant int() so it simply uses math.floor(...) (or better, compute p95_idx =
min(math.floor(0.95 * n), n - 1)) while keeping the use of latencies, p95_lat,
and mean_lat unchanged.

In `@taosmd/http_server.py`:
- Around line 54-57: The documentation for the single-ingest request is
ambiguous about where the "user metadata dict" belongs; update the single-ingest
endpoint docs in taosmd/http_server.py to explicitly show the request JSON
schema (mirroring the batch endpoint) — i.e. the body should be {"text": str,
"agent": str, "project"?: str, "metadata"?: {"forget_after"?: float,
"forget_reason"?: str}} — and ensure the single-ingest handler (the function
that parses the single-item POST) expects and validates a top-level "metadata"
object with optional forget_after and forget_reason fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1928af9d-97a0-4fbf-8c6c-769167078a3d

📥 Commits

Reviewing files that changed from the base of the PR and between 357ebb5 and c0956a7.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • benchmarks/locomo_runner.py
  • taosmd/http_server.py
  • taosmd/vector_memory.py
  • tests/test_locomo_memscore.py
  • tests/test_ttl_filter.py

Comment on lines +1198 to +1205
# Three-number performance summary (accuracy / latency / context size).
# Never collapsed to a single score \u2014 each dimension is independent signal.
print(
f"Latency p50={overall.get('mean_latency_ms', 0.0):.0f} ms "
f"p95={overall.get('p95_latency_ms', 0.0):.0f} ms | "
f"Context ~{overall.get('mean_context_tokens', 0.0):.0f} tok/query (mean)"
)
print(sep)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: "p50" label is incorrect — you're printing the mean, not the median.

Line 1201 labels mean_latency_ms as "p50", but p50 is the median (50th percentile), not the arithmetic mean. _summary computes mean_lat = sum(latencies) / n, which is the mean. Either compute the actual p50 and print it, or relabel the output as "mean".

🔧 Recommended fix: relabel as "mean"
     print(
-        f"Latency  p50={overall.get('mean_latency_ms', 0.0):.0f} ms  "
+        f"Latency  mean={overall.get('mean_latency_ms', 0.0):.0f} ms  "
         f"p95={overall.get('p95_latency_ms', 0.0):.0f} ms  |  "
         f"Context ~{overall.get('mean_context_tokens', 0.0):.0f} tok/query (mean)"
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/locomo_runner.py` around lines 1198 - 1205, The printed label
"p50" is incorrect because the code is using overall.get('mean_latency_ms', ...)
which is the arithmetic mean; update the print f-string in
benchmarks/locomo_runner.py (the block that prints latency using
overall.get('mean_latency_ms', ...) and overall.get('p95_latency_ms', ...)) to
relabel "p50=" as "mean=" (or "mean_latency=") so it accurately reflects the
metric, leaving the p95 line unchanged.

Comment on lines +176 to +184
def test_print_summary_includes_latency_line():
runner = _load_runner()
overall = _make_overall()
meta = _make_meta()
out = _capture(lambda: runner._print_summary(meta, {}, overall))
assert "Latency" in out or "latency" in out.lower()
assert "p95" in out or "P95" in out
assert "1200" in out or "1200.0" in out # mean latency
assert "2500" in out or "2500.0" in out # p95

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test gap: does not verify label correctness.

test_print_summary_includes_latency_line checks that "1200" appears (the value of mean_latency_ms) but does not verify whether the label says "mean" or "p50". Since _print_summary currently mislabels mean as "p50", this test passes despite the bug. Consider asserting that the label correctly identifies the metric type.

🧪 Suggested assertion to catch label bugs
 def test_print_summary_includes_latency_line():
     runner = _load_runner()
     overall = _make_overall()
     meta = _make_meta()
     out = _capture(lambda: runner._print_summary(meta, {}, overall))
     assert "Latency" in out or "latency" in out.lower()
     assert "p95" in out or "P95" in out
     assert "1200" in out or "1200.0" in out  # mean latency
     assert "2500" in out or "2500.0" in out  # p95
+    # Verify the label for 1200 is "mean", not "p50" (since overall["mean_latency_ms"]=1200)
+    assert "mean=" in out.lower() or "mean:" in out.lower()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_locomo_memscore.py` around lines 176 - 184, The test
test_print_summary_includes_latency_line should also assert that the printed
latency line uses the correct label for the mean latency instead of a p50 label;
update the test to call runner._print_summary(meta, {}, overall) (as it does)
and assert that the output contains the expected label for mean latency (e.g.,
"mean" or "mean latency") and that the p95 value is labeled appropriately (e.g.,
"p95"), referencing the test name test_print_summary_includes_latency_line and
the runner._print_summary invocation and the overall fields mean_latency_ms and
p95_latency_ms so the test fails if labels are swapped or incorrect.

@jaylfc
jaylfc merged commit 3cf58a8 into master Jun 11, 2026
1 of 2 checks passed
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.

1 participant