@yottameta/yotta-memory 0.8.5 → 0.9.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 CHANGED
@@ -1,8 +1,29 @@
1
+ ## v0.9.0 (2026-09-01)
2
+
3
+ 召回质量与上下文选择升级。
4
+
5
+ - **两阶段召回**:词法候选 + 可选本地 embedding 插件候选,统一按语义分与效用分融合排序。
6
+ - **可选 embedding 插件**:`--embedding <command>` 或 `config set embedding_cmd <command>`;本地子进程、`stdin/stdout` JSON 协议、超时默认 3000ms、失败自动降级为词法召回。
7
+ - **任务感知上下文**:`context --focus <关键词>` 新增任务相关记忆段,按身份 / 边界 / 承诺 / 画像 / 任务记忆 / 近期记忆优先级组装。
8
+ - **选择解释**:`context --explain` 输出 included / dropped 与原因;`recall --explain` 继续显示命中理由并补充 embedding 分。
9
+ - **MCP 同步**:`recall` / `search` 新增 `embedding` / `embeddingTimeout` / `explain` 参数;新增 `context` 工具。
10
+ - **版本四件对齐**:package.json / SKILL.md / CHANGELOG.md / 引擎 VERSION = 0.9.0。
11
+
12
+ ## v0.8.7 (2026-09-01)
13
+
14
+ 评测反馈优化(文档 + 错误提示,功能不变)。
15
+
16
+ - 新增 `references/faq.md`:10 条常见问题 / 避坑(类型选错、私密区加密、多智能体权限、记忆找不到、忘记主口令、局域网连接、MCP 加载、记忆库位置、跨会话恢复、备份迁移)。
17
+ - 错误提示友好化:顶层错误附「修复建议」人话(检查 memory_home、主口令与恢复钥匙等)。
18
+ - README 中英:新增「命令输出样例」(init / remember / recall / context 屏幕输出示意)+「常见问题 FAQ 速查」。
19
+ - SKILL.md:新增「常见问题 FAQ(速查)」小节,指向 references/faq.md。
20
+ - 版本四件对齐 0.8.7(package.json / SKILL.md / CHANGELOG / 引擎 VERSION)。
21
+
1
22
  # 更新日志
2
23
 
3
24
  ## v0.8.5 (2026-08-29)
4
25
 
5
- 安全修复(SkillHub 安全扫描发现高危,修复后三源同步升版):
26
+ 安全修复(安全扫描发现高危,修复后三源同步升版):
6
27
 
7
28
  - **MCP 命令执行入口封死**:MCP `distill` 不再接受 `--model`(`callTool` 在工具边界直接拒绝),远端智能体无法再借 MCP 在引擎主机执行任意命令。
8
29
  - **MCP 任意路径读写封死**:MCP `export` / `import` 的 `out` / `src` 必须落在记忆库根内(新增 `resolveWithinRoot` 校验,防 `..` 穿越),库外路径直接拒绝,与「远程只能读写记忆」承诺对齐。
@@ -19,7 +40,7 @@
19
40
 
20
41
  ## v0.8.3 (2026-08-28)
21
42
 
22
- 中英双语 README 对齐(老张拍板「英文门面 + 中文全档」):
43
+ 中英双语 README 对齐(确定「英文门面 + 中文全档」):
23
44
 
24
45
  - **README.md 改为英文**:作为 GitHub / npm / ClawHub 首页的英文门面(翻译 + 精简,覆盖定位 / 核心价值 / 四类型 / 权限隔离 / 身份 / 语义检索 / 生命周期 / 对比 / 安装(CLI+技能双装)/ 升级 / CLI 用法 / 局域网共享 / 开发校验全流程)。
25
46
  - **新增 README.zh-CN.md**:原中文完整主文档整体平移,顶部加语言切换链接。
package/README.md CHANGED
@@ -23,6 +23,8 @@
23
23
 
24
24
  > 📖 The user-facing operations manual lives in [USER_GUIDE.md](USER_GUIDE.md).
25
25
 
26
+ > 🆕 **v0.9.0**: recall quality + context selection — optional local embedding plugin, `context --focus`, and `--explain` selection trace.
27
+
26
28
  > 🆕 **v0.8.5**: security hardening — MCP `distill` no longer accepts `--model`; MCP `export` / `import` paths are restricted to the memory root; CLI `distill --model` no longer shells out (allowlist-based).
27
29
 
28
30
  ## Core value
@@ -71,18 +73,19 @@ Each agent has a globally unique agent ID: it is the ownership key for private m
71
73
  - **No token locally**: local CLI / stdio direct connection bypasses the network and does not validate tokens; identity is declared via the agent's MCP `env.YOTTA_AGENT_ID`.
72
74
  - **Private memory requires an owner**: writing PREF / BOUND / COMMIT without declaring identity is rejected (public FACT is unaffected), mechanically preventing ID spoofing.
73
75
 
74
- ### Profile & start-of-work context (v0.6.0)
76
+ ### Profile & start-of-work context (v0.6.0 + v0.9.0)
75
77
 
76
78
  - **profile**: aggregates `private/<owner>/` PREF / BOUND / COMMIT verbatim, grouped by type + subject + tags, written to `profile.md`; the engine infers nothing — profile conclusions are formed internally by the AI per the "memory discipline", never pasted as labels.
77
- - **context**: one-shot start-of-work package — multi-agent integration rules + identity + user profile digest + recent memory (importance-sorted) + boundary reminders + commitments/anchors; supports `--budget` character budget (constant tokens, does not grow with memory).
79
+ - **context**: one-shot start-of-work package — multi-agent integration rules + identity + user profile digest + optional task-focused memory (`--focus`) + recent memory (importance-sorted) + boundary reminders + commitments/anchors; supports `--budget` character budget and `--explain` selection trace.
78
80
  - **Memory discipline**: SKILL.md embeds a rule layer (type red lines / proactive trigger capture / know-the-user three stages / psychological grounding & alignment / bottom lines / host isolation / anti-patterns).
79
81
 
80
- ### Retrieval: semantic search (v0.8.0) + Chinese tokenization scoring
82
+ ### Retrieval: semantic search (v0.8.0 + v0.9.0 embedding)
81
83
 
82
84
  - `remember` auto-builds the `index.json` index (version 4 since v0.8.1 with field weighting and pinyin tokens; public indexes over 5000 entries shard by year `index-<year>.json`; old indexes rebuild on first recall); recall defaults to semantic search — exact (field weighting: subject×3 / tags×2 / statement×1) + synonyms (built-in wordlist, extensible) + pinyin (full / initials, built-in 3755 common characters) + fuzzy (edit distance ≤ 2) + substring fallback, blended with utility score (0.65 × semantic + 0.35 × utility), zero-dependency.
83
85
  - `recall --explain`: shows each hit's reason (exact / synonym / pinyin / fuzzy + field) and utility components.
84
86
  - **Candidate pre-filtering (v0.8.1)**: before semantic scoring, index tokens coarsely filter the candidate set (exact / synonym / pinyin / substring / fuzzy length gate) — hit set identical to v0.8.0; the `view` platform paginates by offset, fetching only the current page.
85
- - Optional embedding plugin: protocol reserved (implemented in v0.9); without a plugin it degrades to zero-dependency automatically.
87
+ - **Optional embedding plugin (v0.9.0)**: `recall --embedding <command>` or `config set embedding_cmd <command>` runs a local subprocess that accepts JSON on stdin and returns vectors on stdout; results are blended into the same ranking. Failures, timeouts, or malformed output automatically fall back to zero-dependency lexical recall.
88
+ - **Embedding cache**: vectors are cached under each memory root at `.embed/cache.json`, keyed by `sha256(command + text)`; only vectors are stored, not plaintext.
86
89
  - The `tokens` field of `index.json` is a Chinese-tokenization term-frequency table (for TF scoring), **not** an access credential; auth tokens live at `.server/tokens.json`.
87
90
  - Supports keywords, `--type` filter, `--limit` truncation, project-level priority.
88
91
  - **Root de-duplication (v0.6.5)**: when project and user roots point at the same directory, recall / context uniquify roots so a file shows once.
@@ -100,6 +103,21 @@ Each agent has a globally unique agent ID: it is the ownership key for private m
100
103
  - `export` / `import`: export the whole store to JSON / import from JSON; an intermediate format for migration and backup.
101
104
  - git: the whole store can be version-controlled — rollback / audit / team sync.
102
105
 
106
+ ## FAQ (quick reference)
107
+
108
+ | Question | Answer (see references/faq.md) |
109
+ |---|---|
110
+ | Wrong memory type? | Hint only; forget and rewrite; --no-hint to disable |
111
+ | Private encryption? | init encrypts by default (master password + recovery key); migrate to encrypt a plaintext store |
112
+ | Multi-agent isolation? | FACT public; PREF/BOUND/COMMIT per-owner, grant via key authorize / view |
113
+ | Memory not found? | config get -> reindex -> recall/search |
114
+ | Lost master password? | reset-password with recovery key |
115
+ | LAN connect? | lan enable + token new; client url+token |
116
+ | MCP not loaded? | Check mcpServers + restart; use CLI directly |
117
+ | Where is the store? | config get; project-level .yottamemory |
118
+ | Cross-session resume? | Run context + recall at session start |
119
+ | Backup / migrate? | export / import |
120
+
103
121
  ## Comparison with other approaches
104
122
 
105
123
  > Compared by solution type, not product names. Criteria: data sovereignty, deployment cost, permission boundaries, auditability, cross-agent ability.
@@ -151,6 +169,42 @@ bash install.sh --list # list agents -> default directories
151
169
  ```
152
170
 
153
171
  > Method 1 uses the npm registry (npmmirror / npmjs) and does not depend on GitHub; Methods 2/3 use GitHub and may fail without a proxy in China.
172
+ ## Example outputs
173
+
174
+ > Illustrative (actual output may vary by version) so you know what to expect on screen.
175
+
176
+ **init**:
177
+
178
+ ```text
179
+ Memory store initialized: D:/.yottamemory
180
+ Master password set; recovery key: xxxx-xxxx-xxxx-xxxx (keep it safe)
181
+ ```
182
+
183
+ **remember** (with verify):
184
+
185
+ ```text
186
+ Recorded: D:/.yottamemory/facts/2026-09-01-0001.md
187
+ [verify] read-back OK: facts/2026-09-01-0001.md
188
+ ```
189
+
190
+ **recall**:
191
+
192
+ ```text
193
+ 3 memories (top 3):
194
+ [FACT] subject: statement... (D:/.yottamemory/facts/xxx.md)
195
+ ```
196
+
197
+ **context**:
198
+
199
+ ```text
200
+ # Start-of-work context (yotta-memory context)
201
+ ## 1. Identity
202
+ ## 2. User profile summary
203
+ ## 3. Recent memories (top 10 by activity)
204
+ ## 4. Boundaries (BOUND)
205
+ ## 5. Commitments / anchors (COMMIT)
206
+ ```
207
+
154
208
  ## Upgrade
155
209
 
156
210
  Two upgrade paths match the two install paths:
@@ -170,9 +224,9 @@ npm i -g @yottameta/yotta-memory
170
224
  |---|---|
171
225
  | `yotta-memory init [--project] [--dir <dir>]` | Initialize the store (default user-level `~/.yottamemory/`; --dir sets an explicit location) |
172
226
  | `yotta-memory remember <type> <subject> <statement> [--owner <id>] [--source <src>] [--weight <0..>] [--verify] [--no-hint]` | Write a memory (same subject+statement auto-updates; --owner marks ownership; --source records origin; --weight importance, dedup takes max; --verify read-back; --no-hint disables type hints) |
173
- | `yotta-memory recall [keywords] [--type T] [--limit N] [--agent <id>] [--owner <id>] [--all] [--unsafe]` | Search memory (index + TF scoring, partitioned reads; cross-reading other agents' private is denied by default, needs grant / identity=user / `--unsafe`; project-level priority) |
227
+ | `yotta-memory recall [keywords] [--type T] [--limit N] [--agent <id>] [--owner <id>] [--all] [--unsafe] [--explain] [--semantic] [--embedding <cmd>] [--embedding-timeout N]` | Search memory (semantic + utility ranking; optional local embedding plugin; partitioned reads; cross-reading other agents' private is denied by default, needs grant / identity=user / `--unsafe`; project-level priority) |
174
228
  | `yotta-memory profile [--owner <id>]` | Generate a user profile (aggregates `private/<owner>` verbatim, zero inference, writes `profile.md`; cross-owner denied by default) |
175
- | `yotta-memory context [--limit N] [--owner <id>] [--budget N]` | Generate the start-of-work package (identity + multi-agent rules + profile + recent memory + boundaries + commitments; --budget budgets recent-memory chars, constant tokens) |
229
+ | `yotta-memory context [--limit N] [--owner <id>] [--budget N] [--focus <text>] [--explain] [--embedding <cmd>]` | Generate the start-of-work package (identity + multi-agent rules + profile + task-focused memory + recent memory + boundaries + commitments; --budget caps chars, --focus adds task relevance, --explain shows included/dropped) |
176
230
  | `yotta-memory forget <file>` | Delete a memory (by type-dir path or file name) |
177
231
  | `yotta-memory archive [--days 180] [--threshold 0.4]` | Archive old memory (final score + age; immutable excluded) |
178
232
  | `yotta-memory reindex` | Rebuild the index (after manually editing .md) |
@@ -250,7 +304,7 @@ Register the connection in the agent's MCP config (`url` + two headers):
250
304
  }
251
305
  ```
252
306
 
253
- Once connected, MCP tools (remember / recall / search / forget / archive / reindex / export / import / agent_info) read/write memory and confirm identity; management actions (init / config / token / lan / serve) are not exposed via MCP, and token management is never exposed remotely. MCP `export` / `import` paths are restricted inside the memory root, and MCP `distill` does not support `--model` (local CLI only). `X-Agent-Id` must match the token's registered agent; read-partition rules are the same as the CLI (FACT public-readable, PREF / BOUND / COMMIT private).
307
+ Once connected, MCP tools (remember / recall / search / context / forget / archive / reindex / export / import / agent_info) read/write memory and confirm identity; management actions (init / config / token / lan / serve) are not exposed via MCP, and token management is never exposed remotely. MCP `export` / `import` paths are restricted inside the memory root, MCP `distill` does not support `--model`, and MCP never accepts a raw embedding command from remote callers — the local embedding plugin must be configured on the engine host with `config set embedding_cmd`. `X-Agent-Id` must match the token's registered agent; read-partition rules are the same as the CLI (FACT public-readable, PREF / BOUND / COMMIT private).
254
308
 
255
309
  ### Location persistence
256
310
 
package/README.zh-CN.md CHANGED
@@ -23,6 +23,8 @@
23
23
 
24
24
  > 📖 面向用户的操作手册见 [USER_GUIDE.md](USER_GUIDE.md)。
25
25
 
26
+ > 🆕 **v0.9.0**:召回质量与上下文选择——可选本地 embedding 插件、`context --focus`、`--explain` 选择解释。
27
+
26
28
  > 🆕 **v0.8.5**:安全加固——MCP `distill` 不再接受 `--model`;MCP `export` / `import` 路径限记忆库内;CLI `distill --model` 不再走 shell(改为允许清单)。
27
29
 
28
30
  > 🆕 **v0.8.2**:发布元数据修复——三次源重发带 `--name 元忆 yotta-memory`,修复 ClawHub 展示名缺失中文(原为裸 `yotta-memory`);无功能变更。
@@ -98,18 +100,19 @@
98
100
  - **本机免 token**:本机 CLI / stdio 直连不经网络、不校验 token;身份经该智能体 MCP 配置的 `env.YOTTA_AGENT_ID` 声明。本机多个智能体各自声明唯一 ID,互不撞。
99
101
  - **私密记忆必须有 owner**:写 PREF / BOUND / COMMIT 时未声明身份会被拒绝(公共 FACT 不受影响),从机制上防止「抄别人的 ID」。
100
102
 
101
- ### 画像与开工上下文(v0.6.0)
103
+ ### 画像与开工上下文(v0.6.0 + v0.9.0)
102
104
 
103
105
  - **profile**:聚合 `private/<owner>/` 下 PREF / BOUND / COMMIT 原文,按 type + subject + tags 归组,写 `profile.md`;引擎零推断,画像结论由 AI 依据「记忆守则」内部形成,不当面贴标签。
104
- - **context**:一键生成开工上下文包——多智能体接入铁律 + 身份 + 用户画像摘要 + 近期记忆(按 importance 排序)+ 边界提醒 + 承诺 / 锚点;支持 `--budget` 字符预算(token 恒定,不随记忆膨胀)。
106
+ - **context**:一键生成开工上下文包——多智能体接入铁律 + 身份 + 用户画像摘要 + 任务相关记忆(`--focus`,v0.9.0)+ 近期记忆(按 importance 排序)+ 边界提醒 + 承诺 / 锚点;支持 `--budget` 字符预算与 `--explain` 选择解释。
105
107
  - **记忆守则**:SKILL.md 内置规则层(类型红线 / 主动捕获触发信号 / 了解用户三阶段四手法 / 心理学底座与对齐 / 底线与边界 / 宿主隔离 / 反模式),让 AI「越用越懂」有章法。
106
108
 
107
- ### 检索:语义检索(v0.8.0)+ 中文分词打分
109
+ ### 检索:语义检索(v0.8.0 + v0.9.0 embedding)
108
110
 
109
111
  - `remember` 时自动构建 `index.json` 索引(v0.8.1 起 version=4,含字段加权与拼音 token;公共索引超过 5000 条按年份分片 `index-<year>.json`;旧索引首次 recall 自动重建);recall 默认语义检索——精确(字段加权:subject×3 / tags×2 / statement×1)+ 同义词(内置词表,可扩展)+ 拼音(全拼 / 首字母,内置 3755 常用字表)+ 模糊(编辑距离 ≤ 2)+ 子串兜底,并与效用分融合排序(0.65 × 语义 + 0.35 × 效用),零依赖。
110
112
  - `recall --explain`:展示每条命中理由(精确 / 同义 / 拼音 / 模糊 + 字段)与效用分项。
111
113
  - **recall 候选预过滤(v0.8.1)**:语义打分前先用索引 token 粗筛候选集(精确 / 同义 / 拼音 / 子串 / 模糊长度门槛),命中集与 v0.8.0 完全一致;`view` 查看平台按偏移分页返回,一次只取当前页。
112
- - 可选 embedding 插件:协议预留(v0.9 实装),无插件自动零依赖降级。
114
+ - **可选 embedding 插件(v0.9.0)**:`recall --embedding <命令>` 或 `config set embedding_cmd <命令>`,本地子进程通过 stdin/stdout JSON 协议返回向量;结果并入同一套排序。插件失败、超时或输出非法时自动降级为零依赖词法检索。
115
+ - **向量缓存**:向量缓存在各记忆根下 `.embed/cache.json`,键为 `sha256(命令 + 文本)`;只存向量,不存明文。
113
116
  - `index.json` 的 `tokens` 字段是中文分词的词频表(TF 打分用),**不是**访问凭证;鉴权令牌在记忆目录下 `.server/tokens.json`。
114
117
  - 支持关键词、`--type` 过滤、`--limit` 截断、项目级优先。
115
118
  - **根位置去重(v0.6.5)**:当项目级与用户级记忆库指向同一目录(如 cwd = home 或其父)时,`recall` / `context` 自动唯一化根,同一文件只展示一次。
@@ -139,6 +142,21 @@
139
142
  - **安全边界**:管理动作(init / config / token / lan / serve)不进 MCP,token 不远程暴露;远程智能体只能读写记忆,且路径限记忆库内(export/import 的 out/src 必须落在库内)、distill 不支持 `--model`(仅本地 CLI),不能改配置、不能管 token。
140
143
  - 完整操作步骤见上文「局域网多机共享」章节与 [USER_GUIDE.md](USER_GUIDE.md)。
141
144
 
145
+ ## 常见问题 FAQ(速查)
146
+
147
+ | 问题 | 答案(详见 references/faq.md) |
148
+ |---|---|
149
+ | 类型选错? | 只提示不阻止;forget 后重写;--no-hint 关提示 |
150
+ | 私密区加密? | init 默认加密(主口令+恢复钥匙);明文库 migrate 升级 |
151
+ | 多智能体权限? | FACT 公共;PREF/BOUND/COMMIT 按 owner 隔离,需 key authorize / view 授权 |
152
+ | 记忆找不到? | config get 查位置 → reindex 重建索引 → recall/search |
153
+ | 忘记主口令? | 用恢复钥匙 reset-password(无私密区锁定的预期行为) |
154
+ | 局域网怎么连? | 引擎 lan enable + token new;客户端配 url+token |
155
+ | MCP 没加载? | 检查 mcpServers + 重启会话;本机直连用 CLI |
156
+ | 记忆库在哪? | config get;项目级 .yottamemory |
157
+ | 跨会话恢复? | 开工跑 context + recall |
158
+ | 备份迁移? | export / import |
159
+
142
160
  ## 与其他方案对比
143
161
 
144
162
  > 以下按方案类型对比,不涉及具体产品名称。判断标准:数据主权、部署成本、权限边界、可审计性、跨智能体能力。
@@ -190,6 +208,42 @@ bash install.sh --list # 列出智能体 -> 默认目录
190
208
  ```
191
209
 
192
210
  > 方式一走 npm 源(npmmirror / npmjs),不依赖 GitHub;方式二 / 三走 GitHub,国内无代理可能失败。
211
+ ## 命令输出样例
212
+
213
+ > 以下为示意输出(以实际版本为准),让你知道执行命令后屏幕上会出现什么。
214
+
215
+ **init(初始化记忆库)**:
216
+
217
+ ```text
218
+ 初始化记忆库成功:D:\.yottamemory
219
+ 主口令已设置;恢复钥匙:xxxx-xxxx-xxxx-xxxx(请妥善保存)
220
+ ```
221
+
222
+ **remember(写入记忆,带 verify 回读)**:
223
+
224
+ ```text
225
+ 已记录: D:\.yottamemory\facts\2026-09-01-0001.md
226
+ [verify] 已写回读 OK: facts/2026-09-01-0001.md
227
+ ```
228
+
229
+ **recall(检索)**:
230
+
231
+ ```text
232
+ 共 3 条记忆(前 3 条):
233
+ [FACT] 项目名: 描述……(D:\.yottamemory\facts\xxx.md)
234
+ ```
235
+
236
+ **context(开工上下文包)**:
237
+
238
+ ```text
239
+ # 开工上下文包(yotta-memory context)
240
+ ## 1. 身份
241
+ ## 2. 用户画像摘要
242
+ ## 3. 近期记忆(按活跃度前 10 条)
243
+ ## 4. 边界提醒(BOUND)
244
+ ## 5. 承诺 / 锚点(COMMIT)
245
+ ```
246
+
193
247
  ## 升级
194
248
 
195
249
  两种升级方式对应两种安装方式:
@@ -210,9 +264,9 @@ npm i -g @yottameta/yotta-memory
210
264
  |---|---|
211
265
  | `yotta-memory init [--project] [--dir <目录>]` | 初始化记忆库(默认用户级 `~/.yottamemory/`;--dir 显式指定位置)|
212
266
  | `yotta-memory remember <type> <subject> <statement> [--owner <id>] [--source <来源>] [--weight <0..>] [--verify] [--no-hint]` | 写入记忆(同 subject+statement 自动更新;--owner 标注归属;--source 记录来源;--weight 重要性权重、去重取 max;--verify 写后回读;--no-hint 关闭类型提示)|
213
- | `yotta-memory recall [关键词] [--type T] [--limit N] [--agent <id>] [--owner <id>] [--all] [--unsafe]` | 检索记忆(索引+TF 打分,读取分区过滤;越界读其它智能体私密默认拒绝,需 grant / identity=user / `--unsafe`;`--agent <其它>` 仅作身份声明、不授予跨读;项目级优先)|
267
+ | `yotta-memory recall [关键词] [--type T] [--limit N] [--agent <id>] [--owner <id>] [--all] [--unsafe] [--explain] [--semantic] [--embedding <命令>] [--embedding-timeout N]` | 检索记忆(语义+效用分排序;可选本地 embedding 插件;读取分区过滤;越界读其它智能体私密默认拒绝,需 grant / identity=user / `--unsafe`;`--agent <其它>` 仅作身份声明、不授予跨读;项目级优先)|
214
268
  | `yotta-memory profile [--owner <id>]` | 生成用户画像(聚合 `private/<owner>/` 原文,零推断,写 `profile.md`;跨 owner 默认拒绝)|
215
- | `yotta-memory context [--limit N] [--owner <id>] [--budget N]` | 生成开工上下文包(身份 + 多智能体铁律 + 画像 + 近期记忆 + 边界 + 承诺;--budget 近期记忆字符预算,token 恒定)|
269
+ | `yotta-memory context [--limit N] [--owner <id>] [--budget N] [--focus <关键词>] [--explain] [--embedding <命令>]` | 生成开工上下文包(身份 + 多智能体铁律 + 画像 + 任务相关记忆 + 近期记忆 + 边界 + 承诺;--budget 字符预算;--focus 任务聚焦;--explain 输出 included/dropped 选择解释)|
216
270
  | `yotta-memory forget <文件>` | 删除一条记忆(按类型目录路径或文件名)|
217
271
  | `yotta-memory archive [--days 180] [--threshold 0.4]` | 归档旧记忆(盖棺分+年龄,immutable 除外)|
218
272
  | `yotta-memory reindex` | 重建索引(手动改 .md 后校正)|
@@ -290,7 +344,7 @@ yotta-memory recall --type FACT --limit 10
290
344
  }
291
345
  ```
292
346
 
293
- 连接后可通过 MCP tools(remember / recall / search / forget / archive / reindex / export / import / agent_info)读写记忆与确认身份;管理动作(init / config / token / lan / serve)不进 MCP,token 管理不远程暴露;MCP export/import 路径限记忆库内、distill 不支持 `--model`(仅本地 CLI)。`X-Agent-Id` 必须与 token 登记的智能体一致;读取分区规则与 CLI 相同(FACT 公共可读,PREF / BOUND / COMMIT 私密隔离)。
347
+ 连接后可通过 MCP tools(remember / recall / search / context / forget / archive / reindex / export / import / agent_info)读写记忆与确认身份;管理动作(init / config / token / lan / serve)不进 MCP,token 管理不远程暴露;MCP export/import 路径限记忆库内、distill 不支持 `--model`,MCP 也不接受远端传入 embedding 命令——embedding 插件只能由引擎主机本地 `config set embedding_cmd` 配置。`X-Agent-Id` 必须与 token 登记的智能体一致;读取分区规则与 CLI 相同(FACT 公共可读,PREF / BOUND / COMMIT 私密隔离)。
294
348
 
295
349
  ### 位置持久化
296
350
 
@@ -309,4 +363,4 @@ yotta-memory config get
309
363
 
310
364
  ## 许可证
311
365
 
312
- MIT © YottaMeta —— 详见 [LICENSE](./LICENSE)。
366
+ MIT © YottaMeta —— 详见 [LICENSE](./LICENSE)。
package/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: yotta-memory
3
- description: "元忆 —— 有权限边界的文件式智能体记忆。文件式、零依赖、可 diff/回滚:让任何 AI 智能体活过会话,开工 recall 恢复上下文、重要信息 remember 落盘、收工归档。类型体系 FACT(公共共享)/ PREF / BOUND / COMMIT(私密隔离)。触发:记住、别忘了、记一笔、记忆、remember、recall、跨会话、上次说到、续测、交接、归档、记忆盘、共享记忆、局域网记忆、画像、开工上下文、记忆守则、profile、context、越用越懂、语义检索、反馈、维护、蒸馏、feedback、maintain、distill、explain、自我学习、自我进化、自我提升、查看平台分页、recall 候选预过滤"
4
- version: 0.8.5
3
+ description: "元忆 —— 有权限边界的文件式智能体记忆。文件式、零依赖、可 diff/可回滚:让任何 AI 智能体活过会话,开工 recall 恢复上下文、重要信息 remember 落盘、收工归档。类型体系 FACT(公共共享)/ PREF / BOUND / COMMIT(私密隔离)。触发:记住、别忘了、记一笔、记忆、remember、recall、跨会话、上次说到、续测、交接、归档、记忆盘、共享记忆、局域网记忆、画像、开工上下文、记忆守则、profile、context、越用越懂、语义检索、反馈、维护、蒸馏、feedback、maintain、distill、explain、自我学习、自我进化、自我提升、查看平台分页、recall 候选预过滤、任务相关记忆、--focus、--embedding"
4
+ version: 0.9.0
5
5
  license: MIT
6
6
  ---
7
7
 
@@ -18,6 +18,7 @@ license: MIT
18
18
  - **双级存储**:用户级 `~/.yottamemory/`(跨项目)+ 项目级 `.yottamemory/`(随项目共享)。
19
19
  - **越用越懂**:`profile` 聚合用户画像(引擎零推断,只归组原文)+ `context` 一键生成开工上下文包(身份 + 画像 + 近期记忆 + 边界 + 承诺)+ SKILL「记忆守则」规则层;只注入规则与机制,不注入人格数据(出厂零数据)。
20
20
  - **自我学习 / 自我进化 / 自我提升(v0.8.0)**:`recall` 语义检索(同义词 / 拼音 / 字段加权 / 模糊匹配,零依赖);`feedback` 显式使用反馈闭环(useful / useless → weight / confidence / feedback_net 演化,越用越懂);`maintain` 规则层自组织(统一效用分 + 年龄自动归档 / 遗忘候选 / 去重,默认 dry-run,immutable / BOUND 豁免);`distill` 心理日志蒸馏(统计摘要 / 主题画像 / 知识地图,可选 `--model` 外部模型增强);`explain` 查看单条记忆效用分项。
21
+ - **召回质量与上下文选择(v0.9.0)**:`recall` 支持可选本地 embedding 插件(`--embedding <command>` / `config set embedding_cmd <command>`);`context --focus <关键词>` 生成任务感知上下文;`--explain` 输出选择 trace,无插件时自动降级为词法检索。
21
22
 
22
23
  ## 何时使用(触发)
23
24
 
@@ -28,7 +29,7 @@ license: MIT
28
29
 
29
30
  ## 核心流程
30
31
 
31
- 1. **开工定向**:先按「开工第一步:确认记忆位置 + 智能体身份」检测记忆库与身份,再运行 `yotta-memory context`(主注入:身份 + 用户画像 + 近期记忆 + 边界 + 承诺)恢复上下文,需要细节再 `yotta-memory recall <关键词>`;项目级记忆优先,其次用户级。
32
+ 1. **开工定向**:先按「开工第一步:确认记忆位置 + 智能体身份」检测记忆库与身份,再运行 `yotta-memory context`(主注入:身份 + 用户画像 + 近期记忆 + 边界 + 承诺)恢复上下文,需要细节再 `yotta-memory recall <关键词>`;若有明确任务关键词,用 `context --focus <关键词>` 获得任务相关记忆;项目级记忆优先,其次用户级。
32
33
  2. **进行中落盘**:重要信息立即 `yotta-memory remember <type> <subject> <statement>`,不攒到收工。
33
34
  3. **收工归档**:写会话小结(COMMIT / 笔记),旧记录定期 `yotta-memory archive`。
34
35
  4. **多智能体纪律**:FACT 写入公共区,PREF / BOUND / COMMIT 只写本智能体私密区;不读取其他智能体私密区。**一切读写一律走 `yotta-memory` CLI / MCP 工具**——禁止用 shell(`Get-ChildItem` / `Get-Content` / `cat` / `ls` / `type` 等)直接读或改记忆库目录下的 `.md` / `index.json` / `tokens.json` / `agents.json` / `grants.json` 等文件,否则会绕过权限边界、读到别的智能体私密内容。
@@ -73,7 +74,7 @@ license: MIT
73
74
  - 三角建模:情绪(措辞 / 标点 / 长度)→ 认知(归因 / 信念 / 控制感)→ 行为(作息 / 应对 / 执行);三者冲突时优先行为与认知。
74
75
  - 共情至少到第 3 阶段:识别 → 理解 → 回应 → 验证。
75
76
  - 诚实声明:AI 是模式匹配非真感受,不伪装、不编造伪情感记忆。
76
- - 危机识别:不评判、持续在场、温和引导现实支持;不擅自越界联系第三方。
77
+ - 危机识别:不评判、持续在场、温和引导现实支持;不擅自越界联系外部。
77
78
  - 对齐:回应时让积累的理解自然影响语气与措辞(让用户感到「被记得、被懂」),而非机械引用记忆条目。
78
79
  - 情感外包双刃剑:做增益真实生活的陪伴,不替代真实关系。
79
80
 
@@ -161,9 +162,9 @@ license: MIT
161
162
  | `yotta-memory reset-password [--password <当前> | --recovery-key <钥匙>] [--new-password <新>]` | 重设主口令(忘口令用恢复钥匙)|
162
163
  | `yotta-memory key list / authorize <id> / revoke <id>` | 管理 AI 私密读取授权缓存(authorize 需主口令;revoke 立即吊销该 AI 解密能力)|
163
164
  | `yotta-memory remember <type> <subject> <statement> [--owner <id>] [--source <来源>] [--weight <0..>] [--verify] [--no-hint]` | 写入(同 subject+statement 自动更新;--owner 标注归属;--source 记录来源;--weight 重要性权重默认 1.0、去重取 max;--verify 写后回读校验;--no-hint 关闭类型启发式提示)|
164
- | `yotta-memory recall [关键词] [--type T] [--limit N] [--agent <id>] [--owner <id>] [--all] [--unsafe] [--explain] [--semantic]` | 检索(v0.8.0 默认语义检索:同义词 / 拼音全拼+首字母 / 字段加权 / 模糊匹配 + 效用分融合排序;`--explain` 显示命中理由与效用分项;`--semantic` 显式开启;读取分区过滤;越界读其它智能体私密默认拒绝,需 grant / identity=user / `--unsafe`;`--agent <其它>` 只作身份声明/展示,不授予跨读——读他人私密同样要授权;项目级优先)|
165
+ | `yotta-memory recall [关键词] [--type T] [--limit N] [--agent <id>] [--owner <id>] [--all] [--unsafe] [--explain] [--semantic] [--embedding <command>] [--embedding-timeout N]` | 检索(v0.8.0 默认语义检索:同义词 / 拼音全拼+首字母 / 字段加权 / 模糊匹配 + 效用分融合排序;v0.9.0 支持可选本地 embedding 插件,失败自动降级;`--explain` 显示命中理由与效用分项;`--semantic` 显式开启;读取分区过滤;越界读其它智能体私密默认拒绝,需 grant / identity=user / `--unsafe`;`--agent <其它>` 只作身份声明/展示,不授予跨读——读他人私密同样要授权;项目级优先)|
165
166
  | `yotta-memory profile [--owner <id>]` | 生成用户画像(聚合 `private/<owner>/` 原文,零推断,写 `profile.md`;跨 owner 默认拒绝)|
166
- | `yotta-memory context [--limit N] [--owner <id>] [--budget N]` | 生成开工上下文包(身份 + 多智能体铁律 + 画像 + 近期记忆 + 边界 + 承诺;--budget 近期记忆字符预算,0=不限)|
167
+ | `yotta-memory context [--limit N] [--owner <id>] [--budget N] [--focus <关键词>] [--explain] [--embedding <command>]` | 生成开工上下文包(身份 + 多智能体铁律 + 画像 + 任务相关记忆 + 近期记忆 + 边界 + 承诺;--budget 字符预算,0=不限;--explain 输出 included / dropped 选择 trace)|
167
168
  | `yotta-memory forget <文件>` | 删除(按类型目录路径或文件名)|
168
169
  | `yotta-memory archive [--days 180] [--threshold 0.35]` | 归档旧记忆(v0.8.0 统一效用分+年龄,immutable 除外;阈值默认读 config `maintain_archived_utility`)|
169
170
  | `yotta-memory reindex` | 重建索引(手动改 .md 后校正)|
@@ -323,6 +324,16 @@ license: MIT
323
324
  ### 4.10 复用
324
325
  - 成功后优先复用现有连接;失败(token 吊销等)再回 4.3。
325
326
 
327
+ ## 常见问题 FAQ(速查)
328
+
329
+ 常见问题与避坑见 `references/faq.md`:
330
+ - 类型选错 → 只提示不阻止;`forget` 后按正确类型重写;
331
+ - 私密区加密 → `init` 默认加密(主口令+恢复钥匙),明文库 `migrate` 升级,`view` 平台口令解锁;
332
+ - 多智能体权限 → FACT 公共、私密按 owner 隔离,需 `key authorize` / `view` 授权;
333
+ - 记忆找不到 → `config get` 查位置 → `reindex` → `recall` / `search`;
334
+ - 忘记主口令 → 用恢复钥匙 `reset-password`;
335
+ - 局域网 → 引擎 `lan enable` + `token new`,客户端配 url+token。
336
+
326
337
  ## 渐进披露
327
338
 
328
339
  - 协议细节、目录结构与类型规则见 `references/protocol.md`,需要时读取,不要每次全读。
package/USER_GUIDE.md CHANGED
@@ -25,12 +25,12 @@
25
25
 
26
26
  **典型使用场景:**
27
27
 
28
- - **跨会话续接**:AI 开工 `recall` 恢复上次上下文,不丢记忆。
28
+ - **跨会话续接**:AI 开工 `recall` 恢复上次上下文,不丢记忆;v0.9.0 起可用 `context --focus <关键词>` 生成任务感知上下文。
29
29
  - **多智能体共享**:FACT 进公共区共享,PREF / BOUND / COMMIT 各自私密隔离。
30
30
  - **交接与团队协作**:项目级 `.yottamemory` 随仓库走,交接即恢复。
31
31
  - **便携记忆盘**:记忆装在固定主机上,本机与局域网其它主机共享同一份记忆(见第 4 / 5 篇)。
32
32
  - **越用越懂(v0.6.0)**:AI 按「记忆守则」主动捕获信号,`profile` 聚合画像、`context` 开工注入——用得越久越懂你。
33
- - **自我学习 / 自我进化 / 自我提升(v0.8.0)**:`recall` 语义检索(同义词 / 拼音 / 字段加权 / 模糊)+ `feedback` 使用反馈闭环 + `maintain` 规则层自组织(自动归档 / 遗忘候选 / 去重)+ `distill` 心理日志蒸馏——记忆系统会自己整理、提炼、演化。
33
+ - **自我学习 / 自我进化 / 自我提升(v0.8.0 + v0.9.0)**:`recall` 语义检索(同义词 / 拼音 / 字段加权 / 模糊,v0.9.0 可选本地 embedding 插件)+ `feedback` 使用反馈闭环 + `maintain` 规则层自组织 + `distill` 心理日志蒸馏——记忆系统会自己整理、提炼、演化。
34
34
 
35
35
  ## 2. 安装(CLI + 技能)
36
36
 
@@ -77,7 +77,7 @@ yotta-memory config get # 查看记忆库位置
77
77
  ```bash
78
78
  yotta-memory profile # 生成用户画像(写 private/<owner>/profile.md)
79
79
  yotta-memory context --limit 10 --budget 1800 # 生成开工上下文包(身份+铁律+画像+近期记忆+边界+承诺,预算控 token)
80
- yotta-memory iam <id> --name 元忆 --user 老张 --relationship 伙伴 # 自我档案扩展显示名/用户/关系
80
+ yotta-memory iam <id> --name 元忆 --user 用户 --relationship 伙伴 # 自我档案扩展显示名/用户/关系
81
81
  ```
82
82
 
83
83
  - `profile` 引擎零推断:只按类型 / 主题 / 标签归组呈现原文,画像结论由 AI 内部形成,不当面贴标签。
@@ -142,9 +142,16 @@ statement: 本周完成发布
142
142
 
143
143
  - `recall <关键词>` 默认语义检索:同义词(内置词表)、拼音(全拼 / 首字母,内置 3755 常用字表)、字段加权(subject 优先)、模糊匹配(编辑距离 ≤ 2)、子串兜底。
144
144
  - 例:记过「越用越懂」,用 `recall yyyd`(首字母)或 `recall yueyong yuedong`(拼音)都能找回。
145
- - `recall --explain`:显示每条命中理由与效用分项。
145
+ - `recall --explain`:显示每条命中理由与效用分项;配置 embedding 插件后还会显示向量相似度。
146
146
  - 旧索引首次 recall 自动重建(version 4),无需手动 reindex。
147
147
 
148
+ **可选本地 embedding 插件(v0.9.0)**
149
+
150
+ - `recall <关键词> --embedding <命令>`:调用本地子进程,stdin 传 `{texts:[]}`,stdout 返回 `{vectors:[]}`;向量与词法得分共同参与排序。
151
+ - `config set embedding_cmd <命令>`:持久启用插件;`--embedding` 优先级更高。
152
+ - `--embedding-timeout N`:默认 3000ms;插件失败、超时或输出非法时自动降级为词法检索,不会中断命令。
153
+ - 向量缓存写入各记忆根下 `.embed/cache.json`,只存向量不存明文;同一命令与文本命中缓存时不再重复调用插件。
154
+
148
155
  **使用反馈闭环(feedback)**
149
156
 
150
157
  - `feedback <文件> --useful`:这条记忆有用 → weight ×1.2(上限 3.0)、confidence +0.05、feedback_net +1。
@@ -308,9 +315,9 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
308
315
  |---|---|
309
316
  | `yotta-memory init [--project] [--dir <目录>]` | 初始化记忆库 |
310
317
  | `yotta-memory remember <类型> <主题> <内容> [--owner <id>] [--source <来源>] [--weight <0..>] [--verify] [--no-hint]` | 写入记忆(--source 来源;--weight 重要性权重;--verify 写后回读;--no-hint 关闭类型提示)|
311
- | `yotta-memory recall [关键词] [--type T] [--limit N] [--agent <id>] [--owner <id>] [--all] [--unsafe] [--explain]` | 检索记忆(v0.8.0 默认语义检索:同义词 / 拼音 / 字段加权 / 模糊 + 效用分排序;`--explain` 显示命中理由;读取分区过滤;`--agent <其它>` 仅作身份声明、不授予跨读;越界读其它智能体私密默认拒绝,需 grant / identity=user / `--unsafe`)|
318
+ | `yotta-memory recall [关键词] [--type T] [--limit N] [--agent <id>] [--owner <id>] [--all] [--unsafe] [--explain] [--semantic] [--embedding <命令>] [--embedding-timeout N]` | 检索记忆(语义 + 效用分排序;可选本地 embedding 插件;读取分区过滤;越界读其它智能体私密默认拒绝,需 grant / identity=user / `--unsafe`)|
312
319
  | `yotta-memory profile [--owner <id>]` | 生成用户画像(零推断,写 `profile.md`)|
313
- | `yotta-memory context [--limit N] [--owner <id>] [--budget N]` | 开工上下文包(身份+铁律+画像+近期记忆+边界+承诺;--budget 近期记忆字符预算)|
320
+ | `yotta-memory context [--limit N] [--owner <id>] [--budget N] [--focus <关键词>] [--explain] [--embedding <命令>]` | 开工上下文包(身份+铁律+画像+任务相关记忆+近期记忆+边界+承诺;--focus 任务聚焦;--explain 输出 included/dropped 选择解释)|
314
321
  | `yotta-memory forget <文件>` | 删除一条记忆 |
315
322
  | `yotta-memory archive [--days 180] [--threshold 0.4]` | 归档旧记忆 |
316
323
  | `yotta-memory reindex` | 重建索引 |
@@ -22,7 +22,7 @@ const crypto = require('crypto');
22
22
  const http = require('http');
23
23
  const child_process = require('child_process');
24
24
 
25
- const VERSION = '0.8.5';
25
+ const VERSION = '0.9.0';
26
26
  const TYPES = ['FACT', 'PREF', 'BOUND', 'COMMIT'];
27
27
  const TYPE_DIRS = { FACT: 'facts', PREF: 'prefs', BOUND: 'bounds', COMMIT: 'commits' };
28
28
  const PUBLIC_DIR = 'facts';
@@ -150,6 +150,103 @@ function runDistillModel(modelStr, payload) {
150
150
  const r = child_process.spawnSync(argv[0], argv.slice(1), { input: payload, encoding: 'utf8', maxBuffer: 1024 * 1024, shell: false, windowsHide: true });
151
151
  return r;
152
152
  }
153
+ function cosineSimilarity(a, b) {
154
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return 0;
155
+ let dot = 0, na = 0, nb = 0;
156
+ for (let i = 0; i < a.length; i++) {
157
+ const x = Number(a[i]) || 0;
158
+ const y = Number(b[i]) || 0;
159
+ dot += x * y;
160
+ na += x * x;
161
+ nb += y * y;
162
+ }
163
+ if (!na || !nb) return 0;
164
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
165
+ }
166
+ function runEmbeddingPlugin(command, texts, timeoutMs) {
167
+ const argv = splitCommandArgv(String(command || ''));
168
+ if (!argv.length) throw new Error('embedding 命令为空');
169
+ const payload = JSON.stringify({ texts: (texts || []).map(String) });
170
+ const timeout = Math.max(1, parseInt(timeoutMs, 10) || 3000);
171
+ const r = child_process.spawnSync(argv[0], argv.slice(1), {
172
+ input: payload,
173
+ encoding: 'utf8',
174
+ maxBuffer: 1024 * 1024,
175
+ shell: false,
176
+ windowsHide: true,
177
+ timeout: timeout
178
+ });
179
+ if (r.error) throw r.error;
180
+ if (r.status !== 0) throw new Error('embedding 插件退出码 ' + r.status);
181
+ const parsed = JSON.parse(String(r.stdout || ''));
182
+ if (!parsed || !Array.isArray(parsed.vectors)) throw new Error('embedding 插件输出缺少 vectors 数组');
183
+ if (parsed.vectors.length !== texts.length) throw new Error('embedding 向量数量不匹配');
184
+ return parsed.vectors;
185
+ }
186
+ function embeddingCandidates(entries, query, opts) {
187
+ opts = opts || {};
188
+ const command = opts.embedding || '';
189
+ if (!command) return [];
190
+ const texts = [String(query || '')].concat((entries || []).map(function (e) {
191
+ return [e.subject || '', e.statement || '', (e.tags || []).join(' ')].join(' ');
192
+ }));
193
+ const root = opts.root ? String(opts.root) : '';
194
+ let cache = {};
195
+ let cachePath = '';
196
+ if (root) {
197
+ cachePath = path.join(root, '.embed', 'cache.json');
198
+ try {
199
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
200
+ cache = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
201
+ } catch (err) {
202
+ cache = {};
203
+ }
204
+ }
205
+ const keys = texts.map(function (text) {
206
+ return crypto.createHash('sha256').update(command + '\0' + text).digest('hex');
207
+ });
208
+ const missing = [];
209
+ const missingIndexes = [];
210
+ const vectors = new Array(texts.length);
211
+ for (let i = 0; i < texts.length; i++) {
212
+ if (cache[keys[i]]) {
213
+ vectors[i] = cache[keys[i]];
214
+ } else {
215
+ missing.push(texts[i]);
216
+ missingIndexes.push(i);
217
+ }
218
+ }
219
+ if (missing.length) {
220
+ const missingVectors = runEmbeddingPlugin(command, missing, opts.embeddingTimeout);
221
+ for (let i = 0; i < missingVectors.length; i++) {
222
+ const idx = missingIndexes[i];
223
+ vectors[idx] = missingVectors[i];
224
+ cache[keys[idx]] = missingVectors[i];
225
+ }
226
+ if (cachePath) {
227
+ try { fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf8'); } catch (err) {}
228
+ }
229
+ }
230
+ const queryVec = vectors[0];
231
+ const out = [];
232
+ for (let i = 1; i < vectors.length; i++) {
233
+ const sim = cosineSimilarity(queryVec, vectors[i]);
234
+ if (sim > 0) out.push({ entry: entries[i - 1], score: sim, detail: ['embedding:' + round3(sim)] });
235
+ }
236
+ return out;
237
+ }
238
+ function effectiveEmbeddingCommand(opts) {
239
+ opts = opts || {};
240
+ if (opts.embedding) return String(opts.embedding);
241
+ const cfg = loadConfig();
242
+ return cfg && cfg.embedding_cmd ? String(cfg.embedding_cmd) : '';
243
+ }
244
+ function effectiveEmbeddingTimeout(opts) {
245
+ opts = opts || {};
246
+ if (opts.embeddingTimeout) return parseInt(opts.embeddingTimeout, 10) || 3000;
247
+ const cfg = loadConfig();
248
+ return cfg && cfg.embedding_timeout ? parseInt(cfg.embedding_timeout, 10) || 3000 : 3000;
249
+ }
153
250
  function parseFrontmatter(text) {
154
251
  const m = text.match(/^---\s*\n([\s\S]*?)\n---/);
155
252
  if (!m) return { meta: {}, body: text };
@@ -1154,6 +1251,28 @@ function recallCore(query, opts) {
1154
1251
  const wantExplain = !!opts.explain;
1155
1252
  const useSemantic = opts.semantic !== false;
1156
1253
  const prefilter = (q && useSemantic) ? recallPrefilter(q) : null;
1254
+ const embeddingMap = new Map();
1255
+ const embeddingCommand = effectiveEmbeddingCommand(opts);
1256
+ const embeddingTimeout = effectiveEmbeddingTimeout(opts);
1257
+ if (q && embeddingCommand) {
1258
+ for (const root of roots) {
1259
+ const rootEntries = ensureIndex(root).filter(function (e) {
1260
+ return classifyRead(e, agent, ownerFilter, allSafe, selfAgent) !== 'denied';
1261
+ });
1262
+ try {
1263
+ const hits = embeddingCandidates(rootEntries, q, {
1264
+ embedding: embeddingCommand,
1265
+ embeddingTimeout: embeddingTimeout,
1266
+ root: root
1267
+ });
1268
+ for (const h of hits) {
1269
+ embeddingMap.set(root + '\0' + h.entry.file, h);
1270
+ }
1271
+ } catch (err) {
1272
+ // Plugin failure is not fatal; keep lexical-only recall.
1273
+ }
1274
+ }
1275
+ }
1157
1276
  const hits = [];
1158
1277
  let deniedCount = 0;
1159
1278
  for (const root of roots) {
@@ -1164,12 +1283,19 @@ function recallCore(query, opts) {
1164
1283
  if (r === 'denied') { deniedCount++; continue; }
1165
1284
  let score = 0;
1166
1285
  let detail = null;
1286
+ const embeddingHit = embeddingMap.get(root + '\0' + e.file) || null;
1167
1287
  if (q) {
1168
1288
  if (useSemantic) {
1169
- if (prefilter && !prefilter(e)) continue; // v0.8.1 候选预过滤:粗筛后再语义打分
1289
+ if (prefilter && !prefilter(e) && !embeddingHit) continue; // v0.8.1 候选预过滤:粗筛后再语义打分;embedding 命中可越过词法预过滤
1170
1290
  const m = semanticMatch(e, q, wantExplain);
1171
1291
  score = m.score;
1172
1292
  if (wantExplain && m.detail && m.detail.length) detail = m.detail;
1293
+ if (embeddingHit) {
1294
+ score = Math.max(score, embeddingHit.score);
1295
+ if (wantExplain && embeddingHit.detail) {
1296
+ detail = (detail || []).concat(embeddingHit.detail);
1297
+ }
1298
+ }
1173
1299
  } else {
1174
1300
  const qtoks = tokenize(q);
1175
1301
  for (const tt of qtoks) { if (e.tokens && e.tokens[tt]) score += e.tokens[tt]; }
@@ -1245,7 +1371,21 @@ function recallCore(query, opts) {
1245
1371
  if (deniedCount > 0 && explicitCross) {
1246
1372
  lines.push('\n[警告] 本次检索共拒绝 ' + deniedCount + ' 条越界访问(其它智能体私密记忆,未授权不展示)。如需读取请加 --unsafe 或 --owner user。');
1247
1373
  }
1248
- return { error: false, exitCode: 0, text: lines.join('\n') };
1374
+ return {
1375
+ error: false,
1376
+ exitCode: 0,
1377
+ text: lines.join('\n'),
1378
+ entries: shown.map(function (h) {
1379
+ return {
1380
+ root: h.root,
1381
+ file: h.entry.file,
1382
+ type: h.entry.type,
1383
+ subject: h.entry.subject,
1384
+ statement: h.entry.statement,
1385
+ score: h.finalScore === undefined ? h.score : h.finalScore
1386
+ };
1387
+ })
1388
+ };
1249
1389
  }
1250
1390
  function resolveMemoryFile(root, ref) {
1251
1391
  const map = {};
@@ -2509,6 +2649,11 @@ function contextCore(opts) {
2509
2649
  const owner = opts.owner || selfAgent;
2510
2650
  const unsafe = !!opts.unsafe;
2511
2651
  const budget = opts.budget ? parseInt(opts.budget, 10) : 0;
2652
+ const focus = opts.focus ? String(opts.focus) : '';
2653
+ const explain = !!opts.explain;
2654
+ const embeddingCommand = effectiveEmbeddingCommand(opts);
2655
+ const embeddingTimeout = effectiveEmbeddingTimeout(opts);
2656
+ const trace = [];
2512
2657
  const lines = [];
2513
2658
  const FENCE = String.fromCharCode(96, 96, 96);
2514
2659
  function usedChars() { return lines.reduce(function (s, x) { return s + String(x).length + 1; }, 0); }
@@ -2561,6 +2706,32 @@ function contextCore(opts) {
2561
2706
  lines.push('(未声明身份,跳过画像;先 iam 登记后可生成)');
2562
2707
  }
2563
2708
  lines.push('');
2709
+
2710
+ if (focus) {
2711
+ lines.push('## 2.5 任务相关记忆(--focus)');
2712
+ lines.push('');
2713
+ const focused = recallCore(focus, {
2714
+ limit: limit,
2715
+ owner: owner,
2716
+ unsafe: unsafe,
2717
+ selfAgent: selfAgent,
2718
+ embedding: embeddingCommand,
2719
+ embeddingTimeout: embeddingTimeout
2720
+ });
2721
+ const focusedEntries = focused.entries || [];
2722
+ if (!focusedEntries.length) lines.push('(无匹配记忆)');
2723
+ for (const e of focusedEntries) {
2724
+ const line = '- [' + e.type + '] ' + e.subject + ': ' + e.statement;
2725
+ if (budget > 0 && usedChars() + line.length > budget) {
2726
+ trace.push('[dropped] ' + e.file + ' reason: budget_exceeded');
2727
+ continue;
2728
+ }
2729
+ lines.push(line);
2730
+ trace.push('[included] ' + e.file + ' reason: focus_match score: ' + round3(e.score));
2731
+ }
2732
+ lines.push('');
2733
+ }
2734
+
2564
2735
  lines.push('## 3. 近期记忆(按活跃度前 ' + limit + ' 条)');
2565
2736
  lines.push('');
2566
2737
  const recent = [];
@@ -2576,8 +2747,12 @@ function contextCore(opts) {
2576
2747
  if (!recentShown.length) lines.push('(暂无记忆)');
2577
2748
  for (const h of recentShown) {
2578
2749
  const line = '- [' + h.e.type + '] ' + h.e.subject + ': ' + h.e.statement;
2579
- if (budget > 0 && usedChars() + line.length > budget) break;
2750
+ if (budget > 0 && usedChars() + line.length > budget) {
2751
+ trace.push('[dropped] ' + h.e.file + ' reason: budget_exceeded');
2752
+ break;
2753
+ }
2580
2754
  lines.push(line);
2755
+ trace.push('[included] ' + h.e.file + ' reason: recent_match');
2581
2756
  }
2582
2757
  lines.push('');
2583
2758
  lines.push('## 4. 边界提醒(BOUND)');
@@ -2620,6 +2795,13 @@ function contextCore(opts) {
2620
2795
  }
2621
2796
  const oldCount = allEntries.filter(function (e) { return e.created && daysBetween(e.created, today()) > 180 && classifyRead(e, owner, '', unsafe, selfAgent) !== 'denied'; }).length;
2622
2797
  if (oldCount > 0) lines.push('- 归档提醒: ' + oldCount + ' 条超 180 天,建议 archive');
2798
+ if (explain) {
2799
+ lines.push('');
2800
+ lines.push('## 7. 选择解释(--explain)');
2801
+ lines.push('');
2802
+ if (!trace.length) lines.push('(无选择记录)');
2803
+ for (const t of trace) lines.push(t);
2804
+ }
2623
2805
  return { error: false, exitCode: 0, text: lines.join('\n') };
2624
2806
  }
2625
2807
  function cmdContext(opts) {
@@ -2630,16 +2812,20 @@ function cmdContext(opts) {
2630
2812
 
2631
2813
  // ---- config 命令 ----
2632
2814
  function cmdConfigSet(key, value) {
2633
- if (key !== 'memory_home') { console.error('未知配置项: ' + key + '(可用: memory_home)'); process.exit(2); }
2634
- if (!value) { console.error('缺少值: config set memory_home <目录>'); process.exit(2); }
2815
+ if (key !== 'memory_home' && key !== 'embedding_cmd' && key !== 'embedding_timeout') { console.error('未知配置项: ' + key + '(可用: memory_home / embedding_cmd / embedding_timeout)'); process.exit(2); }
2816
+ if (!value) { console.error('缺少值: config set ' + key + ' <值>'); process.exit(2); }
2635
2817
  const cfg = loadConfig();
2636
- cfg.memory_home = value;
2818
+ if (key === 'memory_home') cfg.memory_home = value;
2819
+ else if (key === 'embedding_cmd') cfg.embedding_cmd = value;
2820
+ else if (key === 'embedding_timeout') cfg.embedding_timeout = parseInt(value, 10) || 3000;
2637
2821
  saveConfig(cfg);
2638
- console.log('已记住记忆库位置: ' + value);
2822
+ console.log('已写入配置: ' + key + ' = ' + (key === 'embedding_timeout' ? (parseInt(value, 10) || 3000) : value));
2639
2823
  }
2640
2824
  function cmdConfigGet() {
2641
2825
  const cfg = loadConfig();
2642
2826
  console.log('memory_home: ' + (cfg.memory_home || '(未设置,默认 ~/.yottamemory)'));
2827
+ console.log('embedding_cmd: ' + (cfg.embedding_cmd || '(未设置)'));
2828
+ console.log('embedding_timeout: ' + (cfg.embedding_timeout || 3000));
2643
2829
  console.log('当前生效用户级位置: ' + userRoot());
2644
2830
  if (cfg.serve && Object.keys(cfg.serve).length) console.log('serve: ' + JSON.stringify(cfg.serve));
2645
2831
  }
@@ -2648,8 +2834,9 @@ function cmdConfigGet() {
2648
2834
  function mcpTools() {
2649
2835
  return [
2650
2836
  { name: 'remember', description: '写入一条记忆。参数 type(FACT/PREF/BOUND/COMMIT)、subject、statement 必填;owner 可选,默认当前智能体', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'FACT/PREF/BOUND/COMMIT' }, subject: { type: 'string' }, statement: { type: 'string' }, owner: { type: 'string' } }, required: ['type', 'subject', 'statement'] } },
2651
- { name: 'recall', description: '检索记忆。query 可选;type 可选;limit 可选(默认 20)。只返回当前智能体可读记忆', inputSchema: { type: 'object', properties: { query: { type: 'string' }, type: { type: 'string' }, limit: { type: 'number' } } } },
2652
- { name: 'search', description: '检索记忆(同 recall)。query 可选;type 可选;limit 可选(默认 20)', inputSchema: { type: 'object', properties: { query: { type: 'string' }, type: { type: 'string' }, limit: { type: 'number' } } } },
2837
+ { name: 'recall', description: '检索记忆。query 可选;type 可选;limit 可选(默认 20);explain 可选。只返回当前智能体可读记忆;embedding 插件只能由本机 config 配置,远端不可传命令', inputSchema: { type: 'object', properties: { query: { type: 'string' }, type: { type: 'string' }, limit: { type: 'number' }, embeddingTimeout: { type: 'number' }, explain: { type: 'boolean' } } } },
2838
+ { name: 'search', description: '检索记忆(同 recall)。query 可选;type 可选;limit 可选(默认 20);explain 可选;embedding 插件只能由本机 config 配置,远端不可传命令', inputSchema: { type: 'object', properties: { query: { type: 'string' }, type: { type: 'string' }, limit: { type: 'number' }, embeddingTimeout: { type: 'number' }, explain: { type: 'boolean' } } } },
2839
+ { name: 'context', description: '生成开工上下文包。focus 可选;limit 可选;budget 可选;explain 可选;embedding 插件只能由本机 config 配置,远端不可传命令', inputSchema: { type: 'object', properties: { focus: { type: 'string' }, limit: { type: 'number' }, budget: { type: 'number' }, explain: { type: 'boolean' }, embeddingTimeout: { type: 'number' } } } },
2653
2840
  { name: 'forget', description: '删除一条记忆。file 为记忆文件路径(如 facts/2026-08-24-0001.md 或文件名)', inputSchema: { type: 'object', properties: { file: { type: 'string' } }, required: ['file'] } },
2654
2841
  { name: 'archive', description: '归档旧记忆。days 默认 180;threshold 默认 0.4', inputSchema: { type: 'object', properties: { days: { type: 'number' }, threshold: { type: 'number' } } } },
2655
2842
  { name: 'reindex', description: '重建索引(手动改 .md 后校正;扫描 facts/prefs/bounds/commits 四目录)', inputSchema: { type: 'object', properties: {} } },
@@ -2673,7 +2860,24 @@ function callTool(name, args, ctx) {
2673
2860
  return { text: r.text, error: r.error };
2674
2861
  }
2675
2862
  if (name === 'recall' || name === 'search') {
2676
- const r = recallCore(args.query ? String(args.query) : null, { limit: args.limit || 20, type: args.type ? String(args.type) : null, agent: agent });
2863
+ const r = recallCore(args.query ? String(args.query) : null, {
2864
+ limit: args.limit || 20,
2865
+ type: args.type ? String(args.type) : null,
2866
+ agent: agent,
2867
+ embeddingTimeout: args.embeddingTimeout || 3000,
2868
+ explain: !!args.explain
2869
+ });
2870
+ return { text: r.text, error: r.error };
2871
+ }
2872
+ if (name === 'context') {
2873
+ const r = contextCore({
2874
+ focus: args.focus ? String(args.focus) : '',
2875
+ limit: args.limit || 10,
2876
+ budget: args.budget || 0,
2877
+ explain: !!args.explain,
2878
+ embeddingTimeout: args.embeddingTimeout || 3000,
2879
+ selfAgent: agent
2880
+ });
2677
2881
  return { text: r.text, error: r.error };
2678
2882
  }
2679
2883
  if (name === 'forget') {
@@ -3315,7 +3519,7 @@ function usage() {
3315
3519
  ['view', '启动用户查看平台(--port/--host;口令解锁浏览/授权/吊销 AI)'],
3316
3520
  ['reset-password', '重设主口令(忘口令用恢复钥匙)'],
3317
3521
  ['key', '管理 AI 私密读取授权缓存(list / authorize <id> / revoke <id>)'],
3318
- ['config', '查看/记住记忆库位置(get / set memory_home <目录>)']
3522
+ ['config', '查看/设置配置(get / set memory_home <目录> / set embedding_cmd <命令> / set embedding_timeout <毫秒>)']
3319
3523
  ]],
3320
3524
  ['平台与服务', [
3321
3525
  ['serve', '启动 MCP 记忆引擎(streamable HTTP;--stdio 本地零进程)'],
@@ -3343,7 +3547,7 @@ async function main() {
3343
3547
  if (!args.length) { usage(); return; }
3344
3548
  const opts = {};
3345
3549
  const positional = [];
3346
- const valueOpts = new Set(['--type', '--limit', '--days', '--out', '--owner', '--agent', '--threshold', '--scope', '--host', '--port', '--dir', '--name', '--user', '--relationship', '--source', '--weight', '--budget', '--password', '--new-password', '--recovery-key', '--reason', '--merge', '--model', '--subject']);
3550
+ const valueOpts = new Set(['--type', '--limit', '--days', '--out', '--owner', '--agent', '--threshold', '--scope', '--host', '--port', '--dir', '--name', '--user', '--relationship', '--source', '--weight', '--budget', '--password', '--new-password', '--recovery-key', '--reason', '--merge', '--model', '--subject', '--embedding', '--focus', '--embedding-timeout']);
3347
3551
  for (let i = 0; i < args.length; i++) {
3348
3552
  const a = args[i];
3349
3553
  if (a === '--version' || a === '-v') { console.log(VERSION); return; }
@@ -3394,6 +3598,9 @@ async function main() {
3394
3598
  else if (a === '--merge') opts.merge = v;
3395
3599
  else if (a === '--model') opts.model = v;
3396
3600
  else if (a === '--subject') opts.subject = v;
3601
+ else if (a === '--embedding') opts.embedding = v;
3602
+ else if (a === '--focus') opts.focus = v;
3603
+ else if (a === '--embedding-timeout') opts.embeddingTimeout = parseInt(v, 10) || 3000;
3397
3604
  } else if (a.startsWith('--')) {
3398
3605
  console.error('未知选项: ' + a);
3399
3606
  process.exit(2);
@@ -3411,7 +3618,7 @@ async function main() {
3411
3618
  const sub = rest[0];
3412
3619
  if (sub === 'set') cmdConfigSet(rest[1], rest[2]);
3413
3620
  else if (sub === 'get') cmdConfigGet();
3414
- else { console.error('config 子命令: set memory_home <目录> / get'); process.exit(2); }
3621
+ else { console.error('config 子命令: set memory_home <目录> / set embedding_cmd <命令> / set embedding_timeout <毫秒> / get'); process.exit(2); }
3415
3622
  break;
3416
3623
  }
3417
3624
  case 'remember': cmdRemember(rest[0], rest[1], rest[2], opts); break;
@@ -3464,7 +3671,7 @@ async function main() {
3464
3671
  }
3465
3672
  }
3466
3673
 
3467
- if (require.main === module) { main().catch(function (e) { console.error('错误: ' + (e && e.message ? e.message : String(e))); process.exit(2); }); }
3674
+ if (require.main === module) { main().catch(function (e) { console.error('错误: ' + (e && e.message ? e.message : String(e))); console.error('修复建议: 若与记忆库/密钥/权限有关,请检查 memory_home 路径、主口令与恢复钥匙,或运行 yotta-memory config get 确认位置;仍无法解决请把上面的错误信息反馈给开发者。'); process.exit(2); }); }
3468
3675
  module.exports = {
3469
3676
  VERSION: VERSION,
3470
3677
  userRoot: userRoot,
@@ -3536,6 +3743,9 @@ module.exports = {
3536
3743
  utilityScore: utilityScore,
3537
3744
  utilityBreakdown: utilityBreakdown,
3538
3745
  semanticMatch: semanticMatch,
3746
+ runEmbeddingPlugin: runEmbeddingPlugin,
3747
+ cosineSimilarity: cosineSimilarity,
3748
+ embeddingCandidates: embeddingCandidates,
3539
3749
  pinyinTokens: pinyinTokens,
3540
3750
  isEncrypted: isEncrypted,
3541
3751
  collectOwners: collectOwners,
@@ -3585,4 +3795,3 @@ module.exports = {
3585
3795
  viewServerCore: viewServerCore,
3586
3796
 
3587
3797
  };
3588
-
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-memory",
3
- "version": "0.8.5",
4
- "description": "Yuanyi (元忆) — boundary-aware, file-based memory for AI agents. File-based, zero-dependency, diff/rollback-able; FACT/PREF/BOUND/COMMIT four types (public shared / private isolated), user-level + project-level storage; v0.8 semantic search + usage feedback loop + rule-layer self-organization + psychological-log distillation (self-learning/self-evolving/self-improving).",
3
+ "version": "0.9.0",
4
+ "description": "Yuanyi (元忆) — boundary-aware, file-based memory for AI agents. File-based, zero-dependency, diff/rollback-able; FACT/PREF/BOUND/COMMIT four types (public shared / private isolated), user-level + project-level storage; v0.9 recall quality, context focus, optional local embedding plugin, plus v0.8 semantic search, feedback loop, self-organization, and distillation.",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "agent-skills",
@@ -23,7 +23,8 @@
23
23
  "bin",
24
24
  "README.zh-CN.md",
25
25
  "NOTICE",
26
- "CHANGELOG.md"
26
+ "CHANGELOG.md",
27
+ "!scripts/test_*.py"
27
28
  ],
28
29
  "repository": {
29
30
  "type": "git",
@@ -0,0 +1,33 @@
1
+ # 元忆 FAQ / 避坑指南
2
+
3
+ > 常见问题速查:记忆找不到、权限/加密困惑、连不上时先看这里。
4
+
5
+ ## 1. 记忆类型选错了怎么办?
6
+ 类型只在写入时提示、不阻止(FACT=公共 / PREF、BOUND、COMMIT=私密)。写错不影响已写入内容;想改类型可 `forget` 后按正确类型重写。不需要提示可用 `--no-hint`。
7
+
8
+ ## 2. 私密区加密怎么用?
9
+ `init` 默认初始化加密库(需主口令 + 恢复钥匙,请妥善保存);明文库可用 `migrate` 升级为加密。私密区文件为 `.md.enc`,可 git 版本化。查看/授权用 `yotta-memory view`(口令解锁,浏览/授权/吊销 AI)。
10
+
11
+ ## 3. 多智能体权限怎么隔离?
12
+ 公共 FACT 所有智能体可读;PREF / BOUND / COMMIT 按 owner 物理隔离,其他智能体需显式授权(`key authorize <id>` 或 `view` 平台授权)。不授权读不到,也不会被别的智能体读到。
13
+
14
+ ## 4. 记忆找不到了?
15
+ 先 `config get` 确认 `memory_home` 指向的库;再 `reindex` 重建索引(升级后索引版本变化会自动重建);最后 `recall <关键词>` / `search <词>` 语义检索。跨项目记忆在项目级 `.yottamemory`。
16
+
17
+ ## 5. 忘记主口令了?
18
+ 用初始化时保存的**恢复钥匙**:`yotta-memory reset-password`。没有恢复钥匙则私密区无法解锁(这是加密的预期行为),公共 FACT 不受影响。
19
+
20
+ ## 6. 局域网(便携记忆盘)怎么连?
21
+ 引擎主机 `lan enable` 注册开机自启(Windows 计划任务 / Linux systemd)→ `token new --agent <id>` 生成 token;客户端配 `url: http://<主机IP>:8787/mcp` + `Authorization: Bearer <token>` + `X-Agent-Id: <id>`。`lan status` 查状态。
22
+
23
+ ## 7. MCP 工具没加载?
24
+ 检查客户端 `mcpServers` 已配置 yotta-memory(url + token);改配置后重启/重载会话。本机直连可不配 MCP,直接用 CLI。
25
+
26
+ ## 8. 记忆库在哪个目录?
27
+ `yotta-memory config get` 查看;`config set memory_home <目录>` 改位置。项目级记忆用 `init --project`(存 `.yottamemory/` 随项目共享)。
28
+
29
+ ## 9. 跨会话恢复上下文?
30
+ 开工运行 `yotta-memory context`(身份 + 画像 + 近期记忆 + 边界 + 承诺),需要细节再 `recall <关键词>`。
31
+
32
+ ## 10. 备份与迁移?
33
+ `export --out 文件.json` 导出全部记忆;`import <文件.json>` 恢复。公共 FACT 是明文文件可直接 git 备份;私密区用 export(需解锁)。