agent_os_kernel 3.1.0__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.
Files changed (337) hide show
  1. agent_control_plane/__init__.py +662 -0
  2. agent_control_plane/a2a_adapter.py +543 -0
  3. agent_control_plane/adapter.py +417 -0
  4. agent_control_plane/agent_hibernation.py +394 -0
  5. agent_control_plane/agent_kernel.py +470 -0
  6. agent_control_plane/compliance.py +720 -0
  7. agent_control_plane/constraint_graphs.py +478 -0
  8. agent_control_plane/control_plane.py +854 -0
  9. agent_control_plane/example_executors.py +195 -0
  10. agent_control_plane/execution_engine.py +231 -0
  11. agent_control_plane/flight_recorder.py +846 -0
  12. agent_control_plane/governance_layer.py +435 -0
  13. agent_control_plane/hf_utils.py +563 -0
  14. agent_control_plane/interfaces/__init__.py +55 -0
  15. agent_control_plane/interfaces/kernel_interface.py +361 -0
  16. agent_control_plane/interfaces/plugin_interface.py +497 -0
  17. agent_control_plane/interfaces/protocol_interfaces.py +387 -0
  18. agent_control_plane/kernel_space.py +1009 -0
  19. agent_control_plane/langchain_adapter.py +424 -0
  20. agent_control_plane/lifecycle.py +3113 -0
  21. agent_control_plane/mcp_adapter.py +653 -0
  22. agent_control_plane/ml_safety.py +563 -0
  23. agent_control_plane/multimodal.py +727 -0
  24. agent_control_plane/mute_agent.py +422 -0
  25. agent_control_plane/observability.py +787 -0
  26. agent_control_plane/orchestrator.py +482 -0
  27. agent_control_plane/plugin_registry.py +750 -0
  28. agent_control_plane/policy_engine.py +954 -0
  29. agent_control_plane/process_isolation.py +777 -0
  30. agent_control_plane/shadow_mode.py +310 -0
  31. agent_control_plane/signals.py +493 -0
  32. agent_control_plane/supervisor_agents.py +430 -0
  33. agent_control_plane/time_travel_debugger.py +557 -0
  34. agent_control_plane/tool_registry.py +452 -0
  35. agent_control_plane/vfs.py +697 -0
  36. agent_kernel/__init__.py +69 -0
  37. agent_kernel/analyzer.py +435 -0
  38. agent_kernel/auditor.py +36 -0
  39. agent_kernel/completeness_auditor.py +237 -0
  40. agent_kernel/detector.py +203 -0
  41. agent_kernel/kernel.py +744 -0
  42. agent_kernel/memory_manager.py +85 -0
  43. agent_kernel/models.py +374 -0
  44. agent_kernel/nudge_mechanism.py +263 -0
  45. agent_kernel/outcome_analyzer.py +338 -0
  46. agent_kernel/patcher.py +582 -0
  47. agent_kernel/semantic_analyzer.py +316 -0
  48. agent_kernel/semantic_purge.py +349 -0
  49. agent_kernel/simulator.py +449 -0
  50. agent_kernel/teacher.py +85 -0
  51. agent_kernel/triage.py +152 -0
  52. agent_os/__init__.py +409 -0
  53. agent_os/_adversarial_impl.py +200 -0
  54. agent_os/_circuit_breaker_impl.py +232 -0
  55. agent_os/_mcp_metrics.py +193 -0
  56. agent_os/adversarial.py +20 -0
  57. agent_os/agents_compat.py +490 -0
  58. agent_os/audit_logger.py +135 -0
  59. agent_os/base_agent.py +651 -0
  60. agent_os/circuit_breaker.py +34 -0
  61. agent_os/cli/__init__.py +659 -0
  62. agent_os/cli/cmd_audit.py +128 -0
  63. agent_os/cli/cmd_init.py +152 -0
  64. agent_os/cli/cmd_policy.py +41 -0
  65. agent_os/cli/cmd_policy_gen.py +180 -0
  66. agent_os/cli/cmd_validate.py +258 -0
  67. agent_os/cli/mcp_scan.py +265 -0
  68. agent_os/cli/output.py +192 -0
  69. agent_os/cli/policy_checker.py +330 -0
  70. agent_os/compat.py +74 -0
  71. agent_os/constraint_graph.py +234 -0
  72. agent_os/content_governance.py +140 -0
  73. agent_os/context_budget.py +305 -0
  74. agent_os/credential_redactor.py +224 -0
  75. agent_os/diff_policy.py +89 -0
  76. agent_os/egress_policy.py +159 -0
  77. agent_os/escalation.py +276 -0
  78. agent_os/event_bus.py +124 -0
  79. agent_os/exceptions.py +180 -0
  80. agent_os/execution_context_policy.py +141 -0
  81. agent_os/github_enterprise.py +96 -0
  82. agent_os/health.py +20 -0
  83. agent_os/integrations/__init__.py +279 -0
  84. agent_os/integrations/a2a_adapter.py +279 -0
  85. agent_os/integrations/agent_lightning/__init__.py +30 -0
  86. agent_os/integrations/anthropic_adapter.py +420 -0
  87. agent_os/integrations/autogen_adapter.py +620 -0
  88. agent_os/integrations/base.py +1137 -0
  89. agent_os/integrations/compat.py +229 -0
  90. agent_os/integrations/config.py +98 -0
  91. agent_os/integrations/conversation_guardian.py +957 -0
  92. agent_os/integrations/crewai_adapter.py +467 -0
  93. agent_os/integrations/drift_detector.py +425 -0
  94. agent_os/integrations/dry_run.py +124 -0
  95. agent_os/integrations/escalation.py +582 -0
  96. agent_os/integrations/gemini_adapter.py +364 -0
  97. agent_os/integrations/google_adk_adapter.py +633 -0
  98. agent_os/integrations/guardrails_adapter.py +394 -0
  99. agent_os/integrations/health.py +197 -0
  100. agent_os/integrations/langchain_adapter.py +654 -0
  101. agent_os/integrations/llamafirewall.py +343 -0
  102. agent_os/integrations/llamaindex_adapter.py +188 -0
  103. agent_os/integrations/logging.py +191 -0
  104. agent_os/integrations/maf_adapter.py +631 -0
  105. agent_os/integrations/mistral_adapter.py +365 -0
  106. agent_os/integrations/openai_adapter.py +816 -0
  107. agent_os/integrations/openai_agents_sdk.py +406 -0
  108. agent_os/integrations/policy_compose.py +171 -0
  109. agent_os/integrations/profiling.py +144 -0
  110. agent_os/integrations/pydantic_ai_adapter.py +420 -0
  111. agent_os/integrations/rate_limiter.py +130 -0
  112. agent_os/integrations/rbac.py +143 -0
  113. agent_os/integrations/registry.py +113 -0
  114. agent_os/integrations/scope_guard.py +303 -0
  115. agent_os/integrations/semantic_kernel_adapter.py +769 -0
  116. agent_os/integrations/smolagents_adapter.py +629 -0
  117. agent_os/integrations/templates.py +178 -0
  118. agent_os/integrations/token_budget.py +134 -0
  119. agent_os/integrations/tool_aliases.py +190 -0
  120. agent_os/integrations/webhooks.py +177 -0
  121. agent_os/lite.py +208 -0
  122. agent_os/mcp_gateway.py +385 -0
  123. agent_os/mcp_message_signer.py +273 -0
  124. agent_os/mcp_protocols.py +161 -0
  125. agent_os/mcp_response_scanner.py +232 -0
  126. agent_os/mcp_security.py +924 -0
  127. agent_os/mcp_session_auth.py +231 -0
  128. agent_os/mcp_sliding_rate_limiter.py +184 -0
  129. agent_os/memory_guard.py +409 -0
  130. agent_os/metrics.py +134 -0
  131. agent_os/mute.py +428 -0
  132. agent_os/mute_agent.py +209 -0
  133. agent_os/policies/__init__.py +77 -0
  134. agent_os/policies/async_evaluator.py +275 -0
  135. agent_os/policies/backends.py +670 -0
  136. agent_os/policies/bridge.py +169 -0
  137. agent_os/policies/budget.py +85 -0
  138. agent_os/policies/cli.py +294 -0
  139. agent_os/policies/conflict_resolution.py +270 -0
  140. agent_os/policies/data_classification.py +252 -0
  141. agent_os/policies/evaluator.py +239 -0
  142. agent_os/policies/policy_schema.json +228 -0
  143. agent_os/policies/rate_limiting.py +145 -0
  144. agent_os/policies/schema.py +115 -0
  145. agent_os/policies/shared.py +331 -0
  146. agent_os/prompt_injection.py +694 -0
  147. agent_os/providers.py +182 -0
  148. agent_os/py.typed +0 -0
  149. agent_os/retry.py +81 -0
  150. agent_os/reversibility.py +251 -0
  151. agent_os/sandbox.py +432 -0
  152. agent_os/sandbox_provider.py +140 -0
  153. agent_os/secure_codegen.py +525 -0
  154. agent_os/security_skills.py +538 -0
  155. agent_os/semantic_policy.py +422 -0
  156. agent_os/server/__init__.py +15 -0
  157. agent_os/server/__main__.py +25 -0
  158. agent_os/server/app.py +277 -0
  159. agent_os/server/models.py +104 -0
  160. agent_os/shift_left_metrics.py +130 -0
  161. agent_os/stateless.py +742 -0
  162. agent_os/supervisor.py +148 -0
  163. agent_os/task_outcome.py +148 -0
  164. agent_os/transparency.py +181 -0
  165. agent_os/trust_root.py +128 -0
  166. agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
  167. agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
  168. agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
  169. agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
  170. agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
  171. agent_os_observability/__init__.py +27 -0
  172. agent_os_observability/dashboards.py +898 -0
  173. agent_os_observability/metrics.py +398 -0
  174. agent_os_observability/server.py +223 -0
  175. agent_os_observability/tracer.py +232 -0
  176. agent_primitives/__init__.py +24 -0
  177. agent_primitives/failures.py +84 -0
  178. agent_primitives/py.typed +0 -0
  179. amb_core/__init__.py +177 -0
  180. amb_core/adapters/__init__.py +57 -0
  181. amb_core/adapters/aws_sqs_broker.py +376 -0
  182. amb_core/adapters/azure_servicebus_broker.py +340 -0
  183. amb_core/adapters/kafka_broker.py +260 -0
  184. amb_core/adapters/nats_broker.py +285 -0
  185. amb_core/adapters/rabbitmq_broker.py +235 -0
  186. amb_core/adapters/redis_broker.py +262 -0
  187. amb_core/broker.py +145 -0
  188. amb_core/bus.py +481 -0
  189. amb_core/cloudevents.py +509 -0
  190. amb_core/dlq.py +345 -0
  191. amb_core/hf_utils.py +536 -0
  192. amb_core/memory_broker.py +410 -0
  193. amb_core/models.py +141 -0
  194. amb_core/persistence.py +529 -0
  195. amb_core/schema.py +294 -0
  196. amb_core/tracing.py +358 -0
  197. atr/__init__.py +640 -0
  198. atr/access.py +348 -0
  199. atr/composition.py +645 -0
  200. atr/decorator.py +357 -0
  201. atr/executor.py +384 -0
  202. atr/health.py +557 -0
  203. atr/hf_utils.py +449 -0
  204. atr/injection.py +422 -0
  205. atr/metrics.py +440 -0
  206. atr/policies.py +403 -0
  207. atr/py.typed +2 -0
  208. atr/registry.py +452 -0
  209. atr/schema.py +480 -0
  210. atr/tools/safe/__init__.py +75 -0
  211. atr/tools/safe/calculator.py +467 -0
  212. atr/tools/safe/datetime_tool.py +443 -0
  213. atr/tools/safe/file_reader.py +402 -0
  214. atr/tools/safe/http_client.py +316 -0
  215. atr/tools/safe/json_parser.py +374 -0
  216. atr/tools/safe/text_tool.py +537 -0
  217. atr/tools/safe/toolkit.py +175 -0
  218. caas/__init__.py +162 -0
  219. caas/api/__init__.py +7 -0
  220. caas/api/server.py +1328 -0
  221. caas/caching.py +834 -0
  222. caas/cli.py +210 -0
  223. caas/conversation.py +223 -0
  224. caas/decay.py +72 -0
  225. caas/detection/__init__.py +9 -0
  226. caas/detection/detector.py +238 -0
  227. caas/enrichment.py +130 -0
  228. caas/gateway/__init__.py +27 -0
  229. caas/gateway/trust_gateway.py +474 -0
  230. caas/hf_utils.py +479 -0
  231. caas/ingestion/__init__.py +23 -0
  232. caas/ingestion/processors.py +253 -0
  233. caas/ingestion/structure_parser.py +188 -0
  234. caas/models.py +356 -0
  235. caas/pragmatic_truth.py +444 -0
  236. caas/routing/__init__.py +10 -0
  237. caas/routing/heuristic_router.py +58 -0
  238. caas/storage/__init__.py +9 -0
  239. caas/storage/store.py +389 -0
  240. caas/triad.py +213 -0
  241. caas/tuning/__init__.py +9 -0
  242. caas/tuning/tuner.py +329 -0
  243. caas/vfs/__init__.py +14 -0
  244. caas/vfs/filesystem.py +452 -0
  245. cmvk/__init__.py +218 -0
  246. cmvk/audit.py +402 -0
  247. cmvk/benchmarks.py +478 -0
  248. cmvk/constitutional.py +904 -0
  249. cmvk/hf_utils.py +301 -0
  250. cmvk/metrics.py +473 -0
  251. cmvk/profiles.py +300 -0
  252. cmvk/py.typed +0 -0
  253. cmvk/types.py +12 -0
  254. cmvk/verification.py +956 -0
  255. emk/__init__.py +89 -0
  256. emk/causal.py +352 -0
  257. emk/hf_utils.py +421 -0
  258. emk/indexer.py +83 -0
  259. emk/py.typed +0 -0
  260. emk/schema.py +204 -0
  261. emk/sleep_cycle.py +347 -0
  262. emk/store.py +281 -0
  263. iatp/__init__.py +166 -0
  264. iatp/attestation.py +461 -0
  265. iatp/cli.py +317 -0
  266. iatp/hf_utils.py +472 -0
  267. iatp/ipc_pipes.py +580 -0
  268. iatp/main.py +412 -0
  269. iatp/models/__init__.py +447 -0
  270. iatp/policy_engine.py +337 -0
  271. iatp/py.typed +2 -0
  272. iatp/recovery.py +321 -0
  273. iatp/security/__init__.py +270 -0
  274. iatp/sidecar/__init__.py +519 -0
  275. iatp/telemetry/__init__.py +164 -0
  276. iatp/tests/__init__.py +1 -0
  277. iatp/tests/test_attestation.py +370 -0
  278. iatp/tests/test_cli.py +131 -0
  279. iatp/tests/test_ed25519_attestation.py +211 -0
  280. iatp/tests/test_models.py +130 -0
  281. iatp/tests/test_policy_engine.py +347 -0
  282. iatp/tests/test_recovery.py +281 -0
  283. iatp/tests/test_security.py +222 -0
  284. iatp/tests/test_sidecar.py +167 -0
  285. iatp/tests/test_telemetry.py +175 -0
  286. mcp_kernel_server/__init__.py +28 -0
  287. mcp_kernel_server/cli.py +274 -0
  288. mcp_kernel_server/resources.py +217 -0
  289. mcp_kernel_server/server.py +564 -0
  290. mcp_kernel_server/tools.py +1174 -0
  291. mute_agent/__init__.py +68 -0
  292. mute_agent/core/__init__.py +1 -0
  293. mute_agent/core/execution_agent.py +166 -0
  294. mute_agent/core/handshake_protocol.py +201 -0
  295. mute_agent/core/reasoning_agent.py +238 -0
  296. mute_agent/knowledge_graph/__init__.py +1 -0
  297. mute_agent/knowledge_graph/graph_elements.py +65 -0
  298. mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
  299. mute_agent/knowledge_graph/subgraph.py +224 -0
  300. mute_agent/listener/__init__.py +43 -0
  301. mute_agent/listener/adapters/__init__.py +31 -0
  302. mute_agent/listener/adapters/base_adapter.py +189 -0
  303. mute_agent/listener/adapters/caas_adapter.py +344 -0
  304. mute_agent/listener/adapters/control_plane_adapter.py +436 -0
  305. mute_agent/listener/adapters/iatp_adapter.py +332 -0
  306. mute_agent/listener/adapters/scak_adapter.py +251 -0
  307. mute_agent/listener/listener.py +610 -0
  308. mute_agent/listener/state_observer.py +436 -0
  309. mute_agent/listener/threshold_config.py +313 -0
  310. mute_agent/super_system/__init__.py +1 -0
  311. mute_agent/super_system/router.py +204 -0
  312. mute_agent/visualization/__init__.py +10 -0
  313. mute_agent/visualization/graph_debugger.py +502 -0
  314. nexus/README.md +60 -0
  315. nexus/__init__.py +51 -0
  316. nexus/arbiter.py +359 -0
  317. nexus/client.py +466 -0
  318. nexus/dmz.py +444 -0
  319. nexus/escrow.py +430 -0
  320. nexus/exceptions.py +286 -0
  321. nexus/pyproject.toml +36 -0
  322. nexus/registry.py +393 -0
  323. nexus/reputation.py +425 -0
  324. nexus/schemas/__init__.py +51 -0
  325. nexus/schemas/compliance.py +276 -0
  326. nexus/schemas/escrow.py +251 -0
  327. nexus/schemas/manifest.py +225 -0
  328. nexus/schemas/receipt.py +208 -0
  329. nexus/tests/__init__.py +0 -0
  330. nexus/tests/conftest.py +146 -0
  331. nexus/tests/test_arbiter.py +192 -0
  332. nexus/tests/test_dmz.py +194 -0
  333. nexus/tests/test_escrow.py +276 -0
  334. nexus/tests/test_exceptions.py +225 -0
  335. nexus/tests/test_registry.py +232 -0
  336. nexus/tests/test_reputation.py +328 -0
  337. nexus/tests/test_schemas.py +295 -0
@@ -0,0 +1,629 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ HuggingFace smolagents Integration for Agent-OS
5
+ ================================================
6
+
7
+ Provides kernel-level governance for smolagents agent workflows.
8
+
9
+ Features:
10
+ - Extends BaseIntegration with wrap/unwrap for smolagents agents
11
+ - Policy enforcement via tool-call interception
12
+ - Tool allow/block lists
13
+ - Content filtering with blocked patterns
14
+ - Human approval workflow for sensitive tools
15
+ - Token/call budget tracking
16
+ - Full audit trail of tool calls and agent runs
17
+ - Works without smolagents installed (graceful import handling)
18
+ - Compatible with CodeAgent and ToolCallingAgent
19
+
20
+ Example:
21
+ >>> from agent_os.integrations.smolagents_adapter import SmolagentsKernel
22
+ >>>
23
+ >>> kernel = SmolagentsKernel(
24
+ ... max_tool_calls=10,
25
+ ... blocked_tools=["exec_code", "shell"],
26
+ ... blocked_patterns=["DROP TABLE", "rm -rf"],
27
+ ... require_human_approval=True,
28
+ ... sensitive_tools=["delete_file", "send_email"],
29
+ ... )
30
+ >>>
31
+ >>> # Wrap an existing agent
32
+ >>> from smolagents import CodeAgent, HfApiModel
33
+ >>> agent = CodeAgent(tools=[my_tool], model=HfApiModel())
34
+ >>> governed = kernel.wrap(agent)
35
+ >>> result = governed.run("Summarize this document")
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ import logging
41
+ import time
42
+ from dataclasses import dataclass, field
43
+ from typing import Any, Callable
44
+
45
+ from .base import BaseIntegration, GovernancePolicy
46
+
47
+ logger = logging.getLogger(__name__)
48
+
49
+ # Graceful import of smolagents
50
+ try:
51
+ import smolagents as _smolagents # noqa: F401
52
+
53
+ _HAS_SMOLAGENTS = True
54
+ except ImportError:
55
+ _HAS_SMOLAGENTS = False
56
+
57
+
58
+ def _check_smolagents_available() -> None:
59
+ """Raise a helpful error when the ``smolagents`` package is missing."""
60
+ if not _HAS_SMOLAGENTS:
61
+ raise ImportError(
62
+ "The 'smolagents' package is required for live smolagents agent wrapping. "
63
+ "Install it with: pip install smolagents"
64
+ )
65
+
66
+
67
+ @dataclass
68
+ class PolicyConfig:
69
+ """Policy configuration for smolagents governance."""
70
+
71
+ max_tool_calls: int = 50
72
+ max_agent_calls: int = 20
73
+ timeout_seconds: int = 300
74
+
75
+ allowed_tools: list[str] = field(default_factory=list)
76
+ blocked_tools: list[str] = field(default_factory=list)
77
+
78
+ blocked_patterns: list[str] = field(default_factory=list)
79
+
80
+ log_all_calls: bool = True
81
+
82
+ require_human_approval: bool = False
83
+ sensitive_tools: list[str] = field(default_factory=list)
84
+
85
+ max_budget: float | None = None
86
+
87
+
88
+ class PolicyViolationError(Exception):
89
+ """Raised when a governance policy is violated."""
90
+
91
+ def __init__(self, policy_name: str, description: str, severity: str = "high"):
92
+ self.policy_name = policy_name
93
+ self.description = description
94
+ self.severity = severity
95
+ super().__init__(f"Policy violation ({policy_name}): {description}")
96
+
97
+
98
+ @dataclass
99
+ class AuditEvent:
100
+ """Single audit trail entry."""
101
+
102
+ timestamp: float
103
+ event_type: str
104
+ agent_name: str
105
+ details: dict[str, Any]
106
+
107
+
108
+ class SmolagentsKernel(BaseIntegration):
109
+ """
110
+ Governance kernel for HuggingFace smolagents.
111
+
112
+ Extends BaseIntegration and intercepts tool calls on smolagents
113
+ CodeAgent and ToolCallingAgent instances by wrapping each tool's
114
+ ``forward`` method with governance checks.
115
+
116
+ Supports human approval workflows for sensitive tools and
117
+ token/call budget tracking.
118
+ """
119
+
120
+ def __init__(
121
+ self,
122
+ policy: PolicyConfig | None = None,
123
+ on_violation: Callable[[PolicyViolationError], None] | None = None,
124
+ *,
125
+ # Convenience kwargs (create PolicyConfig automatically)
126
+ max_tool_calls: int = 50,
127
+ max_agent_calls: int = 20,
128
+ timeout_seconds: int = 300,
129
+ allowed_tools: list[str] | None = None,
130
+ blocked_tools: list[str] | None = None,
131
+ blocked_patterns: list[str] | None = None,
132
+ require_human_approval: bool = False,
133
+ sensitive_tools: list[str] | None = None,
134
+ max_budget: float | None = None,
135
+ ):
136
+ if policy is not None:
137
+ self._sm_config = policy
138
+ else:
139
+ self._sm_config = PolicyConfig(
140
+ max_tool_calls=max_tool_calls,
141
+ max_agent_calls=max_agent_calls,
142
+ timeout_seconds=timeout_seconds,
143
+ allowed_tools=allowed_tools or [],
144
+ blocked_tools=blocked_tools or [],
145
+ blocked_patterns=blocked_patterns or [],
146
+ require_human_approval=require_human_approval,
147
+ sensitive_tools=sensitive_tools or [],
148
+ max_budget=max_budget,
149
+ )
150
+
151
+ # Initialize BaseIntegration with a GovernancePolicy mapped from PolicyConfig
152
+ governance_policy = GovernancePolicy(
153
+ max_tool_calls=self._sm_config.max_tool_calls,
154
+ timeout_seconds=self._sm_config.timeout_seconds,
155
+ allowed_tools=list(self._sm_config.allowed_tools),
156
+ blocked_patterns=list(self._sm_config.blocked_patterns),
157
+ require_human_approval=self._sm_config.require_human_approval,
158
+ log_all_calls=self._sm_config.log_all_calls,
159
+ )
160
+ super().__init__(policy=governance_policy)
161
+
162
+ self.on_violation = on_violation or self._default_violation_handler
163
+
164
+ # Counters
165
+ self._tool_call_count: int = 0
166
+ self._agent_call_count: int = 0
167
+ self._start_time: float = time.time()
168
+ self._budget_spent: float = 0.0
169
+
170
+ # Audit trail
171
+ self._audit_log: list[AuditEvent] = []
172
+
173
+ # Violations collected
174
+ self._violations: list[PolicyViolationError] = []
175
+
176
+ # Human approval tracking
177
+ self._pending_approvals: dict[str, dict[str, Any]] = {}
178
+ self._approved_calls: dict[str, bool] = {}
179
+
180
+ # Wrapped agents registry and original forward methods
181
+ self._wrapped_agents: dict[str, Any] = {}
182
+ self._original_forwards: dict[str, Callable[..., Any]] = {}
183
+
184
+ # ------------------------------------------------------------------
185
+ # BaseIntegration abstract methods
186
+ # ------------------------------------------------------------------
187
+
188
+ def wrap(self, agent: Any) -> Any:
189
+ """
190
+ Wrap a smolagents agent with governance.
191
+
192
+ Intercepts each tool's ``forward`` method so that every tool call
193
+ passes through policy checks before execution. The agent's
194
+ ``toolbox`` (dict of tool-name → Tool) is iterated and each tool
195
+ is wrapped in-place.
196
+
197
+ Works without smolagents installed (for testing with mocks).
198
+ """
199
+ agent_name = getattr(agent, "name", None) or str(id(agent))
200
+
201
+ # smolagents stores tools in agent.toolbox (dict-like or has .tools)
202
+ tools = self._get_tools(agent)
203
+ for tool_name, tool_obj in tools.items():
204
+ self._wrap_tool(tool_obj, tool_name, agent_name)
205
+
206
+ self._wrapped_agents[agent_name] = agent
207
+ self._record("agent_wrapped", agent_name, {"agent_type": type(agent).__name__})
208
+ logger.info("Wrapped smolagents agent '%s' with governance kernel", agent_name)
209
+ return agent
210
+
211
+ def unwrap(self, governed_agent: Any) -> Any:
212
+ """Remove governance wrapper and restore original tool forwards."""
213
+ agent_name = getattr(governed_agent, "name", None) or str(id(governed_agent))
214
+
215
+ tools = self._get_tools(governed_agent)
216
+ for tool_name, tool_obj in tools.items():
217
+ key = f"{agent_name}:{tool_name}"
218
+ if key in self._original_forwards:
219
+ tool_obj.forward = self._original_forwards.pop(key)
220
+
221
+ self._wrapped_agents.pop(agent_name, None)
222
+ return governed_agent
223
+
224
+ # ------------------------------------------------------------------
225
+ # Tool wrapping
226
+ # ------------------------------------------------------------------
227
+
228
+ @staticmethod
229
+ def _get_tools(agent: Any) -> dict[str, Any]:
230
+ """Extract the tool dict from a smolagents agent.
231
+
232
+ smolagents agents expose tools via ``agent.toolbox`` which may be
233
+ a ``Toolbox`` object (with a ``.tools`` dict) or a plain dict.
234
+ Falls back to an empty dict when no toolbox is found.
235
+ """
236
+ toolbox = getattr(agent, "toolbox", None)
237
+ if toolbox is None:
238
+ return {}
239
+ # Toolbox object has a .tools dict
240
+ if hasattr(toolbox, "tools"):
241
+ return toolbox.tools
242
+ # Plain dict
243
+ if isinstance(toolbox, dict):
244
+ return toolbox
245
+ return {}
246
+
247
+ def _wrap_tool(self, tool: Any, tool_name: str, agent_name: str) -> None:
248
+ """Replace ``tool.forward`` with a governed version."""
249
+ original_forward = tool.forward
250
+ key = f"{agent_name}:{tool_name}"
251
+ self._original_forwards[key] = original_forward
252
+
253
+ kernel = self
254
+
255
+ def governed_forward(*args: Any, **kwargs: Any) -> Any:
256
+ """Governed wrapper around a smolagents tool's forward method.
257
+
258
+ Intercepts the tool invocation, validates the call against
259
+ the active policy, updates call counters and the audit log,
260
+ then delegates to the original forward implementation.
261
+
262
+ Args:
263
+ *args: Positional arguments forwarded to the original tool.
264
+ **kwargs: Keyword arguments forwarded to the original tool.
265
+
266
+ Returns:
267
+ The result from the original tool's forward method.
268
+
269
+ Raises:
270
+ PolicyViolationError: If the call violates the active policy.
271
+ """
272
+ # Pre-execution governance check
273
+ result = kernel.before_tool_call(
274
+ tool_name=tool_name,
275
+ tool_args=kwargs or (args[0] if args else {}),
276
+ agent_name=agent_name,
277
+ )
278
+ if result is not None:
279
+ raise PolicyViolationError(
280
+ result.get("policy", "governance"),
281
+ result.get("error", "Tool call blocked by policy"),
282
+ )
283
+
284
+ # Execute original tool
285
+ output = original_forward(*args, **kwargs)
286
+
287
+ # Post-execution governance check
288
+ filtered = kernel.after_tool_call(
289
+ tool_name=tool_name,
290
+ tool_result=output,
291
+ agent_name=agent_name,
292
+ )
293
+ return filtered
294
+
295
+ tool.forward = governed_forward
296
+
297
+ # ------------------------------------------------------------------
298
+ # Internal helpers
299
+ # ------------------------------------------------------------------
300
+
301
+ def _default_violation_handler(self, error: PolicyViolationError) -> None:
302
+ """Default handler called when a policy violation occurs.
303
+
304
+ Logs the violation as an error. Override by passing a custom
305
+ on_violation callback to the kernel constructor.
306
+
307
+ Args:
308
+ error: The PolicyViolationError that was raised.
309
+ """
310
+ logger.error(f"Policy violation: {error}")
311
+
312
+ def _record(self, event_type: str, agent_name: str, details: dict[str, Any]) -> None:
313
+ """Append an audit event to the internal audit log.
314
+
315
+ Records the event only when log_all_calls is enabled.
316
+
317
+ Args:
318
+ event_type: Short string label for the event.
319
+ agent_name: ID or name of the agent generating the event.
320
+ details: Arbitrary dict of additional context.
321
+ """
322
+ if self._sm_config.log_all_calls:
323
+ self._audit_log.append(
324
+ AuditEvent(
325
+ timestamp=time.time(),
326
+ event_type=event_type,
327
+ agent_name=agent_name,
328
+ details=details,
329
+ )
330
+ )
331
+
332
+ def _check_tool_allowed(self, tool_name: str) -> tuple[bool, str]:
333
+ """Check whether a tool is permitted by the active policy.
334
+
335
+ Args:
336
+ tool_name: Name of the tool to check.
337
+
338
+ Returns:
339
+ Tuple of (allowed: bool, reason: str).
340
+ """
341
+ if tool_name in self._sm_config.blocked_tools:
342
+ return False, f"Tool '{tool_name}' is blocked by policy"
343
+ if self._sm_config.allowed_tools and tool_name not in self._sm_config.allowed_tools:
344
+ return False, f"Tool '{tool_name}' not in allowed list"
345
+ return True, ""
346
+
347
+ def _check_content(self, content: str) -> tuple[bool, str]:
348
+ """Scan a string for policy-blocked patterns.
349
+
350
+ Args:
351
+ content: The text to scan.
352
+
353
+ Returns:
354
+ Tuple of (allowed: bool, reason: str).
355
+ """
356
+ content_lower = content.lower()
357
+ for pattern in self._sm_config.blocked_patterns:
358
+ if pattern.lower() in content_lower:
359
+ return False, f"Content matches blocked pattern: '{pattern}'"
360
+ return True, ""
361
+
362
+ def _check_timeout(self) -> tuple[bool, str]:
363
+ """Check whether the kernel has exceeded its configured timeout.
364
+
365
+ Returns:
366
+ Tuple of (within_limit: bool, reason: str).
367
+ """
368
+ elapsed = time.time() - self._start_time
369
+ if elapsed > self._sm_config.timeout_seconds:
370
+ return False, f"Execution timeout ({elapsed:.0f}s > {self._sm_config.timeout_seconds}s)"
371
+ return True, ""
372
+
373
+ def _check_budget(self, cost: float = 1.0) -> tuple[bool, str]:
374
+ """Check whether a tool call would exceed the configured cost budget.
375
+
376
+ Args:
377
+ cost: Cost units to add for this call (default 1.0).
378
+
379
+ Returns:
380
+ Tuple of (within_budget: bool, reason: str).
381
+ """
382
+ if self._sm_config.max_budget is not None:
383
+ if self._budget_spent + cost > self._sm_config.max_budget:
384
+ return False, (
385
+ f"Budget exceeded: spent {self._budget_spent} + {cost} "
386
+ f"> limit {self._sm_config.max_budget}"
387
+ )
388
+ return True, ""
389
+
390
+ def _needs_approval(self, tool_name: str) -> bool:
391
+ """Check if a tool call requires human approval."""
392
+ if not self._sm_config.require_human_approval:
393
+ return False
394
+ if self._sm_config.sensitive_tools:
395
+ return tool_name in self._sm_config.sensitive_tools
396
+ return True
397
+
398
+ def _raise_violation(self, policy_name: str, description: str) -> PolicyViolationError:
399
+ """Create, record, and surface a PolicyViolationError.
400
+
401
+ Appends the error to the violations list and calls on_violation.
402
+
403
+ Args:
404
+ policy_name: Short identifier for the violated policy rule.
405
+ description: Human-readable description of the violation.
406
+
407
+ Returns:
408
+ The constructed PolicyViolationError (caller may raise it).
409
+ """
410
+ error = PolicyViolationError(policy_name, description)
411
+ self._violations.append(error)
412
+ self.on_violation(error)
413
+ return error
414
+
415
+ # ------------------------------------------------------------------
416
+ # Tool-call governance hooks
417
+ # ------------------------------------------------------------------
418
+
419
+ def before_tool_call(
420
+ self,
421
+ tool_name: str = "unknown",
422
+ tool_args: Any = None,
423
+ agent_name: str = "unknown",
424
+ cost: float = 1.0,
425
+ ) -> dict[str, Any] | None:
426
+ """
427
+ Pre-execution governance check for a tool call.
428
+
429
+ Returns None to allow execution, or a dict with error info to block it.
430
+ """
431
+ if tool_args is None:
432
+ tool_args = {}
433
+
434
+ self._record("before_tool", agent_name, {"tool": tool_name, "args": tool_args})
435
+
436
+ # Check timeout
437
+ ok, reason = self._check_timeout()
438
+ if not ok:
439
+ error = self._raise_violation("timeout", reason)
440
+ return {"error": str(error), "policy": "timeout"}
441
+
442
+ # Check tool count
443
+ self._tool_call_count += 1
444
+ if self._tool_call_count > self._sm_config.max_tool_calls:
445
+ error = self._raise_violation(
446
+ "tool_limit",
447
+ f"Tool call count ({self._tool_call_count}) exceeds limit ({self._sm_config.max_tool_calls})",
448
+ )
449
+ return {"error": str(error), "policy": "tool_limit"}
450
+
451
+ # Check budget
452
+ ok, reason = self._check_budget(cost)
453
+ if not ok:
454
+ error = self._raise_violation("budget_exceeded", reason)
455
+ return {"error": str(error), "policy": "budget_exceeded"}
456
+
457
+ # Check tool allowed
458
+ ok, reason = self._check_tool_allowed(tool_name)
459
+ if not ok:
460
+ error = self._raise_violation("tool_filter", reason)
461
+ return {"error": str(error), "policy": "tool_filter"}
462
+
463
+ # Check content in arguments
464
+ if isinstance(tool_args, dict):
465
+ for value in tool_args.values():
466
+ if isinstance(value, str):
467
+ ok, reason = self._check_content(value)
468
+ if not ok:
469
+ error = self._raise_violation("content_filter", reason)
470
+ return {"error": str(error), "policy": "content_filter"}
471
+ elif isinstance(tool_args, str):
472
+ ok, reason = self._check_content(tool_args)
473
+ if not ok:
474
+ error = self._raise_violation("content_filter", reason)
475
+ return {"error": str(error), "policy": "content_filter"}
476
+
477
+ # Human approval check
478
+ if self._needs_approval(tool_name):
479
+ call_id = f"{agent_name}:{tool_name}:{self._tool_call_count}"
480
+ if call_id not in self._approved_calls:
481
+ self._pending_approvals[call_id] = {
482
+ "tool_name": tool_name,
483
+ "tool_args": tool_args,
484
+ "agent_name": agent_name,
485
+ "timestamp": time.time(),
486
+ }
487
+ self._record("approval_required", agent_name, {
488
+ "tool": tool_name, "call_id": call_id,
489
+ })
490
+ error = self._raise_violation(
491
+ "human_approval_required",
492
+ f"Tool '{tool_name}' requires human approval (call_id={call_id})",
493
+ )
494
+ return {
495
+ "error": str(error),
496
+ "call_id": call_id,
497
+ "needs_approval": True,
498
+ "policy": "human_approval_required",
499
+ }
500
+
501
+ # Track budget spend
502
+ self._budget_spent += cost
503
+
504
+ return None # Allow execution
505
+
506
+ def after_tool_call(
507
+ self,
508
+ tool_name: str = "unknown",
509
+ tool_result: Any = None,
510
+ agent_name: str = "unknown",
511
+ ) -> Any:
512
+ """
513
+ Post-execution governance check for a tool call.
514
+
515
+ Inspects tool output for blocked patterns.
516
+ Returns the (possibly modified) tool_result, or raises on violation.
517
+ """
518
+ self._record("after_tool", agent_name, {
519
+ "tool": tool_name,
520
+ "result_type": type(tool_result).__name__,
521
+ })
522
+
523
+ if isinstance(tool_result, str):
524
+ ok, reason = self._check_content(tool_result)
525
+ if not ok:
526
+ self._raise_violation("output_filter", reason)
527
+ return f"[BLOCKED] {reason}"
528
+
529
+ if isinstance(tool_result, dict):
530
+ for value in tool_result.values():
531
+ if isinstance(value, str):
532
+ ok, reason = self._check_content(value)
533
+ if not ok:
534
+ self._raise_violation("output_filter", reason)
535
+ return {"error": reason}
536
+
537
+ return tool_result
538
+
539
+ # ------------------------------------------------------------------
540
+ # Human Approval API
541
+ # ------------------------------------------------------------------
542
+
543
+ def approve(self, call_id: str) -> bool:
544
+ """Approve a pending tool call by its call_id."""
545
+ if call_id in self._pending_approvals:
546
+ self._approved_calls[call_id] = True
547
+ info = self._pending_approvals.pop(call_id)
548
+ self._record("approval_granted", info.get("agent_name", "unknown"), {
549
+ "call_id": call_id, "tool": info.get("tool_name"),
550
+ })
551
+ return True
552
+ return False
553
+
554
+ def deny(self, call_id: str) -> bool:
555
+ """Deny a pending tool call by its call_id."""
556
+ if call_id in self._pending_approvals:
557
+ info = self._pending_approvals.pop(call_id)
558
+ self._record("approval_denied", info.get("agent_name", "unknown"), {
559
+ "call_id": call_id, "tool": info.get("tool_name"),
560
+ })
561
+ return True
562
+ return False
563
+
564
+ def get_pending_approvals(self) -> dict[str, dict[str, Any]]:
565
+ """Return all pending approval requests."""
566
+ return dict(self._pending_approvals)
567
+
568
+ # ------------------------------------------------------------------
569
+ # Public API
570
+ # ------------------------------------------------------------------
571
+
572
+ def reset(self) -> None:
573
+ """Reset counters and start time (for new execution runs)."""
574
+ self._tool_call_count = 0
575
+ self._agent_call_count = 0
576
+ self._start_time = time.time()
577
+ self._budget_spent = 0.0
578
+
579
+ def get_audit_log(self) -> list[AuditEvent]:
580
+ """Return the full audit trail."""
581
+ return list(self._audit_log)
582
+
583
+ def get_violations(self) -> list[PolicyViolationError]:
584
+ """Return all collected violations."""
585
+ return list(self._violations)
586
+
587
+ def get_stats(self) -> dict[str, Any]:
588
+ """Get governance statistics."""
589
+ return {
590
+ "tool_calls": self._tool_call_count,
591
+ "agent_calls": self._agent_call_count,
592
+ "violations": len(self._violations),
593
+ "audit_events": len(self._audit_log),
594
+ "elapsed_seconds": round(time.time() - self._start_time, 2),
595
+ "budget_spent": self._budget_spent,
596
+ "budget_limit": self._sm_config.max_budget,
597
+ "pending_approvals": len(self._pending_approvals),
598
+ "policy": {
599
+ "max_tool_calls": self._sm_config.max_tool_calls,
600
+ "max_agent_calls": self._sm_config.max_agent_calls,
601
+ "blocked_tools": self._sm_config.blocked_tools,
602
+ "allowed_tools": self._sm_config.allowed_tools,
603
+ "require_human_approval": self._sm_config.require_human_approval,
604
+ "sensitive_tools": self._sm_config.sensitive_tools,
605
+ },
606
+ }
607
+
608
+ def health_check(self) -> dict[str, Any]:
609
+ """Return adapter health status."""
610
+ elapsed = time.time() - self._start_time
611
+ has_violations = len(self._violations) > 0
612
+ return {
613
+ "status": "degraded" if has_violations else "healthy",
614
+ "backend": "smolagents",
615
+ "smolagents_available": _HAS_SMOLAGENTS,
616
+ "wrapped_agents": len(self._wrapped_agents),
617
+ "violations": len(self._violations),
618
+ "uptime_seconds": round(elapsed, 2),
619
+ }
620
+
621
+
622
+ __all__ = [
623
+ "SmolagentsKernel",
624
+ "PolicyConfig",
625
+ "PolicyViolationError",
626
+ "AuditEvent",
627
+ "_HAS_SMOLAGENTS",
628
+ "_check_smolagents_available",
629
+ ]