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,144 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Performance profiling for governance checks and adapter operations.
5
+
6
+ Provides a decorator and context manager for measuring execution time,
7
+ call counts, and memory usage of adapter methods.
8
+ """
9
+
10
+ import functools
11
+ import time
12
+ import tracemalloc
13
+ from dataclasses import dataclass, field
14
+ from typing import Any, Callable, Optional
15
+
16
+
17
+ @dataclass
18
+ class MethodStats:
19
+ """Statistics for a single profiled method."""
20
+ name: str
21
+ call_count: int = 0
22
+ total_time_ms: float = 0.0
23
+ min_time_ms: float = float("inf")
24
+ max_time_ms: float = 0.0
25
+ total_memory_delta: int = 0
26
+
27
+ @property
28
+ def avg_time_ms(self) -> float:
29
+ return self.total_time_ms / self.call_count if self.call_count else 0.0
30
+
31
+
32
+ @dataclass
33
+ class ProfilingReport:
34
+ """Aggregated profiling results."""
35
+ methods: dict[str, MethodStats] = field(default_factory=dict)
36
+
37
+ @property
38
+ def total_calls(self) -> int:
39
+ return sum(m.call_count for m in self.methods.values())
40
+
41
+ @property
42
+ def total_time_ms(self) -> float:
43
+ return sum(m.total_time_ms for m in self.methods.values())
44
+
45
+ def format_report(self) -> str:
46
+ """Return a human-readable table of profiling results."""
47
+ if not self.methods:
48
+ return "No profiling data collected."
49
+ header = f"{'Method':<30} {'Calls':>6} {'Total ms':>10} {'Avg ms':>10} {'Min ms':>10} {'Max ms':>10}"
50
+ sep = "-" * len(header)
51
+ lines = [header, sep]
52
+ for stats in sorted(self.methods.values(), key=lambda s: s.total_time_ms, reverse=True):
53
+ lines.append(
54
+ f"{stats.name:<30} {stats.call_count:>6} "
55
+ f"{stats.total_time_ms:>10.2f} {stats.avg_time_ms:>10.2f} "
56
+ f"{stats.min_time_ms:>10.2f} {stats.max_time_ms:>10.2f}"
57
+ )
58
+ lines.append(sep)
59
+ lines.append(f"{'TOTAL':<30} {self.total_calls:>6} {self.total_time_ms:>10.2f}")
60
+ return "\n".join(lines)
61
+
62
+
63
+ # Global report used by the decorator
64
+ _global_report = ProfilingReport()
65
+
66
+
67
+ def get_report() -> ProfilingReport:
68
+ """Retrieve the global profiling report."""
69
+ return _global_report
70
+
71
+
72
+ def reset_report() -> None:
73
+ """Reset the global profiling report."""
74
+ _global_report.methods.clear()
75
+
76
+
77
+ def profile_governance(func: Optional[Callable] = None, *, track_memory: bool = False):
78
+ """Decorator that profiles execution time (and optionally memory) of a method.
79
+
80
+ Usage:
81
+ @profile_governance
82
+ def my_method(self, ...): ...
83
+
84
+ @profile_governance(track_memory=True)
85
+ def my_method(self, ...): ...
86
+ """
87
+ def decorator(fn: Callable) -> Callable:
88
+ @functools.wraps(fn)
89
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
90
+ key = fn.__name__
91
+ if key not in _global_report.methods:
92
+ _global_report.methods[key] = MethodStats(name=key)
93
+ stats = _global_report.methods[key]
94
+
95
+ mem_before = 0
96
+ if track_memory:
97
+ if not tracemalloc.is_tracing():
98
+ tracemalloc.start()
99
+ _, mem_before = tracemalloc.get_traced_memory()
100
+
101
+ start = time.perf_counter()
102
+ try:
103
+ return fn(*args, **kwargs)
104
+ finally:
105
+ elapsed_ms = (time.perf_counter() - start) * 1000
106
+ stats.call_count += 1
107
+ stats.total_time_ms += elapsed_ms
108
+ stats.min_time_ms = min(stats.min_time_ms, elapsed_ms)
109
+ stats.max_time_ms = max(stats.max_time_ms, elapsed_ms)
110
+
111
+ if track_memory:
112
+ _, mem_after = tracemalloc.get_traced_memory()
113
+ stats.total_memory_delta += mem_after - mem_before
114
+
115
+ return wrapper
116
+
117
+ if func is not None:
118
+ return decorator(func)
119
+ return decorator
120
+
121
+
122
+ class ProfileGovernanceContext:
123
+ """Context manager for scoped profiling.
124
+
125
+ Usage:
126
+ with ProfileGovernanceContext() as report:
127
+ # run profiled code
128
+ print(report.format_report())
129
+ """
130
+
131
+ def __init__(self, track_memory: bool = False):
132
+ self.track_memory = track_memory
133
+ self.report = ProfilingReport()
134
+ self._previous_report: Optional[ProfilingReport] = None
135
+
136
+ def __enter__(self) -> ProfilingReport:
137
+ global _global_report
138
+ self._previous_report = _global_report
139
+ _global_report = self.report
140
+ return self.report
141
+
142
+ def __exit__(self, *exc: Any) -> None:
143
+ global _global_report
144
+ _global_report = self._previous_report # type: ignore
@@ -0,0 +1,420 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ PydanticAI Integration for Agent-OS
5
+ ====================================
6
+
7
+ Provides kernel-level governance for PydanticAI agent workflows.
8
+
9
+ Features:
10
+ - Policy enforcement for agent tool calls
11
+ - Tool call interception via PydanticAI's tool system
12
+ - Human approval workflows for sensitive operations
13
+ - Call budget enforcement (max_tool_calls)
14
+ - Audit logging for all tool executions
15
+ - Blocked pattern detection in tool arguments
16
+ - Graceful degradation when pydantic-ai is not installed
17
+
18
+ Example:
19
+ >>> from agent_os.integrations.pydantic_ai_adapter import PydanticAIKernel
20
+ >>> from agent_os.integrations.base import GovernancePolicy
21
+ >>> from pydantic_ai import Agent
22
+ >>>
23
+ >>> policy = GovernancePolicy(
24
+ ... max_tool_calls=10,
25
+ ... allowed_tools=["search", "read_file"],
26
+ ... blocked_patterns=["rm -rf", "DROP TABLE"],
27
+ ... )
28
+ >>> kernel = PydanticAIKernel(policy=policy)
29
+ >>>
30
+ >>> agent = Agent("openai:gpt-4o", system_prompt="You are helpful.")
31
+ >>> governed = kernel.wrap(agent)
32
+ >>>
33
+ >>> result = await governed.run("Analyze this data")
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import logging
39
+ import time
40
+ from datetime import datetime, timezone
41
+ from functools import wraps
42
+ from typing import Any, Callable
43
+
44
+ from .base import (
45
+ BaseIntegration,
46
+ ExecutionContext,
47
+ GovernancePolicy,
48
+ PolicyInterceptor,
49
+ PolicyViolationError,
50
+ ToolCallRequest,
51
+ ToolCallResult,
52
+ )
53
+
54
+ logger = logging.getLogger(__name__)
55
+
56
+ # Graceful import handling for pydantic-ai
57
+ try:
58
+ import pydantic_ai # noqa: F401
59
+ HAS_PYDANTIC_AI = True
60
+ except ImportError:
61
+ HAS_PYDANTIC_AI = False
62
+
63
+
64
+ class HumanApprovalRequired(PolicyViolationError):
65
+ """Raised when a tool call requires human approval."""
66
+
67
+ def __init__(self, tool_name: str, arguments: dict[str, Any]):
68
+ self.tool_name = tool_name
69
+ self.arguments = arguments
70
+ super().__init__(
71
+ f"Tool '{tool_name}' requires human approval before execution"
72
+ )
73
+
74
+
75
+ class PydanticAIKernel(BaseIntegration):
76
+ """
77
+ PydanticAI adapter for Agent OS.
78
+
79
+ Supports:
80
+ - Agent wrapping with governance (run / run_sync)
81
+ - Individual tool call interception (allowed_tools, blocked_patterns)
82
+ - Human approval workflows for sensitive tools
83
+ - Call budget enforcement (max_tool_calls)
84
+ - Audit logging of all tool executions
85
+ """
86
+
87
+ def __init__(
88
+ self,
89
+ policy: GovernancePolicy | None = None,
90
+ approval_callback: Callable[[str, dict[str, Any]], bool] | None = None,
91
+ ) -> None:
92
+ super().__init__(policy)
93
+ self._wrapped_agents: dict[int, Any] = {}
94
+ self._audit_log: list[dict[str, Any]] = []
95
+ self._approval_callback = approval_callback
96
+ self._start_time: float = time.monotonic()
97
+ self._last_error: str | None = None
98
+ logger.debug("PydanticAIKernel initialized with policy=%s", policy)
99
+
100
+ @property
101
+ def audit_log(self) -> list[dict[str, Any]]:
102
+ """Return the full audit log."""
103
+ return list(self._audit_log)
104
+
105
+ def _record_audit(
106
+ self,
107
+ event_type: str,
108
+ tool_name: str = "",
109
+ allowed: bool = True,
110
+ reason: str = "",
111
+ arguments: dict[str, Any] | None = None,
112
+ agent_id: str = "",
113
+ ) -> dict[str, Any]:
114
+ """Record an audit entry and return it."""
115
+ entry = {
116
+ "timestamp": datetime.now(timezone.utc).isoformat(),
117
+ "event_type": event_type,
118
+ "tool_name": tool_name,
119
+ "allowed": allowed,
120
+ "reason": reason,
121
+ "arguments": arguments or {},
122
+ "agent_id": agent_id,
123
+ }
124
+ if self.policy.log_all_calls:
125
+ self._audit_log.append(entry)
126
+ return entry
127
+
128
+ def wrap(self, agent: Any) -> Any:
129
+ """
130
+ Wrap a PydanticAI Agent with governance.
131
+
132
+ Intercepts:
133
+ - agent.run() / agent.run_sync()
134
+ - All registered tool calls
135
+ - Result validation
136
+
137
+ Args:
138
+ agent: A pydantic_ai.Agent instance (or mock).
139
+
140
+ Returns:
141
+ A governed wrapper around the agent.
142
+ """
143
+ agent_id = getattr(agent, "name", None) or f"agent-{id(agent)}"
144
+ ctx = self.create_context(agent_id)
145
+ self._wrapped_agents[id(agent)] = agent
146
+
147
+ logger.info(
148
+ "Wrapping PydanticAI agent with governance: agent_id=%s", agent_id
149
+ )
150
+
151
+ original = agent
152
+ kernel = self
153
+
154
+ class GovernedPydanticAIAgent:
155
+ """PydanticAI agent wrapped with Agent OS governance."""
156
+
157
+ def __init__(self_inner):
158
+ self_inner._original = original
159
+ self_inner._ctx = ctx
160
+ self_inner._kernel = kernel
161
+ self_inner._agent_id = agent_id
162
+ self_inner._wrap_tools()
163
+
164
+ def _wrap_tools(self_inner):
165
+ """Intercept all tools registered on the agent."""
166
+ tools = _get_agent_tools(self_inner._original)
167
+ for tool_entry in tools:
168
+ _wrap_single_tool(tool_entry, self_inner, kernel, ctx)
169
+
170
+ async def run(self_inner, prompt: str, **kwargs) -> Any:
171
+ """Governed async run."""
172
+ allowed, reason = kernel.pre_execute(ctx, prompt)
173
+ if not allowed:
174
+ kernel._last_error = reason
175
+ kernel._record_audit(
176
+ "run_blocked",
177
+ reason=reason or "",
178
+ agent_id=agent_id,
179
+ )
180
+ raise PolicyViolationError(reason or "Pre-execution check failed")
181
+
182
+ kernel._record_audit(
183
+ "run_start",
184
+ agent_id=agent_id,
185
+ reason=f"prompt_length={len(prompt)}",
186
+ )
187
+
188
+ try:
189
+ result = await self_inner._original.run(prompt, **kwargs)
190
+ kernel._record_audit("run_complete", agent_id=agent_id)
191
+ return result
192
+ except PolicyViolationError:
193
+ raise
194
+ except Exception as exc:
195
+ kernel._last_error = str(exc)
196
+ kernel._record_audit(
197
+ "run_error",
198
+ agent_id=agent_id,
199
+ reason=str(exc),
200
+ allowed=False,
201
+ )
202
+ raise
203
+
204
+ def run_sync(self_inner, prompt: str, **kwargs) -> Any:
205
+ """Governed sync run."""
206
+ allowed, reason = kernel.pre_execute(ctx, prompt)
207
+ if not allowed:
208
+ kernel._last_error = reason
209
+ kernel._record_audit(
210
+ "run_blocked",
211
+ reason=reason or "",
212
+ agent_id=agent_id,
213
+ )
214
+ raise PolicyViolationError(reason or "Pre-execution check failed")
215
+
216
+ kernel._record_audit(
217
+ "run_start",
218
+ agent_id=agent_id,
219
+ reason=f"prompt_length={len(prompt)}",
220
+ )
221
+
222
+ try:
223
+ result = self_inner._original.run_sync(prompt, **kwargs)
224
+ kernel._record_audit("run_complete", agent_id=agent_id)
225
+ return result
226
+ except PolicyViolationError:
227
+ raise
228
+ except Exception as exc:
229
+ kernel._last_error = str(exc)
230
+ kernel._record_audit(
231
+ "run_error",
232
+ agent_id=agent_id,
233
+ reason=str(exc),
234
+ allowed=False,
235
+ )
236
+ raise
237
+
238
+ @property
239
+ def original(self_inner) -> Any:
240
+ """Return the original unwrapped agent before governance wrapping."""
241
+ return self_inner._original
242
+
243
+ @property
244
+ def context(self_inner) -> ExecutionContext:
245
+ """Return the ExecutionContext tracking call counts and session state."""
246
+ return self_inner._ctx
247
+
248
+ def __getattr__(self_inner, name: str) -> Any:
249
+ return getattr(self_inner._original, name)
250
+
251
+ return GovernedPydanticAIAgent()
252
+
253
+ def unwrap(self, governed_agent: Any) -> Any:
254
+ """Remove governance wrapper and return original agent."""
255
+ if hasattr(governed_agent, "_original"):
256
+ return governed_agent._original
257
+ return governed_agent
258
+
259
+ def intercept_tool_call(
260
+ self,
261
+ ctx: ExecutionContext,
262
+ tool_name: str,
263
+ arguments: dict[str, Any],
264
+ ) -> ToolCallResult:
265
+ """
266
+ Evaluate a tool call against the governance policy.
267
+
268
+ Returns a ToolCallResult indicating whether the call is allowed.
269
+ """
270
+ # Handle human approval callback before the interceptor
271
+ if self.policy.require_human_approval:
272
+ if self._approval_callback:
273
+ approved = self._approval_callback(tool_name, arguments)
274
+ if not approved:
275
+ return ToolCallResult(
276
+ allowed=False,
277
+ reason=f"Human approval denied for tool '{tool_name}'",
278
+ )
279
+ # Approved — skip the interceptor's require_human_approval check
280
+ # by using a policy copy without the flag
281
+ from dataclasses import replace
282
+ policy_for_interceptor = replace(self.policy, require_human_approval=False)
283
+ else:
284
+ return ToolCallResult(
285
+ allowed=False,
286
+ reason=f"Tool '{tool_name}' requires human approval",
287
+ )
288
+ else:
289
+ policy_for_interceptor = self.policy
290
+
291
+ interceptor = PolicyInterceptor(policy_for_interceptor, ctx)
292
+ request = ToolCallRequest(
293
+ tool_name=tool_name,
294
+ arguments=arguments,
295
+ agent_id=ctx.agent_id,
296
+ )
297
+ return interceptor.intercept(request)
298
+
299
+ def get_stats(self) -> dict[str, Any]:
300
+ """Get governance statistics."""
301
+ total_calls = sum(c.call_count for c in self.contexts.values())
302
+ return {
303
+ "total_sessions": len(self.contexts),
304
+ "wrapped_agents": len(self._wrapped_agents),
305
+ "total_tool_calls": total_calls,
306
+ "audit_entries": len(self._audit_log),
307
+ "policy": {
308
+ "max_tool_calls": self.policy.max_tool_calls,
309
+ "allowed_tools": self.policy.allowed_tools,
310
+ "blocked_patterns": [
311
+ p if isinstance(p, str) else p[0]
312
+ for p in self.policy.blocked_patterns
313
+ ],
314
+ "require_human_approval": self.policy.require_human_approval,
315
+ },
316
+ }
317
+
318
+ def health_check(self) -> dict[str, Any]:
319
+ """Return adapter health status."""
320
+ uptime = time.monotonic() - self._start_time
321
+ status = "degraded" if self._last_error else "healthy"
322
+ return {
323
+ "status": status,
324
+ "backend": "pydantic_ai",
325
+ "backend_available": HAS_PYDANTIC_AI,
326
+ "backend_connected": bool(self._wrapped_agents),
327
+ "last_error": self._last_error,
328
+ "uptime_seconds": round(uptime, 2),
329
+ }
330
+
331
+
332
+ # ── Helper functions ──────────────────────────────────────────
333
+
334
+
335
+ def _get_agent_tools(agent: Any) -> list:
336
+ """Extract the list of tool entries from a PydanticAI agent."""
337
+ # PydanticAI stores tools in _function_tools (list of Tool objects)
338
+ if hasattr(agent, "_function_tools"):
339
+ return list(agent._function_tools)
340
+ # Fallback for mocks or alternative structures
341
+ if hasattr(agent, "tools"):
342
+ tools = agent.tools
343
+ return list(tools) if tools else []
344
+ return []
345
+
346
+
347
+ def _wrap_single_tool(
348
+ tool_entry: Any,
349
+ governed: Any,
350
+ kernel: PydanticAIKernel,
351
+ ctx: ExecutionContext,
352
+ ) -> None:
353
+ """Wrap a single tool's function with governance interception."""
354
+ if getattr(tool_entry, "_governed", False):
355
+ return
356
+
357
+ # Determine the tool name and callable
358
+ tool_name = getattr(tool_entry, "name", None) or getattr(
359
+ tool_entry, "__name__", str(tool_entry)
360
+ )
361
+ original_fn = getattr(tool_entry, "function", None) or getattr(
362
+ tool_entry, "_run", None
363
+ )
364
+ if original_fn is None:
365
+ return
366
+
367
+ @wraps(original_fn)
368
+ def governed_fn(*args: Any, **kwargs: Any) -> Any:
369
+ """Governed wrapper that validates and delegates PydanticAI tool calls."""
370
+ # Build arguments dict for policy check
371
+ call_args: dict[str, Any] = kwargs.copy()
372
+ if args:
373
+ call_args["_positional"] = list(args)
374
+
375
+ result = kernel.intercept_tool_call(ctx, tool_name, call_args)
376
+
377
+ if not result.allowed:
378
+ kernel._record_audit(
379
+ "tool_blocked",
380
+ tool_name=tool_name,
381
+ allowed=False,
382
+ reason=result.reason or "",
383
+ arguments=call_args,
384
+ agent_id=ctx.agent_id,
385
+ )
386
+ raise PolicyViolationError(
387
+ result.reason or f"Tool '{tool_name}' blocked by policy"
388
+ )
389
+
390
+ ctx.call_count += 1
391
+ kernel._record_audit(
392
+ "tool_executed",
393
+ tool_name=tool_name,
394
+ allowed=True,
395
+ arguments=call_args,
396
+ agent_id=ctx.agent_id,
397
+ )
398
+ return original_fn(*args, **kwargs)
399
+
400
+ # Patch the tool entry
401
+ if hasattr(tool_entry, "function"):
402
+ tool_entry.function = governed_fn
403
+ elif hasattr(tool_entry, "_run"):
404
+ tool_entry._run = governed_fn
405
+
406
+ tool_entry._governed = True
407
+
408
+
409
+ # Convenience function
410
+ def wrap(agent: Any, policy: GovernancePolicy | None = None, **kwargs) -> Any:
411
+ """Quick wrapper for PydanticAI agents."""
412
+ return PydanticAIKernel(policy, **kwargs).wrap(agent)
413
+
414
+
415
+ __all__ = [
416
+ "PydanticAIKernel",
417
+ "HumanApprovalRequired",
418
+ "HAS_PYDANTIC_AI",
419
+ "wrap",
420
+ ]
@@ -0,0 +1,130 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Tool-call rate limiting tied to governance policy.
4
+
5
+ This module enforces token-bucket limits for tool invocations, optionally scoped
6
+ per agent and governed by ``GovernancePolicy.max_tool_calls``.
7
+
8
+ See also:
9
+ - hypervisor.security.rate_limiter: runtime-layer per-agent/per-ring limits.
10
+ - agentmesh.services.rate_limiter: service/proxy-level limits in Agent Mesh.
11
+ - agentmesh.services.rate_limit_middleware: HTTP edge middleware in Agent Mesh.
12
+ - agent_os.policies.rate_limiting: shared token-bucket primitives.
13
+ """
14
+
15
+ import threading
16
+ import time
17
+ from dataclasses import dataclass
18
+ from typing import Optional
19
+
20
+ from .base import GovernancePolicy
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class RateLimitStatus:
25
+ """Snapshot of an agent's rate-limit state."""
26
+ allowed: bool
27
+ remaining_calls: int
28
+ reset_at: float
29
+ wait_seconds: float
30
+
31
+
32
+ class RateLimiter:
33
+ """Thread-safe token-bucket rate limiter for tool calls.
34
+
35
+ Args:
36
+ max_calls: Maximum number of calls allowed per time window (bucket size).
37
+ time_window: Duration of the time window in seconds.
38
+ per_agent: If ``True``, limits are tracked independently per agent.
39
+ If ``False``, a single global bucket is used for all agents.
40
+ policy: Optional GovernancePolicy whose ``max_tool_calls`` overrides
41
+ *max_calls*.
42
+ """
43
+
44
+ _GLOBAL_KEY = "__global__"
45
+
46
+ def __init__(
47
+ self,
48
+ max_calls: int = 10,
49
+ time_window: float = 60.0,
50
+ per_agent: bool = True,
51
+ policy: Optional[GovernancePolicy] = None,
52
+ ) -> None:
53
+ if max_calls <= 0:
54
+ raise ValueError("max_calls must be positive")
55
+ if time_window <= 0:
56
+ raise ValueError("time_window must be positive")
57
+
58
+ self._max_calls = policy.max_tool_calls if policy is not None else max_calls
59
+ self._time_window = float(time_window)
60
+ self._per_agent = per_agent
61
+ self._lock = threading.Lock()
62
+ # Each bucket: (tokens: float, last_refill: float)
63
+ self._buckets: dict[str, list] = {}
64
+
65
+ # ------------------------------------------------------------------
66
+ # Internal helpers
67
+ # ------------------------------------------------------------------
68
+
69
+ def _key(self, agent_id: str) -> str:
70
+ return agent_id if self._per_agent else self._GLOBAL_KEY
71
+
72
+ def _refill(self, bucket: list, now: float) -> None:
73
+ """Add tokens accrued since the last refill."""
74
+ elapsed = now - bucket[1]
75
+ if elapsed > 0:
76
+ rate = self._max_calls / self._time_window
77
+ bucket[0] = min(self._max_calls, bucket[0] + elapsed * rate)
78
+ bucket[1] = now
79
+
80
+ def _get_bucket(self, key: str, now: float) -> list:
81
+ bucket = self._buckets.get(key)
82
+ if bucket is None:
83
+ bucket = [float(self._max_calls), now]
84
+ self._buckets[key] = bucket
85
+ else:
86
+ self._refill(bucket, now)
87
+ return bucket
88
+
89
+ # ------------------------------------------------------------------
90
+ # Public API
91
+ # ------------------------------------------------------------------
92
+
93
+ def allow(self, agent_id: str) -> bool:
94
+ """Try to consume one token. Returns ``True`` if the call is allowed."""
95
+ now = time.monotonic()
96
+ with self._lock:
97
+ bucket = self._get_bucket(self._key(agent_id), now)
98
+ if bucket[0] >= 1.0:
99
+ bucket[0] -= 1.0
100
+ return True
101
+ return False
102
+
103
+ def check(self, agent_id: str) -> RateLimitStatus:
104
+ """Return current rate-limit status without consuming a token."""
105
+ now = time.monotonic()
106
+ with self._lock:
107
+ bucket = self._get_bucket(self._key(agent_id), now)
108
+ remaining = int(bucket[0])
109
+ allowed = remaining >= 1
110
+ if allowed:
111
+ wait = 0.0
112
+ else:
113
+ rate = self._max_calls / self._time_window
114
+ wait = (1.0 - bucket[0]) / rate if rate > 0 else 0.0
115
+ reset_at = now + self._time_window
116
+ return RateLimitStatus(
117
+ allowed=allowed,
118
+ remaining_calls=remaining,
119
+ reset_at=reset_at,
120
+ wait_seconds=wait,
121
+ )
122
+
123
+ def wait_time(self, agent_id: str) -> float:
124
+ """Return seconds until at least one token is available (0.0 if available now)."""
125
+ return self.check(agent_id).wait_seconds
126
+
127
+ def reset(self, agent_id: str) -> None:
128
+ """Reset the bucket for *agent_id* (or the global bucket if ``per_agent=False``)."""
129
+ with self._lock:
130
+ self._buckets.pop(self._key(agent_id), None)