agentic-exception-sdk 1.1.3__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 (111) hide show
  1. agentic_exception_sdk-1.1.3/LICENSE +21 -0
  2. agentic_exception_sdk-1.1.3/PKG-INFO +291 -0
  3. agentic_exception_sdk-1.1.3/README.md +225 -0
  4. agentic_exception_sdk-1.1.3/pyproject.toml +187 -0
  5. agentic_exception_sdk-1.1.3/setup.cfg +4 -0
  6. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/__init__.py +308 -0
  7. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/__init__.pyi +38 -0
  8. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/budget/__init__.py +14 -0
  9. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/budget/__init__.pyi +5 -0
  10. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/budget/models.py +48 -0
  11. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/budget/models.pyi +14 -0
  12. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/budget/streaming.py +144 -0
  13. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/budget/streaming.pyi +27 -0
  14. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/budget/watchdog.py +285 -0
  15. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/budget/watchdog.pyi +24 -0
  16. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/bundle.py +102 -0
  17. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/bundle.pyi +37 -0
  18. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/config/__init__.py +10 -0
  19. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/config/__init__.pyi +3 -0
  20. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/config/validate.py +74 -0
  21. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/config/validate.pyi +11 -0
  22. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/escalation/__init__.py +22 -0
  23. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/escalation/__init__.pyi +5 -0
  24. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/escalation/checkpoint.py +116 -0
  25. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/escalation/checkpoint.pyi +13 -0
  26. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/escalation/handlers.py +27 -0
  27. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/escalation/handlers.pyi +7 -0
  28. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/escalation/router.py +137 -0
  29. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/escalation/router.pyi +22 -0
  30. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/multi_agent/__init__.py +32 -0
  31. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/multi_agent/__init__.pyi +6 -0
  32. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/multi_agent/consensus.py +139 -0
  33. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/multi_agent/consensus.pyi +18 -0
  34. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/multi_agent/parallel.py +204 -0
  35. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/multi_agent/parallel.pyi +35 -0
  36. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/multi_agent/router.py +131 -0
  37. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/multi_agent/router.pyi +14 -0
  38. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/multi_agent/sla.py +63 -0
  39. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/multi_agent/sla.pyi +10 -0
  40. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/noop.py +27 -0
  41. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/noop.pyi +5 -0
  42. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/observability/__init__.py +24 -0
  43. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/observability/__init__.pyi +6 -0
  44. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/observability/metrics.py +189 -0
  45. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/observability/metrics.pyi +36 -0
  46. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/observability/otel.py +211 -0
  47. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/observability/otel.pyi +9 -0
  48. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/observability/sink.py +68 -0
  49. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/observability/sink.pyi +12 -0
  50. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/observability/structured_log.py +110 -0
  51. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/observability/structured_log.pyi +8 -0
  52. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/persistence/__init__.py +27 -0
  53. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/persistence/__init__.pyi +35 -0
  54. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/persistence/attestation.py +36 -0
  55. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/persistence/attestation.pyi +8 -0
  56. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/persistence/availability.py +14 -0
  57. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/persistence/availability.pyi +7 -0
  58. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/persistence/null_provider.py +71 -0
  59. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/persistence/null_provider.pyi +19 -0
  60. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/persistence/provider.py +70 -0
  61. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/persistence/provider.pyi +33 -0
  62. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/propagation/__init__.py +39 -0
  63. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/propagation/__init__.pyi +5 -0
  64. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/propagation/bus.py +162 -0
  65. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/propagation/bus.pyi +30 -0
  66. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/propagation/dlq.py +173 -0
  67. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/propagation/dlq.pyi +28 -0
  68. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/propagation/protocol.py +129 -0
  69. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/propagation/protocol.pyi +10 -0
  70. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/py.typed +0 -0
  71. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/__init__.py +59 -0
  72. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/__init__.pyi +7 -0
  73. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/circuit_breaker.py +650 -0
  74. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/circuit_breaker.pyi +58 -0
  75. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/compensating.py +97 -0
  76. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/compensating.pyi +9 -0
  77. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/fallback.py +88 -0
  78. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/fallback.pyi +16 -0
  79. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/recovery.py +50 -0
  80. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/recovery.pyi +11 -0
  81. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/retry.py +455 -0
  82. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/retry.pyi +62 -0
  83. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/wrap.py +1073 -0
  84. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/resilience/wrap.pyi +28 -0
  85. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/__init__.py +56 -0
  86. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/__init__.pyi +7 -0
  87. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/classifier.py +216 -0
  88. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/classifier.pyi +8 -0
  89. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/enums.py +77 -0
  90. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/enums.pyi +22 -0
  91. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/envelope.py +223 -0
  92. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/envelope.pyi +29 -0
  93. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/errors.py +173 -0
  94. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/errors.pyi +34 -0
  95. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/owasp.py +55 -0
  96. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/taxonomy/owasp.pyi +19 -0
  97. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/validation/__init__.py +27 -0
  98. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/validation/__init__.pyi +6 -0
  99. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/validation/gates.py +93 -0
  100. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/validation/gates.pyi +16 -0
  101. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/validation/guard_rails.py +114 -0
  102. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/validation/guard_rails.pyi +18 -0
  103. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/validation/rules_version.py +5 -0
  104. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/validation/rules_version.pyi +3 -0
  105. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/validation/trust_boundary.py +420 -0
  106. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk/validation/trust_boundary.pyi +18 -0
  107. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk.egg-info/PKG-INFO +291 -0
  108. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk.egg-info/SOURCES.txt +109 -0
  109. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk.egg-info/dependency_links.txt +1 -0
  110. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk.egg-info/requires.txt +48 -0
  111. agentic_exception_sdk-1.1.3/src/agentic_exception_sdk.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 29monsankye
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,291 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentic-exception-sdk
3
+ Version: 1.1.3
4
+ Summary: Domain-agnostic exception management SDK for agentic systems
5
+ Author-email: 29monsankye <29monsank@gmail.com>
6
+ Maintainer-email: 29monsankye <29monsank@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/29monsankye/agentic-exception-sdk
9
+ Project-URL: Repository, https://github.com/29monsankye/agentic-exception-sdk
10
+ Project-URL: Issues, https://github.com/29monsankye/agentic-exception-sdk/issues
11
+ Keywords: agentic,agents,llm,exception-handling,resilience,circuit-breaker,observability,sdk
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: jcs>=0.2.1
25
+ Requires-Dist: pydantic>=2.0.0
26
+ Requires-Dist: uuid-utils>=0.10.0
27
+ Provides-Extra: otel
28
+ Requires-Dist: opentelemetry-api>=1.29.0; extra == "otel"
29
+ Requires-Dist: opentelemetry-sdk>=1.29.0; extra == "otel"
30
+ Provides-Extra: redis
31
+ Requires-Dist: redis>=5.0.0; extra == "redis"
32
+ Provides-Extra: prometheus
33
+ Requires-Dist: prometheus-client>=0.20.0; extra == "prometheus"
34
+ Provides-Extra: structlog
35
+ Requires-Dist: structlog>=24.0.0; extra == "structlog"
36
+ Provides-Extra: re2
37
+ Requires-Dist: google-re2>=1.1; extra == "re2"
38
+ Provides-Extra: all
39
+ Requires-Dist: opentelemetry-api>=1.29.0; extra == "all"
40
+ Requires-Dist: opentelemetry-sdk>=1.29.0; extra == "all"
41
+ Requires-Dist: redis>=5.0.0; extra == "all"
42
+ Requires-Dist: prometheus-client>=0.20.0; extra == "all"
43
+ Requires-Dist: structlog>=24.0.0; extra == "all"
44
+ Requires-Dist: google-re2>=1.1; extra == "all"
45
+ Provides-Extra: dev
46
+ Requires-Dist: setuptools>=68; extra == "dev"
47
+ Requires-Dist: wheel; extra == "dev"
48
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
49
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
50
+ Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
51
+ Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
52
+ Requires-Dist: pytest-randomly>=3.15.0; extra == "dev"
53
+ Requires-Dist: pytest-timeout>=2.3.0; extra == "dev"
54
+ Requires-Dist: pytest-benchmark>=4.0.0; extra == "dev"
55
+ Requires-Dist: freezegun>=1.5.0; extra == "dev"
56
+ Requires-Dist: hypothesis>=6.100.0; extra == "dev"
57
+ Requires-Dist: mypy>=1.10.0; extra == "dev"
58
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
59
+ Requires-Dist: uv>=0.5.0; extra == "dev"
60
+ Requires-Dist: build>=1.2.0; extra == "dev"
61
+ Requires-Dist: bandit>=1.7.0; extra == "dev"
62
+ Requires-Dist: mutmut>=2.0; extra == "dev"
63
+ Requires-Dist: pip-audit>=2.7.0; extra == "dev"
64
+ Requires-Dist: types-redis; extra == "dev"
65
+ Dynamic: license-file
66
+
67
+ # agentic-exception-sdk
68
+
69
+ A domain-agnostic exception management SDK for agentic systems. Classifies
70
+ failures into three tiers, routes them through pluggable handlers, wraps tool
71
+ boundaries with resilient call wrappers, and provides safe no-op defaults for
72
+ zero-config adoption.
73
+
74
+ Designed as a **standalone Python package** installed and wired into host
75
+ projects via constructor injection. Contains no host-specific imports, no global
76
+ state, and no module-level singletons.
77
+
78
+ **Python 3.11+** — no `exceptiongroup` backport required.
79
+
80
+ ---
81
+
82
+ ## Three-Tier Classification
83
+
84
+ Every failure is assigned to exactly one class:
85
+
86
+ | Class | Meaning | Action |
87
+ |---|---|---|
88
+ | `EXCEPTION` | Gracefully resolvable | Automatic retry or fallback — no human involved |
89
+ | `ISSUE` | Requires intervention | Agent pauses; checkpoint handoff or human escalation |
90
+ | `HARD_KILL` | Not resolvable | Agent terminates immediately; envelope written to DLQ |
91
+
92
+ `HARD_KILL` raises `AgentHardKillError(BaseException)` — it is never caught by
93
+ `except Exception` anywhere in the call stack.
94
+
95
+ ---
96
+
97
+ ## Install
98
+
99
+ ```bash
100
+ # Core only (pydantic + RFC 8785 JCS canonicalization)
101
+ pip install agentic-exception-sdk
102
+
103
+ # With OpenTelemetry export
104
+ pip install agentic-exception-sdk[otel]
105
+
106
+ # With Redis circuit-breaker state
107
+ pip install agentic-exception-sdk[redis]
108
+
109
+ # With Prometheus metrics
110
+ pip install agentic-exception-sdk[prometheus]
111
+
112
+ # With structlog JSON renderer
113
+ pip install agentic-exception-sdk[structlog]
114
+
115
+ # Everything
116
+ pip install agentic-exception-sdk[all]
117
+ ```
118
+
119
+ > **ReDoS protection:** Install with `pip install "agentic-exception-sdk[re2]"` to
120
+ > enable linear-time google-re2 redaction. Without it the SDK falls back to stdlib
121
+ > `re` with patterns designed to avoid catastrophic backtracking.
122
+
123
+ ---
124
+
125
+ ## Quick Start
126
+
127
+ ```python
128
+ from agentic_exception_sdk import (
129
+ AgentHardKillError,
130
+ NoOpResilienceBundle,
131
+ SafeContextSnapshot,
132
+ resilient,
133
+ )
134
+
135
+ # Zero-config: every component is a safe no-op
136
+ bundle = NoOpResilienceBundle()
137
+
138
+ # Wrap any tool boundary with the curried factory:
139
+ # resilient(bundle, ...)(fn)(*args, **kwargs)
140
+ result = resilient(
141
+ bundle,
142
+ tool_name="search_flights",
143
+ agent_id="booking-agent",
144
+ correlation_id="req-abc123",
145
+ context_snapshot=SafeContextSnapshot({"booking_id": "BK-001"}),
146
+ fallback_value=[], # returned on EXCEPTION or ISSUE if set
147
+ )(flight_client.search)(origin, destination, dates)
148
+
149
+ # Catch HARD_KILL only at the outermost executor boundary
150
+ try:
151
+ executor.run(booking_request)
152
+ except AgentHardKillError as hk:
153
+ audit.emit(hk.envelope.exception_id)
154
+ raise
155
+ ```
156
+
157
+ Wire a real `ResilienceBundle` in the composition root when you need retries,
158
+ circuit breaking, budget enforcement, or escalation routing:
159
+
160
+ ```python
161
+ from agentic_exception_sdk import (
162
+ AgentBudget,
163
+ BudgetWatchdog,
164
+ ExponentialBackoffRetry,
165
+ InMemoryCircuitBreaker,
166
+ InMemoryMetricsCollector,
167
+ ResilienceBundle,
168
+ )
169
+
170
+ bundle = ResilienceBundle(
171
+ retry_policy=ExponentialBackoffRetry(
172
+ max_attempts=3,
173
+ base_delay_seconds=0.5,
174
+ jitter=True,
175
+ retryable_exceptions=(TimeoutError, ConnectionError),
176
+ ),
177
+ circuit_breaker=InMemoryCircuitBreaker(
178
+ failure_threshold=5,
179
+ cooldown_seconds=30,
180
+ ),
181
+ agent_budget=BudgetWatchdog(AgentBudget(
182
+ max_seconds=45.0,
183
+ max_tool_calls=10,
184
+ failure_budget=3,
185
+ max_total_cost_micros_usd=2_000_000, # $2.00
186
+ )),
187
+ metrics_collector=InMemoryMetricsCollector(), # swap for PrometheusMetricsCollector
188
+ )
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Key Concepts
194
+
195
+ - **`ResilienceBundle`** — the single injection object. Every field defaults to
196
+ a safe no-op. Inject only what you need.
197
+ - **`NoOpResilienceBundle()`** — returns a fully defaulted bundle. Use as the
198
+ default when no bundle is injected.
199
+ - **`resilient()` / `async_resilient()`** — curried factory wrappers. Preserve
200
+ wrapped callable parameter types via `ParamSpec`. Use `async_resilient()` with
201
+ `asyncio.timeout()` for async tool calls and LLM calls with timeouts.
202
+ - **`AgentHardKillError(BaseException)`** — raised on `HARD_KILL`. Propagates
203
+ past all `except Exception` handlers. Catch only at the outermost executor
204
+ boundary.
205
+ - **`SafeContextSnapshot`** — frozen Pydantic `RootModel` for agent state.
206
+ Enforces JSON-safe values, max depth 5, max 100 keys per dict, and rejects
207
+ non-finite floats.
208
+ - **`extend_lineage()`** — validated factory for multi-agent envelope
209
+ propagation. Caps lineage at 64 hops; exceeding the cap auto-promotes to
210
+ `HARD_KILL`.
211
+ - **`TrustBoundaryValidator`** — runs pattern-based redaction (secrets, tokens,
212
+ PANs, bidi controls) over exception **messages** and over **string values in
213
+ context snapshots**. Snapshot handling redacts values under sensitively-named
214
+ keys (`password`, `token`, `secret`, …) outright, and pattern-scans every
215
+ other string value — including nested and list elements — so a secret embedded
216
+ in an otherwise-benign field (`{"note": "token is sk-…"}`) is still redacted,
217
+ not merely length-bounded. A single ReDoS budget spans the walk and fails
218
+ closed. Redaction remains best-effort — see **Limitations & Scope**.
219
+ - **`ResilienceBundle.metrics_collector`** — optional `MetricsCollector`
220
+ (protocol). `PrometheusMetricsCollector` emits `agent_exceptions_total`,
221
+ `agent_hard_kills_total`, `agent_retries_total`, `agent_budget_exhausted_total`.
222
+ - **`ResilienceBundle.meter_provider`** — optional OTel `MeterProvider`. When
223
+ set, creates an `agent_tool_call_duration_seconds` histogram per call.
224
+ - **`ResilienceBundle.dlq`** — `DeadLetterQueue` for HARD_KILL envelopes.
225
+ Defaults to `InMemoryDLQ` (ring buffer, drop-oldest). Swap for `AsyncInMemoryDLQ`
226
+ in async contexts, or a satellite-package adapter (SQS, Pub/Sub, Redis).
227
+ - **`ResilienceBundle.recovery_policy`** — optional post-classification recovery
228
+ hook called before the default fallback/escalation path on EXCEPTION and ISSUE
229
+ tiers. Satellite package `agentic-exception-retry` supplies `AdaptiveRetryPolicy`
230
+ and `CircuitAwareRetryPolicy`.
231
+ - **`ResilienceBundle.async_circuit_breaker`** — optional async circuit breaker
232
+ used by `async_resilient()`. Falls back to sync `circuit_breaker` when not set.
233
+ - **`allow_sync_llm_timeout`** — opt-in flag on `resilient()` that runs `fn` in
234
+ a bounded `ThreadPoolExecutor` with the given `timeout_seconds`. The leaked
235
+ thread cannot be cancelled and may continue spending tokens after the wrapper
236
+ returns; prefer `async_resilient()` with `asyncio.timeout()` for LLM calls.
237
+ - **`PersistenceProvider` / `NullProvider`** — the audit-persistence boundary.
238
+ Every envelope carries a full-envelope integrity leaf hash (`envelope_leaf_hash()`),
239
+ distinct from the `envelope_canonical_sha256()` dedup key. The default in-process
240
+ `NullProvider` computes real leaf hashes and in-memory Merkle roots but emits
241
+ unsigned, non-durable checkpoints. `sentirock.attestation()` reports the active
242
+ provider's capabilities (`durable`, `checkpoint_signing`, `worm`).
243
+
244
+ ## Integrity & audit status
245
+
246
+ Every governed call emits a leaf hash over the redacted envelope and participates
247
+ in an in-memory Merkle log. In the default (in-process) provider these checkpoints
248
+ are unsigned and non-durable — the SDK's attestation API and span attributes
249
+ (`sentirock.provider.durable`, `sentirock.audit.degraded`) report this explicitly.
250
+ Durable, signed, WORM-retained audit trails are delivered by the persistence
251
+ substrate on our enterprise roadmap; the `PersistenceProvider` interface it plugs
252
+ into is already published in this repo (the conformance suite that will gate full
253
+ attestation is planned).
254
+
255
+ ## Streaming Budget Enforcement
256
+
257
+ ```python
258
+ from agentic_exception_sdk import AgentBudget, BudgetWatchdog
259
+ from agentic_exception_sdk.budget import StreamingBudgetGuard
260
+
261
+ watchdog = BudgetWatchdog(AgentBudget(max_output_tokens=100))
262
+ stream = [{"text": "hi", "tokens": 1}, {"text": " there", "tokens": 2}]
263
+
264
+ for chunk in StreamingBudgetGuard.wrap_sync_stream(
265
+ stream,
266
+ watchdog,
267
+ tool_name="openai-chat",
268
+ agent_id="agent-1",
269
+ token_extractor=lambda c: c["tokens"],
270
+ ):
271
+ print(chunk["text"])
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Limitations & Scope
277
+
278
+ The SDK's controls are **mitigations, not guarantees**, and are not a substitute for defense-in-depth. Please read this before relying on the SDK for a security- or compliance-critical outcome:
279
+
280
+ - **Redaction is best-effort.** The trust-boundary sanitizer is pattern-based; novel, split, or obfuscated secrets can pass through. It is not a substitute for a dedicated DLP control, and should not be your sole barrier against secret exposure.
281
+ - **`HARD_KILL` is in-execution termination, not an operator kill switch.** `AgentHardKillError` stops the agent's *own run* when a tripwire fires. It is **not** an out-of-band control that deactivates a running agent, agent type, or fleet on demand — that capability is on the roadmap. Do not represent it as a fleet-wide or human-pullable deactivation switch.
282
+ - **`AgentHardKillError` extends `BaseException` — mind your runtime.** This is deliberate (it survives `except Exception`), but runtimes that treat `BaseException` as fatal (Celery workers, some ASGI/thread-pool executors) may tear down the worker when it propagates. Catch it at your outermost executor boundary and translate it to your runtime's shutdown/abort convention; do not let it escape uncaught into a shared worker pool.
283
+ - **Integrity hashing is not a durable audit trail by default.** The in-process provider emits unsigned, non-durable checkpoints and attests this via `sentirock.attestation()` (`durable=false`). Durable, signed, WORM-retained audit is delivered by the enterprise substrate on the roadmap.
284
+ - **Not a compliance certification.** Any coverage mapping (e.g. to the OWASP LLM Top 10) documents what the SDK *does*; it does not make your system compliant with any regulation. Compliance is a property of your overall system and processes.
285
+ - **No warranty.** Provided under the MIT License, **"AS IS," without warranty of any kind** (see [LICENSE](LICENSE)). You are responsible for validating its behavior in your own environment.
286
+
287
+ ---
288
+
289
+ ## License
290
+
291
+ MIT License — see [LICENSE](LICENSE). The SDK is provided "AS IS", without warranty of any kind; the authors are not liable for any claim or damages arising from its use. See **Limitations & Scope** above.
@@ -0,0 +1,225 @@
1
+ # agentic-exception-sdk
2
+
3
+ A domain-agnostic exception management SDK for agentic systems. Classifies
4
+ failures into three tiers, routes them through pluggable handlers, wraps tool
5
+ boundaries with resilient call wrappers, and provides safe no-op defaults for
6
+ zero-config adoption.
7
+
8
+ Designed as a **standalone Python package** installed and wired into host
9
+ projects via constructor injection. Contains no host-specific imports, no global
10
+ state, and no module-level singletons.
11
+
12
+ **Python 3.11+** — no `exceptiongroup` backport required.
13
+
14
+ ---
15
+
16
+ ## Three-Tier Classification
17
+
18
+ Every failure is assigned to exactly one class:
19
+
20
+ | Class | Meaning | Action |
21
+ |---|---|---|
22
+ | `EXCEPTION` | Gracefully resolvable | Automatic retry or fallback — no human involved |
23
+ | `ISSUE` | Requires intervention | Agent pauses; checkpoint handoff or human escalation |
24
+ | `HARD_KILL` | Not resolvable | Agent terminates immediately; envelope written to DLQ |
25
+
26
+ `HARD_KILL` raises `AgentHardKillError(BaseException)` — it is never caught by
27
+ `except Exception` anywhere in the call stack.
28
+
29
+ ---
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ # Core only (pydantic + RFC 8785 JCS canonicalization)
35
+ pip install agentic-exception-sdk
36
+
37
+ # With OpenTelemetry export
38
+ pip install agentic-exception-sdk[otel]
39
+
40
+ # With Redis circuit-breaker state
41
+ pip install agentic-exception-sdk[redis]
42
+
43
+ # With Prometheus metrics
44
+ pip install agentic-exception-sdk[prometheus]
45
+
46
+ # With structlog JSON renderer
47
+ pip install agentic-exception-sdk[structlog]
48
+
49
+ # Everything
50
+ pip install agentic-exception-sdk[all]
51
+ ```
52
+
53
+ > **ReDoS protection:** Install with `pip install "agentic-exception-sdk[re2]"` to
54
+ > enable linear-time google-re2 redaction. Without it the SDK falls back to stdlib
55
+ > `re` with patterns designed to avoid catastrophic backtracking.
56
+
57
+ ---
58
+
59
+ ## Quick Start
60
+
61
+ ```python
62
+ from agentic_exception_sdk import (
63
+ AgentHardKillError,
64
+ NoOpResilienceBundle,
65
+ SafeContextSnapshot,
66
+ resilient,
67
+ )
68
+
69
+ # Zero-config: every component is a safe no-op
70
+ bundle = NoOpResilienceBundle()
71
+
72
+ # Wrap any tool boundary with the curried factory:
73
+ # resilient(bundle, ...)(fn)(*args, **kwargs)
74
+ result = resilient(
75
+ bundle,
76
+ tool_name="search_flights",
77
+ agent_id="booking-agent",
78
+ correlation_id="req-abc123",
79
+ context_snapshot=SafeContextSnapshot({"booking_id": "BK-001"}),
80
+ fallback_value=[], # returned on EXCEPTION or ISSUE if set
81
+ )(flight_client.search)(origin, destination, dates)
82
+
83
+ # Catch HARD_KILL only at the outermost executor boundary
84
+ try:
85
+ executor.run(booking_request)
86
+ except AgentHardKillError as hk:
87
+ audit.emit(hk.envelope.exception_id)
88
+ raise
89
+ ```
90
+
91
+ Wire a real `ResilienceBundle` in the composition root when you need retries,
92
+ circuit breaking, budget enforcement, or escalation routing:
93
+
94
+ ```python
95
+ from agentic_exception_sdk import (
96
+ AgentBudget,
97
+ BudgetWatchdog,
98
+ ExponentialBackoffRetry,
99
+ InMemoryCircuitBreaker,
100
+ InMemoryMetricsCollector,
101
+ ResilienceBundle,
102
+ )
103
+
104
+ bundle = ResilienceBundle(
105
+ retry_policy=ExponentialBackoffRetry(
106
+ max_attempts=3,
107
+ base_delay_seconds=0.5,
108
+ jitter=True,
109
+ retryable_exceptions=(TimeoutError, ConnectionError),
110
+ ),
111
+ circuit_breaker=InMemoryCircuitBreaker(
112
+ failure_threshold=5,
113
+ cooldown_seconds=30,
114
+ ),
115
+ agent_budget=BudgetWatchdog(AgentBudget(
116
+ max_seconds=45.0,
117
+ max_tool_calls=10,
118
+ failure_budget=3,
119
+ max_total_cost_micros_usd=2_000_000, # $2.00
120
+ )),
121
+ metrics_collector=InMemoryMetricsCollector(), # swap for PrometheusMetricsCollector
122
+ )
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Key Concepts
128
+
129
+ - **`ResilienceBundle`** — the single injection object. Every field defaults to
130
+ a safe no-op. Inject only what you need.
131
+ - **`NoOpResilienceBundle()`** — returns a fully defaulted bundle. Use as the
132
+ default when no bundle is injected.
133
+ - **`resilient()` / `async_resilient()`** — curried factory wrappers. Preserve
134
+ wrapped callable parameter types via `ParamSpec`. Use `async_resilient()` with
135
+ `asyncio.timeout()` for async tool calls and LLM calls with timeouts.
136
+ - **`AgentHardKillError(BaseException)`** — raised on `HARD_KILL`. Propagates
137
+ past all `except Exception` handlers. Catch only at the outermost executor
138
+ boundary.
139
+ - **`SafeContextSnapshot`** — frozen Pydantic `RootModel` for agent state.
140
+ Enforces JSON-safe values, max depth 5, max 100 keys per dict, and rejects
141
+ non-finite floats.
142
+ - **`extend_lineage()`** — validated factory for multi-agent envelope
143
+ propagation. Caps lineage at 64 hops; exceeding the cap auto-promotes to
144
+ `HARD_KILL`.
145
+ - **`TrustBoundaryValidator`** — runs pattern-based redaction (secrets, tokens,
146
+ PANs, bidi controls) over exception **messages** and over **string values in
147
+ context snapshots**. Snapshot handling redacts values under sensitively-named
148
+ keys (`password`, `token`, `secret`, …) outright, and pattern-scans every
149
+ other string value — including nested and list elements — so a secret embedded
150
+ in an otherwise-benign field (`{"note": "token is sk-…"}`) is still redacted,
151
+ not merely length-bounded. A single ReDoS budget spans the walk and fails
152
+ closed. Redaction remains best-effort — see **Limitations & Scope**.
153
+ - **`ResilienceBundle.metrics_collector`** — optional `MetricsCollector`
154
+ (protocol). `PrometheusMetricsCollector` emits `agent_exceptions_total`,
155
+ `agent_hard_kills_total`, `agent_retries_total`, `agent_budget_exhausted_total`.
156
+ - **`ResilienceBundle.meter_provider`** — optional OTel `MeterProvider`. When
157
+ set, creates an `agent_tool_call_duration_seconds` histogram per call.
158
+ - **`ResilienceBundle.dlq`** — `DeadLetterQueue` for HARD_KILL envelopes.
159
+ Defaults to `InMemoryDLQ` (ring buffer, drop-oldest). Swap for `AsyncInMemoryDLQ`
160
+ in async contexts, or a satellite-package adapter (SQS, Pub/Sub, Redis).
161
+ - **`ResilienceBundle.recovery_policy`** — optional post-classification recovery
162
+ hook called before the default fallback/escalation path on EXCEPTION and ISSUE
163
+ tiers. Satellite package `agentic-exception-retry` supplies `AdaptiveRetryPolicy`
164
+ and `CircuitAwareRetryPolicy`.
165
+ - **`ResilienceBundle.async_circuit_breaker`** — optional async circuit breaker
166
+ used by `async_resilient()`. Falls back to sync `circuit_breaker` when not set.
167
+ - **`allow_sync_llm_timeout`** — opt-in flag on `resilient()` that runs `fn` in
168
+ a bounded `ThreadPoolExecutor` with the given `timeout_seconds`. The leaked
169
+ thread cannot be cancelled and may continue spending tokens after the wrapper
170
+ returns; prefer `async_resilient()` with `asyncio.timeout()` for LLM calls.
171
+ - **`PersistenceProvider` / `NullProvider`** — the audit-persistence boundary.
172
+ Every envelope carries a full-envelope integrity leaf hash (`envelope_leaf_hash()`),
173
+ distinct from the `envelope_canonical_sha256()` dedup key. The default in-process
174
+ `NullProvider` computes real leaf hashes and in-memory Merkle roots but emits
175
+ unsigned, non-durable checkpoints. `sentirock.attestation()` reports the active
176
+ provider's capabilities (`durable`, `checkpoint_signing`, `worm`).
177
+
178
+ ## Integrity & audit status
179
+
180
+ Every governed call emits a leaf hash over the redacted envelope and participates
181
+ in an in-memory Merkle log. In the default (in-process) provider these checkpoints
182
+ are unsigned and non-durable — the SDK's attestation API and span attributes
183
+ (`sentirock.provider.durable`, `sentirock.audit.degraded`) report this explicitly.
184
+ Durable, signed, WORM-retained audit trails are delivered by the persistence
185
+ substrate on our enterprise roadmap; the `PersistenceProvider` interface it plugs
186
+ into is already published in this repo (the conformance suite that will gate full
187
+ attestation is planned).
188
+
189
+ ## Streaming Budget Enforcement
190
+
191
+ ```python
192
+ from agentic_exception_sdk import AgentBudget, BudgetWatchdog
193
+ from agentic_exception_sdk.budget import StreamingBudgetGuard
194
+
195
+ watchdog = BudgetWatchdog(AgentBudget(max_output_tokens=100))
196
+ stream = [{"text": "hi", "tokens": 1}, {"text": " there", "tokens": 2}]
197
+
198
+ for chunk in StreamingBudgetGuard.wrap_sync_stream(
199
+ stream,
200
+ watchdog,
201
+ tool_name="openai-chat",
202
+ agent_id="agent-1",
203
+ token_extractor=lambda c: c["tokens"],
204
+ ):
205
+ print(chunk["text"])
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Limitations & Scope
211
+
212
+ The SDK's controls are **mitigations, not guarantees**, and are not a substitute for defense-in-depth. Please read this before relying on the SDK for a security- or compliance-critical outcome:
213
+
214
+ - **Redaction is best-effort.** The trust-boundary sanitizer is pattern-based; novel, split, or obfuscated secrets can pass through. It is not a substitute for a dedicated DLP control, and should not be your sole barrier against secret exposure.
215
+ - **`HARD_KILL` is in-execution termination, not an operator kill switch.** `AgentHardKillError` stops the agent's *own run* when a tripwire fires. It is **not** an out-of-band control that deactivates a running agent, agent type, or fleet on demand — that capability is on the roadmap. Do not represent it as a fleet-wide or human-pullable deactivation switch.
216
+ - **`AgentHardKillError` extends `BaseException` — mind your runtime.** This is deliberate (it survives `except Exception`), but runtimes that treat `BaseException` as fatal (Celery workers, some ASGI/thread-pool executors) may tear down the worker when it propagates. Catch it at your outermost executor boundary and translate it to your runtime's shutdown/abort convention; do not let it escape uncaught into a shared worker pool.
217
+ - **Integrity hashing is not a durable audit trail by default.** The in-process provider emits unsigned, non-durable checkpoints and attests this via `sentirock.attestation()` (`durable=false`). Durable, signed, WORM-retained audit is delivered by the enterprise substrate on the roadmap.
218
+ - **Not a compliance certification.** Any coverage mapping (e.g. to the OWASP LLM Top 10) documents what the SDK *does*; it does not make your system compliant with any regulation. Compliance is a property of your overall system and processes.
219
+ - **No warranty.** Provided under the MIT License, **"AS IS," without warranty of any kind** (see [LICENSE](LICENSE)). You are responsible for validating its behavior in your own environment.
220
+
221
+ ---
222
+
223
+ ## License
224
+
225
+ MIT License — see [LICENSE](LICENSE). The SDK is provided "AS IS", without warranty of any kind; the authors are not liable for any claim or damages arising from its use. See **Limitations & Scope** above.