recallnest 3.0.0 → 4.0.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/CHANGELOG.md +304 -0
- package/README.md +251 -258
- package/README_CN.md +226 -247
- package/assets/ui/app.js +6 -1
- package/bin/recallnest.mjs +20 -37
- package/docs/api-reference.md +8 -0
- package/docs/architecture.md +18 -13
- package/eval/cases-canary.json +107 -9
- package/integrations/examples/claude-agent-sdk/memory-agent.ts +2 -0
- package/integrations/examples/langchain/memory-chain.py +4 -0
- package/integrations/examples/openai-agents-sdk/memory-agent.py +4 -0
- package/package.json +12 -3
- package/src/activity-counter.ts +1 -0
- package/src/adaptive-retrieval.ts +23 -0
- package/src/api-server.ts +13 -11
- package/src/asset-sync.ts +26 -2
- package/src/audit-log.ts +2 -0
- package/src/auto-gc.ts +4 -3
- package/src/belief-history.ts +137 -31
- package/src/capture-engine.ts +459 -71
- package/src/cascade-forget.ts +7 -3
- package/src/checkpoint-mirror.ts +417 -0
- package/src/cli.ts +574 -54
- package/src/conflict-engine.ts +43 -15
- package/src/consolidation-engine.ts +24 -3
- package/src/context-collapse-renderer.ts +8 -1
- package/src/context-composer-stable.ts +5 -1
- package/src/context-composer.ts +11 -2
- package/src/distill-lock.ts +246 -74
- package/src/dream-pipeline.ts +3 -1
- package/src/embedder.ts +24 -0
- package/src/env-config.ts +136 -0
- package/src/flock.ts +97 -0
- package/src/forget-engine.ts +342 -58
- package/src/gateway-server.ts +4 -0
- package/src/ingest-plan.ts +56 -0
- package/src/ingest.ts +294 -121
- package/src/kg-contradiction.ts +753 -0
- package/src/kg-extractor.ts +17 -0
- package/src/kg-store.ts +1 -1
- package/src/known-item-eval.ts +455 -0
- package/src/llm-client.ts +13 -26
- package/src/llm-consolidation.ts +2 -2
- package/src/mcp-tool-deps.ts +6 -0
- package/src/mcp-tools-advanced.ts +80 -45
- package/src/mcp-tools-core.ts +23 -17
- package/src/mcp-tools-governance.ts +13 -7
- package/src/memory-assets.ts +144 -8
- package/src/memory-boundaries.ts +19 -0
- package/src/memory-evolution.ts +9 -0
- package/src/memory-lint.ts +1 -1
- package/src/memory-output.ts +311 -14
- package/src/memory-reconcile.ts +1437 -0
- package/src/memory-schema.ts +11 -0
- package/src/memory-store-port.ts +1 -0
- package/src/minis-archive.ts +151 -0
- package/src/noise-filter.ts +15 -2
- package/src/pii-detector.ts +31 -1
- package/src/pivot-apply.ts +55 -5
- package/src/pivot-evidence.ts +128 -0
- package/src/ppr-traversal.ts +25 -1
- package/src/preference-matcher.ts +40 -28
- package/src/prospective-memory.ts +7 -4
- package/src/retriever.ts +297 -57
- package/src/retroactive-boost.ts +3 -2
- package/src/runtime-config.ts +15 -1
- package/src/scope-policy.ts +1 -0
- package/src/session-distiller.ts +6 -2
- package/src/session-engine.ts +11 -4
- package/src/session-output.ts +20 -1
- package/src/session-schema.ts +5 -2
- package/src/session-store.ts +59 -12
- package/src/skill-promotion.ts +67 -1
- package/src/store-receipts.ts +109 -0
- package/src/store-write-budget.ts +37 -0
- package/src/store.ts +298 -57
- package/src/text-fingerprint.ts +25 -0
- package/src/trigger-store.ts +443 -0
- package/src/ui-server.ts +22 -21
- package/src/version-manager.ts +19 -5
- package/src/write-verifier.ts +16 -11
- package/src/__tests__/access-tracker-novelty.test.ts +0 -324
- package/src/__tests__/activity-counter.test.ts +0 -125
- package/src/__tests__/adaptive-fidelity.test.ts +0 -102
- package/src/__tests__/admission-control.test.ts +0 -206
- package/src/__tests__/age-label.test.ts +0 -33
- package/src/__tests__/anchor-generator.test.ts +0 -67
- package/src/__tests__/api-server-security.test.ts +0 -102
- package/src/__tests__/auto-consolidation.test.ts +0 -168
- package/src/__tests__/auto-recall.test.ts +0 -131
- package/src/__tests__/babel-integration.test.ts +0 -144
- package/src/__tests__/batch-dedup.test.ts +0 -65
- package/src/__tests__/belief-history.test.ts +0 -220
- package/src/__tests__/canary-eval.test.ts +0 -114
- package/src/__tests__/capture-engine.test.ts +0 -1328
- package/src/__tests__/capture-heuristic.test.ts +0 -171
- package/src/__tests__/cascade-forget.test.ts +0 -210
- package/src/__tests__/category-min-scores.test.ts +0 -116
- package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
- package/src/__tests__/circuit-breaker.test.ts +0 -99
- package/src/__tests__/cluster-consolidation.test.ts +0 -1236
- package/src/__tests__/confidence-metadata.test.ts +0 -125
- package/src/__tests__/confidence-tracker.test.ts +0 -261
- package/src/__tests__/conflict-advisor.test.ts +0 -206
- package/src/__tests__/conflict-escalation.test.ts +0 -149
- package/src/__tests__/conflict-lifecycle.test.ts +0 -83
- package/src/__tests__/conflict-output.test.ts +0 -85
- package/src/__tests__/conflict-store.test.ts +0 -418
- package/src/__tests__/connector-v1.test.ts +0 -181
- package/src/__tests__/consolidation-engine.test.ts +0 -520
- package/src/__tests__/constructive-retrieval.test.ts +0 -509
- package/src/__tests__/context-collapse-renderer.test.ts +0 -214
- package/src/__tests__/context-composer-pins.test.ts +0 -64
- package/src/__tests__/context-composer-task-selection.test.ts +0 -24
- package/src/__tests__/context-reconstructor.test.ts +0 -65
- package/src/__tests__/context-renderer.test.ts +0 -91
- package/src/__tests__/continuity-eval.test.ts +0 -275
- package/src/__tests__/continuity-schema.test.ts +0 -97
- package/src/__tests__/contracts.test.ts +0 -262
- package/src/__tests__/conversation-importer.test.ts +0 -692
- package/src/__tests__/core-recall-eval.test.ts +0 -235
- package/src/__tests__/core-summary.test.ts +0 -109
- package/src/__tests__/data-checkup.test.ts +0 -304
- package/src/__tests__/decay-exemption.test.ts +0 -129
- package/src/__tests__/dedup-actions.test.ts +0 -58
- package/src/__tests__/dedup-guard.test.ts +0 -39
- package/src/__tests__/deterministic-id.test.ts +0 -39
- package/src/__tests__/distill-lock.test.ts +0 -305
- package/src/__tests__/distill-session-argshape.test.ts +0 -79
- package/src/__tests__/doctor.test.ts +0 -319
- package/src/__tests__/dream-pipeline.test.ts +0 -820
- package/src/__tests__/dream-scope-isolation.test.ts +0 -386
- package/src/__tests__/dream-write-batching.test.ts +0 -211
- package/src/__tests__/drill-down.test.ts +0 -108
- package/src/__tests__/embedder.test.ts +0 -122
- package/src/__tests__/emotion-decay.test.ts +0 -46
- package/src/__tests__/emotion-detector.test.ts +0 -56
- package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
- package/src/__tests__/emotion-retrieval.test.ts +0 -56
- package/src/__tests__/entity-resolver.test.ts +0 -58
- package/src/__tests__/env-config-invariant.test.ts +0 -80
- package/src/__tests__/env-config.test.ts +0 -211
- package/src/__tests__/error-signature-boost.test.ts +0 -87
- package/src/__tests__/error-signature.test.ts +0 -95
- package/src/__tests__/error-taxonomy.test.ts +0 -129
- package/src/__tests__/ethics-layer.test.ts +0 -476
- package/src/__tests__/event-segmenter.test.ts +0 -153
- package/src/__tests__/feature-flags-integration.test.ts +0 -54
- package/src/__tests__/frequency-tracker.test.ts +0 -113
- package/src/__tests__/freshness.test.ts +0 -186
- package/src/__tests__/graph-export.test.ts +0 -489
- package/src/__tests__/hotness-blend.test.ts +0 -283
- package/src/__tests__/ingest-chunking.test.ts +0 -44
- package/src/__tests__/ingest-scope-prefix.test.ts +0 -69
- package/src/__tests__/ingest.test.ts +0 -360
- package/src/__tests__/interference-detector.test.ts +0 -203
- package/src/__tests__/kg-extractor.test.ts +0 -192
- package/src/__tests__/kg-store.test.ts +0 -300
- package/src/__tests__/llm-client-pivot.test.ts +0 -628
- package/src/__tests__/llm-consolidation.test.ts +0 -226
- package/src/__tests__/maintenance-locks.test.ts +0 -197
- package/src/__tests__/mcp-efficiency.test.ts +0 -288
- package/src/__tests__/mcp-related-scopes.test.ts +0 -151
- package/src/__tests__/memory-boundaries.test.ts +0 -282
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -362
- package/src/__tests__/memory-evolution.test.ts +0 -271
- package/src/__tests__/memory-health-rebalance.test.ts +0 -144
- package/src/__tests__/memory-lint.test.ts +0 -421
- package/src/__tests__/memory-output.test.ts +0 -224
- package/src/__tests__/memory-promotion.test.ts +0 -315
- package/src/__tests__/memory-utility.test.ts +0 -256
- package/src/__tests__/migrate-dedup-ids.test.ts +0 -65
- package/src/__tests__/multi-vector.test.ts +0 -242
- package/src/__tests__/narrative-architecture.test.ts +0 -456
- package/src/__tests__/noise-filter.test.ts +0 -205
- package/src/__tests__/noise-prototype-bank.test.ts +0 -280
- package/src/__tests__/obsidian-connector.test.ts +0 -363
- package/src/__tests__/omitted-reporting.test.ts +0 -98
- package/src/__tests__/openai-contract.test.ts +0 -513
- package/src/__tests__/pending-review.test.ts +0 -77
- package/src/__tests__/pii-redact.test.ts +0 -333
- package/src/__tests__/pivot-apply.test.ts +0 -373
- package/src/__tests__/pivot-distill-supervisor.test.ts +0 -457
- package/src/__tests__/pivot-distill.test.ts +0 -1178
- package/src/__tests__/post-retrieval-filter.test.ts +0 -178
- package/src/__tests__/ppr-traversal.test.ts +0 -256
- package/src/__tests__/prediction-engine.test.ts +0 -349
- package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
- package/src/__tests__/preference-guard-smoke.test.ts +0 -211
- package/src/__tests__/preference-matcher.test.ts +0 -282
- package/src/__tests__/preference-slots.test.ts +0 -205
- package/src/__tests__/promotion-distinct-sources.test.ts +0 -162
- package/src/__tests__/prospective-memory.test.ts +0 -283
- package/src/__tests__/pull-from-macbook-sync-only.test.ts +0 -679
- package/src/__tests__/query-entity-detector.test.ts +0 -171
- package/src/__tests__/query-expander-alias.test.ts +0 -146
- package/src/__tests__/query-expander.test.ts +0 -34
- package/src/__tests__/recall-gate.test.ts +0 -266
- package/src/__tests__/recall-governor.test.ts +0 -227
- package/src/__tests__/recall-mode.test.ts +0 -64
- package/src/__tests__/resolve-db-path.test.ts +0 -130
- package/src/__tests__/resolve-tier.test.ts +0 -64
- package/src/__tests__/result-synthesizer.test.ts +0 -198
- package/src/__tests__/resume-context.test.ts +0 -4898
- package/src/__tests__/retrieval-eval.test.ts +0 -163
- package/src/__tests__/retrieval-trace.test.ts +0 -213
- package/src/__tests__/retrieve-audit-revision.test.ts +0 -255
- package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
- package/src/__tests__/retriever-lazy-init-dispatch.test.ts +0 -123
- package/src/__tests__/retroactive-boost.test.ts +0 -223
- package/src/__tests__/salience-scoring.test.ts +0 -106
- package/src/__tests__/scope-inventory.test.ts +0 -200
- package/src/__tests__/scope-policy.test.ts +0 -69
- package/src/__tests__/scope-suggester.test.ts +0 -80
- package/src/__tests__/session-checkpoint.test.ts +0 -291
- package/src/__tests__/session-distiller.test.ts +0 -640
- package/src/__tests__/session-gc.test.ts +0 -215
- package/src/__tests__/skill-engine.test.ts +0 -552
- package/src/__tests__/skill-promotion.test.ts +0 -654
- package/src/__tests__/skill-verifier.test.ts +0 -122
- package/src/__tests__/source-heartbeat.test.ts +0 -254
- package/src/__tests__/stable-context-length-contract.test.ts +0 -62
- package/src/__tests__/store-patch-metadata.test.ts +0 -235
- package/src/__tests__/store-read-consistency.test.ts +0 -122
- package/src/__tests__/store-schema-migration.test.ts +0 -87
- package/src/__tests__/store-scope-audit.test.ts +0 -81
- package/src/__tests__/store-upsert.test.ts +0 -201
- package/src/__tests__/supersede-chain.test.ts +0 -154
- package/src/__tests__/synthesis-contract.test.ts +0 -252
- package/src/__tests__/synthesis-promotion.test.ts +0 -369
- package/src/__tests__/temporal-parser.test.ts +0 -241
- package/src/__tests__/temporal-validity.test.ts +0 -160
- package/src/__tests__/term-registry-terminal-roster.test.ts +0 -127
- package/src/__tests__/term-registry.test.ts +0 -121
- package/src/__tests__/tool-error-surfacing.test.ts +0 -98
- package/src/__tests__/tool-output-compressor.test.ts +0 -160
- package/src/__tests__/topic-tag.test.ts +0 -113
- package/src/__tests__/trusted-memory.test.ts +0 -867
- package/src/__tests__/ui-server-security.test.ts +0 -93
- package/src/__tests__/usage-tracker.test.ts +0 -287
- package/src/__tests__/utility-weight.test.ts +0 -57
- package/src/__tests__/verbatim-self-recall.test.ts +0 -279
- package/src/__tests__/version-manager.test.ts +0 -248
- package/src/__tests__/workflow-observation-skill-outcome.test.ts +0 -352
- package/src/__tests__/workflow-observation.test.ts +0 -433
- package/src/__tests__/write-verifier.test.ts +0 -122
package/docs/api-reference.md
CHANGED
|
@@ -225,6 +225,14 @@ Store a new durable memory entry.
|
|
|
225
225
|
|
|
226
226
|
If the same `canonicalKey` is already occupied by a different durable category, RecallNest returns `disposition = "conflict"` plus a `conflictId` instead of silently creating a second durable owner.
|
|
227
227
|
|
|
228
|
+
`stored` is `true` (status `201`) only when the write landed new content: `disposition` is `stored`, `updated` or `promoted`. Otherwise the status is `200` and `stored` is `false`:
|
|
229
|
+
|
|
230
|
+
| `disposition` | Meaning | `id` |
|
|
231
|
+
|---|---|---|
|
|
232
|
+
| `deduped` | The content already exists; no new row was written | the existing memory |
|
|
233
|
+
| `conflict` | Canonical key owned by another category; parked in the conflict store | the existing owner |
|
|
234
|
+
| `rejected` | Turned away by admission control; `rejectionReason` says why (`text_too_short`, `noise_detected`, `importance_too_low`, `rate_limited`) | a placeholder, no row exists |
|
|
235
|
+
|
|
228
236
|
For slot-aware preferences, RecallNest now derives structured keys instead of collapsing everything into a broader same-topic key. Examples:
|
|
229
237
|
|
|
230
238
|
- atomic brand-item preference: `我喜欢吃麦当劳的麦辣鸡翅` -> `preferences:brand-item:麦当劳:麦辣鸡翅`
|
package/docs/architecture.md
CHANGED
|
@@ -47,9 +47,12 @@ See [memory-boundary-contract.md](./memory-boundary-contract.md).
|
|
|
47
47
|
│ │
|
|
48
48
|
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────┐ │
|
|
49
49
|
│ │ Retriever │ │ Classifier │ │ Query Expander │ │
|
|
50
|
-
│ │ (
|
|
51
|
-
│ │ vector
|
|
52
|
-
│ │
|
|
50
|
+
│ │ (default: │ │ (6 categories│ │ (synonym + │ │
|
|
51
|
+
│ │ vector-only;│ │ auto-assign)│ │ semantic expand) │ │
|
|
52
|
+
│ │ optional │ │ │ │ │ │
|
|
53
|
+
│ │ hybrid: │ │ │ │ │ │
|
|
54
|
+
│ │ weighted │ │ │ │ │ │
|
|
55
|
+
│ │ vector+BM25)│ │ │ │ │ │
|
|
53
56
|
│ └──────┬───────┘ └──────────────┘ └────────────────────┘ │
|
|
54
57
|
│ │ │
|
|
55
58
|
│ ┌──────┴───────┐ ┌──────────────┐ ┌────────────────────┐ │
|
|
@@ -116,13 +119,15 @@ Query Results
|
|
|
116
119
|
│
|
|
117
120
|
▼
|
|
118
121
|
┌──────────────┐ ┌──────────────┐ ┌───────────────┐
|
|
119
|
-
│ Query │───►│
|
|
120
|
-
│ Expander │ │
|
|
121
|
-
│ (synonyms) │ │
|
|
122
|
-
└──────────────┘ │
|
|
123
|
-
│
|
|
124
|
-
│
|
|
125
|
-
|
|
122
|
+
│ Query │───►│ Retrieval │───►│ Post-process │──► Top-K
|
|
123
|
+
│ Expander │ │ default: │ │ │ Results
|
|
124
|
+
│ (synonyms) │ │ vector-only │ │ - Decay │
|
|
125
|
+
└──────────────┘ │ hybrid mode: │ │ - Access boost│
|
|
126
|
+
│ Vector: 0.7 │ │ - Score floor │
|
|
127
|
+
│ BM25: 0.3 │ │ - Dedup │
|
|
128
|
+
│ weighted │ └───────────────┘
|
|
129
|
+
│ fusion │
|
|
130
|
+
└──────────────┘
|
|
126
131
|
```
|
|
127
132
|
|
|
128
133
|
## Key Design Decisions
|
|
@@ -131,8 +136,8 @@ Query Results
|
|
|
131
136
|
|----------|-----------|
|
|
132
137
|
| **LanceDB** (not SQLite/Postgres) | Native vector search, columnar storage, zero-config, single-file DB |
|
|
133
138
|
| **Jina v5** (not OpenAI embeddings) | Task-aware embeddings (query vs passage), better multilingual, 1024-dim sweet spot |
|
|
134
|
-
| **Hybrid retrieval** (vector + BM25) | Vector alone misses keyword matches; BM25 alone misses semantic similarity |
|
|
135
|
-
| **
|
|
139
|
+
| **Hybrid retrieval** (vector + BM25, optional) | Vector alone misses keyword matches; BM25 alone misses semantic similarity. Default `retrieval.mode` is `"vector"` (vector-only search; `DEFAULT_RETRIEVAL_CONFIG` in `src/retriever.ts`, `config.json.example`). Set `retrieval.mode` to `"hybrid"` in `config.json` to add BM25 full-text search; if the store has no full-text index, retrieval stays vector-only |
|
|
140
|
+
| **Weighted score fusion** (hybrid mode only) | Applies only when `retrieval.mode` is `"hybrid"` (default is `"vector"`). Hybrid mode combines vector and BM25 scores as a weighted average (defaults `vectorWeight: 0.7`, `bm25Weight: 0.3`), with a 5% bonus when both legs return the same memory; a memory found by only one leg is scored by that leg alone (BM25-only hits get ×1.15 on short queries). For short queries (≤ 4 tokens) the vector weight is multiplied by 0.7 and the BM25 weight by 1.5, capped at 0.6 (`fuseResults()` in `src/retriever.ts`) |
|
|
136
141
|
| **Weibull decay** (not exponential) | Better models human forgetting: slow start, accelerating fade |
|
|
137
142
|
| **6 categories** (not free-form tags) | Structured enough for filtering and lifecycle rules, simple enough to auto-classify |
|
|
138
143
|
| **HTTP API + MCP** (not just MCP) | MCP is great for CLI tools, but HTTP API works with any language/framework |
|
|
@@ -146,7 +151,7 @@ src/
|
|
|
146
151
|
├── mcp-server.ts # MCP server (stdio transport)
|
|
147
152
|
├── cli.ts # CLI entry point (lm command)
|
|
148
153
|
├── store.ts # LanceDB storage layer
|
|
149
|
-
├── retriever.ts # Hybrid retrieval (vector + BM25
|
|
154
|
+
├── retriever.ts # Hybrid retrieval (vector + BM25, weighted score fusion)
|
|
150
155
|
├── embedder.ts # Jina embedding client
|
|
151
156
|
├── ingest.ts # Multi-source ingestion pipeline
|
|
152
157
|
├── chunker.ts # Text chunking with noise filtering
|
package/eval/cases-canary.json
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
"query": "mem0 对 recallnest 有没有借鉴作用",
|
|
5
5
|
"profile": "fact-check",
|
|
6
6
|
"limit": 8,
|
|
7
|
-
"targets": [
|
|
7
|
+
"targets": [
|
|
8
|
+
"a2fe9b62"
|
|
9
|
+
],
|
|
8
10
|
"expectTopK": 3,
|
|
9
11
|
"notes": "A 单目标:mem0→recallnest 借鉴审计结论该被召回。"
|
|
10
12
|
},
|
|
@@ -13,7 +15,9 @@
|
|
|
13
15
|
"query": "recallnest 本地两个目录反复踩坑是怎么解决的",
|
|
14
16
|
"profile": "debug",
|
|
15
17
|
"limit": 8,
|
|
16
|
-
"targets": [
|
|
18
|
+
"targets": [
|
|
19
|
+
"dbcb317a"
|
|
20
|
+
],
|
|
17
21
|
"expectTopK": 3,
|
|
18
22
|
"notes": "A 单目标:dual-dir 符号链接统一 case(dbcb317a, cases, imp 0.9, baseline rank1)。原 target e40ab64b 系标注错——e40ab64b 实为『RecallNest 演进优先 workflow pattern』,与本 query 无关;dbcb317a 才是『~/recallnest→~/Projects/recallnest symlink 统一』。"
|
|
19
23
|
},
|
|
@@ -22,8 +26,13 @@
|
|
|
22
26
|
"query": "recallnest 的 capture 是纯 heuristic 吗,提取到底用不用 LLM",
|
|
23
27
|
"profile": "fact-check",
|
|
24
28
|
"limit": 8,
|
|
25
|
-
"targets": [
|
|
26
|
-
|
|
29
|
+
"targets": [
|
|
30
|
+
"727da608"
|
|
31
|
+
],
|
|
32
|
+
"expectOrder": [
|
|
33
|
+
"727da608",
|
|
34
|
+
"a2fe9b62"
|
|
35
|
+
],
|
|
27
36
|
"expectTopK": 3,
|
|
28
37
|
"notes": "B 新旧对:727da608(cases,imp0.8) 正文明确『修正 a2fe9b62 中 capture 纯heuristic/zero-LLM 的不准确表述』,是本 query 准确答案;a2fe9b62(events,imp0.72,mem0借鉴审计) 含被纠正的旧表述。expectOrder 测『两者同时召回时准确版 727 须排在旧版 a2 前』——纯排名对比(rank 唯一,<与≤等价),不靠结构化 supersede。校准修正:原 notes『验 confidence/recency/decay』机制声称不成立(二者无 evolution 链/降权链,靠内容质量排序);且主话题不重合(727=ingest去重史 / a2=mem0审计),实测 a2 常落选(baseline rank=null),故本 case 当前主要测 727 召回质量+弱新旧序保护,非真正新旧竞争。理想 B 类应换一对同话题、会同时召回的新旧记忆(P1 后续)。"
|
|
29
38
|
},
|
|
@@ -32,8 +41,12 @@
|
|
|
32
41
|
"query": "工作记忆要不要做 scope 隔离",
|
|
33
42
|
"profile": "default",
|
|
34
43
|
"limit": 8,
|
|
35
|
-
"targets": [
|
|
36
|
-
|
|
44
|
+
"targets": [
|
|
45
|
+
"491c8b30"
|
|
46
|
+
],
|
|
47
|
+
"hardNegatives": [
|
|
48
|
+
"b59e86b2"
|
|
49
|
+
],
|
|
37
50
|
"expectTopK": 2,
|
|
38
51
|
"notes": "C 干扰项:491c8b30(元决策『工作记忆不做 scope 隔离』,baseline rank1) 应召回。hardNegative b59e86b2(entities,imp0.65,scope=codex:019cfba5) 是 codex 端关于 scope 模型/associative access/隔离的设计讨论片段——词面同话题、别处语境(非 Alice 元决策),baseline rank2 进了 top。补此 hardNeg 后本 case 在当前裸 retrieve 下会 fail,暴露『跨-scope 旁支讨论被召回进 top』的真问题(契合 P0 scope 主题)。"
|
|
39
52
|
},
|
|
@@ -42,7 +55,13 @@
|
|
|
42
55
|
"query": "Alice 的写作风格偏好是什么",
|
|
43
56
|
"profile": "writing",
|
|
44
57
|
"limit": 6,
|
|
45
|
-
"expectContentAny": [
|
|
58
|
+
"expectContentAny": [
|
|
59
|
+
"口语化",
|
|
60
|
+
"不端着",
|
|
61
|
+
"剧评腔",
|
|
62
|
+
"书面非口语",
|
|
63
|
+
"可自嘲"
|
|
64
|
+
],
|
|
46
65
|
"expectTopK": 3,
|
|
47
66
|
"notes": "D 稳定偏好:不钉 id,召回文本命中要点即算对。"
|
|
48
67
|
},
|
|
@@ -51,8 +70,87 @@
|
|
|
51
70
|
"query": "Alice 的配图审美偏好",
|
|
52
71
|
"profile": "default",
|
|
53
72
|
"limit": 6,
|
|
54
|
-
"expectContentAny": [
|
|
73
|
+
"expectContentAny": [
|
|
74
|
+
"手绘",
|
|
75
|
+
"撞色",
|
|
76
|
+
"高对比",
|
|
77
|
+
"静物无人物",
|
|
78
|
+
"涂鸦"
|
|
79
|
+
],
|
|
55
80
|
"expectTopK": 3,
|
|
56
81
|
"notes": "D 稳定偏好:同上。"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "canary-assoc-onboarding-written",
|
|
85
|
+
"query": "接新的 AI 是不是又要从头讲一遍",
|
|
86
|
+
"profile": "default",
|
|
87
|
+
"scope": "memory:pivot",
|
|
88
|
+
"limit": 8,
|
|
89
|
+
"targets": [
|
|
90
|
+
"5a9affa2"
|
|
91
|
+
],
|
|
92
|
+
"expectTopK": 5,
|
|
93
|
+
"notes": "assoc 对照组:query 就是正文尾巴写下的『她以后会这么问起』原句。09-22 基线 #1。"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "canary-assoc-onboarding-zero",
|
|
97
|
+
"query": "新接的那个 app 答应会遵守约定,结果每次开新对话都当没听过",
|
|
98
|
+
"profile": "default",
|
|
99
|
+
"scope": "memory:pivot",
|
|
100
|
+
"limit": 8,
|
|
101
|
+
"targets": [
|
|
102
|
+
"5a9affa2"
|
|
103
|
+
],
|
|
104
|
+
"expectTopK": 5,
|
|
105
|
+
"notes": "assoc 零词面重合:三层规则结构 / 口头承诺不是机制。09-22 基线 #1(path=vector)。"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "canary-assoc-rebuild-pool",
|
|
109
|
+
"query": "要不要把老的那十几万条清一清",
|
|
110
|
+
"profile": "default",
|
|
111
|
+
"scope": "memory:pivot",
|
|
112
|
+
"limit": 8,
|
|
113
|
+
"targets": [
|
|
114
|
+
"9f908ad2"
|
|
115
|
+
],
|
|
116
|
+
"expectTopK": 5,
|
|
117
|
+
"notes": "assoc:08-01 从零建池不修补存量、12.9 万条不删。09-22 基线 #1。"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "canary-assoc-commit-gate",
|
|
121
|
+
"query": "记忆写进去之前要不要先让人审一下再入库",
|
|
122
|
+
"profile": "default",
|
|
123
|
+
"scope": "memory:pivot",
|
|
124
|
+
"limit": 8,
|
|
125
|
+
"targets": [
|
|
126
|
+
"545562b9",
|
|
127
|
+
"a81ccffb"
|
|
128
|
+
],
|
|
129
|
+
"expectTopK": 5,
|
|
130
|
+
"notes": "assoc:提案/提交分离早就有 + deepbrain 否决。09-22 基线 #1 / #5。"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "canary-assoc-benchmark-tmem",
|
|
134
|
+
"query": "T-Mem 论文里那个榜单我们能不能也上去比比",
|
|
135
|
+
"profile": "default",
|
|
136
|
+
"scope": "memory:pivot",
|
|
137
|
+
"limit": 8,
|
|
138
|
+
"targets": [
|
|
139
|
+
"64df5691"
|
|
140
|
+
],
|
|
141
|
+
"expectTopK": 5,
|
|
142
|
+
"notes": "assoc 已知坏样本①:07-25 公开 benchmark 否决,正文尾巴已带『她以后可能会这样问起』句。09-22 基线 #7(limit 5 漏)。"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "canary-assoc-benchmark-tencent",
|
|
146
|
+
"query": "腾讯那篇论文的榜我们要不要也去排个名",
|
|
147
|
+
"profile": "default",
|
|
148
|
+
"scope": "memory:pivot",
|
|
149
|
+
"limit": 8,
|
|
150
|
+
"targets": [
|
|
151
|
+
"64df5691"
|
|
152
|
+
],
|
|
153
|
+
"expectTopK": 5,
|
|
154
|
+
"notes": "assoc 已知坏样本②:同上目标,措辞换成『榜 / 排名』。09-22 基线 top-10 不出现。"
|
|
57
155
|
}
|
|
58
|
-
]
|
|
156
|
+
]
|
|
@@ -113,6 +113,8 @@ async function handleTool(
|
|
|
113
113
|
}),
|
|
114
114
|
});
|
|
115
115
|
if (!res.ok) return `Error: ${res.status} ${await res.text()}`;
|
|
116
|
+
const data = await res.json() as { stored: boolean; disposition: string; id: string; rejectionReason?: string };
|
|
117
|
+
if (!data.stored) return `Memory not stored (${data.disposition}: ${data.rejectionReason ?? data.id}).`;
|
|
116
118
|
return "Memory stored successfully.";
|
|
117
119
|
}
|
|
118
120
|
|
|
@@ -78,6 +78,10 @@ def store_memory(text: str, category: str = "events") -> str:
|
|
|
78
78
|
},
|
|
79
79
|
)
|
|
80
80
|
r.raise_for_status()
|
|
81
|
+
data = r.json()
|
|
82
|
+
if not data.get("stored"):
|
|
83
|
+
reason = data.get("rejectionReason") or data.get("id")
|
|
84
|
+
return f"Memory not stored ({data.get('disposition')}: {reason})."
|
|
81
85
|
return "Memory stored successfully."
|
|
82
86
|
|
|
83
87
|
|
|
@@ -79,6 +79,10 @@ async def store_memory(text: str, category: str = "events") -> str:
|
|
|
79
79
|
},
|
|
80
80
|
)
|
|
81
81
|
r.raise_for_status()
|
|
82
|
+
data = r.json()
|
|
83
|
+
if not data.get("stored"):
|
|
84
|
+
reason = data.get("rejectionReason") or data.get("id")
|
|
85
|
+
return f"Memory not stored ({data.get('disposition')}: {reason})."
|
|
82
86
|
return "Memory stored successfully."
|
|
83
87
|
|
|
84
88
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "recallnest",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "RecallNest — MCP-native, local-first memory search and distillation for AI conversations",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"eval/continuity/*.json",
|
|
23
23
|
"integrations/",
|
|
24
24
|
"README_CN.md",
|
|
25
|
-
"src/"
|
|
25
|
+
"src/",
|
|
26
|
+
"!src/**/__tests__/**"
|
|
26
27
|
],
|
|
27
28
|
"scripts": {
|
|
28
29
|
"search": "bun run src/cli.ts search",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"eval": "bun run src/eval.ts",
|
|
46
47
|
"eval:continuity": "bun run src/eval.ts --mode continuity --cases eval/continuity/cases.json",
|
|
47
48
|
"eval:canary": "bun run src/eval.ts --mode canary --cases eval/cases-canary.json",
|
|
49
|
+
"eval:known-item": "bun run eval/known-item.ts run",
|
|
48
50
|
"profiles": "bun run src/cli.ts profiles",
|
|
49
51
|
"doctor": "bun run src/cli.ts doctor",
|
|
50
52
|
"demo": "bun run src/cli.ts demo",
|
|
@@ -60,10 +62,17 @@
|
|
|
60
62
|
"engines": {
|
|
61
63
|
"node": ">=22"
|
|
62
64
|
},
|
|
65
|
+
"overrides": {
|
|
66
|
+
"fast-uri": "^3.1.8",
|
|
67
|
+
"qs": "^6.16.0",
|
|
68
|
+
"hono": "^4.13.7",
|
|
69
|
+
"ip-address": "^10.7.1",
|
|
70
|
+
"proxy-addr": "^2.0.8"
|
|
71
|
+
},
|
|
63
72
|
"dependencies": {
|
|
64
73
|
"@hono/node-server": "1.19.17",
|
|
65
74
|
"@lancedb/lancedb": "^0.26.0",
|
|
66
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
75
|
+
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
67
76
|
"commander": "^12.0.0",
|
|
68
77
|
"openai": "^7.5.0",
|
|
69
78
|
"tsx": "^4.19.0",
|
package/src/activity-counter.ts
CHANGED
|
@@ -104,6 +104,7 @@ const ACTIVITY_LOCK_KEY = "activity-stats";
|
|
|
104
104
|
const ACTIVITY_LOCK_OPTS = { onBusy: "wait" as const, waitTimeoutMs: 2_000, expireMs: 30_000 };
|
|
105
105
|
|
|
106
106
|
async function withStatsLock<T>(fn: () => T): Promise<T> {
|
|
107
|
+
// lock-wait: no store-write inside — fn only reads and rewrites the activity stats file.
|
|
107
108
|
const outcome = await withLock(ACTIVITY_LOCK_KEY, async () => fn(), ACTIVITY_LOCK_OPTS);
|
|
108
109
|
// onBusy:"wait" resolves ran:true or throws on timeout; ran:false unreachable.
|
|
109
110
|
if (!outcome.ran) throw new Error("activity-stats lock unavailable");
|
|
@@ -74,3 +74,26 @@ export function shouldSkipRetrieval(query: string, minLength?: number): boolean
|
|
|
74
74
|
|
|
75
75
|
return false;
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 检索入口用的判定:这条检索要不要直接返回空。
|
|
80
|
+
*
|
|
81
|
+
* 上面那套规则是「一条聊天消息值不值得自动检索」的判定(省嵌入调用),只该管自动召回。
|
|
82
|
+
* 人主动发起的查询(MCP search_memory、CLI、HTTP、本地 UI)以 `git` / `docker` / `test` 开头、
|
|
83
|
+
* 纯数字或 `#22`、不含中文又短于 15 字的短语,都是正常的查法;被闸掉时返回的空结果和
|
|
84
|
+
* 「库里确实没有」看不出区别。所以:
|
|
85
|
+
* - `trivialQueryGate` 显式给了就按它;
|
|
86
|
+
* - 没给时只在 `source === "auto-recall"` 时开闸——`source` 同时管着三处强化写入,
|
|
87
|
+
* 有些人主动发起的检索(search_memory 的相关 scope 旁路、known-item eval)为了不写库也标成
|
|
88
|
+
* auto-recall,它们用 `trivialQueryGate: false` 把闸关掉;
|
|
89
|
+
* - 空串与纯空白不管哪条路都跳过。
|
|
90
|
+
*/
|
|
91
|
+
export function shouldSkipRetrievalFor(context: {
|
|
92
|
+
query: string;
|
|
93
|
+
source?: "manual" | "auto-recall" | "cli";
|
|
94
|
+
trivialQueryGate?: boolean;
|
|
95
|
+
}): boolean {
|
|
96
|
+
if (context.query.trim().length === 0) return true;
|
|
97
|
+
const gate = context.trivialQueryGate ?? context.source === "auto-recall";
|
|
98
|
+
return gate && shouldSkipRetrieval(context.query);
|
|
99
|
+
}
|
package/src/api-server.ts
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { createComponentResolver, loadConfig, loadDotEnv } from "./runtime-config.js";
|
|
10
|
+
import { HTTP_WRITE_IDLE_TIMEOUT_S } from "./store-write-budget.js";
|
|
10
11
|
import { DurableMemoryCategorySchema } from "./memory-schema.js";
|
|
11
12
|
import { persistCaseMemory, persistMemory, persistMemoryBatch, persistWorkflowPattern, promoteMemory } from "./capture-engine.js";
|
|
12
13
|
import { buildSessionCheckpointResult } from "./session-engine.js";
|
|
14
|
+
import { buildStoreHttpResponse } from "./store-receipts.js";
|
|
13
15
|
import { SessionCheckpointStore } from "./session-store.js";
|
|
16
|
+
import { mirrorCheckpointWithDeadline } from "./checkpoint-mirror.js";
|
|
14
17
|
import { composeResumeContext } from "./context-composer.js";
|
|
15
18
|
import { extractMemoryProvenance } from "./memory-boundaries.js";
|
|
16
19
|
import { ConflictStatusSchema } from "./conflict-schema.js";
|
|
@@ -242,17 +245,8 @@ async function handleStore(request: Request): Promise<Response> {
|
|
|
242
245
|
try {
|
|
243
246
|
const { store, embedder } = getComponents();
|
|
244
247
|
const stored = await persistMemory({ store, embedder, conflictStore, auditLogger }, body);
|
|
245
|
-
|
|
246
|
-
return jsonResponse(
|
|
247
|
-
id: stored.id,
|
|
248
|
-
stored: stored.disposition !== "conflict",
|
|
249
|
-
disposition: stored.disposition,
|
|
250
|
-
storedAt: stored.storedAt,
|
|
251
|
-
category: stored.category,
|
|
252
|
-
scope: stored.resolvedScope,
|
|
253
|
-
canonicalKey: stored.canonicalKey,
|
|
254
|
-
conflictId: stored.conflictId,
|
|
255
|
-
}, 201);
|
|
248
|
+
const response = buildStoreHttpResponse(stored);
|
|
249
|
+
return jsonResponse(response.body, response.status);
|
|
256
250
|
} catch (err) {
|
|
257
251
|
const message = err instanceof Error ? err.message : String(err);
|
|
258
252
|
return errorResponse(400, message);
|
|
@@ -454,6 +448,11 @@ async function handleCheckpoint(request: Request): Promise<Response> {
|
|
|
454
448
|
...result,
|
|
455
449
|
record: stored,
|
|
456
450
|
}));
|
|
451
|
+
// Mirror into the memory table so search can find it; never throws, capped wait.
|
|
452
|
+
await mirrorCheckpointWithDeadline(() => {
|
|
453
|
+
const { store, embedder } = getComponents();
|
|
454
|
+
return { store, embedder };
|
|
455
|
+
}, stored);
|
|
457
456
|
return jsonResponse(stored, 201);
|
|
458
457
|
} catch (err) {
|
|
459
458
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -651,6 +650,9 @@ const port = clampInt(envConfig.apiPortRaw(), 4318, 1, 65535);
|
|
|
651
650
|
|
|
652
651
|
const server = Bun.serve({
|
|
653
652
|
port,
|
|
653
|
+
// Writes can wait up to 30 s for store-write before they start; Bun's 10 s default would drop
|
|
654
|
+
// the connection while the handler still commits.
|
|
655
|
+
idleTimeout: HTTP_WRITE_IDLE_TIMEOUT_S,
|
|
654
656
|
hostname: LOCAL_HTTP_HOSTNAME,
|
|
655
657
|
async fetch(request) {
|
|
656
658
|
const policy = await enforceLocalHttpRequestPolicy(request);
|
package/src/asset-sync.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Embedder } from "./embedder.js";
|
|
2
|
-
import type { MemoryStore } from "./store.js";
|
|
3
|
-
import type
|
|
2
|
+
import type { MemoryEntry, MemoryStore } from "./store.js";
|
|
3
|
+
import { buildPinAsset, savePinAsset, type BriefAsset, type MemoryAsset, type PinAsset } from "./memory-assets.js";
|
|
4
|
+
import type { RetrievalProfileName } from "./retrieval-profiles.js";
|
|
4
5
|
|
|
5
6
|
function buildPinnedAssetText(asset: PinAsset): string {
|
|
6
7
|
const tags = asset.tags.join(", ");
|
|
@@ -77,3 +78,26 @@ export async function indexAsset(
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
export const indexPinnedAsset = indexAsset;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* pin_memory(MCP 工具、CLI `pin`、本地 UI `/api/pin` 共用):先把这条记忆的 importance 提到至少 0.95,
|
|
84
|
+
* update() 返回 null(行在查到之后被删掉了)就返回 null——不写 pin 文件、不建索引副本,由调用方报 not found;
|
|
85
|
+
* 写成功才用 update() 返回的那一行生成 pin。2026-10-17 起 update() 不再把被删的行插回来,
|
|
86
|
+
* 原先这里不看返回值,会给一条已经不存在的记忆留下 pin 文件和索引副本。
|
|
87
|
+
*/
|
|
88
|
+
export async function pinMemory(
|
|
89
|
+
store: MemoryStore,
|
|
90
|
+
embedder: Embedder,
|
|
91
|
+
entry: MemoryEntry,
|
|
92
|
+
scopeFilter: string[] | undefined,
|
|
93
|
+
options: { title?: string; summary?: string; query?: string; profile?: RetrievalProfileName },
|
|
94
|
+
save: (asset: PinAsset) => string = savePinAsset,
|
|
95
|
+
): Promise<{ asset: PinAsset; path: string; entry: MemoryEntry } | null> {
|
|
96
|
+
const updated = await store.update(entry.id, { importance: Math.max(entry.importance || 0.7, 0.95) }, scopeFilter);
|
|
97
|
+
if (!updated) return null;
|
|
98
|
+
const score = updated.importance || 0.7;
|
|
99
|
+
const asset = buildPinAsset({ entry: updated, score, sources: { fused: { score } } }, options);
|
|
100
|
+
const path = save(asset);
|
|
101
|
+
await indexAsset(store, embedder, asset);
|
|
102
|
+
return { asset, path, entry: updated };
|
|
103
|
+
}
|
package/src/audit-log.ts
CHANGED
|
@@ -18,6 +18,8 @@ export type AuditOperation =
|
|
|
18
18
|
| "supersede"
|
|
19
19
|
| "consolidate"
|
|
20
20
|
| "forget"
|
|
21
|
+
/** A forget that stopped after changing something (see forget-engine.ts). Not a `forget`: the memory may still be there. */
|
|
22
|
+
| "forget_incomplete"
|
|
21
23
|
| "cascade_forget"
|
|
22
24
|
| "reject";
|
|
23
25
|
|
package/src/auto-gc.ts
CHANGED
|
@@ -22,7 +22,7 @@ import type { RetentionPolicy } from "./retention-policy.js";
|
|
|
22
22
|
import type { AuditLogger } from "./audit-log.js";
|
|
23
23
|
import * as envConfig from "./env-config.js";
|
|
24
24
|
import { isUsageSignalActive } from "./usage-tracker.js";
|
|
25
|
-
import { acquireLock, releaseLock, getLastDistillTime, stampLock, lockPathForKey } from "./distill-lock.js";
|
|
25
|
+
import { acquireLock, releaseLock, getLastDistillTime, stampLock, clearStamp, lockPathForKey } from "./distill-lock.js";
|
|
26
26
|
import { logInfo } from "./stderr-log.js";
|
|
27
27
|
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
@@ -315,9 +315,10 @@ export function resetGcTimestamp(): void {
|
|
|
315
315
|
lastGcTimestamp = 0;
|
|
316
316
|
lastVersionGroupRepairTimestamp = 0;
|
|
317
317
|
try {
|
|
318
|
-
|
|
318
|
+
clearStamp({ lockPath: lockPathForKey("gc-last") });
|
|
319
|
+
clearStamp({ lockPath: lockPathForKey("version-group-repair-last") });
|
|
320
|
+
// Only releases gc-run if this process holds it; another holder's lock is never removed.
|
|
319
321
|
releaseLock({ lockPath: lockPathForKey("gc-run") });
|
|
320
|
-
releaseLock({ lockPath: lockPathForKey("version-group-repair-last") });
|
|
321
322
|
} catch {
|
|
322
323
|
/* ignore — nothing to clear */
|
|
323
324
|
}
|