Skip to content

feat: add Phase 5D memory scoring and pruning - #111

Merged
besfeng23 merged 2 commits into
mainfrom
phase-5d-memory-usefulness-pruning
Jun 29, 2026
Merged

feat: add Phase 5D memory scoring and pruning#111
besfeng23 merged 2 commits into
mainfrom
phase-5d-memory-usefulness-pruning

Conversation

@besfeng23

@besfeng23 besfeng23 commented Jun 29, 2026

Copy link
Copy Markdown
Owner

1. Phase 5D summary

Phase 5D adds deterministic, model-free memory usefulness scoring, review-first stale-memory pruning, and confidence-weighted retrieval ranking. It is safe-by-default and review-first:

  • Scoring is deterministic (no embeddings, no model calls). Secrets are always blocked to a zero score and never retained.
  • retrieval_weight = usefulness*0.40 + confidence*0.25 + freshness*0.20 + feedback*0.10 − contradiction*0.20, clamped to 0..1. Per-category freshness half-lives mean durable preferences decay slowly while operational/transient details decay fast — so current production facts outrank stale ones and durable preferences outrank transient logs.
  • Pruning is review-only: it produces keep / stale / superseded / low_value / unsafe / duplicate recommendations and never hard-deletes. High-confidence durable memories are protected; newer same-subject verified facts supersede older ones.
  • Retrieval re-ranks recent events by retrieval_weight with a null-safe fallback for unscored rows. Deterministic structured retrieval stays primary; semantic/embeddings/model gates remain disabled.
  • Namespaces stay isolated: AU/story memory can never contaminate real_life scoring, pruning, or retrieval.
  • New gates are optional and safe-defaulted, and are catalogued as managed-but-not-required so they cannot reintroduce the prior RED Env Broker drift false-positive.

2. Files changed (12, +1031 / −5)

New:

  • lib/services/memory-usefulness-scoring-service.ts — deterministic scoring (scoreMemoryUsefulness, scoreFreshness, scoreFeedback, scoreContradictionRisk, computeRetrievalWeight, classifyStaleness, labels + ranking helpers)
  • lib/services/memory-pruning-service.tsevaluatePruningRecommendation, findPruningCandidates, runPhase5dMaintenance
  • lib/config/phase-5d-config.ts — optional gate resolution + gates summary
  • app/api/memory/jobs/phase-5d-maintenance/route.ts — internal-job-token maintenance job (dry-run by default)
  • app/api/admin/memory/phase-5d/status/route.ts — operator-protected status route
  • supabase/migrations/20260629000000_phase_5d_memory_scoring.sql — additive migration
  • tests/phase5d-memory-scoring.test.ts — 19 tests

Edited (additive, non-breaking):

  • lib/services/memory-hybrid-retrieval-service.ts — confidence-weighted ranking of recent events
  • lib/services/admin-memory-candidate-loader.ts + app/admin/memory/candidates/page.tsx — usefulness/freshness labels in candidate review
  • lib/services/env-discovery-service.ts + lib/services/env-validation-service.ts — catalog Phase 5D gates as managed/optional and classify _MODE/_VERSION config keys

3. Migration filename

supabase/migrations/20260629000000_phase_5d_memory_scoring.sql — fully additive (nullable score columns on memory_events and memory_capture_candidates; new review-only memory_pruning_candidates table with user-scoped RLS and namespace check). No drops, no deletes.

4. Test results

  • Phase 5D: 19/19 passing (covers all 14 required cases: 0..1 clamping, secret-blocking, durable > transient, current > old production, stale→candidate-not-deleted, supersede marking, AU↔real_life isolation, null-score fallback, gate preservation, dry-run non-mutation, unauthorized rejection, redacted summary, and Env Broker GREEN no-regression).
  • Full suite: 546/546 (84 files).
  • npm run typecheck ✓ (exit 0) · npm run lint ✓ (0 errors) · npm run build ✓ (both routes registered) · npm run env:policy ✓ (89 keys).

5. Production deploy required?

Not from this PR by itself — no runtime behavior changes until the migration is applied and the optional scoring gate is enabled. A redeploy is part of the post-merge steps below so the new routes and migration take effect. Default behavior writes nothing and mutates no memory.

6. Exact post-merge steps

  1. Merge this PR into main.
  2. Apply the Supabase migration 20260629000000_phase_5d_memory_scoring.sql.
  3. Redeploy production (deploy main; wait for READY).
  4. Run Phase 5D dry-run maintenance for real_life:
    POST /api/memory/jobs/phase-5d-maintenance with the internal job token, body { "dryRun": true, "namespace": "real_life" }.
  5. Run Phase 5D dry-run maintenance for au:
    POST /api/memory/jobs/phase-5d-maintenance with the internal job token, body { "dryRun": true, "namespace": "au" }.
  6. Optionally enable PANDORA_ENABLE_MEMORY_USEFULNESS_SCORING=true (the Env Broker stays GREEN — this flag is optional, not a required provider env).
  7. Keep pruning review-only — leave PANDORA_ENABLE_MEMORY_PRUNING unset/false and PANDORA_MEMORY_PRUNING_MODE=review_only unless pruning is explicitly approved.

Guardrails for this rollout: do not enable pruning by default, do not mutate production memory yet (dry-run only), and do not print or expose secrets (the job uses the internal token from env only and returns redacted summaries with rawTokenReturned-style safety; no raw values are rendered).

Checklist

  • Does this PR add or modify env vars? Yes — 4 optional Phase 5D flags (PANDORA_ENABLE_MEMORY_USEFULNESS_SCORING, PANDORA_ENABLE_MEMORY_PRUNING, PANDORA_MEMORY_PRUNING_MODE, PANDORA_MEMORY_SCORING_VERSION).
  • If yes, were they added to Env Broker catalog? Yes — catalogued as managed with safe defaults, and not required provider envs (cannot trigger RED drift).
  • Are secrets server-only? Yes — the job/operator tokens are read from env only; summaries are redacted; no raw secrets are logged or returned.
  • Are NEXT_PUBLIC values public-safe only? Yes — no NEXT_PUBLIC_ values are added or modified.

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new memory review and scoring experience, including richer candidate details and status reporting.
    • Introduced a safer, review-first maintenance workflow with dry-run support and authenticated status checks.
  • Bug Fixes

    • Improved memory ranking and staleness handling so recent, more relevant items surface more consistently.
    • Added safeguards to keep sensitive content from appearing in results or maintenance summaries.
  • Chores

    • Updated configuration handling and environment discovery for the new memory features.

Phase 5D adds deterministic, model-free memory usefulness scoring, review-first
stale-memory pruning, and confidence-weighted retrieval ranking. Safe-by-default
and review-first: scoring/pruning are gated off by default, pruning never
hard-deletes, and namespaces stay isolated (AU never contaminates real_life).

- Scoring service: usefulness/freshness/feedback/contradiction factors combine into
  a clamped 0..1 retrieval_weight. Secrets are always blocked to zero and never
  retained. Durable preferences outrank transient logs; current production facts
  outrank stale ones.
- Pruning service: produces keep/stale/superseded/low_value/unsafe/duplicate
  recommendations (review-only) plus a non-destructive maintenance runner.
- Retrieval: recent events are ranked by retrieval_weight with null-safe fallback;
  semantic/embeddings/model gates remain disabled and deterministic retrieval stays
  primary.
- Candidate review UI shows plain usefulness/freshness labels.
- Additive migration: nullable score columns on memory_events/candidates plus a
  review-only memory_pruning_candidates table.
- Optional gates (scoring/pruning/mode/version) are catalogued as managed but never
  required provider envs, so they cannot reintroduce the RED drift false-positive.
- Admin status route and internal-token maintenance job (dry-run by default).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013izDrMYfyugjRhhNpL3dJW
@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
memory Ready Ready Preview, Comment Jun 29, 2026 1:57am

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@besfeng23, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 42 minutes and 55 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba8e5b65-eb1c-419f-b6dd-9f0d402cac4d

📥 Commits

Reviewing files that changed from the base of the PR and between 4b387e2 and 0220451.

📒 Files selected for processing (7)
  • app/api/admin/memory/phase-5d/status/route.ts
  • app/api/memory/jobs/phase-5d-maintenance/route.ts
  • lib/services/memory-hybrid-retrieval-service.ts
  • lib/services/memory-pruning-service.ts
  • lib/services/memory-usefulness-scoring-service.ts
  • supabase/migrations/20260629000000_phase_5d_memory_scoring.sql
  • tests/phase5d-memory-scoring.test.ts
📝 Walkthrough

Walkthrough

Introduces Phase 5D memory lifecycle management: a deterministic usefulness scoring engine, review-first pruning recommendation service, additive database migration, two new API routes (maintenance job and admin status), env-broker registration for Phase 5D flags, hybrid retrieval ranking by retrieval weight, and admin UI display of scoring fields.

Changes

Phase 5D Memory Usefulness Scoring & Review-First Pruning

Layer / File(s) Summary
Usefulness scoring engine
lib/services/memory-usefulness-scoring-service.ts
Defines ScoreableMemory, RetrievalWeightBreakdown, MemoryScoreSummary and all deterministic per-factor scoring functions (classifyContent, computeRetrievalWeight, classifyStaleness, isProtectedMemory, summarizeMemoryScore, rankMemoriesByRetrievalWeight).
Pruning recommendation logic and maintenance runner
lib/services/memory-pruning-service.ts
Defines PruningCandidate types, evaluatePruningRecommendation ordered decision flow, findPruningCandidates namespace-grouped evaluation, and runPhase5dMaintenance which optionally writes scoring fields back to memory_events and inserts into memory_pruning_candidates.
Phase 5D config, env discovery, and validation
lib/config/phase-5d-config.ts, lib/services/env-discovery-service.ts, lib/services/env-validation-service.ts
Adds Phase5dConfig with hard-coerced pruningMode: "review_only", registers Phase 5D optional env keys in discovery, and adds _VERSION/_MODE as public_safe env key suffixes.
Database migration
supabase/migrations/20260629000000_phase_5d_memory_scoring.sql
Adds nullable scoring columns to memory_events and memory_capture_candidates, creates memory_pruning_candidates table with RLS and indexes.
Scored admin candidate loader and hybrid retrieval
lib/services/admin-memory-candidate-loader.ts, lib/services/memory-hybrid-retrieval-service.ts
listMemoryCandidatesForAdmin now returns ScoredMemoryCaptureCandidate with attached score; getHybridMemoryContext ranks recent_events by retrieval weight.
Maintenance job and status API routes
app/api/memory/jobs/phase-5d-maintenance/route.ts, app/api/admin/memory/phase-5d/status/route.ts
Adds POST maintenance job route (Zod-validated, bearer-auth, calls runPhase5dMaintenance) and GET admin status route (queries pruning candidate counts and last scoring timestamp).
Admin candidates UI
app/admin/memory/candidates/page.tsx
Adds a "usefulness" section displaying score.labels, score.retrieval_weight, and score.stale_status to the candidate details card.
Phase 5D test suite
tests/phase5d-memory-scoring.test.ts
Covers scoring clamping, secret blocking, retrieval ranking, pruning recommendations, namespace isolation, null-safe ranking, env-broker safety, maintenance dry-run non-mutation, secret redaction, auth rejection, and route-level tests.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • besfeng23/Memory#103: Introduced the Phase 4C hybrid retrieval context that getHybridMemoryContext builds on; this PR extends it to rank recent_events by Phase 5D retrieval weight.

Suggested labels

codex

🐇 A rabbit scored each memory's worth,
From secret tokens blocked at birth,
Ranked by freshness, weight, and care—
No hard deletes, just "review" there.
Phase 5D hops with review-first flair! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: Phase 5D memory scoring and pruning.
Description check ✅ Passed The description matches the template and includes the required env-var checklist with clear answers.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase-5d-memory-usefulness-pruning

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b387e2feb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
// Safe-by-default: any run without an explicit dryRun:false stays a non-destructive dry run.
const dryRun = parsed.data.dryRun ?? parsed.data.dry_run ?? true;
const user_id = parsed.data.user_id ?? process.env.PANDORA_MEMORY_BRIDGE_USER_ID;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Derive the maintenance user on the server

Because this route uses the bridge admin client below, accepting parsed.data.user_id lets any holder of PANDORA_INTERNAL_JOB_TOKEN choose an arbitrary account and, with dryRun:false, update that user's memory_events and insert pruning candidates. This bypasses the server/RLS-derived identity requirement for memory writes; restrict the job to the configured bridge user or derive the user from an authenticated operator/session instead of the request body.

Useful? React with 👍 / 👎.

Comment thread lib/services/memory-pruning-service.ts Outdated
Comment on lines +233 to +235
const upd = await client
.from("memory_events")
.update({ usefulness_score: breakdown.usefulness_score, confidence_score: breakdown.confidence_score, freshness_score: breakdown.freshness_score, contradiction_score: breakdown.contradiction_score, retrieval_weight: breakdown.retrieval_weight, stale_status: stale, scoring_version: config.scoringVersion, scored_at: new Date(now).toISOString() })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Append patch/audit records for score writes

When dryRun:false and PANDORA_ENABLE_MEMORY_USEFULNESS_SCORING=true, this directly changes persisted memory_events metadata (retrieval_weight, stale_status, scored_at, etc.) but the maintenance path never appends corresponding memory_patches or audit_logs records. The memory browser/proof flow relies on those patch/audit rows to explain persisted changes, so Phase 5D score writes become untraceable once the gate is enabled.

Useful? React with 👍 / 👎.

Comment on lines +247 to +248
.from("memory_pruning_candidates")
.insert({ user_id: scope.user_id, namespace: scope.namespace, memory_id: candidate.memory_id, pruning_category: candidate.category, recommendation: candidate.recommendation, reason: candidate.reason, stale_status: candidate.stale_status, retrieval_weight: candidate.retrieval_weight, superseded_by_memory_id: candidate.superseded_by_memory_id ?? null, scoring_version: config.scoringVersion, status: "open" })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make pruning candidate writes idempotent

When pruning is enabled, each non-dry-run maintenance call executes a blind insert for every candidate, and the migration adds no unique constraint/upsert to collapse (user_id, namespace, memory_id, pruning_category) duplicates. Re-running the job after a retry or schedule will create multiple open review rows for the same stale/unsafe memory, so the review queue is no longer idempotent.

Useful? React with 👍 / 👎.

@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: 9

🤖 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 `@app/api/admin/memory/phase-5d/status/route.ts`:
- Around line 23-36: The status aggregation in the route handler is only
counting stale, superseded, and low_value candidates, so it drops unsafe and
duplicate pruning categories from the admin response. Update the totals building
logic in the memory phase-5d status route to include every category emitted by
the pruning service, especially unsafe and duplicate, and make sure the response
object and any related type definitions in this handler reflect the added
counts.
- Around line 37-39: The scoring summary logic in the status route is only
checking a single row, so `totals.scored` is not an actual count and
`lastScoringRun` may come from a null value. Update the query in the status
handler to fetch the latest non-null `scored_at` separately for
`lastScoringRun`, and use a count-based query for scored rows in the same
`route.ts` flow so `totals.scored` reflects the exact number of scored events.

In `@lib/services/memory-hybrid-retrieval-service.ts`:
- Around line 6-10: The recent events query in memory-hybrid-retrieval-service
is not selecting the fields used by rankMemoriesByRetrievalWeight, so ranking
falls back to incomplete scores. Update the memory_events select in the hybrid
retrieval flow to include the Phase 5D ranker inputs it reads, especially
retrieval_weight, confidence, freshness timestamps, feedback counts, and
contradiction markers, so recent_events are ordered using the intended weights
instead of defaults.

In `@lib/services/memory-pruning-service.ts`:
- Around line 246-250: The insert path in memory-pruning-service.ts is creating
duplicate open rows for the same memory/category on repeated runs. Update the
logic around the client.from("memory_pruning_candidates").insert(...) flow to
first check for an existing open candidate for the same user_id, namespace,
memory_id, and pruning_category, or switch to a uniqueness-backed upsert so only
one open row exists per candidate. Keep the fix localized to the pruning
candidate creation path and preserve the existing select("*").single() behavior
for the inserted/updated row.
- Around line 79-81: The superseding check in memory-pruning-service’s
sameNamespacePeers.find currently compares created_at as strings, which can let
null or invalid timestamps win over real ones. Update the logic in the newer
selection so it only considers peers with valid created_at values and compares
actual timestamps (or parsed dates) before treating a production_fact as newer
than memory. Keep the change localized to the find predicate and the created_at
handling around classifyContent and projectSubject.

In `@lib/services/memory-usefulness-scoring-service.ts`:
- Around line 75-81: Move the structured `memory_type` checks in
`classifyContent` ahead of the blank-text early return so empty text still
respects typed memories. In `memory-usefulness-scoring-service.ts`, make sure
`secret_or_credential` is classified as `"secret"` and other typed categories
are evaluated before `raw.trim()` can return `"general"`, so `attachScore()` in
`admin-memory-candidate-loader.ts` will correctly keep blocked secrets at zero
usefulness.

In `@supabase/migrations/20260629000000_phase_5d_memory_scoring.sql`:
- Line 44: The `memory_pruning_candidates.stale_status` column is missing the
same restricted `stale_status` constraint used elsewhere, so update the Phase 5D
migration to enforce the app’s `StaleStatus` contract instead of allowing
free-form text. Apply the same constraint pattern already used for the other
Phase 5D columns in this migration, and keep the change localized to the
`memory_pruning_candidates` table definition so the schema stays consistent.

In `@tests/phase5d-memory-scoring.test.ts`:
- Around line 227-243: The mock cleanup in the phase-5d maintenance test is not
guaranteed to run if the import or assertions fail, leaving
`PANDORA_INTERNAL_JOB_TOKEN`, `vi.doMock`, and `@/lib/supabase/bridge-admin`
state polluted for later tests. Wrap the test body around the `route.POST`
import/assertions in a try/finally and move the env restore,
`vi.doUnmock("`@/lib/supabase/bridge-admin`")`, and `vi.resetModules()` into the
finally block so teardown always runs.
- Around line 155-163: The test is mutating process.env directly and restoring
it inline, which can leak state if an assertion throws and can leave absent keys
present after cleanup. Update the affected test in getHybridMemoryContext to
save whether each PANDORA_ENABLE_* key existed, restore the previous values
inside a finally block, and delete keys that were originally absent; apply the
same cleanup pattern to the other process.env.PANDORA_INTERNAL_JOB_TOKEN
mutations in the suite.
🪄 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

Run ID: 7b269a28-ff66-481e-a65e-64a6e4039c24

📥 Commits

Reviewing files that changed from the base of the PR and between e34662d and 4b387e2.

📒 Files selected for processing (12)
  • app/admin/memory/candidates/page.tsx
  • app/api/admin/memory/phase-5d/status/route.ts
  • app/api/memory/jobs/phase-5d-maintenance/route.ts
  • lib/config/phase-5d-config.ts
  • lib/services/admin-memory-candidate-loader.ts
  • lib/services/env-discovery-service.ts
  • lib/services/env-validation-service.ts
  • lib/services/memory-hybrid-retrieval-service.ts
  • lib/services/memory-pruning-service.ts
  • lib/services/memory-usefulness-scoring-service.ts
  • supabase/migrations/20260629000000_phase_5d_memory_scoring.sql
  • tests/phase5d-memory-scoring.test.ts

Comment thread app/api/admin/memory/phase-5d/status/route.ts Outdated
Comment thread app/api/admin/memory/phase-5d/status/route.ts Outdated
Comment thread lib/services/memory-hybrid-retrieval-service.ts Outdated
Comment thread lib/services/memory-pruning-service.ts Outdated
Comment thread lib/services/memory-pruning-service.ts
Comment thread lib/services/memory-usefulness-scoring-service.ts Outdated
Comment thread supabase/migrations/20260629000000_phase_5d_memory_scoring.sql Outdated
Comment thread tests/phase5d-memory-scoring.test.ts
Comment thread tests/phase5d-memory-scoring.test.ts Outdated
- Maintenance job derives the user from server config only (no body user_id), so a
  job-token holder can never target another account's memory writes (P1).
- Score writes append an audit_logs trail for traceability (memory_patches is FK-bound
  to memory_items, so audit_logs is the correct table for memory_events) (P1).
- Pruning candidate inserts are idempotent: existence check + partial unique index on
  open (user, namespace, memory_id, category) so re-runs don't inflate the queue.
- classifyContent checks structured memory_type before the blank-text fast path, keeping
  redacted blocked_secret candidates at zero usefulness.
- Supersede check parses timestamps instead of string-comparing created_at.
- Hybrid retrieval selects the score fields the ranker reads.
- Status route reports all pruning categories (incl. unsafe/duplicate) and an exact
  scored count from non-null scored_at.
- Migration: stale_status CHECK constraint + partial unique index on open candidates.
- Tests: env/mocks teardown moved into finally; added an idempotency test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013izDrMYfyugjRhhNpL3dJW
@besfeng23
besfeng23 merged commit da88f5f into main Jun 29, 2026
5 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.

2 participants