dsh-memento 0.4.5 → 0.5.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/ARCHITECTURE.md +23 -1
- package/CHANGELOG.md +8 -0
- package/README.es.md +39 -0
- package/README.hi.md +39 -0
- package/README.md +39 -0
- package/README.pt.md +39 -0
- package/README.zh.md +39 -0
- package/bin/mcp-server.mjs +18 -0
- package/index.mjs +90 -11
- package/lib/constants.mjs +2 -0
- package/lib/embedding.mjs +205 -0
- package/lib/errors.mjs +26 -0
- package/lib/mcp.mjs +287 -0
- package/lib/protocol.mjs +1 -0
- package/lib/retrieval.mjs +204 -0
- package/lib/store.mjs +14 -0
- package/package.json +5 -1
- package/types.d.ts +72 -0
package/ARCHITECTURE.md
CHANGED
|
@@ -105,7 +105,7 @@ memory 工具(add)
|
|
|
105
105
|
- 实测(Node 22 内置 SQLite,FTS5 可用):trigram 分词器无法索引单字 CJK 字符——`'中文测试'` 中查 `'中文'` 零命中;unicode61 把 CJK 连续段当一个 token,仅前缀可查。本插件语料以中文记忆为主,子串语义必须对 CJK 成立,instr 是唯一正确的内置引擎。
|
|
106
106
|
- query 大小写不敏感(lower() 折叠 ASCII;CJK 无大小写不受影响),与面板过滤、sessionQuery 文本检索语义一致;replace/remove/consolidate 定位同语义(`lib/match.mjs` 的 `findUniqueMatch` 统一折叠,store 层 lower(instr) 与之一致)。
|
|
107
107
|
- 召回排序:query 命中页的条目 `recall_count` +1、`last_recalled` 落地(SCHEMA v3 列);排序 `recall_count DESC, updated_at DESC`(高频即重要)。快照仍走 `listEntries` 创建序(冻结块稳定优先)。
|
|
108
|
-
- 未来真正的升级路径是 harness 出现 embedding seam 后的语义召回(Provider 角色天然兼容),不是 FTS5
|
|
108
|
+
- 未来真正的升级路径是 harness 出现 embedding seam 后的语义召回(Provider 角色天然兼容),不是 FTS5——本仓库已按决策 16 落地检索/嵌入 seam 的最小接入。
|
|
109
109
|
|
|
110
110
|
11. **第三维 agentKey(per-agent 作用域,SCHEMA v3)**。
|
|
111
111
|
- 写方 session 的 `header.agentPreset` 经 `agentKeyOf` 规范化(缺失→'' 共享层);条目与提案落 `agent_key`。
|
|
@@ -175,3 +175,25 @@ gate → 预算复审 → 落盘 → 审计的完整流水线、唯一子串定
|
|
|
175
175
|
仓库 CI 以黄金参考全绿;第三方 Provider 拷贝目录即可跑同一套用例。协议文档:
|
|
176
176
|
`docs/protocol-v1.md`(双语)、`docs/schemas/dsh-memory-protocol-v1.schema.json`、
|
|
177
177
|
`docs/adapters-guide.md`(双语)、`docs/upstream-proposal.md`(双语,官方 seam 采纳论证与迁移路径)。
|
|
178
|
+
|
|
179
|
+
## P0:检索与嵌入 seam(可插拔检索 + 伪嵌入向量召回)
|
|
180
|
+
|
|
181
|
+
### 16. 检索 Provider seam(lib/retrieval.mjs)与嵌入 Provider seam(lib/embedding.mjs)
|
|
182
|
+
|
|
183
|
+
把 memory recall 的"检索"抽成可插拔检索器,并新增嵌入 Provider 接口,两者都是完整的
|
|
184
|
+
三角色 seam(Service Definition / Provider / Consumer),零 DSH 依赖、零重依赖:
|
|
185
|
+
|
|
186
|
+
- **检索 seam**(`ctx.memoryRetrieval`,`lib/retrieval.mjs`):`RetrievalProvider` 契约 +
|
|
187
|
+
`RetrievalProviderRegistry`(register 可逆 / list / get / resolve)。内置 `SubstringRetriever`
|
|
188
|
+
是零依赖主路径(大小写不敏感子串 + 召回频次排序,语义与 `store.queryEntries` 的 instr 一致);
|
|
189
|
+
`VectorRetriever` 是可选后端,消费嵌入 provider 做内存内暴力余弦排序(小语料,与决策 10 一致)。
|
|
190
|
+
- **嵌入 seam**(`ctx.memoryEmbedding`,`lib/embedding.mjs`):`EmbeddingProvider` 契约 +
|
|
191
|
+
`EmbeddingProviderRegistry`。默认 `FakeEmbeddingProvider` 是确定性的 token 哈希分桶计数 +
|
|
192
|
+
L2 归一化(固定 256 维单位向量)——它不做语义建模,只验证 seam 接线与余弦召回路径可复现;
|
|
193
|
+
真实嵌入由可选 provider 注册(本地模型 / peer),本仓库不引入 sqlite-vec / ONNX / 本地模型。
|
|
194
|
+
- **Consumer 接线**:`Config.retrieval.vector`(默认 `false`)开启后,`memory_recall` 的记忆段改走
|
|
195
|
+
vector 检索器(可见集 = `visibleEntries` + 检索器排序 + `store.bumpRecall` + `recalled` 审计);
|
|
196
|
+
默认仍走 `service.query` 的 substring 主路径,行为不变。
|
|
197
|
+
- **探测 → 使用 → 优雅降级**:`detectVectorBackend` 只要求 embedding provider 可用;sqlite-vec 是
|
|
198
|
+
可选 loadable 扩展、恒不在本仓库打包(`sqliteVec: false`),P0 向量召回走内存内暴力余弦。
|
|
199
|
+
缺 embedding / vector 关闭时优雅降级回 substring,绝不响亮失败(可选后端缺失不是配置错误)。
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.0] - 2026-08-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Embedding Provider seam (`ctx.memoryEmbedding`)** — new `lib/embedding.mjs` registry ships a deterministic fake-hash provider by default, so third-party plugins can register real embedding backends behind the same Service Definition.
|
|
13
|
+
- **Retrieval Provider seam (`ctx.memoryRetrieval`)** — new `lib/retrieval.mjs` registry keeps the built-in substring retriever as the zero-dependency main path and adds an optional `VectorRetriever` for semantic recall, enabled when `config.retrieval.vector` is `true` and an embedding provider is detected (graceful fallback to substring otherwise).
|
|
14
|
+
- **stdio MCP server export** — new `bin/mcp-server.mjs` and `lib/mcp.mjs` expose the memory seam as an MCP server through the `dsh-memento-mcp` bin.
|
|
15
|
+
|
|
8
16
|
## [0.4.5] - 2026-08-23
|
|
9
17
|
|
|
10
18
|
### Changed
|
package/README.es.md
CHANGED
|
@@ -82,6 +82,7 @@ Todos los parámetros son campos Schemastery `Config` (modificables desde cordis
|
|
|
82
82
|
| `recall.snippetCap` | `5` | Fragmentos por sesión en `memory_recall` |
|
|
83
83
|
| `recall.snippetChars` | `300` | Caracteres de fragmento en `memory_recall` |
|
|
84
84
|
| `recall.windowDays` | `30` | Ventana de antigüedad en días de `memory_recall` |
|
|
85
|
+
| `retrieval.vector` | `false` | Interruptor de recuperación semántica: `true` activa la recuperación vectorial de `memory_recall` (incrustación hash falsa) cuando hay un proveedor de incrustación; en caso contrario degrada a subcadena |
|
|
85
86
|
| `panelEntriesLimit` | `200` | Tamaño de página de entradas del panel web |
|
|
86
87
|
| `panelAuditLimit` | `20` | Filas de auditoría del panel web por defecto |
|
|
87
88
|
| `auditRetentionDays` | `0` | Retención de auditoría (0 = conservar para siempre) |
|
|
@@ -98,6 +99,44 @@ Todos los parámetros son campos Schemastery `Config` (modificables desde cordis
|
|
|
98
99
|
| `/memory` | command | `list` · `query` · `add` · `remove` · `consolidate` · `proposals` · `budgets` · `audit` · `export` · `import <path>` · `adapters` |
|
|
99
100
|
| web panel | client drawer | Solo lectura: explorar entradas, buscar, barras de presupuesto, cola de auditoría |
|
|
100
101
|
|
|
102
|
+
## MCP server
|
|
103
|
+
|
|
104
|
+
`dsh-memento` incluye un **servidor MCP** stdio de solo lectura (`dsh-memento-mcp`) para que clientes MCP externos (Claude, Codex, …) consulten el almacén de memoria sin el harness. Habla JSON-RPC 2.0 sobre JSON delimitado por saltos de línea (NDJSON): un objeto JSON por línea, sin tramado `Content-Length`.
|
|
105
|
+
|
|
106
|
+
**Solo lectura.** La base de datos se abre con `readOnly: true` de `node:sqlite` (sin migraciones, sin escrituras WAL, sin incremento del contador de recuperación); si el archivo no existe, devuelve resultados vacíos en lugar de fallar.
|
|
107
|
+
|
|
108
|
+
| Herramienta | Propósito |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `memory_search` | `{query, limit?}` → entradas ordenadas (subcadena insensible a mayúsculas vía el seam del Provider de recuperación) |
|
|
111
|
+
| `memory_stats` | `{}` → `{total, namespaces}` conteo de entradas + resumen por track/scope |
|
|
112
|
+
|
|
113
|
+
Ejecución directa:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
node bin/mcp-server.mjs
|
|
117
|
+
# o, tras npm install: npx dsh-memento-mcp
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
La ruta de la base de datos es `$DSH_MEMENTO_DB_PATH` (absoluta, o relativa a `$DSH_HOME`); por defecto `$DSH_HOME/dsh-memento/memory.db`.
|
|
121
|
+
|
|
122
|
+
Ejemplo para Claude Desktop (`claude_desktop_config.json`):
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"mcpServers": {
|
|
127
|
+
"dsh-memento": {
|
|
128
|
+
"command": "npx",
|
|
129
|
+
"args": ["-y", "dsh-memento-mcp"],
|
|
130
|
+
"env": {
|
|
131
|
+
"DSH_MEMENTO_DB_PATH": "/home/you/.dsh/dsh-memento/memory.db"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
El servidor es de solo lectura: sin red, sin escrituras, sin puerta de aprobación — solo búsqueda y estadísticas.
|
|
139
|
+
|
|
101
140
|
## How it's different
|
|
102
141
|
|
|
103
142
|
| Plugin | Qué es | La diferencia de dsh-memento |
|
package/README.hi.md
CHANGED
|
@@ -82,6 +82,7 @@ dsh --profile web --dump-config | grep -A3 'id: memento'
|
|
|
82
82
|
| `recall.snippetCap` | `5` | `memory_recall` में प्रति-सत्र स्निपेट |
|
|
83
83
|
| `recall.snippetChars` | `300` | `memory_recall` स्निपेट अक्षर |
|
|
84
84
|
| `recall.windowDays` | `30` | `memory_recall` की दिनों में हाल-समय विंडो |
|
|
85
|
+
| `retrieval.vector` | `false` | सिमेंटिक रिकॉल स्विच: `true` से `memory_recall` वेक्टर रिकॉल (फ़ेक हैश एम्बेडिंग) सक्षम होता है जब कोई एम्बेडिंग प्रदाता उपलब्ध हो; अन्यथा सबस्ट्रिंग पर डिग्रेड होता है |
|
|
85
86
|
| `panelEntriesLimit` | `200` | वेब पैनल प्रविष्टि पृष्ठ आकार |
|
|
86
87
|
| `panelAuditLimit` | `20` | वेब पैनल डिफ़ॉल्ट ऑडिट पंक्तियाँ |
|
|
87
88
|
| `auditRetentionDays` | `0` | ऑडिट अवधारण (0 = हमेशा रखें) |
|
|
@@ -98,6 +99,44 @@ dsh --profile web --dump-config | grep -A3 'id: memento'
|
|
|
98
99
|
| `/memory` | command | `list` · `query` · `add` · `remove` · `consolidate` · `proposals` · `budgets` · `audit` · `export` · `import <path>` · `adapters` |
|
|
99
100
|
| web panel | client drawer | केवल-पठन: प्रविष्टियाँ ब्राउज़ करें, खोजें, बजट बार, ऑडिट पूँछ |
|
|
100
101
|
|
|
102
|
+
## MCP server
|
|
103
|
+
|
|
104
|
+
`dsh-memento` एक केवल-पठन stdio **MCP सर्वर** (`dsh-memento-mcp`) भी देता है ताकि बाहरी MCP क्लाइंट (Claude, Codex, …) बिना harness के मेमोरी स्टोर खोज सकें। यह newline-delimited JSON (NDJSON) पर JSON-RPC 2.0 बोलता है — प्रति पंक्ति एक JSON ऑब्जेक्ट, कोई `Content-Length` फ़्रेमिंग नहीं।
|
|
105
|
+
|
|
106
|
+
**केवल-पठन।** डेटाबेस `node:sqlite` के `readOnly: true` से खुलता है (कोई माइग्रेशन नहीं, कोई WAL लेखन नहीं, recall-count में वृद्धि नहीं); अगर फ़ाइल मौजूद नहीं है तो क्रैश के बजाय खाली परिणाम मिलते हैं।
|
|
107
|
+
|
|
108
|
+
| टूल | उद्देश्य |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `memory_search` | `{query, limit?}` → क्रमबद्ध प्रविष्टियाँ (retrieval Provider seam से केस-इनसेंसिटिव सबस्ट्रिंग) |
|
|
111
|
+
| `memory_stats` | `{}` → `{total, namespaces}` प्रविष्टि गणना + track/scope अवलोकन |
|
|
112
|
+
|
|
113
|
+
सीधे चलाएँ:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
node bin/mcp-server.mjs
|
|
117
|
+
# या, npm install के बाद: npx dsh-memento-mcp
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
डेटाबेस पथ `$DSH_MEMENTO_DB_PATH` है (निरपेक्ष, या `$DSH_HOME` के सापेक्ष); डिफ़ॉल्ट `$DSH_HOME/dsh-memento/memory.db`।
|
|
121
|
+
|
|
122
|
+
Claude Desktop (`claude_desktop_config.json`) उदाहरण:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"mcpServers": {
|
|
127
|
+
"dsh-memento": {
|
|
128
|
+
"command": "npx",
|
|
129
|
+
"args": ["-y", "dsh-memento-mcp"],
|
|
130
|
+
"env": {
|
|
131
|
+
"DSH_MEMENTO_DB_PATH": "/home/you/.dsh/dsh-memento/memory.db"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
सर्वर केवल-पठन है: कोई नेटवर्क नहीं, कोई लेखन नहीं, कोई अनुमोदन द्वार नहीं — केवल खोज और आँकड़े।
|
|
139
|
+
|
|
101
140
|
## How it's different
|
|
102
141
|
|
|
103
142
|
| Plugin | यह क्या है | dsh-memento का अंतर |
|
package/README.md
CHANGED
|
@@ -83,6 +83,7 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). Inval
|
|
|
83
83
|
| `recall.snippetCap` | `5` | `memory_recall` snippets per session |
|
|
84
84
|
| `recall.snippetChars` | `300` | `memory_recall` snippet characters |
|
|
85
85
|
| `recall.windowDays` | `30` | `memory_recall` recency window in days |
|
|
86
|
+
| `retrieval.vector` | `false` | Semantic recall switch: `true` enables `memory_recall` vector recall (fake hash embedding) when an embedding provider is available; otherwise degrades to substring |
|
|
86
87
|
| `panelEntriesLimit` | `200` | Web panel entries page size |
|
|
87
88
|
| `panelAuditLimit` | `20` | Web panel audit rows by default |
|
|
88
89
|
| `auditRetentionDays` | `0` | Audit retention (0 = keep forever) |
|
|
@@ -99,6 +100,44 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). Inval
|
|
|
99
100
|
| `/memory` | command | `list` · `query` · `add` · `remove` · `consolidate` · `proposals` · `budgets` · `audit` · `export` · `import <path>` · `adapters` |
|
|
100
101
|
| web panel | client drawer | Read-only: browse entries, search, budget bars, audit tail |
|
|
101
102
|
|
|
103
|
+
## MCP server
|
|
104
|
+
|
|
105
|
+
`dsh-memento` ships a read-only stdio **MCP server** (`dsh-memento-mcp`) so external MCP clients (Claude, Codex, …) can search the memory store without a harness. It speaks JSON-RPC 2.0 over newline-delimited JSON (NDJSON) — one JSON object per line, no `Content-Length` framing.
|
|
106
|
+
|
|
107
|
+
**Read-only.** The database is opened with `node:sqlite` `readOnly: true` (no migrations, no WAL writes, no recall-count bump); a missing database returns empty results instead of crashing.
|
|
108
|
+
|
|
109
|
+
| Tool | Purpose |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `memory_search` | `{query, limit?}` → ranked entries (case-insensitive substring via the retrieval Provider seam) |
|
|
112
|
+
| `memory_stats` | `{}` → `{total, namespaces}` entry count + per-track/scope overview |
|
|
113
|
+
|
|
114
|
+
Run it directly:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
node bin/mcp-server.mjs
|
|
118
|
+
# or, after npm install: npx dsh-memento-mcp
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The database path is `$DSH_MEMENTO_DB_PATH` (absolute, or relative to `$DSH_HOME`); it defaults to `$DSH_HOME/dsh-memento/memory.db`.
|
|
122
|
+
|
|
123
|
+
Claude Desktop (`claude_desktop_config.json`) example:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"mcpServers": {
|
|
128
|
+
"dsh-memento": {
|
|
129
|
+
"command": "npx",
|
|
130
|
+
"args": ["-y", "dsh-memento-mcp"],
|
|
131
|
+
"env": {
|
|
132
|
+
"DSH_MEMENTO_DB_PATH": "/home/you/.dsh/dsh-memento/memory.db"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The server is read-only: no network, no writes, no approval gate — search and stats only.
|
|
140
|
+
|
|
102
141
|
## How it's different
|
|
103
142
|
|
|
104
143
|
| Plugin | What it is | dsh-memento's difference |
|
package/README.pt.md
CHANGED
|
@@ -82,6 +82,7 @@ Todos os parâmetros são campos Schemastery `Config` (alteráveis pelo cordis.y
|
|
|
82
82
|
| `recall.snippetCap` | `5` | Fragmentos por sessão no `memory_recall` |
|
|
83
83
|
| `recall.snippetChars` | `300` | Caracteres de fragmento no `memory_recall` |
|
|
84
84
|
| `recall.windowDays` | `30` | Janela de recência em dias do `memory_recall` |
|
|
85
|
+
| `retrieval.vector` | `false` | Interruptor de recuperação semântica: `true` ativa a recuperação vetorial do `memory_recall` (embedding de hash falso) quando há um provedor de embedding; caso contrário degrada para substring |
|
|
85
86
|
| `panelEntriesLimit` | `200` | Tamanho de página de entradas do painel web |
|
|
86
87
|
| `panelAuditLimit` | `20` | Linhas de auditoria do painel web por padrão |
|
|
87
88
|
| `auditRetentionDays` | `0` | Retenção de auditoria (0 = manter para sempre) |
|
|
@@ -98,6 +99,44 @@ Todos os parâmetros são campos Schemastery `Config` (alteráveis pelo cordis.y
|
|
|
98
99
|
| `/memory` | command | `list` · `query` · `add` · `remove` · `consolidate` · `proposals` · `budgets` · `audit` · `export` · `import <path>` · `adapters` |
|
|
99
100
|
| web panel | client drawer | Somente leitura: navegar entradas, buscar, barras de orçamento, cauda de auditoria |
|
|
100
101
|
|
|
102
|
+
## MCP server
|
|
103
|
+
|
|
104
|
+
O `dsh-memento` inclui um **servidor MCP** stdio somente-leitura (`dsh-memento-mcp`) para que clientes MCP externos (Claude, Codex, …) pesquisem o armazenamento de memória sem o harness. Ele fala JSON-RPC 2.0 sobre JSON delimitado por novas linhas (NDJSON): um objeto JSON por linha, sem enquadramento `Content-Length`.
|
|
105
|
+
|
|
106
|
+
**Somente leitura.** O banco é aberto com `readOnly: true` do `node:sqlite` (sem migrações, sem gravações WAL, sem incremento do contador de recall); um banco ausente retorna resultados vazios em vez de falhar.
|
|
107
|
+
|
|
108
|
+
| Ferramenta | Propósito |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `memory_search` | `{query, limit?}` → entradas ordenadas (substring sem distinção de maiúsculas via o seam do Provider de recuperação) |
|
|
111
|
+
| `memory_stats` | `{}` → `{total, namespaces}` contagem de entradas + visão geral por track/scope |
|
|
112
|
+
|
|
113
|
+
Execução direta:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
node bin/mcp-server.mjs
|
|
117
|
+
# ou, após npm install: npx dsh-memento-mcp
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
O caminho do banco é `$DSH_MEMENTO_DB_PATH` (absoluto, ou relativo a `$DSH_HOME`); padrão `$DSH_HOME/dsh-memento/memory.db`.
|
|
121
|
+
|
|
122
|
+
Exemplo para o Claude Desktop (`claude_desktop_config.json`):
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"mcpServers": {
|
|
127
|
+
"dsh-memento": {
|
|
128
|
+
"command": "npx",
|
|
129
|
+
"args": ["-y", "dsh-memento-mcp"],
|
|
130
|
+
"env": {
|
|
131
|
+
"DSH_MEMENTO_DB_PATH": "/home/you/.dsh/dsh-memento/memory.db"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
O servidor é somente-leitura: sem rede, sem gravações, sem porta de aprovação — apenas busca e estatísticas.
|
|
139
|
+
|
|
101
140
|
## How it's different
|
|
102
141
|
|
|
103
142
|
| Plugin | O que é | A diferença do dsh-memento |
|
package/README.zh.md
CHANGED
|
@@ -82,6 +82,7 @@ dsh --profile web --dump-config | grep -A3 'id: memento'
|
|
|
82
82
|
| `recall.snippetCap` | `5` | `memory_recall` 每个会话的片段数 |
|
|
83
83
|
| `recall.snippetChars` | `300` | `memory_recall` 片段字符数 |
|
|
84
84
|
| `recall.windowDays` | `30` | `memory_recall` 近期窗口天数 |
|
|
85
|
+
| `retrieval.vector` | `false` | 语义召回开关:`true` 且探测到嵌入 provider 时 `memory_recall` 走向量召回(伪嵌入),否则优雅降级回 substring |
|
|
85
86
|
| `panelEntriesLimit` | `200` | Web 面板条目分页大小 |
|
|
86
87
|
| `panelAuditLimit` | `20` | Web 面板默认审计行数 |
|
|
87
88
|
| `auditRetentionDays` | `0` | 审计保留天数(0 = 永久保留) |
|
|
@@ -98,6 +99,44 @@ dsh --profile web --dump-config | grep -A3 'id: memento'
|
|
|
98
99
|
| `/memory` | command | `list` · `query` · `add` · `remove` · `consolidate` · `proposals` · `budgets` · `audit` · `export` · `import <path>` · `adapters` |
|
|
99
100
|
| web panel | client drawer | 只读:浏览条目、搜索、预算条、审计尾部 |
|
|
100
101
|
|
|
102
|
+
## MCP server
|
|
103
|
+
|
|
104
|
+
`dsh-memento` 附带一个只读 stdio **MCP 服务器**(`dsh-memento-mcp`),让外部 MCP 客户端(Claude、Codex 等)无需 harness 即可检索记忆库。它通过 newline-delimited JSON(NDJSON)承载 JSON-RPC 2.0——每行一个 JSON 对象,不支持 `Content-Length` 分帧。
|
|
105
|
+
|
|
106
|
+
**只读。** 数据库以 `node:sqlite` 的 `readOnly: true` 打开(不跑迁移、不写 WAL、不 bump recall-count);库文件不存在时返回空结果而非崩溃。
|
|
107
|
+
|
|
108
|
+
| 工具 | 用途 |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `memory_search` | `{query, limit?}` → 排序后的条目(经检索 Provider seam 的大小写不敏感子串检索) |
|
|
111
|
+
| `memory_stats` | `{}` → `{total, namespaces}` 条目总数 + 按轨道/作用域概览 |
|
|
112
|
+
|
|
113
|
+
直接运行:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
node bin/mcp-server.mjs
|
|
117
|
+
# 或 npm 安装后:npx dsh-memento-mcp
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
数据库路径取自 `$DSH_MEMENTO_DB_PATH`(绝对路径,或相对 `$DSH_HOME`);默认为 `$DSH_HOME/dsh-memento/memory.db`。
|
|
121
|
+
|
|
122
|
+
Claude Desktop(`claude_desktop_config.json`)配置示例:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"mcpServers": {
|
|
127
|
+
"dsh-memento": {
|
|
128
|
+
"command": "npx",
|
|
129
|
+
"args": ["-y", "dsh-memento-mcp"],
|
|
130
|
+
"env": {
|
|
131
|
+
"DSH_MEMENTO_DB_PATH": "/home/you/.dsh/dsh-memento/memory.db"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
服务器只读:无网络、无写入、无审批门——仅检索与统计。
|
|
139
|
+
|
|
101
140
|
## How it's different
|
|
102
141
|
|
|
103
142
|
| Plugin | 是什么 | dsh-memento 的差异 |
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// dsh-memento stdio MCP server launcher. Reads the memory database read-only
|
|
3
|
+
// and serves memory_search / memory_stats over newline-delimited JSON-RPC 2.0
|
|
4
|
+
// on stdio. No harness, no network, no write path — the database is opened
|
|
5
|
+
// with node:sqlite readOnly:true and a missing file yields empty results.
|
|
6
|
+
//
|
|
7
|
+
// Database path: $DSH_MEMENTO_DB_PATH (absolute, or relative to $DSH_HOME);
|
|
8
|
+
// defaults to $DSH_HOME/dsh-memento/memory.db (falls back to ~/.dsh).
|
|
9
|
+
import { readFileSync } from 'node:fs'
|
|
10
|
+
import { resolveDbPath } from '../lib/store.mjs'
|
|
11
|
+
import { createMcpServer, ReadOnlyMemoryStore, runStdioServer } from '../lib/mcp.mjs'
|
|
12
|
+
|
|
13
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
|
|
14
|
+
const dbPath = resolveDbPath(process.env.DSH_MEMENTO_DB_PATH ?? '')
|
|
15
|
+
const store = new ReadOnlyMemoryStore(dbPath)
|
|
16
|
+
const server = createMcpServer({ store, version: pkg.version })
|
|
17
|
+
|
|
18
|
+
runStdioServer(server)
|
package/index.mjs
CHANGED
|
@@ -43,6 +43,8 @@ import { renderSnapshot, visibleEntries, visibleProposals } from './lib/snapshot
|
|
|
43
43
|
import { openMemoryStore, resolveDbPath } from './lib/store.mjs'
|
|
44
44
|
import { workspaceKeyOf, agentKeyOf } from './lib/workspace.mjs'
|
|
45
45
|
import { extractEventText } from './lib/extract.mjs'
|
|
46
|
+
import { EmbeddingProviderRegistry, FakeEmbeddingProvider } from './lib/embedding.mjs'
|
|
47
|
+
import { RetrievalProviderRegistry, SubstringRetriever, VectorRetriever, detectVectorBackend } from './lib/retrieval.mjs'
|
|
46
48
|
|
|
47
49
|
/**
|
|
48
50
|
* @typedef {import('./types.js').MemoryEntry} MemoryEntry
|
|
@@ -55,6 +57,7 @@ import { extractEventText } from './lib/extract.mjs'
|
|
|
55
57
|
* @typedef {object} StoreHandle - ctx.memory 依赖的 Provider 面。
|
|
56
58
|
* @property {(filter?: {track?: string, scope?: string, text?: string, limit?: number}) => MemoryQueryResult} queryEntries
|
|
57
59
|
* @property {() => MemoryEntry[]} listEntries
|
|
60
|
+
* @property {(ids: string[]) => void} bumpRecall
|
|
58
61
|
* @property {(track: string, scope: string, match: string, opts?: {agentKey?: string, workspaceKey?: string}) => MemoryEntry[]} matchCandidates
|
|
59
62
|
* @property {(track: string, scope: string) => number} usage
|
|
60
63
|
* @property {(input: object) => MemoryEntry} insertEntry
|
|
@@ -91,6 +94,7 @@ import { extractEventText } from './lib/extract.mjs'
|
|
|
91
94
|
* @property {number} [commandListLimit]
|
|
92
95
|
* @property {number} [commandAuditLimit]
|
|
93
96
|
* @property {{historyLimitDefault?: number, snippetCap?: number, snippetChars?: number, windowDays?: number}} [recall]
|
|
97
|
+
* @property {{vector?: boolean}} [retrieval]
|
|
94
98
|
* @property {number} [panelEntriesLimit]
|
|
95
99
|
* @property {number} [panelAuditLimit]
|
|
96
100
|
* @property {number} [auditRetentionDays]
|
|
@@ -145,6 +149,8 @@ export const DEFAULT_SNAPSHOT_ORDER = -50
|
|
|
145
149
|
* @property {number} [commandAuditLimit] /memory audit 单次渲染审计行上限(默认 10)。
|
|
146
150
|
* @property {{historyLimitDefault?: number, snippetCap?: number, snippetChars?: number, windowDays?: number}} [recall]
|
|
147
151
|
* memory_recall 历史段默认值(默认 8/5/300/30)。
|
|
152
|
+
* @property {{vector?: boolean}} [retrieval] 语义召回开关(默认 false:substring 主路径;
|
|
153
|
+
* true 且探测到 embedding provider 时 memory_recall 走向量召回,否则优雅降级回 substring)。
|
|
148
154
|
* @property {number} [panelEntriesLimit] 面板条目页上限与钳制(默认 200)。
|
|
149
155
|
* @property {number} [panelAuditLimit] 面板审计默认条数(默认 20;上限 200 为协议常量)。
|
|
150
156
|
* @property {number} [auditRetentionDays] 审计保留天数(默认 0 = 不限)。
|
|
@@ -177,6 +183,9 @@ export const Config = Schema.object({
|
|
|
177
183
|
snippetChars: Schema.number().default(300),
|
|
178
184
|
windowDays: Schema.number().default(30),
|
|
179
185
|
}),
|
|
186
|
+
retrieval: Schema.object({
|
|
187
|
+
vector: Schema.boolean().default(false),
|
|
188
|
+
}),
|
|
180
189
|
panelEntriesLimit: Schema.number().default(200),
|
|
181
190
|
panelAuditLimit: Schema.number().default(20),
|
|
182
191
|
auditRetentionDays: Schema.number().default(0),
|
|
@@ -656,6 +665,9 @@ export function apply(ctx, /** @type {PluginConfig} */ config = {}) {
|
|
|
656
665
|
snippetChars: config.recall?.snippetChars ?? 300,
|
|
657
666
|
windowDays: config.recall?.windowDays ?? 30,
|
|
658
667
|
},
|
|
668
|
+
retrieval: {
|
|
669
|
+
vector: config.retrieval?.vector ?? false,
|
|
670
|
+
},
|
|
659
671
|
panelEntriesLimit: config.panelEntriesLimit ?? 200,
|
|
660
672
|
panelAuditLimit: config.panelAuditLimit ?? 20,
|
|
661
673
|
auditRetentionDays: config.auditRetentionDays ?? 0,
|
|
@@ -731,6 +743,21 @@ export function apply(ctx, /** @type {PluginConfig} */ config = {}) {
|
|
|
731
743
|
ctx.effect(() => adapters.register(adapter), `dsh-memento.adapter.${adapter.id}`)
|
|
732
744
|
}
|
|
733
745
|
|
|
746
|
+
// embedding Provider seam(ctx.memoryEmbedding):注册表 + 默认确定性伪嵌入
|
|
747
|
+
// Provider(零依赖,接口级演示;真实嵌入由可选 provider 注册)。register 可逆,
|
|
748
|
+
// 经 ctx.effect 随插件生命周期自动回收。
|
|
749
|
+
const embeddings = new EmbeddingProviderRegistry()
|
|
750
|
+
ctx.provide('memoryEmbedding', embeddings)
|
|
751
|
+
ctx.effect(() => embeddings.register(new FakeEmbeddingProvider()), 'dsh-memento.embedding.fake-hash')
|
|
752
|
+
|
|
753
|
+
// retrieval Provider seam(ctx.memoryRetrieval):内置 substring 检索器(零依赖
|
|
754
|
+
// 主路径)+ 可选 vector 检索器。vector 仅当 Config.retrieval.vector=true 且探测到
|
|
755
|
+
// embedding provider 时启用;否则优雅降级回 substring(retriever 保持 null)。
|
|
756
|
+
const retrievers = new RetrievalProviderRegistry()
|
|
757
|
+
ctx.provide('memoryRetrieval', retrievers)
|
|
758
|
+
ctx.effect(() => retrievers.register(new SubstringRetriever()), 'dsh-memento.retrieval.substring')
|
|
759
|
+
const resolvedRetriever = resolved.retrieval.vector === true ? setupVectorRetriever(ctx, retrievers, embeddings) : null
|
|
760
|
+
|
|
734
761
|
// 审批 answerer:认领本插件的记忆写请求并按粒度策略裁决(writePolicies 精确键 >
|
|
735
762
|
// track/scope > 全局 writePolicy;prepend 保证 auto/off 的确定性先于 UI answerer;
|
|
736
763
|
// 会话级 never 策略在审批服务内部先裁决,任何 answerer 都无法绕过)。
|
|
@@ -797,7 +824,7 @@ export function apply(ctx, /** @type {PluginConfig} */ config = {}) {
|
|
|
797
824
|
// V2 观察面:/memory 命令(用户触发)、memory_recall 工具、面板 JSON 路由。
|
|
798
825
|
// commands/webServer 为可选服务,缺失(headless)自动跳过。
|
|
799
826
|
registerCommands(ctx, service)
|
|
800
|
-
ctx.tools.register(/** @type {import('@deepseek-ai/dsh-tools').ToolDefinition} */ (makeMemoryRecallTool(service, ctx, resolved.recall, resolved.language)))
|
|
827
|
+
ctx.tools.register(/** @type {import('@deepseek-ai/dsh-tools').ToolDefinition} */ (makeMemoryRecallTool(service, ctx, resolved.recall, resolved.language, resolvedRetriever)))
|
|
801
828
|
registerWebRoutes(ctx, service, resolved)
|
|
802
829
|
|
|
803
830
|
// auto-capture:监听会话事件火线,压缩结束后生成记忆提案(只落提案,不写记忆、不调模型)。
|
|
@@ -807,6 +834,24 @@ export function apply(ctx, /** @type {PluginConfig} */ config = {}) {
|
|
|
807
834
|
})
|
|
808
835
|
}
|
|
809
836
|
|
|
837
|
+
/**
|
|
838
|
+
* 按 Config.retrieval.vector 探测并装配 vector 检索器:探测到 embedding provider
|
|
839
|
+
* 才注册 VectorRetriever 并返回之;否则优雅降级(返回 null,调用方回退 substring
|
|
840
|
+
* 主路径),绝不响亮失败——vector 是可选后端,缺 embedding 不构成配置错误。
|
|
841
|
+
* @param {import('@deepseek-ai/cordis').Context} ctx - Cordis 上下文(注册 effect)。
|
|
842
|
+
* @param {import('./lib/retrieval.mjs').RetrievalProviderRegistry} retrievers - 检索注册表。
|
|
843
|
+
* @param {import('./lib/embedding.mjs').EmbeddingProviderRegistry} embeddings - 嵌入注册表。
|
|
844
|
+
* @returns {import('./lib/retrieval.mjs').RetrievalProvider | null} vector 检索器或 null(降级)。
|
|
845
|
+
*/
|
|
846
|
+
function setupVectorRetriever(ctx, retrievers, embeddings) {
|
|
847
|
+
const embedding = embeddings.get('fake-hash')
|
|
848
|
+
const probe = detectVectorBackend({ embedding })
|
|
849
|
+
if (!probe.available) return null
|
|
850
|
+
const retriever = new VectorRetriever({ embedding })
|
|
851
|
+
ctx.effect(() => retrievers.register(retriever), 'dsh-memento.retrieval.vector')
|
|
852
|
+
return retriever
|
|
853
|
+
}
|
|
854
|
+
|
|
810
855
|
/**
|
|
811
856
|
* auto-capture 提案生成:缓存每会话最近的 compaction/summary 文本,compaction/end
|
|
812
857
|
* 成功时截断落 proposals 表((session_id, kind) 幂等;pending 满则弃新)。
|
|
@@ -1439,9 +1484,11 @@ function renderEntryLine(/** @type {{track: string, scope: string, workspaceKey?
|
|
|
1439
1484
|
* @param {import('@deepseek-ai/cordis').Context} ctx - Cordis 上下文(查 sessionQuery)。
|
|
1440
1485
|
* @param {{historyLimitDefault: number, snippetCap: number, snippetChars: number, windowDays: number}} recall - Config.recall(默认值)。
|
|
1441
1486
|
* @param {'en'|'zh'} [language] - 'en' | 'zh'。
|
|
1487
|
+
* @param {import('./lib/retrieval.mjs').RetrievalProvider | null} [retriever] - 语义检索器;
|
|
1488
|
+
* null = 默认 substring 主路径(走 service.query 的 SQL instr)。
|
|
1442
1489
|
* @returns {object} 工具定义。
|
|
1443
1490
|
*/
|
|
1444
|
-
export function makeMemoryRecallTool(service, ctx, recall, language = 'en') {
|
|
1491
|
+
export function makeMemoryRecallTool(service, ctx, recall, language = 'en', retriever = null) {
|
|
1445
1492
|
const description = language === 'zh'
|
|
1446
1493
|
? [
|
|
1447
1494
|
'对记忆与会话历史的两段式召回:返回 (1) dsh-memento 库中与查询匹配的有界记忆条目,以及 (2) 经 session-query 服务的近期会话历史匹配。',
|
|
@@ -1529,15 +1576,13 @@ export function makeMemoryRecallTool(service, ctx, recall, language = 'en') {
|
|
|
1529
1576
|
},
|
|
1530
1577
|
execute: /** @type {(args: any, exec: any) => Promise<any>} */ (async (args, exec) => {
|
|
1531
1578
|
exec.signal.throwIfAborted()
|
|
1532
|
-
const
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
},
|
|
1540
|
-
)
|
|
1579
|
+
const sessionId = /** @type {string | undefined} */ (exec.agent?.session?.id)
|
|
1580
|
+
const session = /** @type {MemorySessionLike | null | undefined} */ (exec.agent?.session ?? null)
|
|
1581
|
+
const agentKey = agentKeyOf(/** @type {string | undefined} */ (exec.agent?.session?.header?.agentPreset))
|
|
1582
|
+
const limit = args.memoryLimit ?? 10
|
|
1583
|
+
const memory = retriever === null
|
|
1584
|
+
? service.query({ text: args.query, limit }, { sessionId, session, agentKey })
|
|
1585
|
+
: recallViaRetriever(service, retriever, args.query, limit, { sessionId, session, agentKey })
|
|
1541
1586
|
const history = await recallHistory(
|
|
1542
1587
|
ctx,
|
|
1543
1588
|
args.query,
|
|
@@ -1561,6 +1606,40 @@ export function makeMemoryRecallTool(service, ctx, recall, language = 'en') {
|
|
|
1561
1606
|
})
|
|
1562
1607
|
}
|
|
1563
1608
|
|
|
1609
|
+
/**
|
|
1610
|
+
* 语义召回路径(retrieval seam 的 Consumer 面):可见条目 → 检索器排序 → 召回计数
|
|
1611
|
+
* + 审计。与 service.query 的子串路径对齐:命中页召回计数 +1(bumpRecall)、带
|
|
1612
|
+
* sessionId 时记 recalled 审计行。可见集 = 会话 cwd 工作区层 + 共享/本 agent 层
|
|
1613
|
+
* (与快照 visibleEntries 同语义)。
|
|
1614
|
+
* @param {MemoryService} service - ctx.memory(提供 store)。
|
|
1615
|
+
* @param {import('./lib/retrieval.mjs').RetrievalProvider} retriever - 语义检索器。
|
|
1616
|
+
* @param {string} query - 检索词。
|
|
1617
|
+
* @param {number} limit - 返回上限。
|
|
1618
|
+
* @param {{sessionId?: string, session?: MemorySessionLike | null, agentKey: string}} opts - {sessionId, session, agentKey}。
|
|
1619
|
+
* @returns {MemoryQueryResult}。
|
|
1620
|
+
*/
|
|
1621
|
+
function recallViaRetriever(service, retriever, query, limit, opts) {
|
|
1622
|
+
const workspaceKey = workspaceKeyOf(/** @type {string | undefined} */ (opts.session?.header?.cwd))
|
|
1623
|
+
const entries = visibleEntries(
|
|
1624
|
+
/** @type {Array<{id: string, track: string, scope: string, workspaceKey: string, agentKey: string, text: string, createdAt: number}>} */ (service.store.listEntries()),
|
|
1625
|
+
workspaceKey,
|
|
1626
|
+
opts.agentKey,
|
|
1627
|
+
)
|
|
1628
|
+
const ranked = /** @type {MemoryEntry[]} */ (retriever.retrieve(query, entries))
|
|
1629
|
+
const shown = ranked.slice(0, limit)
|
|
1630
|
+
service.store.bumpRecall(shown.map((entry) => entry.id))
|
|
1631
|
+
if (opts.sessionId !== undefined) {
|
|
1632
|
+
service.store.auditAppend({
|
|
1633
|
+
action: 'recalled',
|
|
1634
|
+
text: query,
|
|
1635
|
+
outcome: 'ok',
|
|
1636
|
+
source: /** @type {string} */ (service.sourceLabel),
|
|
1637
|
+
sessionId: opts.sessionId,
|
|
1638
|
+
})
|
|
1639
|
+
}
|
|
1640
|
+
return { entries: shown, total: ranked.length, truncated: ranked.length > shown.length }
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1564
1643
|
/**
|
|
1565
1644
|
* 近期会话历史召回(sessionQuery 可选;rc.6 记录形状 = {header:{id}},事件为元数据记录)。
|
|
1566
1645
|
* 服务端下推:filterSessions 以会话 cwd(原值直传,harness 按存储值比较)与
|
package/lib/constants.mjs
CHANGED
|
@@ -56,6 +56,8 @@ export const ERROR_CODES = {
|
|
|
56
56
|
STORE_UNSUPPORTED_VERSION: 'STORE_UNSUPPORTED_VERSION',
|
|
57
57
|
ADAPTER_NOT_FOUND: 'ADAPTER_NOT_FOUND',
|
|
58
58
|
ADAPTER_PAYLOAD: 'ADAPTER_PAYLOAD',
|
|
59
|
+
EMBEDDING_NOT_FOUND: 'EMBEDDING_NOT_FOUND',
|
|
60
|
+
RETRIEVAL_NOT_FOUND: 'RETRIEVAL_NOT_FOUND',
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
/** 会话事件名(SessionEventMap 声明合并的词汇表;运行时按已知类型自适应派发)。 */
|