bd recall 命令深度指南:用 Beads 按 key 检索持久记忆
【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads
导读
bd recall <key>是 Beads 持久记忆体系(memory plane)的核心读取命令,用于按 key 检索一条记忆的完整内容。它由 docs/cli-reference/recall.md 定义,是bd remember/bd memories/bd forget组成的记忆四命令之一:remember写入、recall读取、memories枚举搜索、forget删除。读完本文,你将掌握bd recall的完整用法、JSON 与退出码契约、未命中行为,以及它背后从 CLI 前端到memoryops角色契约再到kv.memory.存储编码的完整调用链,并能直接把它接入 Agent 的会话钩子与自动化脚本。
命令概览与基本用法
bd recall是一个顶层命令,属于setup命令组,功能定位为"Retrieve the full content of a memory by its key"(按 key 检索一条记忆的完整内容)。它的命令语法为:
bd recall <key> [flags]来自官方文档的示例:
bd recall dolt-phantoms bd recall auth-jwt用法要点:
- 必须且只能接收一个位置参数
<key>:在 cmd/bd/memory.go 中通过Args: cobra.ExactArgs(1)约束,传入多于或少于一个参数都会直接报错,不会执行任何读取; - key 就是记忆的名字:key 可以由
bd remember从内容自动派生(slug),也可以用--key显式指定;bd recall只负责按这个名字把整条记忆取回来; - 读取的是完整内容:与
bd memories列表里做了单行截断(truncateMemory把换行替换为空格并截断到 120 字符)的预览不同,bd recall返回的是存储时的原文,多行内容、Unicode、前后空格都逐字节保留。
前置条件:直接数据库访问
recall在RunE中通过openMemories("recall requires direct database access")获取持久记忆的访问句柄(cmd/bd/memory.go)。openMemories会做双路线分派:
- 若当前调用处于代理服务器(proxied)路线(如通过
bd serve起的 HTTP 后端),则经由proxiedMemories从 UOW provider 获取受保护的内存面; - 否则要求直接模式(direct mode):必须在已初始化的 workspace/仓库内运行,且底层存储(Dolt 数据库)可用,否则命令会以明确提示失败。
因此bd recall并非纯粹的字符串工具,它读的是工作区自己的持久化记忆库——记忆存放在 Dolt 工作集里,跨会话、跨账号轮换依然存在。
命中与未命中:输出与退出码契约
recall的输出渲染由printRecallResult完成(cmd/bd/memory.go),它对"找到"与"未找到"两种结局给出了完全不同的契约。
找到记忆时的文本输出
$ bd recall dolt-phantoms Dolt phantom DBs hide in three places: ...- 找到时,记忆的完整内容被原样打印到 stdout(
fmt.Printf("%s\n", value)),不做任何截断,换行保持原样; - 退出码为 0,可以直接用于 shell 管道或变量捕获。
未找到时的 SilentExit 契约
$ bd recall no-such-key No memory with key "no-such-key"- 提示信息写入stderr(
fmt.Fprintf(os.Stderr, ...)),stdout 保持干净; - 随后调用
SilentExit():以非零退出码静默退出,不再打印任何 usage 帮助信息。
对 Agent 和脚本而言,这个契约非常重要:判定"未命中"应该看退出码,而不是解析 stdout。未命中不是 Go 层面的错误(Recall对未知 key 返回Found=false, nil error),而是前端把"结果未找到"翻译成了退出码信号,这与bd config get等读取类命令的未命中约定一致。
JSON 输出模式
bd recall支持全局--json输出。JSON 模式下,命中返回:
{"key": "dolt-phantoms", "value": "Dolt phantom DBs hide in three places: ...", "found": true}未命中返回:
{"key": "no-such-key", "value": "", "found": false}随后同样走SilentExit()非零退出。注意 JSON 里用found字段显式区分"记忆存在但内容为空串"与"记忆不存在"两种语义,避免下游把空字符串误读成有效内容。
底层角色契约:memoryops.Memories
bd recall的前端逻辑非常薄——它只负责参数校验、路由分派和输出渲染,真正的读取语义由memoryops.Memories接口承担(memoryops/memories.go)。这是 Beads 的"角色(role)"架构:CLI、HTTP 门面、代理服务器共用同一套契约,保证各路线行为一致。
接口中与 recall 相关的定义:
type RecallRequest struct{ Key string } type RecallResult struct { Key string Value string Found bool } Recall(ctx context.Context, req RecallRequest) (RecallResult, error)几个值得注意的语义(均写在该文件的文档注释中,属于明确的设计决策):
Recall是纯读操作:不记录历史、不触发 completion 钩子、不修改任何行;- 未命中的 key 返回
Found=false且 error 为 nil,绝不是ErrNotFound——因为底层存储无法区分"配置行不存在"与"行存在但存的是空字符串"这两种情况(见 memoryops/errors.go),角色层面拒绝编造它看不见的区分;memoryops特意不导出ErrNotFound就是为了把这个决定钉死; Found的语义是Value != "":一条以空串存储的记忆与一条不存在的记忆,对Recall而言是同一个答案;唯一能区分它们的是bd memories(key 存在就会被枚举出来)。而前端remember拒绝存储空内容,所以正常流程不会制造这种歧义,只有绕过前端的带外写入才会。
recallCmd的RunE(cmd/bd/memory.go)把这一切串起来:打开记忆面 → 构造RecallRequest{Key: args[0]}→ 调用memories.Recall→ 交给printRecallResult渲染。整个命令只有这四步,错误则统一走HandleErrorRespectJSON(自动感知 JSON 模式决定输出格式)。
key 校验规则
请求进入角色前,key 会经过memoryapi.ValidateKey(internal/memoryapi/memoryapi.go)校验:空白 key 属于确定性校验失败(ErrValidation),因为"空 key"不存在任何调用者能指代的记忆行,回答Found=false等于为一个从未被提出的问题作答。校验通过后 key原样返回、不做任何裁剪——bd remember --key接受任意字符串,一个仅靠空格与其他 key 区分的 key 可能是调用者真实持有的,裁掉空格就会答非所问。
存储编码:kv.memory. 前缀与合并语义
记忆并不存在单独的数据库表里,而是存放在与配置共享的同一张表中,但位于保留命名空间下(memoryops/doc.go)。一条名为dolt-phantoms的记忆,实际存储的配置键是:
kv.memory.dolt-phantoms前缀kv.memory.的唯一权威定义在 internal/storage/kvkeys(常量memoryPrefix,cmd/bd/memory.go 直接引用它)。这套编码有两个关键推论:
- 合并即收敛:存储层的 merge resolver 只有在冲突键全部带
kv.memory.前缀时,才用--theirs自动消解配置冲突(见internal/storage/versioncontrolops/mergesettle.go)。也就是说,"拉取后自动收敛"是记忆这个概念的组成部分——而普通设置行没有这个待遇。这正是记忆与设置分开成两个角色(memoryops与issueops.WorkspaceConfig)的根本原因; - 按前缀隔离:
bd memories枚举时只会看到记忆平面,设置行、通用bd kv行永远不会混进来;反过来,bd recall也只认kv.memory.下的行。一个 shadow 了设置名的记忆 key(如kv.memory.issue_prefix)只是叫issue_prefix的记忆,不会与真正的设置混淆。
用户侧的所有请求与结果都只携带用户 key(不带前缀),前缀的拼写是存储实现层的私事,这样角色契约就与编码解耦,避免两处拼写漂移。
与 remember 的联动:bare key 的"欲望路径"
bd recall还隐身在bd remember里:bd remember <bare-slug>在特定条件下会转为读取而非写入(cmd/bd/memory.go)。逻辑是:
- 当没有显式
--key、且位置参数内容经DeriveKey往返后原样不变(即它本身就是个 slug,而不是一句人话)时:- 若该 key 已存在 →直接执行一次
Recall,等价于bd recall <key>,并在 stderr 提示(recalled ... -- a bare existing key READS. To overwrite: bd remember "<new content>" --key <key>); - 若该 key 不存在 → 拒绝写入,提示"没有名为 X 的记忆可读取,且拒绝把裸 key 当内容存储"。
- 若该 key 已存在 →直接执行一次
这正是bd recall命令在 docs/cli-reference/recall.md 中两个示例(dolt-phantoms、auth-jwt)的来源——它们与bd remember文档中的--key示例(docs/cli-reference/remember.md)互相呼应,构成一个完整的记忆写入-检索闭环。该路径的集成测试见 cmd/bd/memory_proxied_integration_test.go,覆盖了多行内容经 recall 逐字节往返、未命中 recall 的失败输出、以及 bare existing key 读取代写等场景。
在 Agent 工作流中的典型用法
记忆的写入端是bd remember,读取端是bd recall,而bd prime(cmd/bd/prime.go)会在会话启动时把记忆批量注入给编码 Agent(Claude Code、Gemini CLI、Codex 的 SessionStart 钩子),让 Agent 在上下文压缩后不至于遗忘工作区约定。bd recall在此生态中的角色是按需精确读取:
- 当 Agent 需要一条特定记忆的完整细节(而 prime 注入的只是摘要或列表)时,
bd recall <key>是最低成本的单条读取; - 在脚本中判断记忆是否存在:依赖退出码而非文本解析;
- 需要结构化消费时加
--json,用found字段区分命中与空值; - 与
bd memories(枚举/搜索)配合:先bd memories dolt找到 key,再bd recall dolt-phantoms取全文。
小结
bd recall表面上是"一行命令读一条记忆",但它的每一层都体现了 Beads 持久记忆体系的设计取舍:前端层有命中/未命中双契约与 JSON 输出;角色层由memoryops.Memories.Recall定义纯读、Found语义与"无 ErrNotFound"决策;存储层以kv.memory.保留前缀实现"合并即收敛"。把这四层串起来,你就能在 Agent 会话与自动化脚本中安全、准确地消费工作区记忆。相关文档与源码入口:命令文档 docs/cli-reference/recall.md、命令实现 cmd/bd/memory.go、角色契约 memoryops/memories.go、语义函数 internal/memoryapi/memoryapi.go、CLI 总览 docs/cli-reference/index.md。
【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考