Mnemosyne OS

自托管 AI 记忆:数据主权、部署形态与合规实践

把用户记忆放在别人的服务器上,是一个需要论证的决定,不是一个默认选项。本文讲清自托管真正解决的问题、三种部署形态怎么选、备份与恢复怎么做才不会在关键时刻失效,以及合规审查时对方会追问的那几个点。所有容量与延迟数字为实测。

先明确边界:自托管不等于自动安全。它把「数据在哪里」这个问题的答案交给你,同时也把「谁负责保护它」的责任交给你。本文按这个前提写,不做「自托管一定更安全」的笼统断言。

一、自托管真正解决的问题

常见说法是「自托管更安全」。这句话太粗,掩盖了真正有价值的三个具体点。

1. 删除的彻底性可以被验证

用户说「把我的记忆删掉」,托管服务会告诉你「已删除」。但你能验证吗?对方的备份里可能还有、灾备副本里可能还有、日志里可能还有。你只能相信,不能检查。

自托管下,整个记忆库就是你磁盘上的一个目录。删掉它、确认目录不存在,删除就完成了——没有副本要追。这是可验证的,不是承诺。

2. 数据不出你的网络边界

对受监管行业(医疗、金融、政务),数据出境或离开受控网络本身就是合规事件。自托管让「数据在哪里」变成一个物理问题而不是合同问题。

3. 成本模型可预测

托管按订阅或按调用计费,用量增长时成本线性上升。自托管的边际成本接近零(已有硬件),只在你扩容时才产生支出。对高频写入场景,这个差异会随时间放大。

二、三种部署形态

形态适合数据位置多用户运维成本
单机进程个人使用、本地开发本机磁盘不需要几乎为零
容器 + 持久卷团队共享、内网服务云盘或 NAS支持中(需管卷与备份)
Serverless低频、突发流量取决于挂载支持低,但冷启动有影响

形态 A:单机进程

最简单的形态。引擎作为库嵌入你的程序,或作为本地进程运行。

pip install mnemosyne-os

# 作为库使用
python -c "
from mnemosyne import Memory
from mnemosyne.api.config import MemoryConfig
m = Memory(MemoryConfig(brain_dir='./mem', namespace='default'))
m.add('Alice 偏好深色模式', user_id='alice')
print(m.search('alice', filters={'user_id':'alice'}, top_k=3))
"

记忆库就是 ./mem 这个目录。备份 = 复制目录。

形态 B:容器 + 持久卷

# 关键点:记忆库必须挂在持久卷上
docker run -d   -p 8788:8788   -v /srv/mnemosyne-data:/data   -e MNEMOSYNE_MCP_TOKEN="a-long-random-secret"   -e MNEMOSYNE_DIR=/data   your-image

最容易犯的错:记忆库落在容器根文件系统。表现是「每次重新部署,所有记忆消失」。这不是 bug,是卷没挂。部署前务必确认挂载点,并用 df -h /data 之类的方式验证它确实是独立卷。

形态 C:Serverless

适合调用稀疏的场景。注意两点:冷启动会增加首次调用延迟;本地文件系统通常是临时的,必须挂载持久存储,否则记忆不持久。

三、备份与恢复:别在关键时刻才发现失效

记忆库是单文件存储,备份很简单,但「简单」不等于「一定有效」。以下三条来自实际踩坑。

1. 备份要能恢复,不只是能复制

# 备份(SQLite 需要一致性快照,不能直接 cp 正在写的库)
sqlite3 /data/memory.db ".backup '/backup/memory-$(date +%F).db'"

# 或者停进程后整目录打包(更简单,代价是短暂停机)
tar czf /backup/mem-$(date +%F).tar.gz -C /data .

# 恢复
tar xzf /backup/mem-2026-10-27.tar.gz -C /data

必须定期演练恢复。一个从没恢复过的备份,等于没有备份。建议每月在测试环境恢复一次并跑完整性校验。

2. 完整性校验是备份的验收标准

# 校验哈希链,检测篡改并定位到具体记录
python -c "
from mnemosyne import Memory
from mnemosyne.api.config import MemoryConfig
m = Memory(MemoryConfig(brain_dir='/data', namespace='default'))
print(m.verify_integrity())
"
# 实测输出(120 条):{'default': {'valid': True, 'total': 120, 'first_broken_at': None}}

实测 120 条记忆的全链校验耗时 4.5 ms,完全可以放进每日定时任务。

3. 备份要加密,且密钥不要和数据放一起

记忆库里是用户的个人事实——偏好、住址、健康状况、工作内容。备份文件如果明文躺在对象存储里,等于把记忆库公开了。加密后再上传,密钥单独管理。

四、多用户隔离:最容易出事的环节

自托管常被用于团队或产品后端,这时隔离就是核心风险点。

做法隔离强度失败模式
同一张表 + WHERE user_id = ?弱任一处漏写条件 = 跨用户泄露
每个用户一个命名空间(独立文件)强查错库需显式传错参数,由文件系统兜底
每个用户一个数据库实例最强运维成本高,备份碎片化

为什么推荐物理隔离:逻辑隔离的安全性依赖「每一处查询都记得加条件」,这是靠人保证的。物理隔离的安全性依赖「参数指向了哪个文件」,这是靠代码路径保证的。后者出错概率低一个量级。

必须做的一条:命名空间由服务端决定

如果 API 接受客户端传 namespace 并直接采信,那么任何持有有效 token 的人都能读别人的库。正确做法是:服务端从 token 反查归属,强制覆盖客户端传来的值。

# 错误做法
ns = request.json.get("namespace")   # 客户端说了算 → 越权

# 正确做法
ns = key_namespace(token_owner)      # 服务端从 token 推导
payload["namespace"] = ns            # 强制覆盖

五、日志:最隐蔽的泄露面

记忆系统的日志是最容易被忽视的隐私缺口。因为记忆内容会经过日志——请求体、错误堆栈、调试输出都可能带正文。

该记不该记
请求方法、路径、状态码请求体(含记忆正文)
工具名(retain / recall)工具参数里的 content
命名空间标识用户邮箱、手机号
耗时、错误类型完整异常堆栈里的业务数据

本项目网关的日志实现刻意只打 method、工具名、作用域,不打正文——原因写在源码注释里:记忆内容属用户隐私,日志会进可观测平台,不该出现用户数据。

自托管时特别注意:如果你用的是第三方日志服务或 APM,它们可能默认采集请求体。部署前检查采集配置,必要时在采集侧加字段过滤,而不是只靠应用层不打日志。

七、安全加固清单:自托管必须自己做的事

托管服务替你做了这些。自托管下,漏掉任何一条都是真实的暴露面。

项做什么漏掉的后果
磁盘加密整盘或目录级加密(LUKS / BitLocker / 云盘加密)磁盘被拆走或快照泄露 = 记忆库明文外泄
访问控制只对内网开放;管理端点不暴露公网端口被扫到即被尝试
TLS对外一律 HTTPS,禁止明文token 与记忆正文在链路上可被截获
令牌强度长随机串,不用 change-me 这类默认值可被字典攻击
令牌轮换定期更换,离职/泄露时立即换旧令牌永久有效
日志脱敏不记请求体;第三方日志服务加字段过滤记忆明文落盘到日志系统
备份加密加密后上传,密钥单独管理备份成为最薄弱的泄露点
依赖更新定期升级引擎与系统包已知漏洞长期存在
完整性监控定期 verify_integrity()篡改无法被发现

一个具体的配置示例

# 只监听本机,前面放反向代理做 TLS 与鉴权
python -m mnemosyne.webui.mcp_server   --transport http --host 127.0.0.1 --port 8788   --brain-dir /data --namespace default

# nginx 侧:强制 HTTPS + 限制来源
# server {
#   listen 443 ssl http2;
#   server_name mem.internal.example.com;
#   location /mcp {
#     allow 10.0.0.0/8;   # 只允许内网
#     deny all;
#     proxy_pass http://127.0.0.1:8788;
#   }
# }

八、故障排查:自托管特有的坑

现象常见原因排查命令
重启后记忆全丢记忆库在容器根文件系统docker inspect 看 Mounts;df -h /data
写入极慢记忆库在网络卷 / NFS / overlay 层SQLite 对网络文件系统不友好,换本地卷
数据库被锁多进程同时写同一库SQLite 单写者模型;改用单进程或队列
磁盘满未清理旧备份 / 记忆增长超预期du -sh /data /backup
校验失败文件被截断或人为修改verify_integrity() 返回 first_broken_at 定位
跨用户看到别人数据作用域未强制、或客户端可传 namespace检查服务端是否强制覆盖 namespace

九、容量与性能实测

本机实测(Windows / Python 3.13.12 / 8.1.0.post1 / 默认零配置):

指标实测值
引擎初始化0.24 ms
写入一条(中位)11.91 ms
召回一次(中位)3.924 ms
召回一次(P95)5.955 ms
完整性校验(120 条)4.5 ms
120 条记忆占盘5.5 MB

容量估算:按实测比例,万条记忆约占数百 MB 量级(含向量与索引)。这个量级下单机 SQLite 完全够用。真正的瓶颈在记忆量到十万级时的向量检索延迟——那时需要引入 numpy 加速,或外接专用向量库。

十、合规审查会问什么

问题自托管的回答能否举证
数据存放在哪里?你自己的服务器 / 内网,可指到具体机房与磁盘能(物理位置可查)
是否出境?不出境(除非你主动同步)能(网络流量可审计)
谁能访问?由你的访问控制决定能(访问日志)
删除如何执行?删文件,可当场验证目录不存在能(当场验证)
是否有第三方处理者?核心零第三方依赖,不向外部发送数据能(依赖清单 + 网络监控)
如何证明未被篡改?哈希链校验,可定位到被改的具体记录能(随时自跑)

最后两条是自托管相对托管服务的实质优势:托管服务也可以做哈希链,但校验发生在对方服务器上,你拿到的仍是「对方告诉你的结果」。自托管下你可以自己在任意时间点跑校验,并且能当场演示删除。

十一、常见问题

自托管会不会很麻烦?

单机形态几乎零成本——装一个包、跑一行代码。麻烦的是团队形态:卷管理、备份、监控、升级。如果你的使用场景是个人或小团队,麻烦程度远低于想象。

没有 GPU 能跑吗?

能。默认配置用内置 128 维确定性嵌入,不需要 GPU,也不需要下载模型。要更高召回质量时才需要配置嵌入模型,那时可以选远端 API(不占本地算力)或本地模型(需内存/显存)。

能不能部分自托管?

可以。常见组合是:记忆存储自托管(数据在自己手里),嵌入模型用远端 API(省算力)。这时要评估的是「送出去的只有待嵌入的文本片段」,与「整个记忆库在别人服务器」是两个不同量级的暴露面。

怎么从托管迁回自托管?

导出为 JSONL(Memory Exchange Protocol)后导入本地库。建议先核对条数,再切换流量。

九、下一步

看接入指南 →看信任中心