构建AI记忆桥梁:打通数据孤岛,打造个人知识大脑
1. 项目概述:为什么我们需要一个“记忆桥梁”?
如果你和我一样,在日常开发中重度依赖各种AI助手——比如用Claude Code写代码、用Hermes管理长期记忆、用Codex CLI快速调试——那么你肯定遇到过这个痛点:你的“数字记忆”散落在各处,格式五花八门。当你想回顾三个月前和Claude讨论的那个架构决策,或者想查找Hermes里记录的个人工作习惯时,你面对的是一堆杂乱的JSONL文件、SQLite数据库和Markdown碎片。更糟的是,像gBrain这样优秀的个人知识图谱工具,默认只“吃”干净的Markdown。这就形成了一个尴尬的局面:你拥有海量的对话历史和记忆,却无法将它们统一到一个可搜索、可关联的“大脑”里。
hermes-gbrain-bridge这个项目,就是为了解决这个“最后一公里”问题而生的。它是一个轻量级、零依赖的转换桥梁,专门将来自Hermes、Claude Code、Codex、OpenClaw等不同AI工具的、格式各异的记忆文件,转换成gBrain能够直接导入的标准化Markdown。它的核心价值不是创造新数据,而是打通数据孤岛,让你过去所有的AI交互记录,都能成为未来决策的养料。
我最初动手写这个工具,是因为受够了在多个终端和历史记录里来回翻找。一个典型的开发机上,AI记忆至少存在于五个地方:~/.hermes/、~/.claude/projects/、~/.codex/sessions/,还有各种迁移存档。手动整理?工程浩大且不可持续。这个桥接器的出现,意味着你可以用一条命令,就将过去30天、甚至更久的所有有价值对话,全部喂给你的个人知识库,实现真正的“一个大脑,全局搜索”。
2. 核心设计思路:转换器而非侵入者
在动手构建任何数据管道之前,一个关键的设计决策会决定项目的成败:你的工具是直接操作目标系统(比如gBrain的数据库),还是只负责准备数据?hermes-gbrain-bridge坚定地选择了后者。它的架构哲学非常清晰:只生产Markdown,绝不直接触碰数据库。
2.1 架构拆解:清晰的责任边界
整个工作流可以被清晰地划分为三个阶段,每个阶段职责单一,故障影响范围最小化。
你的本地机器 (数据源) -> Bridge (转换器) -> 暂存目录 (Markdown) -> gBrain (导入、嵌入、查询)- 发现与采集阶段:工具扫描你机器上所有已知的AI记忆存储路径。这一步是“只读”的,仅仅统计文件数量、大小,让你心里有数。
- 转换与暂存阶段:这是核心。针对每种数据源(如Claude Code的JSONL事件流),有对应的“适配器”进行解析、过滤、清洗,并输出为统一的“规范事件”格式,最后序列化成Markdown文件,写入一个你指定的临时目录(如
/tmp/gbrain-staging)。 - 导入与使用阶段:完全交由
gbrain命令行工具处理。它读取暂存目录里的Markdown,进行去重、分块、向量化嵌入,并存入数据库(如PostgreSQL + pgvector)。后续的查询也由gBrain服务端完成。
这种设计带来了巨大的好处:
- 安全性:即使桥接器有Bug,最坏情况是生成了一些格式错误的Markdown文件。你的核心知识库(gBrain的数据库)安然无恙。修复Bug后重新运行转换即可。
- 可维护性:gBrain和桥接器可以独立升级。只要Markdown的输出格式约定不变,两者互不影响。
- 灵活性:未来如果你想换用另一个支持Markdown导入的知识库系统,这个桥接器产出的数据依然可用。
2.2 统一中间层:规范事件格式
不同来源的数据结构天差地别。Claude Code的JSONL里充满了tool_use、progress等事件噪音,而Hermes的长期记忆可能是用§符号分隔的Markdown。如果每个适配器都直接写Markdown,代码会充满重复和特例。
这个项目的巧妙之处在于定义了一个中间的CanonicalEvent(规范事件)格式。每个适配器的工作,就是将原始数据(无论多复杂)转换成一个或多个这种规范事件。这个事件通常包含:
id: 唯一标识符。timestamp: 事件发生的时间。source: 来源(如claude-code,hermes-session)。role: 发言者(user,assistant,system)。content: 纯文本内容。metadata: 额外的结构化信息,如会话ID、文件路径、使用的模型等。
所有适配器都输出同一种格式的内存对象后,再由一个统一的“序列化器”将这些事件按逻辑(通常是一个会话或一个文件)组织成一篇连贯的Markdown文档。这样做,添加对新数据源的支持就变成了实现一个“原始数据 -> CanonicalEvent”的转换函数,极大地降低了开发复杂度。
3. 实操指南:从零开始搭建你的记忆桥梁
理论讲完了,我们来看看具体怎么用。假设你已经在日常使用Claude Code和Hermes,并且已经按照gBrain的文档搭好了知识库后端(比如用Railway部署了Postgres)。下面是一步一步的操作流程。
3.1 环境准备与工具安装
首先,确保你的基础环境就绪。这个项目基于Bun运行时,速度很快。
# 1. 安装Bun (如果你还没有的话) # 访问 https://bun.sh 查看官方安装命令,通常如下: curl -fsSL https://bun.sh/install | bash # 2. 克隆仓库并进入目录 git clone https://github.com/howardpen9/hermes-gbrain-bridge cd hermes-gbrain-bridge # 3. 安装项目依赖 bun install安装完成后,你可以通过bun run src/cli.ts来查看命令行工具的所有可用命令和参数。
注意:这个项目本身没有外部依赖,安装瞬间完成。这比那些需要拉取上百个NPM包的项目要清爽得多,也减少了供应链攻击的风险。
3.2 探索数据:先看后动
在真正开始转换数据之前,强烈建议你先“侦察”一下。使用discover命令,它可以告诉你各个来源有多少文件、总大小是多少,而不会进行任何写操作。
bun run src/cli.ts discover --days 30这个命令会扫描所有已支持的源路径,并列出在过去30天内修改过的文件。输出大概长这样:
Discovering agent memory sources (last 30 days)... [hermes-sessions] ~/.hermes/sessions/*.jsonl: 45 files (1.2 MB) [hermes-memory] ~/.hermes/memories/*.md: 2 files (84 KB) [claude-code] ~/.claude/projects/**/*.jsonl: 1247 files (893 MB) [codex] ~/.codex/sessions/**/*.jsonl: 312 files (67 MB) [openclaw-archive] ~/.openclaw.pre-migration/workspace/**/*.md: 0 files (0 B)看到那个893MB的Claude Code数据了吗?这就是你即将处理的数据量。这一步能让你避免意外处理了过多或过旧的数据,特别是当你想控制嵌入成本时。
3.3 执行转换:分步推进
了解了数据概况后,我们可以开始转换了。建议先做一次“干跑”来预览效果。
# 1. 干跑:模拟转换Claude Code的数据,看看会生成多少Markdown bun run src/cli.ts export --source=claude-code --dry-run --days 30--dry-run参数意味着程序会走完所有的读取、解析、过滤逻辑,并打印出将会创建的文件列表和统计信息,但不会实际写入任何文件。这是验证你的过滤规则(如时间窗口、大小阈值)是否合理的绝佳方式。
如果干跑结果符合预期,就可以进行真实导出了。你可以一次导出一个源,也可以一次性导出所有源。
# 2. 真实导出:将所有源的数据导出到指定目录 bun run src/cli.ts export --source=all --out=/tmp/gbrain-staging --days 30执行后,去/tmp/gbrain-staging目录下看看,你应该能看到一堆以.md结尾的文件,每个文件对应一个会话或一个记忆单元。打开一个看看,内容应该是结构清晰、包含元数据(如来源、时间戳)的对话记录。
3.4 导入gBrain:完成最后一步
现在,干净的Markdown数据已经准备好了,最后一步就是交给gBrain处理。
# 进入gBrain的项目目录(假设你已经克隆了gbrain) cd /path/to/your/gbrain # 1. 导入Markdown文件到数据库 bun run src/cli.ts import /tmp/gbrain-staging --no-embed # `--no-embed` 表示先只导入文本,不计算向量嵌入。 # 2. 为所有导入的内容计算向量嵌入(这步需要OPENAI_API_KEY) export OPENAI_API_KEY='your-api-key-here' bun run src/cli.ts embed --stale # `--stale` 参数表示只为新导入的、尚未嵌入的内容计算嵌入向量。嵌入过程可能会花费一些时间,取决于数据量和你使用的嵌入模型(默认是text-embedding-3-large)。完成后,你的个人知识大脑就构建好了!
3.5 进行查询:验证成果
让我们问一个只有你的历史对话才知道的问题,来测试一下。
bun run src/cli.ts query "上周我和Claude讨论的关于用户认证模块的重构方案是什么?"如果一切顺利,gBrain会返回最相关的几个文本片段(chunks),并附上相似度分数和来源。至此,你成功地将散落的记忆连接成了一个可检索的整体。
4. 核心适配器解析:如何“读懂”不同的记忆格式
桥接器的核心能力体现在一个个“适配器”上。每个适配器都是一个独立的模块,负责理解一种特定的数据格式。我们来深入看看两个最复杂也最常用的适配器是如何工作的。
4.1 Claude Code适配器:从事件流噪声中提取对话精华
Claude Code的会话存储在~/.claude/projects/<encoded-path-hash>/*.jsonl路径下,每个文件是一个JSON Lines格式的事件流。这是**最需要“过滤”**的源。
原始数据的挑战: 一个原始的Claude Code JSONL文件,里面可能包含多达几十种事件类型,例如:
progress: 任务进度更新(“思考中...”)。queue-operation: 内部队列管理事件。tool_use/tool_result: AI调用工具(如读写文件)的请求和响应。user/assistant: 用户和AI的实际对话内容。
如果你把所有这些都导入知识库,那么当你搜索“如何优化数据库查询”时,搜索结果里可能会混入大量“正在写入文件config.json”这样的工具调用记录,毫无意义。
适配器的处理策略:
- 严格过滤:适配器会只保留
type为user或assistant的事件。这是最核心的一步,通常能过滤掉90%以上的数据体积。 - 内容提取:
user事件的内容通常在message.content字段里,但它可能是一个字符串,也可能是一个数组(比如包含文本和图像)。适配器需要稳健地将其处理成纯文本。 - 上下文关联:将同一个会话中的所有
user和assistant事件按时间顺序拼接起来,形成一篇连贯的对话记录。 - 元数据附加:从文件路径中解析出编码后的项目路径,作为元数据的一部分写入Markdown的Frontmatter,方便后续追踪来源。
实操心得:
- 性能:处理上千个JSONL文件时,流式读取(line-by-line)比一次性读入内存要可靠得多,尤其是当单个文件可能很大时。
- 健壮性:总是假设
message.content的结构可能变化,做好类型判断和回退处理。早期版本我就因为一个会话里包含了一张图片(content是数组)而导致整个转换进程崩溃。
4.2 Hermes适配器:处理结构化和非结构化记忆
Hermes的记忆相对规整,主要分两类:
- 会话记忆(
~/.hermes/sessions/*.jsonl):格式也是JSONL,但结构更简单,通常直接包含角色和内容。适配器处理起来类似Claude Code,但无需过滤大量工具事件。 - 长期记忆(
~/.hermes/memories/*.md):这就是Markdown文件,但有其内部格式。例如,MEMORY.md文件可能用§符号来分隔不同的记忆条目。
对于长期记忆Markdown的处理: 这里的策略不是“转换”,而是“规范化”。适配器需要:
- 识别格式:读取文件,判断其是普通的Markdown还是用特殊分隔符的结构化记忆。
- 拆分与封装:如果检测到
§分隔符,就将文件按此分隔符拆分成多个独立的记忆条目。每个条目被包装成一个独立的CanonicalEvent,并共享源文件的元数据(如“这是Hermes的长期记忆”)。 - 直接传递:对于没有特殊格式的Markdown(如
USER.md用户档案),则几乎可以直接传递,只需确保文件头包含正确的元数据即可。
设计启示: 处理既有结构化数据(JSONL)又有非结构化文本(Markdown)的源时,适配器需要有分支逻辑。关键在于保持输出(CanonicalEvent)的一致性,无论输入多么不同。
5. 性能优化与成本控制实战经验
将数GB的原始对话数据转化为知识库,有两个核心成本:时间和金钱(主要是调用OpenAI Embedding API的费用)。在项目实践中,我们积累了一些关键经验。
5.1 过滤是省钱的第一法则
这是本项目带来的最直接、最显著的价值。回顾一下我们案例中的数据:
| 数据源 | 原始大小 | 过滤后大小 | 过滤比例 |
|---|---|---|---|
| Claude Code JSONL | 1.4 GB | ~200 MB | ~86% |
| Codex JSONL | 123 MB | ~80 MB | ~35% |
| OpenClaw Archive | 9.6 MB | ~9.6 MB | 0% (纯Markdown) |
Claude Code的数据过滤掉了超过1GB的“噪音”!这些噪音主要是tool_use,progress等内部事件。如果把这些全部向量化,假设使用text-embedding-3-large模型(每1M tokens约$0.13),这1GB的文本可能会产生数十美元的毫无意义的API开销。而经过过滤,我们只为核心对话内容付费,最终整个项目的嵌入成本不到2美元。
给你的建议:
- 始终开启时间窗口:使用
--days参数。你很少需要搜索一年前的每一次对话。 - 考虑大小阈值:对于存档类数据(如OpenClaw),可以使用
--min-size参数忽略掉太小的碎片文件。 - 理解你的数据:在编写或使用适配器时,务必弄清楚原始数据中哪些是“信号”,哪些是“噪声”。只转换“信号”。
5.2 分块质量比嵌入模型更重要
很多人认为嵌入成本是知识库构建的主要瓶颈,但根据我们的实测,分块策略对检索质量的影响远大于模型选择。
gBrain默认使用基于字符的递归分块算法。这种算法简单快速,但在处理混合内容(如代码、段落文本、列表)的文档时,容易产生语义不连贯的块。例如,它可能在一个代码块的中间将句子切断,或者将一个问题的描述和答案分到两个不同的块里。
这导致了一个现象:即使你问了一个非常具体的问题(例如“上周三下午关于用户登录失败的讨论”),返回的最相关块的相关性分数(余弦相似度)可能也只有0.05到0.1(满分1.0)。这意味着系统需要扫描非常多的候选块才能找到真正相关的,效率低下。
更优的策略:
- 语义分块:使用一个轻量级LLM(如GPT-3.5-Turbo)来分析文档结构,按照语义边界(如“一个问答对”、“一个完整的代码示例加解释”、“一个独立的章节”)进行分块。这能显著提升单个块的语义完整性。
- 混合分块:对于代码库,可以按函数/类分块;对于对话,按完整的问答轮次分块。
- 重叠分块:在块与块之间设置一定的重叠区域(如50个字符),确保上下文信息不会在边界处完全丢失。
虽然语义分块会增加前期处理成本,但它能极大提升后续检索的准确率和效率,让你的嵌入开销“花在刀刃上”。gBrain社区正在积极实验这方面的改进。
5.3 处理数据源的不平衡性
在我们的案例中,最终生成的57,627个文本块里,有53,191个来自Claude Code,占比高达92%。这意味着,当你搜索一个本应存在于Hermes记忆(比如你的个人时区设置)中的信息时,语义搜索的结果可能会被海量的Claude Code对话块“淹没”,排名靠前的都是相关性不高的Claude Code会话。
解决方案:
- 源感知加权:在检索时,为不同来源的块设置不同的权重。例如,将Hermes记忆块的权重设为2.0,Claude Code块的权重设为1.0。这样,即使Hermes的块数量少,其相关性分数也会被放大。
- 源过滤查询:在查询时增加一个过滤器,例如
gbrain query "我的时区" --source=hermes,只从指定的来源中搜索。 - 建立多个专业大脑:为什么不只建一个“全能大脑”,而是建几个“专业大脑”呢?比如,一个“代码对话大脑”(只包含Claude Code/Codex数据),一个“个人记忆大脑”(只包含Hermes数据)。根据问题类型,选择向对应的大脑提问。
6. 安全与隐私考量
处理本地历史数据,尤其是可能包含API密钥、密码等敏感信息的对话记录,安全是重中之重。hermes-gbrain-bridge内置了基础的秘密信息擦除功能。
6.1 内置的擦除模式
在src/normalize.ts文件中,工具定义了一系列正则表达式模式,用于识别和擦除常见类型的秘密:
- OpenAI / Anthropic API密钥:
sk-* - Slack Bot Tokens:
xoxb-* - GitHub Personal Access Tokens:
ghp_*,github_pat_* - AWS Access Keys:
AKIA* - 数据库连接字符串:
postgres://,mysql://,mongodb+srv:// - 通用密码模式(简易): 匹配
password=...或passwd=...等。
在转换过程的“规范事件”生成阶段,所有文本内容都会经过redactSecrets(text)函数处理,将匹配到的秘密替换为[REDACTED]。
重要警告:这个擦除列表是保守的、非穷尽的。它无法识别所有可能的秘密格式,特别是自定义的或新出现的令牌格式。绝对不要假设经过此工具处理的数据就是完全“干净”的。
6.2 你必须做的安全检查
在将生成的Markdown文件导入到任何共享的、云端的或你认为敏感的知识库之前,请务必进行手动审查。
- 审查暂存目录:在运行
gbrain import之前,用文本编辑器或grep命令快速扫描/tmp/gbrain-staging目录下的Markdown文件,搜索key、token、secret、password等关键词。grep -r -i "key\|token\|secret\|password" /tmp/gbrain-staging/ | head -20 - 处理敏感项目:如果你在对话中讨论过极其敏感的项目(如公司内部系统架构、未公开的商业计划),最好在导出时使用
--source参数排除相关数据源,或者事后在gBrain中手动删除相关页面。 - 本地优先:对于高度敏感的数据,考虑始终将gBrain部署在本地机器上,而不是Railway或Supabase这样的云平台。
hermes-gbrain-bridge生成文件,gbrain也可以完全在本地运行,使用本地的PostgreSQL和本地嵌入模型(如通过Ollama运行nomic-embed-text)。
安全无小事,尤其是在自动化处理个人数据时。养成检查中间产物的习惯。
7. 扩展指南:如何支持新的AI工具
生态在不断发展,新的AI工具和它们的记忆格式层出不穷。hermes-gbrain-bridge的设计使得添加新适配器变得相对简单。如果你想让工具支持你的某个AI助手的记忆,可以参照以下步骤。
7.1 适配器接口剖析
所有适配器都实现了一个统一的接口,核心是export函数。这个函数接收配置项(如时间窗口、输出目录),并返回一个Promise<void>。其内部工作流通常是:
- 发现:根据工具约定的路径模式,找到所有相关的文件。
- 过滤:根据修改时间、文件大小等条件过滤文件。
- 读取与解析:读取文件内容,并按照原始格式进行解析。
- 转换:将解析后的数据转换为一个或多个
CanonicalEvent对象。 - 序列化与写入:将属于同一会话或同一单元的
CanonicalEvent组合起来,通过serializeToMarkdown函数生成Markdown字符串,并写入到输出目录。
你可以参考src/adapters/claude-code.ts或src/adapters/codex.ts作为模板。关键的TypeScript类型定义在src/types.ts中。
7.2 为新工具编写适配器的步骤
假设我们要为一个名为 “ChatDevX” 的新CLI工具添加支持,它的会话记录存储在~/.chatdevx/history/*.chat文件中,每行是一个JSON对象。
- 创建文件:在
src/adapters/目录下创建chatdevx.ts。 - 实现接口:导入必要的类型和函数,实现
export函数。// src/adapters/chatdevx.ts import type { Adapter, ExportOptions } from '../types.js'; import { canonicalizeEvents, serializeToMarkdown, redactSecrets } from '../normalize.js'; import { discoverFiles, shouldProcessFile } from '../utils.js'; export const exportChatDevX: Adapter['export'] = async (options: ExportOptions) => { const sourceDir = `${process.env.HOME}/.chatdevx/history`; const files = await discoverFiles(`${sourceDir}/*.chat`, options.days); for (const file of files) { if (!(await shouldProcessFile(file, options))) continue; const content = await Bun.file(file).text(); const lines = content.split('\n').filter(l => l.trim()); const events = []; for (const line of lines) { try { const entry = JSON.parse(line); // 解析你的特定格式,转换为 CanonicalEvent events.push({ id: `${path.basename(file)}-${entry.timestamp}`, timestamp: new Date(entry.timestamp).toISOString(), source: 'chatdevx', role: entry.speaker === 'human' ? 'user' : 'assistant', // 映射角色 content: redactSecrets(entry.message), // 记得擦除秘密! metadata: { /* 其他有用信息 */ } }); } catch (e) { // 忽略解析错误的行 } } if (events.length > 0) { const markdown = serializeToMarkdown(events, { source: 'chatdevx', filePath: file, // ... 其他元数据 }); // ... 写入到 options.outDir } } }; - 注册适配器:在
src/adapters/index.ts文件中,将你的新适配器添加到adapters对象中。import { exportChatDevX } from './chatdevx.js'; export const adapters = { // ... 其他适配器 'chatdevx': { export: exportChatDevX }, }; - 更新CLI和文档:在
src/cli.ts中,将'chatdevx'添加到支持的源列表。同时,更新项目README中的支持列表。 - 测试:使用
bun run src/cli.ts discover和bun run src/cli.ts export --source=chatdevx --dry-run来测试你的适配器是否正常工作。
整个过程的核心在于理解新工具的存储格式,并稳健地将其映射到统一的CanonicalEvent格式。一旦完成,这个新工具的记忆就能无缝融入你的知识大脑了。
8. 与AI工作流深度集成:超越一次性导入
将历史记忆导入gBrain只是一个开始。真正的威力在于让这个“大脑”在你日常的AI协作中实时发挥作用。gBrain通过MCP协议提供了强大的实时查询和更新能力。
8.1 配置MCP服务器实现无缝交互
MCP(Model Context Protocol)正逐渐成为AI工具与外部服务通信的标准。gBrain内置了MCP服务器。
- 启动服务器:在gBrain项目目录下,运行
bun run src/cli.ts serve。这会启动一个MCP服务器,通过stdio与支持MCP的客户端通信。 - 在Claude Code中连接:这是最丝滑的体验。在Claude Code中,你可以通过MCP配置直接连接。
配置成功后,重启Claude Code。你会发现AI助手可用的工具列表中,多了诸如# 在终端中,为当前用户添加gbrain作为MCP服务器 claude mcp add gbrain -s user -- \ /path/to/.bun/bin/bun run /path/to/gbrain/src/cli.ts servegbrain_search、gbrain_query、gbrain_get_page等工具。AI可以直接调用这些工具来搜索你的知识库,无需你手动复制粘贴。
实际场景:当你正在编写一个新的API时,你可以对AI说:“查一下我们之前关于分页查询最佳实践的讨论。” AI会通过MCP调用gbrain_query工具,获取相关的历史对话片段,并基于此给出更贴合你团队上下文的建议。
8.2 为不支持MCP的工具提供CLI后备方案
不是所有工具都支持MCP。对于这些工具,你可以通过修改其系统提示词(CLAUDE.md,AGENTS.md或基础指令),教它们使用gbrain命令行。
在你的AI助手的基础指令中加入如下段落:
## 可用工具与知识库 你运行在一个开发者环境中,可以执行shell命令。 此外,你可以访问一个名为“gBrain”的个人知识库,其中存档了过往的开发对话、决策和笔记。 当你需要查询历史信息时,请使用以下命令: - **搜索关键词**: `gbrain search "关键词"` - **语义查询**: `gbrain query "你的自然语言问题"` - **获取特定页面**: `gbrain get <页面slug>` - **列出页面**: `gbrain list` 例如,如果你需要知道某个项目的部署流程,可以执行 `gbrain query "项目X的部署流程是什么?"` 来获取相关记录。这样,AI在需要历史上下文时,会主动执行这些命令,并将返回的文本作为参考。虽然不如MCP的结构化响应直接,但同样有效。
8.3 设计持续的记忆回流机制
目前,hermes-gbrain-bridge主要处理的是“历史批量导入”。但对于一个活的系统,你可能会希望新的对话也能自动进入知识库。
一个简单的方案是创建一个定时任务(Cron Job),定期(例如每天凌晨)运行桥接器,只导入过去24小时内新增或修改的记忆文件,然后触发gBrain的增量导入和嵌入。
# 一个示例的crontab条目 0 2 * * * cd /path/to/hermes-gbrain-bridge && bun run src/cli.ts export --source=all --out=/tmp/gbrain-new --days 1 && cd /path/to/gbrain && bun run src/cli.ts import /tmp/gbrain-new --no-embed && bun run src/cli.ts embed --stale这样,你的知识库就能保持“新鲜”,成为一个不断成长的、真正反映你全部AI协作经历的动态大脑。
