@yottameta/yotta-memory 0.8.7 → 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,3 +1,14 @@
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
+
1
12
  ## v0.8.7 (2026-09-01)
2
13
 
3
14
  评测反馈优化(文档 + 错误提示,功能不变)。
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.
@@ -221,9 +224,9 @@ npm i -g @yottameta/yotta-memory
221
224
  |---|---|
222
225
  | `yotta-memory init [--project] [--dir <dir>]` | Initialize the store (default user-level `~/.yottamemory/`; --dir sets an explicit location) |
223
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) |
224
- | `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) |
225
228
  | `yotta-memory profile [--owner <id>]` | Generate a user profile (aggregates `private/<owner>` verbatim, zero inference, writes `profile.md`; cross-owner denied by default) |
226
- | `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) |
227
230
  | `yotta-memory forget <file>` | Delete a memory (by type-dir path or file name) |
228
231
  | `yotta-memory archive [--days 180] [--threshold 0.4]` | Archive old memory (final score + age; immutable excluded) |
229
232
  | `yotta-memory reindex` | Rebuild the index (after manually editing .md) |
@@ -301,7 +304,7 @@ Register the connection in the agent's MCP config (`url` + two headers):
301
304
  }
302
305
  ```
303
306
 
304
- 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).
305
308
 
306
309
  ### Location persistence
307
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` 自动唯一化根,同一文件只展示一次。
@@ -261,9 +264,9 @@ npm i -g @yottameta/yotta-memory
261
264
  |---|---|
262
265
  | `yotta-memory init [--project] [--dir <目录>]` | 初始化记忆库(默认用户级 `~/.yottamemory/`;--dir 显式指定位置)|
263
266
  | `yotta-memory remember <type> <subject> <statement> [--owner <id>] [--source <来源>] [--weight <0..>] [--verify] [--no-hint]` | 写入记忆(同 subject+statement 自动更新;--owner 标注归属;--source 记录来源;--weight 重要性权重、去重取 max;--verify 写后回读;--no-hint 关闭类型提示)|
264
- | `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 <其它>` 仅作身份声明、不授予跨读;项目级优先)|
265
268
  | `yotta-memory profile [--owner <id>]` | 生成用户画像(聚合 `private/<owner>/` 原文,零推断,写 `profile.md`;跨 owner 默认拒绝)|
266
- | `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 选择解释)|
267
270
  | `yotta-memory forget <文件>` | 删除一条记忆(按类型目录路径或文件名)|
268
271
  | `yotta-memory archive [--days 180] [--threshold 0.4]` | 归档旧记忆(盖棺分+年龄,immutable 除外)|
269
272
  | `yotta-memory reindex` | 重建索引(手动改 .md 后校正)|
@@ -341,7 +344,7 @@ yotta-memory recall --type FACT --limit 10
341
344
  }
342
345
  ```
343
346
 
344
- 连接后可通过 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 私密隔离)。
345
348
 
346
349
  ### 位置持久化
347
350
 
@@ -360,4 +363,4 @@ yotta-memory config get
360
363
 
361
364
  ## 许可证
362
365
 
363
- 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.7
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` 等文件,否则会绕过权限边界、读到别的智能体私密内容。
@@ -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 后校正)|
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
 
@@ -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.7';
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;
@@ -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.7",
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",