Diagnosis and Failure Attribution
AgenticLens 0.7.0 adds a deterministic diagnosis layer on top of structured
traces (Run/Span). Given one run, it answers:
- What failed, and what kind of failure was it? (failure taxonomy)
- Which step and which agent caused it, as opposed to failing as a consequence? (faulty-step and faulty-agent attribution)
- In what order did things happen? (incident timeline)
- Is anything unusual compared with healthy runs? (anomaly detection)
- What did the failure cost, and what should I check next? (investigation narrative)
Every diagnosis comes from a rule that cites recorded spans. Nothing here calls a model, and the same saved trace always produces the same report.
agenticlens diagnose run.json --baseline healthy-runs/ \
--save diagnosis.json --markdown investigation.md
from agenticlens.diagnosis import diagnose_run, explain_run
report = diagnose_run(run, baseline=healthy_runs)
print(report.attribution.faulty_step, report.attribution.confidence)
print(explain_run(report))
A complete, offline example lives in
examples/diagnosis_demo.py;
its saved report and narrative are in examples/diagnosis/artifacts/.
Failure taxonomy
Taxonomy version 1.0 (agenticlens.diagnosis.TAXONOMY_VERSION). Each
category records its layer, default severity, an attribution weight (how
strongly it points at its own span as the cause), and a next step.
| Category | Layer | Meaning | Attribution weight |
|---|---|---|---|
tool_error |
tool | A tool call returned an error or raised | 0.90 |
model_error |
model | A model call failed (provider error, refusal, malformed output) | 0.90 |
retrieval_error |
retrieval | A retrieval step failed before returning results | 0.90 |
timeout |
environment | A span failed with a timeout or exceeded timeout_ms |
0.85 |
validation_failure |
quality | An output or intermediate result failed validation | 0.85 |
delegation_failure |
orchestration | Delegating work to another agent failed | 0.85 |
retry_exhausted |
orchestration | Every retry of a failed operation also failed | 0.80 |
tool_loop |
orchestration | The same tool ran repeatedly with the same input | 0.70 |
handoff_loop |
orchestration | Agents handed control back and forth | 0.70 |
stagnation |
quality | Consecutive model calls produced identical output | 0.65 |
empty_retrieval |
retrieval | Retrieval returned no documents | 0.60 |
budget_exceeded |
resource | Run cost, tokens, or latency exceeded a configured budget | 0.40 |
injected_fault |
environment | A deliberately injected fault (agentic-chaos) hit the span | 1.00 |
incomplete_task |
outcome | The task failed and nothing more specific explains it | 0.30 |
Diagnosis rules
| Rule | Detects | Needs in the trace |
|---|---|---|
failed_span |
Origin failures (failed spans whose children did not fail first), grouped when identical, with propagation to failed ancestors and recovery by retries or a later successful attempt | span status, error_type/error_message |
injected_fault |
agentic-chaos faults on spans that did not fail themselves (for example injected latency) | chaos.injected / chaos.fault_type attributes |
retry_exhaustion |
Retries of one failure that never succeeded | retry spans |
tool_loop |
loop_threshold (3) identical consecutive tool calls per agent |
input_data, input_reference, or tool_args (lower confidence without them) |
handoff_loop |
handoff_round_trips (2) A→B→A round trips |
delegation spans with to_agent (or a child span of the receiving agent) |
stagnation |
stagnation_threshold (3) identical consecutive model outputs per agent |
captured output_data or output_reference |
empty_retrieval |
Retrieval spans that returned zero results | result_count, documents, or list output_data |
budget |
Run cost, token, or latency above DiagnosisConfig budgets |
span costs (budget is skipped and noted when cost is incomplete) |
incomplete_task |
Fallback when the run failed but every other diagnosis was recovered | run status / task_success |
Rules are versioned (rule_id@version on every diagnosis). Pass your own
rules with diagnose_run(run, rules=[...]); each rule implements
diagnose(view, config) -> list[Diagnosis].
Faulty-step and faulty-agent attribution
Blame is assigned only from diagnoses. Each diagnosis contributes to its origin span:
weight = taxonomy attribution_weight × diagnosis confidence
× 0.3 if the failure was recovered
× 1.15 for the earliest unrecovered origin (causal precedence)
× 0.5 for unrecovered origins that start after it (likely consequences)
Contributions are summed per span and normalized into shares that sum to 1.
The top candidate is the faulty step; its agent (inherited from the nearest
ancestor that names one) is the faulty agent, and agent_scores gives every
agent's share. The attribution confidence is the top share scaled by the
confidence of its strongest diagnosis, so a single weak lead stays
low-confidence.
Attribution.status is attributed, recovered (failures happened but the
run did not fail), unattributed (the run failed and no span explains it),
or no_failure.
Shares are not probabilities
Attribution shares are rule-weighted blame. Measure how often they are right on controlled failures before relying on them (below).
Attribution accuracy on controlled failures
A controlled failure is a trace whose faulty step is known in advance: either
a span carries agentic-chaos markers (chaos.injected), or the producer sets
run.metadata["expected_faulty_span_id"] (and optionally
expected_faulty_agent, expected_category).
agenticlens attribution-accuracy examples/diagnosis/controlled_failures \
--save accuracy.json --min-step-accuracy 0.9
The report gives top-1 and top-3 faulty-step accuracy, faulty-agent and category accuracy, each with a Wilson interval, and a calibration gap (mean confidence minus top-1 accuracy).
The repository ships a 40-case corpus across ten scenarios (tool timeouts
with exhausted retries, a recovered transient error followed by a validation
failure, injected latency causing a downstream deadline, injected tool
errors, tool loops, planner model errors with propagation, empty retrieval,
stagnation, handoff loops, and nested tool failures), built by
examples/diagnosis/build_controlled_failures.py. Variants change agents and
add distractors. On this corpus, attribution identifies the faulty step,
agent, and category in 40/40 cases (95% Wilson interval 91.2%–100%); mean
confidence is 79.5%, so the confidence is conservative here.
Scope of that result
The corpus is synthetic and was written alongside the rules it tests. It shows the rules behave as designed on known failure shapes; it is not independent evidence of accuracy on production traces. Add your own controlled failures (agentic-chaos runs are a good source) and track the accuracy report in CI.
Incident timeline
report.timeline orders run start, span starts, failures, retries, handoffs,
injected faults, recoveries, and run completion by timestamp, with offsets
from the run start. The attributed root cause is marked. Routine span starts
are dropped first when a timeline exceeds max_events (500).
agenticlens trace timeline run.json --save timeline.json
Anomaly detection
With baseline= runs (or --baseline on the CLI), run-level and per-span
latency, tokens, and cost are compared with the baseline using the modified
z-score 0.6745 × (x − median) / MAD (threshold 3.5). Spans match across
runs by <span_type>:<name>; repeated spans are summed per run. When the
MAD is zero the score falls back to the mean absolute deviation, and when
every baseline value is identical only differences above a relative
tolerance (25%) are reported. At least five baseline runs are required per
metric; keys with fewer are listed in warnings.
Guardrails for automated analysis
AnalysisBudget caps spans analyzed (max_spans), span-tree depth
(max_depth, the recursion cap), diagnoses kept (max_diagnoses), wall-clock
time (max_seconds), and, for iterative analyzers, iterations
(max_iterations), spend (max_cost_usd), and stagnation
(stagnation_limit consecutive iterations that find nothing new).
diagnose_run(..., budget=AnalysisBudget(...)) applies the span, depth,
diagnosis, and time limits and records what was cut short in
report.guardrails. Your own analyzers can use the same guard:
from agenticlens.diagnosis import AnalysisBudget, AnalysisGuard
guard = AnalysisGuard(AnalysisBudget(max_iterations=10, max_cost_usd=0.50))
while not guard.exhausted:
finding = investigate_next() # e.g. a model-assisted step
guard.charge(cost_usd=finding.cost)
guard.record([finding.fingerprint]) # stagnation detection
status = guard.status()
Investigation narratives
explain_run(report) writes Markdown with a verdict, the root cause and its
supporting spans, contributing factors, the key timeline events, the tokens,
latency, and cost spent in failed and retry spans, efficiency findings,
anomalies, next steps from the taxonomy, and limitations.
explain_recommendations(workflow, recommendations) explains why each
optimization recommendation's waste occurred (repeated prompts, oversized
retrieval, history growth, duplicate tool calls, handoff bloat, model tier,
unabsorbed faults), what was measured, the projected impact, the quality
risk, and the cited evidence.
agenticlens explain run.json # trace -> diagnose and explain
agenticlens explain diagnosis.json # saved diagnosis report
agenticlens explain workflow.json # workflow -> recommendations narrative
CLI summary
| Command | Purpose |
|---|---|
diagnose RUN |
Diagnoses, attribution, anomalies (--baseline), budgets, guardrails; --fail-on-unrecovered exits 4 |
explain FILE |
Investigation narrative for a trace, diagnosis report, or workflow |
trace show RUN [--span ID] |
Span tree and metrics, or one span's full record |
trace timeline RUN |
Incident timeline with the root cause marked |
attribution-accuracy PATH |
Accuracy on controlled failures; --min-step-accuracy exits 5 |
Report contract
DiagnosisReport (schema version 1.0) and AttributionAccuracyReport are
published as JSON Schemas in schemas/diagnosis/ and included in the wheel.
Reports carry the taxonomy, engine, and rule versions they were produced
with and contain no wall-clock timestamp of their own, so they reproduce
exactly from the saved trace.
Limitations
- Rules only see what the trace recorded. Missing statuses, inputs, or outputs make some rules silent or lower their confidence.
- Attribution follows recorded structure and timing. A root cause outside the trace (for example an upstream data change) is reported as the first recorded symptom.
- Validation failures are attributed to the validation span; the step that produced the rejected value may be the real cause.
- Anomaly detection assumes baseline runs of the same workflow and is per-metric; it does not correct for multiple comparisons.