@jeeinn/omem
v0.3.1
Published
A git-native personal AI memory layer: Markdown source of truth + MCP + topic-as-identity
Maintainers
Readme
omem
你的记忆是一个 git 仓库,不是一个数据库文件。
omem 是本地的个人 AI 记忆层:一条记忆一个 Markdown 文件,没有索引数据库(内存视图每次启动从 Markdown 重建)。MCP 让多个客户端共用同一份记忆。
- 默认零配置:
omem sync把对话原文放进sessions/,结论由 Agent 写回knowledge/ - 采集只读:Claude Code、WorkBuddy、Cursor、千问办公(QwenWork)、豆包办公(Doubao)、CodeBuddy 等 9 个平台的会话目录,不改对方的文件
- Markdown 是事实源,Obsidian 里改完,下次访问会吸回索引
omem backup用 git 做备份和跨机器同步;冲突只报告,不自动合并- 不自动删除。硬删只有
omem redact - MCP 默认只暴露
search_memory/save_memory
分层、数据流和目录见 技术架构。文档索引(Current vs Archive)见 docs/README.md。
状态
V0.3 正式版 0.3.1(npm install -g @jeeinn/omem)。omem sync 只采集;可选的 API 加工走 omem process。
从 v0.1 升上来时,用 omem init --force 重建数据仓。--force 会先自动打一份快照,再重建 git 仓。npm 上此前的 latest 是 0.1.0-beta.8;0.2.0-beta.1 只打过 git tag。
v0.3 在该 tag 之上落地的行为(S0–S5):显式归档复活、exclude_paths 接线、redact 护栏、写入差异预览、检索结构化证据回传、expected_revision best-effort CAS、去重命中只升不降。S6(证据分层 / 评分模型重构)推迟到 v0.4,评分阶梯保持原样。详情见 docs/omem-v0.3-implementation-plan.md。
这一版的产品形状(自 v0.2 起):
- topic 就是身份:路径是
knowledge/<domain>/<topic>.md,frontmatter 不再写 topic,也不再有 id、年份分桶、superseded状态机 - 没有索引数据库:SQLite 删掉了,索引是每次启动从 Markdown 重建的内存视图;会话只索引
## user证据域,检索是线性扫描 + 确定性排序 - 信任只有一个数字:
confidence。verified字段删了——原地更新下它会退化成「这个主题曾被人看过」;MCP 侧也没有盖章通道,过目只由人自己在omem review里做 - 命令面 14 条:
gc并入review --gc,rebuild改名verify,snapshot并入backup,删掉eval/api/audit/context/ 主题级审阅策略。0.3.1把 API 加工拆成omem process,sync不再调模型 - 印证按独立来源算:同一段用户输入被同步两次、或两个采集器收了同一次对话,只算一份——复制粘贴刷不了分
- 写前快照:覆盖一份已有活文档之前先打快照(
snapshots/,滚动保留 7 份),git 历史是最终退路
包名带 scope 是因为 npm 认为裸名 omem 与已有包太像。安装后命令名仍是 omem。CLI 输出是英文,本文和 docs/ 是中文。
环境
- Node.js ≥ 22.5(无数据库、无原生编译;检索索引是从 Markdown 派生的内存视图)
- git(仅
omem backup需要;没有 git 时给出安装提示,不抛栈)
唯一需要原生二进制的依赖是 @toondepauw/node-zstd——DeepSeek Harness 的会话是 zstd 压缩的,采集时要解压。它提供预编译包(Windows / macOS / Linux,x64 与 arm64),不需要本地编译工具链。
安装
npm install -g @jeeinn/omem
omem init数据在 ~/.omem,可用 OMEM_HOME 换位置。
多机同步
第一台机器:
omem init
omem backup remote [email protected]:you/omem-backup.git
omem backup # pull(空远端,跳过)→ push(发布分支)后续机器(用 --clone,不要直接 init,否则两边历史不相干会 pull 失败):
omem init --clone [email protected]:you/omem-backup.git
omem sync # 采集本机会话如果某台机器已经有了不相干的本地历史,omem backup pull 会自动用 --allow-unrelated-histories 合并;也可以手动重置:
omem init --force --clone [email protected]:you/omem-backup.git # 丢弃本地 git,重新从远端克隆Windows 上若 MCP 起不来,把客户端配置改成用 node 直接启动:"command" 填 node.exe 绝对路径,"args" 为 ["<全局 node_modules>/omem/bin/omem.js", "serve"]。
快速开始
omem install
omem sync --since 30d
omem review --ui # 浏览器里勾选,一次提交
omem review --confirm <topic> # 也可以命令行过目单条(支持 topic1,topic2 批量)
omem add "部署时 docker 镜像构建失败,改用国内源后恢复" --topic docker-build --type experience
omem search "镜像拉取"
omem backup remote [email protected]:you/omem-memory.git
omem backupomem install 默认只预选路径已核实的客户端。非交互环境要写 omem install --target claude-code,cursor。不在列表里的客户端用 omem install --print-config generic。千问办公(QwenWork)、豆包办公(Doubao)、DeepSeek Harness 与 Qoder IDE 的 MCP 配置在应用内部管理,没有可写文件:omem install --target qwenwork,doubao,deepseek,qoder 会打印应用内添加指引(设置 → 连接器 / MCP,或在会话里让 agent 添加 omem serve)。
CodeBuddy 的 MCP 配置在 ~/.codebuddy/mcp.json(中文版 ~/.codebuddycn/mcp.json),是普通 JSON 文件,可以直接写:omem install --target codebuddy。
接入后,会话里可以直接用 search_memory / save_memory。memory_feedback 要用 OMEM_MCP_TOOLS=feedback 才打开。
MCP 侧三个容易踩空的点,现在都在协议里给出了:
initialize会返回instructions(含PROCESS.md绝对路径与写回循环),所以save_memory描述里「先读 instructions 拿路径」这句真的能兑现,不再只依赖额外的导出命令。search_memory支持topic与status:只给topic就返回该主题的活文档(topic 本身就是句柄)。历史版本在 git 里,不在检索结果里。搜会话只认## user段——助手的回答不是证据,也不该被检索到。top_k声明并夹取在 1-20(省略时用config.toml的search.top_k,它可配到 50);响应固定给matched M · showing N,能看出还有多少没返回。
写入结论的路径(save_memory / omem add)默认各留一笔本地提交([process] auto_commit = "session",可设 off 关闭);omem sync 一轮 run 一笔。只有 omem backup 才推送远端——它在拉取前会把未提交改动收成一次提交,冲突时保留本地版,远端版写成 .conflict.md。
采集器支持列表
| ID | 采集源 | 说明 |
| --- | --- | --- |
| claude-code | Claude Code | 会话 JSONL,~/.claude/projects/ |
| workbuddy | WorkBuddy | 会话目录 |
| cursor | Cursor | 对话记录 |
| qwenwork | 千问办公(QwenWork) | 会话目录 |
| doubao | 豆包办公(Doubao) | 会话目录,%LOCALAPPDATA% |
| deepseek | DeepSeek Harness | 会话目录(zstd 压缩,读时解压) |
| qoder | Qoder IDE | 国际版 + 中文版双根 |
| catpaw | CatPaw | 会话纯文本,~/.catpaw/projects/ |
| codebuddy | CodeBuddy | 会话 JSONL,国际版 + 中文版双根(~/.codebuddy / ~/.codebuddycn) |
每新增一个采集器,在此表追加一行。omem sync --collectors id,id 可指定子集;不指定时扫全部。
每个采集器的会话根目录都可以用环境变量覆盖(例如 OMEM_CATPAW_PROJECTS、OMEM_QODER_PROJECTS)。这主要用于测试隔离——把变量指向临时目录,就不必改动真实会话目录。完整变量名清单见 src/collect/registry.ts。
命令
| 命令 | 作用 |
| --- | --- |
| omem init [--force] | 只建库(数据目录、配置、git 数据仓)。已初始化时提示用 --force 重置(删除 .git 重新 git init;重置前自动打一份快照)。采集用 omem sync |
| omem install | 写入客户端 MCP 配置。--target 跳过交互,--print-config 只预览 |
| omem sync [--since 7d] [--collectors id,id] | 采集会话到 sessions/,并更新 PROCESS.md。不调用模型。不带 --collectors 时扫全部已有目录 |
| omem process [--limit N] [--dry-run] | 可选:用 API 模型 drain 待加工队列,写入活文档。不扫采集器。详见下方 API 配置 |
| omem skip <session-id> | 把会话标记为「不必沉淀」,从 PROCESS.md 的待办里移出(list / remove 可回看) |
| omem add <content> --topic <name> | 手工写入一份活文档,自动脱敏。同 topic 再写一次就是覆盖(git 保留历史) |
| omem search <query> | 查结论。--sessions 查原文,--detail 打出正文 |
| omem review [<topic>] | 待审默认最近 10 条。带 topic 打开正文;--confirm <topic[,topic]> / --dismiss / --stale 过目、归档或冷却;--gc [--days N] 扫描长期未过目的文档 |
| omem review --ui | 起一个本地页面,勾选后一次提交。--no-open 只打印链接,--port N 指定端口,--timeout SEC 改超时(默认 600) |
| omem topics [list] | 列活文档主题(主题名 / 项目 / 最近更新 / 置信度 / 标题) |
| omem topics rename <old> <new> | 给主题改名(= 移动文件,历史由 git 保留) |
| omem backup | git pull 然后 push。子命令:status / pull / push / remote / use |
| omem profile [home\|work] | work 档默认禁止备份和导出 |
| omem redact --topic <name> | 唯一硬删。--source 默认只预览,--yes 才删 |
| omem verify | 从 Markdown 重建内存视图、重渲染 MEMORY-INDEX.md,并用账本重算每条自动分的条目与落盘值比对(只报告差异,不改写) |
| omem serve | MCP server,由 install 写好的配置拉起 |
退出码:0 成功(冲突已处理也算成功),1 用法错误,2 后端不可用或策略拒绝。
结论从哪来
默认不调用模型。omem sync 只把原文放进 sessions/,并在 PROCESS.md 里列出还没写进活文档的会话。Agent 读这份文件,用 save_memory 或 omem add --topic 写回。写同一个 topic 就是覆盖同一份文件,不需要额外的 id。
覆盖可用版本基线做 best-effort 保护:search_memory 的命中带 revision(整份 Markdown 全文 sha256 前 32 位,与 index.hash 同口径),把它作为 save_memory(expected_revision=...) 传回,写入前会重新读盘比对;不一致返回 stale-revision,不写盘、不提交、不进 warnings。CLI 侧用 omem search --detail 读 revision、omem add --expected-revision 传回。读-比-写之间仍有 TOCTOU 窗口:这不是跨进程锁,不保证并发写入绝不丢失更新;v0.3 不做文件锁(决策 D3)。
置信度按证据打:对得上 user 原文从 0.55 起,独立来源印证到 0.8,对不上封顶 0.35。omem review --confirm 把人工过目的文档提到 0.9——这套分数不写 verified 字段(v0.2.0 起取消),MCP 侧也没有盖章通道:过目只由人自己在 omem review 里做。
这五档是证据支持程度与排序分数,不是事实正确的概率。人工确认表示「这个主题曾获得过人工关注」,不表示当前正文已被逐项验证:confirmations / last_confirmed 是主题级记录,正文被后续写入覆盖时仍会继承,不会自动失效(v0.3 决策 D2)。
证据门槛是 12 个连续字符,且必须来自会话文件里的 ## user 段(引模型自己的话不算)。session_id 用裸 id——claude-code:probe-0001 这种带前缀的写法会自动剥掉前缀再找,不会因此掉到 0.35。没给 session_id 只给 evidence 时,第一个命中的会话可能是证据的来源本身,因此不计入「独立印证」:指名与不指名同分,指名不会更亏。印证按独立来源算,不按会话文件算:同一段用户输入被同步两次、或两个采集器收了同一次对话,只算一份——复制粘贴刷不了分。
可选的 API 加工入口是 omem process(与 omem sync 拆开:sync 只采集,process 只 drain 队列,互不依赖)。旧写法 omem sync --tier api 仍可用一期,但会打印弃用警告并只跑 process、不再采集——请改用 omem process。
API 模型配置建议
| 项 | 建议 |
| --- | --- |
| 入口 | omem process;先 omem sync 落盘会话,再按需加工 |
| base | OpenAI 兼容的 …/v1;云端必须 https;本机回环(127.0.0.1 / localhost)免 key |
| key | 只放 OMEM_API_KEY(或安装方注入的环境变量)。不要写进 config.toml:omem backup 会把配置推进数据仓 git |
| model | 显式指定;未指定时会探测 /v1/models |
| limit | 默认 20;非本机配置值最高 50,只有命令行 --limit 能再高 |
| timeout_ms | 未设置时本机默认 300000、非本机 60000。非本机(SenseNova 等)建议显式设为 120000–300000——小会话也可能卡满 60s,日志里是 transport_failed / timeout,不是解析失败 |
| max_input_chars | 未设置时本机 6000 / 非本机 12000;超长正文会被截断后再送 |
| --dry-run | 打印端点、队列深度、粗估 token;零网络、不改 api_state |
| 出站 | 非本机时终端会警告:会话正文会离开本机;通用密钥形态会 redact,业务内容不会 |
# 先看队列与端点(不发请求)
omem process --dry-run
# 本机 Ollama / llama-server(回环免 key)
omem process --base http://127.0.0.1:11434/v1 --model minicpm5-2b --limit 5
# 云端 OpenAI 兼容(key 走环境变量;PowerShell 示例)
$env:OMEM_API_KEY = 'sk-xxx'
omem process --base https://api.deepseek.com/v1 --model deepseek-chat --limit 3
# SenseNova 等:务必拉高 timeout_ms(config.toml 的 [api] 段,或 OMEM_API_TIMEOUT_MS)
# timeout_ms = 180000
omem process --base https://api.sensenova.cn/compatible-mode/v1 --model <your-model> --limit 3config.toml 的 [api] 段可写 base / model / limit / timeout_ms / max_input_chars;key 仍优先环境变量。方案与迁移说明见 docs/omem-process-command-plan.md。
主题(topic)
topic 是活文档的身份,也就是文件名:路径是 knowledge/<domain>/<topic>.md,frontmatter 里不写它——主题名只有一个事实源,不会出现「字段和路径对不上」。coding / general 是 domain,不是主题。
主题名必填,一次性记忆也一样(它只是「只被写过一次的 topic」)。
omem add "连接池上限 20,超过就排队" --topic pool-tuning
omem topics # 有哪些主题
omem topics rename old new # 改名 = 移动文件,历史由 git 保留同一主题同时只有一份 active 文档。要更新它,带同一个 topic 再写一次:
omem add "连接池上限 20,超过排队(补充:不要盲调大)" --topic pool-tuning更新是整篇覆盖:同一个 topic 写第二次就是重写那份文件,旧版本留在 git 历史里。覆盖前自动打一次快照。
继承规则:created、source_sessions 与 evidence 都累积(证据最多留 8 条 / 2000 字符,会话 id 最多 200 个),人工过目记录(confirmations / last_confirmed)保留,但置信度永远按这次写入重算。
正文完全相同的再次写入仍会被去重挡回,但分数会被补:如果这次带的 evidence 现在锚得上(典型情形是会话在写入之后才被 omem sync 收进来),frontmatter 里的 confidence 会刷成新算出的值,证据链一并合并,正文一字不动(MCP 回执里的 rescored 会说这件事)。只升不降;人工过目过的 0.9 不碰。补采之后想让一条 0.35 的记忆自愈,就把它连同现在能锚上的引文再发一次——不用改写正文。
主题名规范:建议英文小写连字符(knee-rehab、pool-tuning),专有名词保留原文。中文等 CJK 主题名合法,但同一件事换个说法就会变成第二个主题——名字是唯一身份,没有第二种归并机制。
人工过目、归档与冷却
队列里的条目只有三个动作:
omem review --confirm pool-tuning # 人工过目:置信度提到 0.9
omem review --dismiss noisy-topic # 归档:移出队列,可逆
omem review --stale pool-tuning # 冷却:排在 active 之后过目的产出物只有两个数字:confidence = 0.9 与 confirmations 递增。没有 verified 布尔位——它会随原地更新退化成「这个主题曾被人看过」,而信任只该描述当前这份正文。
归档不会被静默撤销:向 archived / stale 的 topic 写入时,omem 返回 needs-reactivation,说明这次写入会把文档恢复为 active(归档文件还会从 archive/ 移回 knowledge/),并且不落盘、不提交。只有显式意图才能恢复——CLI 用 omem add --reactivate,MCP 用 save_memory(reactivate: true)。omem review --confirm 只提置信度,不改变 status,过目不能把冷却的文档变回 active(v0.3 决策 D4)。
两种「看不见」的补救:
| 情况 | 现在会怎样 |
| --- | --- |
| 不知道库里有哪些主题 | omem search 的命中行带 topic:xxx;PROCESS.md 的 ## Topics 段每行给出主题名 + 所属项目;MCP 用 search_memory(topic="<name>") 直接取 |
| 新主题和老主题只差一两个字 | 写入时告警「is close to existing "xxx"」,并说明「确属不同主题就保留新主题」,照常写入 |
图形化审阅(review --ui)
积压几十条时逐条敲命令不现实。omem review --ui 起一个只绑 127.0.0.1 的页面:左边是队列,右边是正文、evidence 和来源会话标题,勾选后点一次 Apply 全部落盘。
omem review --ui # 提示后按 Enter 打开浏览器
omem review --ui --no-open # 只打印链接,自己开
omem review --ui --topic fm-cmp --timeout 300分级落章:批量勾选的天花板不是「确认」,而是「同意」。
| 你的操作 | 结果 |
| --- | --- |
| 勾选,但没展开这条的正文 | assent:confidence 保底 0.8 |
| 勾选,且展开过正文 | confirm:confidence 0.9(人工过目) |
「展开过」由服务端记录(GET /api/detail/<topic>),前端改不了。这条门槛的目的很直接:置信度决定 MEMORY-INDEX.md 的排序,应该为它付出阅读成本。批量全选只能拿到 0.8。
其它细节:
- 终端里的
Enter只是「授权打开浏览器」,提交的唯一入口是页面按钮 Ctrl+C任何时刻都是关闭服务、零写入、退出码 0;超时(默认 10 分钟)同样零写入- 提交前自动打一次
snapshot,保留 7 份 - 页面打开期间文档可能被 Agent 更新:提交时逐条重校验 status,已归档的跳过而不是整批失败
定时采集
没有常驻进程。新会话要靠再跑一次 omem sync。内容没变的会话会跳过。
*/30 * * * * /path/to/omem/scripts/collect.sh >> /tmp/omem-collect.log 2>&1schtasks /Create /TN "omem-collect" /SC MINUTE /MO 30 /F ^
/TR "powershell -NoProfile -ExecutionPolicy Bypass -File \"C:\path\to\omem\scripts\collect.ps1\""scripts/collect.sh 和 collect.ps1 只是 omem sync 的封装,不进 npm 包。定时任务会重写 PROCESS.md;Agent 正在读的那一版可能过期,间隔别短于 30 分钟。采集不写结论。
PROCESS.md 也会在每次写入结论后(save_memory / omem add)立即重算,所以 Agent 读到的一定是最新待办,不会重复处理已吸收的会话。这两类写入会各自留一笔本地 git 提交(auto_commit = "session",默认开;数据仓不是 git 仓库时静默跳过并在回执里说明),omem backup 负责推送远端。
开发
npm install
npm run build
npm test
npm run smoke行为以源码为准;决策与进度见 docs/README.md(现行:omem-v0.3-implementation-plan.md、omem-重构方案-路B.md;历史评审与一次性格笔记在 docs/archive/,仅作背景)。
安全
- 写入前按通用形态脱敏:API key、token、JWT、PEM、口令。身份证、手机号、薪资这类内容不会自动过滤
[collect] exclude_paths跳过会话文件或整个会话目录:精确路径或目录前缀,~会展开,分隔符与大小写按平台归一化;不做子串/glob 匹配,避免误伤同名兄弟目录。omem sync的 run 汇总会报告被挡下的文件数。exclude_keywords已删除,不再按内容过滤- work 档默认禁止
omem backup与上下文导出,退出码 2。OMEM_ALLOW_WORK_EXPORT=1临时放行,OMEM_PROFILE_LOCKED=1锁定档案 - 硬删只有
omem redact:--topic/--source都默认只预览,要显式--yes才删。删除范围仅限本地 Markdown 与索引行——数据仓 git 历史里的旧版本、snapshots/、已推送的远端备份和其他机器上的副本都不会被删除;omem 不改写 git 历史,也不按 topic 排除备份
配置文件
config.toml 是唯一配置入口,手写内容会被保留。
- 已失效的键(比如
exclude_keywords、default_tier = "rules"):omem init会点名提示,让你手动删掉那行。它不自动改——自动改会丢掉你手写的未知键。 - 新版本新增的键(比如新采集器的
*_projects):omem init会把它们补写进去并给出行名,让你看得见、改得动。补写是文本级插入,不会重排你的其他内容。
两个方向都是同一件事:不让「你以为的配置」和「实际生效的配置」长期不一致。
