membrain
# membrain-lite
给聊天机器人用的**最小记忆内核**:会记、会想、会忘。
不是向量数据库的封装,也不是 RAG 模板。它解决的问题是:
> 一个天天陪你聊天的机器人,三个月后还记不记得你讨厌什么。
核心只有三件事:
1. **写入有取舍** —— 只存「以后用得上」的,一句话能说完就不写一段。
2. **召回有分层** —— 关键词、语义、重要性、新鲜度、图谱五路加权。
3. **遗忘有曲线** —— 不是删掉,是越来越难想起来(三层:习惯 / 可逐渐遗忘 / 边缘)。
零依赖可跑(纯标准库 + SQLite),2 分钟能看到效果。
> 🤖 **如果你是一个 AI agent(DSH / Claude / Codex / Cursor),先读 [`docs/AGENT-GUIDE.md`](docs/AGENT-GUIDE.md)(中文)或 [`docs/AGENT-GUIDE.en.md`](docs/AGENT-GUIDE.en.md)(English)。**
> 那里面是给机器看的:怎么装、文件怎么摆、什么该记什么不该记、怎么调参、
> 以及**装完之后该跟你的主人说什么**。这份 README 是给人看的。
---
## 三分钟上手(傻瓜版)
不想装、不想配、不想懂 Python 也行:
1. 点右上角绿色的 **Code → Download ZIP**,解压到任意文件夹;
2. 双击里面的 **`一键体验.bat`**(Windows);
3. 浏览器会自动打开一张会转的图 —— 那就是它的记忆长什么样。
脚本做的事:写入 29 条示例记忆 → 导成图用的数据 → 起一个本地网页 → 打开浏览器。
关掉那个黑窗口,服务就停了。
> macOS / Linux 用户:终端里进到目录,跑
> `python3 -m membrain demo && python3 -m membrain export --out examples/radial/memories.jsonl && python3 -m http.server 8766`,
> 然后打开 <http://127.0.0.1:8766/examples/radial/>。
> 需要 Python 3.9+,除了标准库什么都不用装。
> (想用 `config.toml` 改权重/半衰期的话要 3.11+:读配置用的是 `tomllib`,
> 低版本会**静默忽略**配置文件、只跑默认值。)
---
## 命令行怎么用
```bash
python -m membrain init # 建库
python -m membrain demo # 写入 29 条示例记忆
python -m membrain recall "阿宁讨厌什么" --explain
python -m membrain eval # 跑评估集
```
`recall` 的输出长这样:
```
[0.678] (fact) 阿宁讨厌被叫笨蛋,也讨厌被说「你这样不行」
why: {"semantic": 0.424, "keyword": 0.6, "importance": 0.92,
"recency": 1.0, "graph": 0.333, "neighbor": false}
```
`--explain` 会把每一路的分都摊开,调参时看它,排查「她为什么想起这个」也看它。
---
## 它是怎么打分的
```
score = 语义 * 0.18
+ 关键词 * 0.46
+ 重要度 * 0.14
+ 新鲜度 * 0.11
+ 图谱 * 0.11
```
几个刻意的选择:
- **关键词权重最高(0.46)**。中文场景里,用户问「阿宁讨厌什么」,
真正命中的是「讨厌」两个字,语义向量经常被高频词带偏。
- **语义只用哈希向量兜底**。缺省不下载模型,`Embedder(model="bge-m3")`
可以换成真的 embedding(需要 `sentence-transformers`)。
- **新鲜度是指数衰减**,不是线性:`s = 0.5 ** (age_days / 半衰期)`。
- **图谱分** = 与种子记忆共享的实体数 / 3,加上邻居记忆的兜底分。
权重、半衰期、去重阈值全在 `config.py` 的 `Weights` / `Decay`,
改完跑一遍 `python -m membrain eval` 就知道有没有变差。
---
## 遗忘:三层与半衰期
| kind | 用来记什么 | 半衰期 |
|---|---|---|
| `fact` | 稳定事实(他是谁、讨厌什么) | 365 天 |
| `lesson` | 教训、原则 | 240 天 |
| `skill` | 具体做法 | 180 天 |
| `reflection` | 反思、情绪 | 60 天 |
| `episode` | 一次性事件 | 21 天 |
- 被想起 **5 次以上** 且强度够 → 进 `habit`(习惯层),基本不会掉
- 强度掉到 **0.18** 以下 → 进 `edge`(边缘层),还查得到但排得靠后
- 超过 `archive_after_days`(默认 400 天)→ 归档,不参与召回
`python -m membrain tick` 手动跑一次衰减(生产环境用 cron 每天一次)。
---
## 接进你的机器人
```python
from membrain.config import Config
from membrain.engine import Engine
eng = Engine(Config.load("config.toml"))
eng.remember("阿宁讨厌被叫笨蛋", kind="fact", importance=0.9,
who="阿宁", topic="边界")
items = eng.recall("阿宁讨厌什么", k=5) # 召回
brief = eng.brief() # 唤醒时注入的那一小段
```
或者直接起 HTTP 服务(标准库,无框架):
```bash
python -m membrain serve --port 8765
```
```
GET /health → {"ok": true, "counts": {...}}
GET /brief → {"ok": true, "text": "【关于阿宁的几条硬记忆】…"}
POST /recall {"query": "…", "k": 5, "explain": true}
POST /remember {"text": "…", "kind": "fact", "importance": 0.9}
POST /tick
```
**会话隔离**是硬规矩:每条记忆都带 `scope`(`group:123` / `private:456`),
`recall(scope=...)` 只返回同 scope + `global` 的。群里的记忆不要带去别的群。
---
## 装进 DSH / Claude Desktop(MCP)
上面那段是「自己写代码接」。如果你用的是支持 MCP 的客户端(DeepSeek Harness、Claude Desktop 等),
不用写代码 —— 把 `membrain.mcp` 当成一个 stdio MCP server 挂上去就行,工具会以
`mcp__membrain__remember` / `mcp__membrain__recall` / `mcp__membrain__brief` 出现。
它只用标准库实现,**不需要 `pip install mcp`**。
**DeepSeek Harness** —— 编辑 `~/.dsh/profiles/web/cordis.patch.yml`,在末尾加一段:
```yaml
- insert:
- id: mcp-membrain-lite
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: membrain
transport: stdio
command: 'C:\Python312\python.exe' # 换成你自己的:Windows 用 where python,macOS/Linux 用 which python3
args:
- '-m'
- 'membrain.mcp'
cwd: 'C:\path\to\membrain-lite' # 换成你解压/克隆的仓库路径
env:
PYTHONIOENCODING: 'utf-8'
MEMBRAIN_HOME: 'C:\path\to\membrain-lite\.membrain'
toolCallTimeoutMs: 60000
```
`serverName` 不能和已有的重名(比如你已经挂了 `memory`,这里就用 `membrain`)。
改完重启 DSH(或让它热重载),工具列表里就会出现 `membrain` 这一家。
**Claude Desktop** —— `claude_desktop_config.json`:
```json
{
"mcpServers": {
"membrain": {
"command": "C:/Python312/python.exe",
"args": ["-m", "membrain.mcp"],
"cwd": "C:/path/to/membrain-lite",
"env": { "MEMBRAIN_HOME": "C:/path/to/membrain-lite/.membrain" }
}
}
}
```
**自测**(不装客户端也能验证协议通不通):
```bash
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
| python -m membrain.mcp
```
正常会吐两行 JSON:先是 `serverInfo`,再是 5 个工具(remember / recall / brief / tick / stats)。
---
## 让别人家的 AI 自己装
如果你不想自己动手,可以把仓库链接丢给你的 AI(DSH / Claude / Codex 都行),让它读:
**[`docs/AGENT-GUIDE.md`](docs/AGENT-GUIDE.md) —— 写给 AI agent 的安装与维护指南(中文)**
**[`docs/AGENT-GUIDE.en.md`](docs/AGENT-GUIDE.en.md) —— Agent Install & Maintenance Guide (English)**
**[`docs/SETUP-CHECK.md`](docs/SETUP-CHECK.md) —— 设置阶段自检清单(命令版,专治 404 / 死引用)**
两份内容逐节对应,按你的 agent 习惯的语言挑一份就行;每份顶部都链着另一份。
那里面是机器视角的完整流程:
- **开始干活之前先跑 [`docs/SETUP-CHECK.md`](docs/SETUP-CHECK.md)**:一条 `python scripts/check_links.py`
把文档里的死链、缺文件和骨架缺失全打出来,有 404 当场修或替换,别带着坏引用往前做;
- 怎么装、怎么验活、`cordis.patch.yml` 里 `serverName` 不能跟已有的 `memory` 重名;
- 文件怎么摆(代码 / 数据 / 备份三分,只有 `brain.sqlite` 必须备份);
- 什么该 `remember`、什么别记、`kind` 和 `importance` 怎么给、`scope` 为什么是硬规矩;
- 怎么用 `eval` 驱动优化,而不是凭感觉调参;
- 一张真实踩过的坑表(BOM、`sqlite3.Row`、去重、跨会话泄漏……);
- **最后一段是「请转告你的主人」**:一段可以直接念给主人听的白话,加 5 条要提醒的事。
零依赖、不改你的说话习惯、数据只在自己机器上——这几句 AI 会替你解释清楚。
---
## 话术模板
`prompts/` 下是配套的基础话术,直接抄进你的 system prompt:
| 文件 | 内容 |
|---|---|
| `01_persona_core.md` | 她是谁(人格,不含规则) |
| `02_wakeup_brief.md` | 唤醒简报怎么用(只用在简报里有的东西) |
| `03_write_rules.md` | 什么该记什么不该记、五类 kind 怎么选 |
| `04_speak_style.md` | 说话长度、语气、具体 > 抽象 |
| `05_when_upset.md` | 被骂时的四步,以及三条「不要做」 |
这几份比代码更值钱。人格和记忆要解耦:**换人格不用动记忆,换记忆不用动人格。**
---
## 文件结构
```
membrain/
config.py 权重、半衰期、路径(tomllib 读 config.toml)
store.py SQLite:memories / vectors / entities / links / meta
embed.py 向量(默认 char-bigram 哈希,可选 sentence-transformers)
decay.py 遗忘曲线与层级
engine.py remember / recall / tick / brief
mirror.py 导出成人能读的 markdown(记忆速查、人物、话题、日记)
api.py 标准库 HTTP 服务
cli.py 命令行
mcp.py MCP server(stdio,零依赖,给 DSH / Claude Desktop 用)
evalrun.py 评估(recall@1 / @3)
prompts/ 五份话术模板
data/ seed_demo.jsonl 示例数据(29 条)
eval/ cases.jsonl 评估集
tests/ 单元测试
examples/radial/ 记忆放射图网页(零依赖)
docs/ 架构说明、隐私说明、**给 AI agent 的安装指南(中 / 英)**
scripts/ backup.py(一致性快照备份 + 轮转)、import_from_old_store.py(从旧库迁移)、
check_privacy.py(公开前查隐私)、check_links.py(设置阶段查死链/缺文件)
```
---
## 演示:记忆放射图
`examples/radial/` 里有一个零依赖的网页(纯 canvas,没有 CDN、没有构建):
- 内圈是**被反复想起的**(habit),中圈在慢慢淡(fading),外圈最早被忘掉(edge);
- 颜色是记忆类型:事件 / 事实 / 反思 / 技能 / 道理;
- 菱形孤岛是**实体**(人、群、话题),连线表示这条记忆提到了它;
- 拖拽旋转、滚轮缩放、悬停看详情、点图例按类型隐藏、搜索框高亮命中。

(静态版:[docs/screenshot-radial.png](docs/screenshot-radial.png))
跑起来,一行就够(Windows 之外):
```bash
python -m membrain demo && python -m membrain export --out examples/radial/memories.jsonl && python -m http.server 8766
```
Windows 直接双击 `一键体验.bat`。想看某个固定角度,可以加 URL 参数:
```
http://127.0.0.1:8766/examples/radial/#yaw=120&pitch=-18&auto=0&zoom=1.1
```
页面直接 `fetch('memories.jsonl')`;直接双击打开(`file://`)会被浏览器拦,
所以页面里内置了一份示例数据兜底 —— 打开就能看见效果。
---
## 设计取舍(为什么不做成别的样子)
- **为什么不用向量库?** 几万条记忆用 SQLite 足够,numpy 都不需要。
真正难的是打分和遗忘,不是存储。
- **为什么不默认下载 embedding 模型?** 骨架要能 `pip install` 完就跑。
- **为什么把「忘」做成三层而不是删?** 删了就真的没了;
分层的意义是「还查得到,但排在后面」——这才像人。
- **为什么话术单独放 md?** 代码里写死人格,改一次要发版。
---
## 路线图
- [x] 核心:写入 / 五路召回 / 衰减 / 镜像 / HTTP / CLI / 评估
- [x] 话术模板五件套
- [ ] 多人格(同一份记忆,多套 prompts)
- [ ] 记忆冲突检测(同一件事两种说法,标出来给人判断)
- [ ] 导出为 JSONL 增量备份
- [ ] 一个最小 Web 演示页(记忆放射图)
---
## 已知局限(说实话)
- **中文两字词靠字面命中**。缺省哈希向量对「熬夜 vs 半夜」这种同义改写几乎无能,
真正的语义召回要自己接 `sentence-transformers`(`pip install .[embed]`)。
- **评估集只有 8 条**,100% 只是「没写崩」,不代表好用。
真想调准,扩到 40 条以上,覆盖「该想起却想不起」的反例。
- **没做冲突检测**。同一件事两种说法会并存,谁排前面看分数。
- **抽取靠调用方**。`remember()` 只负责「存好」,把一段对话切成几条是上层的事。
---
## 隐私
仓库里的示例数据**全是编的**(小满 / 阿宁 / 阿吉 / 老周 / 阳台种菜群),
没有真实 QQ 号、真实群名或真实聊天记录,详见 [docs/PRIVACY.md](docs/PRIVACY.md)。
自己往里加数据以后,提交前跑一下:
```bash
python scripts/check_privacy.py
```
它会扫私人称呼、凭据形态、手机号/QQ 号,命中就返回非零。
## 许可
MIT。拿去用,改,卖都行,别来找我。
如果它对你有用,告诉我一声你拿它做了什么 —— 比 star 有意思。
TDQS
Scored across 5 tools
Each tool has a distinct role: remember writes, recall searches, brief returns a curated wake-up subset, tick runs maintenance, stats reports metrics. The only mild overlap is between recall and brief (both return memories), but the descriptions clearly separate query-driven retrieval from a fixed high-importance digest.
All five names are short, lowercase, single-word tokens with no mixed casing or prefix conventions. There is a slight verb/noun mix (remember/recall/tick vs. brief/stats), but the style is uniform and readable.
Five tools cleanly cover the memory lifecycle: write, retrieve, digest, maintain, and measure. Nothing feels redundant and no operation is split into artificial variants.
The core write/read/maintenance loop is covered, but there is no way to update or delete a specific memory — errors can only be corrected by writing a new memory or waiting for tick decay. This append-only design is plausible but leaves notable gaps for agents needing correction or targeted removal.