名称: braintrust-追踪 描述: Claude Code 的 Braintrust 追踪 - 钩子架构、子代理相关性、调试 用户可调用: false
Claude Code 的 Braintrust 追踪
Braintrust 中追踪 Claude Code 会话的全面指南,包括子代理相关性。
架构概述
父会话
+---------------------+
| 会话开始 |
| (创建根) |
+----------+----------+
|
+----------v----------+
| 用户提示提交 |
| (创建回合) |
+----------+----------+
|
+--------------------+--------------------+
| | |
+---------v--------+ +--------v--------+ +--------v--------+
| 工具使用后 | | 工具使用后 | | 工具使用前 |
| (读取跨度) | | (编辑跨度) | | (任务 - 注入) |
+------------------+ +-----------------+ +--------+--------+
|
+----------v----------+
| 子代理 |
| 会话开始 |
| (新根_跨度_id)|
+----------+----------+
|
+----------v----------+
| 子代理停止 |
| (有会话_id) |
+---------------------+
钩子事件流
| 钩子 | 触发器 | 创建 | 关键字段 |
|---|---|---|---|
| 会话开始 | 会话开始 | 根跨度 | 会话_id, 根_跨度_id |
| 用户提示提交 | 用户发送提示 | 回合跨度 | 提示, 回合_编号 |
| 工具使用前 | 工具运行前 | (修改任务提示) | 工具_输入.提示 |
| 工具使用后 | 工具运行后 | 工具跨度 | 工具_名称, 输入, 输出 |
| 停止 | 回合完成 | LLM 跨度 | 模型, 令牌, 工具_调用 |
| 子代理停止 | 子代理完成 | (无跨度) | 子代理的 会话_id |
| 会话结束 | 会话结束 | (完成根) | 回合_计数, 工具_计数 |
追踪层次
会话 (任务跨度) - 根_跨度_id = 会话_id
|
+-- 回合 1 (任务跨度)
| |
| +-- claude-sonnet (llm 跨度) - 模型调用与工具_使用
| +-- 读取 (工具跨度)
| +-- 编辑 (工具跨度)
| +-- claude-sonnet (llm 跨度) - 工具后的响应
|
+-- 回合 2 (任务跨度)
| |
| +-- claude-sonnet (llm 跨度)
| +-- 任务 (工具跨度) -----> [子代理会话 - 独立追踪]
| +-- claude-sonnet (llm 跨度)
|
+-- 回合 3 ...
子代理追踪:什么有效和什么无效
什么无效
会话开始不接收任务提示。
我们尝试通过工具使用前钩子将追踪上下文注入任务提示:
# 工具使用前钩子注入:
[BRAINTRUST_TRACE_CONTEXT]
{"根_跨度_id": "abc", "父_跨度_id": "xyz", "项目_id": "123"}
[/BRAINTRUST_TRACE_CONTEXT]
但会话开始只接收会话元数据,不接收修改后的提示。注入的上下文丢失了。
什么有效
父会话中的任务跨度包含所有内容:
代理Id- 子代理运行的标识符总令牌数,总工具使用计数- 指标内容- 完整代理响应/摘要工具_输入.提示- 原始任务提示工具_输入.子代理_类型- 代理类型 (例如 “oracle”)
子代理停止钩子接收子代理的 会话_id:
- 这等于子代理的孤立追踪
根_跨度_id - 允许父任务跨度和子追踪之间的相关性
相关性模式
当前状态: 子代理创建孤立追踪 (新 根_跨度_id)。
相关方法:
- 查询父会话的任务跨度以获取代理元数据
- 匹配
代理Id或时间与孤立追踪 - 子代理的
会话_id= 其追踪的根_跨度_id
未来解决方案 (尚未实现):
子代理停止触发 -> 将会话_id写入临时文件
工具使用后 (任务) -> 读取临时文件 -> 添加子_会话_id到任务跨度元数据
这将链接:任务.代理Id + 任务.子_会话_id -> 孤立追踪 根_跨度_id
状态管理
每会话状态文件
~/.claude/state/braintrust_sessions/
{会话_id}.json # 每会话状态
每个会话文件包含:
{
"根_跨度_id": "abc-123",
"项目_id": "proj-456",
"回合_计数": 5,
"工具_计数": 23,
"当前_回合_跨度_id": "turn-789",
"当前_回合_开始": 1703456789,
"开始时间": "2025-12-24T10:00:00.000Z",
"是_子代理": false
}
全局状态
~/.claude/state/braintrust_global.json # 缓存项目_id
~/.claude/state/braintrust_hook.log # 调试日志
调试命令
检查追踪是否激活
# 实时查看钩子日志
tail -f ~/.claude/state/braintrust_hook.log
# 检查会话是否有状态
cat ~/.claude/state/braintrust_sessions/*.json | jq -s '.'
# 验证环境
echo "TRACE_TO_BRAINTRUST=$TRACE_TO_BRAINTRUST"
echo "BRAINTRUST_API_KEY=${BRAINTRUST_API_KEY:+set}"
直接查询 Braintrust
# 列出最近会话
uv run python -m runtime.harness scripts/braintrust_analyze.py --sessions 5
# 分析最后会话
uv run python -m runtime.harness scripts/braintrust_analyze.py --last-session
# 回放特定会话
uv run python -m runtime.harness scripts/braintrust_analyze.py --replay <session-id>
# 查找子代理追踪 (孤立根)
uv run python -m runtime.harness scripts/braintrust_analyze.py --agent-stats
调试钩子执行
# 启用详细日志记录
export BRAINTRUST_CC_DEBUG=true
# 手动测试钩子
echo '{"会话_id":"test-123","类型":"resume"}' | \
bash "$CLAUDE_PROJECT_DIR/.claude/plugins/braintrust-tracing/hooks/session_start.sh"
# 测试工具使用前 (任务注入)
echo '{"会话_id":"test-123","工具_名称":"Task","工具_输入":{"提示":"test"}}' | \
bash "$CLAUDE_PROJECT_DIR/.claude/plugins/braintrust-tracing/hooks/pre_tool_use.sh"
故障排除清单
-
无追踪出现:
- 检查
.claude/settings.local.json中的TRACE_TO_BRAINTRUST=true - 验证 API 密钥:
echo $BRAINTRUST_API_KEY - 检查日志:
tail -20 ~/.claude/state/braintrust_hook.log
- 检查
-
子代理未链接:
- 这是预期的 - 子代理创建孤立追踪
- 使用
--agent-stats查找代理活动 - 通过时间或父任务跨度中的
代理Id相关性
-
缺少跨度:
- 检查会话状态中的
当前_回合_跨度_id - 确保停止钩子运行 (回合完成)
- 在日志中查找 “创建失败” 错误
- 检查会话状态中的
-
状态损坏:
- 移除会话状态:
rm ~/.claude/state/braintrust_sessions/*.json - 清除全局缓存:
rm ~/.claude/state/braintrust_global.json
- 移除会话状态:
关键文件
| 文件 | 目的 |
|---|---|
.claude/plugins/braintrust-tracing/hooks/common.sh |
共享实用程序、API、状态管理 |
.claude/plugins/braintrust-tracing/hooks/session_start.sh |
创建根跨度、处理子代理上下文 |
.claude/plugins/braintrust-tracing/hooks/user_prompt_submit.sh |
每用户消息创建回合跨度 |
.claude/plugins/braintrust-tracing/hooks/pre_tool_use.sh |
将追踪上下文注入任务提示 |
.claude/plugins/braintrust-tracing/hooks/post_tool_use.sh |
创建工具跨度、捕获代理/技能元数据 |
.claude/plugins/braintrust-tracing/hooks/stop_hook.sh |
创建 LLM 跨度、完成回合 |
.claude/plugins/braintrust-tracing/hooks/session_end.sh |
完成会话、触发学习提取 |
scripts/braintrust_analyze.py |
查询和分析追踪会话 |
~/.claude/state/braintrust_sessions/ |
每会话状态文件 |
~/.claude/state/braintrust_hook.log |
调试日志 |
环境变量
| 变量 | 必需 | 默认 | 描述 |
|---|---|---|---|
TRACE_TO_BRAINTRUST |
是 | - | 设置为 "true" 启用 |
BRAINTRUST_API_KEY |
是 | - | Braintrust 的 API 密钥 |
BRAINTRUST_CC_PROJECT |
否 | claude-code |
项目名称 |
BRAINTRUST_CC_DEBUG |
否 | false |
详细日志记录 |
BRAINTRUST_API_URL |
否 | https://api.braintrust.dev |
API 端点 |
会话学习
我们学到的关于子代理追踪的知识 (2025年12月)
尝试过: 通过工具使用前钩子将追踪上下文注入任务提示。
结果: 失败 - 会话开始只接收会话元数据,不接收提示。
发现: 任务跨度已包含丰富的子代理数据:
元数据.代理_类型- 来自子代理_类型的代理类型元数据.技能_名称- 来自技能工具的技能工具_输入- 发送给代理的完整提示工具_输出- 代理响应
当前相关路径:
- 父会话任务跨度有
代理Id和时间 - 子代理创建孤立追踪,
根_跨度_id = 会话_id - 子代理停止提供子代理的
会话_id - 手动相关性:匹配时间或使用
会话_id链接
未来工作: 在子代理停止后,从工具使用后写入 子_会话_id 到任务跨度元数据。
我们学到的关于子代理相关性的知识
问题
- 通过任务工具生成的子代理创建孤立的 Braintrust 追踪
- 父会话有带
代理Id的任务跨度,子代理有独立的会话_id - 它们之间没有内置链接
什么无效
1. 通过工具使用前的提示注入
会话开始钩子只接收会话元数据 (会话_id, 类型, cwd),不接收提示。注入的追踪上下文从未被看到。
钩子接收:
{
"会话_id": "...",
"类型": "start|resume|compact|clear",
"cwd": "...",
"环境": {...}
}
没有提示字段 - 在会话开始时无法进行上下文注入。
2. 子代理停止 → 工具使用后文件交接
竞态条件。这些是独立的异步钩子,没有时间保证:
- 子代理停止在子代理会话结束时触发
- 工具使用后 (任务) 在任务工具完成时触发
- 它们之间没有顺序保证
- 写入相关性文件创建竞态
3. 工具使用前相关性文件
会话开始无法访问 任务_跨度_id,因为它没有上下文关于哪个任务生成了它。工具使用前修改提示但不创建会话开始可以可靠访问的状态文件。
什么有效
后处理匹配以构建数据集:
父会话任务跨度包含:
代理Id- 子代理运行的标识符总令牌数,总工具使用计数- 聚合指标内容- 完整代理响应/摘要工具_输入.提示- 原始任务提示工具_输入.子代理_类型- 代理类型 (例如 “oracle”)- 开始/结束时间戳
子代理会话包含:
会话_id(等于孤立追踪根_跨度_id)- 开始/结束时间戳
- 所有内部跨度和工具调用
相关策略:
- 导出父会话追踪 (查询父
根_跨度_id) - 导出子代理追踪 (查询在父时间窗口内创建的所有会话)
- 匹配:
- 时间:任务跨度结束 ≈ 子代理会话结束
- 元数据:任务提示中的
子代理_类型 - ID:子代理停止钩子提供
会话_id(可捕获和记录)
架构洞察
会话开始输入是故意最少的 - 它不包含提示或工具上下文:
interface SessionStartInput {
会话_id: string;
类型: "start" | "resume" | "compact" | "clear";
cwd: string;
环境: { [key: string]: string };
// 无:提示, 工具_上下文, 任务_跨度_id, 父_跨度_id
}
此设计边界防止了钩子时间的实时相关性。
建议
对于构建具有子代理相关性的代理运行数据集:
- 会话内日志记录: 在日志或状态中捕获子代理停止
会话_id - 会话后导出: 查询 Braintrust API 获取父和子代理追踪
- 离线相关性: 在脚本中通过时间和元数据匹配追踪
- 不要尝试实时链接: 钩子没有必要的上下文
示例脚本模式:
# 1. 导出父会话
braintrust_analyze.py --replay <parent-session-id> > parent_traces.json
# 2. 查询孤立的子代理追踪 (在父时间窗口内创建的)
braintrust_analyze.py --agent-stats > all_agent_traces.json
# 3. 在 Python 中相关:
# - 父任务跨度 -> 代理Id, 时间戳, 子代理_类型
# - 孤立追踪 -> 根_跨度_id, 时间戳
# - 通过时间和类型匹配
这种方法可靠、可测试,且不需要钩子维护隐式状态。