agentdebugx 0.2.9__tar.gz → 0.2.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/PKG-INFO +1 -1
  2. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/23_status_v0_2.md +1 -1
  3. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/pyproject.toml +1 -1
  4. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/__init__.py +5 -1
  5. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/attribution.py +213 -4
  6. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/LICENSE +0 -0
  7. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/README.md +0 -0
  8. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/00_overview.md +0 -0
  9. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/01_literature_survey.md +0 -0
  10. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/02_architecture.md +0 -0
  11. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/03_taxonomy.md +0 -0
  12. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/04_trace_schema.md +0 -0
  13. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/05_adapters.md +0 -0
  14. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/06_detectors.md +0 -0
  15. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/07_attribution.md +0 -0
  16. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/08_recovery.md +0 -0
  17. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/09_error_database.md +0 -0
  18. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/10_taxonomy_induction.md +0 -0
  19. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/11_multimodal.md +0 -0
  20. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/12_ui_dashboard.md +0 -0
  21. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/13_class_design.md +0 -0
  22. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/14_api_reference.md +0 -0
  23. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/15_roadmap.md +0 -0
  24. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/16_governance.md +0 -0
  25. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/17_claude_code_design_patterns.md +0 -0
  26. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/18_comparison_codex_vs_design.md +0 -0
  27. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/19_error_hub.md +0 -0
  28. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/20_deep_debug.md +0 -0
  29. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/21_integrations.md +0 -0
  30. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/22_industry_track_paper_eval_plan.md +0 -0
  31. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/ERROR_TAXONOMY.md +0 -0
  32. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/OPEN_SOURCE_DEVELOPMENT_PLAN.md +0 -0
  33. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/README.md +0 -0
  34. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/RESEARCH_SURVEY.md +0 -0
  35. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/benchmarks/e2e_v0_2_3.md +0 -0
  36. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/benchmarks/e2e_v0_2_4.md +0 -0
  37. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/benchmarks/v0_1_smoke.json +0 -0
  38. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/benchmarks/v0_1_smoke.md +0 -0
  39. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/docs/benchmarks/who_when_v0_2_6_leaderboard.md +0 -0
  40. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/adapters/__init__.py +0 -0
  41. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/adapters/base.py +0 -0
  42. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/adapters/crewai.py +0 -0
  43. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/adapters/langgraph.py +0 -0
  44. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/adapters/otel.py +0 -0
  45. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/adapters/raw.py +0 -0
  46. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/analyzers.py +0 -0
  47. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/cli.py +0 -0
  48. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/deep.py +0 -0
  49. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/detectors.py +0 -0
  50. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/events.py +0 -0
  51. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/hub/__init__.py +0 -0
  52. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/hub/backend_base.py +0 -0
  53. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/hub/backends.py +0 -0
  54. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/hub/bundle.py +0 -0
  55. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/hub/scrub.py +0 -0
  56. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/instrumentation.py +0 -0
  57. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/integrations/__init__.py +0 -0
  58. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/integrations/claude_skill.py +0 -0
  59. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/integrations/openhands.py +0 -0
  60. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/judges.py +0 -0
  61. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/llm.py +0 -0
  62. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/models.py +0 -0
  63. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/recorder.py +0 -0
  64. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/recovery.py +0 -0
  65. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/storage.py +0 -0
  66. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/taxonomy.py +0 -0
  67. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/traceback.py +0 -0
  68. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/ui/__init__.py +0 -0
  69. {agentdebugx-0.2.9 → agentdebugx-0.2.10}/src/agentdebug/ui/server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentdebugx
3
- Version: 0.2.9
3
+ Version: 0.2.10
4
4
  Summary: Portable error analysis, tracing, and recovery framework for agentic AI systems. Import as `agentdebug`.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -23,6 +23,7 @@ the forward-looking plan; this doc is the rear-view mirror.
23
23
  | Attribution | `agentdebug.attribution.BinarySearchAttributor` | ✅ **new 0.2.3** | oracle-LLM logarithmic convergence + fallback + render elision |
24
24
  | Attribution | `agentdebug.attribution.CounterfactualAttributor` | ✅ **new 0.2.7** | scripted-rescue-prob ranking + candidate selection priority (findings → errors → tail) + dual fallback (no candidates / silent LLM) |
25
25
  | Attribution | `agentdebug.attribution.SBFLAttributor` (Tarantula / Ochiai / DStar) | ✅ **new 0.2.8** | signature design + ranking on synthetic corpus + 3 formulas + dual fallback + edge cases. Model-free: zero LLM cost at inference. |
26
+ | Attribution | `agentdebug.attribution.EnsembleAttributor` (+ `AttributionBudget`) | ✅ **new 0.2.10** | Borda + Bayesian merge, weighted backends, walltime/backend-count budget, swallows backend exceptions, falls back when all empty, honest per-source provenance |
26
27
  | Recovery | `agentdebug.recovery.ReflexionSuggestion` | ✅ stable | per-finding + empty |
27
28
  | Recovery | `agentdebug.recovery.CriticRecoverer` + `VerifierSpec` registry | ✅ **new 0.2.3** | 5 family-matched verifier templates; dedup + custom-override |
28
29
  | Recovery | `agentdebug.recovery.SelfRefineLoop` | ✅ **new 0.2.9** | scripted-LLM happy + silent-LLM safe + multi-iter; per-finding generator→critic→refiner |
@@ -54,7 +55,6 @@ across 32 source files.
54
55
  | [07_attribution.md](./07_attribution.md) | `CounterfactualAttributor` — *real* replay variant | true re-rollout requires framework-specific replay surface; the v0.2.7 LLM-simulated variant ships now, the real-replay variant is gated on adapter support (LangGraph checkpointer / OpenHands rewind) | v0.4 |
55
56
  | [07_attribution.md](./07_attribution.md) | `SBFLAttributor` — *corpus* | shipped in 0.2.8 (`tarantula`/`ochiai`/`dstar`); awaiting paired-trace adoption to gather a useful corpus in production | corpus tooling deferred to v0.4 |
56
57
  | [07_attribution.md](./07_attribution.md) | `DeltaDebugAttributor` (Zeller) | same replay constraint | v0.3 |
57
- | [07_attribution.md](./07_attribution.md) | `EnsembleAttributor` | trivial once Counterfactual lands; awaits Counterfactual | v0.3 |
58
58
  | [08_recovery.md](./08_recovery.md) | `LangGraphRewind` | depends on LangGraph checkpointer; ships when we have a real LangGraph user | v0.3 |
59
59
  | [08_recovery.md](./08_recovery.md) | `SagaRollback` | needs compensation registry on tool definitions; new schema | v0.3 |
60
60
  | [08_recovery.md](./08_recovery.md) | `MCTSBranchExploration` (LATS) | heavy; v2 feature | v2.0 |
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "agentdebugx"
3
- version = "0.2.9"
3
+ version = "0.2.10"
4
4
  description = "Portable error analysis, tracing, and recovery framework for agentic AI systems. Import as `agentdebug`."
5
5
  authors = ["ULab @ UIUC <ulab@illinois.edu>"]
6
6
  license = "MIT"
@@ -11,11 +11,13 @@ See ``docs/14_api_reference.md`` for the full public surface and
11
11
  from agentdebug.analyzers import HeuristicAnalyzer
12
12
  from agentdebug.attribution import (
13
13
  AllAtOnceAttributor,
14
+ AttributionBudget,
14
15
  AttributionResult,
15
16
  Attributor,
16
17
  BinarySearchAttributor,
17
18
  Blame,
18
19
  CounterfactualAttributor,
20
+ EnsembleAttributor,
19
21
  HeuristicAttributor,
20
22
  SBFLAttributor,
21
23
  StepByStepAttributor,
@@ -65,6 +67,7 @@ __all__ = [
65
67
  'Attributor',
66
68
  'Blame',
67
69
  'BusEvent',
70
+ 'AttributionBudget',
68
71
  'AutoManualRules',
69
72
  'BinarySearchAttributor',
70
73
  'CascadeFrame',
@@ -73,6 +76,7 @@ __all__ = [
73
76
  'DEFAULT_VERIFIERS',
74
77
  'Detector',
75
78
  'DetectorConfig',
79
+ 'EnsembleAttributor',
76
80
  'RepeatedStateDetector',
77
81
  'RepeatedToolCallDetector',
78
82
  'SBFLAttributor',
@@ -104,4 +108,4 @@ __all__ = [
104
108
  'get_failure_mode',
105
109
  ]
106
110
 
107
- __version__ = '0.2.9'
111
+ __version__ = '0.2.10'
@@ -19,7 +19,7 @@ from __future__ import annotations
19
19
 
20
20
  import logging
21
21
  from dataclasses import dataclass, field
22
- from typing import Any, Dict, List, Optional, Protocol, cast
22
+ from typing import Any, Dict, List, Optional, Protocol, Tuple, cast
23
23
 
24
24
 
25
25
  # Forward decl so BinarySearchAttributor.attribute can reference _EllipsisEvent
@@ -936,8 +936,217 @@ def _suspiciousness(
936
936
  return float((ef ** dstar_exponent) / denom)
937
937
 
938
938
 
939
+ @dataclass
940
+ class AttributionBudget:
941
+ """Optional cap on ensemble cost."""
942
+
943
+ max_backends: Optional[int] = None # short-circuit after N successful backends
944
+ max_seconds: Optional[float] = None # walltime cap; remaining backends skipped
945
+
946
+ def exceeded(
947
+ self, *, completed: int, elapsed_s: float,
948
+ ) -> bool:
949
+ if self.max_backends is not None and completed >= self.max_backends:
950
+ return True
951
+ if self.max_seconds is not None and elapsed_s >= self.max_seconds:
952
+ return True
953
+ return False
954
+
955
+
956
+ class EnsembleAttributor:
957
+ """Compose any subset of attributors into a single ranked result.
958
+
959
+ Two merge strategies:
960
+
961
+ * ``"borda"`` (default) — each backend's ranked list contributes
962
+ Borda points (top hypothesis = N, next = N-1, …). Steps are
963
+ ranked by total points × backend weight; ties broken by primary
964
+ confidence × weight.
965
+ * ``"bayesian"`` — combine confidences as 1 - ∏(1 - w_i × c_i)
966
+ across backends that nominated the same (event_id, step_index).
967
+ Treats each backend as an independent noisy classifier; good when
968
+ backends are heterogeneous and confidences are well-calibrated.
969
+
970
+ Provenance is HONEST: every output ``Blame.sources`` lists every
971
+ backend that contributed; the rationale aggregates source-by-source
972
+ so the UI can show which backends agreed.
973
+
974
+ Pair with ``AttributionBudget`` to cap wall-clock or backend count
975
+ when running expensive backends (e.g., BinarySearch + Counterfactual)
976
+ alongside cheap ones (Heuristic + SBFL).
977
+ """
978
+
979
+ id = 'ensemble'
980
+
981
+ def __init__(
982
+ self,
983
+ backends: List[Attributor],
984
+ *,
985
+ weights: Optional[Dict[str, float]] = None,
986
+ merge: str = 'borda',
987
+ top_k: int = 5,
988
+ budget: Optional[AttributionBudget] = None,
989
+ fallback: Optional[Attributor] = None,
990
+ ) -> None:
991
+ if not backends:
992
+ raise ValueError('EnsembleAttributor requires at least one backend')
993
+ if merge not in {'borda', 'bayesian'}:
994
+ raise ValueError(
995
+ f"Unknown merge strategy {merge!r}; use 'borda' or 'bayesian'"
996
+ )
997
+ self.backends = list(backends)
998
+ self.weights = weights or {b.id: 1.0 for b in backends}
999
+ self.merge = merge
1000
+ self.top_k = top_k
1001
+ self.budget = budget
1002
+ self.fallback: Attributor = fallback or HeuristicAttributor()
1003
+
1004
+ def attribute(
1005
+ self,
1006
+ trajectory: AgentTrajectory,
1007
+ findings: List[FailureFinding],
1008
+ ) -> AttributionResult:
1009
+ import time as _time
1010
+
1011
+ per_backend: List[Tuple[str, AttributionResult]] = []
1012
+ started = _time.perf_counter()
1013
+ for backend in self.backends:
1014
+ if self.budget and self.budget.exceeded(
1015
+ completed=len(per_backend),
1016
+ elapsed_s=_time.perf_counter() - started,
1017
+ ):
1018
+ LOG.debug('ensemble budget hit; stopping early')
1019
+ break
1020
+ try:
1021
+ result = backend.attribute(trajectory, findings)
1022
+ except Exception as exc: # pragma: no cover - defensive
1023
+ LOG.warning('ensemble backend %s raised: %s', backend.id, exc)
1024
+ continue
1025
+ per_backend.append((backend.id, result))
1026
+
1027
+ # Any backend produced hypotheses? Otherwise fall back.
1028
+ if not any(r.hypotheses for _id, r in per_backend):
1029
+ return self.fallback.attribute(trajectory, findings)
1030
+
1031
+ if self.merge == 'borda':
1032
+ merged = self._merge_borda(per_backend)
1033
+ else:
1034
+ merged = self._merge_bayesian(per_backend)
1035
+
1036
+ merged = merged[: self.top_k]
1037
+ elapsed_ms = int((_time.perf_counter() - started) * 1000)
1038
+ return AttributionResult(
1039
+ method=self.id,
1040
+ hypotheses=merged,
1041
+ elapsed_ms=elapsed_ms,
1042
+ raw={
1043
+ 'merge': self.merge,
1044
+ 'backends_run': [bid for bid, _ in per_backend],
1045
+ 'weights': dict(self.weights),
1046
+ 'budget_exceeded': bool(
1047
+ self.budget
1048
+ and self.budget.exceeded(
1049
+ completed=len(per_backend),
1050
+ elapsed_s=_time.perf_counter() - started,
1051
+ )
1052
+ ),
1053
+ },
1054
+ )
1055
+
1056
+ # ---- merge strategies ----
1057
+
1058
+ def _merge_borda(
1059
+ self, per_backend: List[Tuple[str, AttributionResult]],
1060
+ ) -> List[Blame]:
1061
+ # Aggregator keyed by canonical step identity.
1062
+ agg: Dict[Tuple[Optional[str], Optional[int]], _MergeRow] = {}
1063
+ for backend_id, result in per_backend:
1064
+ w = self.weights.get(backend_id, 1.0)
1065
+ n = len(result.hypotheses)
1066
+ for rank, h in enumerate(result.hypotheses):
1067
+ key = (h.span_id, h.step_index)
1068
+ row = agg.setdefault(key, _MergeRow(span_id=h.span_id,
1069
+ step_index=h.step_index))
1070
+ # Borda points: top = N, next = N-1, … last = 1
1071
+ row.borda_points += (n - rank) * w
1072
+ row.weighted_conf_sum += w * h.confidence
1073
+ row.weight_sum += w
1074
+ row.sources.add(backend_id)
1075
+ if h.agent_name and not row.agent_name:
1076
+ row.agent_name = h.agent_name
1077
+ if h.rationale and backend_id not in row.rationales:
1078
+ row.rationales[backend_id] = h.rationale
1079
+ row.evidence.extend(h.evidence)
1080
+ ranked = sorted(
1081
+ agg.values(),
1082
+ key=lambda r: (-r.borda_points, -r.weighted_conf_sum),
1083
+ )
1084
+ return [self._row_to_blame(r) for r in ranked]
1085
+
1086
+ def _merge_bayesian(
1087
+ self, per_backend: List[Tuple[str, AttributionResult]],
1088
+ ) -> List[Blame]:
1089
+ agg: Dict[Tuple[Optional[str], Optional[int]], _MergeRow] = {}
1090
+ for backend_id, result in per_backend:
1091
+ w = self.weights.get(backend_id, 1.0)
1092
+ for h in result.hypotheses:
1093
+ key = (h.span_id, h.step_index)
1094
+ row = agg.setdefault(key, _MergeRow(span_id=h.span_id,
1095
+ step_index=h.step_index))
1096
+ # P(no source supports) *= (1 - w*c)
1097
+ effective = max(0.0, min(1.0, w * h.confidence))
1098
+ row.bayesian_not_pos *= (1.0 - effective)
1099
+ row.weighted_conf_sum += w * h.confidence
1100
+ row.weight_sum += w
1101
+ row.sources.add(backend_id)
1102
+ if h.agent_name and not row.agent_name:
1103
+ row.agent_name = h.agent_name
1104
+ if h.rationale and backend_id not in row.rationales:
1105
+ row.rationales[backend_id] = h.rationale
1106
+ row.evidence.extend(h.evidence)
1107
+ ranked = sorted(
1108
+ agg.values(),
1109
+ key=lambda r: (-(1.0 - r.bayesian_not_pos), -r.weighted_conf_sum),
1110
+ )
1111
+ return [self._row_to_blame(r) for r in ranked]
1112
+
1113
+ def _row_to_blame(self, row: '_MergeRow') -> Blame:
1114
+ if self.merge == 'borda':
1115
+ # Confidence = weighted average of backend confidences (cap at 1).
1116
+ conf = min(1.0, row.weighted_conf_sum / max(row.weight_sum, 1e-9))
1117
+ else:
1118
+ conf = max(0.0, min(1.0, 1.0 - row.bayesian_not_pos))
1119
+ rationale = '; '.join(
1120
+ f'{bid}: {txt[:140]}' for bid, txt in row.rationales.items()
1121
+ ) or 'ensemble agreement'
1122
+ return Blame(
1123
+ span_id=row.span_id,
1124
+ step_index=row.step_index,
1125
+ agent_name=row.agent_name,
1126
+ confidence=conf,
1127
+ rationale=rationale,
1128
+ evidence=list(dict.fromkeys(row.evidence)), # dedupe preserve order
1129
+ sources=sorted(row.sources),
1130
+ )
1131
+
1132
+
1133
+ @dataclass
1134
+ class _MergeRow:
1135
+ span_id: Optional[str]
1136
+ step_index: Optional[int]
1137
+ agent_name: Optional[str] = None
1138
+ borda_points: float = 0.0
1139
+ bayesian_not_pos: float = 1.0
1140
+ weighted_conf_sum: float = 0.0
1141
+ weight_sum: float = 0.0
1142
+ sources: set[str] = field(default_factory=set)
1143
+ rationales: Dict[str, str] = field(default_factory=dict)
1144
+ evidence: List[str] = field(default_factory=list)
1145
+
1146
+
939
1147
  __all__ = [
940
- 'Attributor', 'Blame', 'AttributionResult',
941
- 'HeuristicAttributor', 'AllAtOnceAttributor', 'StepByStepAttributor',
942
- 'BinarySearchAttributor', 'CounterfactualAttributor', 'SBFLAttributor',
1148
+ 'AllAtOnceAttributor', 'AttributionBudget', 'AttributionResult',
1149
+ 'Attributor', 'BinarySearchAttributor', 'Blame',
1150
+ 'CounterfactualAttributor', 'EnsembleAttributor', 'HeuristicAttributor',
1151
+ 'SBFLAttributor', 'StepByStepAttributor',
943
1152
  ]
File without changes
File without changes
File without changes