Failure clustering
Failure clustering is how Neens turns a pile of failing traces into a short, named list of failure modes. It embeds each failing session’s behavior — what the agent did, where it errored, which quality checks it failed — and groups similar sessions together, so a thousand bad traces become a dozen patterns you can actually triage. The results power the Failure Modes page (see Issues & failure modes for the taxonomy built on top).
At a glance
| Where it lives | Diagnose → Failure Modes (cards + Scatter view); each cluster has a detail page |
| Key API routes | POST /clusters/run, GET /clusters/failure-modes?range=, GET /clusters/configs, POST /clusters/configs/{id}/activate, POST /clusters/assign, GET /clusters/{id}/exemplars, POST /clusters/{id}/merge, PATCH /clusters/{id}, POST /taxonomy/failure-modes/{id}/links |
| What it needs | Nothing to cluster — embedding runs locally on the server. An agent LLM connection (Settings → LLM connections) to label clusters and draft root causes |
| Scope | Per agent — settings, runs, and results never cross agents |
Reading the Failure Modes tab
The Failure Modes tab groups clusters into buckets (Timeouts, Tool failures, and so on — see Issues & failure modes) and, within each bucket, sorts cards by classified trace count descending — the highest-impact failure mode in the bucket is always first. Use Expand all / Collapse all at the top of the tab to open or close every bucket group at once, or a group’s own header to toggle just that one.
A summary strip above the groups gives you the shape of the page at a glance: total modes, affected traces (the sum of each mode’s trace count in the window), how many buckets are represented, how many modes are eroding a business KPI (see Business KPIs), and a breakdown of fix status across every mode — how many already have an applied fix, how many have one proposed and in flight, and how many have no fix yet.
The time window
The same time-range picker used on the Issues & Taxonomy tab (Today, 24h, 7d, 30d, All time, or custom) also scopes the Failure Modes tab: it re-derives each mode’s displayed trace count, trend sparkline, and the summary’s affected-traces total to the window you pick. It does not re-cluster — the clusters themselves (their membership, labels, and root causes) come from the last completed analysis run regardless of the window; the picker only changes which of that run’s members are counted as “in window” for display. Leave it at the default to see the same window the last analysis used, or narrow it to see what’s happening right now within an existing clustering.
What counts as a failure
Clustering runs over the agent’s failure set, and you control what’s in it. The Analyzing: chip at the top of the Failure Modes page shows the current criteria in plain language; click Configure clustering on the same page to change them and every other knob (see Clustering configurations below).
| Selection mode | Sessions included |
|---|---|
| Score (default) | Sessions with an error status, or a failing / below-threshold score, or a human “fail” annotation |
| All | Every session in the analysis window (no failure filter) |
| Errors only | Only sessions whose status is error |
In Score mode you also pick which score is consulted:
| Metric option | Meaning |
|---|---|
| Primary Score (recommended) | The default — the session’s primary quality composite must be at or above the threshold |
| Any score | Any below-threshold score on the session marks it a failure |
| A specific metric | Only that metric’s scores are consulted |
The Score threshold (0–1) defaults to 0.5: a session scoring below it counts as a
failure even if it finished without an error.
Why score-based is the default. Most agent failures are silent: the trace completes with
status ok, but the answer was wrong, irrelevant, or unhelpful. Clustering only errored traces
misses all of them. The default criteria — low primary score, plus hard errors, plus human
“fail” labels — catch quality failures your judges detect, not just crashes.
In Score mode, hard errors and human-flagged failures are always included regardless of
their scores.
The failure set is also windowed and capped so runs stay fast at volume: by default the last 30 days, at most 5,000 sessions per run (sampled with day-and-agent stratification when the set is larger — the page then labels counts as a sample-based estimate).
When clustering runs
- Nightly — a full clustering pass runs at 2:00 AM (server timezone) for every agent.
- First-run readiness sweep — every 30 minutes, Neens checks agents that have never been analyzed and triggers their first run as soon as they cross the readiness threshold (30 failure traces by default, shown as a progress meter on the page). A low-traffic agent older than a week gets its first run at 15 failures so the feature is never invisible.
- On demand — click Run analysis now (or Re-analyze once results exist), or call
POST /clusters/run. A manual run uses exactly the same per-agent settings as the nightly job, so the button and the scheduler always produce the same clustering of the same data.
Only one run per agent executes at a time; a crashed run stops blocking after an hour. The
run returns an honest outcome — completed, skipped_insufficient_data (with the count it
found vs. the minimum it needs), queued (the analysis was handed to a background worker), or
failed — and the page shows Last analyzed freshness from the most recent run.
How it works
Build a structured failure summary per session
For each session in the failure set, Neens composes a deterministic summary of the agent name, status, span names, error messages, failed tool calls, and every judge score verdict. Folding score verdicts in is what lets silent quality failures cluster together even when their surface text differs.
Embed locally
Summaries are embedded with a local sentence-transformer model on the server — no external API call, and no LLM is needed for this step. Embeddings are cached per session so repeat runs only embed new sessions.
Cluster
Neens clusters the embeddings with HDBSCAN (variable density, no fixed radius, a native
noise bucket) and falls back to DBSCAN automatically when HDBSCAN isn’t installed. The
minimum cluster size defaults to 3 and scales up automatically on large failure sets
(0.5% of the fitted set) so a 5,000-session run yields dozens of meaningful modes instead of
hundreds of micro-clusters.
Keep identities stable
Fresh clusters are stitched to the previous run’s clusters by centroid similarity and member
overlap, so a recurring failure mode keeps the same id, label, and root cause night after
night instead of appearing as a new duplicate. Near-identical fragments within one run are
merged before labeling. Each run is written atomically — a failure mid-run leaves no partial
clusters behind — and clusters superseded by a new run are marked resolved (kept as
history), never deleted.
Label and diagnose new clusters
Genuinely new clusters are labeled and diagnosed with an LLM (see below). Stitched clusters reuse last run’s label and root cause, so recurring modes cost nothing to re-label.
Surface the leftovers as novelty
Sessions that fit no cluster aren’t dropped: any that sit far from every known cluster are flagged as novel failure candidates and raise a novelty insight on your Insights feed — an early warning that a new kind of failure is emerging.
Labeling and root cause — the LLM connection
Cluster labeling, cluster-level root-cause hypotheses, and draft remediations all use the agent’s default LLM connection, configured in Settings → LLM connections (Anthropic, Bedrock, or any OpenAI-compatible endpoint). There is no platform-wide key.
Labels are pushed to be specific: if the model returns something generic (“Agent Error”),
Neens retries with an explicit correction, then falls back to a deterministic label derived
from the dominant failing tool or failed metric (for example get_order_status tool failure
or Low answer relevancy score).
No LLM connection? Clustering still runs. Without a resolvable connection, clusters are produced unlabeled (shown as “Unlabeled cluster”, with no root-cause hypothesis) — grouping, counts, trends, and exemplars all still work. Configure a connection and the next run backfills labels and root causes for existing clusters automatically.
Online assignment, novelty, and “more like this”
Between full runs, Neens classifies new failing sessions against the fitted clusters without re-clustering:
- Intraday assignment — every 30 minutes, recently arrived failure sessions (the last hour, up to 500) are matched to the nearest cluster, so fresh failures land in existing modes and novel ones raise insights the same day, not the next morning.
- On demand —
POST /clusters/assignwith a list of session ids returns a status per session:assigned(joins a known cluster),novel(far from everything — a candidate new failure mode, which also fires a novelty insight),unassigned(in between), orno_model(no clustering run has completed yet). - Similar sessions —
GET /sessions/{id}/similarreturns the sessions semantically closest to a given one (nearest-neighbor over the same embeddings), useful for “show me more traces like this failure”.
Exemplars, status, and the taxonomy
- Exemplars —
GET /clusters/{id}/exemplarsreturns a cluster’s most representative members (closest to its center; 5 by default, up to 50). The UI uses these when you promote a cluster: Confirm candidate requires reviewing at least 3 exemplar traces first, so the taxonomy stays grounded in real evidence. - Status — clusters are
active(current) orresolved(superseded by a later run or no longer recurring). Pickers and the Failure Modes page show active clusters only. - Failure modes — clusters are the machine-discovered evidence behind the human-curated taxonomy. Confirming a cluster creates a failure mode linked to it, and after each run Neens auto-proposes cluster-to-mode links wherever a clear majority of a cluster’s members were classified into one mode. See Issues & failure modes, and Remediations for drafting a fix from a cluster.
From a cluster to action
Every cluster card carries the same fix path as an Issue, plus one action of its own:
- View traces — opens the sessions behind the cluster in the selected window.
- Generate fix → View fix — the same stateful action as on an Issue card: Generate fix drafts a typed remediation grounded in the cluster’s evidence when none exists yet; once one does, the same spot becomes View fix. See Remediations.
- Promote to issue — link the cluster into your curated taxonomy as evidence for a named issue, instead of Create eval (that action lives on Issue cards, once a mode already has evidence behind it).
Promote a cluster to an issue
Click Promote to issue on the cluster card
Review the exemplar traces first — the same 3-exemplar grounding Confirm candidate requires, so you’re not promoting a cluster you haven’t actually looked at.
Pick a target
Choose an existing failure mode from the picker to add this cluster as more evidence for it, or create a new one on the spot (the same as + New issue type, pre-filled from the cluster’s label).
Confirm
Neens links the cluster to the mode (idempotent — promoting the same cluster to the same mode twice doesn’t duplicate the link). The mode’s Issue card on the Issues & Taxonomy tab now counts this cluster’s sessions, and Cluster evidence in the mode’s detail drawer lists the link.
Promote to issue vs. Confirm candidate. Both ground a taxonomy entry in real cluster evidence. Confirm candidate is the quick path for a brand-new auto-discovered cluster that doesn’t map to anything you’ve named yet. Promote to issue is the general form: it also lets you fold a cluster into an issue that already exists — useful once your taxonomy has grown and a new cluster turns out to be more evidence for a failure mode you already track.
Keeping failure modes clean
Over time an agent can end up with near-duplicate failure modes — two cards with the same name, or one broad mode that should really be several. Neens keeps the list tidy in two ways: it dedupes duplicate names automatically on every run, and it lets you merge and rename modes by hand from a card.
Automatic dedup of duplicate names
Cluster names are auto-generated (from the dominant failing tool or metric), so two unrelated
modes can land on the same label — e.g. two different clusters both named
Low faithfulness score. When that happens, the dedup pass resolves the collision one of two
ways:
- Disambiguate — if the two modes are genuinely distinct, Neens keeps them separate and appends a distinguishing suffix so each name is unique, e.g. Low faithfulness score (checkout) and Low faithfulness score (search).
- Coalesce — if the two modes are effectively the same failure, Neens folds them into one so you don’t triage the same pattern twice.
This runs on every clustering pass and is controlled by the Dedup duplicate labels toggle in the clustering config (Advanced section), on by default. Turn it off if you’d rather see the raw, un-disambiguated names. A name you set yourself (see Rename below) is never touched by this pass.
Merge a failure mode
When two cards describe the same problem, merge one into the other from its card.
Open the mode’s actions
On a failure-mode card, open the actions menu (Failure mode actions) and choose Merge.
Pick a target
In Merge failure mode, use the Merge into picker to choose another mode. The picker lists only the other modes in the same bucket, so you can’t merge across unrelated groups. Confirm with Merge.
The traces from the mode you started on move into the target, and the now-empty source mode is
marked resolved (kept as history, not deleted). Behind the card this calls
POST /clusters/{id}/merge against the target, with the source mode’s id. If a bucket has no other
modes, the dialog says so and there’s nothing to merge into.
Rename a failure mode (sticky names)
Auto-generated names are a starting point — rename any mode to something your team recognizes.
Choose Rename
From the card’s actions menu, choose Rename.
Set the name
In Rename failure mode, type a new Name and confirm.
A renamed mode is pinned: the name you set sticks across every future analysis run, so the
nightly pass and every Re-analyze keep your wording instead of reverting to the auto-generated
label. Pinned modes show a small pin marker next to their name (hover it for “This name was set
manually — future analysis runs won’t rename it.”). Renaming a mode also exempts it from automatic
dedup. Behind the card this is PATCH /clusters/{id} with the new label.
Example. After a run you see two cards both named get_order_status tool failure. You open the
second card, Rename it to Order status: auth timeout (now pinned, with a pin marker), and
Merge a third near-identical card into it. Tomorrow’s nightly run keeps your name and doesn’t
resurrect the merged duplicate.
Clustering configurations
Everything above — what counts as a failure, which score is consulted, how fine the clusters are, how much data a run reads — is captured in a clustering configuration. Configs work like judges: an agent keeps a collection of named configs, but exactly one is active, and only the active config drives analysis. Open the collection from Configure clustering on the Failure Modes page.
Out of the box, there’s nothing to set up. Every agent starts with one active config named Default that mirrors the sane defaults in the reference table below. You only create more configs when you want to try a different way of grouping failures.
The knobs
Every config carries the same set of tunables, grouped in the editor:
Selection criteria — what goes into the failure set:
- What gets clustered (
selectionMode) — Score (default), All traces, or Errors only. See What counts as a failure for the full behavior of each. - Score metric and Score threshold (0–1) — in Score mode, which score is consulted
(Primary Score (recommended), Any score, or a specific metric) and the cutoff below
which a session counts as a failure (default
0.5).
Advanced — how the run behaves:
- Min cluster size — the granularity lever, and the one worth tuning first. It’s the
fewest sessions that can form a cluster; smaller = more, finer failure modes, larger =
fewer, broader ones. Default
3, floor2. (Clustering is density-based, so there’s no “number of clusters” to set — you steer granularity with this instead.) - Min traces to run (
minFailureSessions) — how many failing traces must exist before the first run starts (default30). Lower it for a low-volume agent so analysis begins sooner. - Sampling %, Window (days), and Max traces per run — down-sample the eligible set,
set the rolling look-back window, and cap how many sessions a single run reads (defaults
100,30,5000). Together they keep runs fast at volume. - Dedup duplicate labels — automatically disambiguate or merge failure modes that get the same auto-generated name (default on). See Automatic dedup of duplicate names.
- Enable this config for scheduled clustering — turn scheduled and manual runs off without deleting the config.
As you edit, a live estimate shows how many traces match the current criteria in the window — e.g. “142 eligible traces · meets the 30-trace minimum” — so you can see the effect before you save. It’s a quick count, not a trial run, so it returns instantly.
Lifecycle: Experimental, Finalized, Archived
Each config carries a lifecycle stage — a colored badge of Experimental (amber), Finalized (green), or Archived (gray). It’s a curation label only: it tells your team how settled a config is and controls which configs show by default. It never changes what runs — only activating a config does that.
- A config you create starts Experimental. The seeded Default config is Finalized.
- The config list defaults to the Finalized view (plus the active config, which is always shown); switch the filter to All to see experimental and archived configs too.
- Move a config between stages any time with Change lifecycle stage — there’s no fixed order and no config is ever frozen. Archive a config you’ve stopped using to tuck it out of the default view without losing it.
Activate a config
Activate is the switch that matters: the active config — and only the active config — drives the nightly run, the readiness sweep, and every Run analysis now. Activation is independent of lifecycle stage; you can run an Experimental config while you’re still tuning it.
Create a config to try
On the Failure Modes page, open Configure clustering, click New config, name it (e.g. Strict failures), adjust the knobs, watch the estimate, and Create config. It’s saved as Experimental and does not run yet — activating is a separate, deliberate step.
Activate it
Click Activate on that config. Neens atomically makes it the agent’s one active config and deactivates the previous one. The next scheduled or manual run uses it.
Re-run to see the effect
Click Run analysis now (or Re-analyze) to cluster immediately with the new config, rather than waiting for the nightly pass.
Exactly one config is active per agent. Activating a config always deactivates the one that was active — you never have to switch the old one off yourself. You can’t delete the active config (activate another first), and an agent always keeps at least one config.
Reference: clustering configs
Manage the collection per agent via the config routes; the active config’s knobs are also
readable through the compatibility route GET /clusters/settings.
| Route | Purpose |
|---|---|
GET /clusters/configs?lifecycle=finalized|experimental|all | List configs (defaults to finalized, plus the active one) with the active config’s id |
POST /clusters/configs | Create a config (saved Experimental, inactive) |
GET / PATCH / DELETE /clusters/configs/{id} | Read, edit, or delete a config |
POST /clusters/configs/{id}/activate | Make a config the agent’s active one |
PATCH /clusters/configs/{id}/lifecycle | Change its lifecycle stage |
POST /clusters/configs/estimate | Live count of eligible traces for a set of knobs (no run) |
Each config’s tunable knobs and their defaults:
| Setting | Default | Notes |
|---|---|---|
enabled | true | Turns scheduled + manual clustering off for the config |
selectionMode | score | score, all, or error_only |
scoreMetricKey | null | null = the primary metric; __any__ = any score; else a specific metric key |
scoreThreshold | 0.5 | Failure cutoff for scores, clamped to 0–1 |
windowDays | 30 | Rolling analysis window (minimum 1) |
maxSessions | 5000 | Per-run cap; larger sets are sampled (stratified by day and agent) |
samplingPct | 100 | Optional down-sampling of the windowed set, 0–100 |
minClusterSize | 3 | Minimum sessions per cluster (floor 2); auto-scales on large sets |
minFailureSessions | 30 | Failure traces needed before the first run (minimum 15) |
dedupLabels | true | Auto-disambiguate or coalesce failure modes that get the same auto-generated name |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Clusters show “Unlabeled cluster” and no root cause | No default LLM connection resolved for the agent, or its credential is invalid | Configure or fix the connection in Settings → LLM connections; the next run (or Re-analyze) backfills labels |
| ”Not enough data to cluster yet” | Fewer failure traces in the window than the minimum cluster size / readiness threshold | Send more traffic, widen the window, or loosen the selection criteria |
| ”Analysis queued — results will appear once the run completes.” | This server doesn’t run the ML stack; the run was handed to a background worker | Wait a few minutes, then refresh |
| Counts marked “est.” / sample-based | The windowed failure set exceeded maxSessions (or samplingPct is below 100), so the run fit on a sample | Raise maxSessions if you want exact counts and can afford longer runs |
| The failure count doesn’t match the Sessions page | The readiness meter counts individual failure traces; the Sessions page rolls traces up into conversations | Compare against the Traces view instead |