Files
2026-09-25 13:38:17 -05:00

21 KiB
Raw Permalink Blame History

name, description
name description
bmem 用 bmem 管理跨机器长期记忆、导入 Notes,并处理 bmem sync 或 Gitea 同步故障;当用户要求记住、查历史、同步记忆或排障时使用。

bmem:Claude Code 与 Codex 共享的跨机器长期记忆

bmem 把记忆分四层:三层是 Markdown(用 gitea 在多机之间同步),第四层 log 是中心 PostgreSQL + 本机 SQLite 镜像。Markdown 是三层记忆的唯一事实源,SQLite 镜像是可 随时删掉重建的派生物。

一句话分工:Markdown 记知识(我们知道什么、为什么这么定),log 记经历(什么时候、发生了什么)。

层 回答什么 存在哪 判据
全局 global 我通常怎么工作、跨项目的通用规则 ~/bmem.md 换个项目还成立吗?成立 → global
项目 project 这个项目是什么、为什么这样设计 <项目根>/bmem_<prjID>.md 项目结束后还有意义吗?没有 → project
机器 machine 这台机器有什么、能做什么、账号配在哪 ~/bmem_<machineID>.md 换台机器就不成立了吗?是 → machine
log 这个项目发生过什么、Notes 里写了什么 中心 PG + ~/.bmem/bmem_local.sqlite 这是"发生了一件事"还是"我们知道一件事"?发生 → log

~/bmem.md 里还有两张注册表:哪些机器加入了系统、每个项目在每台机器上的绝对路径。 项目身份是 prjID,不是路径——同一个项目在不同机器上路径不同,引用项目内文件要用相对路径。

先确认它能用

bmem help

bmem 不在 PATH 上时,用 <langgraph conda env>/Scripts/bmem.exe(路径见 ~/skills_env.md)。 本机还没加入系统会提示 run bmem install first;当前目录不属于任何项目会提示先 bmem init。

开工之前:先把记忆取回来

bmem 没有 context 命令——记忆就是几个 Markdown 文件,直接读:

  1. ~/bmem.md 的「全局规则」节:适用于所有项目的硬约束,先读这个。
  2. 当前项目根目录的 bmem_<prjID>.md:项目简介、规则/约束、设计决策、当前状态与未解决问题。
  3. 需要了解本机环境(工具装在哪、账号怎么配)时再读 ~/bmem_<machineID>.md。

要查"以前做过没有、上次怎么解决的",用本 skill 自带的检索脚本查 log 层:

python <skill>/scripts/bmem_recall.py "时延 扩展"          # 语义条目 + 文档章节 + 事件
python <skill>/scripts/bmem_recall.py "CP 长度" --project 52-link
python <skill>/scripts/bmem_recall.py --kind decision       # 这个项目做过哪些决策
python <skill>/scripts/bmem_recall.py --recent 10           # 最近发生了什么

它只读本机镜像,不连中心库,离线可用。中文按 bigram 索引,两到四个字的词组比整句命中率高。

为什么值得在动手前花这一分钟:你即将做的决定可能三个月前已经做过,理由写在某条 decision 里;你即将踩的坑可能上次已经踩过并记了下来。事后返工的成本远大于开头查一次。

命中只是线索,不是依据——按结果里的 项目:Notes/xxx.md § N 回读原文再下结论。

./Notes 是这个记忆系统的一等公民

用户的工作习惯是:PRxx.md(需求)→ Taskxx.md(方案)→ Resultxx.md(结果), 都放在项目的 ./Notes 下。bmem 按文件名把它们认成一组任务,全文入库、按标题拆成章节。

bmem notes                     # 扫描 ./Notes,把变化同步进记忆
bmem notes --check             # 只比对差异,不写入任何内容
bmem notes --sync              # 导入后立即同步到中心库

每次扫描会报出四种状态:新增 / 变更 / 一致 / 消失。判定用的是内容的 canonical sha256(去 BOM、CRLF→LF、NFC),所以改了空行或换行符不会被误判成变更,反复跑是安全的—— 内容没变就什么都不写。

两件值得知道的事:

  • 文档从 ./Notes 里删掉后会被标记为"消失":它的历史修订全部保留(那是历史), 但不再计入任务闭环——否则闭环会显示成"由一个已不存在的文件闭合",那是假信息。 文件放回来会自动恢复。
  • 项目记忆 bmem_<prjID>.md 里有一个自动维护的「Notes 索引」小节:任务闭环表 (含「最近更新」= 日期 + 内容指纹)+ 其它文档清单。它完全由文件名、标题和内容 sha 确定性推导(不经 LLM),内容变了它就变、没变它就一动不动(连日期都不写, 否则会天天重写文件、每台机器都收到空更新);且只动这一节,手写的「设计决策」等内容不会被碰。 好处是:只读项目记忆就能知道有哪些文档、哪些任务还没闭环、各自是不是最新的, 不必查数据库。

目录铁律(这是全局规则 G-0001):./Notes 根目录只允许放用户手动创建、或用户明确要求 创建的 md 文档。你不能把运行过程中自动产生的状态、日志、中间结果写进 ./Notes/ 根目录, 那些只能写到 ./Notes/auto/(该子目录不会被导入记忆)。临时脚本和分析产物同理。

什么时候跑 bmem notes:用户新写或改完一篇 PR/Task/Result 之后、一段实质性工作收尾时。 重复跑是安全的——内容没变就什么都不做(不脏数据、不写 journal)。

语义条目:把文档内容变成可检索的结构

bmem notes 只做到"全文 + 章节"。把文档内容抽成需求/决策/结论/遗留问题/参数/账号指针, 是你的活——bmem 自己不调用 LLM。跑完 bmem notes 它会打印新修订清单和 sha256, 按这个格式写 JSON 再喂回去:

{"facts": [
  {"rel_path": "Notes/Task07.md",
   "content_sha256": "<该修订的 sha256,从 bmem notes 的输出里抄>",
   "items": [
     {"kind": "decision", "title": "N2 的 CP 取 2us",
      "body": "1us 余量不足,4us 损失过多有效符号时间;2us 是鲁棒性与效率的折中。",
      "status": "done", "event_date": "2026-09-01", "section_no": 3}
   ]}
]}
bmem notes --facts facts.json --agent claude-code --model <你的模型名>

kind 取值:requirement decision result issue param credential-pointer lesson other。 status 取值:open done superseded(可省略)。

三件事值得知道:

  • 条目挂在具体修订的 sha256 下,所以文档一改,旧条目自动退出"当前"视图,不会拿过期结论骗人。
  • 同一修订重复提交 = 重抽,bmem 会写成新一代 extraction_gen,旧代保留但不再是当前—— 抽得不好可以重来,不用怕污染。
  • 抽取失败不影响主流程:全文和章节已经进库了,检索仍然兜得住。

抽什么值得抽:设计取舍和它的理由、被否决的方案和否决理由、遗留问题、关键参数、 踩过的坑。不值得抽:文档结构的复述、代码里已经写清楚的东西。

判断:这条信息该进哪一层

这是用 bmem 时唯一真正需要判断力的地方。用最上面那张表的判据问自己一遍。

# 全局:跨项目成立
bmem add --global "改完 Simulink 原生模型必须同步仿真模型,否则 RTL 已变而仿真还在验证旧行为"

# 项目:这个项目的约束/结论(默认写进「项目规则/约束」)
bmem add --prj "ADC 采样率固定 61.44MSps,下游所有速率按它推导"

# 项目:设计决策要写进「设计决策」节,并说清备选项和否决理由
bmem add --prj --section "设计决策" "N2 的 CP 取 2us:1us 余量不足,4us 损失过多有效符号时间"

# 机器:只对这台机器成立的事实
bmem add --machine "ADRV9009 评估板接在这台机器上,JTAG 走 USB-Blaster II"
bmem add --machine --section "账号/服务配置" "gitea 账号 bowei,凭证在 Windows 凭据管理器"

条目会自动编号(G-/P-/D-/M-)并带日期,追加写入,不会覆盖已有内容。

--section 决定它落在文件的哪一节,不给就用默认节。 各层可用的节和默认值:

层 默认节 其它可用节
--global 全局规则 (通常就这一个)
--prj 项目规则/约束 设计决策、当前状态 / 未解决问题、项目简介
--machine 工具环境 账号/服务配置、本机项目位置

节名要原样写全(含空格,如 --section "当前状态 / 未解决问题");写一个不存在的节名会新建一节。

这件事值得留意:尚未解决的问题不是"规则/约束"。用默认节会把一条待办混进项目铁律里, 半年后读的人分不清哪些是必须遵守的、哪些只是还没做完的。用 bmem add --prj --section "当前状态 / 未解决问题" "..." 直接放对位置—— 不要写完再去手工编辑 Markdown 搬家,那样绕过了自动编号,也容易破坏表格和已有条目。

凭证的处理:默认只在机器记忆里记「账号 + 凭证放在哪」,明文口令留在本机 ~/.bmem/config.toml(不进任何仓库)。用户明确要求把某条明文写进机器记忆时照做—— 那是他的私有 gitea,由他决定;但不要自作主张替他把口令写进去。

什么不值得记

写太多等于没记,检索会被噪音淹没:

  • 代码和 git 历史已经说清楚的:文件结构、某次修复的 diff、函数签名。记忆要回答"为什么这么改"。
  • 只对当前这轮对话有意义的中间状态:临时变量、调试到一半的猜测、下一步计划。
  • 未经验证的猜测——除非明确写成 issue 并标 status: open。
  • 会话转录全文。

反过来有一类东西特别值得记而常常被忘记:被否决的方案和否决它的理由。 只记结论,半年后会有人(很可能是你自己)重新提出同一个被否决的方案。

收尾之前

一段实质性工作结束前:

  1. 该写的记忆按层写进去(bmem add);
  2. 用户新增/改动的 Notes 文档导进来,并附上本次会话摘要:
    bmem notes --session-summary <摘要文件> --agent claude-code
    
    摘要文件写到临时目录或 ./Notes/auto/,不要写进 ./Notes/ 根目录。
  3. 对新修订生成语义条目 JSON 并入库(见上);
  4. 同步出去:
    bmem sync
    

bmem sync 做两件事:git 同步三层 Markdown(工作副本 ↔ ~/.bmem/repo clone ↔ gitea), 以及与中心 PostgreSQL 双向同步 log 层。它只碰 bmem 自己的文件,不会动你项目的 git 分支。

bmem sync 的 Gitea 故障:立即修复并实测闭环

bmem sync 的 Git 层失败时先判型。若 refs/API 仍可读而 clone/fetch 报 bad pack header、early EOF、RPC failed 或 git-upload-pack 异常,这是 Gitea pack 传输故障;当轮立即使用 tools-sync skill 阶段 1.5 登录服务器诊断并修复,不要只重试 bmem sync、只报“远端待恢复”,或把 PostgreSQL 成功当成整个同步成功。

  1. 从 ~/.bmem/repo 的 origin 取得 bmem 仓库 URL;运行 tools-sync/scripts/gitea_doctor.ps1 -RepoUrl <URL> 确认 refs 与真正的浅克隆两个面,然后 -Fix。服务器地址、容器名、SSH 主机指纹取自本机 ~/skills_env.md,凭据只从本机已授权的凭据存储或 bmem 既有记录读取,绝不输出、硬编码到 skill/仓库或提交。若 OpenSSH 私钥缺失,doctor 应尝试已登记密码和固定主机指纹的 PuTTY 通道;仍不能登录时立即说明缺的是哪种访问能力并请求用户提供,不能声称完成。
  2. 在服务端先核对精确目标,再对异常全局 Git 配置或注入脚本做可恢复隔离、重启并重新检查;不进行强推、仓库手术或不可恢复清理,除非另有明确授权。修复后的判断依据是从客户端真正完成 fetch/clone,不是 Gitea 网页、ls-remote 或容器自称 healthy。
  3. 立刻重跑 bmem sync。传输恢复后若出现真实 rebase 冲突,保留本地和远端语义,解决 ~/.bmem/repo 及会重新覆盖仓库的本机 Markdown 工作副本,特别核对自动编号唯一;只做非强制推送,再次运行 bmem sync。
  4. 只有 bmem sync 返回成功、Git 拉取/推送成功、远端 main 与本地提交相同、客户端浅克隆可读,才报告 Markdown 层已同步;PostgreSQL 另按自身结果报告。若新故障持续,继续做安全范围内的诊断;真正缺少登录权限或需要额外授权时报告具体阻塞和所需输入,不把未同步状态遗留为“已完成”。

项目记忆由谁同步:一个文件只能有一个归属

项目记忆 bmem_<prjID>.md 躺在项目目录里,而项目目录往往已经被 Syncthing / SyncTrayzor 整目录同步。同一个可变文件有两个同步归属,就是冲突制造机:git 这边 write_back 把仓库 版本盖回工作副本,文件同步工具再忠实地把这次覆盖传播出去,可能抹掉别的机器几秒前的修改; 两边同时改则留下 .sync-conflict 副本,记忆悄悄分叉而没人知道。

所以 bmem 的规矩是:项目目录已被文件同步工具管理时,项目记忆归它,git 不重复同步 (靠向上查找 .stfolder 自动识别,无需配置)。具体表现:

  • bmem sync:跳过该文件,并在报告里说明原因;bmem.md 与机器记忆不受影响(它们在用户 根目录,通常不在同步范围内)。
  • bmem init:只有文件不存在时才创建骨架,之后完全让给文件同步工具;也不会把仓库里 可能更旧的副本还原进来——那会被当成一次新编辑反向传播,覆盖别的机器上更好的版本。
  • bmem delete:不删这个文件。删掉会连带在所有机器上消失,而 delete 只应注销本机。 真要连别的机器一起删,用 --delete-shared-doc。
  • 任何命令发现 bmem_*.sync-conflict-*.md 都会明确报出来——那是记忆已经分叉的信号, 必须人工合并,机器不该替你猜哪份对。

想改这个行为:~/.bmem/config.toml 的 [git] project_doc_sync,取 auto(默认)/ git(总是走 git)/ none(bmem 永不同步它)。

同步失败时它会自己去看服务端

gitea 和中心 PostgreSQL 跑在同一台机器上(见全局记忆 G-0005 的两条登录方式)。 同步失败时,bmem sync 会 SSH 上去诊断,并把该跑却没跑的服务拉起来,然后重试一次:

bmem sync                # 配置了维护主机就会自动检修(config.toml 的 auto_repair)
bmem sync --repair       # 显式要求检修
bmem sync --no-repair    # 这次别动服务端

它遵守两条规矩,理解它们比记住命令更重要:

  1. 先诊断再动手。每一步修复都基于观察到的状态,不基于"大概是什么坏了"的猜测。
  2. 只恢复服务,不改配置。可以做的:启动没在跑的 PostgreSQL、启动已停止的 gitea 容器、 重启一个在跑但 HTTP 不健康的容器。不会做的:改镜像版本、改 listen_addresses、 改 pg_hba、改任何认证设置——这些一律只报告,交给人决定。 区别在于:把服务恢复到本该有的状态是可逆的,重新定义这个服务不是。

两种失败它只报告不处理:服务本身响应正常但 git 传输仍失败(pack 传输故障,按上一节交给 tools-sync 阶段 1.5 的 gitea 医生;根因见 G-0006,G-0004 的“换镜像版本”已作废);以及维护主机根本连不上。

只有真正失败的那一半会被检修——PG 挂了不会顺手重启 gitea。路由会先走 tailscale, 不通再自动回退到公网 frps。

多机与本机状态

bmem machines                      # 所有机器 + 各自的项目和路径
bmem machines --project 52-link    # 这个项目在哪些机器上、各自路径、最近谁动过
bmem machines --json               # 结构化输出
bmem sqlite                        # 本机镜像:位置、表结构、项目情况、内容摘要
bmem sqlite --schema               # additionally 每张表的列定义

两条都只读本机镜像、不连中心库,离线可用。

bmem machines 会报注册表漂移:某机器只在 bmem.md 里有 = 它还没 push 或你还没 pull 到; 只在镜像里有 = 本机的 bmem.md 落后了。这不是错误,是状态,读的时候要把它当成 "我看到的可能不是最新的"。

新项目、新机器

bmem install       # 每台机器一次:生成 machineID、拉记忆仓库、装 CLAUDE.md/AGENTS.md adapter
bmem init          # 每个项目一次,在项目根目录跑

bmem init 自己会判断三种情况:本机已注册过就提示不要重复注册;bmem.md 里已有这个项目 (说明别的机器建过)就从记忆仓库把 bmem_<prjID>.md 取回来并登记本机路径;全新项目才新建。 所以在第二台机器上直接跑 bmem init 是对的,不会造成身份分裂。

bmem install 幂等,可以反复跑:它会顺带把机器记忆里的 OS、用户根目录刷新成当前实际值。

从本机注销一个项目

bmem delete              # 只打印会动哪些东西,不做任何改动
bmem delete --yes --sync # 确认执行并同步给其它机器

它只注销本机:删掉项目根目录的 bmem_<prjID>.md(直接删除,不留备份文件—— find_project 是向上查找的,残留一份会让它下面所有子目录都被误判成这个项目的根; 删除前会先把内容存进记忆仓库 ~/.bmem/repo/projects/,所以内容不丢)、 bmem.md 里本机那一行、机器记忆的位置行、项目 CLAUDE.md/AGENTS.md 里的 BMEM 块, 以及中心库里 project_locations 中本机那一行。

它不会删:./Notes 下的任何文档、log 层的历史(修订/章节/条目/事件都留在中心库和镜像里)、 其它机器的登记、记忆仓库里的 projects/bmem_<prjID>.md。所以将来在任意机器上 bmem init 就能重新接上,历史一条不少。

三件要注意的事:

  • 默认只预览。不加 --yes 什么都不会发生,输出会逐项列出"将删除"与"不会删除"。 破坏性操作先看清楚再动手,这是这条命令唯一的使用纪律。
  • 预览会告诉你还有哪些机器登记着这个项目。如果显示"本机是最后一台",说明删完就没有 任何机器持有它了——不是不能删,但要清楚这一步的含义。
  • 中心库的删除是排队执行的:离线也能删,删除意图记在本地并写进 journal, 下次 bmem sync 成功后其它机器才会看到。所以删完记得 sync。

出问题时

症状 含义与处理
pg: 不可用(cannot reach PostgreSQL…) 中心库连不上。数据没丢:留在本机 outbox 和 ~/.bmem/journal/ 里,网络恢复后重跑 bmem sync 即可。所有命令本地照常可用
git: 冲突,已中止 rebase 两台机器改了同一个记忆文件。bmem 不会自动合并(Markdown 是事实源),去 ~/.bmem/repo 手工合并再 bmem sync。本地内容一条没丢
pull failed before any merge 传输失败,不是冲突。本地提交完好,push 仍会照常尝试
本地库损坏 / 想清空重来 直接删 ~/.bmem/bmem_local.sqlite 再 bmem sync:已同步部分从中心库拉回,未推送部分由 journal 回放补齐。这是设计好的恢复路径,不是下策
项目 X 已在本机注册,无需重复注册 正常提示,不是错误
facts reference an unknown revision facts JSON 里的 sha256 对不上。先跑一次 bmem notes,用它打印出来的 sha256
unknown fact kind / unknown status 枚举值写错了,对照上面的取值表
想知道还有多少没推上去 bmem sqlite 底部的「未推送」和 journal 那一行

一条通则:bmem 的报告里出现的不都是待办。「pg 不可用」「bmem.md 落后了」描述的是状态, 先判断这个状态该不该改,再决定要不要动手。为了让输出变干净而执行有副作用的命令, 是把手段当成了目的。

命令速查

用途 命令
读记忆 直接读 ~/bmem.md、bmem_<prjID>.md、~/bmem_<machineID>.md
查历史 scripts/bmem_recall.py <关键词> / --kind / --recent
写记忆 bmem add --global|--prj|--machine [--section 节名] <内容>
导入 Notes bmem notes [--check] [--session-summary F] [--facts F] [--sync]
看多机/本机状态 bmem machines [--project P] [--json] / bmem sqlite [--schema] [--json]
同步 bmem sync [--repair | --no-repair]
安装与初始化 bmem install / bmem init
注销项目 bmem delete(预览)/ bmem delete --yes --sync(执行)
帮助 bmem help

曾经的边界,2026-09-02 已修复:gitea 的 pack 服务坏了约一周(push 正常、clone/fetch 报 bad pack header),Markdown 层因此在机器之间停摆。根因是 gitea 的全局 gitconfig 被注入 了 [uploadpack] packObjectsHook,把 git pack-objects 换成了一个不产出 pack 的脚本; 排查与处置写在 tools-sync skill 的阶段 1。现在 bmem sync 的 git 部分应当正常。 再看到 bmem machines 报"本机的 bmem.md 落后了",就按普通的没 pull 处理,不要再归因于服务端。