Dashboard
14 settembre 2026

I sub-agenti sono semplicemente sbagliati

Explore with AI

L’agente autofix di Polylane era stato originariamente costruito come un workflow di più sub-agenti, ciascuno responsabile di un singolo compito:

  • un triage per vagliare migliaia di alert e segnali
  • un coordinatore per gestire i sub-agenti
  • fino a quindici sub-agenti per indagare tutte le possibili ipotesi sull’issue
  • e un agente di coding per inviare, alla fine, una pull request

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

Un’issue confermata poteva portare fino a 18 agenti e sub-agenti per l’indagine e la correzione. Oggi lo stesso lavoro viene svolto da un singolo agente.

Il workflow era una progettazione solida basata su quanto sapevamo all’epoca: prompt piccoli, set di strumenti ridotti, un orchestratore per trasferire i risultati tra gli specialisti. Era però estremamente costoso e difficile da gestire e da comprendere.

Cos’è un’issue?

Polylane analizza log, metriche e trace da ogni risorsa cloud (funzione Lambda, Cloudflare Worker, progetto Vercel, ecc.) su ogni provider collegato. Per ogni risorsa memorizziamo baseline su più orizzonti temporali, in modo da catturare la stagionalità nei dati. Valutiamo poi lo stato attuale della risorsa cloud e lo confrontiamo con i dati storici. I valori che superano la baseline vengono registrati come issue.

Le issue possono anche essere create dagli alert ricevuti da soluzioni di observability ed error tracking.

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

Come risolviamo le issue?

Per risolvere un’issue dobbiamo:

  1. Triage: raccogliere dati per confermare o respingere l’issue.
  2. Indagine: valutare più ipotesi e determinare la causa radice.
  3. Aprire una pull request o fare escalation. Scrivere una pull request per risolvere l’issue, fare escalation verso un engineer se la correzione non è una modifica al codice, oppure scrivere un report che evidenzia i risultati e fermarsi.

La stragrande maggioranza dei run termina prima di una pull request o di un’escalation, quando Polylane conclude che l’issue è un falso positivo o benigna.

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

Architettura a sub-agenti

La nostra architettura iniziale era basata su più sub-agenti.

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

Usavamo un agente orchestratore per coordinare il lavoro tra più sub-agenti. I sub-agenti venivano istanziati per indagare le ipotesi, cercando di dimostrare o confutare ciascuna ipotesi con punti di partenza diversi (per esempio partendo dai log, o partendo dalla codebase, ecc.).

I risultati dei sub-agenti venivano votati con semplice aritmetica e riassunti da un altro agente prima di essere trasmessi all’agente coordinatore.

Il verdetto di ogni ipotesi era un voto ponderato per confidenza tra i suoi pass.

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 };
}

Una volta indagate tutte le ipotesi, se veniva identificata una causa radice, il coordinatore faceva escalation verso un engineer oppure scriveva un piano per la correzione. Questo piano veniva poi passato a un sub-agente di coding per implementare la correzione e inviare la pull request.

Il problema principale di questa architettura è la perdita di contesto tra i sub-agenti. Ogni agente trasmetteva verso l’alto o verso il basso solo frazioni del proprio contesto, tramite riassunti, e sia gli agenti a monte che quelli a valle dovevano regolarmente rifare lo stesso lavoro.

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

Inoltre, l’agente che scriveva la correzione disponeva solo del piano, senza contesto sull’issue originale o sulle prove raccolte durante l’indagine. Questo portava a pull request di qualità inferiore, spesso concentrate sui sintomi piuttosto che sulla causa radice.

Abbiamo scelto questa architettura soprattutto perché, quando l’abbiamo progettata per la prima volta a marzo 2026, i modelli più avanzati non erano sempre in grado di condurre un’indagine end-to-end, compresa la scrittura della correzione, e anche, semplicemente, il nostro harness non era abbastanza buono.

È diventato sempre più difficile costruire su queste fondamenta perché:

  • Il debug richiedeva molte trace. Ragionare su un’indagine end-to-end richiedeva di analizzare più trace, a volte dozzine
  • La valutazione era per fase. Ogni sub-agente poteva essere valutato individualmente e superare il test, mentre il risultato finale era scarso, perché i fallimenti si trovavano nei passaggi di consegne.

Abbiamo iterato su questa architettura per mesi, migliorando ogni agente individualmente e ottimizzando i prompt e i passaggi di consegne, ma i risultati non hanno mai eguagliato lo sforzo. Era costosa, difficile da debuggare, e le pull request non erano abbastanza buone.

Agente singolo

Il 3 settembre abbiamo sostituito il workflow a sub-agenti con un singolo agente che fa tutto, dal triage alla pull request. Lo stesso agente raccoglie le prove, indaga tutte le ipotesi e invia la pull request.

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

Abbiamo eliminato l’agente di triage, il coordinatore, gli agenti delle ipotesi e l’agente autofix, insieme ai workflow che trasportavano i risultati tra loro e ai gate che precedevano la pull request. I benefici operativi sono stati immediati: una sola trace da valutare, e nessun riassunto tra i sub-agenti.

Ancora più importante, la qualità delle pull request è migliorata, e il tempo tra il rilevamento di un’issue e l’apertura della pull request che la corregge è crollato. Nel mese intorno al passaggio, la mediana è passata da 2.2 ore a 35 minuti e il p90 da nove giorni a meno di due ore. Con la pipeline, un finding rimaneva tipicamente per diverse ore in una catena di workflow, ognuno in attesa del precedente. Con l’agente singolo, la pull request si apre pochi minuti dopo il rilevamento dell’issue.

15 min 1 h 4 h 1 g 4 g 2 sett 14 Aug 18 Aug 22 Aug 26 Aug 30 Aug 3 Sep 7 Sep 11 Sep 3 set: un agente 30 min
Mediana giornaliera Mediana giornaliera to p90 Axis not to scale
Figura 1
Ore dal rilevamento di un'issue all'apertura della pull request

Ora apriamo anche molte più pull request. Quando usavamo i sub-agenti, lo 0.6% delle issue rilevate finiva in una pull request. Con l’agente singolo è il 4.2%, e in crescita. La differenza sono le modalità di fallimento. Una pipeline con più sub-agenti deve sopravvivere a ogni passaggio di consegne: il triage deve promuovere il finding, il coordinatore deve produrre le ipotesi, il fan-out deve raggiungere un verdetto, ecc. Ogni passaggio di consegne è una potenziale modalità di fallimento.

0% 2% 4% 6% 8% 10% 14 Aug 18 Aug 22 Aug 26 Aug 30 Aug 3 Sep 7 Sep 11 Sep 3 set: un agente 9.1%
Figura 2
Percentuale di issue rilevate che sono terminate in una pull request

Anche il costo per pull request è diminuito. Ogni run ora parte con il modello più potente, quindi un finding scartato costa più di quanto costasse con la pipeline. Ma il costo medio per pull request è passato da $111 a circa $18 nei primi nove giorni dell’agente singolo. Questo calo non è esclusivamente il risultato del cambio di architettura, dato che stiamo iterando attivamente su ogni aspetto di Polylane. Parte di esso è la normale deriva di un sistema in costante iterazione.

$1 $10 $100 $1,000 14 Aug 18 Aug 22 Aug 26 Aug 30 Aug 3 Sep 7 Sep 11 Sep 3 set: un agente $2.88
Logarithmic scale
Figura 3
Spesa del modello per pull request aperta
Ogni valore dietro i tre grafici, per giorno UTC
GiornoIssue rilevate con pull requestMedianap90Spesa per pull request
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 · un agente 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 · fino alle 16:00 8.2% 30 min 55 min $2.88

Non costruire sub-agenti

  • I passaggi di consegne perdono più di quanto salvino. Ogni riassunto passato tra gli agenti è contesto che l’agente successivo non avrà mai. L’agente che raccoglie le prove dovrebbe essere l’agente che agisce su di esse.
  • Valuta il run, non gli agenti. Le valutazioni per singolo agente superano il test mentre il sistema fallisce, perché i fallimenti si trovano tra gli agenti.
  • Mantieni una sola trace per run. Una decisione sbagliata distribuita su una dozzina di trace richiede un pomeriggio per essere spiegata. In una sola trace basta uno scroll.

Nel 2026 nessuno dovrebbe essere in on-call. Polylane osserva la tua infrastruttura, indaga e ripara ciò che si rompe.

Iscriviti alla lista d'attesa