仪表盘
2026年9月14日

子智能体就是错的

Explore with AI

Polylane的autofix智能体最初被构建为由多个子智能体组成的工作流,每个子智能体负责单一任务:

  • 一个分诊智能体,用于筛选成千上万条告警和信号
  • 一个协调智能体,用于操作各个子智能体
  • 最多十五个子智能体,用于调查该问题的所有可能假设
  • 以及一个编程智能体,用于最终提交拉取请求

graph TB
    S["Alerts and signals"] --> T["Triage agent"]
    T -->|"confirmed issue"| C["Coordinator agent"]
    C -->|"hypotheses"| H["Up to 15 hypothesis sub-agents"]
    H -->|"verdicts"| C
    C -->|"plan"| A["Coding agent"]
    A --> PR["Pull request"]
    style C fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d
    style H fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d

一个已确认的问题可能会调用多达18个智能体和子智能体来进行调查和修复。如今,同样的工作由单个智能体完成。

这个工作流是基于我们当时所了解信息而做出的合理设计:小型提示词、小型工具集,以及一个在各专业智能体之间传递结果的编排智能体。然而,它的运行成本极高,也很难运维和推理。

什么是问题?

Polylane会扫描每个已连接提供商上每个云资源(Lambda函数、Cloudflare Worker、Vercel项目等)的日志、指标和追踪数据。对于每个资源,我们会存储跨多个时间跨度的基线,从而能够捕捉数据中的季节性规律。然后我们会评估云资源的当前状态,并将其与历史数据进行比较。突破基线的数值会被记录为问题。

问题也可以由我们从可观测性和错误追踪解决方案接收到的告警创建。

graph TB
    R["Cloud resource"] -->|"telemetry"| B["Compare with its baselines"]
    A["Alert from an observability<br/>or error tracking provider"] --> I
    B -->|"breaks the baseline"| I["Issue"]
    B -->|"within the baseline"| N["No issue"]
    style I fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d

我们如何解决问题?

要解决一个问题,我们需要:

  1. 分诊: 收集数据以确认或否定该问题。
  2. 调查: 评估多个假设并确定根因。
  3. 开启拉取请求或上报。 编写用于解决问题的拉取请求,如果修复不属于代码变更就上报给工程师,或者撰写一份说明调查结果的报告并停止。

绝大多数运行会在提交拉取请求或上报之前终止,此时Polylane判定该问题要么是误报,要么是良性的。

graph TB
    I["Triage the issue"] --> V["Investigate the root cause"]
    V -->|"dismiss, fold, or reopen"| E["Report and stop"]
    V -->|"confirm"| X["Open a pull request"]
    V -->|"confirm, no code change"| Z["Escalate to an engineer"]
    style V fill:#fef3c7,stroke:#fcd34d,color:#78350f
    style X fill:#d1fae5,stroke:#6ee7b7,color:#065f46

子智能体架构

我们最初的架构基于多个子智能体。

graph TB
    F["Finding"] --> T["Triage agent"]
    T -->|"confirm"| C["Orchestrator agent"]
    C -->|"hypotheses"| W["Fan-out workflow"]
    W --> H1["Hypothesis agent 1"]
    W --> H2["Hypothesis agent 2"]
    W --> H3["Hypothesis agent ..."]
    W --> H15["Hypothesis agent 15"]
    H1 --> AG["Vote and summarize"]
    H2 --> AG
    H3 --> AG
    H15 --> AG
    AG -->|"summary as a message"| C
    C -->|"confirmed hypothesis"| AF["Coding agent"]
    AF --> PR["Pull request"]
    style C fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d
    style AG fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d

我们使用一个编排智能体来协调多个子智能体之间的工作。子智能体被实例化来调查各个假设,尝试从不同的起点(例如从查看日志开始,或从代码库开始等)来证实或证伪每个假设。

子智能体的调查结果通过简单的算术方式进行投票,并由另一个智能体进行汇总,然后再汇总给协调智能体。

每个假设的判定结果,都是其各轮次结果按置信度加权投票得出的。

const CONFIDENCE_RANK = { definitive: 4, strong: 3, moderate: 2, weak: 1, speculative: 0 };

function aggregatePassVerdicts(passes: PassResult[]): Verdict {
  const weights: Record<string, number> = {};
  const counts: Record<string, number> = {};
  for (const p of passes) {
    weights[p.verdict] = (weights[p.verdict] ?? 0) + CONFIDENCE_RANK[p.confidence];
    counts[p.verdict] = (counts[p.verdict] ?? 0) + 1;
  }
  const totalWeight = Object.values(weights).reduce((a, b) => a + b, 0);
  const [winner, winnerWeight] = Object.entries(weights).sort((a, b) => b[1] - a[1])[0];

  const countMajority = counts[winner] > passes.length / 2;
  const weightMajority = winnerWeight > totalWeight / 2;
  if (!countMajority && !weightMajority) {
    return { verdict: "inconclusive", confidence: "weak", summary: `No consensus across ${passes.length} passes.` };
  }

  const majority = passes.filter((p) => p.verdict === winner);
  const confidence = majority.reduce((min, p) => (CONFIDENCE_RANK[p.confidence] < CONFIDENCE_RANK[min] ? p.confidence : min), majority[0].confidence);
  const lead = majority.reduce((best, p) => (CONFIDENCE_RANK[p.confidence] > CONFIDENCE_RANK[best.confidence] ? p : best));
  return { verdict: winner, confidence, summary: lead.summary };
}

一旦所有假设都调查完毕,如果确定了根因,协调智能体要么上报给工程师,要么撰写一份修复计划。这份计划会被交给一个编程子智能体来实施修复并提交拉取请求。

这种架构的主要问题在于子智能体之间的上下文丢失。每个智能体都只能通过摘要向上或向下传递自己一小部分上下文,导致上游和下游的智能体经常需要重复做同样的工作。

graph TB
    I["Issue"] --> O["Orchestrator<br/>splits the issue into briefs"]
    O -->|"brief A"| A["Sub-agent A<br/>investigates brief A"]
    O -->|"brief B"| B["Sub-agent B<br/>investigates brief B"]
    A -.->|"summary A"| M["Orchestrator, later<br/>writes the plan from the summaries"]
    B -.->|"summary B"| M
    M -.->|"plan"| F["Coding agent<br/>writes the fix"]
    F --> PR["Pull request"]
    style I fill:none,stroke:none
    style PR fill:none,stroke:none
    style O stroke:#4C8C57,color:#3d7046
    style A stroke:#3b7dd8,color:#2b5fa8
    style B stroke:#7c5cd6,color:#5b3fb0
    style M stroke:#a07d1c,color:#7a5f14
    style F stroke:#6b7280,color:#4b5563
    %% aside O right #4C8C57 Issue, alerts and history
    %% aside A left #3b7dd8 Brief A and its evidence
    %% aside B right #7c5cd6 Brief B and its evidence
    %% aside M right #a07d1c History and two summaries, no evidence
    %% aside F right #6b7280 The plan, nothing else

此外,负责编写修复方案的智能体只拿到了这份计划,缺乏关于原始问题或调查过程中收集到的证据的上下文。这导致提交的拉取请求质量欠佳,往往只针对表面症状,而非根因。

我们之所以选择这种架构,主要是因为在2026年3月首次设计它时,前沿模型并不总能端到端地完成一次调查(包括编写修复方案),而且坦白说,我们的harness当时也不够好。

在这个基础上继续构建变得越来越困难,原因是:

  • 调试需要查看大量追踪记录。 要推理出一次端到端的调查过程,需要查看多条、有时甚至是几十条追踪记录
  • 评估是按阶段进行的。 每个子智能体都可以单独评估并通过,但最终结果却很差,因为失败往往发生在交接环节。

我们在这种架构上迭代了数月,逐个改进各个智能体,并调整提示词和交接环节,但结果始终配不上投入的努力。它成本高昂、难以调试,提交的拉取请求也不够好。

单一智能体

9月3日,我们用一个单一智能体取代了子智能体工作流,由它完成从分诊到拉取请求的全部工作。同一个智能体收集证据、调查所有假设,并提交拉取请求。

graph TB
    F["Issue"] --> A["Single agent"]
    A --> I["Triage"]
    I --> B["Investigate"]
    B --> V["One verdict"]
    V -->|"confirm"| X["Clone, edit, validate in the sandbox"]
    X --> PR["Open the pull request"]
    V -->|"dismiss, fold, or reopen"| E["Report and stop"]
    V -->|"confirm, no code change"| Z["Escalate to an engineer"]
    style A fill:#d1fae5,stroke:#6ee7b7,color:#065f46
    style PR fill:#d1fae5,stroke:#6ee7b7,color:#065f46

我们删除了分诊智能体、协调智能体、假设智能体和autofix智能体,以及在它们之间传递结果的工作流,还有位于拉取请求之前的各个关卡。运维上的收益立竿见影:只需评估一条追踪记录,子智能体之间也不再需要摘要。

更重要的是,拉取请求的质量提高了,从检测到问题到开启修复它的拉取请求之间所耗费的时间大幅缩短。在切换前后的那个月里,中位数从2.2小时降到了35分钟,p90从9天降到了不到2小时。在流水线架构下,一项调查发现通常要在一连串相互等待的工作流中停留数小时。而在单一智能体架构下,拉取请求会在问题被检测到后的几分钟内开启。

15分钟 1小时 4小时 1天 4天 2周 14 Aug 18 Aug 22 Aug 26 Aug 30 Aug 3 Sep 7 Sep 11 Sep 9月3日:单一智能体 30 min
每日中位数 每日中位数 to p90 Axis not to scale
图1
从检测到问题到开启拉取请求所耗费的小时数

如今我们提交的拉取请求也大幅增多。在使用子智能体时,只有0.6%被检测出的问题最终会形成拉取请求。在单一智能体架构下,这一比例是4.2%,并且还在上升。差异在于失败模式。一个由多个子智能体组成的流水线,必须在每一次交接中都存活下来:分诊必须推进这项发现,协调智能体必须产出假设,扇出调查必须得出一个判定结果,等等。每一次交接都是一个潜在的失败点。

0% 2% 4% 6% 8% 10% 14 Aug 18 Aug 22 Aug 26 Aug 30 Aug 3 Sep 7 Sep 11 Sep 9月3日:单一智能体 9.1%
图2
最终形成拉取请求的已检测问题占比

每个拉取请求的成本也下降了。现在每次运行都从更强的模型开始,因此一项被驳回的发现,其成本要比在流水线架构下更高。但是,在单一智能体上线的头9天里,每个拉取请求的平均成本从$111降到了约$18。这一下降并非完全是架构变更的结果,因为我们也在持续迭代Polylane的方方面面。其中一部分只是一个持续迭代中的系统所常见的正常波动。

$1 $10 $100 $1,000 14 Aug 18 Aug 22 Aug 26 Aug 30 Aug 3 Sep 7 Sep 11 Sep 9月3日:单一智能体 $2.88
Logarithmic scale
图3
每个已开启拉取请求的模型花费
按UTC日期列出的三张图表背后的全部数值
日期带有拉取请求的已检测问题中位数p90每个拉取请求的花费
14 Aug 1.9% 2.3 h 2.7 h $144
15 Aug 1.1% 5.6 h 5.9 h $270
16 Aug 2.3% 1.2 h 4.2 d $121
17 Aug 1.5% 38 min 4.5 d $120
18 Aug 0.9% 20 min 27 min $57
19 Aug 0.7% 49 min 12.2 h $157
20 Aug 0.7% 1 h 35.9 h $202
21 Aug 0.8% 44.2 h 4.2 d $211
22 Aug 0.8% 7.7 d 10.4 d $563
23 Aug 1.3% 32 min 35.3 h $205
24 Aug 0.6% 2.5 d 5.6 d $90
25 Aug 1.4% 1.5 h 13.4 h $43
26 Aug 0.3% 1.5 h 2.1 h $45
27 Aug 0.7% 1.9 h 2.5 h $58
28 Aug 1% 11.7 d 14.1 d $49
29 Aug 1.5% 26.1 h 10.4 d $32
30 Aug 0.5% 2 d 2.8 d $51
31 Aug 0.2% 9 d 10.4 d $77
1 Sep 0.1% 4.2 d 4.2 d $169
2 Sep 0.1% 6.7 d 6.7 d $93
3 Sep · 单一智能体 0.4% 29 min 2.6 d $69
4 Sep 2.4% 23 min 5.4 h $25
5 Sep 2.9% 34 min 3.4 d $15
6 Sep 1.4% 34 min 43.7 h $23
7 Sep 1.8% 32 min 4.5 h $16
8 Sep 4% 41 min 19.4 h $26
9 Sep 7.6% 34 min 1.2 h $15
10 Sep 5% 34 min 1.3 h $17
11 Sep 5.4% 56 min 2.1 h $12
12 Sep 9.1% 35 min 1.3 h $3.46
13 Sep · 截至16:00 8.2% 30 min 55 min $2.88

不要构建子智能体

  • 交接的损失大于收益。 智能体之间传递的每一份摘要,都是下一个智能体永远无法获得的上下文。收集证据的智能体,应该就是采取行动的智能体。
  • 要评估整个运行过程,而不是各个智能体。 逐个智能体的评估可以通过,但整个系统却会失败,因为失败发生在智能体之间。
  • 每次运行只保留一条追踪记录。 一个分散在十几条追踪记录中的错误决策,需要花一下午时间来解释清楚。而在一条追踪记录里,只需要滚动查看即可。

2026年,不该再有人需要on-call。 Polylane观察你的基础设施,进行调查,并修复出问题的地方。

加入候补名单