# Memory

> 跨会话记忆用户偏好、项目决策和历史上下文。当用户说"记住"或提问依赖过往信息时自动检索。不支持临时信息、密钥或可从代码直接查到的事实。

- Skill: `laomou/memory` (Agent Skill)
- Install (CLI): `npx skillmds@latest add laomou/memory`
- Raw SKILL.md: https://api.skillmd.com/api/skills/laomou/memory/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: laomou (https://skillmd.com/u/laomou)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/laomou/memory

---


## ⚡ 速查表（30秒决策）

| 用户说了什么 | 你的行动 |
|-------------|---------|
| 帮我写/改/重构代码 | 🔍 先检索代码风格偏好 |
| 我之前说过... / 还记得吗 | 🔍 检索历史记忆 |
| 我喜欢/习惯/总是... | 💾 保存为偏好 |
| 记住这个 / 记下来 | 💾 立即保存 |
| 这次用 X 方式（一次性） | ⛔ 不保存 |
| 我改成 X 了（与旧记忆矛盾） | 🔄 先询问确认 → 确认后调用 `update_memory` 覆盖 |
| 忘掉/删除我之前说的 X | 🗑️ 调用 `search_memories` 找到后 `delete_memory` |
| 检索无结果 | 正常回复 + 询问是否需要记住 |

> 四条底线:**主动但克制**(该查才查,不滥用工具)、**精准保存**(只存稳定的长期事实)、**保持整洁**(优先更新已有记忆而非新增)、**尊重用户**(记忆服务于用户,用户可随时修正)。

---

## 🚀 新会话冷启动

**这是一次会话里最先做的事**，先于下面所有规则。

新会话第一轮涉及**代码/行为决策**时,先调 `get_user_context(user_id=...)`
把用户的核心偏好/身份/环境一次性吃进上下文(而非每次 search)。
`user_id` 按「🎯作用域选择指南 · 如何确定 user_id」解析(`git config user.name`,
取不到退回 `$USER`)。

- 返回按 importance:high 优先,默认 10 条
- 只包含 `preference` / `identity` / `environment` 三类核心 category
- 用完了这次会话不用再查(除非用户切了 user_id)
- **`user_id` 必须传**:留空只会返回"无 user_id 归属"的全局记忆(这是防跨用户
  泄露的设计),拿不到该用户的画像 —— 冷启动会静默变成空转

替代方案:如果用户明确说"你不用记忆",跳过这步。

---

## 🔍 何时检索记忆

### 🔴 高优先级（必须检索）

在以下情况，你**必须**先调用 `search_memories` 再回复：

| 触发场景 | 关键词/模式 | 检索目标 |
|---------|------------|---------|
| 用户提及过往对话 | 我之前说过、还记得吗、按照老规矩、你上次 | 历史决策、过往偏好 |
| 代码生成/重构请求 | 帮我写、重构、优化、改成 | 代码风格、命名规范、架构偏好 |
| 个人偏好表达 | 我喜欢、我习惯、我倾向、我不喜欢 | 用户偏好、习惯设置 |
| 个性化需求 | 按照我的风格、符合我平常的做法 | 长期偏好 |

**检索示例**：
- 用户：帮我写一个排序函数 → 检索：`代码风格`、`命名规范`、`排序偏好`
- 用户：还记得我之前说的项目结构吗？ → 检索：`项目结构`、`文件组织`

### 🟡 中优先级（建议检索）

在以下情况，**建议**先检索，但非强制：

| 触发场景 | 说明 |
|---------|------|
| 提及项目名或技术栈 | 可能有相关的项目约定 |
| 询问历史决策原因 | 为什么上次选择了 X？ |
| 表达模糊偏好 | 我喜欢干净一点的代码 |

### 🟢 低优先级（按需检索）

在以下情况，一般不需要检索：

- 纯技术性问题（Python 3.11 发布于何时？）
- 通用知识问答
- 当前任务的明确一次性指令（这次帮我用 X 方式）

---

## 📭 检索无结果时的处理

当 `search_memories` 返回空列表时：

1. **不要直接说"找不到"**，而是：
   - 我好像没有记录过这方面的偏好，需要我记住吗？
   - 暂时没找到相关记忆。如果你想让我记住，随时告诉我。

2. **如果用户随后表达了偏好**：调用 `add_memory` 保存

3. **不要因为检索无结果而拒绝回答**：正常响应用户请求，只是额外询问是否需要记忆

---

## 📖 如何利用检索到的记忆

检索到相关记忆后：

1. **显式引用**：在回复中明确提到"根据你之前的偏好..."
2. **主动验证**：如果记忆时间较久（>30天），可以顺带确认：我记得你喜欢 X，这个偏好还有效吗？
3. **优先使用**：如果检索到多条记忆，优先采用更新时间最新的
4. **末尾透明化引用**（关键):在回复的**最末尾**加一行,让用户知道你用了哪些记忆:

```
🧠 用了 2 条记忆:pytest 偏好、代码洁癖
```

用户能一眼看出你是"根据记忆答的"还是"猜的",信任度大幅提升。规则:
- 用了 ≥1 条记忆就加这行,列出核心内容摘要(≤10 字/条)
- 完全没用记忆(纯语言模型答复)不加
- 不要加太多字,3-5 条已经上限——多了改成"用了 N 条记忆"

---

## 💾 何时保存记忆

**一条一事，精炼原子事实，第一人称陈述句**。不要把整段对话塞进去。
**用用户使用的语言存**——用户用中文就存中文，英文就存英文。语义检索跨语言效果差。

### ✅ 必须保存（调用 `add_memory`）

| 触发场景 | 示例 | 说明 |
|---------|------|------|
| 用户明确要求记住 | 记住这个、以后都这样、记下来 | 最高优先级 |
| 稳定的偏好（重复 ≥2 次） | 不同时间多次表达相同偏好 | 确认为长期偏好 |
| 绝对化表达 | 我总是、我从不、我坚决 | 强烈信号 |
| 长期事实 | 我的项目用 Python 3.11、我的邮箱是 xxx | 稳定事实 |

> ⚠️ 即使是绝对化表达，也建议先确认是否稳定。如果用户在情绪化场景下说出（如"我永远讨厌这个 Bug"），应谨慎保存。

### 🔶 考虑保存（评估后决定）

| 场景 | 判断标准 | 行动 |
|------|---------|------|
| 单次偏好表达 | 用户说了一次"我喜欢简洁" | 先不保存，等确认稳定后再存 |
| 当前任务相关 | 这次帮我用 tabs | 不保存（仅本次有效） |
| 隐含偏好 | 从对话中推断的偏好 | 先询问用户确认 |

### ⛔ 不保存（避免污染记忆）

- 一次性临时要求
- 用户明确说"只这次"
- 纯粹的情绪表达
- 与现有记忆矛盾的信息（应先询问）
- **密钥、令牌、未脱敏 PII** —— 记忆可检索，敏感值直接不存
- **第三方隐私**：用户提到"我同事 alice 说了 X"，去掉人名匿名化("团队某成员反馈 X")
- 从代码/git log/CLAUDE.md 直接查得到的事实

### 正例 / 反例

| 反例 ❌ | 正例 ✅ | 为什么 |
|---------|---------|--------|
| `"用户问怎么写测试"` | `"用户偏好 pytest，不用 unittest"` | 反例记录了**问题**，不是**事实/结论** |
| `"讨论了项目结构后决定用 monorepo"` | `"项目用 monorepo，原因是共享依赖多"` | 反例是叙述历史，正例是可复用事实 |
| `"用户说他很忙"` | (不存) | 情绪/临时状态无跨会话价值 |
| `"今天修了 auth 的 bug"` | (不存) | git log 里就有，记忆里没意义 |
| `"用户 API key 是 sk-xxx"` | (拒绝存) | 密钥类信息不能进记忆 |

---

## 🔄 更新、去重与矛盾处理

当 `search_memories` 返回**高度相似**的记忆（语义相似度 ≥ 0.85）时，优先更新而不是新增。

### 信息一致但更详细 → 补充细节
- **旧记忆**：用户喜欢简洁代码
- **新信息**：用户喜欢简洁代码，希望单行函数也保持简洁
- **行动**：调用 `update_memory` 补充细节

### 信息发生变化 → 直接更新
- **旧记忆**：用户使用 tabs 缩进
- **新信息**：用户改用 spaces 了
- **行动**：调用 `update_memory` 更新内容

### 信息相互矛盾 → 先问再改
- **旧记忆**：用户喜欢 Vue
- **新信息**：用户现在用 React
- **行动**：**先询问用户**：我记忆中你偏好 Vue，现在转向 React 了吗？确认后再更新

同理，**发现库里已有多条记忆互相矛盾**时：也是先询问用户，确认后删错留对（`delete_memory` 删掉过期的那条）。

### 确认话术模板
- 我记忆中你偏好 X，现在要改为 Y 吗？确认后我会更新记忆。
- 之前记录的是 X，你说的是 Y，这两者矛盾。请确认哪个是对的？
- 我记忆里的 X 是旧版本，现在升级到 Y 了对吗？

### 用户口头纠正记忆
- 用户：我之前说的不是 X，是 Y
- 行动：调用 `update_memory` 直接修正，无需询问
- 话术：已更新记忆，将 X 修正为 Y。
- 若用户**否认整条**（"我从没说过我喜欢 X"）：直接 `delete_memory` 删除并致歉——"抱歉，我记错了，已删除该条记忆。"

### 去重决策

`add_memory` 默认查重（相似度 ≥0.85），返回包含 `duplicate_id` 的 JSON。三种处理：

| 情况 | 处理 | 例子 |
|------|------|------|
| 内容真变了（事实迭代） | `update_memory(duplicate_id, content=新)` | 旧："用户偏好 pytest" → 新："用户偏好 pytest + hypothesis" |
| 只是换措辞，信息量相同 | 跳过，不入库 | "我用 pytest" vs "偏好 pytest" |
| 是新维度（信息互补） | `add_memory(..., force=True)` | 已存"偏好 pytest"，新增"偏好 mock 尽量少" |

**一次会话内多次调整偏好**——同话题内等最终结论确定再 update 一次；不同话题分别处理。

> ⚠️ **查重只在同一作用域内比对**。同样的内容换个 `user_id` 会各存一份、不会命中
> `duplicate_id`。所以"为什么这条明显重复却没触发查重"通常是作用域填得不一致 ——
> 先确认 `user_id` / `app_id` 是不是同一个值，而不是直接 `force=True`。

---

## 🎯 作用域选择指南

| 场景 | 使用作用域 | 示例 |
|------|-----------|------|
| 记住用户个人偏好 | `user_id` | 代码风格、语言偏好、个人习惯 |
| 记住 Agent 自己的策略 | `agent_id` | 你上次推荐的方案、你自己的决策逻辑 |
| 记住项目级约定 | `app_id` | 这个项目用 tabs 缩进、项目的技术栈 |
| 记住某次会话上下文 | `run_id` | 本次讨论的临时约定、这次的决策背景 |

### 组合使用
- `user_id` + `app_id`：该用户在该项目中的偏好（**最常见的组合**）
- `user_id` + `agent_id`：该用户针对该 Agent 的特殊要求
- `agent_id` + `run_id`：该 Agent 在本次会话中的临时策略

### 默认规则（按信息归属强制填，不要只填 user_id）

- 信息属于**用户全局偏好**（代码风格、语言、个人习惯）→ `user_id`
- 信息与**某个项目/应用相关**（技术栈、版本规则、项目约定、路径配置）→ **必填** `app_id`（= 项目名）
- 信息是 **Agent 自身策略** → `agent_id`
- 信息**仅本次会话有效** → `run_id`
- 组合优先 `user_id` + `app_id`（该用户在该项目中的事实）
- **`add_memory` 不带作用域也不会报错**，会存成一条"无归属"的全局记忆 —— 工具不会
  拦你，所以作用域得靠上面的规则自觉填全。（只有 `delete_all_memories` 强制要求
  至少一个作用域，那是为了防误删整库。）

### 如何确定 user_id

`user_id` 标识**人**,必须全程稳定 —— 同一个人在不同 agent、不同项目下必须解析出同一个值,否则记忆会互相看不见。取值：

1. 优先 `git config user.name`
2. 取不到时退回操作系统用户名（`$USER`，Windows 用 `%USERNAME%`）

**原样使用,不要转大小写、不要替换空格** —— 任何变换都必须在所有地方一致,否则同一个人会被拆成多个作用域。

> ⚠️ **务必配成全局**:`git config --global user.name <名字>`。
> 如果只在某个仓库里配过（`--local`），那在其他项目下 `git config user.name` 取到的是空,
> 于是回退到 `$USER` —— 同一个人在 A 项目是 `laomou`、在 B 项目变成 `mourui`,
> 两边的记忆彼此不可见,而且不会有任何报错提示你。

### 如何推断 app_id

凡涉及具体项目的信息，`app_id` 必填。取值 = **`<group>_<repo>`**（namespace + 仓库名，避免不同 group 下同名仓库冲突）：

1. 优先从 remote URL 解析：`git remote get-url origin`，取主机名后、`.git` 前那段路径，把 `/` 换成 `_`（即 `<group>_<repo>`）
2. 没有 remote 时，退回 `git rev-parse --show-toplevel` 的 basename（仅 repo 名，无 group 前缀）
3. 都没有，取当前工作目录 basename

> 分隔符用 `_`：group/repo 名内部常用 `-`，用 `_` 作分隔符不会冲突。
> 跨项目通用的用户偏好（如"我偏好 pytest"）不填 `app_id`。

---

## 🏷️ metadata 约定

保存记忆时，在 `metadata` 中添加标准化字段，便于精确检索：

| key | 值（只能选一个） | 说明 |
|-----|-----|------|
| `category` | `preference` / `identity` / `environment` / `decision` / `anti_pattern` / `episode` / `concept` | 记忆类型 |
| `importance` | `high` / `medium` / `low` | 重要度 |
| `source` | `user` / `inferred` / `system` | 来源 |

**category 对照**：

| category | 适用信号 | 例子 |
|----------|---------|------|
| `preference` | "我喜欢/偏好…" | 用户偏好 pytest，不用 unittest |
| `identity` | 角色、技能、习惯（**人的属性**） | 资深 Go 工程师、有代码洁癖 |
| `environment` | 工具/环境（**外部条件**） | 团队用 GitLab、Mac M1、公司代理 |
| `decision` | "我们决定…""约定是…" | 项目选了 PostgreSQL |
| `anti_pattern` | "X 行不通…""别再试…" | 之前试过 Redis 队列，吞吐不够 |
| `episode` | "上次/之前…"的结论 | 上次定了迁移方案 A |
| `concept` | 术语、缩写、约定俗成 | 项目里 `svc` 指订单服务 |

**`tags` 是自由主题词**（逗号分隔），用于浏览/展示，不同于 metadata.category 的结构化类型。

`metadata_filter` **只支持多键 AND 精确匹配**——同时满足多个 key 的条件。**不支持** `OR` / `IN` / `NOT` / 子串。

```json
// ✅ 能查出 category=preference AND importance=high 的结果
metadata_filter='{"category":"preference","importance":"high"}'

// ❌ 不支持 OR 或子串
metadata_filter='{"category":"preference OR decision"}'
```

---

## ⏰ 何时设置 TTL（过期时间）

先分清短期与长期 —— 这决定了要不要设 TTL：

| 类型 | 存储方式 | 适用场景 |
|------|---------|---------|
| **短期上下文** | 不保存，或设置短 TTL（如 `run_id` + 1天） | "这次帮我用 X 方式" |
| **长期记忆** | 不设 TTL，关联 `user_id` | "我喜欢简洁代码" |

按 category 的建议：

| 信息类型 | TTL 建议 | 示例 |
|---------|---------|------|
| `preference`/`identity`/`environment`/`decision` | 不设置（永久） | 除非用户显式改口，不该过期 |
| `concept`/`anti_pattern` | 不设置 | 术语和踩坑长期有价值 |
| `episode` | 1 天 ~ 不设 | 情节可能过时，视具体决策的时效性定 |
| 临时约定/便签 | 1 小时 ~ 7 天 | 单次 run 内的进度，到期后被回收 |

TTL 到期后，记忆**立刻从检索/列表结果中消失**，但数据行还在库里 —— 真正删除发生在
两个时机:`lm-mem mcp` 启动时会自动清一次(可用 `LM_MEM_AUTO_PURGE=0` 关闭),
或你主动调 `purge_expired`。

所以 TTL 的语义是"到期即不可见",不要指望它在会话中途把行也删掉。

---

## 📝 真实对话示例（Few-shot）

### 示例 1：检索代码风格偏好
**用户**：帮我写一个读取 CSV 文件的函数
**AI 思考**：这是代码生成请求 → 触发高优先级检索
**AI 行动**：`search_memories` 检索 `代码风格`、`偏好`
**检索结果**：用户喜欢 pandas 风格，偏好链式调用
**AI 回复**：好的，我按你喜欢的 pandas 链式调用来写...

### 示例 2：更新事实变更
**用户**：我的项目已经从 Python 3.10 升级到 3.12 了
**AI 思考**：这是长期事实变更 → 需要更新
**AI 行动**：`search_memories` 找到旧记忆 "项目用 Python 3.10"
**AI 回复**：确认一下，我记忆中你的项目用的是 Python 3.10，现在升级到 3.12 了对吗？确认后我帮你更新记忆。
**用户**：对
**AI 行动**：`update_memory` 覆盖旧记忆

### 示例 3：不保存一次性指令
**用户**：这次帮我用 tabs 缩进
**AI 思考**：用户说"这次" → 一次性指令，不保存
**AI 行动**：不调用任何记忆工具
**AI 回复**：好的，这次用 tabs 缩进。

---

## ⚠️ 异常情况与日常运维

### 检索结果过多（> 10 条）
- 只取相似度最高的 3-5 条融入回复
- 如果结果相互矛盾，优先采用更新时间最新的

### 记忆工具调用失败（网络/服务异常）
- 不要反复重试（最多 2 次）
- 正常回复用户请求，但告知"记忆服务暂时不可用，本次对话内容不会被记录"
- 提示用户稍后可以重新触发记忆

### 用户一次给出多条偏好
- 用户：我习惯用 tabs，喜欢简洁代码，项目用 Python
- 行动：分别保存为 3 条独立的记忆
- 不要合并成一条（粒度太粗，检索不精准）

### 主动提醒记忆可能过期
- 当用户提到与旧记忆相关的话题时，可以主动询问：
  - 我记忆中你偏好 X，这个偏好还有效吗？
  - 之前记录过你的项目用 X，现在有变化吗？

### 记忆库变大后的整理
- 当记忆数量超过 50 条时，可以在对话中建议：
  - 你的记忆库已经有 50+ 条了，需要我帮你导出备份或清理过期记录吗？
  - 可以用 `memory_stats` 查看统计，`export_memories` 导出备份。

---

## ✅ 保存前自检清单

在调用 `add_memory` 或 `update_memory` 之前，快速确认：

- [ ] 这条信息是**稳定的**（非一次性、非临时）？
- [ ] 这条信息对**未来的对话**有帮助？
- [ ] 是否已存在**相似或矛盾**的记忆？（如果是，应先检索确认）
- [ ] 这条信息的**作用域**按归属填全了？（全局偏好→user_id；项目相关→**必加 app_id**；agent 策略→agent_id；本次会话→run_id）
- [ ] `metadata.category` 值是否在 7 个约定值中？
- [ ] 是否需要设置 **TTL**（过期时间）？

如果全部勾选 → 放心保存 ✅
如果有任何一项不确认 → 先询问用户

---

## 🔧 常用工具速查

日常最常用 6 个,其余按需查 MCP 描述:

| 工具 | 场景 |
|------|------|
| `get_user_context(user_id, limit=10)` | **新会话冷启动**,一次拉核心 preference/identity/environment。`user_id` 别留空(留空只返回无归属的全局记忆) |
| `add_memory(content, user_id?, tags?, metadata?, ttl_seconds?)` | 保存(默认自动查重) |
| `search_memories(query, user_id?, metadata_filter?, limit?)` | 语义检索 |
| `update_memory(mem_id, content?, metadata?, tags?)` | 事实变化时原地更新。`tags` 不传=不改，传 `""`=清空标签 |
| `delete_memory(mem_id)` | 显式忘记某条 |
| `import_memories(data, fmt?, overwrite?, new_ids?)` | 从 `export_memories` 结果还原备份 |

