agentrust-py 0.0.6__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 (112) hide show
  1. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/PKG-INFO +5 -1
  2. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/__init__.py +11 -2
  3. agentrust_py-0.0.8/agentrust_sdk/adapters/autogen.py +457 -0
  4. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/config.py +1 -1
  5. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/decorator.py +12 -1
  6. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/manifest.py +1 -1
  7. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/pyproject.toml +5 -1
  8. agentrust_py-0.0.8/tests/adapters/_ag2.py +57 -0
  9. agentrust_py-0.0.8/tests/adapters/test_ag2_governed_function.py +232 -0
  10. agentrust_py-0.0.8/tests/adapters/test_ag2_quickstart_example.py +145 -0
  11. agentrust_py-0.0.8/tests/adapters/test_ag2_reply_hook.py +157 -0
  12. agentrust_py-0.0.8/tests/adapters/test_ag2_research_team_example.py +122 -0
  13. agentrust_py-0.0.8/tests/adapters/test_autogen_quickstart_example.py +138 -0
  14. agentrust_py-0.0.8/tests/adapters/test_autogen_v04_governed_tool.py +243 -0
  15. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_callback_url.py +1 -1
  16. agentrust_py-0.0.6/agentrust_sdk/adapters/autogen.py +0 -235
  17. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/.gitignore +0 -0
  18. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/CHANGELOG.md +0 -0
  19. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/LICENSE +0 -0
  20. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/README.md +0 -0
  21. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust/__init__.py +0 -0
  22. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/_evaluations.py +0 -0
  23. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/_real_validator.py +0 -0
  24. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/adapters/__init__.py +0 -0
  25. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/adapters/_core.py +0 -0
  26. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/adapters/claude_agents.py +0 -0
  27. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/adapters/crewai.py +0 -0
  28. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/adapters/langgraph.py +0 -0
  29. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/adapters/mcp.py +0 -0
  30. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/adapters/openai_agents.py +0 -0
  31. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/audit/__init__.py +0 -0
  32. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/audit/logger.py +0 -0
  33. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/auth.py +0 -0
  34. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/auto.py +0 -0
  35. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/autoload.py +0 -0
  36. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/classifier/__init__.py +0 -0
  37. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/classifier/engine.py +0 -0
  38. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/classifier/profiles.py +0 -0
  39. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/classifier/system_prompt.md +0 -0
  40. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/cli.py +0 -0
  41. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/cli_certify.py +0 -0
  42. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/client.py +0 -0
  43. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/embedded.py +0 -0
  44. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/golden_suite.py +0 -0
  45. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/governance/__init__.py +0 -0
  46. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/governance/hitl.py +0 -0
  47. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/governance/kernel.py +0 -0
  48. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/governance/kill_switch.py +0 -0
  49. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/governance/modes.py +0 -0
  50. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/hooks.py +0 -0
  51. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/models.py +0 -0
  52. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/notifications/__init__.py +0 -0
  53. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/notifications/discord.py +0 -0
  54. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/notifications/dispatcher.py +0 -0
  55. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/notifications/slack.py +0 -0
  56. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/policy/__init__.py +0 -0
  57. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/policy/backends/__init__.py +0 -0
  58. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/policy/backends/native.py +0 -0
  59. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/policy/backends/opa.py +0 -0
  60. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/policy/engine.py +0 -0
  61. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/policy/loader.py +0 -0
  62. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/py.typed +0 -0
  63. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/queue_replay.py +0 -0
  64. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/tiers.py +0 -0
  65. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/tool_tracking.py +0 -0
  66. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/version_negotiation.py +0 -0
  67. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/agentrust_sdk/webhooks.py +0 -0
  68. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/policy/base.yaml +0 -0
  69. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/policy/default-policy.json +0 -0
  70. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/policy/financial.yaml +0 -0
  71. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/policy/medical.yaml +0 -0
  72. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/policy/profiles/advisory.json +0 -0
  73. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/policy/profiles/balanced.json +0 -0
  74. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/policy/profiles/strict.json +0 -0
  75. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/__init__.py +0 -0
  76. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/_cfg.py +0 -0
  77. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/__init__.py +0 -0
  78. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/_crews.py +0 -0
  79. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/_graphs.py +0 -0
  80. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_crewai_baseline.py +0 -0
  81. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_crewai_failure_modes.py +0 -0
  82. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_crewai_governed_tools.py +0 -0
  83. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_crewai_holds_and_chain.py +0 -0
  84. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_crewai_quickstart_example.py +0 -0
  85. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_crewai_task_checks.py +0 -0
  86. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_baseline.py +0 -0
  87. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_failure_modes.py +0 -0
  88. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_governed_tools.py +0 -0
  89. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_holds_and_chain.py +0 -0
  90. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_node.py +0 -0
  91. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/adapters/test_langgraph_quickstart_example.py +0 -0
  92. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/conformance/__init__.py +0 -0
  93. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/conformance/bindings.py +0 -0
  94. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/conformance/test_c1_c10.py +0 -0
  95. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/conftest.py +0 -0
  96. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_adapter_tier_gate.py +0 -0
  97. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_adapters.py +0 -0
  98. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_async_advanced.py +0 -0
  99. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_chaos.py +0 -0
  100. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_cli_certify.py +0 -0
  101. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_client.py +0 -0
  102. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_client_adapter_contract.py +0 -0
  103. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_config.py +0 -0
  104. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_decorator.py +0 -0
  105. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_embedded.py +0 -0
  106. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_embedded_adapter_contract.py +0 -0
  107. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_golden_suite.py +0 -0
  108. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_governor.py +0 -0
  109. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_hooks.py +0 -0
  110. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_manifest.py +0 -0
  111. {agentrust_py-0.0.6 → agentrust_py-0.0.8}/tests/test_policy_packaging.py +0 -0
  112. {agentrust_py-0.0.6 → 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.6
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
@@ -23,6 +23,10 @@ Requires-Python: >=3.10
23
23
  Requires-Dist: httpx<1.0.0,>=0.27.0
24
24
  Requires-Dist: pydantic<3.0.0,>=2.0.0
25
25
  Requires-Dist: pyyaml<7.0,>=6.0.3
26
+ Provides-Extra: ag2
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'
26
30
  Provides-Extra: autoload
27
31
  Provides-Extra: crewai
28
32
  Requires-Dist: crewai<2.0,>=1.0; extra == 'crewai'
@@ -38,7 +38,14 @@ from .client import (
38
38
  from .config import SDK_CONFIG
39
39
  from .hooks import auto_instrument, auto_wrap
40
40
  from .adapters.mcp import mcp_tool_guard, MCPServerGuard
41
- from .adapters.autogen import AgentTrustReplyHook, AgentTrustGroupChatMonitor
41
+ from .adapters.autogen import (
42
+ AgentTrustGroupChatMonitor,
43
+ AgentTrustReplyHook,
44
+ governed_function,
45
+ governed_functions,
46
+ governed_tool as governed_autogen_tool,
47
+ governed_tools as governed_autogen_tools,
48
+ )
42
49
  from .adapters.claude_agents import ClaudeAgentGuard
43
50
  from .adapters.openai_agents import agent_guard as openai_agent_guard, OpenAIAgentsGuard, SwarmGuard
44
51
  from .adapters.crewai import AgentTrustCallback
@@ -87,6 +94,8 @@ from .webhooks import (
87
94
  __all__ = [
88
95
  # Primary API
89
96
  "harness",
97
+ "governed_function",
98
+ "governed_functions",
90
99
  # Embedded gateway (zero-dep plug-and-play)
91
100
  "embed_gateway",
92
101
  "EmbeddedGateway",
@@ -147,4 +156,4 @@ __all__ = [
147
156
  "validate",
148
157
  ]
149
158
 
150
- __version__ = "0.0.6"
159
+ __version__ = "0.0.8"
@@ -0,0 +1,457 @@
1
+ """
2
+ AutoGen adapter — AgentTrust governance for Microsoft AutoGen agents.
3
+
4
+ Architecture doc (two-pager): "Connects to … AutoGen agents."
5
+
6
+ Supports both AutoGen v0.2 (pyautogen) and AutoGen v0.4 (autogen-agentchat):
7
+
8
+ Pattern 0 — governed_function (AG2 / pyautogen 0.2 tools):
9
+ Wrap each tool before register_function so every call is pre-checked,
10
+ held for approval when needed, run, post-checked and audited. A refusal
11
+ is returned as the tool result, so the chat keeps going.
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
+
17
+ Pattern 1 — AgentTrustReplyHook (v0.2 + v0.4):
18
+ Attach to any ConversableAgent / AssistantAgent to validate every reply
19
+ before it is sent.
20
+
21
+ Pattern 2 — AgentTrustGroupChatMonitor (v0.2 GroupChat):
22
+ Wraps a GroupChat's speaker-selection to validate messages between agents.
23
+
24
+ Usage::
25
+
26
+ from autogen import AssistantAgent
27
+ from agentrust_sdk.adapters.autogen import AgentTrustReplyHook
28
+
29
+ hook = AgentTrustReplyHook(agent_id="research-team")
30
+ assistant = AssistantAgent("researcher", llm_config={...})
31
+ hook.attach(assistant)
32
+
33
+ Requires Team tier.
34
+ """
35
+ from __future__ import annotations
36
+
37
+ import functools
38
+ import inspect
39
+ import logging
40
+ import time
41
+ from typing import Any, Callable, Sequence
42
+
43
+ from ..tiers import Capability
44
+ from ._core import Allowed, Governor, OnBlock, Refused, check_adapter_tier
45
+
46
+ logger = logging.getLogger(__name__)
47
+
48
+ _FRAMEWORK = "ag2"
49
+ _FRAMEWORK_V04 = "autogen"
50
+
51
+
52
+ def governed_function(
53
+ fn: Callable[..., Any],
54
+ *,
55
+ agent_id: str,
56
+ name: str | None = None,
57
+ on_block: OnBlock = "refuse",
58
+ review_timeout_s: float = 300.0,
59
+ user: str = "ag2",
60
+ base_url: str | None = None,
61
+ api_key: str | None = None,
62
+ governor: Governor | None = None,
63
+ ) -> Callable[..., Any]:
64
+ """Wrap an AG2 tool function so AgentTrust governs every call.
65
+
66
+ The result keeps the name, docstring and signature of *fn*, so AG2 builds
67
+ the same tool schema from it. Pass it to ``register_function`` or
68
+ ``register_for_llm``. Async functions stay async.
69
+ """
70
+ check_adapter_tier(Capability.AUTOGEN_ADAPTER, api_key)
71
+ gov = governor or Governor(
72
+ agent_id,
73
+ framework=_FRAMEWORK,
74
+ on_block=on_block,
75
+ review_timeout_s=review_timeout_s,
76
+ user=user,
77
+ base_url=base_url,
78
+ api_key=api_key,
79
+ )
80
+ tool_name = name or fn.__name__
81
+ signature = inspect.signature(fn)
82
+
83
+ def call_args(args: tuple, kwargs: dict) -> dict[str, Any]:
84
+ bound = signature.bind(*args, **kwargs)
85
+ bound.apply_defaults()
86
+ return dict(bound.arguments)
87
+
88
+ def unwrap(outcome: Allowed | Refused) -> Any:
89
+ # The refusal text is the tool result the agent reads next.
90
+ return outcome.message if isinstance(outcome, Refused) else outcome.result
91
+
92
+ if inspect.iscoroutinefunction(fn):
93
+
94
+ @functools.wraps(fn)
95
+ async def governed(*args: Any, **kwargs: Any) -> Any:
96
+ params = call_args(args, kwargs)
97
+ outcome = await gov.aguard_tool(
98
+ tool_name, params, lambda: fn(*args, **kwargs), input=_describe_call(tool_name, params),
99
+ )
100
+ return unwrap(outcome)
101
+
102
+ else:
103
+
104
+ @functools.wraps(fn)
105
+ def governed(*args: Any, **kwargs: Any) -> Any:
106
+ params = call_args(args, kwargs)
107
+ outcome = gov.guard_tool(
108
+ tool_name, params, lambda: fn(*args, **kwargs), input=_describe_call(tool_name, params),
109
+ )
110
+ return unwrap(outcome)
111
+
112
+ return governed
113
+
114
+
115
+ def governed_functions(
116
+ fns: Sequence[Callable[..., Any]],
117
+ *,
118
+ agent_id: str,
119
+ on_block: OnBlock = "refuse",
120
+ review_timeout_s: float = 300.0,
121
+ user: str = "ag2",
122
+ base_url: str | None = None,
123
+ api_key: str | None = None,
124
+ governor: Governor | None = None,
125
+ ) -> list[Callable[..., Any]]:
126
+ """Wrap each function with :func:`governed_function`, sharing one Governor."""
127
+ check_adapter_tier(Capability.AUTOGEN_ADAPTER, api_key)
128
+ gov = governor or Governor(
129
+ agent_id,
130
+ framework=_FRAMEWORK,
131
+ on_block=on_block,
132
+ review_timeout_s=review_timeout_s,
133
+ user=user,
134
+ base_url=base_url,
135
+ api_key=api_key,
136
+ )
137
+ return [governed_function(fn, agent_id=agent_id, governor=gov) for fn in fns]
138
+
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
+
236
+ def _describe_call(name: str, args: dict[str, Any]) -> str:
237
+ """The proposed call as the pre-check's request input."""
238
+ return f"tool call {name}({', '.join(f'{k}={v!r}' for k, v in args.items())})"
239
+
240
+
241
+ def _validate_sync(
242
+ agent_id: str,
243
+ base_url: str,
244
+ api_key: str | None,
245
+ user: str,
246
+ input_text: str,
247
+ output: dict,
248
+ framework: str,
249
+ block_on_block: bool,
250
+ ) -> None:
251
+ from agentrust_sdk.client import AgentTrustClient, GatewayUnavailableError
252
+ from agentrust_sdk.config import SDK_CONFIG
253
+ from agentrust_sdk.decorator import _check_decision, BlockedError
254
+
255
+ try:
256
+ with AgentTrustClient(base_url=base_url, api_key=api_key) as client:
257
+ resp = client.validate(
258
+ agent_id=agent_id,
259
+ user=user,
260
+ input=input_text,
261
+ output=output,
262
+ framework=framework,
263
+ )
264
+ _check_decision(resp, block_on_block, False)
265
+ except (BlockedError, GatewayUnavailableError):
266
+ raise
267
+ except Exception as exc:
268
+ if SDK_CONFIG.failure_mode == "closed":
269
+ raise
270
+ logger.warning("[AgentTrust] AutoGen validate failed (non-fatal): %s", exc)
271
+
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
+
306
+ class AgentTrustReplyHook:
307
+ """
308
+ Reply hook that validates every AutoGen agent reply via AgentTrust.
309
+
310
+ Compatible with AutoGen v0.2 (pyautogen) reply_func mechanism and
311
+ AutoGen v0.4 (autogen-agentchat) message hook pattern.
312
+
313
+ Usage (v0.2)::
314
+
315
+ hook = AgentTrustReplyHook(agent_id="my-autogen")
316
+ assistant = AssistantAgent("researcher", llm_config={...})
317
+ hook.attach(assistant)
318
+
319
+ Usage (v0.4)::
320
+
321
+ hook = AgentTrustReplyHook(agent_id="my-autogen")
322
+ agent = AssistantAgent("researcher", model_client=..., tools=[...])
323
+ hook.attach_v4(agent)
324
+ """
325
+
326
+ def __init__(
327
+ self,
328
+ agent_id: str,
329
+ *,
330
+ base_url: str = "http://localhost:8000",
331
+ api_key: str | None = None,
332
+ user: str = "autogen",
333
+ block_on_block: bool = True,
334
+ framework: str = "AutoGen",
335
+ ) -> None:
336
+ check_adapter_tier(Capability.AUTOGEN_ADAPTER, api_key)
337
+ self._agent_id = agent_id
338
+ self._base_url = base_url
339
+ self._api_key = api_key
340
+ self._user = user
341
+ self._block_on_block = block_on_block
342
+ self._framework = framework
343
+
344
+ # ------------------------------------------------------------------
345
+ # AutoGen v0.2
346
+ # ------------------------------------------------------------------
347
+
348
+ def _check_outgoing(
349
+ self, sender: Any = None, message: Any = None, recipient: Any = None, silent: bool = False,
350
+ ) -> Any:
351
+ """AG2 ``process_message_before_send`` hook: check the agent's own reply."""
352
+ content = message.get("content") if isinstance(message, dict) else message
353
+ if not isinstance(content, str) or not content:
354
+ return message
355
+
356
+ t0 = time.perf_counter()
357
+ _validate_sync(
358
+ self._agent_id, self._base_url, self._api_key, self._user,
359
+ content[:500], {"content": content[:4000], "role": "assistant"},
360
+ self._framework, self._block_on_block,
361
+ )
362
+ logger.debug(
363
+ "[AgentTrust] AutoGen reply validated in %.1f ms", (time.perf_counter() - t0) * 1000
364
+ )
365
+ return message
366
+
367
+ def attach(self, agent: Any) -> None:
368
+ """Attach to an AG2 / AutoGen v0.2 ConversableAgent.
369
+
370
+ Checks every message the agent sends, before it leaves the agent.
371
+ """
372
+ if not hasattr(agent, "register_hook"):
373
+ raise TypeError(
374
+ f"Expected a ConversableAgent with register_hook(), got {type(agent).__name__}"
375
+ )
376
+ agent.register_hook("process_message_before_send", self._check_outgoing)
377
+ logger.info(
378
+ "[AgentTrust] AutoGen reply hook attached to agent '%s'", getattr(agent, "name", "?")
379
+ )
380
+
381
+ # ------------------------------------------------------------------
382
+ # AutoGen v0.4
383
+ # ------------------------------------------------------------------
384
+
385
+ def attach_v4(self, agent: Any) -> None:
386
+ """Attach to an AutoGen v0.4 BaseChatAgent via on_messages hook."""
387
+ original_on_messages = agent.on_messages
388
+
389
+ async def _wrapped_on_messages(messages: Any, cancellation_token: Any = None) -> Any:
390
+ result = await original_on_messages(messages, cancellation_token)
391
+ # result is a Response object with a .chat_message attribute
392
+ content = ""
393
+ if hasattr(result, "chat_message") and result.chat_message:
394
+ content = getattr(result.chat_message, "content", "") or ""
395
+ output = {"content": content[:4000]}
396
+ await _avalidate(
397
+ self._agent_id, self._base_url, self._api_key,
398
+ self._user, content[:500], output, self._framework, self._block_on_block,
399
+ )
400
+ return result
401
+
402
+ agent.on_messages = _wrapped_on_messages
403
+ logger.info(
404
+ "[AgentTrust] AutoGen v0.4 hook attached to agent '%s'",
405
+ getattr(agent, "name", "?"),
406
+ )
407
+
408
+
409
+ class AgentTrustGroupChatMonitor:
410
+ """
411
+ Monitors AutoGen GroupChat messages and validates each speaker's reply.
412
+
413
+ Usage::
414
+
415
+ from autogen import GroupChat, GroupChatManager
416
+ from agentrust_sdk.adapters.autogen import AgentTrustGroupChatMonitor
417
+
418
+ monitor = AgentTrustGroupChatMonitor(agent_id="planning-crew")
419
+ groupchat = GroupChat(agents=[...], messages=[])
420
+ manager = GroupChatManager(groupchat=groupchat)
421
+ monitor.attach(groupchat)
422
+ """
423
+
424
+ def __init__(
425
+ self,
426
+ agent_id: str,
427
+ *,
428
+ base_url: str = "http://localhost:8000",
429
+ api_key: str | None = None,
430
+ user: str = "autogen-group",
431
+ block_on_block: bool = False, # Default False — group chats tolerate review
432
+ framework: str = "AutoGen",
433
+ ) -> None:
434
+ check_adapter_tier(Capability.AUTOGEN_ADAPTER, api_key)
435
+ self._agent_id = agent_id
436
+ self._base_url = base_url
437
+ self._api_key = api_key
438
+ self._user = user
439
+ self._block_on_block = block_on_block
440
+ self._framework = framework
441
+
442
+ def attach(self, groupchat: Any) -> None:
443
+ """Patch groupchat.append to intercept every new message."""
444
+ original_append = groupchat.append
445
+
446
+ def _governed_append(message: dict, speaker: Any) -> None:
447
+ content = message.get("content", "") or ""
448
+ _validate_sync(
449
+ self._agent_id, self._base_url, self._api_key,
450
+ self._user, content[:500],
451
+ {"content": content[:4000], "speaker": getattr(speaker, "name", str(speaker))},
452
+ self._framework, self._block_on_block,
453
+ )
454
+ original_append(message, speaker)
455
+
456
+ groupchat.append = _governed_append
457
+ logger.info("[AgentTrust] AutoGen GroupChatMonitor attached")
@@ -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.6"
50
+ _SDK_VERSION = "0.0.8"
51
51
  _MIN_GATEWAY_VERSION = "0.0.1a1"
52
52
 
53
53
 
@@ -329,6 +329,17 @@ def _check_decision(resp: Any, block_on_block: bool, block_on_review: bool) -> N
329
329
  raise BlockedError(outcome, resp.decision.reason, resp.envelope_id)
330
330
 
331
331
 
332
+ def _ag2_installed() -> bool:
333
+ """AG2 installs the ``autogen`` module under the distribution name ``ag2``."""
334
+ from importlib.metadata import PackageNotFoundError, version
335
+
336
+ try:
337
+ version("ag2")
338
+ except PackageNotFoundError:
339
+ return False
340
+ return True
341
+
342
+
332
343
  def _detect_framework() -> str:
333
344
  """Best-effort framework detection from installed packages."""
334
345
  try:
@@ -341,7 +352,7 @@ def _detect_framework() -> str:
341
352
  ("llama_index", "LlamaIndex"),
342
353
  ]:
343
354
  if importlib.util.find_spec(name):
344
- return label
355
+ return "AG2" if name == "autogen" and _ag2_installed() else label
345
356
  except Exception:
346
357
  pass
347
358
  return "REST"
@@ -50,7 +50,7 @@ class CertifyManifest(BaseModel):
50
50
  display_name: str = ""
51
51
  framework: str = Field(
52
52
  default="rest",
53
- pattern=r"^(langchain|langgraph|crewai|autogen|openai-agents|mcp|rest)$",
53
+ pattern=r"^(langchain|langgraph|crewai|autogen|ag2|openai-agents|mcp|rest)$",
54
54
  )
55
55
  language: str = Field(default="python", pattern=r"^(python|typescript|rest)$")
56
56
  model: ModelDeclaration = Field(default_factory=ModelDeclaration)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentrust-py"
7
- version = "0.0.6"
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" }
@@ -41,6 +41,10 @@ llm = [
41
41
  # Framework adapters (optional, tier-gated)
42
42
  langgraph = ["langgraph>=0.2.0,<2.0.0"]
43
43
  crewai = ["crewai>=1.0,<2.0"]
44
+ # AG2 0.x keeps the ConversableAgent API (module `autogen`); 1.x is a new API
45
+ ag2 = ["ag2>=0.9,<1.0"]
46
+ # Microsoft AutoGen 0.4+ (module autogen_agentchat)
47
+ autogen = ["autogen-agentchat>=0.4,<1.0"]
44
48
  openai = ["openai>=2.41.1,<3.0.0"]
45
49
  langchain = ["langchain>=0.2.0,<1.0.0"]
46
50
  # Retry/backoff (recommended for production)
@@ -0,0 +1,57 @@
1
+ """A scripted chat model for real AG2 agents, shared by the AG2 binding tests."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from types import SimpleNamespace as NS
6
+ from typing import Any
7
+
8
+
9
+ def tool_call(name: str, args: dict[str, Any]) -> tuple[str, dict[str, Any]]:
10
+ """The reply in which the agent calls a tool."""
11
+ return name, args
12
+
13
+
14
+ def final(text: str = "done DONE") -> str:
15
+ return text
16
+
17
+
18
+ class ScriptedModel:
19
+ """Stands in for ``OpenAIWrapper.create``: replays *replies* in order."""
20
+
21
+ def __init__(self, *replies):
22
+ self.replies = list(replies)
23
+ self.prompts: list[str] = []
24
+
25
+ def create(self, wrapper, **params):
26
+ self.prompts.append(json.dumps(params.get("messages", []), default=str))
27
+ reply = self.replies.pop(0)
28
+ if isinstance(reply, str):
29
+ message = {"role": "assistant", "content": reply}
30
+ msg = NS(content=reply, tool_calls=None, function_call=None, role="assistant")
31
+ else:
32
+ name, args = reply
33
+ call = {"id": "call-1", "type": "function",
34
+ "function": {"name": name, "arguments": json.dumps(args)}}
35
+ message = {"role": "assistant", "content": None, "tool_calls": [call]}
36
+ msg = NS(content=None, tool_calls=[call], function_call=None, role="assistant")
37
+ msg.model_dump = lambda: message
38
+ return NS(
39
+ choices=[NS(message=msg, finish_reason="stop")], model="scripted", cost=0, id="r",
40
+ usage=NS(prompt_tokens=0, completion_tokens=0, total_tokens=0),
41
+ )
42
+
43
+ @staticmethod
44
+ def extract(response):
45
+ msg = response.choices[0].message
46
+ return [msg.model_dump()] if msg.tool_calls else [msg.content]
47
+
48
+
49
+ def install(monkeypatch, *replies) -> ScriptedModel:
50
+ from autogen.oai.client import OpenAIWrapper
51
+
52
+ model = ScriptedModel(*replies)
53
+ monkeypatch.setattr(OpenAIWrapper, "create", lambda self, **kw: model.create(self, **kw))
54
+ monkeypatch.setattr(
55
+ OpenAIWrapper, "extract_text_or_completion_object", classmethod(lambda cls, r: model.extract(r)),
56
+ )
57
+ return model