agentgovern 2.2.0__tar.gz → 2.3.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 (69) hide show
  1. {agentgovern-2.2.0 → agentgovern-2.3.0}/.gitignore +7 -0
  2. {agentgovern-2.2.0 → agentgovern-2.3.0}/CHANGELOG.md +143 -0
  3. {agentgovern-2.2.0 → agentgovern-2.3.0}/PKG-INFO +43 -29
  4. {agentgovern-2.2.0 → agentgovern-2.3.0}/README.md +39 -10
  5. {agentgovern-2.2.0 → agentgovern-2.3.0}/RELEASING.md +5 -3
  6. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/__init__.py +54 -11
  7. agentgovern-2.3.0/agentgovern/_version.py +1 -0
  8. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/client.py +195 -15
  9. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/hook/data/hook.py +30 -5
  10. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/hook/drift.py +22 -0
  11. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/hook/installer.py +124 -43
  12. {agentgovern-2.2.0 → agentgovern-2.3.0}/pyproject.toml +15 -6
  13. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_hook_v2_context_snapshot.py +22 -4
  14. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_hook_v2_payloads.py +77 -20
  15. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_installer.py +151 -45
  16. agentgovern-2.3.0/tests/hook/test_matcher_coverage.py +115 -0
  17. agentgovern-2.3.0/tests/hook/test_tool_map_classification.py +68 -0
  18. agentgovern-2.3.0/tests/test_client.py +548 -0
  19. agentgovern-2.3.0/tests/test_import_guard.py +222 -0
  20. agentgovern-2.3.0/tests/test_langchain_1x_agent.py +166 -0
  21. agentgovern-2.3.0/tests/test_register_contract.py +120 -0
  22. agentgovern-2.2.0/agentgovern/_version.py +0 -1
  23. agentgovern-2.2.0/tests/test_client.py +0 -210
  24. {agentgovern-2.2.0 → agentgovern-2.3.0}/LICENSE +0 -0
  25. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/__main__.py +0 -0
  26. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/exceptions.py +0 -0
  27. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/gate2.py +0 -0
  28. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/hook/__init__.py +0 -0
  29. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/hook/cli.py +0 -0
  30. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/hook/data/__init__.py +0 -0
  31. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/hook/settings.py +0 -0
  32. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/instrumentors/__init__.py +0 -0
  33. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/instrumentors/_base.py +0 -0
  34. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/instrumentors/langchain.py +0 -0
  35. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/instrumentors/langgraph.py +0 -0
  36. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentgovern/types.py +0 -0
  37. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentshield/__init__.py +0 -0
  38. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentshield/client.py +0 -0
  39. {agentgovern-2.2.0 → agentgovern-2.3.0}/agentshield/types.py +0 -0
  40. {agentgovern-2.2.0 → agentgovern-2.3.0}/examples/langgraph_credit_decision_agent.py +0 -0
  41. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/__init__.py +0 -0
  42. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/__init__.py +0 -0
  43. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/conftest.py +0 -0
  44. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_bundled_hook.py +0 -0
  45. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_drift.py +0 -0
  46. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_hook_v2_dispatch.py +0 -0
  47. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_hook_v2_outbox_queue.py +0 -0
  48. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_hook_v2_payload_extraction.py +0 -0
  49. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_hook_v2_remediation_message.py +0 -0
  50. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_hook_v2_timing_log.py +0 -0
  51. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_hook_v2_transcript_reading.py +0 -0
  52. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_hook_v2_turn_index.py +0 -0
  53. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_hook_v2_warn_verdict.py +0 -0
  54. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/hook/test_settings.py +0 -0
  55. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_base_instrumentor.py +0 -0
  56. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_evaluate_input.py +0 -0
  57. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_gate2_full_path.py +0 -0
  58. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_gate2_latency.py +0 -0
  59. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_gate2_verdict_handler.py +0 -0
  60. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_instrument_langchain.py +0 -0
  61. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_langchain.py +0 -0
  62. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_langchain_gate1.py +0 -0
  63. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_langchain_gate2_callbacks.py +0 -0
  64. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_langgraph.py +0 -0
  65. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_output_capture_contract.py +0 -0
  66. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_payload_schema.py +0 -0
  67. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_rate_limit_handling.py +0 -0
  68. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_timestamp.py +0 -0
  69. {agentgovern-2.2.0 → agentgovern-2.3.0}/tests/test_version_consistency.py +0 -0
@@ -32,6 +32,13 @@ test-results/
32
32
  # Not project config — skills/ and agents/ under .claude/ remain tracked.
33
33
  .claude/settings.json
34
34
  .claude/settings.local.json
35
+ # Backups of the above, which carry the same content and the same reason to stay
36
+ # untracked. Added 2026-08-21: the exact-filename rules above do not match a
37
+ # `.bak-*` sibling, so a pre-change backup sat untracked-but-not-ignored inside
38
+ # the repo, where a `git add -A` would have staged it — and, with both `ask`
39
+ # arrays now empty, without prompting.
40
+ .claude/settings.json.bak-*
41
+ .claude/settings.local.json.bak-*
35
42
 
36
43
  .vercel
37
44
 
@@ -1,5 +1,148 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.0 — release date is the `sdk-v2.3.0` tag's (prepared 2026-09-29)
4
+
5
+ ### Security — the Claude Code hook now sees every tool call, but only after you reinstall it (NAGT-187)
6
+
7
+ Until this release, `agentgovern hook install` registered the hook for a fixed
8
+ list of tools (ten in 2.2.1: `Bash`, `Write`, `Edit`, `MultiEdit`,
9
+ `NotebookEdit`, `Read`, `Glob`, `Grep`, `WebFetch`, `WebSearch`). **A tool call
10
+ through any other tool never reached the hook, so no policy was applied to
11
+ it.** On Windows, Claude Code's `PowerShell` tool was one such tool: every
12
+ content rule could be bypassed by routing a command through it.
13
+
14
+ - The `PreToolUse` and `PostToolUse` matchers are now `*`: every tool,
15
+ including tools that do not exist yet.
16
+ - `PowerShell` is classified like `Bash` (`execute` / `code_execution`).
17
+ - A tool the hook has no classification for is recorded as
18
+ `action_type="unmapped_tool"`. Before, it was recorded as `"custom"`, the
19
+ value the SDK reserves for your own `track_action()` calls. **A policy that
20
+ relied on `"custom"` as a catch-all for unknown tools needs
21
+ `"unmapped_tool"` added.**
22
+ - The bundled hook script is version 2.3.0.
23
+
24
+ > **Action required for existing installs.** Upgrading the package does not
25
+ > change a hook that is already installed. Until you reinstall it, the hook
26
+ > keeps its old tool list:
27
+ >
28
+ > ```bash
29
+ > pip install --upgrade agentgovern
30
+ > agentgovern hook install --force # repeat --capture-mode=minimal, --matcher or --python-command if you used them
31
+ > agentgovern hook status # no [DRIFT] lines once it is current
32
+ > ```
33
+ >
34
+ > Then restart any running Claude Code session. `--force` writes the current
35
+ > defaults and does not remember options from the earlier install, so pass
36
+ > them again. Without `--capture-mode=minimal`, it turns a minimal install
37
+ > into a full one.
38
+
39
+ ### Change — `hook install` tells you when an existing hook is stale (NAGT-200)
40
+
41
+ Before, running `agentgovern hook install` against an existing hook said
42
+ *"already installed"*, even when that hook was out of date. It now adds which
43
+ matcher, timeout or script version differs from what this package would
44
+ write, and says to re-run with `--force`. **It still does not upgrade the
45
+ hook itself.** The exit code and every refusal are unchanged, and `--force`
46
+ is still required to write anything.
47
+
48
+ ### Fix — `httpx` and `pydantic` are now required dependencies (NAGT-324)
49
+
50
+ `pip install agentgovern` installed no dependencies at all. The package
51
+ imported cleanly, but 13 of its 23 public names, including `ActionType`,
52
+ `ActionStatus` and `AgentGovernClient`, were bound to `None`. `init()` raised
53
+ a clear `ImportError`, so any documented integration failed loudly at
54
+ startup. But `register_agent()`, `track_action()` and `shutdown()` return
55
+ silently when the SDK is uninitialised, so an integration that skipped or
56
+ swallowed `init()` recorded nothing and reported nothing.
57
+
58
+ This affected every release from 0.2.0 through 2.2.1. LangChain and LangGraph
59
+ users were unaffected in practice: `langchain-core` depends on both `httpx`
60
+ and `pydantic`, so those extras' own dependencies supplied them.
61
+
62
+ - `httpx>=0.27.0` and `pydantic>=2.10.0` are now unconditional dependencies.
63
+ A bare install now also brings in their own dependencies: `anyio`,
64
+ `certifi`, `h11`, `httpcore`, `idna`, `pydantic-core`, `annotated-types`,
65
+ `typing-extensions` and `typing-inspection`.
66
+ - **`python-dotenv` is removed.** No module in the package ever imported it.
67
+ If you relied on `agentgovern[sdk]` to install it, add it directly.
68
+ - The `[sdk]` extra is kept, now empty, so `pip install "agentgovern[sdk]"`,
69
+ `[langchain]` and `[langgraph]` keep working unchanged, with no pip warning.
70
+ - The import guard still catches any `ImportError`, so the hook CLI keeps
71
+ working in a `pip install --no-deps` or half-installed environment. **What
72
+ changed is the message:** `init()` now reports the error that actually
73
+ occurred. Before, it always told you to install the `[sdk]` extra, even
74
+ when that extra was already installed and something else had failed.
75
+ - `init()`'s docstring now says what the fail-silent promise covers
76
+ (AgentGovern being *unreachable* once initialised). It also says what it
77
+ does not cover (the SDK being absent or misconfigured), and why `init()`
78
+ should not be wrapped in a bare `except`.
79
+
80
+ ### Docs
81
+
82
+ - The LangChain samples in this README are updated for langchain 1.x, which
83
+ removed `AgentExecutor` and `create_openai_tools_agent`. They now use
84
+ `langchain.agents.create_agent` and pass the handler as
85
+ `config={"callbacks": [handler]}`. A new test runs such an agent and
86
+ confirms the handler's callbacks fire.
87
+ - New README section: *Upgrading an existing install*.
88
+
89
+ ### Internal (not part of the installed package)
90
+
91
+ - A new release gate: the wheel is built, its declared dependencies are
92
+ checked, and it is installed into a clean environment and imported before
93
+ it is uploaded. This runs on every pull request and in the publish workflow
94
+ itself.
95
+ - `langchain>=1.0.0` added to the `dev` extra, for the new test.
96
+ - Test fixes that make the suite trustworthy rather than change behaviour:
97
+ - NAGT-280: a test that pinned the hook version to the stale literal
98
+ `2.1.0` is removed; the test that checks the snapshot against the
99
+ module's own version stays;
100
+ - NAGT-326: Stop-hook tests no longer assume the temp directory is inside
101
+ `$HOME`;
102
+ - NAGT-327: the buffer-cap test is deterministic, with no live flush thread
103
+ and no network.
104
+
105
+ ## 2.2.1 — 2026-08-13
106
+
107
+ ### Fix — `register_agent()` silently no-op'd when called immediately after `init()`, the SDK's own documented call pattern (STORY-109)
108
+
109
+ `register_agent()` previously scheduled its HTTP call directly against the
110
+ background thread's event loop, gated on that loop already being confirmed
111
+ running (`if self._loop is not None and self._loop.is_running():`). If
112
+ called before the loop was ready — typically `<100ms` after `init()`, and
113
+ this SDK's own README quickstart calls `register_agent()` on the very next
114
+ line after `init()` — the registration was dropped with nothing queued
115
+ anywhere and no error surfaced. No explicit `register_agent()` call had
116
+ ever landed successfully in any installation of this SDK, for any agent,
117
+ in over three months; every registered-looking agent row was actually the
118
+ ingest route's own auto-provisioning fallback.
119
+
120
+ - `register_agent()` now enqueues onto a dedicated buffer instead of
121
+ scheduling directly — the loop-readiness dependency is removed from the
122
+ call entirely. Drained by a new internal flush routine on the same
123
+ cadence actions already use.
124
+ - Deduped by `external_id`: calling `register_agent()` more than once for
125
+ the same agent before a flush collapses to one pending entry with the
126
+ latest values, matching the server's own always-overwrite upsert
127
+ behavior, rather than queuing one call per invocation.
128
+ - A failed registration now prints a `stderr` warning after repeated
129
+ consecutive failures, matching the existing warning behavior for action
130
+ ingest failures — never raises into the caller, but no longer silent
131
+ either.
132
+ - `status()` gained two new informational fields:
133
+ `reg_buffer_size`, `reg_consecutive_failures`.
134
+ - Internal null-strip added to the registration payload before
135
+ serialization, generalizing the same fix already applied to action
136
+ ingest (`_post_batch`) in `0.1.0b4` — Pydantic's `model_dump_json()`
137
+ sends unset optional fields as explicit JSON `null`; the server's schema
138
+ treats that as a distinct value from an absent key.
139
+
140
+ No public API changes — `register_agent()`'s signature is unchanged, and
141
+ every internal addition is private. Patch release: this restores
142
+ previously-documented behavior rather than adding new capability.
143
+
144
+ Full design and build record: `docs/audit-reports/story-109-register-agent-startup-race-2026-08-13.md`.
145
+
3
146
  ## 2.2.0 — 2026-08-11
4
147
 
5
148
  ### Fix — capture-only hook events (UserPromptSubmit/PostToolUse/Stop) no longer block on a discarded synchronous verdict; async audit-event parity (NAGT-137)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentgovern
3
- Version: 2.2.0
3
+ Version: 2.3.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
@@ -20,40 +20,25 @@ Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Topic :: System :: Monitoring
22
22
  Requires-Python: >=3.9
23
+ Requires-Dist: httpx>=0.27.0
24
+ Requires-Dist: pydantic>=2.10.0
23
25
  Provides-Extra: crewai
24
26
  Requires-Dist: crewai>=0.80.0; extra == 'crewai'
25
- Requires-Dist: httpx>=0.27.0; extra == 'crewai'
26
- Requires-Dist: pydantic>=2.10.0; extra == 'crewai'
27
- Requires-Dist: python-dotenv>=1.0.0; extra == 'crewai'
28
27
  Provides-Extra: dev
29
- Requires-Dist: httpx>=0.27.0; extra == 'dev'
30
- Requires-Dist: pydantic>=2.10.0; extra == 'dev'
28
+ Requires-Dist: langchain>=1.0.0; extra == 'dev'
31
29
  Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
32
30
  Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
33
31
  Requires-Dist: pytest>=8.3.0; extra == 'dev'
34
- Requires-Dist: python-dotenv>=1.0.0; extra == 'dev'
35
32
  Requires-Dist: respx>=0.21.0; extra == 'dev'
36
33
  Requires-Dist: ruff>=0.8.0; extra == 'dev'
37
34
  Provides-Extra: langchain
38
- Requires-Dist: httpx>=0.27.0; extra == 'langchain'
39
35
  Requires-Dist: langchain-core>=0.3.0; extra == 'langchain'
40
- Requires-Dist: pydantic>=2.10.0; extra == 'langchain'
41
- Requires-Dist: python-dotenv>=1.0.0; extra == 'langchain'
42
36
  Provides-Extra: langgraph
43
- Requires-Dist: httpx>=0.27.0; extra == 'langgraph'
44
37
  Requires-Dist: langchain-core>=0.3.0; extra == 'langgraph'
45
38
  Requires-Dist: langgraph<2.0.0,>=0.2.0; extra == 'langgraph'
46
- Requires-Dist: pydantic>=2.10.0; extra == 'langgraph'
47
- Requires-Dist: python-dotenv>=1.0.0; extra == 'langgraph'
48
39
  Provides-Extra: openai
49
- Requires-Dist: httpx>=0.27.0; extra == 'openai'
50
40
  Requires-Dist: openai>=1.50.0; extra == 'openai'
51
- Requires-Dist: pydantic>=2.10.0; extra == 'openai'
52
- Requires-Dist: python-dotenv>=1.0.0; extra == 'openai'
53
41
  Provides-Extra: sdk
54
- Requires-Dist: httpx>=0.27.0; extra == 'sdk'
55
- Requires-Dist: pydantic>=2.10.0; extra == 'sdk'
56
- Requires-Dist: python-dotenv>=1.0.0; extra == 'sdk'
57
42
  Description-Content-Type: text/markdown
58
43
 
59
44
  # AgentGovern Python SDK
@@ -74,7 +59,7 @@ pip install "agentgovern[langchain]"
74
59
 
75
60
  ```python
76
61
  import agentgovern
77
- from langchain.agents import AgentExecutor, create_openai_tools_agent
62
+ from langchain.agents import create_agent
78
63
  from langchain_openai import ChatOpenAI
79
64
 
80
65
  # 1. Initialize once at startup
@@ -94,12 +79,13 @@ agentgovern.register_agent(
94
79
  # 3. Get the callback handler — binds to credit-scoring-v2 automatically
95
80
  handler = agentgovern.instrument_langchain()
96
81
 
97
- # 4. Pass it to your AgentExecutor — no other changes needed
98
- llm = ChatOpenAI(model="gpt-4o")
99
- agent = create_openai_tools_agent(llm, tools, prompt)
100
- executor = AgentExecutor(agent=agent, tools=tools, callbacks=[handler])
82
+ # 4. Build the agent and pass the handler via config — no other changes needed
83
+ agent = create_agent(model=ChatOpenAI(model="gpt-4o"), tools=tools)
101
84
 
102
- result = executor.invoke({"input": "Evaluate loan application for customer #12345"})
85
+ result = agent.invoke(
86
+ {"messages": [{"role": "user", "content": "Evaluate loan application for customer #12345"}]},
87
+ config={"callbacks": [handler]},
88
+ )
103
89
  ```
104
90
 
105
91
  `instrument_langchain()` binds to the most recently registered agent. Every tool call,
@@ -154,8 +140,11 @@ agentgovern.register_agent("kyc-agent", name="KYC Agent")
154
140
  handler_fraud = agentgovern.instrument_langchain("fraud-detector")
155
141
  handler_kyc = agentgovern.instrument_langchain("kyc-agent")
156
142
 
157
- fraud_executor = AgentExecutor(agent=..., tools=..., callbacks=[handler_fraud])
158
- kyc_executor = AgentExecutor(agent=..., tools=..., callbacks=[handler_kyc])
143
+ fraud_agent = create_agent(model=..., tools=...)
144
+ kyc_agent = create_agent(model=..., tools=...)
145
+
146
+ fraud_agent.invoke(inputs, config={"callbacks": [handler_fraud]})
147
+ kyc_agent.invoke(inputs, config={"callbacks": [handler_kyc]})
159
148
  ```
160
149
 
161
150
  ## Manual instrumentation (all frameworks)
@@ -206,8 +195,10 @@ Enable policy packs from the [AgentGovern dashboard](https://agentgovern.zirahn.
206
195
  ## Claude Code Hook
207
196
 
208
197
  The SDK ships a built-in CLI for installing the AgentGovern hook into Claude Code. The hook
209
- intercepts `PreToolUse` events and enforces your tenant policies in real time — before any
210
- write-class tool (`Bash`, `Write`, `Edit`, `MultiEdit`, `NotebookEdit`) executes.
198
+ intercepts `PreToolUse` events for every tool call and enforces your tenant policies in real
199
+ time — before the tool executes. A tool AgentGovern doesn't yet have a specific classification
200
+ for is still captured and evaluated, tagged `action_type=unmapped_tool` rather than silently
201
+ skipped.
211
202
 
212
203
  ### Install
213
204
 
@@ -219,6 +210,29 @@ This deploys the hook script to `~/.agentgovern/hook.py`, merges the correct ent
219
210
  `~/.claude/settings.json` (with an atomic backup), and prints instructions for any missing
220
211
  env vars.
221
212
 
213
+ ### Upgrading an existing install
214
+
215
+ Upgrading the package does **not** update a hook that is already installed. `hook install`
216
+ copies the hook script to `~/.agentgovern/hook.py` and writes its entries into
217
+ `~/.claude/settings.json`, and a plain `agentgovern hook install` against an existing install
218
+ reports it as already installed and changes nothing. After upgrading, reinstall with `--force`:
219
+
220
+ ```bash
221
+ pip install --upgrade agentgovern # or the extra you installed with, e.g. "agentgovern[langchain]"
222
+ agentgovern hook install --force
223
+ agentgovern hook status # reports no [DRIFT] lines once the hook is current
224
+ ```
225
+
226
+ `--force` writes the current defaults. It does not remember options from the earlier install: if
227
+ you installed with `--capture-mode=minimal`, `--matcher` or `--python-command`, pass them again —
228
+ `--force` without `--capture-mode=minimal` turns a minimal install into a full one.
229
+ Your environment variables are not touched. Restart any running Claude Code session afterwards.
230
+
231
+ **If your hook was installed by 2.2.1 or earlier, this step matters for coverage, not just
232
+ freshness.** Those versions wrote a `PreToolUse` matcher naming a fixed list of tools (ten, in 2.2.1), so a call
233
+ through any other tool (`PowerShell`, for example) never reached the hook. Starting with 2.3.0
234
+ the matcher is `*`, every tool — but only once `hook install --force` has rewritten the entry.
235
+
222
236
  ### Required env vars
223
237
 
224
238
  ```bash
@@ -16,7 +16,7 @@ pip install "agentgovern[langchain]"
16
16
 
17
17
  ```python
18
18
  import agentgovern
19
- from langchain.agents import AgentExecutor, create_openai_tools_agent
19
+ from langchain.agents import create_agent
20
20
  from langchain_openai import ChatOpenAI
21
21
 
22
22
  # 1. Initialize once at startup
@@ -36,12 +36,13 @@ agentgovern.register_agent(
36
36
  # 3. Get the callback handler — binds to credit-scoring-v2 automatically
37
37
  handler = agentgovern.instrument_langchain()
38
38
 
39
- # 4. Pass it to your AgentExecutor — no other changes needed
40
- llm = ChatOpenAI(model="gpt-4o")
41
- agent = create_openai_tools_agent(llm, tools, prompt)
42
- executor = AgentExecutor(agent=agent, tools=tools, callbacks=[handler])
39
+ # 4. Build the agent and pass the handler via config — no other changes needed
40
+ agent = create_agent(model=ChatOpenAI(model="gpt-4o"), tools=tools)
43
41
 
44
- result = executor.invoke({"input": "Evaluate loan application for customer #12345"})
42
+ result = agent.invoke(
43
+ {"messages": [{"role": "user", "content": "Evaluate loan application for customer #12345"}]},
44
+ config={"callbacks": [handler]},
45
+ )
45
46
  ```
46
47
 
47
48
  `instrument_langchain()` binds to the most recently registered agent. Every tool call,
@@ -96,8 +97,11 @@ agentgovern.register_agent("kyc-agent", name="KYC Agent")
96
97
  handler_fraud = agentgovern.instrument_langchain("fraud-detector")
97
98
  handler_kyc = agentgovern.instrument_langchain("kyc-agent")
98
99
 
99
- fraud_executor = AgentExecutor(agent=..., tools=..., callbacks=[handler_fraud])
100
- kyc_executor = AgentExecutor(agent=..., tools=..., callbacks=[handler_kyc])
100
+ fraud_agent = create_agent(model=..., tools=...)
101
+ kyc_agent = create_agent(model=..., tools=...)
102
+
103
+ fraud_agent.invoke(inputs, config={"callbacks": [handler_fraud]})
104
+ kyc_agent.invoke(inputs, config={"callbacks": [handler_kyc]})
101
105
  ```
102
106
 
103
107
  ## Manual instrumentation (all frameworks)
@@ -148,8 +152,10 @@ Enable policy packs from the [AgentGovern dashboard](https://agentgovern.zirahn.
148
152
  ## Claude Code Hook
149
153
 
150
154
  The SDK ships a built-in CLI for installing the AgentGovern hook into Claude Code. The hook
151
- intercepts `PreToolUse` events and enforces your tenant policies in real time — before any
152
- write-class tool (`Bash`, `Write`, `Edit`, `MultiEdit`, `NotebookEdit`) executes.
155
+ intercepts `PreToolUse` events for every tool call and enforces your tenant policies in real
156
+ time — before the tool executes. A tool AgentGovern doesn't yet have a specific classification
157
+ for is still captured and evaluated, tagged `action_type=unmapped_tool` rather than silently
158
+ skipped.
153
159
 
154
160
  ### Install
155
161
 
@@ -161,6 +167,29 @@ This deploys the hook script to `~/.agentgovern/hook.py`, merges the correct ent
161
167
  `~/.claude/settings.json` (with an atomic backup), and prints instructions for any missing
162
168
  env vars.
163
169
 
170
+ ### Upgrading an existing install
171
+
172
+ Upgrading the package does **not** update a hook that is already installed. `hook install`
173
+ copies the hook script to `~/.agentgovern/hook.py` and writes its entries into
174
+ `~/.claude/settings.json`, and a plain `agentgovern hook install` against an existing install
175
+ reports it as already installed and changes nothing. After upgrading, reinstall with `--force`:
176
+
177
+ ```bash
178
+ pip install --upgrade agentgovern # or the extra you installed with, e.g. "agentgovern[langchain]"
179
+ agentgovern hook install --force
180
+ agentgovern hook status # reports no [DRIFT] lines once the hook is current
181
+ ```
182
+
183
+ `--force` writes the current defaults. It does not remember options from the earlier install: if
184
+ you installed with `--capture-mode=minimal`, `--matcher` or `--python-command`, pass them again —
185
+ `--force` without `--capture-mode=minimal` turns a minimal install into a full one.
186
+ Your environment variables are not touched. Restart any running Claude Code session afterwards.
187
+
188
+ **If your hook was installed by 2.2.1 or earlier, this step matters for coverage, not just
189
+ freshness.** Those versions wrote a `PreToolUse` matcher naming a fixed list of tools (ten, in 2.2.1), so a call
190
+ through any other tool (`PowerShell`, for example) never reached the hook. Starting with 2.3.0
191
+ the matcher is `*`, every tool — but only once `hook install --force` has rewritten the entry.
192
+
164
193
  ### Required env vars
165
194
 
166
195
  ```bash
@@ -11,15 +11,17 @@ Before the first automated publish, configure trusted publishing on PyPI:
11
11
 
12
12
  | Field | Value |
13
13
  |-------------------|------------------------------|
14
- | Owner | ahmedkhan-zirahn |
14
+ | Owner | ZirahnAI |
15
15
  | Repository name | agentgovern |
16
16
  | Workflow filename | publish-sdk.yml |
17
17
  | Environment name | pypi |
18
18
 
19
+ **Note:** the repository moved from `ahmedkhan-zirahn` to `ZirahnAI` in September 2026. A publisher for the old owner existed and was **deleted on 2026-09-14**. A trusted publisher that names a path the organisation no longer controls is a standing binding to a namespace someone else could later claim. See NAGT-314 in `docs/ROADMAP.md`.
20
+
19
21
  3. Save. No API token needed after this.
20
22
 
21
23
  Also create the `pypi` environment in GitHub:
22
- - Go to https://github.com/ahmedkhan-zirahn/agentgovern/settings/environments
24
+ - Go to https://github.com/ZirahnAI/agentgovern/settings/environments
23
25
  - Create environment named `pypi` (no protection rules required)
24
26
 
25
27
  ## Release steps
@@ -53,7 +55,7 @@ Also create the `pypi` environment in GitHub:
53
55
 
54
56
  If you need to publish without tagging (e.g., to re-publish after a failed run), trigger the workflow manually:
55
57
 
56
- 1. Go to https://github.com/ahmedkhan-zirahn/agentgovern/actions/workflows/publish-sdk.yml
58
+ 1. Go to https://github.com/ZirahnAI/agentgovern/actions/workflows/publish-sdk.yml
57
59
  2. Click **Run workflow** → select branch `main` → **Run workflow**
58
60
 
59
61
  The manual run builds and publishes whatever version is currently in `packages/sdk-python/pyproject.toml`. Make sure the version in `pyproject.toml` and `_version.py` match before triggering.
@@ -29,9 +29,23 @@ from typing import Any
29
29
 
30
30
  from agentgovern._version import __version__
31
31
 
32
- # SDK deps (httpx, pydantic) are optional — required only for agent SDK usage.
33
- # The hook CLI (agentgovern hook install/...) works with a bare install.
34
- # Install with: pip install agentgovern[sdk]
32
+ # httpx and pydantic are REQUIRED dependencies (NAGT-324). A normal
33
+ # `pip install agentgovern` always has them, so this guard is not the
34
+ # optional-extra switch it used to be.
35
+ #
36
+ # It still exists for one case: an install that deliberately suppressed
37
+ # dependency resolution (`pip install --no-deps`), or one that is broken or
38
+ # half-finished. In those, the Claude Code hook CLI -- which is stdlib-only
39
+ # all the way down -- must keep working, and it cannot, because importing
40
+ # `agentgovern.__main__` imports THIS module first.
41
+ #
42
+ # The catch stays BROAD on purpose. Narrowing it to `_exc.name in ("httpx",
43
+ # "pydantic")` was tried and withdrawn: ImportError.name is the INNERMOST
44
+ # missing module, so a broken `pydantic_core` (a compiled wheel, and one of
45
+ # pydantic's most common real failures) reports 'pydantic_core', gets
46
+ # re-raised, and takes the hook CLI down in exactly the case this guard is
47
+ # for. What was actually wrong was the DIAGNOSTIC, not the breadth -- so the
48
+ # exception is recorded and `_require_sdk()` reports what really happened.
35
49
  try:
36
50
  from agentgovern.client import AgentGovernClient
37
51
  from agentgovern.exceptions import (
@@ -51,8 +65,10 @@ try:
51
65
  )
52
66
 
53
67
  _SDK_AVAILABLE = True
54
- except ImportError:
68
+ _SDK_IMPORT_ERROR: ImportError | None = None
69
+ except ImportError as _exc:
55
70
  _SDK_AVAILABLE = False
71
+ _SDK_IMPORT_ERROR = _exc
56
72
  # Provide placeholder names so `from agentgovern import X` fails at call
57
73
  # time with a clear message rather than at import time.
58
74
  AgentGovernClient = None # type: ignore[assignment,misc]
@@ -71,12 +87,29 @@ except ImportError:
71
87
 
72
88
 
73
89
  def _require_sdk() -> None:
90
+ """Raise if the SDK half of this package could not be imported.
91
+
92
+ The message is derived from the exception actually raised, never assumed.
93
+ Before NAGT-324 this always said "install agentgovern[sdk]", which was
94
+ wrong whenever the real cause was anything else -- it told people to
95
+ install a dependency that was, in those cases, already installed.
96
+ """
74
97
  if not _SDK_AVAILABLE:
98
+ _missing = getattr(_SDK_IMPORT_ERROR, "name", None)
99
+ if _missing in ("httpx", "pydantic"):
100
+ raise ImportError(
101
+ f"The AgentGovern SDK requires {_missing}, which is not "
102
+ f"importable. It is a declared dependency, so this usually "
103
+ f"means the install skipped dependency resolution "
104
+ f"(pip install --no-deps) or is incomplete. Reinstall with: "
105
+ f"pip install --force-reinstall agentgovern"
106
+ ) from _SDK_IMPORT_ERROR
75
107
  raise ImportError(
76
- "The AgentGovern SDK requires additional dependencies. "
77
- "Install them with: pip install agentgovern[sdk]\n"
78
- "The hook CLI (agentgovern hook install) works without this extra."
79
- )
108
+ f"The AgentGovern SDK failed to load: {_SDK_IMPORT_ERROR!r}. "
109
+ f"This is NOT a missing optional extra -- the installation is "
110
+ f"broken or incomplete. Reinstall with: "
111
+ f"pip install --force-reinstall agentgovern"
112
+ ) from _SDK_IMPORT_ERROR
80
113
 
81
114
 
82
115
  _client: AgentGovernClient | None = None
@@ -100,8 +133,17 @@ def init(
100
133
  Initialize the AgentGovern SDK.
101
134
 
102
135
  Must be called once at application startup before any other SDK calls.
103
- The SDK is designed to never break the customer's agent — if AgentGovern
104
- is unreachable, all errors are silently discarded.
136
+
137
+ The fail-silent contract covers AgentGovern being UNREACHABLE: once the
138
+ SDK is initialised, network errors, timeouts and policy-engine failures
139
+ are discarded rather than raised into your agent.
140
+
141
+ It does NOT cover AgentGovern being absent or misconfigured. This function
142
+ raises if the SDK's dependencies cannot be imported, and it is the only
143
+ place that failure is reported — do not wrap this call in a bare
144
+ ``except``. If it raises, the SDK is not installed correctly and no action
145
+ will be recorded; ``status()`` returns ``{"initialized": False}`` for as
146
+ long as that is true.
105
147
 
106
148
  Args:
107
149
  api_key: SDK ingest key from the dashboard.
@@ -110,7 +152,8 @@ def init(
110
152
  policy_engine_url: Render policy engine URL (Gate 1 evaluate/input).
111
153
  Defaults to ``AGENTGOVERN_POLICY_ENGINE_URL`` env var, then
112
154
  ``https://agentgovern.onrender.com``.
113
- fail_silently: If ``True``, SDK errors never raise into your agent.
155
+ fail_silently: If ``True``, SDK errors never raise into your agent,
156
+ after initialisation. It does not affect this function.
114
157
  """
115
158
  _require_sdk()
116
159
  global _client # noqa: PLW0603
@@ -0,0 +1 @@
1
+ __version__ = "2.3.0"