agentgovern 2.2.1__tar.gz → 2.4.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 (70) hide show
  1. {agentgovern-2.2.1 → agentgovern-2.4.0}/.gitignore +7 -0
  2. {agentgovern-2.2.1 → agentgovern-2.4.0}/CHANGELOG.md +128 -0
  3. {agentgovern-2.2.1 → agentgovern-2.4.0}/PKG-INFO +50 -29
  4. {agentgovern-2.2.1 → agentgovern-2.4.0}/README.md +46 -10
  5. agentgovern-2.4.0/RELEASING.md +72 -0
  6. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/__init__.py +55 -12
  7. agentgovern-2.4.0/agentgovern/_version.py +1 -0
  8. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/client.py +8 -1
  9. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/hook/data/hook.py +30 -5
  10. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/hook/drift.py +22 -0
  11. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/hook/installer.py +124 -43
  12. {agentgovern-2.2.1 → agentgovern-2.4.0}/pyproject.toml +15 -6
  13. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_hook_v2_context_snapshot.py +22 -4
  14. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_hook_v2_payloads.py +77 -20
  15. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_installer.py +151 -45
  16. agentgovern-2.4.0/tests/hook/test_matcher_coverage.py +115 -0
  17. agentgovern-2.4.0/tests/hook/test_tool_map_classification.py +68 -0
  18. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_client.py +40 -4
  19. agentgovern-2.4.0/tests/test_import_guard.py +222 -0
  20. agentgovern-2.4.0/tests/test_langchain_1x_agent.py +166 -0
  21. agentgovern-2.4.0/tests/test_policy_engine_url.py +79 -0
  22. agentgovern-2.2.1/RELEASING.md +0 -63
  23. agentgovern-2.2.1/agentgovern/_version.py +0 -1
  24. {agentgovern-2.2.1 → agentgovern-2.4.0}/LICENSE +0 -0
  25. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/__main__.py +0 -0
  26. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/exceptions.py +0 -0
  27. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/gate2.py +0 -0
  28. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/hook/__init__.py +0 -0
  29. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/hook/cli.py +0 -0
  30. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/hook/data/__init__.py +0 -0
  31. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/hook/settings.py +0 -0
  32. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/instrumentors/__init__.py +0 -0
  33. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/instrumentors/_base.py +0 -0
  34. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/instrumentors/langchain.py +0 -0
  35. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/instrumentors/langgraph.py +0 -0
  36. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentgovern/types.py +0 -0
  37. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentshield/__init__.py +0 -0
  38. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentshield/client.py +0 -0
  39. {agentgovern-2.2.1 → agentgovern-2.4.0}/agentshield/types.py +0 -0
  40. {agentgovern-2.2.1 → agentgovern-2.4.0}/examples/langgraph_credit_decision_agent.py +0 -0
  41. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/__init__.py +0 -0
  42. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/__init__.py +0 -0
  43. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/conftest.py +0 -0
  44. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_bundled_hook.py +0 -0
  45. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_drift.py +0 -0
  46. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_hook_v2_dispatch.py +0 -0
  47. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_hook_v2_outbox_queue.py +0 -0
  48. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_hook_v2_payload_extraction.py +0 -0
  49. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_hook_v2_remediation_message.py +0 -0
  50. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_hook_v2_timing_log.py +0 -0
  51. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_hook_v2_transcript_reading.py +0 -0
  52. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_hook_v2_turn_index.py +0 -0
  53. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_hook_v2_warn_verdict.py +0 -0
  54. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/hook/test_settings.py +0 -0
  55. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_base_instrumentor.py +0 -0
  56. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_evaluate_input.py +0 -0
  57. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_gate2_full_path.py +0 -0
  58. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_gate2_latency.py +0 -0
  59. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_gate2_verdict_handler.py +0 -0
  60. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_instrument_langchain.py +0 -0
  61. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_langchain.py +0 -0
  62. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_langchain_gate1.py +0 -0
  63. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_langchain_gate2_callbacks.py +0 -0
  64. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_langgraph.py +0 -0
  65. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_output_capture_contract.py +0 -0
  66. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_payload_schema.py +0 -0
  67. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_rate_limit_handling.py +0 -0
  68. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_register_contract.py +0 -0
  69. {agentgovern-2.2.1 → agentgovern-2.4.0}/tests/test_timestamp.py +0 -0
  70. {agentgovern-2.2.1 → agentgovern-2.4.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,133 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.4.0 — release date is the `sdk-v2.4.0` tag's (prepared 2026-09-30)
4
+
5
+ ### Change — the SDK reaches the policy engine at a stable name, `https://engine.zirahn.com`
6
+
7
+ `evaluate_input()` (Gate 1) and Gate 2's output checks call the policy engine
8
+ directly. **Versions 2.3.0 and earlier call it at
9
+ `https://agentgovern.onrender.com`, a hostname Render issued for one specific
10
+ service.** Render cannot move a service to another region: a new service
11
+ means a new hostname. So an SDK pinned to that name cannot follow the engine
12
+ if it moves.
13
+
14
+ - The default `policy_engine_url` is now **`https://engine.zirahn.com`**. It
15
+ is a custom domain pointing at the same engine today, so **latency and
16
+ behaviour are unchanged**. What changes is that a future move of the engine
17
+ needs no SDK release.
18
+ - **2.3.0 and earlier keep working.** `agentgovern.onrender.com` still
19
+ serves the same engine, and it will not be removed while any supported
20
+ version defaults to it. Upgrading moves you to the stable name:
21
+ `pip install --upgrade agentgovern`.
22
+ - **Overrides are unchanged.** The `policy_engine_url` argument to `init()`,
23
+ then the `AGENTGOVERN_POLICY_ENGINE_URL` environment variable, still take
24
+ precedence over the default. **If you set either yourself, you are not
25
+ affected.**
26
+ - The Claude Code hook is unaffected. It reaches the engine through the
27
+ dashboard's API, not directly.
28
+
29
+ ## 2.3.0 — release date is the `sdk-v2.3.0` tag's (prepared 2026-09-29)
30
+
31
+ ### Security — the Claude Code hook now sees every tool call, but only after you reinstall it (NAGT-187)
32
+
33
+ Until this release, `agentgovern hook install` registered the hook for a fixed
34
+ list of tools (ten in 2.2.1: `Bash`, `Write`, `Edit`, `MultiEdit`,
35
+ `NotebookEdit`, `Read`, `Glob`, `Grep`, `WebFetch`, `WebSearch`). **A tool call
36
+ through any other tool never reached the hook, so no policy was applied to
37
+ it.** On Windows, Claude Code's `PowerShell` tool was one such tool: every
38
+ content rule could be bypassed by routing a command through it.
39
+
40
+ - The `PreToolUse` and `PostToolUse` matchers are now `*`: every tool,
41
+ including tools that do not exist yet.
42
+ - `PowerShell` is classified like `Bash` (`execute` / `code_execution`).
43
+ - A tool the hook has no classification for is recorded as
44
+ `action_type="unmapped_tool"`. Before, it was recorded as `"custom"`, the
45
+ value the SDK reserves for your own `track_action()` calls. **A policy that
46
+ relied on `"custom"` as a catch-all for unknown tools needs
47
+ `"unmapped_tool"` added.**
48
+ - The bundled hook script is version 2.3.0.
49
+
50
+ > **Action required for existing installs.** Upgrading the package does not
51
+ > change a hook that is already installed. Until you reinstall it, the hook
52
+ > keeps its old tool list:
53
+ >
54
+ > ```bash
55
+ > pip install --upgrade agentgovern
56
+ > agentgovern hook install --force # repeat --capture-mode=minimal, --matcher or --python-command if you used them
57
+ > agentgovern hook status # no [DRIFT] lines once it is current
58
+ > ```
59
+ >
60
+ > Then restart any running Claude Code session. `--force` writes the current
61
+ > defaults and does not remember options from the earlier install, so pass
62
+ > them again. Without `--capture-mode=minimal`, it turns a minimal install
63
+ > into a full one.
64
+
65
+ ### Change — `hook install` tells you when an existing hook is stale (NAGT-200)
66
+
67
+ Before, running `agentgovern hook install` against an existing hook said
68
+ *"already installed"*, even when that hook was out of date. It now adds which
69
+ matcher, timeout or script version differs from what this package would
70
+ write, and says to re-run with `--force`. **It still does not upgrade the
71
+ hook itself.** The exit code and every refusal are unchanged, and `--force`
72
+ is still required to write anything.
73
+
74
+ ### Fix — `httpx` and `pydantic` are now required dependencies (NAGT-324)
75
+
76
+ `pip install agentgovern` installed no dependencies at all. The package
77
+ imported cleanly, but 13 of its 23 public names, including `ActionType`,
78
+ `ActionStatus` and `AgentGovernClient`, were bound to `None`. `init()` raised
79
+ a clear `ImportError`, so any documented integration failed loudly at
80
+ startup. But `register_agent()`, `track_action()` and `shutdown()` return
81
+ silently when the SDK is uninitialised, so an integration that skipped or
82
+ swallowed `init()` recorded nothing and reported nothing.
83
+
84
+ This affected every release from 0.2.0 through 2.2.1. LangChain and LangGraph
85
+ users were unaffected in practice: `langchain-core` depends on both `httpx`
86
+ and `pydantic`, so those extras' own dependencies supplied them.
87
+
88
+ - `httpx>=0.27.0` and `pydantic>=2.10.0` are now unconditional dependencies.
89
+ A bare install now also brings in their own dependencies: `anyio`,
90
+ `certifi`, `h11`, `httpcore`, `idna`, `pydantic-core`, `annotated-types`,
91
+ `typing-extensions` and `typing-inspection`.
92
+ - **`python-dotenv` is removed.** No module in the package ever imported it.
93
+ If you relied on `agentgovern[sdk]` to install it, add it directly.
94
+ - The `[sdk]` extra is kept, now empty, so `pip install "agentgovern[sdk]"`,
95
+ `[langchain]` and `[langgraph]` keep working unchanged, with no pip warning.
96
+ - The import guard still catches any `ImportError`, so the hook CLI keeps
97
+ working in a `pip install --no-deps` or half-installed environment. **What
98
+ changed is the message:** `init()` now reports the error that actually
99
+ occurred. Before, it always told you to install the `[sdk]` extra, even
100
+ when that extra was already installed and something else had failed.
101
+ - `init()`'s docstring now says what the fail-silent promise covers
102
+ (AgentGovern being *unreachable* once initialised). It also says what it
103
+ does not cover (the SDK being absent or misconfigured), and why `init()`
104
+ should not be wrapped in a bare `except`.
105
+
106
+ ### Docs
107
+
108
+ - The LangChain samples in this README are updated for langchain 1.x, which
109
+ removed `AgentExecutor` and `create_openai_tools_agent`. They now use
110
+ `langchain.agents.create_agent` and pass the handler as
111
+ `config={"callbacks": [handler]}`. A new test runs such an agent and
112
+ confirms the handler's callbacks fire.
113
+ - New README section: *Upgrading an existing install*.
114
+
115
+ ### Internal (not part of the installed package)
116
+
117
+ - A new release gate: the wheel is built, its declared dependencies are
118
+ checked, and it is installed into a clean environment and imported before
119
+ it is uploaded. This runs on every pull request and in the publish workflow
120
+ itself.
121
+ - `langchain>=1.0.0` added to the `dev` extra, for the new test.
122
+ - Test fixes that make the suite trustworthy rather than change behaviour:
123
+ - NAGT-280: a test that pinned the hook version to the stale literal
124
+ `2.1.0` is removed; the test that checks the snapshot against the
125
+ module's own version stays;
126
+ - NAGT-326: Stop-hook tests no longer assume the temp directory is inside
127
+ `$HOME`;
128
+ - NAGT-327: the buffer-cap test is deterministic, with no live flush thread
129
+ and no network.
130
+
3
131
  ## 2.2.1 — 2026-08-13
4
132
 
5
133
  ### Fix — `register_agent()` silently no-op'd when called immediately after `init()`, the SDK's own documented call pattern (STORY-109)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentgovern
3
- Version: 2.2.1
3
+ Version: 2.4.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)
@@ -201,13 +190,22 @@ Enable policy packs from the [AgentGovern dashboard](https://agentgovern.zirahn.
201
190
  | `api_key` | required | SDK ingest key from the dashboard |
202
191
  | `base_url` | `https://agentgovern.zirahn.com` | API endpoint |
203
192
  | `environment` | `"production"` | `"production"` \| `"staging"` \| `"development"` |
193
+ | `policy_engine_url` | `https://engine.zirahn.com` | Policy engine endpoint for Gate 1 (`evaluate_input`) and Gate 2 checks. Override with this argument or the `AGENTGOVERN_POLICY_ENGINE_URL` env var |
204
194
  | `fail_silently` | `True` | If `True`, SDK errors never raise into your agent |
205
195
 
196
+ **Policy engine address (2.4.0).** The default engine address is now the stable name
197
+ `https://engine.zirahn.com`. Versions 2.3.0 and earlier call `https://agentgovern.onrender.com`
198
+ directly. That address keeps working, so nothing breaks if you stay on an older version, but only
199
+ the stable name can follow the engine if it moves. Upgrade with `pip install --upgrade agentgovern`.
200
+ If you set `policy_engine_url` or `AGENTGOVERN_POLICY_ENGINE_URL` yourself, it still takes precedence.
201
+
206
202
  ## Claude Code Hook
207
203
 
208
204
  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.
205
+ intercepts `PreToolUse` events for every tool call and enforces your tenant policies in real
206
+ time — before the tool executes. A tool AgentGovern doesn't yet have a specific classification
207
+ for is still captured and evaluated, tagged `action_type=unmapped_tool` rather than silently
208
+ skipped.
211
209
 
212
210
  ### Install
213
211
 
@@ -219,6 +217,29 @@ This deploys the hook script to `~/.agentgovern/hook.py`, merges the correct ent
219
217
  `~/.claude/settings.json` (with an atomic backup), and prints instructions for any missing
220
218
  env vars.
221
219
 
220
+ ### Upgrading an existing install
221
+
222
+ Upgrading the package does **not** update a hook that is already installed. `hook install`
223
+ copies the hook script to `~/.agentgovern/hook.py` and writes its entries into
224
+ `~/.claude/settings.json`, and a plain `agentgovern hook install` against an existing install
225
+ reports it as already installed and changes nothing. After upgrading, reinstall with `--force`:
226
+
227
+ ```bash
228
+ pip install --upgrade agentgovern # or the extra you installed with, e.g. "agentgovern[langchain]"
229
+ agentgovern hook install --force
230
+ agentgovern hook status # reports no [DRIFT] lines once the hook is current
231
+ ```
232
+
233
+ `--force` writes the current defaults. It does not remember options from the earlier install: if
234
+ you installed with `--capture-mode=minimal`, `--matcher` or `--python-command`, pass them again —
235
+ `--force` without `--capture-mode=minimal` turns a minimal install into a full one.
236
+ Your environment variables are not touched. Restart any running Claude Code session afterwards.
237
+
238
+ **If your hook was installed by 2.2.1 or earlier, this step matters for coverage, not just
239
+ freshness.** Those versions wrote a `PreToolUse` matcher naming a fixed list of tools (ten, in 2.2.1), so a call
240
+ through any other tool (`PowerShell`, for example) never reached the hook. Starting with 2.3.0
241
+ the matcher is `*`, every tool — but only once `hook install --force` has rewritten the entry.
242
+
222
243
  ### Required env vars
223
244
 
224
245
  ```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)
@@ -143,13 +147,22 @@ Enable policy packs from the [AgentGovern dashboard](https://agentgovern.zirahn.
143
147
  | `api_key` | required | SDK ingest key from the dashboard |
144
148
  | `base_url` | `https://agentgovern.zirahn.com` | API endpoint |
145
149
  | `environment` | `"production"` | `"production"` \| `"staging"` \| `"development"` |
150
+ | `policy_engine_url` | `https://engine.zirahn.com` | Policy engine endpoint for Gate 1 (`evaluate_input`) and Gate 2 checks. Override with this argument or the `AGENTGOVERN_POLICY_ENGINE_URL` env var |
146
151
  | `fail_silently` | `True` | If `True`, SDK errors never raise into your agent |
147
152
 
153
+ **Policy engine address (2.4.0).** The default engine address is now the stable name
154
+ `https://engine.zirahn.com`. Versions 2.3.0 and earlier call `https://agentgovern.onrender.com`
155
+ directly. That address keeps working, so nothing breaks if you stay on an older version, but only
156
+ the stable name can follow the engine if it moves. Upgrade with `pip install --upgrade agentgovern`.
157
+ If you set `policy_engine_url` or `AGENTGOVERN_POLICY_ENGINE_URL` yourself, it still takes precedence.
158
+
148
159
  ## Claude Code Hook
149
160
 
150
161
  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.
162
+ intercepts `PreToolUse` events for every tool call and enforces your tenant policies in real
163
+ time — before the tool executes. A tool AgentGovern doesn't yet have a specific classification
164
+ for is still captured and evaluated, tagged `action_type=unmapped_tool` rather than silently
165
+ skipped.
153
166
 
154
167
  ### Install
155
168
 
@@ -161,6 +174,29 @@ This deploys the hook script to `~/.agentgovern/hook.py`, merges the correct ent
161
174
  `~/.claude/settings.json` (with an atomic backup), and prints instructions for any missing
162
175
  env vars.
163
176
 
177
+ ### Upgrading an existing install
178
+
179
+ Upgrading the package does **not** update a hook that is already installed. `hook install`
180
+ copies the hook script to `~/.agentgovern/hook.py` and writes its entries into
181
+ `~/.claude/settings.json`, and a plain `agentgovern hook install` against an existing install
182
+ reports it as already installed and changes nothing. After upgrading, reinstall with `--force`:
183
+
184
+ ```bash
185
+ pip install --upgrade agentgovern # or the extra you installed with, e.g. "agentgovern[langchain]"
186
+ agentgovern hook install --force
187
+ agentgovern hook status # reports no [DRIFT] lines once the hook is current
188
+ ```
189
+
190
+ `--force` writes the current defaults. It does not remember options from the earlier install: if
191
+ you installed with `--capture-mode=minimal`, `--matcher` or `--python-command`, pass them again —
192
+ `--force` without `--capture-mode=minimal` turns a minimal install into a full one.
193
+ Your environment variables are not touched. Restart any running Claude Code session afterwards.
194
+
195
+ **If your hook was installed by 2.2.1 or earlier, this step matters for coverage, not just
196
+ freshness.** Those versions wrote a `PreToolUse` matcher naming a fixed list of tools (ten, in 2.2.1), so a call
197
+ through any other tool (`PowerShell`, for example) never reached the hook. Starting with 2.3.0
198
+ the matcher is `*`, every tool — but only once `hook install --force` has rewritten the entry.
199
+
164
200
  ### Required env vars
165
201
 
166
202
  ```bash
@@ -0,0 +1,72 @@
1
+ # Releasing agentgovern to PyPI
2
+
3
+ Releases are automated via `.github/workflows/publish-sdk.yml` using PyPI trusted publishing (OIDC — no API tokens needed). Pushing a `sdk-v*` tag triggers a build and publish automatically.
4
+
5
+ ## Prerequisites (one-time PyPI setup)
6
+
7
+ Before the first automated publish, configure trusted publishing on PyPI:
8
+
9
+ 1. Go to https://pypi.org/manage/project/agentgovern/settings/publishing/
10
+ 2. Add a new trusted publisher with these exact values:
11
+
12
+ | Field | Value |
13
+ |-------------------|------------------------------|
14
+ | Owner | ZirahnAI |
15
+ | Repository name | agentgovern |
16
+ | Workflow filename | publish-sdk.yml |
17
+ | Environment name | pypi |
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
+
21
+ 3. Save. No API token needed after this.
22
+
23
+ Also create the `pypi` environment in GitHub, and restrict it to release tags:
24
+ - Go to https://github.com/ZirahnAI/agentgovern/settings/environments
25
+ - Create the environment named `pypi`.
26
+ - Under **Deployment branches and tags**, choose **Selected branches and tags** and add a **tag** rule `sdk-v*`. The publish job carries `environment: pypi`, so it can only run from an `sdk-v*` tag.
27
+ - **Required reviewers is not available here.** GitHub offers it for environments in private repositories only on Enterprise, and this repository is private on the Team plan (operator-confirmed 2026-09-30). So the publish does not pause for approval. **Pushing the tag is the release decision.**
28
+ - Optionally, add a repository **tag ruleset** on `refs/tags/sdk-v*` that restricts creation to admins. One named "sdk release tags" exists. It was **not enforced** when checked on 2026-09-30, so it has no effect until its enforcement is set to Active.
29
+
30
+ Check the environment with `gh api repos/ZirahnAI/agentgovern/environments/pypi/deployment-branch-policies`, which should list `{"name": "sdk-v*", "type": "tag"}`.
31
+
32
+ ## Release steps
33
+
34
+ 1. **Bump the version** in two places:
35
+ ```
36
+ packages/sdk-python/agentgovern/_version.py → __version__ = "0.X.Y"
37
+ packages/sdk-python/pyproject.toml → version = "0.X.Y"
38
+ ```
39
+
40
+ 2. **Add a CHANGELOG entry** (if `CHANGELOG.md` exists) describing what changed.
41
+
42
+ 3. **Commit the version bump:**
43
+ ```bash
44
+ git add packages/sdk-python/agentgovern/_version.py packages/sdk-python/pyproject.toml
45
+ git commit -m "chore(sdk): bump version to 0.X.Y"
46
+ git push
47
+ ```
48
+
49
+ 4. **Tag the merged commit and push that one tag.** Never `git push --tags`, which pushes every local tag, and any stray `sdk-v*` among them would publish:
50
+ ```bash
51
+ git fetch origin
52
+ git tag sdk-v0.X.Y <merged-commit-sha>
53
+ git push origin sdk-v0.X.Y
54
+ ```
55
+
56
+ The `publish-sdk.yml` workflow triggers on the tag push. It builds, checks the wheel's declared dependencies, installs that exact wheel in a clean venv, and then publishes to PyPI. **There is no approval step** (see above). PyPI never accepts a version twice, so a mistaken tag uses up that version for good.
57
+
58
+ 5. **Verify** at https://pypi.org/project/agentgovern/ — the new version should appear within a few minutes.
59
+
60
+ ## Manual fallback (workflow_dispatch)
61
+
62
+ To re-run a publish after a failed run, trigger the workflow manually **from the release tag**:
63
+
64
+ 1. Go to https://github.com/ZirahnAI/agentgovern/actions/workflows/publish-sdk.yml
65
+ 2. Click **Run workflow**, choose the **tag** `sdk-v0.X.Y` under "Use workflow from", then **Run workflow**.
66
+
67
+ **A run dispatched from `main` (or any branch) cannot publish.** Its build job runs, but the publish job targets the `pypi` environment, which only accepts `sdk-v*` tags. That comes from the environment restriction above, per GitHub's documented behaviour; it has not been tested by dispatching from `main`. The run publishes whatever version `pyproject.toml` says at that ref.
68
+
69
+ ## Trusted-publisher history
70
+
71
+ - **2026-09-14:** run `34876650141` (dispatched from `main`) authenticated through the `ZirahnAI/agentgovern` publisher. PyPI then refused to re-upload 2.2.1 (`400 File already exists`), so the run's conclusion was `failure`.
72
+ - **2026-09-30:** 2.3.0 was the first version published end to end from the `ZirahnAI` repository, by run `36667118357` from tag `sdk-v2.3.0`.
@@ -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.
@@ -109,8 +151,9 @@ def init(
109
151
  base_url: Vercel deployment URL (action ingest + agent registration).
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
- ``https://agentgovern.onrender.com``.
113
- fail_silently: If ``True``, SDK errors never raise into your agent.
154
+ ``https://engine.zirahn.com``.
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.4.0"
@@ -53,6 +53,13 @@ def _iso_timestamp_utc(dt: datetime | None = None) -> str:
53
53
  return dt.strftime("%Y-%m-%dT%H:%M:%S.") + f"{dt.microsecond // 1000:03d}Z"
54
54
 
55
55
 
56
+ # The policy engine's stable name. SDK <= 2.3.0 defaulted to the Render-issued
57
+ # hostname https://agentgovern.onrender.com, which names one Render service and
58
+ # cannot follow the engine to a new one (Render cannot change a service's
59
+ # region: docs/audit-reports/engine-region-move-phase1-2026-09-30.md). A custom
60
+ # domain can be re-pointed, so a future engine move needs no SDK release.
61
+ DEFAULT_POLICY_ENGINE_URL = "https://engine.zirahn.com"
62
+
56
63
  _FLUSH_INTERVAL = 1.0 # seconds between automatic flushes
57
64
  _BATCH_SIZE = 100 # flush early when buffer hits this
58
65
  _BUFFER_MAXLEN = 10_000 # drop oldest when full
@@ -107,7 +114,7 @@ class AgentGovernClient:
107
114
  self.policy_engine_url = (
108
115
  policy_engine_url
109
116
  or os.environ.get("AGENTGOVERN_POLICY_ENGINE_URL")
110
- or "https://agentgovern.onrender.com"
117
+ or DEFAULT_POLICY_ENGINE_URL
111
118
  ).rstrip("/")
112
119
  self.fail_silently = fail_silently
113
120
  self.batch_size = batch_size