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 的用户、服务、升级策略) | 已完成 |
| 服务端重建:Slack、GitHub、Datadog、PagerDuty 四个读回器,以及出处和问题 | 进行中 |
| 收件端(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. 读回器:把返回值变回世界
API 返回值本身就是那一刻的世界状态。每个工具一个读回器,按这个 API 的格式把它原样还原。
| trace 里的调用 | 变成世界里的 |
|---|---|
Slack auth.test | bot 的身份:团队 ID、bot 用户 ID |
conversations.list / history / replies | 频道和历史消息,原 ID、原 ts、原文 |
| 收到的 Slack message 事件 | 过程中人发的消息,到了原来的时间点出现 |
GitHub commits、commit 详情、contents | repo 历史和每个版本的文件。读到过的内容直接用;没读到的,用 commit 的 diff 从相邻版本推出来 |
GitHub actions/runs | 部署流水线和它的耗时 |
Datadog monitor | monitor 规则,世界里会继续按它求值 |
| Datadog 日志搜索 | 按固定节奏出现的错误日志,变成"故障期间每 N 秒一条";其余变成历史日志 |
PagerDuty users / services | 人、服务和升级策略,原 ID 不变。确认告警的那个用户就是 agent 自己 |
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 四个工具有读回器,其他工具要逐个补。
- Datadog 的 monitor ID 和 GitHub 的 commit SHA 还没法固定成原值。
- agent 没读过的数据只能列为缺失,等补数据(AI 生成)做出来才能补上。
- 托管运行(E2B 沙箱)在 M3;这一轮客户要在本地或自己的 CI 里跑。