Conversation
Implements the K/E decoupling pattern from arxiv:2602.05665 §III-C and §V-E: static reference data (profile, preferences, entities, patterns) and trajectory data (events, cases) now travel separately through decay and retrieval without splitting storage. Changes: - classifyMemoryType() in src/memory-categories.ts — deterministic 6-cat (+ legacy) → knowledge/experience mapping - memory_type threaded through SmartMemoryMetadata with lazy-backfill on legacy entries in parseSmartMetadata - DecayEngine applies per-type half-life multipliers (knowledge 3.0x, experience 0.7x by default; set both to 1.0 to disable) - analyzeIntent returns memoryType hint; new "experience" rule (last time / 上次 / 之前) routes trajectory queries - applyMemoryTypeBoost() promotes matching-type results after the existing category boost in the auto-recall path - memory_recall tool accepts type: "knowledge" | "experience" | "both" - openclaw.plugin.json schema exposes the two multipliers Backward-compat: old memories backfill on read; unconfigured callers keep working (type defaults to knowledge, which decays slower — safer than losing data). Also registers the pre-existing test/hook-dedup-phase1.test.mjs in verify-ci-test-manifest.mjs EXPECTED_BASELINE (was in the runtime manifest but missing from the baseline check, so `npm test` was failing the manifest-verify step on master). Verified: 197/197 tests pass (npm test, full suite). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 73e4b667f5
ℹ️ 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".
| const typeFilter: MemoryType | undefined = | ||
| type === "knowledge" || type === "experience" ? type : undefined; | ||
| const results = typeFilter | ||
| ? rawResults.filter( |
There was a problem hiding this comment.
Apply type filter before enforcing recall limit
memory_recall now filters by type only after retrieval has already been capped by limit, so mixed-type top hits can crowd out matching items and produce too few (or zero) results even when relevant memories exist just below the cutoff. This is reproducible when callers pass type="knowledge" or type="experience" with small limits (default 3): the tool reports no matches although the store contains matches in later ranks. Fetching a larger candidate set when type is specified (then filtering and truncating) would preserve expected filter semantics.
Useful? React with 👍 / 👎.
|
This PR currently has merge conflicts with the base branch (
Please:
Once that's done, the review pipeline will pick it up automatically on the next scan and do a full pass. Thanks for your patience. |
rwmjhb
left a comment
There was a problem hiding this comment.
Approved based on orchestrator review results. Non-blocking follow-ups were noted separately; no merge-blocking issues from this review.
Summary
Implements the knowledge / experience decoupling pattern from Graph-based Agent Memory: Taxonomy, Techniques, and Applications (arxiv:2602.05665 §III-C, §V-E).
profile/preferences/entities/patterns)events/cases)The plugin already did this partially on the write side (
profilealways-merges,events/casesappend-only). This PR threads the same split through decay and retrieval without splitting storage.What this changes
classifyMemoryType()insrc/memory-categories.ts, stored asmetadata.memory_typewith lazy backfill on readknowledgeHalfLifeMultiplier(default 3.0) orexperienceHalfLifeMultiplier(default 0.7) per memoryanalyzeIntentnow returns amemoryTypehint; new "experience" rule catches上次/之前/last time/when we ...applyCategoryBoostapplyMemoryTypeBoost(1.15x) for type-matched hits in auto-recallquery / scope / categorytype: "knowledge" | "experience" | "both"post-filterdecay.*Multiplierkeys inopenclaw.plugin.jsonschemaDecoupling sits at the semantic layer, not the storage layer — no new tables, same LanceDB
memories, same scopes. Matches this plugin's existing pattern of extending via JSONmetadatarather than schema changes.Test plan
test/knowledge-experience-decoupling.test.mjs— 18 subtests covering classifier mapping,parseSmartMetadatabackfill, per-type half-life, intent routing (EN + 中文),applyMemoryTypeBoostcore-regressioninscripts/ci-test-manifest.mjs+verify-ci-test-manifest.mjsnpm testlocally: 197/197 passcli-smoke,core-regression,storage-and-schema,llm-clients-and-auth,packaging-and-workflow— all greenBackward compatibility
memory_typeabsent in metadata →parseSmartMetadatabackfills on read frommemory_category(or legacycategory). No migration needed; nomemory-pro upgraderequired.typeparam onmemory_recallis optional; schema is additive (additionalProperties: falsestill holds because multipliers are declared).Incidental fix
test/hook-dedup-phase1.test.mjswas inCI_TEST_MANIFESTbut missing fromEXPECTED_BASELINEinverify-ci-test-manifest.mjs, sonpm testonmasterwas already failing its manifest-verify step. Bundled the one-line fix because this PR also touches the baseline.Files touched
src/memory-categories.ts—MemoryType+classifyMemoryType()src/smart-metadata.ts—memory_typefield + backfill +LifecycleMemorypropagationsrc/decay-engine.ts— per-type half-life multiplierssrc/intent-analyzer.ts—memoryTypeonIntentSignal+ new experience rule +applyMemoryTypeBoost()src/tools.ts—typeparam onmemory_recallindex.ts— wiredapplyMemoryTypeBoostafterapplyCategoryBoostin auto-recall;PluginConfig.decaytypeopenclaw.plugin.json— schema + UI labels for the two multiplierstest/knowledge-experience-decoupling.test.mjs— newscripts/ci-test-manifest.mjs+verify-ci-test-manifest.mjs— register new test + hook-dedup baseline fix