agentgovern 2.0.2__tar.gz → 2.1.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.
- {agentgovern-2.0.2 → agentgovern-2.1.0}/.gitignore +10 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/CHANGELOG.md +38 -1
- {agentgovern-2.0.2 → agentgovern-2.1.0}/PKG-INFO +1 -1
- agentgovern-2.1.0/agentgovern/_version.py +1 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/exceptions.py +111 -111
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/gate2.py +202 -202
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/hook/cli.py +8 -2
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/hook/data/hook.py +451 -28
- agentgovern-2.1.0/agentgovern/hook/drift.py +234 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/hook/installer.py +141 -19
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/hook/settings.py +19 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/instrumentors/__init__.py +4 -4
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/instrumentors/_base.py +545 -545
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/instrumentors/langchain.py +411 -411
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/instrumentors/langgraph.py +684 -684
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/types.py +114 -114
- {agentgovern-2.0.2 → agentgovern-2.1.0}/examples/langgraph_credit_decision_agent.py +189 -189
- {agentgovern-2.0.2 → agentgovern-2.1.0}/pyproject.toml +1 -1
- agentgovern-2.1.0/tests/hook/test_drift.py +235 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/hook/test_hook_v2_context_snapshot.py +3 -2
- agentgovern-2.1.0/tests/hook/test_hook_v2_outbox_queue.py +726 -0
- agentgovern-2.1.0/tests/hook/test_hook_v2_payload_extraction.py +66 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/hook/test_hook_v2_payloads.py +116 -0
- agentgovern-2.1.0/tests/hook/test_hook_v2_remediation_message.py +134 -0
- agentgovern-2.1.0/tests/hook/test_hook_v2_timing_log.py +70 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/hook/test_installer.py +229 -7
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_base_instrumentor.py +388 -388
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_client.py +210 -210
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_gate2_full_path.py +255 -255
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_gate2_latency.py +86 -86
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_gate2_verdict_handler.py +258 -258
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_instrument_langchain.py +131 -131
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_langchain.py +193 -193
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_langchain_gate1.py +155 -155
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_langchain_gate2_callbacks.py +293 -293
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_langgraph.py +686 -686
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_output_capture_contract.py +276 -276
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_payload_schema.py +231 -231
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_timestamp.py +81 -81
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_version_consistency.py +34 -34
- agentgovern-2.0.2/agentgovern/_version.py +0 -1
- {agentgovern-2.0.2 → agentgovern-2.1.0}/LICENSE +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/README.md +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/RELEASING.md +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/__init__.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/__main__.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/client.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/hook/__init__.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentgovern/hook/data/__init__.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentshield/__init__.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentshield/client.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/agentshield/types.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/__init__.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/hook/__init__.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/hook/conftest.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/hook/test_bundled_hook.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/hook/test_hook_v2_dispatch.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/hook/test_hook_v2_transcript_reading.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/hook/test_hook_v2_turn_index.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/hook/test_hook_v2_warn_verdict.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/hook/test_settings.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.0}/tests/test_evaluate_input.py +0 -0
- {agentgovern-2.0.2 → agentgovern-2.1.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/
|
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.1.0 — 2026-07-20
|
|
4
|
+
|
|
5
|
+
### Feature — durable local outbox retry queue for the Claude Code hook (NAGT-095)
|
|
6
|
+
|
|
7
|
+
Fire-and-forget hook sends (`UserPromptSubmit`/`PostToolUse`/`Stop`) that fail
|
|
8
|
+
at the network level are no longer silently lost. A failed send is now queued
|
|
9
|
+
to a local durable outbox (`~/.agentgovern/outbox/`, one file per event) and
|
|
10
|
+
retried opportunistically on a later hook invocation — bounded so a backlog
|
|
11
|
+
can never meaningfully delay the invocation performing the retry (at most 5
|
|
12
|
+
items per invocation, a 1.5s wall-clock budget, a 60s per-file cooldown).
|
|
13
|
+
Permanently-abandoned events (48h TTL, or a 500-file size cap) are recorded to
|
|
14
|
+
`~/.agentgovern/outbox-dead/` instead of disappearing without a trace.
|
|
15
|
+
|
|
16
|
+
- **Swallowed-exception capture**: the network exception behind a failed send
|
|
17
|
+
is now captured into the queued record itself, turning the next real-world
|
|
18
|
+
occurrence into inspectable diagnostic data instead of a silent, unlogged
|
|
19
|
+
drop.
|
|
20
|
+
- **`client_event_id`**: every fire-and-forget payload now carries a
|
|
21
|
+
client-generated UUID v4, captured (not yet enforced) server-side —
|
|
22
|
+
positions a future dedup fix to act retroactively on rows this release
|
|
23
|
+
ever writes. Delivery remains at-least-once, not exactly-once, until that
|
|
24
|
+
follow-on ships.
|
|
25
|
+
- **Directory hardening**: `~/.agentgovern/outbox(-dead)/` are created
|
|
26
|
+
owner-only (`chmod 0700` on POSIX, an `icacls` ACL lockdown on Windows).
|
|
27
|
+
- Hook script version (`HOOK_VERSION`, distinct from this package version)
|
|
28
|
+
bumped 2.0.0 → 2.1.0 in lockstep.
|
|
29
|
+
|
|
30
|
+
No API or behavior change for installs that never experience a send failure.
|
|
31
|
+
Requires `agentgovern hook install --force` (or a fresh install) to pick up
|
|
32
|
+
the updated hook script — existing deployed hooks do not auto-update.
|
|
33
|
+
|
|
34
|
+
Full design and build record: `docs/audit-reports/nagt-095-async-prompt-drop-2026-07-20.md`.
|
|
35
|
+
|
|
3
36
|
## 2.0.2 — 2026-07-02
|
|
4
37
|
|
|
5
38
|
### Fix — README quickstart correctness (NAGT-019)
|
|
@@ -50,11 +83,15 @@ pip install agentgovern[sdk]
|
|
|
50
83
|
# After — LangChain (sdk is pulled in automatically)
|
|
51
84
|
pip install agentgovern[langchain]
|
|
52
85
|
|
|
53
|
-
# Hook-only users (Claude Code
|
|
86
|
+
# Hook-only users (Claude Code governance) — no change needed
|
|
54
87
|
pip install agentgovern
|
|
55
88
|
agentgovern hook install
|
|
56
89
|
```
|
|
57
90
|
|
|
91
|
+
**Errata (2026-07-10):** this entry originally read "Claude Code / Cursor
|
|
92
|
+
governance" — Cursor was never shipped as a hook target; corrected to
|
|
93
|
+
Claude Code, the only host with a working client.
|
|
94
|
+
|
|
58
95
|
### Why
|
|
59
96
|
|
|
60
97
|
The hook CLI (`agentgovern hook install/uninstall/status/test`) is pure Python
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentgovern
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.1.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})"
|