Beads 架构解析:基于 Dolt 版本化存储的工作图数据库设计与同步机制
【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads
Beads 是一款为编码 Agent 提供持久记忆的依赖感知型问题跟踪系统,其全部数据都存放在Dolt——一个原生提供 git 语义(分支、合并、diff、push、pull)的版本化 SQL 数据库中。本文以 docs/architecture/index.md 为骨架,结合 docs/architecture/dolt.md 及仓库源码(internal/storage、internal/types、cmd/bd),系统讲解 Beads 的存储布局、数据模型、读写与同步路径、两种部署模式(嵌入式/服务器模式)、目录结构、恢复模型与设计取舍,帮助读者完整理解 Beads 如何用"版本化 SQL"为多 Agent 协作提供离线优先、冲突友好的持久工作记忆。
总体架构:Dolt 作为唯一存储后端
Beads 的架构核心是一句话:Dolt 是唯一存储后端(sole storage backend)。Dolt 是一个版本受控的 SQL 数据库,在数据库层面原生提供类 git 语义(branch、merge、diff、push、pull)。Beads 不自己实现数据文件格式,而是把全部 issue 数据放进 Dolt,从而免费获得完整版本历史、字段级合并与原生分支能力。
从 docs/architecture/index.md 中的架构图可见两个核心组成部分:
- Dolt 数据库(本地):默认以嵌入式模式运行(进程内,无独立服务),数据落在
.beads/embeddeddolt/;多写入者场景(多 Agent、编排器)切换为服务器模式,连接一个运行中的dolt sql-server,数据落在.beads/dolt/。 - Dolt 远程仓库(远程):通过
bd dolt push/bd dolt pull与 DoltHub、S3、GCS 等远程进行同步与备份,支持离线开发、异地恢复。
交互链路很简单:用户通过bd create/bd update写入数据库,通过bd list/bd show读取。每一次写入都会自动提交到 Dolt 历史,形成数据库层面的完整版本控制;恢复也简单——用bd dolt pull从远程拉取,或用bd backup restore从 Dolt 原生备份恢复。
为什么选择 Dolt?
原文档列出六条核心理由,可直接作为选型依据:
- 版本化 SQL:完整 SQL 查询 + 原生版本控制;
- 单元格级合并(Cell-level merge):并发修改在字段层面自动合并,而非整文件覆盖;
- 多写入者:服务器模式支持并发 Agent 同时写入;
- 原生分支:Dolt 分支独立于 git 分支;
- 离线可用:所有查询都在本地数据库执行;
- 可移植:
bd export生成 JSONL 用于迁移与互操作。
在 docs/architecture/index.md 的"Design Decisions"一节中,原文档进一步对比了替代方案:相比普通 SQLite(二进制级合并冲突)和 JSONL(查询慢),Dolt 同时提供快速 SQL 查询与正确的合并语义。
从源码看,这一选型贯穿整个存储层。backend/backend.go中将存储引擎接口显式命名为DoltStorage(type DoltStorage = storage.DoltStorage),并注明"该名称是历史性的——Dolt 存储是其首批实现——但契约本身与后端无关",说明整个引擎接口是为 Dolt 语义设计的,且对外暴露了可插拔的后端注册机制(backend.Register(name, backend),注册名"dolt"被保留)。仓库内internal/storage/下约 980 个 Go 文件、150 个 SQL 文件全部服务于这套存储实现。
数据模型:五类记录与内容派生 ID
数据库存储五类记录:
- Issues——即 beads 本身,一个可跟踪的工作单元;
- Dependencies——类型化边,如
blocks、parent-child、related、discovered-from; - Labels——标签;
- Comments——评论;
- Events——审计追踪(audit trail)。
bd ready正是基于这些记录(尤其是依赖关系)计算"可认领工作前沿"(claimable frontier):打开且没有打开状态阻塞者的 beads。概念模型(beads、dependencies、ready work、molecules)详见 docs/core-concepts/index.md。
哈希 ID:并发写入者永不冲突
Issue ID 是内容派生的哈希(如bd-a1b2),而非自增序号。这样并发写入者不会产生 ID 冲突,也无需中央 ID 协调。设计细节见 docs/core-concepts/hash-ids.md。
源码印证了这一机制。internal/types/id_generator.go中的GenerateHashID从以下输入计算 SHA-256:
// internal/types/id_generator.go func GenerateHashID(prefix, title, description string, created time.Time, workspaceID string) string { h := sha256.New() h.Write([]byte(title)) h.Write([]byte(description)) h.Write([]byte(created.Format(time.RFC3339Nano))) h.Write([]byte(workspaceID)) hash := hex.EncodeToString(h.Sum(nil)) return hash }- 标题(主标识符)、描述、创建时间戳(RFC3339Nano 精度)、工作区 ID 共同参与哈希,其中工作区 ID 用于防止跨工作区冲突;
- 默认取 6 字符短哈希(如
bd-a3f2dd),碰撞时渐进扩展到 7、8 字符(bd-a3f2dda、bd-a3f2dda8); - 代码注释给出了碰撞概率估算:6 字符(24 位)下 1000 个 issue 约 2.94% 概率触发扩展,10000 个 issue 约 94.9% 概率扩展到 7–8 字符,该渐进策略优化了常见场景(约 97% 停留在 6 字符)。
层级 ID 由GenerateChildID(parentID, childNumber)生成,格式为parent.N(如bd-a3f8e9.1、bd-a3f8e9.1.2),最大嵌套深度 3 层。对碰撞概率的生日悖论分析可参考仓库中的 engdocs/COLLISION_MATH.md。
Issue Schema:核心字段与工作流字段组
原文档给出了bd exportJSONL 中每条 issue 的核心字段表,现完整保留并补充说明:
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 唯一哈希 ID(如bd-a1b2) |
title | string | 标题(必填) |
description | string | 详细描述(可选) |
design | string | 设计说明(可选) |
acceptance_criteria | string | 验收标准(可选) |
notes | string | 附加说明(可选) |
status | string | open、in_progress、blocked、deferred、closed、pinned、hooked(默认open;可通过status.custom配置键扩展) |
priority | int | 0–4,0 = 关键(critical),4 = 积压(backlog) |
issue_type | string | bug、feature、task、epic、chore、decision、message、molecule、gate、spike、story、milestone(默认task) |
assignee | string | 指派的用户/Agent(可选) |
estimated_minutes | int | 预估耗时(分钟,可选) |
created_at/updated_at | RFC3339 | 创建与最后修改时间 |
created_by | string | 创建者(可选) |
closed_at/close_reason | RFC3339 / string | 关闭时设置(可选) |
external_ref | string | 外部引用,如gh-9、jira-ABC(可选) |
metadata | JSON | 任意扩展数据,见 docs/core-concepts/metadata.md |
labels | []string | 附加标签(可选) |
dependencies | []Dependency | 指向其他 issue 的类型化边(可选) |
comments | []Comment | 讨论线程(可选) |
Issue 还携带若干工作流层字段组:调度类(due_at、defer_until)、认领租约类(lease_expires_at、heartbeat_at)、门控类(await_type、await_id、timeout)、molecule/wisp 类(ephemeral、mol_type、bonded_from)。
internal/types/types.go中的Issue结构体与上述字段一一对应,并揭示了几个导出字段表未展开的实现细节:
ContentHash string \json:"-"``——对 issue 规范内容的 SHA-256,用于变更检测,永不出现在导出中;SourceRepo、IDPrefix(json:"-")——内部路由字段,不随 git 同步;RowVersion int64 \json:"-"`——不透明乐观并发令牌,封装 issue/wisp 行的row_lock` 单元格,每次状态/归属变更写入时被引擎重写,供 Go 调用方做等值比较(仅可比较、不可排序或解释);- 内部字段
content_hash、source_repo、id_prefix均不进入导出(json:"-"),与文档"Internal fields never appear in exports"一致。
Schema 默认保持稳定:集成方、编排器或团队专属数据应优先使用metadata字段(任意合法 JSON 均可存放),而不是提议新增一等字段。这一边界在仓库 engdocs/PROJECT_CHARTER.md("schema boundary" 一节)中有明确约束。状态与类型的合法取值集合也可以在 backend/types.go 中看到完整枚举:StatusOpen/InProgress/Blocked/Deferred/Closed/Hooked/Pinned,以及TypeBug/Chore/Decision/Epic/Event/Feature/Gate/Message/Milestone/Molecule/Spike/Story/Task。
数据流:写入、读取与同步三条路径
原文档给出了三条核心数据流,这里完整保留:
写入路径(Write Path)
User runs bd create → Dolt database updated → Auto-committed to Dolt history读取路径(Read Path)
User runs bd list → Dolt SQL query → Results returned immediately同步路径(Sync Path)
User runs bd dolt push → Commits pushed to Dolt remote User runs bd dolt pull → Remote commits fetched and mergedDolt 远程可以位于 DoltHub、S3、GCS、文件系统路径,甚至是你现有的 git 远程——issue 历史挂在refs/dolt/data下,与代码分支相互独立。这意味着同一个 git 仓库可以同时承载源码与 issue 历史。跨仓库场景还可以通过**联邦(federation)**在 peer 之间直接交换 beads,详见 docs/multi-agent/federation.md;临时的wisps(挥发性 molecule)默认被排除在联邦推送之外,因此执行痕迹不会进入共享历史,详见 docs/workflows/wisps.md。
跨机器同步注意事项
在多个机器/克隆之间工作时,原文档给出三条黄金规则:
- 切换机器前务必先同步:
bd dolt push # 离开前推送变更 - 在新机器上创建 issue 前先拉取:
bd dolt pull # 先在新机器上拉取变更 bd create "New issue" - 避免并行编辑——如果两台机器在未同步的情况下同时创建 issue,Dolt 的单元格级合并会自动处理大多数冲突。
多机器工作流中防数据丢失的完整预案(Pattern A5/C3)见 docs/recovery/sync-failures.md。该 runbook 给出了标准修复序列:bd dolt stop→ 检查/清理锁文件 →cp -r .beads .beads.backup备份 →bd doctor --dry-run预览 →bd doctor --fix修复 → 重启服务器 →bd dolt push/bd doctor验证。
需要特别澄清的一点(来自 docs/core-concepts/sync-concepts.md):.beads/issues.jsonl只是一个被动导出,供查看器、互换、迁移和备份使用,它不是数据库、不是同步协议、更不是备份。不要用常规的bd import .beads/issues.jsonl替代bd dolt pull——JSONL 导入是 upsert-only 语义,无法推断导出中缺失的记录是被删除、修剪还是从未导出过。
部署模式:嵌入式 vs 服务器模式
Beads 提供两种 Dolt 部署模式,选择依据是写入者数量:
| 模式 | 初始化命令 | 数据位置 | 写入者 |
|---|---|---|---|
| 嵌入式(默认) | bd init | .beads/embeddeddolt/ | 单写入者(文件锁) |
| 服务器模式 | bd init --server | .beads/dolt/ | 多写入者并发 |
嵌入式模式(无需服务器)
嵌入式模式是默认行为(bd init不带任何 flag):Dolt 以进程内方式运行,单写入者,数据在.beads/embeddeddolt/——没有独立服务器进程,也不需要单独安装 Dolt。模式选择持久化在.beads/metadata.json中。
bd create "CI-generated issue" bd dolt push原文档特别指出,除了单人使用外,嵌入式模式天然适合:
- CI/CD 管道(Jenkins、GitHub Actions);
- Docker 容器;
- 临时环境(ephemeral environments);
- 不应残留后台进程的脚本。
从 docs/architecture/dolt.md 可以确认:bd二进制内嵌了 Dolt 引擎(版本由go.mod决定,当前对应上游 v2.2.0 tag),因此嵌入式模式"零运维:无服务器、无端口、无 PID 文件"。嵌入式模式通过文件锁强制单写入者——若出现 "database is locked" 错误,说明有并发访问需求,应切换到服务器模式。
服务器模式(多写入者 / 编排器)
服务器模式连接一个运行中的dolt sql-server,支持多客户端并发访问:
# 启动服务器(编排器侧) gt dolt start # 或手动启动 cd ~/.dolt-data/beads && dolt sql-server --port 3307# 以服务器模式初始化 bd init --server # 或通过环境变量切换 export BEADS_DOLT_SERVER_MODE=1# .beads/config.yaml (server mode settings) dolt: mode: server host: 127.0.0.1 port: 3307 user: root连接参数可通过 flag 或环境变量配置(完整对照见 docs/architecture/dolt.md):
| Flag | 环境变量 | 默认值 |
|---|---|---|
--server-host | BEADS_DOLT_SERVER_HOST | 127.0.0.1 |
--server-port | BEADS_DOLT_SERVER_PORT | 3307 |
--server-socket | BEADS_DOLT_SERVER_SOCKET | (无;默认走 TCP) |
--server-user | BEADS_DOLT_SERVER_USER | root |
BEADS_DOLT_PASSWORD | (无) |
Unix 域套接字:使用--server-socket可通过 Unix socket 而非 TCP 连接。这能避免并发项目之间的端口冲突,在沙箱环境(如 Claude Code)中尤其有用——文件级访问控制比网络白名单更简单。注意 Dolt 服务器必须以dolt sql-server --socket <path>方式启动,且 socket 模式不支持自动启动。
何时切换到服务器模式:
- 多个 Agent 同时写入;
- 编排器多机架(multi-rig)部署;
- 与远程 peer 进行联邦(federation)。
共享服务器模式(Shared Server)
可选的高级部署形态:在所有项目之上运行单个Dolt 服务器(~/.beads/shared-server/),各项目通过前缀隔离的独立数据库共享它。启用方式:
# 通过 config.yaml 键为本项目启用 bd config set dolt.shared-server true # 或通过环境变量机器级启用 export BEADS_DOLT_SHARED_SERVER=1 # 或在 init 时启用 bd init --prefix myproject --shared-server共享模式的好处:项目间无端口冲突(单一服务器跑在 3308 端口,避开编排器的 3307)、资源占用低(一个进程替代多个)、数据库自动隔离(每个项目使用自己的数据库名)。关键约束:共享服务器上的每个项目必须使用唯一前缀(数据库名)——若两个项目恰好同前缀,项目身份检查会检测到不匹配并拒绝连接,防止静默数据损坏。配置键dolt.shared-server的合法性校验(仅接受"true"/"false")可在 internal/config/yaml_config.go 中找到。完整机制见 docs/architecture/dolt.md 的 "Shared Server Mode" 一节。
多克隆场景的竞态风险
原文档以警告形式强调了一个关键坑:同一仓库的多个 git 克隆同时执行同步操作时,push/pull 期间可能发生竞态条件。这在以下场景尤其常见:
- 多 Agent AI 工作流(多个 Claude/GPT 实例);
- 拥有多个 checkout 的开发者工作站;
- 基于 worktree 的开发工作流。
预防措施:
- 在克隆之间切换前停止 Dolt 服务器(
bd dolt stop); - 服务器模式下 Dolt 原生支持 worktree;
- 自动化工作流使用嵌入式模式。
竞态问题的专项排查(Pattern B2)见 docs/recovery/sync-failures.md。
目录布局:.beads/里有什么
原文档给出了完整的目录布局,数据位置随模式而异:
.beads/ ├── embeddeddolt/ # Dolt 数据库(嵌入式模式,默认)— gitignored ├── dolt/ # Dolt 数据库(服务器模式)— gitignored ├── dolt-server.pid # 服务器模式运行时文件(.pid、.log、.port)— gitignored ├── issues.jsonl # 被动 JSONL 导出,供查看器与数据互换使用 ├── metadata.json # 后端配置 — 受 git 跟踪 └── config.yaml # 项目配置(可选)— 受 git 跟踪要点解读:
- 只有数据库目录(随模式二选一)真正持有 issue 数据;其余都是配置、运行时状态或派生导出;
bd init会写入.beads/.gitignore,把数据库和运行时文件排除在 git 之外;- 服务器模式运行时文件直接位于
.beads/下:dolt-server.pid、dolt-server.log、dolt-server.port(cmd/bd的测试与 doctor 修复逻辑中对这些文件名有直接引用,例如 cmd/bd/doctor/fix/remotes.go 处理 stale 的dolt-server.port); issues.jsonl是导出而非数据库本体(详见前文"同步路径"一节的澄清);metadata.json记录后端类型与模式选择(bd init --server的选择就持久化在这里)。
恢复模型:版本控制让恢复变简单
Dolt 的版本控制使数据恢复路径异常直接,原文档的三条主恢复路径完整保留:
- 数据库丢了?→ 从 Dolt 远程拉取:
bd dolt pull - 有备份?→ 恢复它:
bd backup restore [path] --force - 合并冲突?→ Dolt 原生处理单元格级合并
备份用bd backup init(目标可为文件系统路径或 DoltHub)创建、bd backup sync推送。Dolt 原生备份保留完整提交历史;JSONL 导出不保留。bd export不能替代这一流程——JSONL 只含 issues 表记录,不含 Dolt 分支、完整提交历史、工作集状态或其他表。
通用恢复序列
以下序列可解决大多数已报告问题(详细流程见 docs/recovery/index.md):
bd dolt stop # 停止 Dolt 服务器(防止竞态条件) git worktree prune # 清理孤儿 worktree bd dolt pull # 从 Dolt 远程拉取 bd dolt start # 重启服务器谨慎使用bd doctor --fix
bd doctor --fix是强力工具,使用前务必先备份与预览:
- 先备份:
cp -r .beads .beads.backup - 预览变更:
bd doctor --dry-run—— 显示将要修复的内容而不实际修改 - 查看诊断:
bd doctor(不带 flag)—— 仅诊断,不做任何修改 - 然后修复:
bd doctor --fix—— 或bd doctor --fix -i逐条确认每个修复
为何需要谨慎?--fix可能会删除它判定为环形的依赖(包括合法的父子关系)。只有当你确信被标记的依赖确实无效时,才使用--fix-child-parent。
其他诊断工具:
bd blocked—— 检查哪些 issue 被阻塞及原因;bd show <issue-id>—— 检查某个 issue 的具体状态。
bd doctor的功能面比这里展示的更广。从 docs/cli-reference/doctor.md 可见其检查项包括:.beads/目录存在性、数据库版本与迁移状态、Schema 兼容性(必需的表和列齐全)、哈希 ID 与顺序 ID、CLI 版本、git hooks、.gitignore状态等;并支持--perf(性能诊断)、--deep(全图完整性验证,如父子依赖指向存在的 issue、依赖引用有效、epic 完整性、molecule 结构等)、--server(服务器模式健康检查)等多种模式。
专项恢复 runbook 还包括:
- 数据库损坏:docs/recovery/database-corruption.md(症状:命令报错、"database is locked"、issue 缺失、状态不一致;预防:让 Dolt 服务器统一处理同步、系统关机前
bd dolt stop、定期bd doctor); - 合并冲突:docs/recovery/merge-conflicts.md(症状:
bd dolt pull报冲突、克隆间 issue 状态不一致;修复:备份 →bd doctor→bd doctor --fix→bd list/bd stats验证 →bd dolt push;预防:工作会话前后用bd dolt pull/bd dolt push同步,避免无服务器时多克隆并发修改)。
存储空间回收与维护
数据库增长到一定程度后需要维护(见 docs/architecture/dolt.md 与 docs/cli-reference/flatten.md):
bd prune—— 永久删除已关闭的非临时 beads 以回收存储、缩小自动导出体积;bd purge对临时 beads(wisps、瞬时 molecule)做同样的事;两者都需要--force才真正执行:bd prune --older-than 30d # 预览 >30 天的已关闭 beads bd prune --older-than 30d --force # 删除它们 bd prune --older-than 90d --dry-run # 带统计的详细预览 bd purge --force # 删除所有已关闭的临时 beads- 引用感知保护:
bd prune自动跳过 ID 出现在任何 open/in-progress bead 的 description、notes 或 comments 中的已关闭 beads,防止误删仍被下游引用的 ADR、decision、verification beads;--ignore-references可覆盖此行为; bd flatten—— 历史压扁(squash),把全部 Dolt 提交历史合并为单个提交,用于.beads/dolt目录过大、不需要提交级历史(时间旅行)的场景:bd flatten --dry-run # 预览:显示提交数与磁盘占用 bd flatten --force # 真正压扁全部历史bd admin—— 管理命令集(cleanup删除已关闭 issue、compact压缩历史、reset完全重置),针对数据库的定向操作见 docs/cli-reference/admin.md。
版本锁定:为什么固定 Dolt 2.2.0
值得单独说明的一个运维细节(来自 docs/architecture/dolt.md):Beads 将 Dolt 固定为 2.2.0。Dolt 2.3.0(2026-08-13 发布)存在CALL DOLT_RESET('--hard')回归——约百分之几的新建数据库该存储过程不可用(任何会话任何连接都报Error 1105 (HY000): context canceled),且其余一切看似正常(SELECT 1、DOLT_CLEAN()、DOLT_CHECKOUT('.')、DOLT_COMMIT()均正常),损坏直到需要硬重置时才暴露。实测数据(新建库后立即调用该过程):
| Dolt 版本 | 硬重置损坏的新建库比例 |
|---|---|
| 2.1.8 | 0 / 40 |
| 2.2.0 | 0 / 60 |
| 2.3.0 | 3 / 60 |
| 2.3.1 | 3 / 100 |
由于bd flatten、bd admin compact的 Dolt 历史压缩都以"把main硬重置到临时分支"收尾,bd dolt pull/bd sync的 merge-settle 路径在放弃合并时也会回退到硬重置,因此该回归会直接影响这些维护操作。判定某数据库是否受影响,可在干净工作集上依次执行:
dolt sql -q "SELECT * FROM dolt_status" # 1. 确认工作集干净 dolt sql -q "SELECT 1" # 2. 对照组:必须成功 dolt sql -q "CALL DOLT_RESET('--hard')" # 3. 健康库上应为 no-op第 2 步成功而第 3 步报Error 1105 (HY000): context canceled即为受影响。该损坏驻留在运行中的服务器进程而非磁盘上,重启dolt sql-server可暂时清除,但可靠修复仍是迁移到固定版本。此外,上游releases/latestURL 解析的是最近创建的 release 而非最高版本,可能"倒退",因此钉版而非跟踪 latest 是双重理由下的工程决策。
设计决策与取舍
为什么用 Dolt 而非云服务器?
Beads 面向offline-first、local-first开发设计:Dolt 服务器运行在本地——无云依赖、无停机、无厂商锁定,在飞机上或受限网络内功能完整。这一取向与"编码 Agent 的记忆"定位完全一致:工作记忆应随身携带,而不是依赖外部可达性。
取舍清单
原文档给出的 Benefit/Trade-off 对照:
| 收益 | 代价 |
|---|---|
| 离线可用 | 无实时协作 |
| 版本化数据库 | 并发写入者需要服务器模式 |
| 单元格级合并 | 需要初始配置 |
| 本地优先的速度 | 手动同步到远程 |
| SQL 查询 | 依赖 Dolt 存储引擎 |
何时不适合用 Beads
- 大型团队(10+ 人)—— 基于 git 的同步在高频并发编辑下扩展性不佳;
- 非开发者—— 需要 git 与命令行熟练度;
- 实时协作—— 无实时更新,需要显式同步;
- 富媒体附件—— 面向文本型 issue 跟踪设计。
这类场景可考虑 GitHub Issues、Linear 或 Jira。这些边界也在仓库 engdocs/PROJECT_CHARTER.md 中有正式声明。
相关文档导航
继续深入阅读的入口(全部为仓库内路径):
- docs/core-concepts/index.md —— 概念模型:beads、依赖、ready work、molecules;
- docs/core-concepts/sync-concepts.md —— 跨机器同步、线格式与反模式;
- docs/architecture/dolt.md —— 嵌入式与服务器模式的深度对比、共享服务器、迁移、版本钉选;
- docs/recovery/index.md —— 常见问题的分步恢复 runbook;
- docs/cli-reference/index.md —— 全部 108 个
bd命令的完整参考; - docs/cli-reference/dolt.md ——
bd dolt子命令(start/stop/status/show/commit/push/pull/remote 管理); - docs/getting-started/quickstart.md —— 安装与起步;
- docs/multi-agent/federation.md —— 跨仓库 peer-to-peer 联邦同步;
- docs/workflows/wisps.md —— 临时分子(wisp)生命周期;
- engdocs/INTERNALS.md —— 实现细节(贡献者文档);
- engdocs/COLLISION_MATH.md —— 哈希长度与碰撞概率的生日悖论分析。
【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考