Agent 跑完任务后,我们真正需要知道的不是一句"任务已完成",而是它在执行过程中每一步到底做了什么:第几轮调用了哪个工具、传入了什么参数、返回了多少 token、缓存是否命中、这一轮消耗了多少成本。Claude Code 的价值不只是记录日志,而是把 Agent 的执行过程沉淀为一套从记录、分析到反馈优化的闭环,让每一次运行都可追踪、可复盘、可改进。
一、Session Transcript:会话级完整记录
Claude Code 把每一轮对话、每一次工具调用、每一次 API 请求都记录在 ~/.claude/projects/{cwd-hash}/{sessionId}.jsonl 里。JSONL 是每行一个 JSON 对象,append-only 增量追加------产生一条写一条,中途崩溃了前面的不会丢。不是事后导出------每产生一条消息就追加一行,Agent 跑着的时候 Transcript 就已经在写了。
Transcript 解决三件事。
(1)根因追溯。出 bug 了不是翻日志猜 Agent 在想什么------逐轮回放每一步的输入输出,直接定位。
(2)会话恢复。关终端重开不用重新跑------从 Transcript 重建上一轮的 State,不需要重播历史。
(3)子 Agent 审计。Transcript 之间用 parent_event_id(UUID)关联成树状 DAG,子 Agent 的每一行记录通过这个指针挂回父 Agent 派生它的那个时刻,沿链可以追到任何一个操作的触发源。
每一条消息都有类型标记:user 是用户输入,assistant 是模型回复,system 是系统注入的上下文,attachment 是 Skills 或 Memory 的注入,progress 是进度更新,file-history-snapshot 是文件变更快照。每条带精确时间戳。父子 Agent 的 Transcript 存在两个独立文件里,通过 UUID 跨文件引用------组织成一张可追溯的因果关系图,而不是平铺的时间线。
为什么不用数据库?零依赖。Transcript 就是一个文本文件------cat 能看,jq 能解析。为什么不用全量覆写?append-only 保证崩溃不丢数据。代价是 JSONL 文件本身不是合法的 JSON(不是 [{...}] 格式),但对于逐行追加的场景,这个代价可以接受。
二、成本可观测:token 花在哪了
2.1 /cost 命令
/cost 是 Claude Code 内置的会话级成本快照命令。每次 API 调用后,系统更新一个内置的 usage counter。/cost 读取这个 counter,按模型(Opus/Sonnet/Haiku)、按 token 类型(input/output/cache_read/cache_creation)、按美元费用拆解输出。不需要重新扫描 Transcript------counter 是运行时维护的,实时更新。
价格常量维护在代码里------$3.00/M input、$0.30/M cache_read 等。每次 API 返回的 usage 字段包含实际消耗的 token 数,乘以模型对应的价格系数得出美元值。缓存命中的 token 走 cache 价格(十分之一),未命中的走 input 全价。
为什么不在 Transcript 里事后算?实时 counter 保证 /cost 是 O(1) 的------不会因为会话很长就要重读整个 Transcript。为什么按 token 类型拆?同一轮对话里,input、output、cache_read、cache_creation 的价格不同------不拆就看不出缓存到底帮了多少忙。让开发者在自己终端里实时感知钱花在哪了------不是"月底看账单",是"刚调了一次 Sonnet,花了 $0.12"。
2.2 缓存断裂检测
每次 API 调用前后,系统对比预期缓存命中量和实际命中量。调用前记录 12-14 维状态快照------systemHash、toolsHash、model、betas、cacheControlHash 等。调用后检查 API 返回的 cache_read_input_tokens。比预期下降超过 5%,绝对值超过 2,000 token------缓存断了。缓存断裂不会影响 Agent 的行为质量,不会产生任何可见错误------只有账单上的数字会跳。(检测机制让断裂在发生时被感知到,而不是等月底)
Anthropic API 的缓存机制是按前缀 hash 匹配的。前缀相同 → 缓存命中。前缀里任何一条消息变了 → 从那条往后的全部缓存失效,全部按原价重算。12-14 维快照就是为了定位"谁动了前缀"。
有些断裂是必要的------上下文压缩(AutoCompact)替换了部分消息,前缀必然变。断裂检测的目标不是"缓存永远不断"------是"区分计划内的断裂和计划外的断裂"。
2.3 Token Budget
Token Budget 不是简单地限制"每一轮最多给模型多少 token",而是一套贯穿 Agent 执行全过程的预算控制机制。它的目标不是粗暴截断,而是在任务执行、成本消耗和结果完整性之间做动态平衡。
首先,用户可以在 prompt 里直接声明预算。输入 +500k、spend 1M tokens 这类表达时,系统通过正则解析出期望的 token 规模,转化为本次任务的预算上限。也就是说,预算不是隐藏配置,而是可以显式控制的执行参数。
其次,它会监控任务是否还值得继续执行。连续多次触发 continue,但每次模型新增输出都不到 500 token------系统判定进入低收益循环,不再傻等 maxTurns 用完,而是提前终止。这是一种递减收益检测。
第三,它会在预算接近耗尽时主动提醒模型继续推进。token 消耗达到 90% 时,系统注入类似 Stopped at 90%. Keep working --- do not summarize 的提示。这个 nudge 把外部预算状态反馈给模型------模型自己不知道当前会话已经消耗了多少 token,还剩多少预算,必须由系统显式传进去,模型才能调整执行策略。
第四,它会在逼近 API 硬上限前主动停止------留出缓冲空间让模型完成当前步骤、整理结果。一旦撞上硬上限,任务被突然截断,输出不完整,甚至丢失关键状态。
最后,Token Budget 还可以和真实费用绑定。比如设置了 maxBudgetUsd 之后,系统会在每次 API 调用前检查当前累计成本。如果发现继续调用会超过美元预算,就会直接抛出 TerminalError,终止本次任务。这个机制不关心任务是否已经完成,它优先保证成本不会失控。
Token Budget 是贯穿 Agent 运行时的预算治理机制:解析用户预算、检测低收益循环、向模型反馈预算状态、避免撞上 API 硬上限、费用超标前强制停止。它控制的不是某一次模型调用,而是整个 Agent 任务的执行节奏和成本边界。
三、OpenTelemetry:指标、事件与链路追踪
本地 Transcript 和 /cost 解决的是"在终端里看"的问题。但 Agent 部署到团队后,需要一套外部监控体系来追踪几十个并发会话的集体行为。Claude Code 内置了 OpenTelemetry(OTel)SDK,可以在不改代码的情况下把运行数据输出到外部监控系统。
3.1 开启只需要一行环境变量
bash
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
开启后 Claude Code 通过 OTLP(OpenTelemetry Protocol,OTel 的标准数据传输协议)往 Collector 推送三类信号------metrics(指标)、log events(事件)、traces(链路)。协议支持 gRPC(Google 的高性能远程过程调用框架)和 HTTP/JSON,可以对接 Prometheus、Grafana Tempo、SigNoz、Datadog 等任何 OTel 兼容后端。
css
应用 / Agent / Claude Code
↓
OpenTelemetry SDK / Agent
↓
OpenTelemetry Collector
↓
Prometheus / Tempo / Loki / SkyWalking / Jaeger
↓
Grafana Dashboard
企业部署时,管理员可以通过托管配置文件统一开启,开发者的终端不需要手动设环境变量。托管配置放在 /Library/Application Support/ClaudeCode/managed-settings.json(macOS)或 /etc/claude-code/managed-settings.json(Linux),加一行 "CLAUDE_CODE_ENABLE_TELEMETRY": "1" 即可。
3.2 指标:Agent 运行状态的数字快照
指标是持续累加的数字,回答"整体趋势如何"。Claude Code 输出的 OTel 指标按关注点分三组。
成本组: ------ 管钱花在哪了。claude_code.token.usage 按模型和 token 类型拆分:input、output、cache_read、cache_creation。同一轮对话里 input 是全价、cache_read 是十分之一、cache_creation 是 1.25 倍------不拆分就看不出缓存省了多少。claude_code.cost.usage 按模型输出估算美元成本。这两个指标是成本归因的核心------按 team、repo、model 切片后可以看出哪个团队在哪个项目上烧了多少。
产出组: ------ 管 Agent 干了多少活。claude_code.session.count 记录启动的会话数。claude_code.lines_of_code.count 记录增删代码行数。claude_code.commit.count 和 claude_code.pull_request.count 追踪 Git 活动。claude_code.code_edit_tool.decision 追踪编辑被接受还是拒绝。这组指标连起来是 Agent 的产出线------会话多了但代码变更没跟上,说明大量会话在做探索而非产出;commit 多了但拒绝比例高,说明 Agent 改的东西被频繁回退。
效率组: ------ 管时间花在哪了。claude_code.active_time.total 拆成用户输入耗时和模型处理耗时。用户输入时间占比高,瓶颈在人;模型处理时间占比高但产出没跟上,Agent 可能在空转。
v2.1.161 之后 OTEL_RESOURCE_ATTRIBUTES 的值作为 label 附着在每个 datapoint 上------按 team、repo、project 切片。所有指标通过 OTEL_METRICS_EXPORTER 控制输出目标:otlp 走 Collector、prometheus 暴露 /metrics 端点、console 打印到终端。
3.3 事件:每一次决策的审计记录
指标回答"花了多少",事件回答"做了什么决策"。Claude Code 通过 OTel log events 把每一次关键决策结构化输出:
API 与成本: claude_code.api_request 记录每次模型调用的完整参数:input/output/cache_read/cache_creation token 数、美元成本、模型名、耗时。claude_code.api_error 记录错误类型和 HTTP 状态码。
工具调用: claude_code.tool_result 记录每次工具调用的成功/失败、耗时和参数。claude_code.tool_decision 记录权限决策------allow、deny 还是 ask。
权限: claude_code.permission_mode_changed 记录模式切换------从哪个模式切到哪个模式,谁触发的。
Hook: claude_code.hook_execution_complete 记录 Hook 执行完毕------哪个事件、哪个 Hook、是否触发了阻断。
MCP: claude_code.mcp_server_connection 记录 MCP 连接状态和传输类型。
Compact(上下文压缩): claude_code.auto_compact_triggered 在 token 达到约 83.5% 有效窗口时触发,记录压缩前的上下文使用率和 token 数。claude_code.auto_compact_completed 记录压缩后的 token 减少量。claude_code.auto_compact_failed 记录失败原因------断路器在连续 3 次失败后自动禁用当次会话的自动压缩。claude_code.pre_compact_hook_executed 记录 PreCompact Hook 是否成功向压缩 prompt 注入了会话状态。
六大类事件各自独立记录,但共享关联键------同一次工具调用,权限事件里看到它被 allow,工具事件里看到它执行成功,API 事件里看到它消耗了多少 token。每条事件都是结构化的 JSON,可以被 SIEM 实时消费,不是事后导出再解析的文本文件。
隐私设计上两个关键默认值:OTEL_LOG_USER_PROMPTS 默认关------用户的 prompt 内容不上传。OTEL_LOG_TOOL_CONTENT 默认关------文件内容、Bash 输出、diff 内容不上传。代码层面还有一层保护:logEvent() 的 metadata 默认只接受数值和布尔值,字符串必须经过一个 I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS 标记类型才能写入------防止开发者不小心把代码或文件路径写进遥测数据。
3.4 链路追踪:Agent 的因果关系图
如果指标告诉你"花了多少钱",事件告诉你"发生了什么决策",那链路追踪告诉你"这些决策是怎么串起来的"。Claude Code 的 session tracing 用五层 Span 建模一次 Agent 交互的完整生命周期:
interaction 是根 Span,代表用户的一次输入到 Agent 给出结果的完整回合。下面挂着 llm_request(模型调用,带 model、speed mode、token 消耗、首 token 时间等属性)、tool(工具被选中)、tool.blocked_on_user(等待用户确认的时长------单独一个 Span 追踪"人在回路"的时间)、tool.execution(工具真正执行)、hook(仅增强遥测 beta 可见)。
tool 和 tool.execution 拆成两个 Span 回答同一个问题:工具从被选中到真正执行,中间卡在哪。tool.blocked_on_user 耗时长,是人在犹豫。tool 到 tool.execution 之间耗时长,是权限系统或 Hook 在排队。
这五层 Span 通过 AsyncLocalStorage 做上下文传播------子 Span 自动挂在当前活跃的父 Span 下。Agent 并发多个请求时(warmup、topic classifier、主线程调用可能同时飞),每个 Span 实例独立归因。同时用 WeakRef + 30 分钟 TTL 清理悬挂的 Span------Agent runtime 中常态的 abort、uncaught exception、mid-query 中断都会产生未关闭的 Span,不清理会撑爆内存。
W3C Trace Context(W3C 标准化的分布式追踪上下文传播协议,让追踪 ID 在跨服务调用中保持一致)支持让 Claude Code 可以嵌入现有的分布式追踪体系。Agent SDK 和非交互式 claude -p 会话可以从 TRACEPARENT/TRACESTATE 环境变量读入上游 trace context。Bash 子进程可以继承当前 tool execution Span 的 TRACEPARENT------这样 Agent 调用的 Shell 命令产生的追踪也能挂回 Agent 的 Span 树里。
3.5 三类信号的分工
Claude Code 的 OTel 实现遵循一个明确的分离原则:指标管趋势,事件管行为,链路管因果。同一个 API 调用产生三种信号------指标上的一个 counter increment、事件里的一条 api_request log、Span 树上的一个 llm_request 节点。三种信号不同用途,不同存储,不同查询方式,但共享关联键。三种信号各自回答不同层次的问题。
指标:回答"整体趋势如何"。 指标是持续累加的数字,适合做仪表板和告警。查询模式是聚合------"过去一小时所有会话的 token 消耗总量"、按 team 切片、按 model 切片。查询成本极低(O(1) 的 counter read),可以高频刷新。但指标回答不了"为什么"------token 消耗突然涨了 30%,是模型换 Opus 了还是任务变重了还是缓存在某个时间点断裂了。指标告诉你症状,不告诉你病因。
事件:回答"这一次发生了什么决策"。 事件是结构化的、不可变的单次记录。查询模式是过滤------"过去 24 小时内哪些会话触发了 AutoCompact 失败"、"查一下 session_abc123 所有的权限决策"。每一条事件带精确时间戳和完整的上下文参数,可以回放审计链路。但事件存储成本高------每天几千个会话、每个会话几十个事件------不适合高频聚合查询。所以事件通常做抽样或只保留近期数据。
链路:回答"这些决策是怎么串起来的"。 链路是 Span 的树状结构,回答因果关系------"这次 API 调用的耗时大头在哪"、"工具从选中到执行中间卡在哪个阶段"。查询模式是钻取------从根 Span 往下逐层展开。链路数据量最大(每个 Span 带几十个 attribute),查询最复杂(需要 Span 之间的父子关联),但只有它能回答"为什么这次调用这么慢"。
三类信号的存储策略跟着查询模式走。指标走时序数据库(如 Prometheus),保留 90 天,做长期趋势分析。事件走日志系统(如 Loki),保留 30 天,做合规审计。链路走专门的 Tracing 后端(如 Tempo、Jaeger),保留 7 天,做实时排障。事件不适合当指标查------量太大、太慢。链路也不适合当事件用------Span 之间的父子关联在 flat 查询里会全部丢失。
这种分离是 OTel 社区的最佳实践,不是 Claude Code 发明的。但 Claude Code 在实现上的一致性做得好:同一个 API 调用产生的三种信号携带相同的 session_id 和 trace_id,在 Grafana 里可以从指标的 spike 一键跳到对应时间段的链路,再钻取到具体事件。三个信号不是三个独立系统,是同一个数据源的三种投影。
3.6 内部遥测通道
除了自建 OTel Collector 这条"自控"通道,Claude Code 还有一条内部遥测通道。启动后连接四个端点:GrowthBook(开源的功能开关和 A/B 测试平台,第七章展开)下发功能开关和 A/B 实验分组,Datadog 收 838 种 tengu_ 事件中约 44 种白名单(tengu_init、tengu_api_success、tengu_tool_use_error 等运维事件),Anthropic OTEL 1P 把 OTel 日志入 BigQuery,Anthropic Metrics 把 OTel 计数器入 BigQuery。白名单设计让第三方(Datadog)只拿到运维数据,用户行为和工具输出留在 Anthropic 自己的 BigQuery 里。CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 一键关闭全部四个。
四、执行质量监控:Agent 在空转吗
Agent 没人看着的时候,它会自己一直跑下去。Claude Code 用三个层次的机制来发现它什么时候在空转、什么时候该停下来。
4.1 递减收益熔断:三层防护体系
Token Budget 不只是成本控制工具------放在执行质量这个语境下,它是第一道防线。Claude Code 的 Agent 终止判定靠三层递进的防护,不是单一条件:
第一层:递减收益检测: 这是最精准的一层。系统维护一个 BudgetTracker 状态对象,只有四个字段------continuationCount(已自动继续的次数)、lastDeltaTokens(上一次检查后的 token 增量)、lastGlobalTurnTokens(上一次检查时的累计 token)、startedAt(任务启动时间)。没滑动窗口,没 ML 模型,没指数移动平均。
触发熔断需要同时满足三个条件:continuationCount >= 3(已经自动继续了至少 3 次,给了足够的预热期)、deltaSinceLastCheck < 500(本轮新增输出不到 500 token)、lastDeltaTokens < 500(上一轮也不到 500 token)。连续两轮低产出 → 判定为无效循环 → 主动终止。
500 token 这个阈值是怎么定的?一次正常的工具调用轮次------读文件、改代码、跑测试------通常产生几千到几万 token 的输出。低于 500 token 的输出通常意味着模型在读同一份文件、输出"让我再检查一下"之类的内容、或者做了微小改动又撤销。500 够大,不会把"任务完成"(约 30 token)误判为空转;够小,连续两次就能可靠地抓住空转。
三个设计细节。预热期:前 3 次自动继续免检。模型确实需要几轮来读文件、理解上下文,这几轮产出低是正常的。子 Agent 排除:递减收益检查显式跳过子 Agent。子 Agent 有自己的生命周期,Explore Agent 搜文件时的低产出不应该误触发主 Agent 的熔断。"Keep working --- do not summarize":模型在靠近 max_tokens 边界时会本能地开始总结收尾,这个 nudge 告诉它预算还够,继续干活。
第二层:maxTurns 硬上限: 最钝的一层------claude -p "fix lint" --max-turns 1。如果前两层都没拦住(比如模型每轮都产出大量 token 但全是废话),这一层是最后的保险。简单任务 2-4 轮,多文件操作 6-10 轮,复杂调试 10-20 轮,子 Agent 工作流 40-200+ 轮。
第三层:断路器: 给每种重试设独立上限。
三层的关系不是"一层不够再加一层"------是每层的精度不同。递减收益最准,断路器管特定路径,maxTurns 是兜底。三层同时跑,任何一层触发都能终止。
4.2 断路器:给每种重试设独立上限
递减收益检测管的是"整体上 Agent 还在不在产出",断路器管的是"单个操作路径上是不是卡死了"。Claude Code 为不同的重试路径设置了独立的上限,熔断后该路径在当前会话中不再触发。
OTK Recovery:连续 3 次即终止。 OTK(Out of Token Kill)Recovery 是上下文窗口接近上限时的升档恢复机制------模型换用更大的窗口继续。绝大多数需要升档的情况在第一次升档后就完成了。需要第二次的不到 5%。需要第三次的几乎不存在。连续 3 次升档还没解决 → 不是窗口不够大的问题,是任务本身出了问题,终止比继续升档更合理。
Reactive Compact:每轮最多 1 次。 上下文压缩(AutoCompact)将旧的对话历史替换为摘要,腾出空间继续执行。压缩一次约消耗 13,000 token 的缓冲区。如果每轮都在压缩------上一轮压缩完,下一轮又把空间填满了------Agent 在追着自己的上下文跑,没在推进任务。每轮限 1 次确保压缩是腾空间继续工作,而不是变成主要的运行时活动。
Stop Hook:stop_hook_active 防死循环。 Stop Hook 在 Agent 声明"我做完了"之后触发------Hook 返回 exit 2 强制 Agent 继续修复,返回 exit 0 放行。但这里有个陷阱:Agent 修完后再声明完成,Hook 又触发------无限循环。stop_hook_active 是一个布尔标志位,Hook 脚本通过 stdin 的 JSON 拿到它。第一次触发时值为 false------可以阻断。阻断后 Agent 继续工作,再次声明完成时 Hook 再次触发,此时值为 true------必须放行。不是计数器,是二态标志:第一次能拦,第二次必须过。这个设计让 Stop Hook 能强制 Agent 至少修复一次,但不会把它困在永远修不完的循环里。
AutoCompact 断路器:连续 3 次失败后禁用。 AutoCompact 调用 Haiku 做摘要压缩。压缩本身也可能失败------Haiku 超时、返回格式异常、摘要质量不合格。连续失败 3 次后,系统禁用当前会话的自动压缩功能,后续靠 Reactive Compact 和手动 /compact 命令。这是 broken-safe 模式:自动功能出问题时,关掉它比让它反复失败更好。
4.3 工具调用频率:发现僵尸工具
MCP 工具和 Skills 有一个容易被忽略的成本:Schema 税。每个 MCP 服务器注册时,它的工具名、描述、参数定义(Schema)会在每一轮对话中注入上下文------不管这轮用不用这个工具。一个典型的 MCP 服务器可能贡献 2,000-5,000 token 的 Schema。十个服务器就是 20,000-50,000 token------在一个 1M token 的窗口里占了 2-5%。而这些 token 是每一轮都要付的。
僵尸工具是那些 Schema 定义了但从不被调用的工具。Claude Code 内置工具的 Schema 加起来约 17,600 token------如果一个内置工具从注册以来从未被调用,这 17,600 token 里它占的那部分就是纯浪费。更常见的是 MCP 工具------团队装了某个 MCP 服务器,配置了 API key,但它提供的工具在实际使用中一个都没被触发过。
检测方式走 Session Transcript 的 tool_use 记录。系统遍历 ~/.claude/projects/ 下的所有 JSONL,提取每个 tool_use 块的 tool_name,按工具名聚合计数。连续 N 天计数为零的工具标记为待下线。过去 30 天内有调用但过去 7 天零调用的------降级为"低频工具",评估是否保留。过去 30 天零调用的------直接标记为僵尸,列入清理列表。
社区工具也在这个方向上做了不少工作。unclog 扫描所有 MCP 服务器和 Skills,标记 30+ 天未使用的条目,展示每个条目在每轮对话中消耗的基线 token 数。token-audit 更进一步------它不仅找僵尸工具,还识别 12 种效率异味,包括 CHATTY(工具输出过长但模型只用了其中一小部分)、REDUNDANT_CALLS(同一个工具在同一次对话中被连续调用且返回相同结果)、BURST_PATTERN(短时间内密集调用后长期零调用)。
组织层面的问题是工具膨胀。有社区报告提到团队在不到一个月内从 67 个 Skills 膨胀到 183 个------没人知道哪些还在用、哪些已经死了。每个人加自己需要的,没人清理。GitHub 上有一个功能请求(Issue #35319)要求 Claude Code 原生支持 Skill 级别的调用追踪------目前 Session Metadata 只记录 "Skill": 3(聚合计数),不记录具体是哪个 Skill 被调用了。在没有原生支持之前,工具调用频率分析靠 JSONL 扫描。
4.4 权限决策分析:从拒绝模式反推配置问题
权限系统每次做 allow/deny/ask 决策时,决策结果连同工具名、模式、时间戳被记录下来。这些决策日志不只是安全审计的素材------它们是配置优化的信号源。
大量连续 deny 意味着规则配得太严: 用户在 Default 模式下被弹窗弹到麻木------内部遥测显示约 93% 的弹窗用户点了"允许"。如果 deny 比例异常高,不是用户太激进,是 deny 规则写得太宽。比如 deny: Bash(curl:*) 把调试用的 curl localhost:3000 也拦了,用户加一条 allow: Bash(curl:localhost:*) 就能既保留安全约束又不影响正常开发。
大量 ask 且用户总是点"允许"意味着 Auto 模式的分类型阈值可以调。Auto 模式的分类型在阶段一(快速过滤)故意偏保守------约 8.5% 的误拒率,安全操作被拒后弹窗让用户确认。如果一个操作连续 N 次 ask 然后用户点允许,说明分类器对这类操作太保守了------可以调低阈值,减少不必要的弹窗。
权限决策日志通过 OTel 输出到外部 SIEM(如 Splunk、Datadog)做聚合分析。按团队、项目、工具类型切片后可以回答:哪个团队的 deny 率最高(可能需要对他们的 CLAUDE.md 做安全培训)、哪个工具触发的弹窗最多(考虑对该工具调宽 Auto 模式阈值)、哪个时间段权限异常集中(可能是某个新 MCP 服务器上线后的过渡期)。
权限数据也可以和成本数据交叉分析。同一个工具,Auto 模式下的调用频率和 Default 模式下的调用频率如果差距很大------Auto 模式下频繁调用、Default 模式下几乎不调------说明用户在有意识地避免这个工具(可能因为它的弹窗太烦人),但 Auto 模式把它放过了。这类工具需要单独审查安全性。
这些分析不是实时的------是事后聚合的。执行质量监控和成本监控的区别就在这里:成本要实时(刚调了一次 Sonnet,花了 $0.12),质量可以事后(过去一个月的权限模式是什么、怎么改进)。
五、/insights:AI 分析你使用 AI 的方式
/insights 是 Claude Code 2026 年 2 月静默上线的内置命令。它扫描本地所有会话 JSONL,用 Haiku 做六阶段分析,输出一份交互式 HTML 报告。AI 反过来分析你使用 AI 的方式------你是快速迭代型还是详细规划型;卡在哪里最多;哪些工作流高效、哪些反复失败。
阶段一:Scan。 扫 ~/.claude/projects/ 下所有 session JSONL。自动过滤短会话(少于 2 条用户消息、少于 1 分钟)、子 Agent 会话、内部 Facet 提取会话。
阶段二:Extract。 对每个 session 提取 12+ 项元数据------token 用量(按类型)、工具调用分布、Git 活动、用户中断次数、代码变更量、语言检测。长会话超过 30,000 字符时分块摘要,每块 25,000 字符,聚焦"要求了什么、做了什么、卡哪了、结果如何"。
阶段三:Facet。 Haiku 做定性评估。产出 13 种任务类别、6 级满意度、12 种摩擦类型、4 级完成度。结果缓存在 ~/.claude/usage-data/facets/,后续只分析新会话,不用每次都全量重跑。
阶段四:Aggregate。 六个并行 Haiku prompt 各自做不同维度------项目领域(4-5 个)、交互风格、3 个成功模式、3 个摩擦类别(附具体示例)、CLAUDE.md 建议、功能推荐、前瞻性自主工作流。
阶段五:Generate。 最后一次 LLM 调用,综合前段输出,生成总览:做得好的、阻碍进步的、快速尝试建议。
阶段六:Render。 渲染单文件 HTML------统计仪表板、可视化图表(每日活动、工具分布、语言分布、满意度分布)、叙述性板块。所有数据存本地,不上传。
为什么全部用 Haiku?全量 session 的数据量很大,用 Opus 跑六阶段成本太高。分类任务类别、判断摩擦类型、识别成功模式------这些定性判断 Haiku 足够胜任。已知局限:报告生成器可能编造统计数据。有用户报告声称"336 个数据库迁移 session",实际只有约 10 个。定性分析可参考,定量数据需要自己交叉验证。
六、AutoDream 与记忆维护
6.1 记忆腐烂问题
Auto Memory 在每次会话中持续记笔记------构建命令、架构决策、代码风格偏好、已解决的 bug------存在 ~/.claude/projects/ 下。经过 20-100 个会话后,这些笔记会自然退化。
相对日期失效。"昨天我们决定用 Fastify"------三周后"昨天"指的是哪一天,没人知道。矛盾条目。一条记录说"API 用 Express",另一条说"Fastify 迁移完成"------两个都是真的,但时态冲突。重复内容。同一条构建命令被存了 3 次,每条措辞略有不同。过时信息。关于已删除文件的调试技巧,检索出来就是噪音。
最致命的是 MEMORY.md 超出 200 行。Claude Code 启动时只加载 MEMORY.md 的前 200 行------超出部分永远不会被读到。如果关键的项目约束恰好被挤到了第 201 行,它们对 Agent 来说就不存在。
AutoDream 的做法很简单:一个后台子 Agent,在会话之间静默运行,持续整理记忆目录。
名字借自神经科学。REM 睡眠期间,大脑回放白天经历,巩固有用的连接,丢弃噪音。如同AutoDream 对记忆文件做的事一样。
6.2 五层门控:从廉价到昂贵
AutoDream 不是每次会话都跑------每次跑的成本不低(一个子 Agent 做多轮读写)。它用一套从廉价到昂贵的门控序列,任何一层不通过,后续更昂贵的检查直接跳过。
第一层:环境检查。 零 I/O 成本。检查四个条件:非 KAIROS 模式(KAIROS 有自己的记忆管线)、非远程模式、Auto Memory 功能已开启、GrowthBook 远程 flag tengu_onyx_plover.enabled 为 true。第三个条件是用户在 settings.json 里的开关,第四个是 Anthropic 服务端的灰度控制------两个都是 true 才会继续。
第二层:时间门。 一次 stat() 系统调用。读取锁文件的 mtime,计算距上次整合的小时数。默认阈值 24 小时------不足 24 小时直接跳过。为什么用锁文件的 mtime 而不是独立的时间戳文件?因为锁文件的 mtime 本身就代表"上次整合完成的时间",不需要额外维护状态文件。
第三层:扫描节流。 纯内存比较。距离上次扫描不到 10 分钟------跳过。这防止时间门通过后(刚好过了 24 小时),每次会话都重复扫描。10 分钟的冷却让扫描不要太频繁,但也不会错过太久。
第四层:会话门。 目录扫描。统计 mtime 晚于上次整合时间的 session JSONL 文件数量。默认阈值 5 个新会话------不够 5 个就不值得跑一次整合。一个实际案例:913 个会话的整合约需 8-9 分钟。
第五层:文件锁。 文件写入。
五层按成本从低到高排列,廉价检查在前。99% 的会话启动中 AutoDream 在第一层或第二层就被跳过了,不会走到 JSONL 扫描和子 Agent 调用。
6.3 锁机制:mtime 即状态
锁文件 .consolidate-lock 存放在记忆目录下,承担双重职责。文件内容是持有者的 PID(进程 ID),文件 mtime 是上次整合完成的时间戳。文件不存在代表从未整合过。(修改时间戳)
获取锁的流程有五步竞争检测。第一步,读取当前持有者 PID 和 mtime。第二步,检查锁是否超时------Date.now() - mtime < 1 小时 且 PID 仍存活,说明有活跃进程正在整合,阻塞。第三步,写入自己的 PID,mtime 自动更新到当前时间。第四步,重读文件验证 PID------如果写进去的 PID 不是自己的(并发竞争),放弃。第五步,返回旧的 mtime 值(用于失败回滚)。
失败回滚有一个精巧的设计。整合过程中崩溃了,锁文件的 mtime 需要恢复到整合前的值。rollbackConsolidationLock(priorMtime) 做了三步:清空 PID(不再持有锁)、utimes() 恢复旧 mtime(时间门重新打开)、让下一次触发的时间检查通过。恢复 mtime 等于宣告"这次整合从未发生"------下次时间门照常通过,照常重试。
崩溃恢复走另一条路径。进程被 SIGKILL 杀掉,锁文件留在磁盘上,PID 指向不存在的进程。一小时后锁自动过期(HOLDER_STALE_MS),任何进程都可以抢占。不需要心跳线程,不需要看门狗进程------mtime 本身就是超时检测。
6.4 四阶段执行协议
AutoDream 的子 Agent 跑 30 轮上限,执行四个阶段的 Prompt 协议。
Orient(定位)。 列出记忆目录,读 MEMORY.md,浏览已有 topic 文件。目标是建立当前记忆状态的心智模型------哪些 topic 已经存在、哪些是新增的、哪些看起来矛盾。这防止后续写出和已有文件重复的内容。
Gather(信号采集)。 按优先级搜索近期信号。优先走每日日志(logs/YYYY/MM/YYYY-MM-DD.md),其次检查已有记忆是否漂移------事实和代码库现状之间有了矛盾。最后才 grep JSONL Transcript------而且只搜索具体关键词,不遍历全量读取。"不要穷举搜索 Transcript",Prompt 里明确写了------只查你已经有理由相信存在的事情。
Consolidate(整合)。 对每条值得保留的信息执行三项操作。相对日期转绝对日期:"昨天" → "2026-06-07"。合并到已有 topic 文件而非创建新的------三个关于同一条构建命令的笔记,合并成一条干净的权威条目。删除已被新信息覆盖的旧记录------"Express"改成"Fastify 迁移完成"。
Prune & Index(修剪与索引)。 更新 MEMORY.md 索引文件------硬限制 200 行、25KB。每一行是 - [Title](file.md) --- 一行描述(<150 字符)。删掉指向过时/错误/被取代记忆的指针,缩减冗余条目(>200 字符的缩短行,细节移到 topic 文件),解决文件间矛盾。
为什么用 Prompt 协议而不是代码?四种整合操作都需要判断力------什么算矛盾、什么算重复、什么值得保留。规则穷举不了所有边界情况。用一种更便宜的模型(Haiku)做这种判断是值得的------它比规则引擎贵,但比人工整理便宜得多。
6.5 extractMemories 与 AutoDream 的双管线
AutoDream 不是唯一写记忆的自动化。Claude Code 还有 extractMemories------一个 Archivist Agent,在每次 Agent 回复后作为 Fork 子 Agent 运行,最多 5 轮,从刚结束的对话中提取可持久化的记忆。
两者的区别。extractMemories 管"从这一轮对话里发现了什么新的值得记住的东西"------粒度是单次会话,触发是每次回复后。AutoDream 管"之前发现的东西有没有过时、矛盾、重复"------粒度是跨越多次会话,触发是 24 小时 + 5 个会话。
两者互斥。它们都写同一个 memory 目录。如果主 Agent 在对话中已经手动写了记忆文件(用户说了"记住 X"),extractMemories 会检查 hasMemoryWritesSince() 并跳过该区间------防止人工写入和自动提取互相覆盖。同样,AutoDream 和 extractMemories 不会同时跑------锁机制保证。
extractMemories 有一个反直觉的设计:Fork 子 Agent 和父 Agent 共享相同的 System Prompt、工具列表、模型和消息前缀。这不是为了省 token------是为了命中 Anthropic 的 Prompt Cache。相同的 Cache Key 前缀让缓存命中率达到约 92%。如果子 Agent 改了任何 Cache Key 参数(比如 effort: 'low'),命中率会从 92.7% 掉到 61%。
6.6 安全边界:只读的 Agent,只写的记忆
AutoDream 的子 Agent 跑在最严格的限制下。读操作不受限------Read、Grep、Glob 可以访问整个文件系统。写操作只能落在 memory 目录内------FileEdit、FileWrite 的路径必须属于记忆目录树,任何源代码文件、配置文件、项目文件都不能修改。Bash 只能跑只读命令------ls、find、cat、stat、wc、head、tail。重定向和写操作被拒绝。MCP 工具和 Agent 工具全部禁用------不能调外部 API,不能 spawn 更多子 Agent。skipTranscript: true------AutoDream 自己的内部过程不会污染主对话历史。
为什么读不受限但写受限?AutoDream 需要读取 Transcript 和代码库来判断记忆是否过时------"上次记录的 API 端点还在用吗?"这需要读取源代码。但它不需要修改源代码来回答这个问题。读宽写窄的策略让 AutoDream 能准确判断漂移,同时保证它不会意外破坏任何东西。
6.7 设计哲学:Sleep-time Compute
AutoDream 的理论基础来自一篇 2025 年 4 月的研究论文《Sleep-time Compute: Beyond Inference Scaling at Test-time》。核心发现:在空闲期间预处理上下文可以将推理成本降低约 5 倍。AutoDream 把这个理论工程化了------Agent 的"睡眠"时间变成了有用的计算时间。
但 AutoDream 是 broken-safe 的。自动整理记忆很好------但更关键的是"整理崩了之后,记忆不会丢"。PID 锁过期机制确保崩溃不会让锁永久有效。mtime 回滚确保失败后系统可以重试。工具限制确保即使子 Agent 被 prompt injection 攻击,它也只能写 memory 目录。核心不是"可靠",是"失败不产生连锁损害"。
七、A/B 测试与功能开关
7.1 GrowthBook 集成架构
Claude Code 用 GrowthBook(开源的功能开关和 A/B 测试平台,通过远程配置下发 flag 值给客户端 SDK)管理所有新功能的灰度上线。它的集成方式不只是一层 SDK 封装------它实际上是一套运行时控制面:功能开关、A/B 实验、远程熔断、模型热切换、遥测开关,全管。
启动时 GrowthBook 客户端附加用户属性:platform、node_version、is_ci、is_claude_ai_auth、arch。这些属性作为定位参数传给 GrowthBook 服务端,服务端预计算该用户应该在哪些实验的哪个分组里(remoteEval: true),客户端只缓存结果。这意味着 Claude Code 自己不做分组逻辑------它只需要告诉 GrowthBook"我是谁"。
远程配置下发后写本地磁盘缓存。网络不可用时走缓存。优先级链分五层:
| 优先级 | 来源 | 说明 |
|---|---|---|
| 1(最高) | CLAUDE_INTERNAL_FC_OVERRIDES 环境变量 |
仅限 Anthropic 内部 eval harness |
| 2 | /config Gates 面板本地覆盖 |
仅限 Anthropic 内部用户 |
| 3 | GrowthBook 远程下发 | 服务端预计算的值 |
| 4 | 磁盘缓存 | 跨进程持久化,下次会话复用 |
| 5(最低) | 代码内硬编码默认值 | 函数参数的 fallback |
五层设计意味着一个 flag 的值可以被多级覆盖。外部用户只能用到 3-5 层------Anthropic 内部的两层覆盖不编译进公开发行版(Bun 编译时 feature() 做死代码消除)。
7.2 三种读取语义
不是所有 flag 都需要最新的值。Claude Code 为不同类型的 flag 设计了三种读取策略,在时效性和可用性之间做权衡。
容忍过期(_CACHED_MAY_BE_STALE)。 用于低风险功能------启动关键路径上的 flag、UI 调整、非安全相关的功能。优先走缓存,网络慢也不阻塞启动。比如 tengu_ant_model_override(内部用户模型热切换)、tengu_event_sampling_config(事件采样率)。
安全阻塞(checkSecurityRestrictionGate)。 用于安全相关 flag------在确定 flag 值之前不继续。必须等到 GrowthBook 初始化完成才读取。比如 bypassPermissionsKillswitch------在确认远程没有下发熔断指令之前,不能放行任何权限操作。
缓存或阻塞(_CACHED_OR_BLOCKING)。 用于授权类 flag。如果缓存值为 true------信任它,继续。如果缓存值为 false------阻塞直到拿到新鲜值为止。比如用户权限白名单------不能因为网络问题就默认为"没有权限"。
三种策略的选择取决于一个核心判断:用过期值执行一个操作的代价,和因为等待新鲜值而阻塞的代价,哪个更大。
7.3 编译时与运行时:双层开关架构
Claude Code 有 87 个已发现的 feature flag,但它们不全是 GrowthBook 管理的。从源码泄露分析看,这些 flag 分两层。
编译时 flag(feature('XXX'))。 用 Bun 的死代码消除(tree-shaking,编译时移除不会被执行的代码分支)在构建阶段做判断。feature('BUDDY') 为 false 时,整个 Buddy 系统的代码(18 种物种、4 档稀有度、5 项属性、Mulberry32 PRNG 确定性生成)根本不存在于公开发行版里。这些 flag 管理的是"这个功能有没有写完"------没写完的东西,就连代码都不应该有。
运行时 flag(tengu_* GrowthBook 远程)。 用 GrowthBook 在运行时下发。这些 flag 管理的是"这个功能给谁开"------写好的功能,逐步放量。比如 tengu_onyx_plover 控制 AutoDream 的灰度比例,tengu_kairos 控制 KAIROS 模式仅限内部员工。
两层之间没有直接对应关系。一个功能可能编译时 flag 已经开了(代码存在),但 GrowthBook flag 还关着(不触发)。也可能编译时 flag 关着(代码不存在),GrowthBook flag 无论如何都是无效的。"flag 关闭"不等于"功能不存在"------它只代表功能在当前版本的生命周期阶段。
7.4 Flag 生命周期:从开发到全量
每个 flag 不是简单的"开/关"二元状态------它代表这个功能在哪个生命周期阶段。
flag 关闭 = 开发中。 代码在内部版本里,功能还不稳定,通过编译时 flag 关在公开发行版外。
flag 打开但非全量(0%-100% 灰度)= 实验。 代码已发布,GrowthBook 按百分比分批放量。每次放量看 Datadog 的 tengu_* 事件------错误率、延迟分布、用户体验指标。如果异常,远程关掉 flag,不需要发新版本。
GrowthBook flag 删除 = 全量。 所有目标用户的客户端都已经在执行这个功能,flag 成了多余的查表操作,删掉它反而省性能。
几个处于不同阶段的典型 flag:
KAIROS(150+ 处引用,未发布)------编译时 flag 开着(内部版本可用),GrowthBook flag tengu_kairos 仅限内部员工。24/7 后台常驻 daemon,不需要用户主动输入就能自主行动。这是离公开发布最远的 flag------150+ 处代码引用说明功能已经相当完整,但 GrowthBook flag 对所有人都关着。
COORDINATOR(多 Agent 编排)------通过环境变量 CLAUDE_CODE_COORDINATOR_MODE=1 开启。四阶段工作流:Research(workers 并行)→ Synthesis(coordinator 自己做)→ Implementation(workers)→ Verification(workers)。Coordinator 只有三个权限:派活、传话、叫停,明确禁止"委派理解"。
BUDDY(Tamagotchi 风格 AI 伙伴)------18 种物种(鸭、龙、蝾螈、水豚、蘑菇等),4 档稀有度,1% 闪光概率,5 项属性(DEBUGGING/PATIENCE/CHAOS/WISDOM/SNARK)。2026 年 4 月 1 日愚人节彩蛋,4 月 1-7 日通过彩虹色 /buddy 通知引导孵化。用户 ID 通过 'friend-2026-401' 盐值哈希后用 Mulberry32 PRNG 确定性生成------无法刷初始。
7.5 A/B 测试的实验追踪
实验暴露(Exposure)追踪是 A/B 测试的基础:哪个用户、在哪个实验、在哪个分组、看到了哪个版本的功能。Claude Code 的处理有三处值得一提。
去重。 loggedExposures 集合确保每个 flag 每个会话最多上报一次。同一个 flag 在热代码路径上被反复调用 getFeatureValue()------如果每次都上报暴露事件,遥测数据会被同一对 experiment_id + variation_id 淹没。
延迟上报。 如果 flag 在 GrowthBook 初始化完成之前被访问,暴露事件先进 pendingExposures 队列,初始化完成后批量 flush。不丢数据,也不阻塞启动。
磁盘缓存全量替换。 服务端删掉的 flag,客户端磁盘缓存里也删掉------不是累积。这防止 flag 的残留缓存影响客户端行为。一个 flag 在服务端被下了,客户端就应该立刻停止使用它------即使这意味着功能回退到默认行为。
实验分析链路:getFeatureValue() → 缓存查询 → 暴露事件去重 → experiment_id + variation_id 事件带上 user_attributes → Datadog / BigQuery → 按实验分组对比指标。
7.6 远程熔断:不需要发新版本就能关功能
GrowthBook 不只是灰度工具------它也是远程熔断器。几个 flag 直接体现这个能力。
tengu_amber_flint------Agent Teams 的 kill switch。发现严重 bug 后 Anthropic 可以在服务端把这个 flag 设为 false,所有客户端的 Agent Teams 功能立即禁用,不需要用户更新版本。
tengu_max_version_config------版本 kill switch。使用 _BLOCKS_ON_INIT 策略,启动时必须拿到最新值。服务端下发一个最低版本号,低于这个版本的客户端被强制停止工作。这是最硬的一层------不是关某个功能,是关整个客户端。
tengu_frond_boric------Sink 总开关。一个远程 flag 控制整个遥测数据管道(sink pipeline,将事件写入存储后端的通道)的开关。把这个 flag 翻成 false,所有遥测数据写入停止。
这三个 flag 代表三种熔断粒度:关功能、关版本、关管道。不是每个 flag 都需要这种能力------大部分功能的故障不致命,下个版本修掉就好。但紧急情况下,服务端 30 秒就能阻止所有客户端继续跑有问题的代码路径。
7.7 自研 Agent 为什么需要功能开关
功能开关和 A/B 测试在自研 Agent 里几乎总是被跳过。功能加了、觉得好、上线。三个问题:
没有 A/B 测试,说不清"好了多少"。一个优化声称提升了 20% 的完成率------对照的是哪组用户?没有对照组,"20%"是自己和自己比的。局部测试中看起来好的改动,在全局分布上可能是负面的。
没有开关,出了 bug 只能回滚整个版本。一个关键路径上的问题,修复流程是:定位 → 改代码 → 发 PR → Code Review → CI → 发版 → 用户更新。从发现到修复的周期以天或周计。有了远程 flag,翻一下开关就行了------30 秒。
没有实验数据,功能的生命周期没有可观测性。一个灰度到 50% 的功能------它的错误率比旧版本高还是低?用户在这个功能开启后行为有什么变化?没有实验追踪,这些都不可知。功能上线靠直觉,下线靠运气。
八、Harness 工程:模型外面那层壳
8.1 五层 Harness 架构
Harness(线束/约束层)指模型外面所有确定性约束的总和------CLAUDE.md、Memory、Hooks、Permissions、Skills、MCP 配置。模型是概率的,Harness 是确定的。ETH Zurich 的实验数据:CLAUDE.md 单独只能提升约 4% 的可靠性,五层全上才能稳定达到生产级。
| 层 | 内容 | 作用 |
|---|---|---|
| Memory | CLAUDE.md、MEMORY.md、Rules | 上下文和项目约束 |
| Hooks | PreToolUse/PostToolUse 脚本 | 动态强制检查 |
| Tools | MCP 服务器 | 外部能力接入 |
| Permissions | settings.json deny/allow 规则 | 静态边界 |
| Observability | 决策日志、成本追踪、异常检测 | 从决策中学习 |
设置顺序是 Memory → Hooks → Tools → Permissions → Observability------护栏在能力之前。一个项目先有 CLAUDE.md 和 Hook 定义了边界,再接入 MCP 工具、配权限规则、上可观测。顺序反过来------先接工具再想安全------后期补安全的工作量指数增长。
8.2 前馈控制与反馈控制
前馈控制(Feedforward)在模型行动前告诉它边界------System Prompt 注入安全规则、CLAUDE.md 注入项目约束、Permissions 注入工具限制。反馈控制(Feedback)在模型行动后验证结果------PostToolUse Hook 检查输出、Linter/测试/CI 验证、失败信号回到 Agent 或告警。
前馈靠模型遵循指令------模型可能忽略、可能理解偏差、可能遗忘,可靠但不完全可靠。反馈靠代码执行------不依赖模型判断,不遵循就报错。两条路径解决不同的问题:前馈减少大部分不想要的输出(用低成本的文本约束),反馈兜底前馈漏掉的(用高成本的代码检查)。真正重要的是让前馈规则持续向反馈规则演化。今天写在 CLAUDE.md 里的文本约束------"每次改完后跑 linter"------明天应该变成 PostToolUse Hook 里的自动执行脚本。能走确定的,不走文本的。
8.3 规则生命周期管理
规则不是写进文件就永远有效。创建 → 加载(每次会话注入上下文)→ 触发(被实际匹配到)→ 验证(触发后检查是否仍有效)→ 刷新或过期。长期不触发的规则自行老化------要么习惯已经养成,要么这条规则描述的问题已经不存在了。
Cargo-cult 规则是最常见的问题。从别人项目拷贝来的 CLAUDE.md 里有一些从未触发过的规则------它们看起来像安全约束,实际上占着 token 位置但从未阻止过任何操作。(改完 bug 才发现那条"严禁在生产环境执行部署"的规则从 day one 就没触发过------因为项目根本没有生产环境。)需要被定期审计、标记、清理。社区建议每 3-6 个月做一次 Harness 审计------模型升级后 Harness 也跟着变。
8.4 跨会话状态保存
Agent 在长任务中迟早会触发上下文压缩。压缩替换了旧的对话历史------如果关键决策没有被写入摘要,它就丢了。PreCompact Hook 在压缩前触发------这是最后一次机会把还没写入摘要的状态落盘。
PreCompact → 保存 → SessionStart → 恢复。两个 Hook 配对使用,形成一个跨越压缩周期的状态保存/恢复循环。Transcript 记录的 Agent "做了什么",检查点文件记录的是"为什么做到这里、下一步该做什么"。两者互补:Transcript 用来回溯问题,检查点用来继续前进。
九、企业落地的可观测架构
Claude Code 自身的可观测机制解决的是单机、单会话的问题------Transcript 记录了这一轮发生了什么,/cost 告诉你花了多少钱,/insights 帮你复盘。但当 Claude Code 部署到整个团队时,需要一套企业级的可观测架构来管住几十个开发者、几百个并发会话的集体行为。
五根支柱
企业 AI 可观测在 2026 年收敛到五根支柱,每根管一类风险。不是每根都要第一天就上全,但漏了哪根,对应的风险就会在某个周五晚上找上门。
| 支柱 | 管什么 | 为什么重要 |
|---|---|---|
| Performance | P50/P95/P99 延迟、吞吐、首 token 时间、错误率 | 最接近传统 APM,多数工具开箱支持 |
| Quality | 幻觉率、指令遵循度、输出漂移 | 200 OK 不代表输出正确------Agent 可能返回格式完美的错误结论 |
| Reliability | API 错误、限流命中、超时、供应商故障、降级行为 | AI 供应商是第三方依赖,故障模式和传统 API 一样多 |
| Safety | Prompt 注入检测、PII 暴露、越狱尝试、护栏有效性 | Agent 自主运行时这些不是"可能发生"------是"什么时候发生" |
| Cost | 每次请求的 token 归属、团队支出、模型组合效率、缓存节省 | 最容易被跳过的一根,跳过的代价是一个周末的意外账单。 Gartner 预测 2028 年 50% 的 GenAI 部署会投可观测性------现在只有约 15% |
网关架构:所有流量过同一个卡口
企业部署的标准模式是 LLM 网关------Claude Code 不直接连 Anthropic API,而是通过一个网关中转。网关是控制面:认证路由、限流负载均衡、缓存(精确 + 语义)、护栏检查、成本遥测。一套规则管所有开发者,不需要每人配一遍。
网关的五层成本优化栈,Gartner 2026 年认可的最佳实践:
第一层------Provider 路由。 在质量底线内选最便宜的模型。Sonnet 和 Haiku 在大部分日常任务上表现接近,但价格差数倍。路由策略不是一刀切------硬任务走 Opus,日常走 Sonnet,搜索走 Haiku。
第二层------精确缓存。 Hash(model、messages、parameters),相同请求直接返回缓存结果。Agent 内部循环中的命中率 30-60%,因为同一个 Agent 在连续几轮中会重复相似的读取和搜索。面向用户的对话命中率低------每次问题不同。
第三层------语义缓存。 Embedding 相似度匹配。支持类工作负载命中率 20-60%。阈值是关键------太高命中率低(没省到钱),太低输出退化(省了钱但错了)。
第四层------虚拟 Key 预算。 这是最重要的一层,也是反直觉的一层------不是等账单来了再追查,是在请求发出前就计算会不会超预算。一个开发者一个虚拟 Key,挂日预算和月预算。60% 告警、80% 限速、100% 拒绝------不是惩罚,是事前防护。
第五层------OTel 成本遥测。 每条 trace 带 gen_ai.usage.cost_usd、tokens、cache_hit 作为 span attribute。按 tenant_id、model、provider 做 label------你不是在看"这个月花了多少钱",你是在看"每个开发者每完成一个任务花多少钱"。单位经济学从 token 计数升级到产出归因。
Agent 特有的可观测挑战
Agent 的成本是乘性的,不是加性的。一次用户提问触发 4-6 次 LLM 调用------embedding → 编排路由 → 子 Agent → 综合 → 护栏检查。每层都可能产生自己的延迟和 token 消耗。传统 API 的可观测按单次请求追踪就够了,Agent 需要把多步调用串成任务级链路------否则你看得到每棵树的成本,看不到整片森林花了多少。
这种乘性特征意味着断路器、预算预扣和优雅降级不是可选项------Agent 递归重试能在 30 分钟内烧掉数千美元,不是罕见事故。
十、工程方法:企业落地可观测与成本控制的常用模式
前面九章拆的是 Claude Code 怎么做可观测。这一章切换视角------企业自己搭 Agent 可观测和成本控制体系时,有哪些经得起验证的架构模式可以选。
10.1 三种接入模式:网关、SDK 内嵌与 Sidecar 旁路
Agent 可观测性的第一个决策是数据从哪来。2026 年行业收敛到三种接入模式,各自解决不同的场景。
(1)网关模式------不改代码,所有流量过同一个卡口。 在 Agent 和 LLM 供应商之间加一层网关代理(如 LiteLLM、Kong AI Gateway、Solo.io agentgateway)。Agent 不直连供应商,所有请求经网关转发。网关在这一跳上做四件事:认证(JWT 验证、API Key 路由到虚拟 Key)、注入追踪头(统一 trace_id 跨所有 Agent 生效)、采集指标(每次调用的 token 数和延迟)、执行预算策略(超预算直接拒绝,不等月底)。
网关模式的优点是零侵入,Agent 代码不用动,甚至不需要知道网关存在。缺点是额外一跳延迟(通常 5-15ms),以及网关本身是高可用单点------网关挂了,所有 Agent 调用都不可用。
(2)SDK 内嵌模式------每一行业务代码都可以被追踪。 Agent 框架内部嵌入 OpenTelemetry SDK。每次 LLM 调用、每次工具执行、每次检索都自动产生 Span。2026 年的标准做法是在 GenAI 语义约定(gen_ai.request.model、gen_ai.usage.input_tokens 等标准属性名)之上扩展 Agent 专属字段------agent.id、agent.task、agent.tools_available。
SDK 的优点是深度------可以看到 Agent 内部的决策链,不只是"调了什么模型",而是"这一轮为什么选了工具 A 而不是工具 B"。缺点是它属于合作型遥测(Cooperative Telemetry)------Agent 进程控制遥测输出,被 prompt injection 攻击的 Agent 可以伪造或压制自己的追踪数据。SDK 模式适合调试和性能分析,不适合安全审计。
(3)Sidecar 旁路模式------和 Agent 同机但不同进程。 一个独立的观测进程(或 eBPF 内核探针)从 Agent 外部抓取数据。比如在 MCP(Model Context Protocol,模型上下文协议)协议层截获 JSON-RPC 消息,或在网络层抓取 HTTPS 请求的元数据(目标 IP、字节数、频率)。Agent 进程完全感知不到观测的存在。
Sidecar 模式的优点是防篡改------Agent 无法关闭或伪造它不知道存在的数据采集。缺点是信息深度不够。Sidecar 看得到"调了哪个 MCP 工具、耗时多少",但看不到"模型为什么决定调这个工具"。代表实现包括 ARMO Kubescape(eBPF 内核级采集)、service mesh 层遥测(Envoy/Istio)。
三种模式不是互斥的。2026 年成熟企业的标准组合是网关 + SDK:网关做集中的认证、路由、预算控制和成本归因;SDK 做深度链路追踪和性能分析。Sidecar 用于合规要求高的场景(金融、医疗),提供独立于 Agent 进程的审计证据。
10.2 信任分层:数据源的可信度不一样
不同采集方式产生的数据,可信程度不一样。ARMO 在 2026 年提出一个四层分级,直接决定什么数据能用在什么场景:
| 层级 | 采集方式 | 可被 Agent 篡改? | 适用场景 |
|---|---|---|---|
| Tier 1:防篡改 | 内核 eBPF、云审计日志(CloudTrail) | 否------Agent 进程无法触及内核层 | 合规证据、安全事故调查 |
| Tier 2:部分防篡改 | Sidecar 采集、网关代理层采集 | 否------Agent 所在的 Pod 或主机可达,但观测进程独立 | 安全告警、异常检测 |
| Tier 3:合作型 | OTel SDK 内嵌、框架回调 | 是------Agent 进程控制遥测输出 | 性能调试、开发排障 |
| Tier 4:可抵赖 | 应用日志、Agent 自述报告 | 是------Agent 可以压制或伪造 | 仅供参考,不能作为审计证据 |
这个分级的关键含义:追踪数据不能一视同仁。因合规原因查安全事件时,Tier 4(Agent 自己的日志)没有法律效力------Agent 完全可以说"我没调过这个文件",单靠应用日志无法证伪。同一件事必须至少有 Tier 1 或 Tier 2 的独立证据。
实践中,一个设计良好的观测体系会在关键路径上同时采集 Tier 1 和 Tier 3 的信号------内核 eBPF 记录"进程 A 写入了文件 X",OTel Span 记录"Agent 此时认为自己正在写配置文件 Y"。两个信号对得上,可信。对不上,说明 Agent 在说谎或出了 bug。
10.3 运行时预算控制循环
传统 FinOps(给 VM 打标签、月底看账单)对 Agent 不适用------Agent 的成本是行为驱动的,规划循环、工具调用、重试、子 Agent 派发,每一步都在产生费用。Oracle 2026 年提出的运行时预算控制循环是解决这个问题的参考架构:
准入检查 → 步前预扣 → 步后结算 → 重规划或执行
准入检查(Admission)。 任务启动前回答一个问题:以当前剩余预算,这个任务有没有可能完成?不做精确预测------Agent 的实际 token 消耗波动太大。做一个下限判断:剩余 $0.05,而一次 LLM 调用最低也要几分钱------不可能,直接拒绝。
步前预扣(Pre-step Reservation)。 每一步执行前,按最坏情况预扣成本。Redis INCRBYFLOAT 原子操作扣减预算余额。和"API 调用后检查余额"有本质区别:预扣防并发,后查不防 。十个并发 Agent 每个都看到"还有 10预算",然后每个都花2------$20 出去了。硬上限在请求跨过网关之前生效,不是在响应回来之后。
步后结算(Post-step Reconciliation)。 用实际消耗替换预扣值,更新剩余预算预测。实际消耗远小于预扣(工具调用比预期便宜),预算释放回池。实际消耗超出预扣,差额从剩余预算中追加扣除。
重规划或执行(Replan or Enforce)。 剩余预算低于阈值时不直接拒绝------优先级降级。80% 时路由到更便宜模型。95% 时限制输出 token 长度。100% 时只允许只读工具。110% 时终止。拒绝让任务失败;降级让任务还能继续,只是用的资源更少。
10.4 成本归因:从 token 数到部门账单
可观测性的终点不是仪表板上的数字,是可追溯到人和团队的账单。
虚拟 Key 分割。 每个开发者一个虚拟 API Key,挂在团队和成本中心下面。网关在转发请求时把虚拟 Key 映射为真实 API Key,同时把 team_id、project_id、cost_center 作为 Span attribute 写入追踪。成本汇总时按这些 label 聚合即可。
多供应商归一化。 一个 2,000 人的团队可能同时用 Anthropic、OpenAI、GitHub Copilot、Azure OpenAI。每家定价不同(按 token、按席位、按请求)。网关层做归一化------不管供应商怎么定价,最终产出统一美元列,按 developer_id 分组。月底导出一张表给财务。
P10 问题。 前十百分位的开发者消耗通常是中位数的 10 倍。这不一定是问题------重度用户产出可能也最高。但要被看到。设置软告警在 80% 个人预算上限、硬暂停在 110%,确保没人能在不知情的情况下烧超。
Tool-level 成本归因。 更进一步------不光知道哪个开发者在花钱,还知道具体哪个 MCP 工具在烧钱。一个昂贵的向量搜索 API 每次调用 $0.15,而它 90% 的调用场景可以被成本五分之一的开源替代方案覆盖------这就是优化点。每工具的平均 cost/task-completion 是最接近 Agent ROI 的指标。
10.5 熔断与降级:什么时候该叫停
Agent 出错时成本不是线性递增,是指数级的。递归重试、上下文压缩失败重试、子 Agent 无休止搜索------第四章已经提过,没有断路器的情况下,全球每天约 25 万次 API 调用浪费在注定失败的路径上。
熔断动作按严重程度分级,而不是一刀切。Oracle 2026 年建议的五级响应:
| 级别 | 触发条件 | 动作 |
|---|---|---|
| 告警 | 预算消耗 80% | 写日志,发通知,Agent 正常运行 |
| 软限制 | 预算消耗 100% | 发 Webhook 告警,收窄工具权限(只允许只读工具) |
| 硬限制接近 | 预算消耗 110%(软限制后继续烧) | 降级到更便宜模型,限制输出 token 长度 |
| 硬限制 | 预算消耗 120% 或检测到病态行为 | 终止运行,记录 trace-linked intervention(策略 ID + 触发条件 + 原因) |
| 紧急熔断 | 远程 kill switch 下发 | 立即强制停止所有会话,优先级高于一切本地配置 |
每一级动作都 trace-linked------不是"管理员手动杀了进程然后口头记录",而是干预事件直接写入追踪系统,带策略 ID、触发条件和执行时间。事后审计时能从 trace 里看到"这一刻 Agent 被终止了,原因是 budget_policy_v2,触发条件 budget_exhausted_120%"。
10.6 参考栈:一套能落地的组合
把前面的模式串起来,一个 2026 年中等规模企业(50-500 开发者)的可观测参考栈长这样:
追踪。 OpenTelemetry SDK(Agent 内部深度追踪)+ Grafana Tempo(Trace 存储)。网关层自动注入 trace_id,全量保留 7 天热数据、30 天冷数据。
指标。 Prometheus 拉取网关和 SDK 产生的指标。核心面板:按模型/token 类型/团队的成本趋势、P50/P95/P99 延迟、错误率。保留 90 天。
评估。 采样 5-10% 的生产运行做自动评估。Claude Opus 或 Gemini 做 LLM-as-Judge(用大模型做裁判,评估另一模型的输出质量),评指令遵循度、幻觉率、完成度。结果写入追踪系统作为 Span attribute。
成本。 PostgreSQL 或 BigQuery 做成本分类账。每笔 API 调用一条记录,带 developer_id + team + project + model + dollar_cost。小时级汇总,日级 chargeback 报表。
审计。 工具调用日志(谁、什么时间、调了什么工具、参数摘要、结果摘要)保留 7 年。受监管行业走 WORM(Write Once Read Many,不可篡改的合规存储)存储。
告警。 Prometheus AlertManager 规则驱动。成本类告警进 Slack/钉钉,安全类告警进 PagerDuty/飞书紧急通知。
十一、设计思想借鉴
最后把视角拉回 Claude Code。拆完它的全部可观测机制,有三个不依赖具体实现的工程原则可以平移。
可观测是第一行代码,不是最后一层包装。 Transcript 的设计------JSONL append-only,零外部依赖------说明一件事:Agent 的可观测不需要先搭一套监控系统。第一行可观测代码就应该是 Transcript。出了问题,它是唯一能告诉你刚才发生了什么的东西。
成本是乘性的,控制面必须在调用前。 Agent 一次请求触发多次 LLM 调用------成本不是加性的,是乘性的。原子预算计数器在调用前生效,不是在账单出来之后追溯。事后查账是财务工作,事前预扣是工程决策。
反馈回路靠自动化降本,不靠人主动跑。 /insights 自动跑、报告自动生成。AutoDream 自动整理记忆。运营成本接近零,反馈才会高频发生。触发门槛是"你记得去跑一次"的机制,有效频率接近零。
可观测不是运维,是产品。Claude Code 把可观测嵌进了使用体验------/cost 在对话里就能看到,/insights 在终端里跑,Transcript 在本地可以打开。不是在 Grafana 上看指标的运维视角,而是开发者在自己终端里随时感知 Agent 行为的用户视角。
所有自动维护的前提是出错了不会更糟。AutoDream 的 PID 锁和崩溃恢复、GrowthBook 的本地缓存降级------每一个自动化都是 broken-safe 模式。自动整理记忆很好,但整理崩了之后,记忆不会丢。
隐私设计是产品决策,不是合规包袱。默认不收集 prompt 和工具输出,白名单限制发给第三方的事件类型------不是因为法律要求,而是因为用户信任。Agent 拿到的权限越多,这种信任越值钱。