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,633 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Google ADK (Agent Development Kit) Integration for Agent-OS
5
+ ============================================================
6
+
7
+ Provides kernel-level governance for Google ADK agent workflows.
8
+
9
+ Features:
10
+ - Extends BaseIntegration with wrap/unwrap for ADK agents
11
+ - Policy enforcement via ADK's native callback hooks
12
+ - before_tool_callback / after_tool_callback for tool governance
13
+ - before_agent_callback / after_agent_callback for agent lifecycle
14
+ - Content filtering with blocked patterns
15
+ - Tool allow/block lists
16
+ - Human approval workflow for sensitive tools
17
+ - Token/call budget tracking
18
+ - Full audit trail of tool calls and agent runs
19
+ - Works without google-adk installed (graceful import handling)
20
+ - Compatible with LlmAgent, SequentialAgent, ParallelAgent, LoopAgent
21
+
22
+ Example:
23
+ >>> from agent_os.integrations.google_adk_adapter import GoogleADKKernel
24
+ >>> from google.adk.agents import LlmAgent
25
+ >>>
26
+ >>> kernel = GoogleADKKernel(
27
+ ... max_tool_calls=10,
28
+ ... blocked_tools=["exec_code", "shell"],
29
+ ... blocked_patterns=["DROP TABLE", "rm -rf"],
30
+ ... require_human_approval=True,
31
+ ... sensitive_tools=["delete_file", "send_email"],
32
+ ... )
33
+ >>>
34
+ >>> # Option A: callback injection
35
+ >>> agent = LlmAgent(
36
+ ... model="gemini-2.5-flash",
37
+ ... name="assistant",
38
+ ... tools=[my_tool],
39
+ ... **kernel.get_callbacks(),
40
+ ... )
41
+ >>>
42
+ >>> # Option B: wrap the agent object
43
+ >>> agent = kernel.wrap(LlmAgent(model="gemini-2.5-flash", name="assistant"))
44
+ """
45
+
46
+ from __future__ import annotations
47
+
48
+ import logging
49
+ import time
50
+ from dataclasses import dataclass, field
51
+ from typing import Any, Callable
52
+
53
+ from .base import BaseIntegration, GovernancePolicy
54
+
55
+ logger = logging.getLogger(__name__)
56
+
57
+ # Graceful import of google-adk
58
+ try:
59
+ from google.adk.agents import Agent as _ADKAgent # noqa: F401
60
+
61
+ _HAS_ADK = True
62
+ except ImportError:
63
+ _HAS_ADK = False
64
+
65
+
66
+ def _check_adk_available() -> None:
67
+ """Raise a helpful error when the ``google-adk`` package is missing."""
68
+ if not _HAS_ADK:
69
+ raise ImportError(
70
+ "The 'google-adk' package is required for live ADK agent wrapping. "
71
+ "Install it with: pip install google-adk"
72
+ )
73
+
74
+
75
+ @dataclass
76
+ class PolicyConfig:
77
+ """Policy configuration for Google ADK governance."""
78
+
79
+ max_tool_calls: int = 50
80
+ max_agent_calls: int = 20
81
+ timeout_seconds: int = 300
82
+
83
+ allowed_tools: list[str] = field(default_factory=list)
84
+ blocked_tools: list[str] = field(default_factory=list)
85
+
86
+ blocked_patterns: list[str] = field(default_factory=list)
87
+ pii_detection: bool = True
88
+
89
+ log_all_calls: bool = True
90
+
91
+ require_human_approval: bool = False
92
+ sensitive_tools: list[str] = field(default_factory=list)
93
+
94
+ max_budget: float | None = None
95
+
96
+
97
+ class PolicyViolationError(Exception):
98
+ """Raised when a governance policy is violated."""
99
+
100
+ def __init__(self, policy_name: str, description: str, severity: str = "high"):
101
+ self.policy_name = policy_name
102
+ self.description = description
103
+ self.severity = severity
104
+ super().__init__(f"Policy violation ({policy_name}): {description}")
105
+
106
+
107
+ @dataclass
108
+ class AuditEvent:
109
+ """Single audit trail entry."""
110
+
111
+ timestamp: float
112
+ event_type: str
113
+ agent_name: str
114
+ details: dict[str, Any]
115
+
116
+
117
+ class GoogleADKKernel(BaseIntegration):
118
+ """
119
+ Governance kernel for Google ADK.
120
+
121
+ Extends BaseIntegration and provides callback functions that plug
122
+ directly into ADK's before_tool_callback, after_tool_callback,
123
+ before_agent_callback, and after_agent_callback hooks.
124
+
125
+ Supports human approval workflows for sensitive tools and
126
+ token/call budget tracking.
127
+ """
128
+
129
+ def __init__(
130
+ self,
131
+ policy: PolicyConfig | None = None,
132
+ on_violation: Callable[[PolicyViolationError], None] | None = None,
133
+ *,
134
+ # Convenience kwargs (create PolicyConfig automatically)
135
+ max_tool_calls: int = 50,
136
+ max_agent_calls: int = 20,
137
+ timeout_seconds: int = 300,
138
+ allowed_tools: list[str] | None = None,
139
+ blocked_tools: list[str] | None = None,
140
+ blocked_patterns: list[str] | None = None,
141
+ require_human_approval: bool = False,
142
+ sensitive_tools: list[str] | None = None,
143
+ max_budget: float | None = None,
144
+ ):
145
+ if policy is not None:
146
+ self._adk_config = policy
147
+ else:
148
+ self._adk_config = PolicyConfig(
149
+ max_tool_calls=max_tool_calls,
150
+ max_agent_calls=max_agent_calls,
151
+ timeout_seconds=timeout_seconds,
152
+ allowed_tools=allowed_tools or [],
153
+ blocked_tools=blocked_tools or [],
154
+ blocked_patterns=blocked_patterns or [],
155
+ require_human_approval=require_human_approval,
156
+ sensitive_tools=sensitive_tools or [],
157
+ max_budget=max_budget,
158
+ )
159
+
160
+ # Initialize BaseIntegration with a GovernancePolicy mapped from PolicyConfig
161
+ governance_policy = GovernancePolicy(
162
+ max_tool_calls=self._adk_config.max_tool_calls,
163
+ timeout_seconds=self._adk_config.timeout_seconds,
164
+ allowed_tools=list(self._adk_config.allowed_tools),
165
+ blocked_patterns=list(self._adk_config.blocked_patterns),
166
+ require_human_approval=self._adk_config.require_human_approval,
167
+ log_all_calls=self._adk_config.log_all_calls,
168
+ )
169
+ super().__init__(policy=governance_policy)
170
+
171
+ self.on_violation = on_violation or self._default_violation_handler
172
+
173
+ # Counters
174
+ self._tool_call_count: int = 0
175
+ self._agent_call_count: int = 0
176
+ self._start_time: float = time.time()
177
+ self._budget_spent: float = 0.0
178
+
179
+ # Audit trail
180
+ self._audit_log: list[AuditEvent] = []
181
+
182
+ # Violations collected
183
+ self._violations: list[PolicyViolationError] = []
184
+
185
+ # Human approval tracking
186
+ self._pending_approvals: dict[str, dict[str, Any]] = {}
187
+ self._approved_calls: dict[str, bool] = {}
188
+
189
+ # Wrapped agents registry
190
+ self._wrapped_agents: dict[str, Any] = {}
191
+
192
+ # ------------------------------------------------------------------
193
+ # BaseIntegration abstract methods
194
+ # ------------------------------------------------------------------
195
+
196
+ def wrap(self, agent: Any) -> Any:
197
+ """
198
+ Wrap an ADK agent with governance callbacks.
199
+
200
+ Injects before/after callbacks into the agent if it has the
201
+ expected ADK callback attributes. Otherwise returns a wrapper
202
+ object that delegates attribute access to the original agent.
203
+
204
+ Works without google-adk installed (for testing with mocks).
205
+ """
206
+ agent_name = getattr(agent, "name", None) or str(id(agent))
207
+
208
+ # Inject callbacks if the agent supports them
209
+ for attr, cb in self.get_callbacks().items():
210
+ if hasattr(agent, attr):
211
+ setattr(agent, attr, cb)
212
+
213
+ self._wrapped_agents[agent_name] = agent
214
+ self._record("agent_wrapped", agent_name, {"agent_type": type(agent).__name__})
215
+ logger.info("Wrapped ADK agent '%s' with governance kernel", agent_name)
216
+ return agent
217
+
218
+ def unwrap(self, governed_agent: Any) -> Any:
219
+ """Remove governance wrapper and return the original agent.
220
+
221
+ Since ``wrap`` modifies agents in-place (injecting callbacks),
222
+ ``unwrap`` clears the callbacks back to ``None``.
223
+ """
224
+ for attr in self.get_callbacks():
225
+ if hasattr(governed_agent, attr):
226
+ setattr(governed_agent, attr, None)
227
+ agent_name = getattr(governed_agent, "name", None) or str(id(governed_agent))
228
+ self._wrapped_agents.pop(agent_name, None)
229
+ return governed_agent
230
+
231
+ # ------------------------------------------------------------------
232
+ # Internal helpers
233
+ # ------------------------------------------------------------------
234
+
235
+ def _default_violation_handler(self, error: PolicyViolationError) -> None:
236
+ """Default handler called when a policy violation occurs.
237
+
238
+ Logs the violation at ERROR level. Override by passing a custom
239
+ on_violation callable to the kernel constructor.
240
+
241
+ Args:
242
+ error: The PolicyViolationError that was raised.
243
+ """
244
+ logger.error(f"Policy violation: {error}")
245
+
246
+ def _record(self, event_type: str, agent_name: str, details: dict[str, Any]) -> None:
247
+ """Append an audit event to the internal audit log.
248
+
249
+ Records the event only when log_all_calls is enabled.
250
+
251
+ Args:
252
+ event_type: Short string label for the event.
253
+ agent_name: Name of the ADK agent generating the event.
254
+ details: Arbitrary dict of additional context.
255
+ """
256
+ if self._adk_config.log_all_calls:
257
+ self._audit_log.append(
258
+ AuditEvent(
259
+ timestamp=time.time(),
260
+ event_type=event_type,
261
+ agent_name=agent_name,
262
+ details=details,
263
+ )
264
+ )
265
+
266
+ def _check_tool_allowed(self, tool_name: str) -> tuple[bool, str]:
267
+ """Check whether a tool is permitted by the active ADK policy.
268
+
269
+ Args:
270
+ tool_name: Name of the ADK tool to check.
271
+
272
+ Returns:
273
+ Tuple of (allowed: bool, reason: str).
274
+ """
275
+ if tool_name in self._adk_config.blocked_tools:
276
+ return False, f"Tool '{tool_name}' is blocked by policy"
277
+ if self._adk_config.allowed_tools and tool_name not in self._adk_config.allowed_tools:
278
+ return False, f"Tool '{tool_name}' not in allowed list"
279
+ return True, ""
280
+
281
+ def _check_content(self, content: str) -> tuple[bool, str]:
282
+ """Scan a string for policy-blocked patterns.
283
+
284
+ Args:
285
+ content: The text to scan.
286
+
287
+ Returns:
288
+ Tuple of (allowed: bool, reason: str).
289
+ """
290
+ content_lower = content.lower()
291
+ for pattern in self._adk_config.blocked_patterns:
292
+ if pattern.lower() in content_lower:
293
+ return False, f"Content matches blocked pattern: '{pattern}'"
294
+ return True, ""
295
+
296
+ def _check_timeout(self) -> tuple[bool, str]:
297
+ """Check whether the kernel has exceeded its configured timeout.
298
+
299
+ Returns:
300
+ Tuple of (within_limit: bool, reason: str).
301
+ """
302
+ elapsed = time.time() - self._start_time
303
+ if elapsed > self._adk_config.timeout_seconds:
304
+ return False, f"Execution timeout ({elapsed:.0f}s > {self._adk_config.timeout_seconds}s)"
305
+ return True, ""
306
+
307
+ def _check_budget(self, cost: float = 1.0) -> tuple[bool, str]:
308
+ """Check whether a tool call would exceed the configured cost budget.
309
+
310
+ Args:
311
+ cost: Cost units to add for this call (default 1.0).
312
+
313
+ Returns:
314
+ Tuple of (within_budget: bool, reason: str).
315
+ """
316
+ if self._adk_config.max_budget is not None:
317
+ if self._budget_spent + cost > self._adk_config.max_budget:
318
+ return False, (
319
+ f"Budget exceeded: spent {self._budget_spent} + {cost} "
320
+ f"> limit {self._adk_config.max_budget}"
321
+ )
322
+ return True, ""
323
+
324
+ def _needs_approval(self, tool_name: str) -> bool:
325
+ """Check if a tool call requires human approval."""
326
+ if not self._adk_config.require_human_approval:
327
+ return False
328
+ # If sensitive_tools is specified, only those need approval
329
+ if self._adk_config.sensitive_tools:
330
+ return tool_name in self._adk_config.sensitive_tools
331
+ # Otherwise all tools need approval when require_human_approval is True
332
+ return True
333
+
334
+ def _raise_violation(self, policy_name: str, description: str) -> PolicyViolationError:
335
+ """Create, record, and surface a PolicyViolationError.
336
+
337
+ Appends the error to the violations list and calls on_violation.
338
+
339
+ Args:
340
+ policy_name: Short identifier for the violated policy rule.
341
+ description: Human-readable description of the violation.
342
+
343
+ Returns:
344
+ The constructed PolicyViolationError (caller may raise it).
345
+ """
346
+ error = PolicyViolationError(policy_name, description)
347
+ self._violations.append(error)
348
+ self.on_violation(error)
349
+ return error
350
+
351
+ # ------------------------------------------------------------------
352
+ # ADK Callback Hooks
353
+ # ------------------------------------------------------------------
354
+
355
+ def before_tool_callback(self, tool_context: Any = None, **kwargs: Any) -> dict[str, Any] | None:
356
+ """
357
+ ADK before_tool_callback — called before each tool execution.
358
+
359
+ Compatible with ADK's ToolContext. If tool_context is not an ADK
360
+ ToolContext (e.g., in tests), falls back to kwargs for tool_name/tool_args.
361
+
362
+ Returns:
363
+ None to allow execution, or a dict with an error to block it.
364
+ """
365
+ tool_name = getattr(tool_context, "tool_name", kwargs.get("tool_name", "unknown"))
366
+ tool_args = getattr(tool_context, "tool_args", kwargs.get("tool_args", {}))
367
+ agent_name = getattr(tool_context, "agent_name", kwargs.get("agent_name", "unknown"))
368
+
369
+ self._record("before_tool", agent_name, {"tool": tool_name, "args": tool_args})
370
+
371
+ # Check timeout
372
+ ok, reason = self._check_timeout()
373
+ if not ok:
374
+ error = self._raise_violation("timeout", reason)
375
+ return {"error": str(error)}
376
+
377
+ # Check tool count
378
+ self._tool_call_count += 1
379
+ if self._tool_call_count > self._adk_config.max_tool_calls:
380
+ error = self._raise_violation(
381
+ "tool_limit",
382
+ f"Tool call count ({self._tool_call_count}) exceeds limit ({self._adk_config.max_tool_calls})",
383
+ )
384
+ return {"error": str(error)}
385
+
386
+ # Check budget
387
+ cost = kwargs.get("cost", 1.0)
388
+ ok, reason = self._check_budget(cost)
389
+ if not ok:
390
+ error = self._raise_violation("budget_exceeded", reason)
391
+ return {"error": str(error)}
392
+
393
+ # Check tool allowed
394
+ ok, reason = self._check_tool_allowed(tool_name)
395
+ if not ok:
396
+ error = self._raise_violation("tool_filter", reason)
397
+ return {"error": str(error)}
398
+
399
+ # Check content in arguments
400
+ if isinstance(tool_args, dict):
401
+ for value in tool_args.values():
402
+ if isinstance(value, str):
403
+ ok, reason = self._check_content(value)
404
+ if not ok:
405
+ error = self._raise_violation("content_filter", reason)
406
+ return {"error": str(error)}
407
+
408
+ # Human approval check
409
+ if self._needs_approval(tool_name):
410
+ call_id = f"{agent_name}:{tool_name}:{self._tool_call_count}"
411
+ if call_id not in self._approved_calls:
412
+ self._pending_approvals[call_id] = {
413
+ "tool_name": tool_name,
414
+ "tool_args": tool_args,
415
+ "agent_name": agent_name,
416
+ "timestamp": time.time(),
417
+ }
418
+ self._record("approval_required", agent_name, {
419
+ "tool": tool_name, "call_id": call_id,
420
+ })
421
+ error = self._raise_violation(
422
+ "human_approval_required",
423
+ f"Tool '{tool_name}' requires human approval (call_id={call_id})",
424
+ )
425
+ return {"error": str(error), "call_id": call_id, "needs_approval": True}
426
+
427
+ # Track budget spend
428
+ self._budget_spent += cost
429
+
430
+ return None # Allow execution
431
+
432
+ def after_tool_callback(
433
+ self,
434
+ tool_context: Any = None,
435
+ tool_result: Any = None,
436
+ **kwargs: Any,
437
+ ) -> Any:
438
+ """
439
+ ADK after_tool_callback — called after each tool execution.
440
+
441
+ Inspects tool output for blocked patterns.
442
+
443
+ Returns:
444
+ The (possibly modified) tool_result, or a dict with error if blocked.
445
+ """
446
+ tool_name = getattr(tool_context, "tool_name", kwargs.get("tool_name", "unknown"))
447
+ agent_name = getattr(tool_context, "agent_name", kwargs.get("agent_name", "unknown"))
448
+
449
+ self._record("after_tool", agent_name, {"tool": tool_name, "result_type": type(tool_result).__name__})
450
+
451
+ # Check output content
452
+ if isinstance(tool_result, str):
453
+ ok, reason = self._check_content(tool_result)
454
+ if not ok:
455
+ error = self._raise_violation("output_filter", reason)
456
+ return {"error": str(error)}
457
+
458
+ if isinstance(tool_result, dict):
459
+ for value in tool_result.values():
460
+ if isinstance(value, str):
461
+ ok, reason = self._check_content(value)
462
+ if not ok:
463
+ error = self._raise_violation("output_filter", reason)
464
+ return {"error": str(error)}
465
+
466
+ return tool_result
467
+
468
+ def before_agent_callback(self, callback_context: Any = None, **kwargs: Any) -> Any:
469
+ """
470
+ ADK before_agent_callback — called before agent starts processing.
471
+
472
+ Returns:
473
+ None to allow, or a Content-like object to skip the agent.
474
+ """
475
+ agent_name = getattr(callback_context, "agent_name", kwargs.get("agent_name", "unknown"))
476
+
477
+ self._record("before_agent", agent_name, {})
478
+
479
+ # Check timeout
480
+ ok, reason = self._check_timeout()
481
+ if not ok:
482
+ error = self._raise_violation("timeout", reason)
483
+ return {"error": str(error)}
484
+
485
+ # Check agent call count
486
+ self._agent_call_count += 1
487
+ if self._agent_call_count > self._adk_config.max_agent_calls:
488
+ error = self._raise_violation(
489
+ "agent_limit",
490
+ f"Agent call count ({self._agent_call_count}) exceeds limit ({self._adk_config.max_agent_calls})",
491
+ )
492
+ return {"error": str(error)}
493
+
494
+ return None
495
+
496
+ def after_agent_callback(
497
+ self,
498
+ callback_context: Any = None,
499
+ content: Any = None,
500
+ **kwargs: Any,
501
+ ) -> Any:
502
+ """
503
+ ADK after_agent_callback — called after agent finishes.
504
+
505
+ Checks agent output for blocked content.
506
+
507
+ Returns:
508
+ The content (possibly modified), or a dict with error if blocked.
509
+ """
510
+ agent_name = getattr(callback_context, "agent_name", kwargs.get("agent_name", "unknown"))
511
+
512
+ self._record("after_agent", agent_name, {"has_content": content is not None})
513
+
514
+ # Check output content if it's a string
515
+ if isinstance(content, str):
516
+ ok, reason = self._check_content(content)
517
+ if not ok:
518
+ error = self._raise_violation("output_filter", reason)
519
+ return {"error": str(error)}
520
+
521
+ return content
522
+
523
+ # ------------------------------------------------------------------
524
+ # Human Approval API
525
+ # ------------------------------------------------------------------
526
+
527
+ def approve(self, call_id: str) -> bool:
528
+ """Approve a pending tool call by its call_id.
529
+
530
+ Returns True if the call was pending and is now approved.
531
+ """
532
+ if call_id in self._pending_approvals:
533
+ self._approved_calls[call_id] = True
534
+ info = self._pending_approvals.pop(call_id)
535
+ self._record("approval_granted", info.get("agent_name", "unknown"), {
536
+ "call_id": call_id, "tool": info.get("tool_name"),
537
+ })
538
+ return True
539
+ return False
540
+
541
+ def deny(self, call_id: str) -> bool:
542
+ """Deny a pending tool call by its call_id.
543
+
544
+ Returns True if the call was pending and is now denied.
545
+ """
546
+ if call_id in self._pending_approvals:
547
+ info = self._pending_approvals.pop(call_id)
548
+ self._record("approval_denied", 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 get_pending_approvals(self) -> dict[str, dict[str, Any]]:
555
+ """Return all pending approval requests."""
556
+ return dict(self._pending_approvals)
557
+
558
+ # ------------------------------------------------------------------
559
+ # Public API
560
+ # ------------------------------------------------------------------
561
+
562
+ def reset(self) -> None:
563
+ """Reset counters and start time (for new execution runs)."""
564
+ self._tool_call_count = 0
565
+ self._agent_call_count = 0
566
+ self._start_time = time.time()
567
+ self._budget_spent = 0.0
568
+
569
+ def get_audit_log(self) -> list[AuditEvent]:
570
+ """Return the full audit trail."""
571
+ return list(self._audit_log)
572
+
573
+ def get_violations(self) -> list[PolicyViolationError]:
574
+ """Return all collected violations."""
575
+ return list(self._violations)
576
+
577
+ def get_stats(self) -> dict[str, Any]:
578
+ """Get governance statistics."""
579
+ return {
580
+ "tool_calls": self._tool_call_count,
581
+ "agent_calls": self._agent_call_count,
582
+ "violations": len(self._violations),
583
+ "audit_events": len(self._audit_log),
584
+ "elapsed_seconds": round(time.time() - self._start_time, 2),
585
+ "budget_spent": self._budget_spent,
586
+ "budget_limit": self._adk_config.max_budget,
587
+ "pending_approvals": len(self._pending_approvals),
588
+ "policy": {
589
+ "max_tool_calls": self._adk_config.max_tool_calls,
590
+ "max_agent_calls": self._adk_config.max_agent_calls,
591
+ "blocked_tools": self._adk_config.blocked_tools,
592
+ "allowed_tools": self._adk_config.allowed_tools,
593
+ "require_human_approval": self._adk_config.require_human_approval,
594
+ "sensitive_tools": self._adk_config.sensitive_tools,
595
+ },
596
+ }
597
+
598
+ def get_callbacks(self) -> dict[str, Any]:
599
+ """
600
+ Return a dict of all callbacks suitable for unpacking into LlmAgent.
601
+
602
+ Usage:
603
+ agent = LlmAgent(..., **kernel.get_callbacks())
604
+ """
605
+ return {
606
+ "before_tool_callback": self.before_tool_callback,
607
+ "after_tool_callback": self.after_tool_callback,
608
+ "before_agent_callback": self.before_agent_callback,
609
+ "after_agent_callback": self.after_agent_callback,
610
+ }
611
+
612
+ def health_check(self) -> dict[str, Any]:
613
+ """Return adapter health status."""
614
+ elapsed = time.time() - self._start_time
615
+ has_violations = len(self._violations) > 0
616
+ return {
617
+ "status": "degraded" if has_violations else "healthy",
618
+ "backend": "google_adk",
619
+ "adk_available": _HAS_ADK,
620
+ "wrapped_agents": len(self._wrapped_agents),
621
+ "violations": len(self._violations),
622
+ "uptime_seconds": round(elapsed, 2),
623
+ }
624
+
625
+
626
+ __all__ = [
627
+ "GoogleADKKernel",
628
+ "PolicyConfig",
629
+ "PolicyViolationError",
630
+ "AuditEvent",
631
+ "_HAS_ADK",
632
+ "_check_adk_available",
633
+ ]