agenticdome-python-sdk 1.2.35__py3-none-any.whl → 1.2.37__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agenticdome-python-sdk
3
- Version: 1.2.35
3
+ Version: 1.2.37
4
4
  Summary: Official Python SDK for AgenticDome security guardrails, DLP, tool authorization, and multi-agent delegation enforcement.
5
5
  Author: AgenticDome
6
6
  License-Expression: Apache-2.0
@@ -33,7 +33,7 @@ Requires-Dist: requests>=2.31.0
33
33
  Requires-Dist: urllib3>=2.0.0
34
34
  Requires-Dist: cryptography>=42.0.0
35
35
  Provides-Extra: crewai
36
- Requires-Dist: crewai<=1.15.22,>=1.15.5; extra == "crewai"
36
+ Requires-Dist: crewai<=1.15.23,>=1.15.5; extra == "crewai"
37
37
  Provides-Extra: redis
38
38
  Requires-Dist: redis>=4.5.0; extra == "redis"
39
39
  Provides-Extra: pop
@@ -46,7 +46,7 @@ Requires-Dist: pydantic-ai<=2.51.0,>=2.16.0; extra == "pydantic"
46
46
  Requires-Dist: anyio>=4.0.0; extra == "pydantic"
47
47
  Provides-Extra: langgraph
48
48
  Requires-Dist: langgraph<=1.2.12,>=1.2.9; extra == "langgraph"
49
- Requires-Dist: langchain-core<=1.6.5,>=1.5.0; extra == "langgraph"
49
+ Requires-Dist: langchain-core<=1.6.6,>=1.5.0; extra == "langgraph"
50
50
  Requires-Dist: langchain-openai==1.3.5; extra == "langgraph"
51
51
  Requires-Dist: anyio>=4.0.0; extra == "langgraph"
52
52
  Provides-Extra: microsoft
@@ -64,9 +64,9 @@ Requires-Dist: openai-agents<=0.22.3,>=0.18.3; extra == "openai-agents"
64
64
  Provides-Extra: mcp
65
65
  Requires-Dist: mcp<=2.2.0,>=1.26.0; extra == "mcp"
66
66
  Provides-Extra: bedrock
67
- Requires-Dist: boto3<=1.43.103,>=1.43.54; extra == "bedrock"
67
+ Requires-Dist: boto3<=1.43.105,>=1.43.54; extra == "bedrock"
68
68
  Provides-Extra: aws-bedrock
69
- Requires-Dist: boto3<=1.43.103,>=1.43.54; extra == "aws-bedrock"
69
+ Requires-Dist: boto3<=1.43.105,>=1.43.54; extra == "aws-bedrock"
70
70
  Provides-Extra: llamaindex
71
71
  Requires-Dist: llama-index<=0.14.25,>=0.14.23; extra == "llamaindex"
72
72
  Provides-Extra: llama-index
@@ -76,19 +76,19 @@ Requires-Dist: google-adk<=2.10.0,>=2.5.0; extra == "google-adk"
76
76
  Provides-Extra: adk
77
77
  Requires-Dist: google-adk<=2.10.0,>=2.5.0; extra == "adk"
78
78
  Provides-Extra: claude
79
- Requires-Dist: claude-agent-sdk<=0.2.160,>=0.2.126; extra == "claude"
79
+ Requires-Dist: claude-agent-sdk<=0.2.162,>=0.2.126; extra == "claude"
80
80
  Provides-Extra: claude-agent-sdk
81
- Requires-Dist: claude-agent-sdk<=0.2.160,>=0.2.126; extra == "claude-agent-sdk"
81
+ Requires-Dist: claude-agent-sdk<=0.2.162,>=0.2.126; extra == "claude-agent-sdk"
82
82
  Provides-Extra: smolagents
83
83
  Requires-Dist: smolagents==1.26.0; extra == "smolagents"
84
84
  Provides-Extra: all
85
- Requires-Dist: crewai<=1.15.22,>=1.15.5; extra == "all"
85
+ Requires-Dist: crewai<=1.15.23,>=1.15.5; extra == "all"
86
86
  Requires-Dist: redis>=4.5.0; extra == "all"
87
87
  Requires-Dist: cryptography>=42.0.0; extra == "all"
88
88
  Requires-Dist: pydantic-ai<=2.51.0,>=2.16.0; extra == "all"
89
89
  Requires-Dist: anyio>=4.0.0; extra == "all"
90
90
  Requires-Dist: langgraph<=1.2.12,>=1.2.9; extra == "all"
91
- Requires-Dist: langchain-core<=1.6.5,>=1.5.0; extra == "all"
91
+ Requires-Dist: langchain-core<=1.6.6,>=1.5.0; extra == "all"
92
92
  Requires-Dist: langchain-openai==1.3.5; extra == "all"
93
93
  Requires-Dist: autogen-agentchat==0.7.5; extra == "all"
94
94
  Requires-Dist: agno<=3.0.11,>=2.8.0; extra == "all"
@@ -96,10 +96,10 @@ Requires-Dist: azure-ai-projects<=2.7.0,>=2.3.0; extra == "all"
96
96
  Requires-Dist: azure-identity==1.25.3; extra == "all"
97
97
  Requires-Dist: openai-agents<=0.22.3,>=0.18.3; extra == "all"
98
98
  Requires-Dist: mcp<=2.2.0,>=1.26.0; extra == "all"
99
- Requires-Dist: boto3<=1.43.103,>=1.43.54; extra == "all"
99
+ Requires-Dist: boto3<=1.43.105,>=1.43.54; extra == "all"
100
100
  Requires-Dist: llama-index<=0.14.25,>=0.14.23; extra == "all"
101
101
  Requires-Dist: google-adk<=2.10.0,>=2.5.0; extra == "all"
102
- Requires-Dist: claude-agent-sdk<=0.2.160,>=0.2.126; extra == "all"
102
+ Requires-Dist: claude-agent-sdk<=0.2.162,>=0.2.126; extra == "all"
103
103
  Requires-Dist: smolagents==1.26.0; extra == "all"
104
104
  Provides-Extra: dev
105
105
  Requires-Dist: pytest>=8.0.0; extra == "dev"
@@ -177,16 +177,60 @@ agenticdome inspect --output agenticdome-inspection.json
177
177
 
178
178
  `init` writes the secret-free inspection and local configuration. For the
179
179
  certified onboarding path, obtain the assigned sidecar and a dedicated
180
- `integration_copilot` key, run `agenticdome integrate preview`, review its
180
+ `integration_copilot` key, run `agenticdome integrate preview` and
181
+ `agenticdome integrate status`, review the saved
181
182
  local exact diff and change summary, explicitly approve a supported edit or
182
183
  attach other boundaries manually, then run
183
184
  `agenticdome verify --run-tests --output .agenticdome/verification.json`.
184
- Preview obtains the private, signed, catalog-bound semantic plan and keeps
185
+
186
+ If a coding assistant has already made and tested the application edits,
187
+ import its canonical `AgenticDome_Integration.md` in the customer portal and
188
+ run `agenticdome validate-report --report AgenticDome_Integration.md` from
189
+ that same deployable workload. Import the two generated, hash-bound JSON files
190
+ in Onboarding. This avoids repeating the guided edit plan; the Markdown counts
191
+ remain AI-reported and a real action-path test is still required before
192
+ claiming protection. See the [customer onboarding guide](docs/customer-onboarding.md).
193
+
194
+ For the Pilot path, preview obtains the private, signed, catalog-bound semantic plan and keeps
185
195
  application source on your machine. `agenticdome integrate apply` creates a
186
196
  local Git review branch but never commits, pushes or deploys. Existing-source
187
- automatic edits are currently limited to one exact certified smolagents run
188
- pattern; all other paths need manual integration. The older `agenticdome plan`
197
+ automatic edits cover unambiguous CrewAI bootstrap, PydanticAI `Agent(...)`,
198
+ LangChain `create_agent(...)`, Google ADK agent construction, Claude options,
199
+ Agno `Agent(...)`, direct LlamaIndex `FunctionTool.from_defaults(...)`, direct
200
+ OpenAI Agents `FunctionTool(...)` handlers, explicit custom-Python tool
201
+ dispatchers, and the exact smolagents run pattern.
202
+ The CLI leaves dynamic construction,
203
+ existing handwritten AgenticDome calls, MCP forwarding and other framework
204
+ paths for explicit review; a patch never proves all routes are intercepted.
205
+ The older `agenticdome plan`
189
206
  and `agenticdome scaffold` remain available as advanced review-only commands.
207
+ Preview auto-selects an application or pure-MCP review path and reports
208
+ existing Python SDK call-site candidates, including handwritten local
209
+ wrappers. Discovery uses the versioned framework hook catalog plus documented
210
+ prompt, tool, A2A/delegation, MCP and output API methods; categories may
211
+ overlap and TypeScript call sites are not parsed by this Python checker. Mixed
212
+ application/MCP workloads get a separate MCP review path.
213
+ Each preview rescans the selected workload. If code added a framework after
214
+ `init`, its certified hook contract is included in the new plan and flagged for
215
+ review without changing `.agenticdome/config.json`; a framework still in that
216
+ config but absent from the current scan is flagged for confirmation. Run
217
+ `agenticdome integrate status` for the saved next action and import a fresh
218
+ `agenticdome inspect --output agenticdome-inspection.json` after reviewing it.
219
+ These static observations do not prove interception; the CLI avoids
220
+ overlapping automatic rewrites and requires real blocked-action tests.
221
+
222
+ For one consequential tool, run your own safe integration test through the
223
+ actual application route with an `ExecutionSpy` around its test handler, then
224
+ correlate the SDK adapter call, live-mode verdict and handler invocation:
225
+
226
+ ```bash
227
+ agenticdome verify-action --tool billing.refund --expect-verdict BLOCKED --expect-executed no -- pytest -q tests/test_blocked_refund.py
228
+ ```
229
+
230
+ The test must call `spy.assert_calls(0)` (or `1` for an allowed test); see the
231
+ [real action-path test guide](docs/customer-onboarding.md#prove-one-real-action-path).
232
+ This opt-in command does not execute a business action itself, and a fixed
233
+ `agenticdome verify` probe is not a substitute for the real-path test.
190
234
 
191
235
  For an MCP client, host, gateway or server workload, use the focused path:
192
236
 
@@ -224,6 +268,7 @@ openclaw plugins install npm:agenticdome-openclaw-security
224
268
  openclaw plugins enable agenticdome-security
225
269
  openclaw config set plugins.entries.agenticdome-security.hooks.allowConversationAccess true
226
270
  openclaw gateway restart
271
+ openclaw gateway status --require-rpc
227
272
  agenticdome openclaw protect
228
273
  # With AGENTICDOME_API_BASE, AGENTICDOME_TENANT_ID and the dedicated
229
274
  # AGENTICDOME_COPILOT_API_KEY set, generate and review the signed plan:
@@ -232,8 +277,11 @@ agenticdome plan
232
277
  agenticdome openclaw verify --output .agenticdome/verification.json
233
278
  ```
234
279
 
235
- `openclaw protect` fails unless the active OpenClaw runtime reports the loaded
236
- AgenticDome plugin, explicit consent and the exact certified typed hooks.
280
+ `openclaw protect` checks the AgenticDome plugin, explicit consent and exact
281
+ typed hooks in the inspecting CLI process. `gateway status` checks the serving
282
+ Gateway's RPC reachability; neither proves that this Gateway ran a hook on a
283
+ customer tool call. Exercise a safe tool through the actual Gateway and inspect
284
+ the matching AgenticDome runtime decision before claiming that route is protected.
237
285
  The official `openclaw plugins install`, `enable`, `config set` and gateway
238
286
  restart commands are the actual runtime configuration change; the Python CLI
239
287
  does not apply or undo them. No application source patch is needed for native
@@ -246,7 +294,8 @@ includes the MCP transport rehearsal and both `mcp_verification` and
246
294
  `openclaw_verification` in one report. Both sections must be ready before the
247
295
  report is imported; `mcp verify` alone is not a combined-workload proof. The
248
296
  customer Control Panel separately confirms tenant binding and retained
249
- telemetry; neither command silently modifies customer source.
297
+ telemetry for its probes; neither command silently modifies customer source or
298
+ proves that every live Gateway tool route was intercepted.
250
299
 
251
300
  When `.agenticdome/inspection.json` is imported into Developer Integration,
252
301
  the Control Panel adds its detected frameworks to the workload. Frameworks,
@@ -1582,6 +1631,13 @@ async def lookup_customer(customer_id: str) -> str:
1582
1631
  )
1583
1632
  ```
1584
1633
 
1634
+ If you construct `agents.FunctionTool` directly, its `on_invoke_tool`
1635
+ handler receives JSON text. Use `handler_args_format="json"` with
1636
+ `wrap_tool_handler` so the approved or sanitized arguments reach the original
1637
+ handler in that same format. The guided integration preview offers this edit
1638
+ only for a direct, statically named `FunctionTool` with an identifiable
1639
+ handler; other tool factories require manual review and a real-route test.
1640
+
1585
1641
  **Delegated specialist tool pattern:**
1586
1642
 
1587
1643
  ```python
@@ -1,42 +1,46 @@
1
- agenticdome_python_sdk-1.2.35.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
2
- agenticdome_python_sdk-1.2.35.dist-info/licenses/NOTICE,sha256=obvKP6kV1Ny5R8_INFOpf0X5um9U2X43LGwQFobO9kg,550
3
- agenticdome_sdk/__init__.py,sha256=UY__0ppEM4MlsTQoLh8alZ7AoMjtegGKe4j_Sj7CpNk,903
1
+ agenticdome_python_sdk-1.2.37.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
2
+ agenticdome_python_sdk-1.2.37.dist-info/licenses/NOTICE,sha256=obvKP6kV1Ny5R8_INFOpf0X5um9U2X43LGwQFobO9kg,550
3
+ agenticdome_sdk/__init__.py,sha256=1JY5ELhtp0zg-82xhgt6NxZ7k5CWL5e_Tm6BrsuCFXc,1102
4
4
  agenticdome_sdk/_framework_firewall.py,sha256=W3gy7WsK50scE7ypJrmVJhHUOBsr0zAWQ85rghqaJk4,31963
5
5
  agenticdome_sdk/_mode.py,sha256=XvOgh5sW-FG8S39V483h3uLiFh8wSt14OmYXz_6eDO8,1283
6
+ agenticdome_sdk/action_path_proof.py,sha256=5Zlghy7-LNhtRJM0Unb4NPUASzIsAC0LaWNVFR2yzzo,13497
6
7
  agenticdome_sdk/agno.py,sha256=L3yfjTBHW47xzT67a1DsjtwmvrAoYBkbxADpsr85Cp0,60766
7
8
  agenticdome_sdk/attestation.py,sha256=ij4cMff7nzLaIS0W02d2WURFcyKe72bcUlSz-dYkaLE,10310
8
9
  agenticdome_sdk/autogen.py,sha256=KP4qlIcsNXB-4ZT9D7Slc1xWEeWXRIWkc5HGW0U6k2c,29018
9
10
  agenticdome_sdk/aws_bedrock.py,sha256=2zD9j7xi0jna0sMeMf-hKZdc3Y4RPwzj6tMVOUGgIDQ,69581
10
- agenticdome_sdk/catalog_snapshot.json,sha256=fPbiJC7IjrPyp8ORunaNQenLCg11W43XIDHcMlINFEg,951
11
+ agenticdome_sdk/catalog_snapshot.json,sha256=51ls_eO8qy5BCZRamVz6G7F265WYFO2LaOEerO1zcA0,951
11
12
  agenticdome_sdk/claude.py,sha256=XeY9AUu61SYcJjQVz_AtL_WaN6X4KqP5nprEbbr4Q6A,17456
12
- agenticdome_sdk/client.py,sha256=2uVKZR1uXDBIvwOS-lpNPCAXl9K5vroPqsPNNYhBWeg,69473
13
+ agenticdome_sdk/client.py,sha256=SQk7BROrcedyHAfubGiwGNNGycq7W7L-Ncjroj1ugBg,69815
13
14
  agenticdome_sdk/connect.py,sha256=MY30wXYA6OaZhs__E67KcFXEy7mZMBxMSV06BnlBjRg,18766
14
15
  agenticdome_sdk/copilot_ir.py,sha256=zT13tOX8xu11yrOtIPMRFVud8CQ81Gu3WxsXxyQ4XT0,17928
15
16
  agenticdome_sdk/copilot_ir_collector.cjs,sha256=oxLY8ByONo7Es4YjYiJuRz_A7IKL65Sy486UXnu-4Rs,9681
16
17
  agenticdome_sdk/crewai.py,sha256=sSJQ49O7-8kqOAD2_jrsMtMOmBOcJmFkeOv7S3-ayMs,52490
17
18
  agenticdome_sdk/demo.py,sha256=dyfqt2N_cJoyUWKnjPYuQxuw2hb2DrCNUf1jQKeYYoc,8350
19
+ agenticdome_sdk/existing_integration.py,sha256=be6xFqe3P9d98YOydHzDeyRSbuAim4bLBrfafLOJpJM,11465
20
+ agenticdome_sdk/framework_edits.py,sha256=4BgWHxWKEkex8_DMhjVxjovpUVkrMF7TsCZzlS94mpA,23344
21
+ agenticdome_sdk/generic_python.py,sha256=JtXRcYEZ6_Dgxnnv4QgqeMZR8dFVr8EqFDM-XYQ-GMA,6823
18
22
  agenticdome_sdk/google_adk.py,sha256=2TA-tUhY0lOnkGcs1BN9CUEz67ebVX4HJ7mnNoRPcZs,57258
19
- agenticdome_sdk/guided_integration.py,sha256=JQ5THutpsC1J0yV7gEOJWiLHGLQOKhAlPDFTi5SH5OA,36595
20
- agenticdome_sdk/hook_catalog.py,sha256=0dfFWNDS0KCeP0IozWZb1NkNBWf1omaO2-yZKNVdTf0,29272
23
+ agenticdome_sdk/guided_integration.py,sha256=m7EsgLrPEJhfrVMaVWO2DtjYp6S8PwpCKDYZPNbVZ2o,52259
24
+ agenticdome_sdk/hook_catalog.py,sha256=XRc_MeEE3LArX1R0xfO3Vo6nZbF3U-4cOkTOMfMOZS8,29272
21
25
  agenticdome_sdk/identity.py,sha256=mrymGgZSVsKJ9THRde54qGmMT7j_uZHmPJ-HtWk7PXE,4635
22
26
  agenticdome_sdk/langgraph.py,sha256=FUvF6DSA5aZP3eoBoNeldgGtPrz-dO5xcVmrPPSBEhI,84312
23
27
  agenticdome_sdk/lifecycle.py,sha256=9LvxmUU1LC_tMchb5uVFBnZIkVFUxOv3VH9wG_9gPtA,12855
24
28
  agenticdome_sdk/llamaindex.py,sha256=BHplSUIsgMi7k8tBztprzaV9iaOXiljXbdeMRax_2h8,42860
25
- agenticdome_sdk/mcp_host.py,sha256=bJG0pCfMYkI6ig4tmPvtJKcdFFfDOXNmatLbh_X9lso,63283
26
- agenticdome_sdk/mcp_http_gateway.py,sha256=j7B_IVhr0QLSPdsE8iVKcH0mCtQN7EE622JKY3d2LHI,13533
29
+ agenticdome_sdk/mcp_host.py,sha256=T9ICAyz8SjOhssuhXaiEuav5fduWriDhuZ4U_e8Jh9w,69389
30
+ agenticdome_sdk/mcp_http_gateway.py,sha256=XrTgj46ZHIKJ9xS3EQPsac2C9OGUZnrOXomcLdkMk_Y,13629
27
31
  agenticdome_sdk/mcp_rehearsal_server.py,sha256=cox4XqUTL8d0gaDzqxDZcEALkaz8otjOjOqHvoUeeCo,2118
28
- agenticdome_sdk/mcp_verification.py,sha256=VqQS5SpPVFI3G7qCwrT90JmG8vFE6pRJEB7PViHjKqw,14235
32
+ agenticdome_sdk/mcp_verification.py,sha256=WtRhAFVPTBqGOcHNf_db1m4jX9_TO0oxP98XWk4_ISY,14551
29
33
  agenticdome_sdk/microsoft_agent_framework.py,sha256=GfIIuk0n-VhsZaTyoB7P6ilpz9bFLD95TWuX0rFSWeU,67219
30
34
  agenticdome_sdk/microsoft_ai_foundry.py,sha256=YlO968s1dbAYcaEqhqufL92NFR66GuTtngnCv7-1mYc,56911
31
- agenticdome_sdk/onboarding_cli.py,sha256=1g-wNEzEpAmR1s-oYS6mqCd0TgT3PQuEqNQk9M0hAQc,124440
32
- agenticdome_sdk/openai_agents.py,sha256=nNM-YcsUDcE23sGv1buLudcYFz0NLju2Mwiw_wrVaJg,57326
35
+ agenticdome_sdk/onboarding_cli.py,sha256=V1MKiiYudC48SgaZNYMmUysgRjwARGG6S0VUf4ZLLtU,150677
36
+ agenticdome_sdk/openai_agents.py,sha256=0z-7FDBIG4usxnILpan6yjQHF4zXIzm9YLsh3-bW8Lw,57678
33
37
  agenticdome_sdk/outcomes.py,sha256=jadWDzQaZXrHNvpPme8lQY0jihYLCoM3s4CihAq0JRo,2507
34
38
  agenticdome_sdk/proof.py,sha256=ECKc14xSE0V1HHmcpZjfVz8-Q2H_fooah3BfdblFV8M,2878
35
39
  agenticdome_sdk/pydantic.py,sha256=WKb8X0Fgydfid-ep8E-M8Yslz2pWcCP6w7n_aBxt1rI,54961
36
40
  agenticdome_sdk/simulation.py,sha256=UCbZy3u9AzfxQCfY38SCV3CxGxhinV9EA4aHMPlK4mc,8426
37
41
  agenticdome_sdk/smolagents.py,sha256=wjMA1HVAQ-9wRNM38Jr9wlCpKMhF-9vxtTk6R-kwQhs,19094
38
- agenticdome_python_sdk-1.2.35.dist-info/METADATA,sha256=Fn0YW1txEeYaaqT9hykQc-38Qe5IkqwLlcoHreKeS2M,141741
39
- agenticdome_python_sdk-1.2.35.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
40
- agenticdome_python_sdk-1.2.35.dist-info/entry_points.txt,sha256=cRfCphkr5gLrkGIzXJEtL50HdHLajoZLbZtcMLE8NoY,113
41
- agenticdome_python_sdk-1.2.35.dist-info/top_level.txt,sha256=LZbbjXco5mqDrGwlTJpIKI5Sp6R_njzHHjExxAs9c3s,16
42
- agenticdome_python_sdk-1.2.35.dist-info/RECORD,,
42
+ agenticdome_python_sdk-1.2.37.dist-info/METADATA,sha256=MYsYXQMJm4WcWtRlr7bhEtug7k6TuLhJHknT8WjPhoE,145282
43
+ agenticdome_python_sdk-1.2.37.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
44
+ agenticdome_python_sdk-1.2.37.dist-info/entry_points.txt,sha256=cRfCphkr5gLrkGIzXJEtL50HdHLajoZLbZtcMLE8NoY,113
45
+ agenticdome_python_sdk-1.2.37.dist-info/top_level.txt,sha256=LZbbjXco5mqDrGwlTJpIKI5Sp6R_njzHHjExxAs9c3s,16
46
+ agenticdome_python_sdk-1.2.37.dist-info/RECORD,,
@@ -3,6 +3,8 @@ from .identity import IDENTITY_CONTEXT_VERSION, canonicalize_identity_context, e
3
3
  from .proof import create_dpop_proof, generate_rsa_proof_key, jwk_thumbprint
4
4
  from ._mode import LIVE_MODE, LOCAL_SIM_MODE, is_local_sim_mode, resolve_mode
5
5
  from .lifecycle import VerifiedActionContext, VerifiedActionDenied, VerifiedActionReporter, verified_action
6
+ from .action_path_proof import ExecutionSpy
7
+ from .generic_python import AgenticDomeActionDenied, guarded_tool_executor
6
8
 
7
9
  __all__ = [
8
10
  "AgenticDomeClient",
@@ -22,4 +24,7 @@ __all__ = [
22
24
  "VerifiedActionDenied",
23
25
  "VerifiedActionReporter",
24
26
  "verified_action",
27
+ "ExecutionSpy",
28
+ "AgenticDomeActionDenied",
29
+ "guarded_tool_executor",
25
30
  ]
@@ -0,0 +1,263 @@
1
+ """Opt-in, source-free evidence for a customer-run action-path test.
2
+
3
+ The test must install ExecutionSpy around the actual business handler. A
4
+ sidecar verdict on its own never proves that the handler did or did not run.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import functools
10
+ import hashlib
11
+ import inspect
12
+ import json
13
+ import os
14
+ import re
15
+ import stat
16
+ import subprocess
17
+ import sys
18
+ import tempfile
19
+ import time
20
+ from datetime import datetime, timezone
21
+ from pathlib import Path
22
+ from typing import Any, Callable, Dict, Optional, Sequence, Tuple
23
+
24
+
25
+ _ADAPTERS = frozenset({
26
+ "crewai", "pydantic", "langgraph", "microsoft_agent_framework",
27
+ "autogen", "microsoft_ai_foundry", "openai_agents", "claude",
28
+ "smolagents", "agno", "google_adk", "llamaindex", "aws_bedrock",
29
+ "mcp_host", "generic_python", "_framework_firewall",
30
+ })
31
+
32
+
33
+ def _hook_frame() -> Tuple[str, str]:
34
+ frame = sys._getframe(2)
35
+ generic = ("", "")
36
+ try:
37
+ while frame:
38
+ module = str(frame.f_globals.get("__name__", ""))
39
+ if module.startswith("agenticdome_sdk.") and module.rsplit(".", 1)[-1] in _ADAPTERS:
40
+ if module != "agenticdome_sdk._framework_firewall":
41
+ return module, frame.f_code.co_name
42
+ if not generic[0]:
43
+ generic = (module, frame.f_code.co_name)
44
+ frame = frame.f_back
45
+ finally:
46
+ del frame
47
+ return generic
48
+
49
+
50
+ def _append_event(event: Dict[str, Any]) -> None:
51
+ path = os.environ.get("AGENTICDOME_ACTION_PROOF_FILE", "")
52
+ if not path or not os.path.isabs(path):
53
+ return
54
+ event = {"schema": "agenticdome.action-path-event.v1", **event}
55
+ encoded = (json.dumps(event, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
56
+ if len(encoded) > 2048:
57
+ return
58
+ flags = os.O_WRONLY | os.O_APPEND | os.O_CREAT
59
+ if hasattr(os, "O_NOFOLLOW"):
60
+ flags |= os.O_NOFOLLOW
61
+ try:
62
+ fd = os.open(path, flags, 0o600)
63
+ try:
64
+ info = os.fstat(fd)
65
+ if not stat.S_ISREG(info.st_mode) or info.st_size > 262144:
66
+ return
67
+ os.write(fd, encoded)
68
+ finally:
69
+ os.close(fd)
70
+ except OSError:
71
+ # Proof is optional instrumentation; it must not change enforcement.
72
+ return
73
+
74
+
75
+ def record_policy_decision(*, tool_name: Optional[str], platform: Optional[str],
76
+ direction: str, response: Dict[str, Any], live: bool) -> None:
77
+ """Record no prompt, arguments, identity, token, or raw response."""
78
+ if not os.environ.get("AGENTICDOME_ACTION_PROOF_FILE") or not tool_name:
79
+ return
80
+ module, function = _hook_frame()
81
+ envelope = response.get("result") if isinstance(response.get("result"), dict) else response
82
+ verdict = str(envelope.get("verdict") or envelope.get("decision") or "UNKNOWN").upper()
83
+ decision_id = str(envelope.get("decision_id") or response.get("decision_id") or "")
84
+ _append_event({
85
+ "kind": "policy_decision", "tool_name": str(tool_name)[:160],
86
+ "platform": str(platform or "")[:80], "direction": str(direction)[:24],
87
+ "verdict": verdict if verdict in {"ALLOWED", "BLOCKED", "REDACTED"} else "UNKNOWN",
88
+ "live": bool(live), "hook_module": module, "hook_function": function,
89
+ "decision_id": decision_id if re.fullmatch(r"[A-Za-z0-9._:-]{1,100}", decision_id) else "",
90
+ })
91
+
92
+
93
+ class ExecutionSpy:
94
+ """Wrap the real test handler, then assert its invocation count in the test."""
95
+
96
+ def __init__(self, tool_name: str):
97
+ if not tool_name or len(tool_name) > 160:
98
+ raise ValueError("tool_name must contain 1-160 characters")
99
+ self.tool_name = tool_name
100
+ self.calls = 0
101
+
102
+ def wrap(self, handler: Callable[..., Any]) -> Callable[..., Any]:
103
+ if inspect.iscoroutinefunction(handler):
104
+ @functools.wraps(handler)
105
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
106
+ self.calls += 1
107
+ _append_event({"kind": "handler_attempt", "tool_name": self.tool_name})
108
+ return await handler(*args, **kwargs)
109
+ return async_wrapper
110
+
111
+ @functools.wraps(handler)
112
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
113
+ self.calls += 1
114
+ _append_event({"kind": "handler_attempt", "tool_name": self.tool_name})
115
+ return handler(*args, **kwargs)
116
+ return sync_wrapper
117
+
118
+ def assert_calls(self, expected: int) -> None:
119
+ if not isinstance(expected, int) or expected < 0:
120
+ raise ValueError("expected must be a non-negative integer")
121
+ if self.calls != expected:
122
+ raise AssertionError(f"Expected {expected} handler calls; observed {self.calls}")
123
+ _append_event({"kind": "handler_assertion", "tool_name": self.tool_name, "calls": self.calls})
124
+
125
+
126
+ def run_action_path_test(root: Path, *, tool_name: str, expected_verdict: str,
127
+ expected_calls: int, command: Sequence[str],
128
+ timeout_seconds: int = 600) -> Tuple[int, Dict[str, Any]]:
129
+ """Run an explicit customer test command, never a shell, and inspect evidence."""
130
+ if not tool_name or len(tool_name) > 160:
131
+ raise ValueError("tool_name must contain 1-160 characters")
132
+ expected_verdict = expected_verdict.upper()
133
+ if expected_verdict not in {"ALLOWED", "BLOCKED", "REDACTED"}:
134
+ raise ValueError("expected_verdict must be ALLOWED, BLOCKED or REDACTED")
135
+ if expected_calls not in {0, 1}:
136
+ raise ValueError("expected_calls must be 0 or 1 for an isolated action test")
137
+ if (expected_verdict == "BLOCKED" and expected_calls != 0) or (expected_verdict != "BLOCKED" and expected_calls != 1):
138
+ raise ValueError("Blocked actions must not execute; allowed/redacted actions need one observed test-handler call")
139
+ if not command or command[0] == "--" or any(not item or "\x00" in item for item in command):
140
+ raise ValueError("Provide one explicit test command after --; a shell is not used")
141
+ required = ("AGENTICDOME_API_BASE", "AGENTICDOME_API_KEY", "AGENTICDOME_TENANT_ID")
142
+ missing = [key for key in required if not os.environ.get(key, "").strip()]
143
+ if missing:
144
+ raise ValueError("Live action-path proof requires " + ", ".join(missing))
145
+ if os.environ.get("AGENTICDOME_MODE", "").strip().lower() in {"local_sim", "simulation", "sim"}:
146
+ raise ValueError("Action-path proof requires a live assigned sidecar, not local simulation")
147
+ started = time.monotonic()
148
+ with tempfile.TemporaryDirectory(prefix="agenticdome-action-proof-") as temporary:
149
+ journal = Path(temporary) / "events.jsonl"
150
+ environment = os.environ.copy()
151
+ environment["AGENTICDOME_ACTION_PROOF_FILE"] = str(journal)
152
+ try:
153
+ completed = subprocess.run(list(command), cwd=root, env=environment,
154
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
155
+ timeout=timeout_seconds, check=False)
156
+ test_exit = completed.returncode
157
+ timeout = False
158
+ except subprocess.TimeoutExpired:
159
+ test_exit = None
160
+ timeout = True
161
+ events = []
162
+ if journal.exists():
163
+ for line in journal.read_text(encoding="utf-8").splitlines()[:200]:
164
+ try:
165
+ value = json.loads(line)
166
+ except json.JSONDecodeError:
167
+ continue
168
+ if isinstance(value, dict) and value.get("schema") == "agenticdome.action-path-event.v1":
169
+ events.append(value)
170
+ decisions = [item for item in events if item.get("kind") == "policy_decision" and item.get("tool_name") == tool_name]
171
+ attempts = [item for item in events if item.get("kind") == "handler_attempt" and item.get("tool_name") == tool_name]
172
+ assertions = [item for item in events if item.get("kind") == "handler_assertion" and item.get("tool_name") == tool_name]
173
+ decision = decisions[0] if len(decisions) == 1 else {}
174
+ assertion = assertions[0] if len(assertions) == 1 else {}
175
+ reasons = []
176
+ if timeout:
177
+ reasons.append("The customer test timed out")
178
+ elif test_exit != 0:
179
+ reasons.append("The customer test did not pass")
180
+ if len(decisions) != 1:
181
+ reasons.append("Expected exactly one SDK tool decision for this action")
182
+ elif not decision.get("live") or decision.get("direction") != "output":
183
+ reasons.append("The decision was not a live outbound tool decision")
184
+ elif not decision.get("hook_module"):
185
+ reasons.append("No certified SDK framework hook was observed; a direct client call is insufficient")
186
+ elif decision.get("verdict") != expected_verdict:
187
+ reasons.append("Observed verdict differs from the expected verdict")
188
+ if len(assertions) != 1 or assertion.get("calls") != expected_calls or len(attempts) != expected_calls:
189
+ reasons.append("The real-handler spy did not establish the expected execution count")
190
+ elif len(decisions) == 1:
191
+ decision_index = events.index(decision)
192
+ assertion_index = events.index(assertion)
193
+ if assertion_index <= decision_index or any(events.index(attempt) <= decision_index or events.index(attempt) >= assertion_index for attempt in attempts):
194
+ reasons.append("Handler evidence was not ordered after the policy decision and before the test assertion")
195
+ report = {
196
+ "schema": "agenticdome.action-path-proof.v1", "status": "passed" if not reasons else "incomplete",
197
+ "tool_name": tool_name, "expected_verdict": expected_verdict,
198
+ "observed_verdict": decision.get("verdict"),
199
+ "decision_id": decision.get("decision_id") or None,
200
+ "hook_module": decision.get("hook_module"), "hook_function": decision.get("hook_function"),
201
+ "expected_handler_calls": expected_calls,
202
+ "observed_handler_calls": len(attempts),
203
+ "handler_assertion_passed": bool(assertion) and assertion.get("calls") == expected_calls,
204
+ "customer_test_exit_code": test_exit, "timed_out": timeout,
205
+ "duration_ms": round((time.monotonic() - started) * 1000),
206
+ "observed_at": datetime.now(timezone.utc).isoformat(),
207
+ "reasons": reasons,
208
+ "next_action": (
209
+ "Run the same safe test command directly to inspect its logs, then check the selected hook and spy placement. No test output was retained."
210
+ if reasons else
211
+ "Review the corresponding retained sidecar decision and repeat with an allowed/blocked counterpart before promoting this route."
212
+ ),
213
+ "qualification": "The SDK hook and live verdict were observed locally. Handler execution is established by a customer-installed spy around the actual test handler; verify that this test exercises the production route. No business action is run by the CLI itself.",
214
+ }
215
+ report["proof_sha256"] = hashlib.sha256(
216
+ json.dumps(report, sort_keys=True, separators=(",", ":")).encode("utf-8")
217
+ ).hexdigest()
218
+ return (0 if not reasons else 2), report
219
+
220
+
221
+ def load_action_path_proof(root: Path) -> Optional[Dict[str, Any]]:
222
+ """Load a current, intact customer-run report without trusting free text."""
223
+ path = root / ".agenticdome" / "action-proof.json"
224
+ try:
225
+ if path.is_symlink() or not path.is_file() or path.stat().st_size > 16384:
226
+ return None
227
+ report = json.loads(path.read_text(encoding="utf-8"))
228
+ if not isinstance(report, dict) or report.get("schema") != "agenticdome.action-path-proof.v1":
229
+ return None
230
+ expected = str(report.get("proof_sha256") or "")
231
+ source = {key: value for key, value in report.items() if key != "proof_sha256"}
232
+ digest = hashlib.sha256(json.dumps(source, sort_keys=True, separators=(",", ":")).encode("utf-8")).hexdigest()
233
+ if digest != expected:
234
+ return None
235
+ observed_at = datetime.fromisoformat(str(report.get("observed_at") or ""))
236
+ age = datetime.now(timezone.utc) - observed_at.astimezone(timezone.utc)
237
+ if age.total_seconds() < -300 or age.total_seconds() > 7 * 86400:
238
+ return None
239
+ if report.get("status") != "passed" or report.get("hook_module") not in {
240
+ "agenticdome_sdk." + adapter for adapter in _ADAPTERS
241
+ }:
242
+ return None
243
+ if report.get("observed_verdict") not in {"ALLOWED", "BLOCKED", "REDACTED"}:
244
+ return None
245
+ if report.get("expected_handler_calls") not in {0, 1} or report.get("observed_handler_calls") != report.get("expected_handler_calls"):
246
+ return None
247
+ return {
248
+ "schema": "agenticdome.action-path-proof.v1", "status": "passed",
249
+ "tool_name": str(report.get("tool_name") or "")[:160],
250
+ "observed_verdict": report["observed_verdict"],
251
+ "decision_id": str(report.get("decision_id") or "")[:100] or None,
252
+ "hook_module": report["hook_module"],
253
+ "hook_function": str(report.get("hook_function") or "")[:100],
254
+ "observed_handler_calls": report["observed_handler_calls"],
255
+ "observed_at": report["observed_at"],
256
+ "proof_sha256": expected,
257
+ "evidence_origin": "customer_test",
258
+ }
259
+ except (OSError, ValueError, TypeError, OverflowError, json.JSONDecodeError):
260
+ return None
261
+
262
+
263
+ __all__ = ["ExecutionSpy", "run_action_path_test", "record_policy_decision", "load_action_path_proof"]
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schema": "agenticdome.catalog-snapshot.v1",
3
3
  "catalog_schema": "agenticdome.hook-catalog.v1",
4
- "verified_at": "2026-09-26",
5
- "python_sdk_version": "1.2.35",
4
+ "verified_at": "2026-10-02",
5
+ "python_sdk_version": "1.2.37",
6
6
  "published_packages": {
7
7
  "agenticdome-python-sdk": {
8
8
  "registry": "pypi",
9
- "version": "1.2.35",
10
- "url": "https://pypi.org/project/agenticdome-python-sdk/1.2.35/"
9
+ "version": "1.2.37",
10
+ "url": "https://pypi.org/project/agenticdome-python-sdk/1.2.37/"
11
11
  },
12
12
  "agenticdome-sdk": {
13
13
  "registry": "npm",
@@ -21,8 +21,8 @@
21
21
  },
22
22
  "openclaw": {
23
23
  "registry": "npm",
24
- "version": "2026.9.6",
25
- "url": "https://www.npmjs.com/package/openclaw/v/2026.9.6"
24
+ "version": "2026.9.7",
25
+ "url": "https://www.npmjs.com/package/openclaw/v/2026.9.7"
26
26
  }
27
27
  }
28
28
  }
agenticdome_sdk/client.py CHANGED
@@ -805,6 +805,12 @@ class AgenticDomeClient:
805
805
  raise AgenticDomeError(
806
806
  "AgenticDome execution broker did not return a verified, atomically consumed decision"
807
807
  )
808
+ if tool_name and os.environ.get("AGENTICDOME_ACTION_PROOF_FILE"):
809
+ from .action_path_proof import record_policy_decision
810
+ record_policy_decision(
811
+ tool_name=tool_name, platform=platform, direction=normalized_direction,
812
+ response=response, live=not self.is_simulation,
813
+ )
808
814
  return response
809
815
 
810
816
  @staticmethod