ancha.
从生产 trace 到测试世界
这一轮(M1 + M2)做完之后,产品能做什么,以及一条 trace 是怎么变成一个能跑的世界的。2026-11-05。
客户的 agent 把生产环境的 trace 发给我们。我们据此还原出它当时所处的世界:原来的 ID、原来的消息、原来的文件都不变。线上那次失败就成了一条能在本地重跑的测试;代码改好之后再跑一遍,就能看到它通过。
做完之后,客户怎么用
接上 trace在 console 里拿一把只能写入的 ingest key,在 agent 的生产环境里设两个环境变量,现有的 OpenTelemetry exporter 就会把 trace 发过来。也可以直接用
ancha import trace.jsonl 上传一个文件。我们接收、脱敏、存储服务端会去掉认证头和 token,邮箱和电话换成固定的假值(同一个人在 Slack 和 GitHub 里还是同一个人)。原始 trace 默认保留 30 天,在 Traces 页面里每一条都能看到。
自动重建世界每条 trace 生成一个世界,里面有:seed(用原 ID)、过程中出现的人和事件、工具之间的联动、故障规则。每一项都标明是原样还原、推断出来的,还是缺失的。trace 定不下来的地方会变成问开发者的问题,在他回答之前先用默认答案。
拉到本地重跑
ancha import 或 ancha pull 把世界拉到本地,再用 ancha run 把线上那次失败重演一遍。代码改好后再跑,看它变绿。我们自己的运行也能导出
ancha runs export --ir 导出的格式和客户的 trace 完全一样,生产记录和沙箱运行可以直接对比。这一轮做什么,之后做什么
| 能力 | 状态 |
|---|---|
| GitHub twin 的合并和真实 GitHub 一致:分叉的分支走三方合并,有冲突时返回 405 | 已完成 |
Ancha 自己的 trace 会记下 webhook 内容和 query;ancha runs export --ir | 已完成 |
| seed 能固定原始 ID(Slack 的团队、用户、bot、频道、消息 ts;PagerDuty 的用户、服务、升级策略) | 已完成 |
| 服务端重建:IR、OTLP(JSON 和 protobuf)、四个手写读回器、出处和问题 | 已完成 |
| 从 twin 自动学映射,覆盖全部有数据格式的 twin(36 个);在每个 twin 上自动验证;没覆盖到的调用回放兜底 | 本轮 |
| 收件端(OTLP,支持 JSON 和 protobuf)、只写的 ingest key、脱敏、存储 | 本轮 |
后台重建 worker;console 的 Connect 页和 Traces 页;ancha import 和 ancha pull | 本轮 |
| 在 staging 上用模拟客户跑通全链路 | 本轮 |
| 用 E2B 沙箱托管运行客户的 agent,可以并行跑 | M3 |
| World 和变体页面、回答问题的界面、变体生成 | M4 |
| PR 检查、失败聚类、新故障提醒 | M5 |
| LangSmith 和 Langfuse 连接器;能记录完整请求和返回体的 recorder SDK | M6 |
trace 是怎么变成世界的
trace(OTLP 或文件)→统一成 IR→读回器(每个工具一个)→推断:联动、故障、部署→出处 + 问题→scene 文件→校验,再跑
核心靠代码,不靠 AI。AI 只用来补 trace 里本来就没有答案的那部分,见本节最后。
1. trace 里有什么
trace 记的是 agent 看到的东西:每次 API 调用的请求和返回,以及它收到的每个 webhook。所有来源都先统一成一种格式(Ancha Trace IR),后面只认这一种。
{"kind":"call","t":"…T14:00:01.984Z","service":"slack","method":"POST",
"url":"https://slack.com/api/conversations.history",
"request":{"body":{"channel":"C48SKYPN1S3"}},
"response":{"status":200,"body":{"messages":[{"ts":"1792591080.000000","user":"UFMCYC15CSH","text":"Incident runbook | …"}]}}}
{"kind":"inbound","t":"…T14:01:30.000Z","service":"slack",
"payload":{"type":"event_callback","event":{"type":"message","text":"Status request: …","ts":"1792591290.000000"}}}
2. 读回:让 twin 自己教我们
twin 本来就是"状态 → API 返回"这样一个函数,读回是它的反函数。我们不为 38 个 twin 逐个手写反函数,而是让每个 twin 自己把它测出来:
- 打标记:拿 twin 自带的起始数据,把每个文字字段换成一个独一无二的标记值。
- 探测:用这份数据把 twin 起起来,把它所有的读接口都调一遍:先调列表,再顺着返回里的 ID 去调详情。
- 学映射:看每个标记出现在哪个接口返回的哪个位置,就知道"返回里的这个字段 = 世界里的那个字段",以及哪些字段是在引用别的实体。
- 反过来用:真实 trace 的返回值进来后,按学到的映射原样填回世界,ID 保持原值。
- 自动验证:CI 里每个 twin 换一份不同的数据,跑"数据 → 返回 → 读回 → 数据",结果必须一致。twin 一改,映射就重新学。
学出来的映射大概长这样(以 Slack 为例):
| trace 里的调用和返回位置 | 填回世界的位置 |
|---|---|
conversations.list → channels[].name、channels[].id | 频道的名字和原 ID |
conversations.history → messages[].text、messages[].ts | 历史消息的原文和原 ts |
conversations.history → messages[].user | 引用:发消息的是哪个用户 |
users.list → members[].real_name | 用户的全名 |
有两类数据本身不是一条条记录,自动学不出来,所以保留手写,同时拿来对照学出来的结果:GitHub 的 git 内容(每个版本的文件、diff),以及 Datadog 的时间序列和日志节奏。没被映射覆盖的调用,就按录下来的返回原样回放,并标为 replayed。
3. 推断:世界会怎么反应
- 联动:monitor 消息里写了
@pagerduty-…和@slack-…,随后又出现了 Datadog 和 PagerDuty 的 bot 帖子,就推出三条联动:Datadog→PagerDuty、Datadog→Slack、PagerDuty→Slack。这些是 twin 自带的行为。 - 故障从什么时候开始:看最早那条错误日志。
- 看到了恢复:修法就是 agent 合并上线的那个改动,恢复延迟用 trace 里实测的值。
- 没看到恢复:从 git 历史里找出事前最后一次改动,把"撤回它"当作候选修法,并作为问题交给开发者确认。
- 没看到部署:先按常见情况假设"合并到 main 后 15 秒上线",同样作为问题交给开发者确认。
4. 出处和问题
| 状态 | 意思 |
|---|---|
| observed | trace 里看到的,原样还原,并指回来源的那条记录 |
| inferred | 从模式推出来的,可能错,所以会配一个问题 |
| synthetic | agent 从没读过、由生成补上的数据,永远不会覆盖 observed |
| missing | trace 里没有,也没有补;清单里写明缺了什么 |
一条 trace 只走过一条路,所以"世界会怎么反应"有时定不下来。有问题的世界照样能跑,只是标成 needs_answers;开发者回答之后再重建一遍。
为什么核心不用 AI
- 要逐字节准确:ID、时间戳、文件内容、原文都不能变,模型会改,甚至编造。
- trace 很大:一条 3 分钟的就有 160 KB,全部喂给模型又贵又慢。
- 要能复现、能审计:同一条 trace 每次重建的结果都必须一样。
- 按 API 格式还原本来就是机械活,代码做得又快又准。
AI 用在哪
- 补上 agent 没读过的那部分世界,标为 synthetic。
- 把"到点发一条消息"的人变成会对话的人:从他们发过的消息里提取事实和说话方式。
- 生成变体:换说法、换时机、换工具返回。
- 起草检查项,以及问开发者的问题。
- 客户的 trace 只有自定义 tool 层时,帮忙把 tool 对应到 API。
用便宜的模型,生成的东西都标为 inferred 或 synthetic。
已经验证过的
25 / 33只凭一条 trace 原样还原的世界项;另有 3 项近似,5 项缺失,缺失的都是 agent 从没读过的
3 + 3同一个 agent 在原世界和重建世界里各跑 3 次:动作顺序一致,结果分布也一致
0模拟客户通过 OpenTelemetry 发来 61 条记录,在里面扫描密钥和认证头,命中为 0
- 一条 trace 定不下恢复规则:把修复推迟到 2:30 再做,原世界在 3:05 恢复,重建世界却到 4:35 才恢复。原因是一条 trace 区分不了"满 2 分钟才恢复"和"修好后 110 秒恢复"。现在这类地方会变成问题。
- 模拟客户的线上失败复现了:它的 agent 从旧 commit 拉了修复分支,合并被拒。在重建世界里跑两次,有一次在同一步出现了同样的失败。这次也顺带查出 GitHub twin 的合并和真实 GitHub 不一致,已经修好。
现在还做不到的
- 自动学映射要在这一轮做完;做完之前,只有 Slack、GitHub、Datadog、PagerDuty 四个工具能重建。
- 固定原始 ID 目前只做了 Slack 和 PagerDuty,要推广到其他 twin。GitHub 的 commit SHA 是按内容算出来的,没法固定。
- agent 没读过的数据只能列为缺失,等补数据(AI 生成)做出来才能补上。
- 托管运行(E2B 沙箱)在 M3;这一轮客户要在本地或自己的 CI 里跑。