news 2026/9/12 4:29:30

bd recall 命令深度指南:用 Beads 按 key 检索持久记忆

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
bd recall 命令深度指南:用 Beads 按 key 检索持久记忆

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、前后空格都逐字节保留。

前置条件:直接数据库访问

recallRunE中通过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: ...
  • 找到时,记忆的完整内容被原样打印到 stdoutfmt.Printf("%s\n", value)),不做任何截断,换行保持原样;
  • 退出码为 0,可以直接用于 shell 管道或变量捕获。

未找到时的 SilentExit 契约

$ bd recall no-such-key No memory with key "no-such-key"
  • 提示信息写入stderrfmt.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拒绝存储空内容,所以正常流程不会制造这种歧义,只有绕过前端的带外写入才会。

recallCmdRunE(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 直接引用它)。这套编码有两个关键推论:

  1. 合并即收敛:存储层的 merge resolver 只有在冲突键全部带kv.memory.前缀时,才用--theirs自动消解配置冲突(见internal/storage/versioncontrolops/mergesettle.go)。也就是说,"拉取后自动收敛"是记忆这个概念的组成部分——而普通设置行没有这个待遇。这正是记忆与设置分开成两个角色(memoryopsissueops.WorkspaceConfig)的根本原因;
  2. 按前缀隔离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 当内容存储"。

这正是bd recall命令在 docs/cli-reference/recall.md 中两个示例(dolt-phantomsauth-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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 4:28:24

ARM Cortex-M边缘AI语音唤醒模型源码深度审计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:26:50

Mipmap 生成与移动端贴图压缩失真:ASTC 格式下的细节保留

Mipmap 生成与移动端贴图压缩失真&#xff1a;ASTC 格式下的细节保留在移动端游戏开发中&#xff0c;纹理通常占据了整包体积与运行时 GPU 带宽的 60% 以上。ASTC&#xff08;Adaptive Scalable Texture Compression&#xff09;作为跨 Android 与 iOS 平台的主流硬件纹理压缩标…

作者头像 李华
网站建设 2026/9/12 4:23:46

AI落地实战:从场景挖掘到商业变现的完整方法论

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:21:19

Flask构建社区易物系统:智能匹配与安全实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:17:56

从零搭建Text-to-SQL问数智能体:LCODER项目架构设计与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华