GBrain:面向 AI Agent 的个人/团队知识大脑 —— 架构、设计哲学与关键技术决策
摘要
GBrain 是一个为 AI Agent 构建的知识大脑系统。它不是一个笔记应用,也不是一个搜索引擎套壳——它在向量检索之外,把知识图谱、混合排序、内容合成与缺口分析焊在了一起。它用 PGLite(嵌入式的 WASM Postgres)做默认存储引擎,放大规模时切到 Postgres + pgvector,两套引擎在同一个 BrainEngine 接口下同步演进。本文从存储层、组织模型、摄入管线、检索栈、合成层、信任边界、Agent 集成七个维度逐层解剖其架构,并提炼出贯穿始终的设计原则:合约先行、信任默认关闭、引擎等价位、零 LLM 知识图谱、评估驱动。
1. 引言:为什么需要一个"大脑"而不是又一个搜索引擎
先看一个场景:你明天要和 Alice 开会。你打开某个个人知识工具,输入"Alice 最近在做什么"。它给你返回五个页面链接——你得自己打开、自己读、自己拼结论。这工具找到了材料,但它没干活。
GBrain 的思路不同。同样的问题,它输出的是:Alice 在 Acme 做工程主管,你们上次聊是 4 月 22 号,当时她欠你一份安全审查,你承诺了一个定价方案,她还说要招 CISO。每一项都有来源页面。末尾附一句:"最近六周大脑里没有 Alice 和 Acme 的新记录——她可能回过邮件或 Slack,那些渠道大脑看不到。"
这就是 GBrain 的基本立场:检索到页面不算完成任务,阅读页面并写出答案才算。Garry Tan(YC 总裁)在 README 里把它描述为"the next Postgres for memory"——不是说要替代 Postgres,而是说要像 Postgres 之于数据库那样,成为记忆和检索领域的基础设施级组件。这个野心不小,但读完整个架构之后,我觉得它背后有足够坚实的工程支撑。
本文的内容组织如下:第 2 节逐层解剖系统架构,第 3 节提炼跨层设计哲学,第 4 节讨论几个我认为最有争议也最有价值的技术决策,第 5 节做总结。
2. 逐层解剖
2.1 存储层:两套引擎,一份合约
GBrain 的存储层由两个引擎实现支撑,共享同一个 BrainEngine 接口。
PGLite 引擎是默认项。它把 Postgres 17.5 编译成 WASM,嵌在 Bun 进程里跑。gbrain init(不加参数)直接给你一个零配置的本地脑,不需要 Docker,不需要外部服务,两秒钟就能用。适合个人用户在单机上的场景,50K 页以内性能稳定。
但 PGLite 有一个硬限制:单连接。Postgres(即便是 WASM 版本)在单连接模式下只有一个写入者。所以 src/core/pglite-lock.ts 实现了一个基于文件系统 advisory lock 的互斥机制——mkdir 一个锁目录 + 心跳刷新,配合 PID 活性检查和一个 600 秒的 steal grace。两个进程不能同时打开同一个 PGLite 脑。这个设计很细致:它不依赖 PID 文件(因为 PID 会被回收),而是把 PID 检测和心跳刷新配对使用——如果锁持有者的 PID 不存在了,或者超过 grace 时间没刷新,等待者才能 steal。
Postgres 引擎通过 postgres.js 连接 Supabase 或自建的 Postgres + pgvector。支持多连接池、事务模式 PgBouncer、连接恢复。对于 1000+ 文件、多机器同步、团队共享的场景,这是推荐路径。
两套引擎的同步演进由 src/core/postgres-engine.ts 和 src/core/pglite-engine.ts 共同承担——一个新增的 BrainEngine 方法必须在两边同时实现,test/e2e/engine-parity.test.ts 做检查。SQL 层的差异(比如 PGLite 不支持 CREATE INDEX CONCURRENTLY)通过 sqlFor.pglite 分支处理。
这里有一个不易察觉但重要的设计约束:JSONB 写入必须通过 executeRaw 或 executeRawJsonb,传原始对象,不能用 JSON.stringify 转一道。因为 postgres.js 会对字符串做二次编码,而 PGLite 不会触发这个 bug。如果你只在 PGLite 上测试,代码可能默默通过,到了 Postgres 上就炸。项目里有一个 scripts/check-jsonb-pattern.sh 的 CI 守卫来拦截这个模式。
2.2 组织层:Brain ⊥ Source(两个正交轴)
GBrain 的组织模型用两条互相垂直的轴来切分知识空间:
- Brain(脑)= 哪个数据库。你的个人脑叫
host。你可以通过 gbrain mounts add 挂载其他脑(团队发布的、同事分享的),每个脑有自己的数据库和访问策略。
- Source(源)= 数据库里的哪个仓库。一个脑可以放多个源——wiki(个人笔记)、gstack(项目计划)、essays(文章)等等。Slug 在 source 级别唯一:
topics/ai 可以同时存在于 source=wiki 和 source=gstack,它们是不同的页面。
两条轴都走同一个 6 层解析链:命令行 flag → 环境变量 → .gbrain-mount/.gbrain-source dotfile → 路径最长前缀匹配 → 配置默认值 → 系统默认值。如果你懂了一条轴的解析规则,另一条完全一致。
这个双轴设计解决了一个实际问题:怎样让同一个 CLI 命令在 ~/brain/ 和 ~/team-brains/media-team/ 下自动路由到正确的数据库和源?答案就是 dotfile。你 cd 到团队脑的目录,.gbrain-mount 自动把脑切到 media-team;cd 到子目录,.gbrain-source 再精准到 research 源。整个过程不需要 flag。
跨脑查询不走 SQL federation——Agent 自己看脑列表,自己决定什么时候跨脑扇出,自己合成结果。设计者的取舍很清楚:SQL federation 在调试和访问控制上是噩梦,让 Agent 来做路由决策更干净。
2.3 摄入管线:从信号到页面到图谱
GBrain 的数据注入是一个环形管线:信号检测 → 脑优先查询 → 响应 → 写入 → 自动链接 → 同步。
信号检测器 在 Agent 的每条消息上运行。它从消息里抓出想法、实体引用、有时限的待办事项、名字和链接。这个模块不做 LLM 调用——纯规则匹配。
脑优先查询 在 Agent 调用任何外部 API 之前执行。设计者的判断是:你自己的脑是你最便宜、最快、最个性化的信息源。跳过它直接搜外网,等于放弃了你已经拥有的知识。
自动链接 是整条管线里我最欣赏的部分:每一次 put_page 写入,系统用三个正则表达式从 Markdown 正文里提取实体引用——标准链接 [name](path)、Obsidian wikilink [[path|name]]、以及类型化链接块引用。零 LLM token。提取出的边通过一次 addLinksBatch SQL 写入,使用 jsonb_to_recordset 做批量插入(而不是逐条 INSERT,避免了连接开销和参数上限问题)。
边类型推断(attended、works_at、invested_in、founded、advises)从上下文句子中提取——同样不调用 LLM。做这个决定的理由很务实:要让知识图谱在每一次写入时自动生长而不产生 API 费用。一个 17K 页的脑,全量图谱提取在几秒内完成。
同步管线 的 resumability 是另一个值得细看的设计。gbrain sync 通过 op_checkpoint_paths 表做追加式进度记录——每处理完一个文件就写一行。如果进程被 kill,下次启动从最后一条 checkpoint 恢复。锁的生命周期通过直接连接池心跳刷新,配合 GBRAIN_LOCK_STEAL_GRACE_SECONDS 防止误 steal 一个还在运行的慢持有者。五个环境变量(GBRAIN_SYNC_CHECKPOINT_EVERY 等)提供事故时的逃生舱口,但在日常使用中完全不需要碰。
2.4 检索栈:四层策略的叠加效应
GBrain 的检索系统不是一套算法,是四件事叠在一起:
向量检索(HNSW + pgvector) 负责语义相似。用户搜"谁在做 AI agent",能命中讨论了"agent 框架"但从来没提"AI"这个词的页面。
BM25 关键词检索 负责精确匹配。名字、专有名词、代码标识符。那些向量检索会漂移到近义邻居上的情况,BM25 兜底。
倒数秩融合(RRF) 把向量和关键词的排名合并。不做全局加权——每种策略平等投票。src/core/search/hybrid.ts 里的 rrfFusionWeighted 支持配置权重,默认各 0.5。
知识图谱遍历 是真正让分数跳升的东西。BrainBench 的评测数据:纯向量 RAG 的 P@5 约 18%,关了图谱的 hybrid(向量+关键词+RRF)也是约 18%,但开启完整栈(四层叠加)后 P@5 跳到 49.1%,R@5 达到 97.9%。+31.4 个百分点的 P@5 提升——这差距证明了向量相似不等于事实相关。
在这四层之上,还有两个附加信号:
图信号(v0.42.34.0) 在 post-fusion 阶段做三种微调:邻接提升(一个页面被 2+ 个其他 top-K 结果链接——它对这个查询是本地枢纽)、跨源提升(被 2+ 个不同 source 的页面链接——跨团队确认)、会话降权(同一会话产出 3+ 个结果时保留最高分、压低其余)。三种信号都套了 floor-ratio gate——弱页面不会因为"流行度"超过强页面。
重排序器(ZeroEntropy zerank-2) 在 balanced 和 tokenmax 模式下默认开启。一次 cross-encoder 重排能翻掉 60% 的 top-1 结果——意味着 hybrid + RRF + 图谱三层在局部最优,但全局上看仍有很多排错的情况。zerank-2 读查询和候选文档的联合注意力,把那些"语义相关但主题错误"的文档踢下去。延迟 +150ms p50,约 \(0.025/M token——对于下游还要跑 LLM 的 Agent loop 来说,这部分延迟完全被后面的推理时间淹没。
SQL 层还有一套基于 source 的排名权重(`src/core/search/sql-ranking.ts`):精选内容(`originals/`、`concepts/`)权重高于批量内容(`chat/`、`daily/`),`test/` 和 `.raw/` 被硬排除。`archive/` 不硬排除——仓库里有重要历史内容,采用 `0.5x` 降权而非隐藏。
三种搜索模式(`conservative` / `balanced` / `tokenmax`)把上述组件打包成三个配置预设。保守模式约 4K token 预算、不开扩展查询和关系检索;tokenmax 模式无 token 预算、开 LLM 多查询扩展和关系检索、搜索上限翻到 50。从保守模式配 Haiku(\)40/月)到 tokenmax 配 Opus($1000/月),成本跨度 25 倍——所以安装时 gbrain init 会弹出一个成本矩阵让用户选,而不是静默接受默认值。
2.5 合成层:从检索到答案
gbrain think 不是对检索结果做简单的模板拼接。它在检索之后运行几件事:
- 证据标记(evidence stamp):每个结果带上为什么被检索到的说明,以及
create_safety 提示(exists/probable/unknown),让 Agent 知道这个页面是否已经存在,而不是从原始分数去猜。
- 轨迹分析(trajectory):当实体页面有结构化的 metric claim(
metric: mrr、value: 50000、unit: USD 这样的列),find_trajectory 操作能拉出时序历史并自动标记回归。
- 缺口分析(gap detection):答案末尾会诚实地说脑里缺什么——哪个页面过期了、哪条断言没引用、哪两页互相矛盾、哪里有一个你该补上的洞。这是 GBrain 和其他检索系统最大的区别:它告诉你它不知道的事情。
内容质量方面有一层 voice-gate。五个用户界面(pattern_statement、nudge、forecast_blurb、dashboard_caption、morning_pulse)的输出都通过 gateVoice() 过滤器——用 Haiku 做裁判,拒绝学术腔,最多两次重生成,兜底到手工模板。设计原则是:GBrain 说话要像一个知道你的过去的聪明朋友,不是一个临床评分系统。25 词以内的叙事语句,不用"我们建议",不用"根据你的数据",数字要落到实际结果上而不是堆抽象指标。
2.6 信任边界与安全模型
信任在 GBrain 的设计里是 fail-closed 的——这个选择出现在几乎所有重要路径上。
OperationContext.remote 字段是信任边界的单一载体。本地 CLI 调用者(src/cli.ts)设 remote: false;通过 MCP HTTP 进来的调用者(src/mcp/server.ts)设 remote: true。安全检查点全部用 ctx.remote === false(仅信任明确标记为本地)或 ctx.remote !== false(凡是不是明确本地的都当远程处理)。这不是防御性编程——这是把"未标记 = 不信任"写进了类型系统。
这个边界守卫了多个高风险路径:
- 文件上传:
remote=true 时收紧文件系统限制。
- 页面写入:
put_page 在 viaSubagent + allowedSlugPrefixes 时做 allowlist 检查。
- shell 作业提交:保护作业名称不被远程伪造。
- 自动链接:远程 + 不受信任的工作区跳过自动链接。
源隔离走 sourceScopeOpts(ctx) 的优先级梯子:联邦数组(ctx.auth.allowedSources)> 标量(ctx.sourceId)> 空。所有读侧操作都通过它路由,一个 scope-bound 的 OAuth 客户端不会通过 search/query/list_pages 看到邻近源的数据。
OAuth 层面,HTTP 服务器提供 DCR-style 的客户端注册 + OAuth 2.1 + scope 门控(read/write/admin)+ 速率限制。mcp_spend_log 表做按日 UTC 对齐的付费 API 支出追踪,checkBudget 在调用前做预检。本地 CLI 用户跳过预算检查——没有 clientId 就走不掉计费路径。
在安全信息披露方面,项目奉行"功能性描述,不列举攻击面"的策略。CHANGELOG 不写"10 张表被 anon key 公开可读,包括 X、Y、Z",而是写"安全加固 pass,新安装默认安全,存量脑升级自动同步"。测试标准是:一个没有先验知识的读者读了发布说明之后,能不能列出需要探测的具体表名?如果能,说明太具体了。
2.7 Agent 集成层:MCP 协议与技能包
GBrain 通过 MCP 协议暴露 30+ 个工具,支持 stdio 和 HTTP 两种传输。stdio 模式一行命令 claude mcp add gbrain -- gbrain serve——零服务器、零隧道、零 token。HTTP 模式支持 OAuth 2.1 + bearer token + 管理面板,适用于远程部署。
瘦客户端(thin-client)架构值得单拎出来讲。gbrain init --mcp-only 创建的安装没有任何本地脑内容,只有一个指向远程 gbrain serve --http 的 OAuth 客户端。关键是 CLI 里的路由分支:在 connectEngine() 之前先检测 isThinClient(cfg),如果是瘦客户端就通过 callRemoteTool 走远程 MCP,永远不打开空的本地 PGLite——这种情况在 v0.31.1 之前会静默返回"No results."。
OpenClaw 上下文引擎在 Agent 每轮对话的上下文组装阶段运行。它从当轮消息里提取实体候选(纯规则,零 LLM),然后通过别名匹配或精确标题匹配把候选解析成脑内页面指针。这些指针附带 source_id、匹配置信度、匹配 arm(别名 0.9 / 标题 0.8 / slug 后缀 0.6),注入到对话上下文。整个解析过程是确定性的、零 LLM 的——保证 Agent 在每次对话开始时知道哪些脑内页面和当前话题相关。
项目附带 43 个技能(skill),都是 Markdown 文件——工具无关,CLI 和插件上下文都能用。skills/RESOLVER.md 做了两层分发:主路由器列出功能领域,每个领域声明自己负责的子技能。这个设计解决了大型 AGENTS.md 膨胀的问题——在一个真实 fork 上,把 25KB 的路由文件压缩到 13KB(48%),而路由准确率在 Opus/Sonnet/Haiku 上反而提升了 13-17 个百分点。
3. 贯穿的设计哲学
读完架构文档和源代码之后,有几条设计原则反复出现。它们不是写在某个 README 里的口号——每一条都能在代码里找到对应的强制机制。
3.1 合约先行(Contract-First)
src/core/operations.ts 定义了约 90 个共享操作,每个操作带有 scope: 'read'|'write'|'admin' 和可选的 localOnly 标记。CLI 和 MCP 服务器都从这个单一源生成。HTTP 分发在 handler 运行前强制执行 scope/localOnly 检查。
这意味着什么?如果你在 operations.ts 里加了一个操作,CLI 和 MCP 自动就有了它。不存在"CLI 支持但 MCP 忘了加"或者反过来。操作描述的字符串常量(如 QUERY_DESCRIPTION)抽到了独立的 operations-descriptions.ts,更新描述不会动契约主体。
3.2 信任默认关闭(Fail-Closed Trust)
这条原则贯穿了信任边界、源隔离、费用控制、安全信息披露。没有一个地方是"默认允许,遇到问题时再收紧"。所有安全敏感路径都是"默认拒绝,显式标记才能通过"。
ctx.remote !== false(而不是 ctx.remote === true)是这条原则的类型级表达:任何不是明确 false 的东西都当远程处理。HTTP MCP shell-job RCE 漏洞(一个 read+write OAuth token 可以提交 shell 作业)就是这样被堵住的——保护作业名称的检查用了完全相同的 fail-closed 模式。
3.3 引擎等价位(Engine Parity)
PGLite 和 Postgres 引擎在所有 BrainEngine 方法上同步演进。新增方法必须在两边同时实现。test/e2e/engine-parity.test.ts 做检查。test/schema-bootstrap-coverage.test.ts 确保前向引用的列/索引被 bootstrap 探针覆盖。
这条原则的实践意义是:你在 PGLite 上开发的测试不会给你错误的信心——如果代码在 PGLite 上通过但在 Postgres 上会炸(比如 JSONB 双重编码),CI 会发现。反过来,PGLite 独有的限制(单连接、不支持 CREATE INDEX CONCURRENTLY)通过 sqlFor.pglite 分支透明处理,不影响 Postgres 路径。
3.4 零 LLM 知识图谱(Zero-LLM Graph)
知识图谱的自动链接完全依靠正则匹配,不使用 LLM。这不是因为 LLM 不好用——而是因为 LLM 在每一次页面写入时都调用的话,费用会失控。一个 17K 页的脑如果每次写入都触发一次 LLM 调用来提取关系边,累积成本会很可观。三个正则表达式 + 一次批量 SQL,总耗时秒级,零 API 开销。
同样的零 LLM 原则也应用在实体解析(上下文引擎)和重启排序预处理上。LLM 被保留在真正需要语义理解的环节——查询扩展、合成、重排序、缺口分析。
3.5 评估驱动(Eval-Driven)
GBrain 的评测框架不是事后加上的。BrainBench 在 gbrain-evals 仓库里跑 240 页 Opus 生成的丰富语料库,测量 P@5、R@5、MRR、nDCG@5。gbrain eval longmemeval 跑公开的 LongMemEval 基准。gbrain eval export + gbrain eval replay 捕获真实查询并重放——这个机制让检索变更可以被 A/B 测试,而不是靠直觉判断。
每一个 gbrain eval * 或 gbrain search stats 命令打印的指标,都通过 src/core/eval/metric-glossary.ts 解析成术语(P@k、nDCG@k、MRR、Jaccard@k),人类输出里带一行中文解释,JSON 输出里带 _meta.metric_glossary 块。
3.6 Markdown 是通用表示
GBrain 不发明自己的存储格式。所有知识以 Markdown 文件存在 git 仓库里。数据库是从 Markdown 同步进去的索引层。删除 git 里的文件 = DB 里的软删除。这个设计带来几个好处:你可以用任何编辑器浏览脑内容;git 历史是天然的审计日志;备份就是 git push。
4. 值得讨论的设计决策
4.1 为什么 PGLite 是默认引擎?
在"每个人都该装一个 Postgres"和"用 SQLite"之间,GBrain 选了第三个选项:把 Postgres 编译进 WASM。这个选择很务实——90% 的单人用户不需要独立数据库服务,但他们需要一个有完整 Postgres 功能的本地脑。Postgres 的 HNSW 索引、tsvector 全文搜索、递归 CTE(图谱遍历的关键)在 PGLite 里都可用。
代价是:单连接。一个 gbrain serve 长期运行时会占用写入锁,大同步需要先停掉服务。设计者在 README 的 troubleshooting 部分也坦言了这点。
4.2 为什么 source-aware ranking 不改架构而改 SQL?
检索排名不是在整个 pipeline 里统一加权的,而是通过一个 SQL CASE 表达式在 SQL 层按 source 前缀分配不同的权重系数。这是个很"脏"的决策——把业务逻辑放在 SQL 里面——但它避免了在应用层传入大量元数据、也避免了查询在返回后再做二次过滤的性能开销。archive/ 的 0.5x 降权而非硬排除也是个实用的折中:历史内容有信号价值,不能完全隐藏,但也不该和当前内容同等对待。
4.3 为什么 LLM 参与的路径都用 fail-open?
查询扩展(expansion)、LLM 意图分类(llm-intent.ts)和跨模态路由(cross-modal)在 LLM 调用失败时都走 fail-open——返回原始查询、返回 fallback、返回默认文本模式。设计理由很清楚:检索的可用性比一个偶发的 LLM 优化更重要。用户搜东西时不应该因为 API 超时而看到报错。
4.4 跨脑查询为什么是 Agent 的任务而不是 SQL 的?
所有跨脑查询走 Agent 扇出,而不是数据库 federation。这个决策在架构上是对的。SQL federation(跨库 JOIN)在调试时是地狱——你不知道为什么这条数据没出现、那对矛盾怎么来的。让 Agent 自己查看脑列表、自己决定查询哪个脑、自己合成结果,把故障隔离在每个脑的边界之内。访问控制也更干净:每个脑有自己的 OAuth 面,Agent 带着不同的 token 访问不同的脑。
5. 结论与展望
GBrain 是一套在架构上相当严整的系统。它的核心价值不在某个单一算法或功能上,而在于把向量检索、关键词匹配、知识图谱、内容合成、缺口分析装进了一个自洽的框架里。合约先行保证了 CLI 和 MCP 的行为一致性;fail-closed 信任模型让安全边界可以被类型系统检查;零 LLM 知识图谱让图谱在每次写入时自动生长而不烧钱;评估驱动的迭代让检索变更可以被量化和回放。
它的"next Postgres for memory"定位目前看不算夸张。对于单人用户,一个零配置的本地脑确实做到了像 Postgres 一样随手可得;对于团队,多脑挂载和 scope-gated OAuth 提供了团队共享的基础能力。BrainBench 上 +31.4 P@5 的图谱加持相对于纯向量 RAG 的提升,也说明混合检索栈的设计经住了评测。
当然它也有边界。PGLite 单连接限制意味着大规模本地部署下需要从 CLI 和 MCP 服务之间协调写入;瘦客户端对网络延迟敏感;技能体系虽然丰富但安装成本和概念负担对新用户偏高。但这些问题更多是工程成熟度层面的,不触及核心架构的合理性。
从更广的视角看,GBrain 代表了一个趋势:AI Agent 不再满足于"把相关文档片段塞进上下文窗口"——它们需要结构化的事实、需要知道自己不知道什么、需要在没被问到的时候主动推送相关信息。GBrain 的 Retrieval Reflex(上下文引擎)和 volunteer_context 推送操作是这个方向的早期信号。个人知识管理正从"记下来以后搜索"变成"系统替你记得并替你思考"——GBrain 是这条路上一个扎实的工程样本。
参考文献
- GBrain 项目仓库,https://github.com/garrytan/gbrain
- CLAUDE.md — 架构总览与跨切面不变量
- docs/architecture/brains-and-sources.md — Brain ⊥ Source 双轴组织模型
- docs/architecture/RETRIEVAL.md — 混合检索 + 知识图谱的技术原理
- docs/architecture/topologies.md — 部署拓扑
- docs/architecture/thin-client.md — 瘦客户端与远程 MCP 路由
- docs/architecture/KEY_FILES.md — 逐文件索引与行为约束
- DESIGN.md — 设计系统与用户界面语言
- skills/conventions/brain-routing.md — Agent 面向脑路由决策表
- skills/RESOLVER.md — 技能路由分发器
- src/core/operations.ts — 合约先行操作定义
- src/core/engine.ts — 可插拔引擎接口
- src/core/search/hybrid.ts — 混合检索与 post-fusion 处理
- src/core/search/graph-signals.ts — 图信号增强
- gbrain-evals — BrainBench 评测基准,https://github.com/garrytan/gbrain-evals