agentgovern 2.0.2__tar.gz → 2.2.0__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 (64) hide show
  1. {agentgovern-2.0.2 → agentgovern-2.2.0}/.gitignore +10 -0
  2. agentgovern-2.2.0/CHANGELOG.md +174 -0
  3. {agentgovern-2.0.2 → agentgovern-2.2.0}/PKG-INFO +3 -3
  4. agentgovern-2.2.0/agentgovern/_version.py +1 -0
  5. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/exceptions.py +111 -111
  6. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/gate2.py +202 -202
  7. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/hook/cli.py +8 -2
  8. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/hook/data/hook.py +538 -42
  9. agentgovern-2.2.0/agentgovern/hook/drift.py +234 -0
  10. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/hook/installer.py +146 -22
  11. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/hook/settings.py +20 -1
  12. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/instrumentors/__init__.py +4 -4
  13. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/instrumentors/_base.py +545 -545
  14. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/instrumentors/langchain.py +411 -411
  15. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/instrumentors/langgraph.py +684 -684
  16. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/types.py +114 -114
  17. {agentgovern-2.0.2 → agentgovern-2.2.0}/examples/langgraph_credit_decision_agent.py +189 -189
  18. {agentgovern-2.0.2 → agentgovern-2.2.0}/pyproject.toml +2 -2
  19. agentgovern-2.2.0/tests/hook/test_drift.py +235 -0
  20. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/hook/test_hook_v2_context_snapshot.py +3 -2
  21. agentgovern-2.2.0/tests/hook/test_hook_v2_outbox_queue.py +791 -0
  22. agentgovern-2.2.0/tests/hook/test_hook_v2_payload_extraction.py +66 -0
  23. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/hook/test_hook_v2_payloads.py +116 -0
  24. agentgovern-2.2.0/tests/hook/test_hook_v2_remediation_message.py +134 -0
  25. agentgovern-2.2.0/tests/hook/test_hook_v2_timing_log.py +70 -0
  26. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/hook/test_installer.py +229 -7
  27. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_base_instrumentor.py +388 -388
  28. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_client.py +210 -210
  29. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_gate2_full_path.py +255 -255
  30. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_gate2_latency.py +86 -86
  31. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_gate2_verdict_handler.py +258 -258
  32. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_instrument_langchain.py +131 -131
  33. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_langchain.py +193 -193
  34. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_langchain_gate1.py +155 -155
  35. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_langchain_gate2_callbacks.py +293 -293
  36. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_langgraph.py +686 -686
  37. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_output_capture_contract.py +276 -276
  38. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_payload_schema.py +231 -231
  39. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_timestamp.py +81 -81
  40. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_version_consistency.py +34 -34
  41. agentgovern-2.0.2/CHANGELOG.md +0 -67
  42. agentgovern-2.0.2/agentgovern/_version.py +0 -1
  43. {agentgovern-2.0.2 → agentgovern-2.2.0}/LICENSE +0 -0
  44. {agentgovern-2.0.2 → agentgovern-2.2.0}/README.md +0 -0
  45. {agentgovern-2.0.2 → agentgovern-2.2.0}/RELEASING.md +0 -0
  46. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/__init__.py +0 -0
  47. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/__main__.py +0 -0
  48. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/client.py +0 -0
  49. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/hook/__init__.py +0 -0
  50. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentgovern/hook/data/__init__.py +0 -0
  51. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentshield/__init__.py +0 -0
  52. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentshield/client.py +0 -0
  53. {agentgovern-2.0.2 → agentgovern-2.2.0}/agentshield/types.py +0 -0
  54. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/__init__.py +0 -0
  55. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/hook/__init__.py +0 -0
  56. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/hook/conftest.py +0 -0
  57. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/hook/test_bundled_hook.py +0 -0
  58. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/hook/test_hook_v2_dispatch.py +0 -0
  59. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/hook/test_hook_v2_transcript_reading.py +0 -0
  60. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/hook/test_hook_v2_turn_index.py +0 -0
  61. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/hook/test_hook_v2_warn_verdict.py +0 -0
  62. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/hook/test_settings.py +0 -0
  63. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_evaluate_input.py +0 -0
  64. {agentgovern-2.0.2 → agentgovern-2.2.0}/tests/test_rate_limit_handling.py +0 -0
@@ -34,3 +34,13 @@ test-results/
34
34
  .claude/settings.local.json
35
35
 
36
36
  .vercel
37
+
38
+ # Ad-hoc test fixtures with decoy/fake secret-shaped content (never real).
39
+ decoy.env
40
+ decoy_notebook.ipynb
41
+ decoy_multiedit.md
42
+
43
+ .env*
44
+
45
+ # Demo scaffolding (fixtures, decoy secrets for demo scripts) — not product code.
46
+ demo-fixtures/
@@ -0,0 +1,174 @@
1
+ # Changelog
2
+
3
+ ## 2.2.0 — 2026-08-11
4
+
5
+ ### Fix — capture-only hook events (UserPromptSubmit/PostToolUse/Stop) no longer block on a discarded synchronous verdict; async audit-event parity (NAGT-137)
6
+
7
+ `AGENTGOVERN_HOOK_MODE`'s effect is now per-event, not global:
8
+
9
+ - **`UserPromptSubmit`/`PostToolUse`/`Stop`** are capture-only — the hook
10
+ never reads their ingest response for control flow — and now default to
11
+ `evaluation_mode: async` regardless of `AGENTGOVERN_HOOK_MODE`, so they no
12
+ longer pay the policy-engine's synchronous round trip (previously up to
13
+ the full `AGENTGOVERN_HOOK_TIMEOUT_MS`/shell timeout on every turn
14
+ boundary and every tool call). Set `AGENTGOVERN_HOOK_MODE=sync` explicitly
15
+ to force a synchronous verdict on these three as well (debugging).
16
+ - **`PreToolUse` always requests `sync` now** and always enforces the
17
+ verdict it gets back — it is the only event that gates whether a tool
18
+ runs, so its verdict was never safe to discard. **Behavior change:**
19
+ `AGENTGOVERN_HOOK_MODE=async` can no longer make `PreToolUse`
20
+ non-blocking; it could in 2.1.0 and earlier. This was documented (not
21
+ merely latent) — `packages/claude-hook/README.md`'s env-var reference
22
+ table and its rate-limit troubleshooting note both described `async` as
23
+ applying uniformly to all events — both updated in this release to
24
+ reflect the new per-event behavior.
25
+ - **Server-side (`apps/web`):** `evaluateInteractiveAsync` — already the
26
+ default path for all SDK-originated traffic (`evaluation_mode` defaults
27
+ to `async`) — previously never parsed the policy engine's top-level
28
+ `verdict`/`review_id` fields, so any tenant with an enforce-mode policy
29
+ got a generic `interactive_action.failed` audit event with no
30
+ `review_id` and no block-vs-escalate distinction, for every finding.
31
+ `evaluateInteractiveAsync` now fires the same `interactive_action.blocked`
32
+ /`.warned` (with `review_id`) events `evaluateInteractiveSyncAndRespond`
33
+ already did. **Behavior change:** `interactive_action.failed` no longer
34
+ fires for a verdict-bearing block outcome under async — it's retained
35
+ only as the fallback for a malformed/unparseable engine response.
36
+ Consumers filtering the audit-event stream on `.failed` specifically will
37
+ see fewer of them; those findings now surface as `.blocked`.
38
+ - Hook script version (`HOOK_VERSION`) bumped 2.1.0 → 2.2.0 in lockstep.
39
+
40
+ Requires `agentgovern hook install --force` (or a fresh install) to pick up
41
+ the updated hook script — existing deployed hooks do not auto-update.
42
+
43
+ Full design and build record: `docs/audit-reports/nagt-137-hook-sync-blocking-2026-08-03.md`.
44
+
45
+ ### Fix — `langgraph` extra had no upper version bound (NAGT-169)
46
+
47
+ `create_react_agent` is deprecated as of LangGraph v1.0 and scheduled for
48
+ removal in v2.0. The `langgraph` extra's dependency (`langgraph>=0.2.0`) had
49
+ no ceiling, so a fresh `pip install agentgovern[langgraph]` after LangGraph
50
+ 2.0 ships would have pulled it unpinned — breaking this SDK's own
51
+ `test_langgraph.py` integration tests and the `create_react_agent`-based
52
+ onboarding sample on first run. Pinned to `langgraph>=0.2.0,<2.0.0`. The
53
+ `langchain` extra's `langchain-core` dependency was checked and left
54
+ unbounded — the instrumentor only uses the foundational, non-deprecated
55
+ `BaseCallbackHandler`, and `langchain-core` has no announced 2.0.
56
+
57
+ ### Fix — `agentgovern hook status`/`hook test`/`hook install` crash on Windows under the default `cp1252` console codepage (NAGT-090, NAGT-169)
58
+
59
+ Several CLI status/error messages wrote non-ASCII characters (`✓`, `✗`, `→`)
60
+ that `cp1252` — the default console codepage on an unconfigured Windows
61
+ machine — cannot encode, raising `UnicodeEncodeError` in place of the
62
+ intended output. Replaced with ASCII equivalents (`OK`/`MISSING`, `->`) at
63
+ all five confirmed call sites: `hook status`'s per-event detail line and
64
+ `hook test`'s coverage banner (the two originally filed under NAGT-090),
65
+ `hook test`'s invalid-API-key/actor-ID error hints, and the
66
+ `Right-click -> Properties -> Security -> ...` permission-fix hint raised
67
+ when `~/.claude/settings.json` isn't writable — found by an exhaustive
68
+ non-cp1252-character sweep of the installer/CLI/instrumentor surface
69
+ while fixing the two originally-filed sites, not by a new bug report.
70
+
71
+ Full record: `docs/audit-reports/nagt-169-sdk-packaging-hygiene-2026-08-11.md`.
72
+
73
+ ## 2.1.0 — 2026-07-20
74
+
75
+ ### Feature — durable local outbox retry queue for the Claude Code hook (NAGT-095)
76
+
77
+ Fire-and-forget hook sends (`UserPromptSubmit`/`PostToolUse`/`Stop`) that fail
78
+ at the network level are no longer silently lost. A failed send is now queued
79
+ to a local durable outbox (`~/.agentgovern/outbox/`, one file per event) and
80
+ retried opportunistically on a later hook invocation — bounded so a backlog
81
+ can never meaningfully delay the invocation performing the retry (at most 5
82
+ items per invocation, a 1.5s wall-clock budget, a 60s per-file cooldown).
83
+ Permanently-abandoned events (48h TTL, or a 500-file size cap) are recorded to
84
+ `~/.agentgovern/outbox-dead/` instead of disappearing without a trace.
85
+
86
+ - **Swallowed-exception capture**: the network exception behind a failed send
87
+ is now captured into the queued record itself, turning the next real-world
88
+ occurrence into inspectable diagnostic data instead of a silent, unlogged
89
+ drop.
90
+ - **`client_event_id`**: every fire-and-forget payload now carries a
91
+ client-generated UUID v4, captured (not yet enforced) server-side —
92
+ positions a future dedup fix to act retroactively on rows this release
93
+ ever writes. Delivery remains at-least-once, not exactly-once, until that
94
+ follow-on ships.
95
+ - **Directory hardening**: `~/.agentgovern/outbox(-dead)/` are created
96
+ owner-only (`chmod 0700` on POSIX, an `icacls` ACL lockdown on Windows).
97
+ - Hook script version (`HOOK_VERSION`, distinct from this package version)
98
+ bumped 2.0.0 → 2.1.0 in lockstep.
99
+
100
+ No API or behavior change for installs that never experience a send failure.
101
+ Requires `agentgovern hook install --force` (or a fresh install) to pick up
102
+ the updated hook script — existing deployed hooks do not auto-update.
103
+
104
+ Full design and build record: `docs/audit-reports/nagt-095-async-prompt-drop-2026-07-20.md`.
105
+
106
+ ## 2.0.2 — 2026-07-02
107
+
108
+ ### Fix — README quickstart correctness (NAGT-019)
109
+
110
+ Two documentation corrections so the published quickstart actually works for a
111
+ first-time customer. No code or API change.
112
+
113
+ - **Install line** now reads `pip install "agentgovern[langchain]"`. The base
114
+ `pip install agentgovern` installs no runtime dependencies (they moved to the
115
+ `[sdk]`/`[langchain]` extras in 0.2.0), so the LangChain quickstart that
116
+ follows it could not run as written.
117
+ - **API-key prefix** in the Claude Code Hook env-var example corrected from
118
+ `as_prod_` to `ag_prod_`, matching the prefix the server actually issues and
119
+ validates (`ag_prod_`/`ag_stg_`/`ag_dev_`).
120
+
121
+ Surfaced by the IAG-007 customer-docs Phase 1 audit (PR #237).
122
+
123
+ ## 2.0.1 — 2026-06-29
124
+
125
+ ### Fix — Gate 1 wire-shape contract
126
+
127
+ `evaluate_input()` now nests `context` and `metadata` inside the `input` field of
128
+ the request body, matching the engine's `EvaluateInputRequest` schema. Previously
129
+ these were sent at the top level, where the engine silently dropped them — so input
130
+ policies that depend on `context`/`metadata` were evaluated against missing data.
131
+ The public Python signature is unchanged; only the on-the-wire serialization is
132
+ corrected. Surfaced by the SEC-002 pre-flight contract check.
133
+
134
+ ## 0.2.0 — 2026-06-11
135
+
136
+ ### Breaking change — optional SDK dependencies
137
+
138
+ `httpx`, `pydantic`, and `python-dotenv` are no longer installed by default.
139
+ They have moved to the `[sdk]` optional extra.
140
+
141
+ **Affected users:** anyone who installed `agentgovern` and used the agent SDK
142
+ (`init()`, `track_action()`, `evaluate_input()`, framework instrumentors).
143
+
144
+ **Migration:**
145
+
146
+ ```bash
147
+ # Before
148
+ pip install agentgovern
149
+
150
+ # After — agent SDK usage
151
+ pip install agentgovern[sdk]
152
+
153
+ # After — LangChain (sdk is pulled in automatically)
154
+ pip install agentgovern[langchain]
155
+
156
+ # Hook-only users (Claude Code governance) — no change needed
157
+ pip install agentgovern
158
+ agentgovern hook install
159
+ ```
160
+
161
+ **Errata (2026-07-10):** this entry originally read "Claude Code / Cursor
162
+ governance" — Cursor was never shipped as a hook target; corrected to
163
+ Claude Code, the only host with a working client.
164
+
165
+ ### Why
166
+
167
+ The hook CLI (`agentgovern hook install/uninstall/status/test`) is pure Python
168
+ standard library and does not need httpx or pydantic. Moving these to an
169
+ optional extra reduces the installation footprint for hook-only deployments
170
+ from ~45 MB to ~1 MB.
171
+
172
+ ## 0.1.1b15 — prior
173
+
174
+ Initial beta releases. See git history.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: agentgovern
3
- Version: 2.0.2
3
+ Version: 2.2.0
4
4
  Summary: Compliance-as-code middleware for agentic AI workflows.
5
5
  Project-URL: Homepage, https://agentgovern.zirahn.com
6
6
  Project-URL: Repository, https://github.com/ahmedkhan-zirahn/agentgovern
@@ -42,7 +42,7 @@ Requires-Dist: python-dotenv>=1.0.0; extra == 'langchain'
42
42
  Provides-Extra: langgraph
43
43
  Requires-Dist: httpx>=0.27.0; extra == 'langgraph'
44
44
  Requires-Dist: langchain-core>=0.3.0; extra == 'langgraph'
45
- Requires-Dist: langgraph>=0.2.0; extra == 'langgraph'
45
+ Requires-Dist: langgraph<2.0.0,>=0.2.0; extra == 'langgraph'
46
46
  Requires-Dist: pydantic>=2.10.0; extra == 'langgraph'
47
47
  Requires-Dist: python-dotenv>=1.0.0; extra == 'langgraph'
48
48
  Provides-Extra: openai
@@ -0,0 +1 @@
1
+ __version__ = "2.2.0"
@@ -1,111 +1,111 @@
1
- """AgentGovern SDK exceptions."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import TYPE_CHECKING
6
-
7
- if TYPE_CHECKING:
8
- from agentgovern.types import InputEvaluation
9
-
10
-
11
- # noqa: N818 — name is part of stable SDK public API; renaming would break import contracts for existing users.
12
- class PolicyViolation(Exception): # noqa: N818
13
- """
14
- Raised by Gate 1 when enforcement_mode=enforce and the input is blocked.
15
-
16
- Gate 2 (async ingest) never raises — it fails silently per the fail-open guarantee.
17
- """
18
-
19
- def __init__(self, evaluation: InputEvaluation) -> None:
20
- first = evaluation.violations[0] if evaluation.violations else None
21
- msg = first.reason if first else "Input blocked by compliance policy."
22
- super().__init__(msg)
23
- self.evaluation = evaluation
24
-
25
- @property
26
- def rule_code(self) -> str | None:
27
- return self.evaluation.violations[0].rule_code if self.evaluation.violations else None
28
-
29
- @property
30
- def reason(self) -> str:
31
- return str(self.args[0])
32
-
33
- @property
34
- def framework_ref(self) -> str | None:
35
- return self.evaluation.violations[0].framework_ref if self.evaluation.violations else None
36
-
37
- def __repr__(self) -> str:
38
- return (
39
- f"PolicyViolation(rule_code={self.rule_code!r}, "
40
- f"reason={self.reason!r}, "
41
- f"framework_ref={self.framework_ref!r})"
42
- )
43
-
44
-
45
- # noqa: N818 — name is part of stable SDK public API; renaming would break import contracts for existing users.
46
- class GateUnavailable(Exception): # noqa: N818
47
- """
48
- Raised when Gate 1 is unreachable and fail_closed=True.
49
-
50
- With the default fail_closed=False, Gate 1 errors are silently treated as
51
- allowed=True so the agent continues unaffected.
52
- """
53
-
54
-
55
- class OutputBlocked(Exception): # noqa: N818
56
- """
57
- Raised by Gate 2 when the agent's output is blocked in enforce mode.
58
-
59
- The output is never returned to the caller. Catch this to handle a blocked
60
- response (e.g. show an error message, fall back to a safe default, log to
61
- your own audit trail).
62
- """
63
-
64
- def __init__(self, reason: str, *, evaluation_id: str | None = None) -> None:
65
- super().__init__(reason)
66
- self.reason = reason
67
- self.evaluation_id = evaluation_id
68
-
69
- def __repr__(self) -> str:
70
- return (
71
- f"OutputBlocked(reason={self.reason!r}, "
72
- f"evaluation_id={self.evaluation_id!r})"
73
- )
74
-
75
-
76
- class OutputEscalated(Exception): # noqa: N818
77
- """
78
- Raised by Gate 2 when the agent's output is escalated for human review.
79
-
80
- A review_queue row has been created. The output is held until a human
81
- approves or rejects it. Catch this to notify the caller that a review
82
- is in progress and surface the review_id so they can poll for a decision.
83
- """
84
-
85
- def __init__(self, review_id: str, *, evaluation_id: str | None = None) -> None:
86
- super().__init__(f"Output escalated for human review: {review_id}")
87
- self.review_id = review_id
88
- self.evaluation_id = evaluation_id
89
-
90
- def __repr__(self) -> str:
91
- return (
92
- f"OutputEscalated(review_id={self.review_id!r}, "
93
- f"evaluation_id={self.evaluation_id!r})"
94
- )
95
-
96
-
97
- class ClientError(Exception):
98
- """
99
- Raised on 4xx responses from the AgentGovern API.
100
-
101
- This always propagates regardless of fail_silently — a 4xx indicates a
102
- configuration problem (wrong API key, invalid payload) that the caller
103
- should fix, not a transient network issue.
104
- """
105
-
106
- def __init__(self, message: str, *, status_code: int) -> None:
107
- super().__init__(message)
108
- self.status_code = status_code
109
-
110
- def __repr__(self) -> str:
111
- return f"ClientError(status_code={self.status_code}, message={str(self.args[0])!r})"
1
+ """AgentGovern SDK exceptions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from agentgovern.types import InputEvaluation
9
+
10
+
11
+ # noqa: N818 — name is part of stable SDK public API; renaming would break import contracts for existing users.
12
+ class PolicyViolation(Exception): # noqa: N818
13
+ """
14
+ Raised by Gate 1 when enforcement_mode=enforce and the input is blocked.
15
+
16
+ Gate 2 (async ingest) never raises — it fails silently per the fail-open guarantee.
17
+ """
18
+
19
+ def __init__(self, evaluation: InputEvaluation) -> None:
20
+ first = evaluation.violations[0] if evaluation.violations else None
21
+ msg = first.reason if first else "Input blocked by compliance policy."
22
+ super().__init__(msg)
23
+ self.evaluation = evaluation
24
+
25
+ @property
26
+ def rule_code(self) -> str | None:
27
+ return self.evaluation.violations[0].rule_code if self.evaluation.violations else None
28
+
29
+ @property
30
+ def reason(self) -> str:
31
+ return str(self.args[0])
32
+
33
+ @property
34
+ def framework_ref(self) -> str | None:
35
+ return self.evaluation.violations[0].framework_ref if self.evaluation.violations else None
36
+
37
+ def __repr__(self) -> str:
38
+ return (
39
+ f"PolicyViolation(rule_code={self.rule_code!r}, "
40
+ f"reason={self.reason!r}, "
41
+ f"framework_ref={self.framework_ref!r})"
42
+ )
43
+
44
+
45
+ # noqa: N818 — name is part of stable SDK public API; renaming would break import contracts for existing users.
46
+ class GateUnavailable(Exception): # noqa: N818
47
+ """
48
+ Raised when Gate 1 is unreachable and fail_closed=True.
49
+
50
+ With the default fail_closed=False, Gate 1 errors are silently treated as
51
+ allowed=True so the agent continues unaffected.
52
+ """
53
+
54
+
55
+ class OutputBlocked(Exception): # noqa: N818
56
+ """
57
+ Raised by Gate 2 when the agent's output is blocked in enforce mode.
58
+
59
+ The output is never returned to the caller. Catch this to handle a blocked
60
+ response (e.g. show an error message, fall back to a safe default, log to
61
+ your own audit trail).
62
+ """
63
+
64
+ def __init__(self, reason: str, *, evaluation_id: str | None = None) -> None:
65
+ super().__init__(reason)
66
+ self.reason = reason
67
+ self.evaluation_id = evaluation_id
68
+
69
+ def __repr__(self) -> str:
70
+ return (
71
+ f"OutputBlocked(reason={self.reason!r}, "
72
+ f"evaluation_id={self.evaluation_id!r})"
73
+ )
74
+
75
+
76
+ class OutputEscalated(Exception): # noqa: N818
77
+ """
78
+ Raised by Gate 2 when the agent's output is escalated for human review.
79
+
80
+ A review_queue row has been created. The output is held until a human
81
+ approves or rejects it. Catch this to notify the caller that a review
82
+ is in progress and surface the review_id so they can poll for a decision.
83
+ """
84
+
85
+ def __init__(self, review_id: str, *, evaluation_id: str | None = None) -> None:
86
+ super().__init__(f"Output escalated for human review: {review_id}")
87
+ self.review_id = review_id
88
+ self.evaluation_id = evaluation_id
89
+
90
+ def __repr__(self) -> str:
91
+ return (
92
+ f"OutputEscalated(review_id={self.review_id!r}, "
93
+ f"evaluation_id={self.evaluation_id!r})"
94
+ )
95
+
96
+
97
+ class ClientError(Exception):
98
+ """
99
+ Raised on 4xx responses from the AgentGovern API.
100
+
101
+ This always propagates regardless of fail_silently — a 4xx indicates a
102
+ configuration problem (wrong API key, invalid payload) that the caller
103
+ should fix, not a transient network issue.
104
+ """
105
+
106
+ def __init__(self, message: str, *, status_code: int) -> None:
107
+ super().__init__(message)
108
+ self.status_code = status_code
109
+
110
+ def __repr__(self) -> str:
111
+ return f"ClientError(status_code={self.status_code}, message={str(self.args[0])!r})"