08:Observability:Claude Code 如何让 Agent 的行动留下证据
上一篇把 Permission & Safety 理解成动作裁决层。
08:Observability:Claude Code 如何让 Agent 的行动留下证据
上一篇把 Permission & Safety 理解成动作裁决层。
plan 里写着准备怎么做,permission 负责判断这一步能不能执行。到了 08,问题继续往后走:
动作被允许、询问、拒绝、执行或失败以后,系统留下了什么证据?
我一开始容易把 observability 理解成“日志”。命令跑了什么、工具返回了什么、报错是什么、token 花了多少,这些当然重要。但对 Claude Code 这种 coding agent 来说,还要看到行动前后的判断关系。
它的执行路径比固定接口更松动。它会一边观察项目,一边决定下一步:读文件、搜代码、跑命令、改文件、触发权限判断、接收工具结果,再把这些结果放回下一轮判断里。用户最后看到的可能只是一段回答和几个文件变更,中间的行动链如果没有留下轨迹,后面就很难追问。
所以这篇先把 Observability 收成一句话:
可观测性是 Agent 的证据层。它把工具调用、权限裁决、错误、成本和上下文变化,变成以后还能调试、审计、优化和恢复的材料。

这篇文章的主线
08 要接住 07 留下的结果。
07 关心的是“这一步能不能做”。08 关心的是“这一步后来发生了什么”。一个动作被放行以后,可能成功,也可能失败;一个动作被拒绝以后,agent 可能换路径;一次命令执行以后,可能带来测试输出、耗时、权限事件和成本变化。
这些信息如果只留在终端滚动输出里,就只能服务当下的人。它们变成结构化证据以后,才可以继续回答这些问题:
- 为什么 agent 会读这个文件?
- 这条命令为什么被允许?
- 哪一步最慢?
- 哪个工具失败率最高?
- token 和成本消耗在哪一段?
- 失败以后应该重试、换路,还是回滚?
所以 08 的重点先放在行动证据上,telemetry 和观测工具栈只作为支撑材料。主线只有一条:
Claude Code 如何让每一次行动都能在后面被追问。
动作需要留下轨迹
普通程序的执行路径通常更稳定。一个接口进来,经过路由、服务、数据库,再返回响应。日志、指标和 trace 也容易围绕这条链组织。
coding agent 的路径更松动。
同样一句“帮我修一下测试失败”,Claude Code 可能先读 package.json,也可能先跑测试;可能改实现,也可能发现配置错了;可能被权限系统挡住,也可能被 hook 拦住;可能一轮解决,也可能多轮失败以后才收敛。
这类系统最需要留下的是中间那些改变判断的证据:
- 用户目标是什么。
- 当前 plan 指向哪一步。
- 模型请求了哪个工具。
- 工具参数指向哪里。
- 权限裁决结果是什么。
- 工具执行是否成功。
- 错误、耗时、成本和上下文变化是什么。
- 这些结果怎样影响下一轮行动。
有了这些轨迹,Agent Loop 才能摆脱黑盒反应。工具结果、权限拒绝、测试失败、上下文压缩和成本异常,都可以变成下一轮判断的输入。
观察从哪里开始
几篇源码解析资料给出的共同线索是:Claude Code 的可观测性分散在运行链路里的多个证据点上。这里要分清两件事:证据在哪里产生,证据最后放到哪里。
第一处是在会话控制附近。
源码架构分析里提到,QueryEngine 这类会话控制层会管理单个 conversation 的生命周期,里面有消息链、turn 状态、read file cache、permission denials、usage / cost,并持续写入 transcript[1]。这说明可观测性从任务推进本身就开始了:系统需要知道这一轮会话走到哪里、哪些信息被读过、哪些权限被拒绝、成本用了多少。
第二处是在工具执行附近。
多篇源码解析都把工具执行层描述成一个统一中间层:模型提出工具调用以后,系统要做参数检查、权限判定、hook、telemetry、错误分类、日志安全化和 tool result 处理[1],[2]。这正好是 Agent 最容易产生副作用的位置。读文件、写文件、跑命令、调用 MCP、触发 hook,都应该在这里留下证据。
这些证据主要有两个出口。
第一个出口是 session transcript。资料里反复提到 .jsonl 形式的 session transcript[1],[3]。这个格式很朴素,但适合 Agent:一次任务天然是一串事件,JSONL 可以逐条追加,也方便恢复、搜索和局部读取。它不需要一次性把整个会话写成一个大对象,而是把过程按时间线保存下来。
第二个出口是 telemetry。它更适合把工具耗时、错误类型、成本、权限事件这类数据送到外部观测系统,用来做聚合、趋势和告警。
把这几部分合起来看,Claude Code 的可观测链路大概可以这样理解:
- 会话控制层维护任务推进状态,并把关键过程写进 transcript。
- 工具执行层产生单次行动的证据,比如参数、权限、结果、错误和耗时。
- transcript 保存单次任务的时间线,用于回放、resume、rewind 和复盘。
- telemetry 聚合跨任务数据,用于成本、性能、安全和失败率分析。
这里最关键的是工具层。coding agent 真正接触世界的地方,大多发生在工具调用上。只记录模型回答,抓不住副作用;只看最终 diff,也解释不了中间判断。工具调用链才是证据最密集的位置。
两类记录:回放和聚合
08 里最容易混在一起的是 transcript 和 telemetry。
我会先把它们分开看:
| 类型 | 更像什么 | 主要回答什么 |
|---|---|---|
| transcript | 单次任务录像 | 这次会话从用户输入到工具结果,完整发生了什么 |
| telemetry | 运行数据聚合 | 多次任务里成本、耗时、失败率、安全事件有什么趋势 |
transcript 更贴近一次任务本身。它保存用户消息、助手消息、工具调用、工具结果、权限拒绝、文件变更线索。后续 resume、rewind、复盘和失败恢复,都离不开它。
telemetry 更贴近系统治理。General Analysis 的 OTel 文章把 metrics、events、traces 分开讲:metrics 看趋势,events 看离散事件,traces 看因果链[4]。这个分类放到 Claude Code 上很有用。
比如:
- token 使用量突然上涨,更适合先看 metrics。
- 某条 Bash 命令频繁触发权限询问,更像 security event。
- 一次 agent run 卡了十分钟才失败,需要 trace 串起模型请求、工具调用、权限等待和 hook 执行。
所以 transcript 负责“这一次怎么走过来的”,telemetry 负责“很多次运行里发生了什么模式”。两者放在一起,单次任务能回放,整体系统也能优化。
工具调用要留下什么
如果自己设计一个 agent,我会从工具调用开始设计观测事件。
一次工具调用至少要能回答四个问题:
- 它准备做什么?
- 为什么这个动作合理?
- 执行时发生了什么?
- 结果怎样影响下一步?
可以把事件对象先设计得很小:
| 字段 | 作用 |
|---|---|
| run_id | 串起一次完整任务 |
| turn_id | 标记第几轮对话或行动 |
| tool | 使用了哪个工具 |
| target_summary | 目标摘要,比如文件路径、命令摘要、MCP 工具名 |
| permission_decision | allow、ask、deny,以及原因 |
| status | success、failed、blocked、skipped |
| duration_ms | 这一步耗时多久 |
| error_type | 如果失败,属于命令失败、权限拒绝、超时、参数错误还是测试失败 |
| usage_cost | 相关 token、模型请求或外部调用成本 |
| context_effect | 这一步向上下文加入了什么信息 |
| next_effect | 它让下一轮应该继续、换路、询问用户,还是停止 |
这里先不追求一次把所有字段都写全。重要的是先把“工具调用是一条可追踪事件”这件事定下来。
有了这样的事件,后面很多问题就有抓手。测试失败可以比较两次失败类型有没有变化;权限拒绝可以看是否有低风险替代路径;成本异常可以定位是模型请求、工具输出还是上下文压缩导致;恢复任务时也能知道应该从哪个节点继续。
终端输出的限制
Claude Code 是终端产品,用户在界面里能看到很多过程:工具调用、命令输出、错误信息、权限提示、最终总结。
这些输出适合当下阅读。人在场时,它们很有用。
但终端输出很难回答跨时间、跨项目的问题:
- 上周哪些任务最耗 token?
- 哪类工具失败率最高?
- 哪些命令最常触发权限询问?
- 哪些 hook 经常阻塞任务?
- 哪些 MCP server 调用最慢?
- 哪些任务最后需要 recovery?
这些问题需要结构化数据。
claude-code-otel 这类项目把 Claude Code 的成本、token、工具性能、错误和 session analytics 接到 OpenTelemetry、Prometheus、Loki、Grafana 等观测栈里[5]。claude_telemetry 则更像 CLI wrapper,把 agent run、tool call、token、cost、execution time、success / failure 包成 trace 发到 Logfire、Sentry、Honeycomb、Datadog 或 Grafana[6]。
这些实践说明了一个很朴素的分工:终端输出服务当前用户,结构化轨迹服务后续系统。
观测也要有边界
Agent 的轨迹里可能有敏感内容。
完整 prompt 里可能有业务需求和用户数据。工具输出里可能有源码、日志、密钥、数据库内容、客户反馈。Bash 命令、MCP 参数、文件路径也可能泄露项目结构。
所以 observability 需要边界。我的倾向是 metadata-first:默认先记录足够定位问题的元数据,原始内容按场景短期打开。
可以分成三层:
-
默认记录元数据。 工具名、路径摘要、状态、耗时、错误类别、token、成本、权限结果。这些通常足够做趋势分析和初步排查。
-
有条件记录工具细节。 Bash 命令、MCP 工具名、检索 query、文件路径可以记录,但要做脱敏和访问控制。
-
严格限制原始内容。 完整 prompt、源码片段、工具原始输出、API body 只适合在调试或审计场景短期保存。
这点对自己写 Agent 很重要。可观测性越强,越容易把系统内部过程暴露出来。记录太少,出了问题查不到;记录太满,日志本身又会变成风险。
一个比较稳的做法是:长期保存摘要和元数据,短期保存可控的调试细节,敏感原文默认留在原系统里,需要时按权限重新读取。
例子:测试失败卡在哪里
拿一个常见任务看:
“帮我修一下测试失败。”
如果只有终端输出,我们可能只看到 pnpm test 跑过、报了错、agent 改了文件、后来又跑了一次。屏幕滚过去以后,细节就很难再整理。
如果把它写成证据链,过程会清楚很多:
- 用户目标:修复测试失败。
- 当前 plan:先找到测试命令,再复现失败。
- 工具调用:读取
package.json。 - 工具调用:运行
pnpm test。 - 权限裁决:Bash 命令被允许,理由是它服务于验证。
- 工具结果:测试失败,返回失败用例、堆栈摘要和退出码。
- 下一步影响:读取相关测试文件和实现文件。
- 文件编辑:修改实现。
- 再次工具调用:运行同一组测试。
- 对比结果:失败减少、失败类型变化,或仍然相同。
这条链的价值不只在“记录发生过什么”。它还能帮助下一步判断。
如果两次测试失败完全一样,说明刚才的修改没有触达原因。
如果失败类型变了,说明当前方向可能有进展。
如果测试耗时异常,可以看工具耗时。
如果命令被拒绝,可以看 permission decision。
如果最后要回滚,可以看 transcript 和文件变更轨迹。
这就是可观测性的意义:它让 Agent 的错误更容易被看见,也让下一轮行动更有根据。
对我写 Agent 的启发
写完这一篇,我会把 observability 当成运行时协议,放在 agent 设计的一开始。
自己写 agent 时,我会先做几件事:
-
每次任务都有
run_id。 无论是在本地 CLI、后台任务还是 CI 里运行,都能把一次任务完整串起来。 -
每次工具调用都有事件。 工具名、目标摘要、权限结果、耗时、状态、错误类型和成本都进入结构化记录。
-
权限裁决单独成事件。 allow、ask、deny 的来源和理由要能被审计,也能回到下一轮判断。
-
验证结果进入 trace。 测试、构建、lint、产物检查属于任务是否收敛的证据。
-
成本要能分摊。 token 和费用最好能分到模型请求、工具阶段、上下文压缩和重试次数上。
-
默认使用 metadata-first。 长期保存摘要、路径和状态;原始 prompt、源码片段和工具输出按调试场景短期保存。
我现在对 Observability 的理解可以收成一句话:
一个可维护的 Agent,要把过程当成一等公民。结果只是最后一帧,轨迹才是调试、信任和改进的基础。
参考资料
Claude Code 源码解析与架构拆解
-
[1] Claude Code 源码架构分析(含可以启动的源码本地部署)- GitCode
本文主要参考它关于QueryEngine、toolExecution.ts、telemetry、错误分类、session transcript 和 JSONL 落盘结构的分析。 -
[2] Claude Code 源码分析之架构设计 - 腾讯云
本文主要参考它关于 QueryEngine、状态管理、工具链协议、终端渲染和运行轨迹的架构视角。 -
[3] 从 Claude Code 泄露源码看工程架构 - GitCode
本文主要参考它关于审计内建、权限拒绝记录、性能打点和 session logs 的分析。
OpenTelemetry 与观测实践
-
[4] Claude Code Control and Observability with OpenTelemetry - General Analysis
本文主要参考它关于 metrics、events、traces 的区分,以及 tool decision、MCP、hooks、permission mode 在安全观测里的价值。 -
[5] claude-code-otel - GitHub
本文主要参考它如何把 Claude Code 的成本、token、工具性能、错误和 session analytics 接入 OpenTelemetry / Grafana 这类观测栈。 -
[6] claude_telemetry - GitHub
本文主要参考它如何用 CLI wrapper 记录 tool calls、token、cost、execution time 和 success / failure。
下一篇继续追的问题
这一篇把 Observability 理解成 Agent 的证据层。
核心结论是:
Claude Code 的每一次工具调用、权限裁决、错误、成本和上下文变化,都应该尽量变成后面还能追问的轨迹。
到这里,Claude Code 已经能计划动作、裁决动作、执行动作,也能记录动作。
但记录过程只是下一步的基础。Agent 一定会失败:工具会失败,命令会失败,测试会失败,权限会被拒绝,上下文会变得不够,模型也会判断错。
所以 09 要继续追的问题是:
Claude Code 如何使用这些证据,在失败以后重试、换路、回滚、恢复或请求用户介入?