首页
/ claude-mem 分支记忆之 Chroma 分支感知同步:BRANCH-PARITY-01 向量库分支边界补全实战

claude-mem 分支记忆之 Chroma 分支感知同步:BRANCH-PARITY-01 向量库分支边界补全实战

2026-09-04 13:33:23作者:柯茵沙

claude-mem 的「分支记忆(Branch Memory)」功能让 Agent 的记忆感知 Git 分支边界:语义检索不再跨越 Git 祖先关系返回其他分支的结果。本文以开发 Playbook BRANCH-PARITY-01-Chroma-Branch-Sync 为主体,完整还原该阶段的八个任务:合并 main 分支、为 Chroma 类型系统补齐 branch/commit_sha 元数据、改造文档格式化与同步方法、实现分支感知的向量过滤、修复 summary 表缺失列的迁移缺口,以及 19 个配套测试。读完后你能掌握「如何为一个既有向量库同步层增加一套新的过滤维度,并保持旧数据的向后兼容」这一完整的工程方案。

背景:ChromaSync 为什么需要分支边界

分支记忆功能此前已完成 6 个阶段(schema、祖先关系解析、上下文过滤、搜索过滤、边界情况、集成测试),但向量检索层 ChromaSync 始终忽略分支边界。同步到 ChromaDB 的 observation 缺少 branchcommit_sha 元数据,导致语义搜索无视 Git 祖先关系,返回所有分支的结果。本阶段(Phase 01)的目标有二:

  1. 把 main 分支的 v10.5.4–v10.5.5 区间(约 24 个提交的 bugfix)合并进 branch-memory 工作分支,保证后续开发基于最新代码;
  2. 让 ChromaDB 分支感知(branch-aware),使向量搜索尊重分支可见性。

在 claude-mem 中,ChromaSync 类 是 SQLite 记忆层与 ChromaDB 向量层之间的同步桥梁:每条 observation/summary 被拆成多个带元数据的 Chroma 文档写入集合(集合名形如 cm__<project>,见 集合名构造逻辑),再由 ChromaSearchStrategy 通过 where 过滤条件执行向量查询。为它增加分支维度,需要贯穿「类型定义 → 文档格式化 → 同步入口 → 过滤构造 → 回填」五个环节。

任务一:合并 main 分支

Playbook 要求在执行 git fetch origin + git merge origin/main 后,用 npm run build-and-sync 验证合并后的代码库可干净构建。实际合并中唯一的冲突出现在 SessionStore.ts 的 bulk insert:main 引入了 content_hash 字段,branch-memory 引入了 branch/commit_sha 字段,解决方案是在同一条批量插入语句中同时容纳两组字段。这一冲突本身揭示了本次功能与 main 演进的正交性——两者都在扩展同一张表的写入路径。

任务二:类型系统补齐 branch / commit_sha 字段

Playbook 指出三个接口需要更新:

  • src/services/sync/ChromaSync.ts 中的 StoredObservation 接口(约 26 行处,共 16 个字段,以 created_at_epoch 结尾)增加 branch?: string | nullcommit_sha?: string | null
  • 同文件中的 StoredSummary 接口(约 45 行处,结构类似)增加同样两个字段;
  • src/services/worker/search/types.ts 中的 ChromaMetadata 接口(约 38 行处,含 sqlite_iddoc_typeproject 等字段)增加 branch?: stringcommit_sha?: string

对照当前主分支的 StoredObservation 定义StoredSummary 定义 可以看到基线形态:字段列表以 prompt_numbercreated_at_epoch 收尾,与 Playbook 描述一致。这三个接口是数据流上的「契约」——SQLite 行 → 格式化器 → Chroma 文档 → 查询结果反解析,任何一环缺字段,元数据就会静默丢失。Playbook 记录该任务完成时出现了一些预存在的 TypeScript 报错(bun:sqliteComponent 类型),与本次改动无关。

ChromaMetadata 接口位于 搜索类型定义文件,被 ChromaSearchStrategyfilterByRecency() 用作查询结果元数据的类型约束。

任务三:formatObservationDocs / formatSummaryDocs 携带分支元数据

Playbook 的改法遵循既有的「可选元数据守卫模式」:在 formatObservationDocs 已有的可选元数据块(subtitleconceptsfiles_readfiles_modified)之后追加:

if (obs.branch) { baseMetadata.branch = obs.branch; }
if (obs.commit_sha) { baseMetadata.commit_sha = obs.commit_sha; }

formatSummaryDocs()(约 189 行处,当前为 formatSummaryDocs)对 summary 的 baseMetadata 块应用相同模式。这个「字段存在才写入」的守卫不是风格偏好,而是向后兼容的关键:Chroma 元数据是稀疏的(sparse)bag,旧文档没有 commit_sha 键,格式化器不应为它们伪造一个空键——这直接决定了后续过滤器能否用 $or 区分新旧文档。

值得注意的底层细节:addDocuments 在把批文档写入 Chroma 前,会先过滤掉 null/undefined/空字符串的元数据值(cleanMetadatas,见 元数据清洗逻辑)。也就是说格式化器产出的 branch: null 最终不会出现在 Chroma 文档上,与「omitted」等价——两条路径共同保证旧语义不变。

任务四:syncObservation / syncSummary 透传分支参数

Playbook 要求在 syncObservation(约 304 行处)与 syncSummary(约 348 行处)的 discoveryTokens 之后各增加一对可选参数 branch?: string | nullcommitSha?: string | null,并写入内部构造的 StoredObservation/StoredSummary 对象,从而让格式化器把分支元数据传播到 Chroma 文档。

调用点在 ResponseProcessor.ts(约 198 行):原先 syncObservation() 传 7 个实参,Playbook 指引先弄清 storeObservationsAndMarkComplete() 是如何拿到 branch/commit_sha 的(来自 pending 消息的 branch/commit_sha 字段),再把会话上下文的 session.lastBranchsession.lastCommitSha 一并传给两处调用点。这里体现了 claude-mem 的数据流分层:分支信息在转录处理管道中被提取并暂存在会话对象上,响应处理阶段再分发到持久化(SQLite)与向量同步(Chroma)两条下游,保证同一事实只提取一次、两处落盘一致。

任务五:buildWhereFilter 支持分支感知过滤

这是本阶段最有工程含量的部分,对应 ChromaSearchStrategy.buildWhereFilter。Playbook 的设计要求:

  1. 方法签名增加可选参数 commitShas?: string[]
  2. commitShas 非空时,构造 { commit_sha: { $in: commitShas } }(Chroma 支持 $in 操作符);
  3. 必须同时保留「没有 commit_sha 的旧文档」(迁移前数据),可用 Chroma $or 组合:{ $or: [{ commit_sha: { $in: commitShas } }, { commit_sha: { $eq: '' } }] };Playbook 特别提醒:若 Chroma 对缺失字段的 $or 处理不干净,退化方案是在 filterByRecency() 里做后置过滤(该方法本就在遍历结果),并核实 $in 对缺少该元数据键的文档行为;
  4. 新过滤器通过 $and 与既有的 docTypeFilterprojectFilter 组合。

Playbook 记录的实现选择是把 buildWhereFilter() 重构为「条件数组模式」——当前主分支代码正是这一模式的体现:先向 filters 数组按序 push doc_type 条件、project 条件($or 兼容 merged_into_project)、platform_source 条件,最后零条件返回 undefined、单条件直接返回、多条件包 $and(见 过滤组合逻辑)。分支过滤在此模式下的接入点是向数组追加一个 $or 条件即可,无需改写既有组合逻辑。同时 search() 方法负责把 StrategySearchOptions 中的 commit_sha 归一化为字符串或数组后传入过滤器构造器,消除调用方传参形态不一的问题。

从源码结构看,这一「条件数组 + 按需组合」的写法正是为 Phase 01 的扩展铺路:分支过滤作为第五个条件加入,与 doc_type/project/platform_source 正交。

任务六:ensureBackfilled 回填路径与 migration 26

Playbook 的关键洞察是:回填查询 SELECT * FROM observations 本就会返回 SQLite 中已有的 branch/commit_sha 列(这些列是此前 Phase 01–05 建 schema 时加入的),而查询结果被整体 cast 为 StoredObservation[]——类型补齐后,字段会经由 backfillObservationsformatObservationDocs() 自动流入 Chroma 元数据,回填「免费」获得分支感知。

但验证 summary 回填时发现了真正的缺口:session_summaries完全没有 branch/commit_sha 列。修复分三步:

  1. 新增 migration 26(addSummaryBranchColumns())为 session_summaries 加列;
  2. 更新 storeSummary()storeObservations() 中的 summary INSERT 语句写入 branch/commit_sha;
  3. 此后 observation 与 summary 的回填(分别见 backfillSummaries)都能把分支元数据正确传播到 Chroma。

这一节也展示了回填的容错机制:backfillKind 的水印推进是「行原子」而非「批原子」——一行记录展开的多个 Chroma 文档必须全部落盘才会 bump 水印,任何部分写入都会把该行记入 pending 状态待下次重试,防止重启导致某行的「尾部文档」永久滞留。

任务七:19 个测试覆盖同步与过滤

Playbook 要求新建 tests/chroma-branch-sync.test.ts,风格对齐 tests/branch-memory-integration.test.ts,并对 ChromaMcpManager 做 mock/stub(测试环境中 Chroma MCP server 不可用)。最终交付两个 describe 块共 19 个用例:

  • 「Chroma Branch Metadata Sync」(13 个用例)
    • formatObservationDocs 在 branch/commit_sha 存在时写入元数据;
    • branch/commit_sha 为 null/undefined 时从元数据中省略(向后兼容);
    • syncObservation 正确把 branch/commit_sha 透传到格式化文档。
  • 「ChromaSearchStrategy buildWhereFilter with branch filtering」(6 个用例)
    • commit_sha 数组生成正确的 $or/$and 组合过滤器;
    • 未提供 commit_sha 时生成向后兼容过滤器;
    • 单个字符串与数组两种形态的归一化。

同步侧测试 mock addDocuments,过滤侧测试 mock queryChroma,使测试不依赖真实的 Chroma MCP server。

任务八:全量测试与构建验证

收尾验证记录:npm test 全量 1224 通过、3 跳过、3 失败——19 个新增 chroma-branch-sync 用例全部通过,3 个失败来自 main 分支提交中的 renderMarkdownEmptyState,与分支记忆改动无关;npm run build-and-sync 完成,全部产物编译并同步至 marketplace。

小结:为向量库加维度的通用配方

回溯 Playbook 的八项任务,它实际上示范了一套可复用的模式,把「一个新的过滤维度」安全地加进既有向量同步栈:

  1. 契约先行:三个接口(存储形态 × 2 + 查询元数据 × 1)同步补字段,数据流任何一环不缺位;
  2. 稀疏写入:格式化器用守卫模式「有则写、无则省」,写入层再清洗空值,旧文档与旧语义零扰动;
  3. 过滤向后兼容:新维度用 $or(新条件 OR 缺失/空值)包裹,再经条件数组以 $and 与既有正交条件组合;
  4. 回填白拿SELECT * + 类型 cast 让已落库列自动流经既有格式化器,唯一要补的是 schema 迁移(本例的 migration 26);
  5. 测试双保险:格式化器(存在/缺失两态)与过滤器(有/无维度、单值/数组)各自独立 mock,不依赖真实 Chroma。

该 Playbook 所属的分支记忆系列还包括后续的 Viewer 分支展示MCP 输出及验证 两个阶段,读者可结合它们了解分支维度从向量层向展示层与 MCP 工具层继续延伸的完整路径。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384