Home Projects Blog Resume Contact 中文
Back to list
2026年7月2日 3,916 words 9 min read

08:Observability:Claude Code 如何让 Agent 的行动留下证据

上一篇把 Permission & Safety 理解成动作裁决层。

#Claude Code#Coding Agent#可观测性#工程化#Agent Runtime

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 拦住;可能一轮解决,也可能多轮失败以后才收敛。

这类系统最需要留下的是中间那些改变判断的证据:

  1. 用户目标是什么。
  2. 当前 plan 指向哪一步。
  3. 模型请求了哪个工具。
  4. 工具参数指向哪里。
  5. 权限裁决结果是什么。
  6. 工具执行是否成功。
  7. 错误、耗时、成本和上下文变化是什么。
  8. 这些结果怎样影响下一轮行动。

有了这些轨迹,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_decisionallow、ask、deny,以及原因
statussuccess、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:默认先记录足够定位问题的元数据,原始内容按场景短期打开。

可以分成三层:

  1. 默认记录元数据。 工具名、路径摘要、状态、耗时、错误类别、token、成本、权限结果。这些通常足够做趋势分析和初步排查。

  2. 有条件记录工具细节。 Bash 命令、MCP 工具名、检索 query、文件路径可以记录,但要做脱敏和访问控制。

  3. 严格限制原始内容。 完整 prompt、源码片段、工具原始输出、API body 只适合在调试或审计场景短期保存。

这点对自己写 Agent 很重要。可观测性越强,越容易把系统内部过程暴露出来。记录太少,出了问题查不到;记录太满,日志本身又会变成风险。

一个比较稳的做法是:长期保存摘要和元数据,短期保存可控的调试细节,敏感原文默认留在原系统里,需要时按权限重新读取。

例子:测试失败卡在哪里

拿一个常见任务看:

“帮我修一下测试失败。”

如果只有终端输出,我们可能只看到 pnpm test 跑过、报了错、agent 改了文件、后来又跑了一次。屏幕滚过去以后,细节就很难再整理。

如果把它写成证据链,过程会清楚很多:

  1. 用户目标:修复测试失败。
  2. 当前 plan:先找到测试命令,再复现失败。
  3. 工具调用:读取 package.json
  4. 工具调用:运行 pnpm test
  5. 权限裁决:Bash 命令被允许,理由是它服务于验证。
  6. 工具结果:测试失败,返回失败用例、堆栈摘要和退出码。
  7. 下一步影响:读取相关测试文件和实现文件。
  8. 文件编辑:修改实现。
  9. 再次工具调用:运行同一组测试。
  10. 对比结果:失败减少、失败类型变化,或仍然相同。

这条链的价值不只在“记录发生过什么”。它还能帮助下一步判断。

如果两次测试失败完全一样,说明刚才的修改没有触达原因。
如果失败类型变了,说明当前方向可能有进展。
如果测试耗时异常,可以看工具耗时。
如果命令被拒绝,可以看 permission decision。
如果最后要回滚,可以看 transcript 和文件变更轨迹。

这就是可观测性的意义:它让 Agent 的错误更容易被看见,也让下一轮行动更有根据。

对我写 Agent 的启发

写完这一篇,我会把 observability 当成运行时协议,放在 agent 设计的一开始。

自己写 agent 时,我会先做几件事:

  1. 每次任务都有 run_id。 无论是在本地 CLI、后台任务还是 CI 里运行,都能把一次任务完整串起来。

  2. 每次工具调用都有事件。 工具名、目标摘要、权限结果、耗时、状态、错误类型和成本都进入结构化记录。

  3. 权限裁决单独成事件。 allow、ask、deny 的来源和理由要能被审计,也能回到下一轮判断。

  4. 验证结果进入 trace。 测试、构建、lint、产物检查属于任务是否收敛的证据。

  5. 成本要能分摊。 token 和费用最好能分到模型请求、工具阶段、上下文压缩和重试次数上。

  6. 默认使用 metadata-first。 长期保存摘要、路径和状态;原始 prompt、源码片段和工具输出按调试场景短期保存。

我现在对 Observability 的理解可以收成一句话:

一个可维护的 Agent,要把过程当成一等公民。结果只是最后一帧,轨迹才是调试、信任和改进的基础。

参考资料

Claude Code 源码解析与架构拆解

OpenTelemetry 与观测实践

下一篇继续追的问题

这一篇把 Observability 理解成 Agent 的证据层。

核心结论是:

Claude Code 的每一次工具调用、权限裁决、错误、成本和上下文变化,都应该尽量变成后面还能追问的轨迹。

到这里,Claude Code 已经能计划动作、裁决动作、执行动作,也能记录动作。

但记录过程只是下一步的基础。Agent 一定会失败:工具会失败,命令会失败,测试会失败,权限会被拒绝,上下文会变得不够,模型也会判断错。

所以 09 要继续追的问题是:

Claude Code 如何使用这些证据,在失败以后重试、换路、回滚、恢复或请求用户介入?