Skip to content

feat(palace): add MemoryProvider::recent for retention-ranked recall (mp-migration 4/8)#21

Merged
quangdang46 merged 1 commit into
mainfrom
feat/mp-pr4-recent
Jun 3, 2026
Merged

feat(palace): add MemoryProvider::recent for retention-ranked recall (mp-migration 4/8)#21
quangdang46 merged 1 commit into
mainfrom
feat/mp-pr4-recent

Conversation

@quangdang46
Copy link
Copy Markdown
Owner

Summary

Add MemoryProvider::recent(limit, scope) — the retention-ranked recall used by jcode's recall --mode recent and the L1 wake-up layer.

async fn recent(
    &self,
    limit: usize,
    scope: Option<&SearchScope>,
) -> anyhow::Result<Vec<SearchHit>>
where Self: Sized;

Returns the top-N SearchHits scored by:

score = (access_count + 1) / (1 + days_since_last_accessed)

Drawers that have never been accessed fall back to "now" with access_count = 0.

Motivation

Required for the jcode → mempalace Mode C library migration (mp-migration 4/8). jcode has two call sites that need this:

  1. MemoryTool::recall with mode = "recent" (no query) — tool/memory.rs:174-198
  2. L1 wake-up layer in crates/jcode-base/src/memory.rs:856-880 (get_prompt_memories_scoped) — top-N most-relevant memories loaded into every prompt.

Both today compute a retention score by walking the full drawer list and sorting. With recent() on the trait, the adapter can delegate in one call.

This is PR 4/8 in the migration series. Independent of PRs 1/2/7recent only reads metadata["access_count"] and metadata["last_accessed"], doesn't require the typed fields or the mutation methods.

Change

crates/core/src/palace.rs — adds 1 default method to MemoryProvider (after get_drawers, before closing } of the trait). ~70 lines of logic + 30 lines of docs.

async fn recent(
    &self,
    limit: usize,
    scope: Option<&SearchScope>,
) -> anyhow::Result<Vec<SearchHit>>
where
    Self: Sized,
{
    // ... 70 lines of: pull → score → sort → trim → map to SearchHit
}

The scoring formula mirrors crate::retention::calculate_retention but operates on metadata fields rather than the typed Memory struct (which is the consolidate-pipeline's Memory, not the trait's Drawer). When the consolidation pipeline is wired to the trait, this default can be replaced with a direct call to kg.helpfulness_score(id).

Performance

  • O(n) for embedvec (≤5 k drawers): one get_drawers call + in-memory sort.
  • Pulls up to 4096 drawers (or limit.max(64) when a scope is set), then trims to limit after ranking.
  • For larger palaces (usearch, lancedb) override with an indexed path that scores in the store.

Test plan

  • cargo test -p mempalace-core --lib1127 passed, 0 failed (282s)
  • cargo check -p mempalace-core clean
  • cargo fmt --check -p mempalace-core clean
  • CI green on ubuntu/macos/windows

Series status

# Title Status
6 docs(palace): document Embedder reuse on search_with_embedding merged #16
7 feat(palace): add tags/trust/access_count/reinforcements/superseded_by to Drawer open #17
8 feat(palace): add MemoryProvider::graph_stats_legacy (jcode shape) open #18
1 feat(palace): add boost/decay/reinforce/supersede/set_metadata to MemoryProvider open #19
2 feat(palace): add tag/untag/link/list_tags to MemoryProvider open #20
4 feat(palace): add MemoryProvider::recent for retention-ranked recall this PR
3 feat(palace): add MemoryScope::All and Wing/Room variants next
5 feat(palace): add ActivityEvent sink on PalaceBuilder next

🤖 Generated with Claude Code

jcode's `recall --mode recent` (no query) and the L1 wake-up
layer in jcode's ambient runner both need the same capability:
"give me the N most retention-relevant drawers" — the drawers
that have been accessed recently AND frequently, with Ebbinghaus-
style decay applied.

Add a default-implemented `recent(limit, scope)` method on
MemoryProvider that returns the top-N `SearchHit`s scored by:

    score = (access_count + 1) / (1 + days_since_last_accessed)

The default reads `metadata["access_count"]` and
`metadata["last_accessed"]` (set by the boost/touch path in
PR #1), pulls drawers via `get_drawers`, ranks, and trims to
`limit`. Scope filtering is respected (wing/room constraints
flow through to get_drawers).

Implementations that have a wired KG (mp-020 sub-task) should
override and use `kg.helpfulness_score(id)` — which already
factors in episodic memory feedback — for strictly better
ranking than the metadata-only default.

This is PR 4/8 in the jcode → mempalace Mode C library migration
series. No dependency on PRs 1/2/7 (recent only reads metadata,
doesn't mutate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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