agentrust-py 0.0.7__tar.gz → 0.0.8__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 (111) hide show
  1. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/PKG-INFO +3 -1
  2. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/__init__.py +3 -1
  3. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/adapters/autogen.py +135 -1
  4. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/config.py +1 -1
  5. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/pyproject.toml +3 -1
  6. agentrust_py-0.0.8/tests/adapters/test_ag2_quickstart_example.py +145 -0
  7. agentrust_py-0.0.8/tests/adapters/test_autogen_quickstart_example.py +138 -0
  8. agentrust_py-0.0.8/tests/adapters/test_autogen_v04_governed_tool.py +243 -0
  9. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_callback_url.py +1 -1
  10. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/.gitignore +0 -0
  11. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/CHANGELOG.md +0 -0
  12. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/LICENSE +0 -0
  13. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/README.md +0 -0
  14. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust/__init__.py +0 -0
  15. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/_evaluations.py +0 -0
  16. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/_real_validator.py +0 -0
  17. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/adapters/__init__.py +0 -0
  18. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/adapters/_core.py +0 -0
  19. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/adapters/claude_agents.py +0 -0
  20. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/adapters/crewai.py +0 -0
  21. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/adapters/langgraph.py +0 -0
  22. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/adapters/mcp.py +0 -0
  23. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/adapters/openai_agents.py +0 -0
  24. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/audit/__init__.py +0 -0
  25. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/audit/logger.py +0 -0
  26. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/auth.py +0 -0
  27. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/auto.py +0 -0
  28. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/autoload.py +0 -0
  29. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/classifier/__init__.py +0 -0
  30. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/classifier/engine.py +0 -0
  31. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/classifier/profiles.py +0 -0
  32. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/classifier/system_prompt.md +0 -0
  33. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/cli.py +0 -0
  34. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/cli_certify.py +0 -0
  35. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/client.py +0 -0
  36. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/decorator.py +0 -0
  37. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/embedded.py +0 -0
  38. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/golden_suite.py +0 -0
  39. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/governance/__init__.py +0 -0
  40. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/governance/hitl.py +0 -0
  41. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/governance/kernel.py +0 -0
  42. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/governance/kill_switch.py +0 -0
  43. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/governance/modes.py +0 -0
  44. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/hooks.py +0 -0
  45. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/manifest.py +0 -0
  46. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/models.py +0 -0
  47. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/notifications/__init__.py +0 -0
  48. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/notifications/discord.py +0 -0
  49. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/notifications/dispatcher.py +0 -0
  50. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/notifications/slack.py +0 -0
  51. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/policy/__init__.py +0 -0
  52. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/policy/backends/__init__.py +0 -0
  53. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/policy/backends/native.py +0 -0
  54. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/policy/backends/opa.py +0 -0
  55. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/policy/engine.py +0 -0
  56. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/policy/loader.py +0 -0
  57. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/py.typed +0 -0
  58. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/queue_replay.py +0 -0
  59. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/tiers.py +0 -0
  60. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/tool_tracking.py +0 -0
  61. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/version_negotiation.py +0 -0
  62. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/agentrust_sdk/webhooks.py +0 -0
  63. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/policy/base.yaml +0 -0
  64. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/policy/default-policy.json +0 -0
  65. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/policy/financial.yaml +0 -0
  66. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/policy/medical.yaml +0 -0
  67. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/policy/profiles/advisory.json +0 -0
  68. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/policy/profiles/balanced.json +0 -0
  69. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/policy/profiles/strict.json +0 -0
  70. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/__init__.py +0 -0
  71. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/_cfg.py +0 -0
  72. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/__init__.py +0 -0
  73. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/_ag2.py +0 -0
  74. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/_crews.py +0 -0
  75. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/_graphs.py +0 -0
  76. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_ag2_governed_function.py +0 -0
  77. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_ag2_reply_hook.py +0 -0
  78. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_ag2_research_team_example.py +0 -0
  79. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_crewai_baseline.py +0 -0
  80. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_crewai_failure_modes.py +0 -0
  81. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_crewai_governed_tools.py +0 -0
  82. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_crewai_holds_and_chain.py +0 -0
  83. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_crewai_quickstart_example.py +0 -0
  84. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_crewai_task_checks.py +0 -0
  85. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_baseline.py +0 -0
  86. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_failure_modes.py +0 -0
  87. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_governed_tools.py +0 -0
  88. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_holds_and_chain.py +0 -0
  89. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_node.py +0 -0
  90. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_quickstart_example.py +0 -0
  91. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/conformance/__init__.py +0 -0
  92. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/conformance/bindings.py +0 -0
  93. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/conformance/test_c1_c10.py +0 -0
  94. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/conftest.py +0 -0
  95. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_adapter_tier_gate.py +0 -0
  96. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_adapters.py +0 -0
  97. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_async_advanced.py +0 -0
  98. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_chaos.py +0 -0
  99. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_cli_certify.py +0 -0
  100. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_client.py +0 -0
  101. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_client_adapter_contract.py +0 -0
  102. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_config.py +0 -0
  103. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_decorator.py +0 -0
  104. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_embedded.py +0 -0
  105. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_embedded_adapter_contract.py +0 -0
  106. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_golden_suite.py +0 -0
  107. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_governor.py +0 -0
  108. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_hooks.py +0 -0
  109. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_manifest.py +0 -0
  110. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_policy_packaging.py +0 -0
  111. {agentrust_py-0.0.7 → agentrust_py-0.0.8}/tests/test_webhooks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentrust-py
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: AgentTrust Edge — Full Governance Platform. Policy Engine + MAD Classifier + HITL + Kill Switch.
5
5
  Project-URL: Homepage, https://agentrust.io
6
6
  Project-URL: Documentation, https://docs.agentrust.io
@@ -25,6 +25,8 @@ Requires-Dist: pydantic<3.0.0,>=2.0.0
25
25
  Requires-Dist: pyyaml<7.0,>=6.0.3
26
26
  Provides-Extra: ag2
27
27
  Requires-Dist: ag2<1.0,>=0.9; extra == 'ag2'
28
+ Provides-Extra: autogen
29
+ Requires-Dist: autogen-agentchat<1.0,>=0.4; extra == 'autogen'
28
30
  Provides-Extra: autoload
29
31
  Provides-Extra: crewai
30
32
  Requires-Dist: crewai<2.0,>=1.0; extra == 'crewai'
@@ -43,6 +43,8 @@ from .adapters.autogen import (
43
43
  AgentTrustReplyHook,
44
44
  governed_function,
45
45
  governed_functions,
46
+ governed_tool as governed_autogen_tool,
47
+ governed_tools as governed_autogen_tools,
46
48
  )
47
49
  from .adapters.claude_agents import ClaudeAgentGuard
48
50
  from .adapters.openai_agents import agent_guard as openai_agent_guard, OpenAIAgentsGuard, SwarmGuard
@@ -154,4 +156,4 @@ __all__ = [
154
156
  "validate",
155
157
  ]
156
158
 
157
- __version__ = "0.0.7"
159
+ __version__ = "0.0.8"
@@ -10,6 +10,10 @@ Pattern 0 — governed_function (AG2 / pyautogen 0.2 tools):
10
10
  held for approval when needed, run, post-checked and audited. A refusal
11
11
  is returned as the tool result, so the chat keeps going.
12
12
 
13
+ Pattern 0b — governed_tool (AutoGen 0.4+ / autogen-agentchat tools):
14
+ Wrap each FunctionTool (or plain function) before giving it to an
15
+ AssistantAgent. Same checks as governed_function.
16
+
13
17
  Pattern 1 — AgentTrustReplyHook (v0.2 + v0.4):
14
18
  Attach to any ConversableAgent / AssistantAgent to validate every reply
15
19
  before it is sent.
@@ -42,6 +46,7 @@ from ._core import Allowed, Governor, OnBlock, Refused, check_adapter_tier
42
46
  logger = logging.getLogger(__name__)
43
47
 
44
48
  _FRAMEWORK = "ag2"
49
+ _FRAMEWORK_V04 = "autogen"
45
50
 
46
51
 
47
52
  def governed_function(
@@ -132,6 +137,102 @@ def governed_functions(
132
137
  return [governed_function(fn, agent_id=agent_id, governor=gov) for fn in fns]
133
138
 
134
139
 
140
+ def _governor(
141
+ agent_id: str, framework: str, on_block: OnBlock, review_timeout_s: float,
142
+ user: str, base_url: str | None, api_key: str | None,
143
+ ) -> Governor:
144
+ return Governor(
145
+ agent_id, framework=framework, on_block=on_block, review_timeout_s=review_timeout_s,
146
+ user=user, base_url=base_url, api_key=api_key,
147
+ )
148
+
149
+
150
+ def governed_tool(
151
+ tool: Any,
152
+ *,
153
+ agent_id: str,
154
+ on_block: OnBlock = "refuse",
155
+ review_timeout_s: float = 300.0,
156
+ user: str = "autogen",
157
+ base_url: str | None = None,
158
+ api_key: str | None = None,
159
+ governor: Governor | None = None,
160
+ ) -> Any:
161
+ """Wrap an AutoGen 0.4+ tool so AgentTrust governs every call.
162
+
163
+ Accepts a ``BaseTool`` such as ``FunctionTool``, or a plain function (its
164
+ docstring becomes the description). The result has the same name,
165
+ description and argument schema, so the model sees the same tool. Pass it
166
+ in ``AssistantAgent(tools=[...])``. A refused call reaches the agent as the
167
+ tool result.
168
+ """
169
+ check_adapter_tier(Capability.AUTOGEN_ADAPTER, api_key)
170
+ gov = governor or _governor(
171
+ agent_id, _FRAMEWORK_V04, on_block, review_timeout_s, user, base_url, api_key,
172
+ )
173
+ return _governed_tool_class().wrap(_as_tool(tool), gov)
174
+
175
+
176
+ def governed_tools(
177
+ tools: Sequence[Any],
178
+ *,
179
+ agent_id: str,
180
+ on_block: OnBlock = "refuse",
181
+ review_timeout_s: float = 300.0,
182
+ user: str = "autogen",
183
+ base_url: str | None = None,
184
+ api_key: str | None = None,
185
+ governor: Governor | None = None,
186
+ ) -> list[Any]:
187
+ """Wrap each AutoGen 0.4+ tool with :func:`governed_tool`, sharing one Governor."""
188
+ check_adapter_tier(Capability.AUTOGEN_ADAPTER, api_key)
189
+ gov = governor or _governor(
190
+ agent_id, _FRAMEWORK_V04, on_block, review_timeout_s, user, base_url, api_key,
191
+ )
192
+ return [governed_tool(t, agent_id=agent_id, governor=gov) for t in tools]
193
+
194
+
195
+ def _as_tool(tool: Any) -> Any:
196
+ from autogen_core.tools import BaseTool, FunctionTool
197
+
198
+ if isinstance(tool, BaseTool):
199
+ return tool
200
+ return FunctionTool(tool, description=inspect.getdoc(tool) or tool.__name__)
201
+
202
+
203
+ @functools.lru_cache(maxsize=None)
204
+ def _governed_tool_class() -> type:
205
+ """Build GovernedTool on first use, so autogen-core stays optional."""
206
+ from autogen_core import CancellationToken
207
+ from autogen_core.tools import BaseTool
208
+
209
+ class GovernedTool(BaseTool):
210
+ """A tool whose every call goes through an AgentTrust Governor."""
211
+
212
+ def __init__(self, inner: Any, governor: Governor) -> None:
213
+ super().__init__(
214
+ inner.args_type(), inner.return_type(), inner.name, inner.description,
215
+ strict=getattr(inner, "_strict", False),
216
+ )
217
+ self._inner = inner
218
+ self._governor = governor
219
+
220
+ @classmethod
221
+ def wrap(cls, inner: Any, governor: Governor) -> "GovernedTool":
222
+ return cls(inner, governor)
223
+
224
+ async def run(self, args: Any, cancellation_token: CancellationToken) -> Any:
225
+ params = args.model_dump()
226
+ outcome = await self._governor.aguard_tool(
227
+ self.name, params, lambda: self._inner.run(args, cancellation_token),
228
+ input=_describe_call(self.name, params),
229
+ )
230
+ # The refusal text is the tool result the agent reads next.
231
+ return outcome.message if isinstance(outcome, Refused) else outcome.result
232
+
233
+ return GovernedTool
234
+
235
+
135
236
  def _describe_call(name: str, args: dict[str, Any]) -> str:
136
237
  """The proposed call as the pre-check's request input."""
137
238
  return f"tool call {name}({', '.join(f'{k}={v!r}' for k, v in args.items())})"
@@ -169,6 +270,39 @@ def _validate_sync(
169
270
  logger.warning("[AgentTrust] AutoGen validate failed (non-fatal): %s", exc)
170
271
 
171
272
 
273
+ async def _avalidate(
274
+ agent_id: str,
275
+ base_url: str,
276
+ api_key: str | None,
277
+ user: str,
278
+ input_text: str,
279
+ output: dict,
280
+ framework: str,
281
+ block_on_block: bool,
282
+ ) -> None:
283
+ """Async counterpart of :func:`_validate_sync`, safe to await in an event loop."""
284
+ from agentrust_sdk.client import AsyncAgentTrustClient, GatewayUnavailableError
285
+ from agentrust_sdk.config import SDK_CONFIG
286
+ from agentrust_sdk.decorator import _check_decision, BlockedError
287
+
288
+ try:
289
+ async with AsyncAgentTrustClient(base_url=base_url, api_key=api_key) as client:
290
+ resp = await client.validate(
291
+ agent_id=agent_id,
292
+ user=user,
293
+ input=input_text,
294
+ output=output,
295
+ framework=framework,
296
+ )
297
+ _check_decision(resp, block_on_block, False)
298
+ except (BlockedError, GatewayUnavailableError):
299
+ raise
300
+ except Exception as exc:
301
+ if SDK_CONFIG.failure_mode == "closed":
302
+ raise
303
+ logger.warning("[AgentTrust] AutoGen validate failed (non-fatal): %s", exc)
304
+
305
+
172
306
  class AgentTrustReplyHook:
173
307
  """
174
308
  Reply hook that validates every AutoGen agent reply via AgentTrust.
@@ -259,7 +393,7 @@ class AgentTrustReplyHook:
259
393
  if hasattr(result, "chat_message") and result.chat_message:
260
394
  content = getattr(result.chat_message, "content", "") or ""
261
395
  output = {"content": content[:4000]}
262
- _validate_sync(
396
+ await _avalidate(
263
397
  self._agent_id, self._base_url, self._api_key,
264
398
  self._user, content[:500], output, self._framework, self._block_on_block,
265
399
  )
@@ -47,7 +47,7 @@ from pathlib import Path
47
47
  logger = logging.getLogger(__name__)
48
48
 
49
49
  _VALID_FAILURE_MODES = frozenset({"open", "closed", "queue"})
50
- _SDK_VERSION = "0.0.7"
50
+ _SDK_VERSION = "0.0.8"
51
51
  _MIN_GATEWAY_VERSION = "0.0.1a1"
52
52
 
53
53
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentrust-py"
7
- version = "0.0.7"
7
+ version = "0.0.8"
8
8
  description = "AgentTrust Edge — Full Governance Platform. Policy Engine + MAD Classifier + HITL + Kill Switch."
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -43,6 +43,8 @@ langgraph = ["langgraph>=0.2.0,<2.0.0"]
43
43
  crewai = ["crewai>=1.0,<2.0"]
44
44
  # AG2 0.x keeps the ConversableAgent API (module `autogen`); 1.x is a new API
45
45
  ag2 = ["ag2>=0.9,<1.0"]
46
+ # Microsoft AutoGen 0.4+ (module autogen_agentchat)
47
+ autogen = ["autogen-agentchat>=0.4,<1.0"]
46
48
  openai = ["openai>=2.41.1,<3.0.0"]
47
49
  langchain = ["langchain>=0.2.0,<1.0.0"]
48
50
  # Retry/backoff (recommended for production)
@@ -0,0 +1,145 @@
1
+ """examples/ag2_quickstart.py: the calculator quickstart under governed_functions.
2
+
3
+ The example's own agents run with a scripted model against an embedded
4
+ gateway that has the example's manifest (add and multiply declared, divide
5
+ not).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import importlib.util
10
+ import json
11
+ from pathlib import Path
12
+
13
+ import pytest
14
+
15
+ pytest.importorskip("autogen")
16
+ pytestmark = pytest.mark.framework
17
+
18
+ from tests.adapters import _ag2
19
+ from tests.adapters._ag2 import final, tool_call
20
+
21
+ EXAMPLE = Path(__file__).resolve().parents[3] / "examples" / "ag2_quickstart.py"
22
+
23
+
24
+ @pytest.fixture
25
+ def quickstart():
26
+ if not EXAMPLE.exists():
27
+ pytest.skip("examples/ is not part of this checkout")
28
+ spec = importlib.util.spec_from_file_location("ag2_quickstart", EXAMPLE)
29
+ module = importlib.util.module_from_spec(spec)
30
+ spec.loader.exec_module(module)
31
+ return module
32
+
33
+
34
+ @pytest.fixture
35
+ def restore_model(monkeypatch):
36
+ """--scripted patches AG2's model call for good; undo that after the test."""
37
+ from autogen.oai.client import OpenAIWrapper
38
+
39
+ monkeypatch.setattr(OpenAIWrapper, "create", OpenAIWrapper.create)
40
+ monkeypatch.setattr(
41
+ OpenAIWrapper, "extract_text_or_completion_object", OpenAIWrapper.extract_text_or_completion_object,
42
+ )
43
+
44
+
45
+ @pytest.fixture
46
+ def gateway(embedded_gw, quickstart):
47
+ return embedded_gw(manifests={quickstart.AGENT_ID: quickstart.MANIFEST})
48
+
49
+
50
+ def _ledger(gateway):
51
+ """The gateway's decisions for the example's agent, oldest first: phase, tool, decision, envelope, parent."""
52
+ import httpx
53
+
54
+ resp = httpx.get(f"{gateway.url}/v1/audit/executions", params={"limit": 100},
55
+ headers={"Authorization": f"Bearer {gateway._token}"})
56
+ rows = sorted(resp.json()["items"], key=lambda r: r["id"])
57
+ return [[r["phase"], json.loads(r["request_json"] or "{}").get("metadata", {}).get("action", "-"),
58
+ r["decision"], r["envelope_id"][:8], (r["parent_envelope_id"] or "-")[:8]] for r in rows]
59
+
60
+
61
+ def _ask(quickstart, request):
62
+ return quickstart.ask(request, quickstart.llm_config(scripted=True))
63
+
64
+
65
+ def test_declared_tool_runs_and_both_checks_are_recorded(quickstart, gateway, monkeypatch):
66
+ model = _ag2.install(monkeypatch, tool_call("add", {"a": 3, "b": 4}), final("3 + 4 = 7. DONE"))
67
+
68
+ assert "3 + 4 = 7" in _ask(quickstart, "Add 3 and 4.")
69
+
70
+ assert any('"7"' in p or "7" in p.split("tool")[-1] for p in model.prompts[1:])
71
+ pre, post = _ledger(gateway)
72
+ assert pre[:3] == ["pre_check", "add", "approve"]
73
+ assert post[:3] == ["post_check", "add", "approve"]
74
+ assert post[4] == pre[3] # the post-check row points at its pre-check
75
+
76
+
77
+ def test_undeclared_tool_is_refused_before_it_runs(quickstart, gateway, monkeypatch):
78
+ ran = []
79
+
80
+ def divide(a: int, b: int) -> float:
81
+ """Divide `a` by `b`."""
82
+ ran.append((a, b))
83
+ return a / b
84
+
85
+ from agentrust_sdk.adapters.autogen import governed_function
86
+
87
+ monkeypatch.setattr(quickstart, "tools", [governed_function(divide, agent_id=quickstart.AGENT_ID)])
88
+ model = _ag2.install(monkeypatch, tool_call("divide", {"a": 10, "b": 2}), final("I can't divide. DONE"))
89
+
90
+ assert "I can't divide." in _ask(quickstart, "Divide 10 by 2.") # the chat still finished
91
+
92
+ assert ran == []
93
+ refusal = [p for p in model.prompts if "Blocked by AgentTrust" in p]
94
+ assert refusal and "'divide' is not in this agent's declared_tools" in refusal[-1]
95
+ [pre] = _ledger(gateway)
96
+ assert pre[:3] == ["pre_check", "divide", "block"]
97
+
98
+
99
+ def test_agentrust_off_lets_every_tool_run(quickstart, monkeypatch):
100
+ monkeypatch.setenv("AGENTRUST_ENABLED", "false")
101
+ model = _ag2.install(monkeypatch, tool_call("divide", {"a": 10, "b": 2}), final("10 / 2 = 5. DONE"))
102
+
103
+ assert "10 / 2 = 5" in _ask(quickstart, "Divide 10 by 2.")
104
+
105
+ assert any("5.0" in p for p in model.prompts[1:])
106
+ assert not any("Blocked by AgentTrust" in p for p in model.prompts)
107
+
108
+
109
+ def test_scripted_flag_runs_the_whole_quickstart_with_no_model(
110
+ quickstart, gateway, restore_model, capsys, monkeypatch,
111
+ ):
112
+ # --console talks to the gateway the fixture started (embed_gateway keeps one per process).
113
+ monkeypatch.setenv("AGENTRUST_AGENT_ID", quickstart.AGENT_ID)
114
+ monkeypatch.delenv("OPENAI_API_KEY", raising=False)
115
+
116
+ assert quickstart.main(["--console", "--scripted"]) == 0
117
+
118
+ out = capsys.readouterr().out
119
+ assert "model scripted" in out
120
+ assert "Result: 3 + 4 = 7." in out
121
+ assert "Result: That is everything you asked for." in out
122
+ assert [r[:3] for r in _ledger(gateway)] == [
123
+ ["pre_check", "add", "approve"], ["post_check", "add", "approve"], ["pre_check", "divide", "block"],
124
+ ]
125
+
126
+
127
+ def test_without_agentrust_flag_runs_with_agentrust_off(quickstart, restore_model, capsys, monkeypatch):
128
+ monkeypatch.setenv("AGENTRUST_ENABLED", "true") # main() turns it off; monkeypatch restores it
129
+
130
+ assert quickstart.main(["--without-agentrust", "--scripted"]) == 0
131
+
132
+ out = capsys.readouterr().out
133
+ assert "AgentTrust: off" in out
134
+ assert "Blocked by AgentTrust" not in out
135
+
136
+
137
+ def test_console_flag_names_the_missing_settings(quickstart, monkeypatch, capsys):
138
+ for name in ("AGENTRUST_GATEWAY_URL", "AGENTRUST_KEY", "AGENTRUST_AGENT_ID"):
139
+ monkeypatch.delenv(name, raising=False)
140
+
141
+ assert quickstart.main(["--console"]) == 2
142
+
143
+ err = capsys.readouterr().err
144
+ assert "AGENTRUST_GATEWAY_URL, AGENTRUST_KEY, AGENTRUST_AGENT_ID" in err
145
+ assert "--local" in err
@@ -0,0 +1,138 @@
1
+ """examples/autogen_quickstart.py: the calculator quickstart under governed_tools (AutoGen 0.4+).
2
+
3
+ The example's own agent runs with a scripted model against an embedded
4
+ gateway that has the example's manifest (add and multiply declared, divide
5
+ not).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import importlib.util
11
+ import json
12
+ from pathlib import Path
13
+
14
+ import pytest
15
+
16
+ pytest.importorskip("autogen_agentchat")
17
+ pytest.importorskip("autogen_ext")
18
+ pytestmark = pytest.mark.framework
19
+
20
+ EXAMPLE = Path(__file__).resolve().parents[3] / "examples" / "autogen_quickstart.py"
21
+
22
+
23
+ @pytest.fixture
24
+ def quickstart():
25
+ if not EXAMPLE.exists():
26
+ pytest.skip("examples/ is not part of this checkout")
27
+ spec = importlib.util.spec_from_file_location("autogen_quickstart", EXAMPLE)
28
+ module = importlib.util.module_from_spec(spec)
29
+ spec.loader.exec_module(module)
30
+ return module
31
+
32
+
33
+ @pytest.fixture
34
+ def gateway(embedded_gw, quickstart):
35
+ return embedded_gw(manifests={quickstart.AGENT_ID: quickstart.MANIFEST})
36
+
37
+
38
+ def _ledger(gateway):
39
+ """The gateway's decisions for the example's agent, oldest first: phase, tool, decision, envelope, parent."""
40
+ import httpx
41
+
42
+ resp = httpx.get(f"{gateway.url}/v1/audit/executions", params={"limit": 100},
43
+ headers={"Authorization": f"Bearer {gateway._token}"})
44
+ rows = sorted(resp.json()["items"], key=lambda r: r["id"])
45
+ return [[r["phase"], json.loads(r["request_json"] or "{}").get("metadata", {}).get("action", "-"),
46
+ r["decision"], r["envelope_id"][:8], (r["parent_envelope_id"] or "-")[:8]] for r in rows]
47
+
48
+
49
+ def _run(quickstart, request):
50
+ agent = quickstart.build_agent(quickstart.scripted_client())
51
+ return asyncio.run(agent.run(task=request))
52
+
53
+
54
+ def _tool_results(result):
55
+ return [r.content for m in result.messages if m.type == "ToolCallExecutionEvent" for r in m.content]
56
+
57
+
58
+ def test_declared_tool_runs_and_both_checks_are_recorded(quickstart, gateway):
59
+ result = _run(quickstart, "Add 3 and 4.")
60
+
61
+ assert "3 + 4 = 7" in result.messages[-1].content
62
+ assert _tool_results(result) == ["7"]
63
+ pre, post = _ledger(gateway)
64
+ assert pre[:3] == ["pre_check", "add", "approve"]
65
+ assert post[:3] == ["post_check", "add", "approve"]
66
+ assert post[4] == pre[3] # the post-check row points at its pre-check
67
+
68
+
69
+ def test_undeclared_tool_is_refused_before_it_runs(quickstart, gateway, monkeypatch):
70
+ ran = []
71
+ inner = quickstart.tools[2]._inner # divide, behind the AgentTrust wrapper
72
+ original = inner.run
73
+
74
+ async def spy(args, token):
75
+ ran.append(args)
76
+ return await original(args, token)
77
+
78
+ monkeypatch.setattr(inner, "run", spy)
79
+ client = quickstart.scripted_client()
80
+ asyncio.run(quickstart.build_agent(client).run(task="Add 3 and 4.")) # consume the first script pair
81
+ ledger_before = len(_ledger(gateway))
82
+
83
+ result = asyncio.run(quickstart.build_agent(client).run(task="Divide 10 by 2."))
84
+
85
+ assert ran == []
86
+ assert "That is everything you asked for." in result.messages[-1].content # the run still finished
87
+ [refusal] = _tool_results(result)
88
+ assert "Blocked by AgentTrust" in refusal and "'divide' is not in this agent's declared_tools" in refusal
89
+ assert _ledger(gateway)[ledger_before][:3] == ["pre_check", "divide", "block"]
90
+
91
+
92
+ def test_agentrust_off_lets_every_tool_run(quickstart, monkeypatch):
93
+ monkeypatch.setenv("AGENTRUST_ENABLED", "false")
94
+ client = quickstart.scripted_client()
95
+ asyncio.run(quickstart.build_agent(client).run(task="Add 3 and 4."))
96
+
97
+ result = asyncio.run(quickstart.build_agent(client).run(task="Divide 10 by 2."))
98
+
99
+ assert _tool_results(result) == ["5.0"]
100
+
101
+
102
+ def test_scripted_flag_runs_the_whole_quickstart_with_no_model(
103
+ quickstart, gateway, capsys, monkeypatch,
104
+ ):
105
+ # --console talks to the gateway the fixture started (embed_gateway keeps one per process).
106
+ monkeypatch.setenv("AGENTRUST_AGENT_ID", quickstart.AGENT_ID)
107
+ monkeypatch.delenv("OPENAI_API_KEY", raising=False)
108
+
109
+ assert quickstart.main(["--console", "--scripted"]) == 0
110
+
111
+ out = capsys.readouterr().out
112
+ assert "model scripted" in out
113
+ assert "Result: 3 + 4 = 7." in out
114
+ assert "Result: That is everything you asked for." in out
115
+ assert [r[:3] for r in _ledger(gateway)] == [
116
+ ["pre_check", "add", "approve"], ["post_check", "add", "approve"], ["pre_check", "divide", "block"],
117
+ ]
118
+
119
+
120
+ def test_without_agentrust_flag_runs_with_agentrust_off(quickstart, capsys, monkeypatch):
121
+ monkeypatch.setenv("AGENTRUST_ENABLED", "true") # main() turns it off; monkeypatch restores it
122
+
123
+ assert quickstart.main(["--without-agentrust", "--scripted"]) == 0
124
+
125
+ out = capsys.readouterr().out
126
+ assert "AgentTrust: off" in out
127
+ assert "divide returned: 5.0" in out
128
+
129
+
130
+ def test_console_flag_names_the_missing_settings(quickstart, monkeypatch, capsys):
131
+ for name in ("AGENTRUST_GATEWAY_URL", "AGENTRUST_KEY", "AGENTRUST_AGENT_ID"):
132
+ monkeypatch.delenv(name, raising=False)
133
+
134
+ assert quickstart.main(["--console"]) == 2
135
+
136
+ err = capsys.readouterr().err
137
+ assert "AGENTRUST_GATEWAY_URL, AGENTRUST_KEY, AGENTRUST_AGENT_ID" in err
138
+ assert "--local" in err
@@ -0,0 +1,243 @@
1
+ """AutoGen 0.4+ (autogen-agentchat): governed_tool and the async reply hook."""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+
6
+ import httpx
7
+ import pytest
8
+
9
+ pytest.importorskip("autogen_agentchat")
10
+ pytest.importorskip("autogen_ext")
11
+ pytestmark = pytest.mark.framework
12
+
13
+ from autogen_agentchat.agents import AssistantAgent
14
+ from autogen_core import CancellationToken, FunctionCall
15
+ from autogen_core.models import CreateResult, RequestUsage
16
+ from autogen_core.tools import FunctionTool
17
+ from autogen_ext.models.replay import ReplayChatCompletionClient
18
+
19
+ from agentrust_sdk.adapters.autogen import AgentTrustReplyHook, governed_tool, governed_tools
20
+ from tests.test_governor import FakeAsyncClient, _gov, _resp
21
+
22
+ AGENT = "autogen-v04-agent"
23
+ TOKEN = CancellationToken()
24
+
25
+
26
+ def _refund_tool(calls):
27
+ async def refund(amount: float, reason: str = "") -> str:
28
+ """Refund an amount to the customer."""
29
+ calls.append(amount)
30
+ return f"refunded {amount}"
31
+
32
+ return FunctionTool(refund, description="Refund an amount to the customer.")
33
+
34
+
35
+ def _call(name, args):
36
+ import json
37
+
38
+ return CreateResult(
39
+ finish_reason="function_calls",
40
+ content=[FunctionCall(id="c1", name=name, arguments=json.dumps(args))],
41
+ usage=RequestUsage(prompt_tokens=0, completion_tokens=0),
42
+ cached=False,
43
+ )
44
+
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # Unit: a real Governor on a scripted async client
48
+ # ---------------------------------------------------------------------------
49
+
50
+ def test_wrapped_tool_keeps_name_description_and_schema():
51
+ inner = _refund_tool([])
52
+ wrapped = governed_tool(inner, agent_id="a", governor=_gov(FakeAsyncClient()))
53
+
54
+ assert wrapped.name == "refund"
55
+ assert wrapped.description == inner.description
56
+ assert wrapped.schema == inner.schema
57
+
58
+
59
+ def test_allowed_call_is_pre_checked_runs_and_is_post_checked():
60
+ calls, client = [], FakeAsyncClient()
61
+ wrapped = governed_tool(_refund_tool(calls), agent_id="a", governor=_gov(client))
62
+
63
+ result = asyncio.run(wrapped.run_json({"amount": 5, "reason": "late"}, TOKEN))
64
+
65
+ assert result == "refunded 5.0" and calls == [5.0]
66
+ [pre] = client.pre_calls
67
+ assert (pre["action"], pre["params"]) == ("refund", {"amount": 5.0, "reason": "late"})
68
+ assert len(client.post_calls) == 1
69
+
70
+
71
+ def test_blocked_call_does_not_run_and_returns_the_refusal_text():
72
+ calls = []
73
+ client = FakeAsyncClient(pre=[_resp("block", reason="over the limit")])
74
+ wrapped = governed_tool(_refund_tool(calls), agent_id="a", governor=_gov(client))
75
+
76
+ result = asyncio.run(wrapped.run_json({"amount": 9999}, TOKEN))
77
+
78
+ assert calls == [] and "over the limit" in result
79
+
80
+
81
+ def test_plain_function_is_accepted_and_keeps_its_docstring_as_description():
82
+ calls = []
83
+
84
+ async def lookup(order_id: str) -> str:
85
+ """Look up an order."""
86
+ calls.append(order_id)
87
+ return f"order {order_id}"
88
+
89
+ [wrapped] = governed_tools([lookup], agent_id="a", governor=_gov(FakeAsyncClient()))
90
+
91
+ assert wrapped.description == "Look up an order."
92
+ assert asyncio.run(wrapped.run_json({"order_id": "42"}, TOKEN)) == "order 42"
93
+
94
+
95
+ def test_sync_function_tool_runs_without_blocking_the_loop():
96
+ calls = []
97
+
98
+ def ping(host: str) -> str:
99
+ """Ping a host."""
100
+ calls.append(host)
101
+ return f"pong {host}"
102
+
103
+ [wrapped] = governed_tools([ping], agent_id="a", governor=_gov(FakeAsyncClient()))
104
+
105
+ assert asyncio.run(wrapped.run_json({"host": "h"}, TOKEN)) == "pong h"
106
+ assert calls == ["h"]
107
+
108
+
109
+ def test_kill_switch_off_runs_plainly(monkeypatch):
110
+ monkeypatch.setenv("AGENTRUST_ENABLED", "false")
111
+ calls, client = [], FakeAsyncClient()
112
+ wrapped = governed_tool(_refund_tool(calls), agent_id="a", governor=_gov(client))
113
+
114
+ assert asyncio.run(wrapped.run_json({"amount": 1}, TOKEN)) == "refunded 1.0"
115
+ assert client.pre_calls == []
116
+
117
+
118
+ def test_gateway_down_in_closed_mode_refuses():
119
+ from agentrust_sdk.client import GatewayUnavailableError
120
+
121
+ calls = []
122
+ client = FakeAsyncClient(pre_error=GatewayUnavailableError("down"))
123
+ wrapped = governed_tool(_refund_tool(calls), agent_id="a", governor=_gov(client))
124
+
125
+ assert "gateway unavailable" in asyncio.run(wrapped.run_json({"amount": 1}, TOKEN))
126
+ assert calls == []
127
+
128
+
129
+ # ---------------------------------------------------------------------------
130
+ # Real agent on an embedded gateway
131
+ # ---------------------------------------------------------------------------
132
+
133
+ def _ledger(gw):
134
+ rows = httpx.get(f"{gw.url}/v1/audit/executions", params={"limit": 100},
135
+ headers={"Authorization": f"Bearer {gw._token}"}).json()["items"]
136
+ return sorted(rows, key=lambda r: r["id"])
137
+
138
+
139
+ def _agent(tool, *replies):
140
+ model = ReplayChatCompletionClient(list(replies), model_info={
141
+ "vision": False, "function_calling": True, "json_output": False,
142
+ "family": "unknown", "structured_output": False,
143
+ })
144
+ return AssistantAgent("assistant", model_client=model, tools=[tool], reflect_on_tool_use=True)
145
+
146
+
147
+ def test_agent_runs_a_declared_tool_with_pre_and_post_check(embedded_gw):
148
+ gw = embedded_gw(manifests={AGENT: {"declared_tools": [{"name": "refund"}]}})
149
+ calls = []
150
+ agent = _agent(governed_tool(_refund_tool(calls), agent_id=AGENT),
151
+ _call("refund", {"amount": 5}), "All done.")
152
+
153
+ asyncio.run(agent.run(task="refund 5"))
154
+
155
+ assert calls == [5.0]
156
+ assert [(r["phase"], r["decision"]) for r in _ledger(gw)] == [
157
+ ("pre_check", "approve"), ("post_check", "approve"),
158
+ ]
159
+
160
+
161
+ def test_agent_gets_a_refusal_for_an_undeclared_tool_and_still_finishes(embedded_gw):
162
+ gw = embedded_gw(manifests={AGENT: {"declared_tools": [{"name": "calculator"}]}})
163
+ calls = []
164
+ agent = _agent(governed_tool(_refund_tool(calls), agent_id=AGENT),
165
+ _call("refund", {"amount": 5}), "Could not refund.")
166
+
167
+ result = asyncio.run(agent.run(task="refund 5"))
168
+
169
+ assert calls == []
170
+ assert "Could not refund." in result.messages[-1].content
171
+ first = _ledger(gw)[0]
172
+ assert (first["phase"], first["decision"]) == ("pre_check", "block")
173
+ assert any("declared_tools" in str(m.content) for m in result.messages)
174
+
175
+
176
+ # ---------------------------------------------------------------------------
177
+ # attach_v4: async validate, fails closed
178
+ # ---------------------------------------------------------------------------
179
+
180
+ class _AsyncClient:
181
+ outcome = "approve"
182
+ error: Exception | None = None
183
+ calls: list[dict] = []
184
+
185
+ def __init__(self, *a, **kw):
186
+ pass
187
+
188
+ async def __aenter__(self):
189
+ return self
190
+
191
+ async def __aexit__(self, *a):
192
+ return False
193
+
194
+ async def validate(self, **kw):
195
+ type(self).calls.append(kw)
196
+ if type(self).error:
197
+ raise type(self).error
198
+ return _resp(type(self).outcome, reason="not allowed")
199
+
200
+
201
+ @pytest.fixture
202
+ def aclient(monkeypatch):
203
+ _AsyncClient.outcome, _AsyncClient.error, _AsyncClient.calls = "approve", None, []
204
+ monkeypatch.setattr("agentrust_sdk.client.AsyncAgentTrustClient", _AsyncClient)
205
+ return _AsyncClient
206
+
207
+
208
+ def _plain_agent(*replies):
209
+ model = ReplayChatCompletionClient(list(replies))
210
+ return AssistantAgent("assistant", model_client=model)
211
+
212
+
213
+ def test_attach_v4_validates_the_reply_with_the_async_client(aclient):
214
+ agent = _plain_agent("hello there")
215
+ AgentTrustReplyHook(agent_id="a").attach_v4(agent)
216
+
217
+ asyncio.run(agent.run(task="hi"))
218
+
219
+ [call] = aclient.calls
220
+ assert call["output"]["content"] == "hello there"
221
+
222
+
223
+ def test_attach_v4_raises_when_the_reply_is_blocked(aclient):
224
+ from agentrust_sdk.decorator import BlockedError
225
+
226
+ aclient.outcome = "block"
227
+ agent = _plain_agent("leak it")
228
+ AgentTrustReplyHook(agent_id="a").attach_v4(agent)
229
+
230
+ with pytest.raises(BlockedError):
231
+ asyncio.run(agent.run(task="hi"))
232
+
233
+
234
+ def test_attach_v4_fails_closed_when_the_gateway_is_down(aclient, monkeypatch):
235
+ from agentrust_sdk.client import GatewayUnavailableError
236
+
237
+ monkeypatch.setenv("AGENTRUST_FAILURE_MODE", "closed")
238
+ aclient.error = GatewayUnavailableError("down")
239
+ agent = _plain_agent("hello")
240
+ AgentTrustReplyHook(agent_id="a").attach_v4(agent)
241
+
242
+ with pytest.raises(GatewayUnavailableError):
243
+ asyncio.run(agent.run(task="hi"))
@@ -152,7 +152,7 @@ class TestAnswerFields:
152
152
  # Release hygiene: one version everywhere, and the right install name
153
153
  # ---------------------------------------------------------------------------
154
154
  REPO = Path(__file__).resolve().parents[2]
155
- RELEASE = "0.0.7"
155
+ RELEASE = "0.0.8"
156
156
 
157
157
 
158
158
  class TestReleaseHygiene:
File without changes
File without changes
File without changes
File without changes
File without changes