Beadsbd audit全解析:用.beads/interactions.jsonl构建 Agent 交互审计与训练数据集
【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads
bd audit是 Beads(面向编码 Agent 的"记忆升级"工具)提供的可选审计侧车(sidecar)命令,它把 Agent 与 LLM/工具之间的每一次交互以 JSONL 追加式(append-only)格式落盘到.beads/interactions.jsonl。这篇指南将带你完整掌握bd audit record、bd audit label两个子命令的全部参数、启用方式与底层实现原理,并展示如何利用该文件回答"Agent 当时为什么这么做"以及为 SFT/RL 微调生成数据集。
快速导读
- 核心主题:Beads 的审计日志子系统——一个默认关闭、以 JSONL 存储交互事件的可选侧车文件。
- 应用场景:审计 Agent 行为("为什么 Agent 要这样做?")与生成训练数据集(SFT/RL 微调)。
- 你将掌握:如何启用审计、通过
record/label写入事件、从 stdin 注入结构化 JSON、理解追加式与标签引用的数据模型,以及其并发安全与随机 ID 的设计细节。
注意:Beads 数据库中的 issue 历史(通过
bd history <id> --events查看)始终会被记录;.beads/interactions.jsonl只是面向显式交互捕获的可选补充,二者并存而非互相替代。
bd audit命令总览
命令入口定义在 cmd/bd/audit.go,其定位为"记录和标记 Agent 交互(追加式 JSONL)":
bd audit [flags]它有两个子命令:
bd audit record:追加一条审计交互条目;bd audit label:追加一条标签条目,引用一条已存在的交互。
audit命令自身支持issueIDCompletion参数补全(见 cmd/bd/audit.go),便于在已有 issue 上下文中快速录入。
启用审计:两种开关,默认关闭
审计侧车默认是禁用的,全局默认值在 internal/config/config.go 中定义为audit.enabled=false。启用方式有两种:
# 方式一:持久化配置 bd config set audit.enabled true # 方式二:环境变量(运行时生效,可覆盖配置) BD_AUDIT_ENABLED=1 bd audit record ...底层判定逻辑见 internal/audit/audit.go:Enabled()优先读取环境变量BD_AUDIT_ENABLED(接受1/t/true/y/yes/on等真值写法),未设置时回落到配置项audit.enabled。若未启用就调用写入接口,会得到明确报错:
audit JSONL sidecar is disabled; set audit.enabled=true or BD_AUDIT_ENABLED=1 to write interactions.jsonl文件位于仓库的.beads目录下,具体路径由beads.FindBeadsDir()解析得到,固定文件名为interactions.jsonl(常量定义见 internal/audit/audit.go)。.beads目录需包含有效项目元数据(例如metadata.json),测试中即通过写入{"backend":"dolt"}来满足该校验(见 internal/audit/audit_test.go)。
bd audit record:追加一条交互事件
bd audit record [flags]它的职责是"追加一条审计交互条目"。源码 cmd/bd/audit.go 中,命令执行时优先检测 stdin 是否为管道输入(非字符设备)且未提供任何字段参数,若满足则自动走 stdin JSON 解析分支;否则要求--kind必填。
完整 Flags 参数表
| Flag | 类型 | 默认值 | 说明 |
|---|---|---|---|
--kind | string | 空 | 条目类型(如llm_call、tool_call、label),非 stdin 模式必填 |
--model | string | 空 | 模型名称(用于llm_call) |
--prompt | string | 空 | 提示词文本(用于llm_call) |
--response | string | 空 | 响应文本(用于llm_call) |
--issue-id | string | 空 | 关联的 issue id(bd-...) |
--tool-name | string | 空 | 工具名称(用于tool_call) |
--exit-code | int | -1 | 退出码(用于tool_call),只有>= 0才会写入条目 |
--error | string | 空 | 错误字符串(llm_call/tool_call通用) |
--stdin | bool | false | 从 stdin 读取 JSON 对象(必须符合audit.Entry结构) |
以上 Flag 的注册见 cmd/bd/audit.go。
使用示例
# 记录一次 LLM 调用 bd audit record --kind llm_call \ --model gpt-4o \ --prompt "summarize this issue" \ --response "The issue is caused by ..." \ --issue-id bd-123 # 记录一次工具调用(含退出码) bd audit record --kind tool_call \ --tool-name bash \ --exit-code 0 \ --issue-id bd-123 # 记录一次失败的工具调用 bd audit record --kind tool_call \ --tool-name bash \ --exit-code 1 \ --error "command not found: foo"从 stdin 注入结构化 JSON
当命令参数为空且 stdin 被管道输入时(或显式传入--stdin),record会读取整个 stdin 并json.Unmarshal到audit.Entry(见 cmd/bd/audit.go):
echo '{"kind":"llm_call","model":"gpt-4o","prompt":"p","response":"r","issue_id":"bd-123"}' \ | bd audit record --stdin如果--actor全局参数非空,还会覆盖写入Entry.Actor字段。
输出与成功判定
每次成功写入后,命令默认打印新条目的 ID(形如int-<32位hex>);若传入了--json全局输出开关,则输出{"id":"...","kind":"..."}结构。命令在成功时会触发metrics.NewCommandEvent("audit-record")的遥测事件上报(见 cmd/bd/audit.go)。
bd audit label:为已有交互打标签
bd audit label <entry-id> [flags]它负责"追加一条引用既有交互的标签条目",entry-id为必填位置参数(cobra.ExactArgs(1),见 cmd/bd/audit.go)。因为文件是追加式设计,标签不会修改原条目,而是生成一条新的kind=label条目,通过parent_id指向父条目。
完整 Flags 参数表
| Flag | 类型 | 默认值 | 说明 |
|---|---|---|---|
--label | string | 空 | 标签值(如"good"或"bad"),必填 |
--reason | string | 空 | 打标签的原因说明 |
Flag 注册见 cmd/bd/audit.go。若--label为空会直接报错。
使用示例
# 为某次 llm_call 打上 "good" 标签,并注明原因 bd audit label int-4f8a... --label good --reason "accurate and concise summary" # 为某次失败的工具调用打上 "bad" 标签 bd audit label int-9c21... --label bad --reason "exit code 1 with misleading error"标签条目写入后同样打印新条目 ID;--json模式输出{"id":"...","parent_id":"...","label":"good"}。
数据模型:audit.Entry字段详解
所有条目的底层结构是Entry(见 internal/audit/audit.go),其设计"刻意保持灵活":用Kind+ 类型化字段覆盖常见场景,用Extra承接其余任意扩展数据。JSON 字段(omitempty表示可为空省略):
| JSON 字段 | Go 类型 | 用途 |
|---|---|---|
id | string | 条目唯一 ID,前缀int-+ 128 位随机 hex |
kind | string | 条目类型:llm_call/tool_call/label/field_change等 |
created_at | time.Time | 创建时间(UTC,自动填充) |
actor | string | 执行者标识(可选) |
issue_id | string | 关联 issue id(bd-...,可选) |
model | string | 模型名(llm_call) |
prompt | string | 提示词文本(llm_call) |
response | string | 响应文本(llm_call) |
error | string | 错误字符串(llm_call/tool_call) |
tool_name | string | 工具名(tool_call) |
exit_code | *int | 退出码指针(tool_call,仅>=0时写入) |
parent_id | string | 父条目 ID(label类型引用) |
label | string | 标签值("good"/"bad"等) |
reason | string | 标签或变更原因 |
extra | map[string]any | 任意扩展字段 |
ID 生成:随机而非序号
newID()使用crypto/rand生成 16 字节(128 位)熵,前缀int-(见 internal/audit/audit.go)。源码注释指出:8000 个 ID 的碰撞概率约为 9e-32。这刻意规避了"扫描文件种子化序号计数器"的并发竞态问题——并发测试 internal/audit/concurrency_test.go 中记录了这类下游消费者曾出现的重复序号 bug,而 audit 包因"无序号字段 + 随机 ID"从设计上免疫该问题。
底层写入流程与并发安全
追加式写入:Append
Append(见 internal/audit/audit.go)的完整流程:
- 校验条目非 nil 且
kind非空; - 调用
EnsureFile()确保.beads/interactions.jsonl存在(以O_CREATE|O_EXCL方式创建,已存在则忽略os.ErrExist,绝不截断,见 internal/audit/audit.go); - 为空 ID 生成随机 ID;为空
created_at填充time.Now().UTC(); - 以
O_APPEND模式打开文件,一次性f.Write(buf.Bytes())写入完整 JSON 行。
关键细节:写入刻意不用bufio.NewWriter分块写入。源码注释说明(internal/audit/audit.go):分块写会拆成多次write()系统调用,在并发O_APPEND场景下会互相穿插、破坏单行完整性;单次写入依赖 POSIXO_APPEND的原子语义(写入小于 PIPE_BUF 即 4096 字节时原子)。同时使用enc.SetEscapeHTML(false)保证 JSON 输出的 HTML 字符不做额外转义。
开关门面:AppendIfEnabled
所有 CLI 路径都走AppendIfEnabled(见 internal/audit/audit.go):未启用时返回错误并不创建文件。测试 internal/audit/audit_test.go 专门验证了禁用状态下不会生成interactions.jsonl。
并发正确性的三重验证
并发测试 internal/audit/concurrency_test.go 以 8 个 writer × 1000 条条目并发写入,验证三个不变量:
- 返回的 ID 全部唯一(随机 ID,无共享计数器竞态);
- 磁盘上每行都是合法 JSON(无撕裂写);
- 磁盘 ID 集合与返回 ID 集合一致(无丢失、无重复)。
自动化的字段变更审计:field_change
除了显式record,audit 子系统还提供了程序化钩子LogFieldChange(见 internal/audit/audit.go):当 issue 的字段(状态、负责人、优先级等)发生变化时,best-effort 写入一条kind=field_change的条目(错误被静默忽略,绝不阻塞业务操作),字段值差异放入extra的old_value/new_value/field/reason。
在 CLI 层,以下操作会自动触发字段变更审计:
bd close:状态从旧状态变更为closed,并携带关闭原因(cmd/bd/close.go);bd update:状态、负责人、优先级任一字段变更都会记录(cmd/bd/update.go);- Proxied/代理服务器路径同样覆盖:
close_proxied_server.go、reopen_proxied_server.go、gate_proxied_server.go中均调用了LogFieldChange(如重新打开时记录status → open)。
这使.beads/interactions.jsonl不仅能回答"Agent 调用了哪些工具、问了哪些模型",还能回答"issue 的字段在何时被谁改成了什么值"。
用 JSONL 做审计与数据集生成
由于每行是独立的 JSON 对象,可直接用标准命令行工具流式处理:
# 查看所有 LLM 调用 grep '"kind":"llm_call"' .beads/interactions.jsonl # 查看所有工具调用及退出码 grep '"kind":"tool_call"' .beads/interactions.jsonl # 关联父条目(label -> parent) grep '"kind":"label"' .beads/interactions.jsonl # 用 jq 展开 extra 字段(field_change 示例) jq -c 'select(.kind=="field_change") | {id, issue_id, field: .extra.field, old: .extra.old_value, new: .extra.new_value}' .beads/interactions.jsonl审计场景
- "为什么 Agent 当时这么做?":通过
id关联llm_call的 prompt/response、tool_call的退出码与错误、label的人工评判与 reason,完整还原一次决策链; - 文件注释明确说明其设计意图之一是在 git 中版本化并跨克隆共享(见 internal/audit/audit.go),适合团队间共享 Agent 行为日志。
数据集生成场景(SFT/RL 微调)
llm_call条目的prompt+response天然构成(输入, 输出)样本对;label条目的good/bad可作为偏好对(RLHF/DPO)的正负信号,parent_id精确指回被评判的样本;reason字段可承载人工或流水线给出的改进说明,作为高质量微调注释;field_change记录可观察 Agent 对 issue 状态机的操作序列,用于行为建模。
注意事项与限制
- 默认关闭:必须显式
bd config set audit.enabled true或设置BD_AUDIT_ENABLED=1,否则所有写入都会报错且不创建文件; - 追加式不可变:条目只增不改,打标签只能新增
label条目引用父条目,不能回改; - 与数据库历史的区别:issue 生命周期历史始终记录在数据库的 events 表中,可通过
bd history <id> --events查看;JSONL 侧车是显式交互捕获的补充层,两者数据来源和用途不同; - 单行原子性前提:单条 JSON 行需小于 PIPE_BUF(通常 4096 字节),超大的 prompt/response 在极端并发场景下可能越过单次写原子边界——这是设计上已知的权衡;
- 文件权限:文件以
0644创建,目录以0700创建,注释明确这是为了让 JSONL 能通过 git 在克隆与工具间共享。
参考资料
- 命令实现:cmd/bd/audit.go
- 数据模型与写入引擎:internal/audit/audit.go
- 单测(文件创建/禁用不创建/并发不截断):internal/audit/audit_test.go
- 并发唯一性测试:internal/audit/concurrency_test.go
- 默认配置开关:
audit.enabled=false(internal/config/config.go) - 字段变更审计调用点:cmd/bd/close.go、cmd/bd/update.go
【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考