clawmem 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -461,6 +461,8 @@ Recency intent detected ("latest", "recent", "last session"):
461
461
  compositeScore = (0.10 × searchScore + 0.70 × recencyScore + 0.20 × confidenceScore) × qualityMultiplier × coActivationBoost
462
462
  ```
463
463
 
464
+ **`query` tool (v0.13.0+):** for non-recency queries the `query` tool uses retrieval-tuned weights `0.70 × searchScore + 0.15 × recencyScore + 0.15 × confidenceScore` (from a held-out judged eval). Recency intent still switches to the `0.10 / 0.70 / 0.20` weights above. `search`, `vsearch`, `memory_retrieve`, and the context-surfacing hook keep the `0.50 / 0.25 / 0.25` default.
465
+
464
466
  | Content Type | Half-Life | Effect |
465
467
  |--------------|-----------|--------|
466
468
  | decision, deductive, preference, hub | ∞ | Never decay |
package/CLAUDE.md CHANGED
@@ -461,6 +461,8 @@ Recency intent detected ("latest", "recent", "last session"):
461
461
  compositeScore = (0.10 × searchScore + 0.70 × recencyScore + 0.20 × confidenceScore) × qualityMultiplier × coActivationBoost
462
462
  ```
463
463
 
464
+ **`query` tool (v0.13.0+):** for non-recency queries the `query` tool uses retrieval-tuned weights `0.70 × searchScore + 0.15 × recencyScore + 0.15 × confidenceScore` (from a held-out judged eval). Recency intent still switches to the `0.10 / 0.70 / 0.20` weights above. `search`, `vsearch`, `memory_retrieve`, and the context-surfacing hook keep the `0.50 / 0.25 / 0.25` default.
465
+
464
466
  | Content Type | Half-Life | Effect |
465
467
  |--------------|-----------|--------|
466
468
  | decision, deductive, preference, hub | ∞ | Never decay |
package/README.md CHANGED
@@ -814,7 +814,7 @@ User Query + optional intent hint
814
814
  → Intent-Aware Chunk Selection (intent terms at 0.5× weight alongside query terms at 1.0×)
815
815
  → Cross-Encoder Reranking (4000 char context; intent prepended; chunk dedup; batch cap=4)
816
816
  → Rerank/RRF Blend (blendRerank: 0.9·reranker + 0.1·normalized-RRF tiebreaker; reranker can promote over RRF #1; falls back to RRF order if reranker unavailable)
817
- SAME Composite Scoring ((search × 0.5 + recency × 0.25 + confidence × 0.25) × qualityMultiplier × lengthNorm × coActivationBoost + pinBoost)
817
+ → Composite Scoring (query-tuned: (search × 0.70 + recency × 0.15 + confidence × 0.15) × qualityMultiplier × lengthNorm × coActivationBoost + pinBoost; recency-intent queries → 0.10/0.70/0.20; other tools keep the 0.50/0.25/0.25 default)
818
818
  → MMR Diversity Filter (Jaccard bigram similarity > 0.6 → demoted)
819
819
  → Ranked Results
820
820
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmem",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "On-device memory layer for AI agents. Claude Code, OpenClaw, and Hermes. Hooks + MCP server + hybrid RAG search.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/mcp.ts CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  import {
29
29
  applyCompositeScoring,
30
30
  hasRecencyIntent,
31
+ QUERY_WEIGHTS,
31
32
  type EnrichedResult,
32
33
  type CoActivationFn,
33
34
  } from "./memory.ts";
@@ -807,7 +808,10 @@ This is the recommended entry point for ALL memory queries.`,
807
808
 
808
809
  const coFn = (path: string) => store.getCoActivated(path);
809
810
  const enriched = enrichResults(store, searchResults, query);
810
- let scored = applyCompositeScoring(enriched, query, coFn)
811
+ // Phase B (§11.12): the `query` tool's hybrid+rerank pipeline uses QUERY_WEIGHTS (search 0.70)
812
+ // the eval-validated re-weight. Recency intent still wins RECENCY_WEIGHTS by construction
813
+ // (applyCompositeScoring ignores options.weights when hasRecencyIntent(query) and !forceWeights).
814
+ let scored = applyCompositeScoring(enriched, query, coFn, { weights: QUERY_WEIGHTS })
811
815
  .filter(r => r.compositeScore >= (minScore || 0));
812
816
  if (diverse !== false) scored = applyMMRDiversity(scored);
813
817
  scored = scored.slice(0, limit || 10);
package/src/memory.ts CHANGED
@@ -185,6 +185,15 @@ export type CompositeWeights = {
185
185
 
186
186
  export const DEFAULT_WEIGHTS: CompositeWeights = { search: 0.5, recency: 0.25, confidence: 0.25 };
187
187
  export const RECENCY_WEIGHTS: CompositeWeights = { search: 0.1, recency: 0.7, confidence: 0.2 };
188
+ // Query-tool retrieval weights (Phase B, 2026-06-25). A held-out judged eval (n=199, GLM-5.2 judge,
189
+ // quadratic-weighted κ=0.681 vs an independent annotator) showed the default 0.50 search weight
190
+ // under-weights topical relevance for a work-memory vault: w_search 0.70 lifts graded NDCG@10 by +0.064
191
+ // (paired permutation p<1e-4, robust across precision/exploratory/temporal families) with ZERO freshness regression —
192
+ // the newest-correct doc is never demoted in the supersession guard (demotionRate 0), whereas 0.80
193
+ // demotes it out of the top-10 in 2/19 cases. Applied ONLY by the `query` tool's full hybrid+rerank
194
+ // pipeline (the path the eval mirrored). NOT applied under recency intent — RECENCY_WEIGHTS wins by
195
+ // construction in applyCompositeScoring. See BLEND-COMPOSITE-REBALANCE-DESIGN.md §11.12.
196
+ export const QUERY_WEIGHTS: CompositeWeights = { search: 0.7, recency: 0.15, confidence: 0.15 };
188
197
 
189
198
  const RECENCY_PATTERNS = [
190
199
  /\brecent(ly)?\b/i,
@@ -274,13 +283,28 @@ function canonicalMemoryMultiplier(path: string, contentType: string, query: str
274
283
  return 1.0;
275
284
  }
276
285
 
286
+ export type CompositeScoringOptions = {
287
+ /** Query-scoped weights override. Replaces DEFAULT_WEIGHTS; NOT applied under recency intent unless forceWeights. */
288
+ weights?: CompositeWeights;
289
+ /** Injected clock for deterministic scoring (tests/eval). Defaults to new Date(). */
290
+ now?: Date;
291
+ /** Test/experiment only: apply `weights` even under recency intent (bypass the RECENCY_WEIGHTS switch). */
292
+ forceWeights?: boolean;
293
+ };
294
+
277
295
  export function applyCompositeScoring(
278
296
  results: EnrichedResult[],
279
297
  query: string,
280
- coActivationFn?: CoActivationFn
298
+ coActivationFn?: CoActivationFn,
299
+ options?: CompositeScoringOptions
281
300
  ): ScoredResult[] {
282
- const weights = hasRecencyIntent(query) ? RECENCY_WEIGHTS : DEFAULT_WEIGHTS;
283
- const now = new Date();
301
+ const recencyIntent = hasRecencyIntent(query);
302
+ // Recency intent keeps RECENCY_WEIGHTS (production contract) unless forceWeights overrides (experiments).
303
+ // A query-scoped `weights` override otherwise replaces DEFAULT_WEIGHTS; absent options => exact prior behavior.
304
+ const weights = (recencyIntent && !options?.forceWeights)
305
+ ? RECENCY_WEIGHTS
306
+ : (options?.weights ?? (recencyIntent ? RECENCY_WEIGHTS : DEFAULT_WEIGHTS));
307
+ const now = options?.now ?? new Date();
284
308
 
285
309
  const scored = results.map(r => {
286
310
  const recency = recencyScore(r.modifiedAt, r.contentType, now, r.accessCount, r.lastAccessedAt);