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,654 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ LangChain Integration
5
+
6
+ Wraps LangChain agents/chains with Agent OS governance.
7
+
8
+ Usage:
9
+ from agent_os.integrations import LangChainKernel
10
+
11
+ kernel = LangChainKernel()
12
+ governed_chain = kernel.wrap(my_langchain_chain)
13
+
14
+ # Now all invocations go through Agent OS
15
+ result = governed_chain.invoke({"input": "..."})
16
+ """
17
+
18
+ import asyncio
19
+ import functools
20
+ import logging
21
+ import re
22
+ import time
23
+ from datetime import datetime
24
+ from typing import Any, Optional
25
+
26
+ from .base import BaseIntegration, GovernancePolicy
27
+
28
+ logger = logging.getLogger("agent_os.langchain")
29
+
30
+ # Patterns used to detect potential PII / secrets in memory writes
31
+ _PII_PATTERNS = [
32
+ re.compile(r"\b\d{3}-\d{2}-\d{4}\b"), # SSN
33
+ re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"), # email
34
+ re.compile(r"\b(?:password|passwd|secret|token|api[_-]?key)\s*[:=]\s*\S+", re.IGNORECASE),
35
+ ]
36
+
37
+
38
+ class LangChainKernel(BaseIntegration):
39
+ """
40
+ LangChain adapter for Agent OS.
41
+
42
+ Supports:
43
+ - Chains (invoke, ainvoke)
44
+ - Agents (run, arun)
45
+ - Runnables (invoke, batch, stream)
46
+ - Deep hooks: tool registry interception, memory write validation,
47
+ and sub-agent spawn detection (when ``deep_hooks_enabled`` is True).
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ policy: Optional[GovernancePolicy] = None,
53
+ timeout_seconds: float = 300.0,
54
+ deep_hooks_enabled: bool = True,
55
+ ):
56
+ """Initialise the LangChain governance kernel.
57
+
58
+ Args:
59
+ policy: Governance policy to enforce. When ``None`` the default
60
+ ``GovernancePolicy`` is used.
61
+ timeout_seconds: Default timeout in seconds for async operations
62
+ (default 300).
63
+ deep_hooks_enabled: When ``True`` (default), the kernel will
64
+ apply deep integration hooks — tool registry interception,
65
+ memory write validation, and sub-agent spawn detection —
66
+ during :meth:`wrap`.
67
+ """
68
+ super().__init__(policy)
69
+ self.timeout_seconds = timeout_seconds
70
+ self.deep_hooks_enabled = deep_hooks_enabled
71
+ self._wrapped_agents: dict[int, Any] = {} # id(wrapped) -> original
72
+ self._start_time = time.monotonic()
73
+ self._last_error: Optional[str] = None
74
+ self._tool_invocations: list[dict[str, Any]] = []
75
+ self._memory_audit_log: list[dict[str, Any]] = []
76
+ self._delegation_chains: list[dict[str, Any]] = []
77
+
78
+ # ── Deep Integration Hooks ────────────────────────────────────
79
+
80
+ def _intercept_tool_registry(self, agent: Any, ctx: Any) -> None:
81
+ """Intercept the agent's tool registry to apply per-tool governance.
82
+
83
+ After the agent is wrapped this method inspects its ``tools``
84
+ attribute. Each tool's ``_run`` and ``_arun`` methods are replaced
85
+ with governed wrappers that:
86
+
87
+ * Check the tool name against ``blocked_patterns`` in the active
88
+ policy before every invocation.
89
+ * Track each invocation (tool name, arguments, timestamp) in
90
+ :attr:`_tool_invocations`.
91
+ * Respect the ``allowed_tools`` allowlist when configured.
92
+
93
+ Args:
94
+ agent: The underlying LangChain agent / runnable.
95
+ ctx: The :class:`ExecutionContext` for governance checks.
96
+ """
97
+ tools = getattr(agent, "tools", None)
98
+ if not tools:
99
+ return
100
+
101
+ for tool in tools:
102
+ if getattr(tool, "_deep_governed", False):
103
+ continue
104
+ tool_name = getattr(tool, "name", type(tool).__name__)
105
+ self._wrap_tool_method(tool, tool_name, "_run", ctx)
106
+ self._wrap_tool_method(tool, tool_name, "_arun", ctx, is_async=True)
107
+ tool._deep_governed = True
108
+ logger.debug("Deep-governed tool registered: %s", tool_name)
109
+
110
+ def _wrap_tool_method(
111
+ self,
112
+ tool: Any,
113
+ tool_name: str,
114
+ method_name: str,
115
+ ctx: Any,
116
+ is_async: bool = False,
117
+ ) -> None:
118
+ """Replace a single tool method with a governed wrapper.
119
+
120
+ Args:
121
+ tool: The LangChain tool object.
122
+ tool_name: Human-readable tool name for logging/audit.
123
+ method_name: The attribute to patch (``"_run"`` or ``"_arun"``).
124
+ ctx: Execution context.
125
+ is_async: Whether the target method is a coroutine.
126
+ """
127
+ original_method = getattr(tool, method_name, None)
128
+ if original_method is None:
129
+ return
130
+
131
+ kernel = self
132
+
133
+ if is_async:
134
+ @functools.wraps(original_method)
135
+ async def governed_async(*args: Any, **kwargs: Any) -> Any:
136
+ kernel._check_tool_policy(tool_name, args, kwargs, ctx)
137
+ kernel._record_tool_invocation(tool_name, args, kwargs)
138
+ return await original_method(*args, **kwargs)
139
+
140
+ setattr(tool, method_name, governed_async)
141
+ else:
142
+ @functools.wraps(original_method)
143
+ def governed_sync(*args: Any, **kwargs: Any) -> Any:
144
+ kernel._check_tool_policy(tool_name, args, kwargs, ctx)
145
+ kernel._record_tool_invocation(tool_name, args, kwargs)
146
+ return original_method(*args, **kwargs)
147
+
148
+ setattr(tool, method_name, governed_sync)
149
+
150
+ def _check_tool_policy(
151
+ self, tool_name: str, args: Any, kwargs: Any, ctx: Any
152
+ ) -> None:
153
+ """Validate a tool call against the active governance policy.
154
+
155
+ Raises :class:`PolicyViolationError` if the tool is not allowed or
156
+ if its arguments match a blocked pattern.
157
+ """
158
+ # Allowed-tools check
159
+ if self.policy.allowed_tools and tool_name not in self.policy.allowed_tools:
160
+ raise PolicyViolationError(
161
+ f"Tool '{tool_name}' not in allowed list: {self.policy.allowed_tools}"
162
+ )
163
+
164
+ # Blocked-patterns check on arguments
165
+ args_str = str(args) + str(kwargs)
166
+ matched = self.policy.matches_pattern(args_str)
167
+ if matched:
168
+ raise PolicyViolationError(
169
+ f"Blocked pattern '{matched[0]}' detected in tool '{tool_name}' arguments"
170
+ )
171
+
172
+ # Blocked-patterns check on tool name itself
173
+ name_matched = self.policy.matches_pattern(tool_name)
174
+ if name_matched:
175
+ raise PolicyViolationError(
176
+ f"Tool '{tool_name}' matches blocked pattern '{name_matched[0]}'"
177
+ )
178
+
179
+ def _record_tool_invocation(
180
+ self, tool_name: str, args: Any, kwargs: Any
181
+ ) -> None:
182
+ """Append a tool invocation record to the audit log."""
183
+ record = {
184
+ "tool_name": tool_name,
185
+ "args": str(args),
186
+ "kwargs": str(kwargs),
187
+ "timestamp": datetime.now().isoformat(),
188
+ }
189
+ self._tool_invocations.append(record)
190
+ if self.policy.log_all_calls:
191
+ logger.info("Tool invocation: %s", record)
192
+
193
+ # ── Memory Write Interception ─────────────────────────────────
194
+
195
+ def _intercept_memory(self, agent: Any, ctx: Any) -> None:
196
+ """Intercept memory writes on the wrapped agent.
197
+
198
+ If the underlying object exposes a ``memory`` attribute with a
199
+ ``save_context`` method, that method is replaced with a governed
200
+ wrapper that:
201
+
202
+ * Validates the data being written against PII / secret patterns.
203
+ * Checks data against ``blocked_patterns`` in the active policy.
204
+ * Logs every memory write to :attr:`_memory_audit_log`.
205
+
206
+ Args:
207
+ agent: The underlying LangChain agent / chain.
208
+ ctx: The :class:`ExecutionContext` for governance checks.
209
+ """
210
+ memory = getattr(agent, "memory", None)
211
+ if memory is None:
212
+ return
213
+
214
+ save_context = getattr(memory, "save_context", None)
215
+ if save_context is None or getattr(memory, "_deep_governed", False):
216
+ return
217
+
218
+ kernel = self
219
+
220
+ @functools.wraps(save_context)
221
+ def governed_save_context(inputs: Any, outputs: Any) -> Any:
222
+ """Governed wrapper around ``memory.save_context``."""
223
+ kernel._validate_memory_write(inputs, outputs, ctx)
224
+ result = save_context(inputs, outputs)
225
+ kernel._memory_audit_log.append({
226
+ "action": "save_context",
227
+ "inputs_summary": str(inputs)[:200],
228
+ "outputs_summary": str(outputs)[:200],
229
+ "timestamp": datetime.now().isoformat(),
230
+ "agent_id": ctx.agent_id,
231
+ })
232
+ logger.debug(
233
+ "Memory write recorded for agent=%s", ctx.agent_id
234
+ )
235
+ return result
236
+
237
+ memory.save_context = governed_save_context
238
+ memory._deep_governed = True
239
+
240
+ def _validate_memory_write(
241
+ self, inputs: Any, outputs: Any, ctx: Any
242
+ ) -> None:
243
+ """Check memory content for PII, secrets, and blocked patterns.
244
+
245
+ Raises :class:`PolicyViolationError` if the content being written
246
+ to memory matches any PII pattern or blocked policy pattern.
247
+
248
+ Args:
249
+ inputs: The input dict being stored.
250
+ outputs: The output dict being stored.
251
+ ctx: Execution context.
252
+ """
253
+ combined = str(inputs) + str(outputs)
254
+
255
+ # PII / secrets detection
256
+ for pattern in _PII_PATTERNS:
257
+ if pattern.search(combined):
258
+ raise PolicyViolationError(
259
+ f"Memory write blocked: sensitive data detected "
260
+ f"(pattern: {pattern.pattern})"
261
+ )
262
+
263
+ # Policy blocked-patterns check
264
+ matched = self.policy.matches_pattern(combined)
265
+ if matched:
266
+ raise PolicyViolationError(
267
+ f"Memory write blocked: blocked pattern '{matched[0]}' detected"
268
+ )
269
+
270
+ # ── Sub-agent Spawn Detection ─────────────────────────────────
271
+
272
+ def _detect_agent_spawning(self, agent: Any, ctx: Any) -> None:
273
+ """Wrap ``invoke`` calls to detect and govern sub-agent delegation.
274
+
275
+ Monitors the agent's ``invoke`` method (if present on the original
276
+ object) for delegation patterns. Each invocation increments a
277
+ depth counter and is checked against the policy's
278
+ ``max_tool_calls`` as a proxy for maximum delegation depth.
279
+
280
+ Delegation chains are recorded in :attr:`_delegation_chains`.
281
+
282
+ Args:
283
+ agent: The underlying LangChain agent / runnable.
284
+ ctx: The :class:`ExecutionContext` for governance checks.
285
+ """
286
+ original_invoke = getattr(agent, "invoke", None)
287
+ if original_invoke is None or getattr(agent, "_spawn_governed", False):
288
+ return
289
+
290
+ kernel = self
291
+ max_depth = self.policy.max_tool_calls # reuse as delegation depth cap
292
+
293
+ @functools.wraps(original_invoke)
294
+ def governed_invoke(input_data: Any, **kwargs: Any) -> Any:
295
+ # Track delegation depth via ctx metadata
296
+ depth = len(kernel._delegation_chains) + 1
297
+ if depth > max_depth:
298
+ raise PolicyViolationError(
299
+ f"Max delegation depth ({max_depth}) exceeded at depth {depth}"
300
+ )
301
+
302
+ chain_record = {
303
+ "parent_agent": ctx.agent_id,
304
+ "depth": depth,
305
+ "input_summary": str(input_data)[:200],
306
+ "timestamp": datetime.now().isoformat(),
307
+ }
308
+ kernel._delegation_chains.append(chain_record)
309
+ logger.info(
310
+ "Sub-agent delegation detected: agent=%s depth=%d",
311
+ ctx.agent_id, depth,
312
+ )
313
+
314
+ return original_invoke(input_data, **kwargs)
315
+
316
+ agent.invoke = governed_invoke
317
+ agent._spawn_governed = True
318
+
319
+ # ── wrap / unwrap ─────────────────────────────────────────────
320
+
321
+ def wrap(self, agent: Any) -> Any:
322
+ """Wrap a LangChain chain, agent, or runnable with governance.
323
+
324
+ Creates a proxy object that intercepts all execution methods
325
+ (``invoke``, ``ainvoke``, ``run``, ``batch``, ``stream``) and
326
+ applies pre-/post-execution policy checks.
327
+
328
+ When :attr:`deep_hooks_enabled` is ``True`` (the default) the
329
+ following additional hooks are applied:
330
+
331
+ * **Tool registry interception** — each tool's ``_run`` / ``_arun``
332
+ is wrapped with governance checks.
333
+ * **Memory write interception** — ``memory.save_context`` is
334
+ validated for PII and blocked patterns.
335
+ * **Sub-agent spawn detection** — ``invoke`` calls are monitored
336
+ for delegation depth.
337
+
338
+ The wrapping strategy uses a dynamically created inner class so that
339
+ attribute access for non-execution methods (e.g. ``name``,
340
+ ``verbose``) is transparently forwarded to the original object.
341
+
342
+ Args:
343
+ agent: Any LangChain-compatible object that exposes ``invoke``,
344
+ ``run``, ``batch``, or ``stream`` methods.
345
+
346
+ Returns:
347
+ A ``GovernedLangChainAgent`` proxy whose execution calls are
348
+ subject to governance.
349
+
350
+ Raises:
351
+ PolicyViolationError: Raised at execution time if input or
352
+ output violates the active policy.
353
+
354
+ Example:
355
+ >>> kernel = LangChainKernel(policy=GovernancePolicy(
356
+ ... blocked_patterns=["DROP TABLE"]
357
+ ... ))
358
+ >>> governed = kernel.wrap(my_chain)
359
+ >>> result = governed.invoke({"input": "safe query"})
360
+ """
361
+ # Get agent ID from the object
362
+ agent_id = getattr(agent, 'name', None) or f"langchain-{id(agent)}"
363
+ ctx = self.create_context(agent_id)
364
+
365
+ # Store original
366
+ self._wrapped_agents[id(agent)] = agent
367
+
368
+ # Apply deep hooks before creating the wrapper class
369
+ if self.deep_hooks_enabled:
370
+ try:
371
+ self._intercept_tool_registry(agent, ctx)
372
+ except Exception as exc:
373
+ logger.warning("Tool registry interception failed: %s", exc)
374
+ try:
375
+ self._intercept_memory(agent, ctx)
376
+ except Exception as exc:
377
+ logger.warning("Memory interception failed: %s", exc)
378
+ try:
379
+ self._detect_agent_spawning(agent, ctx)
380
+ except Exception as exc:
381
+ logger.warning("Agent spawn detection setup failed: %s", exc)
382
+
383
+ # Create wrapper class
384
+ original = agent
385
+ kernel = self
386
+
387
+ class GovernedLangChainAgent:
388
+ """LangChain agent wrapped with Agent OS governance"""
389
+
390
+ def __init__(self):
391
+ self._original = original
392
+ self._ctx = ctx
393
+ self._kernel = kernel
394
+
395
+ def invoke(self, input_data: Any, **kwargs) -> Any:
396
+ """Governed synchronous invocation.
397
+
398
+ Args:
399
+ input_data: Input to pass to the chain/agent.
400
+ **kwargs: Extra arguments forwarded to the original
401
+ ``invoke`` call.
402
+
403
+ Returns:
404
+ The result from the underlying chain/agent.
405
+
406
+ Raises:
407
+ PolicyViolationError: If the input or output violates
408
+ governance policy.
409
+ """
410
+ logger.debug("invoke called with input=%r kwargs=%r", input_data, kwargs)
411
+ # Pre-check
412
+ allowed, reason = self._kernel.pre_execute(self._ctx, input_data)
413
+ if not allowed:
414
+ logger.info("Policy DENY on invoke: %s", reason)
415
+ raise PolicyViolationError(reason)
416
+ logger.info("Policy ALLOW on invoke")
417
+
418
+ # Execute
419
+ try:
420
+ result = self._original.invoke(input_data, **kwargs)
421
+ except Exception as exc:
422
+ logger.error("invoke failed: %s", exc)
423
+ self._kernel._last_error = str(exc)
424
+ raise
425
+
426
+ # Post-check
427
+ valid, reason = self._kernel.post_execute(self._ctx, result)
428
+ if not valid:
429
+ logger.info("Policy DENY on invoke result: %s", reason)
430
+ raise PolicyViolationError(reason)
431
+
432
+ return result
433
+
434
+ async def ainvoke(self, input_data: Any, **kwargs) -> Any:
435
+ """Governed asynchronous invocation.
436
+
437
+ Async counterpart of :meth:`invoke` — applies identical
438
+ pre-/post-execution policy checks with timeout support.
439
+
440
+ Args:
441
+ input_data: Input to pass to the chain/agent.
442
+ **kwargs: Extra arguments forwarded to the original
443
+ ``ainvoke`` call.
444
+
445
+ Returns:
446
+ The result from the underlying chain/agent.
447
+
448
+ Raises:
449
+ PolicyViolationError: If the input or output violates
450
+ governance policy.
451
+ asyncio.TimeoutError: If the operation exceeds the timeout.
452
+ """
453
+ logger.debug("ainvoke called with input=%r kwargs=%r", input_data, kwargs)
454
+ allowed, reason = self._kernel.pre_execute(self._ctx, input_data)
455
+ if not allowed:
456
+ logger.info("Policy DENY on ainvoke: %s", reason)
457
+ raise PolicyViolationError(reason)
458
+ logger.info("Policy ALLOW on ainvoke")
459
+
460
+ try:
461
+ result = await asyncio.wait_for(
462
+ self._original.ainvoke(input_data, **kwargs),
463
+ timeout=self._kernel.timeout_seconds,
464
+ )
465
+ except asyncio.TimeoutError:
466
+ logger.warning(
467
+ "ainvoke timed out after %ss", self._kernel.timeout_seconds
468
+ )
469
+ self._kernel._last_error = "timeout"
470
+ raise
471
+ except Exception as exc:
472
+ logger.error("ainvoke failed: %s", exc)
473
+ self._kernel._last_error = str(exc)
474
+ raise
475
+
476
+ valid, reason = self._kernel.post_execute(self._ctx, result)
477
+ if not valid:
478
+ logger.info("Policy DENY on ainvoke result: %s", reason)
479
+ raise PolicyViolationError(reason)
480
+
481
+ return result
482
+
483
+ def run(self, *args, **kwargs) -> Any:
484
+ """Governed run for legacy LangChain agents.
485
+
486
+ Args:
487
+ *args: Positional arguments; the first is treated as
488
+ the input for policy checking.
489
+ **kwargs: Keyword arguments forwarded to the original
490
+ ``run`` call.
491
+
492
+ Returns:
493
+ The result from the underlying agent.
494
+
495
+ Raises:
496
+ PolicyViolationError: If the input or output violates
497
+ governance policy.
498
+ """
499
+ input_data = args[0] if args else kwargs
500
+ logger.debug("run called with input=%r", input_data)
501
+ allowed, reason = self._kernel.pre_execute(self._ctx, input_data)
502
+ if not allowed:
503
+ logger.info("Policy DENY on run: %s", reason)
504
+ raise PolicyViolationError(reason)
505
+ logger.info("Policy ALLOW on run")
506
+
507
+ try:
508
+ result = self._original.run(*args, **kwargs)
509
+ except Exception as exc:
510
+ logger.error("run failed: %s", exc)
511
+ self._kernel._last_error = str(exc)
512
+ raise
513
+
514
+ valid, reason = self._kernel.post_execute(self._ctx, result)
515
+ if not valid:
516
+ logger.info("Policy DENY on run result: %s", reason)
517
+ raise PolicyViolationError(reason)
518
+
519
+ return result
520
+
521
+ def batch(self, inputs: list, **kwargs) -> list:
522
+ """Governed batch execution.
523
+
524
+ Each input in the batch is individually checked against
525
+ the governance policy before the batch is submitted.
526
+
527
+ Args:
528
+ inputs: List of inputs to process.
529
+ **kwargs: Extra arguments forwarded to the original
530
+ ``batch`` call.
531
+
532
+ Returns:
533
+ List of results from the underlying chain/agent.
534
+
535
+ Raises:
536
+ PolicyViolationError: If any input or output in the
537
+ batch violates governance policy.
538
+ """
539
+ logger.debug("batch called with %d inputs", len(inputs))
540
+ for inp in inputs:
541
+ allowed, reason = self._kernel.pre_execute(self._ctx, inp)
542
+ if not allowed:
543
+ logger.info("Policy DENY on batch input: %s", reason)
544
+ raise PolicyViolationError(reason)
545
+ logger.info("Policy ALLOW on batch (%d inputs)", len(inputs))
546
+
547
+ try:
548
+ results = self._original.batch(inputs, **kwargs)
549
+ except Exception as exc:
550
+ logger.error("batch failed: %s", exc)
551
+ self._kernel._last_error = str(exc)
552
+ raise
553
+
554
+ for result in results:
555
+ valid, reason = self._kernel.post_execute(self._ctx, result)
556
+ if not valid:
557
+ logger.info("Policy DENY on batch result: %s", reason)
558
+ raise PolicyViolationError(reason)
559
+
560
+ return results
561
+
562
+ def stream(self, input_data: Any, **kwargs):
563
+ """Governed streaming execution.
564
+
565
+ The input is policy-checked before streaming begins.
566
+ Individual chunks are yielded as-is; a post-execution
567
+ check runs after the stream is fully consumed.
568
+
569
+ Args:
570
+ input_data: Input to pass to the chain/agent.
571
+ **kwargs: Extra arguments forwarded to the original
572
+ ``stream`` call.
573
+
574
+ Yields:
575
+ Chunks from the underlying stream.
576
+
577
+ Raises:
578
+ PolicyViolationError: If the input violates governance
579
+ policy.
580
+ """
581
+ logger.debug("stream called with input=%r", input_data)
582
+ allowed, reason = self._kernel.pre_execute(self._ctx, input_data)
583
+ if not allowed:
584
+ logger.info("Policy DENY on stream: %s", reason)
585
+ raise PolicyViolationError(reason)
586
+ logger.info("Policy ALLOW on stream")
587
+
588
+ yield from self._original.stream(input_data, **kwargs)
589
+
590
+ self._kernel.post_execute(self._ctx, None)
591
+
592
+ # Passthrough for non-execution methods
593
+ def __getattr__(self, name):
594
+ return getattr(self._original, name)
595
+
596
+ return GovernedLangChainAgent()
597
+
598
+ def unwrap(self, governed_agent: Any) -> Any:
599
+ """Retrieve the original unwrapped LangChain object.
600
+
601
+ Args:
602
+ governed_agent: A governed wrapper returned by :meth:`wrap`.
603
+
604
+ Returns:
605
+ The original LangChain chain, agent, or runnable.
606
+ """
607
+ return governed_agent._original
608
+
609
+ def health_check(self) -> dict[str, Any]:
610
+ """Return adapter health status.
611
+
612
+ Returns:
613
+ A dict with ``status``, ``backend``, ``last_error``, and
614
+ ``uptime_seconds`` keys.
615
+ """
616
+ uptime = time.monotonic() - self._start_time
617
+ status = "degraded" if self._last_error else "healthy"
618
+ return {
619
+ "status": status,
620
+ "backend": "langchain",
621
+ "backend_connected": True,
622
+ "last_error": self._last_error,
623
+ "uptime_seconds": round(uptime, 2),
624
+ }
625
+
626
+
627
+ class PolicyViolationError(Exception):
628
+ """Raised when a LangChain agent/chain violates governance policy."""
629
+
630
+ pass
631
+
632
+
633
+ # Convenience function
634
+ def wrap(
635
+ agent: Any,
636
+ policy: Optional[GovernancePolicy] = None,
637
+ timeout_seconds: float = 300.0,
638
+ ) -> Any:
639
+ """Convenience wrapper for LangChain agents and chains.
640
+
641
+ Args:
642
+ agent: Any LangChain-compatible object.
643
+ policy: Optional governance policy (uses defaults when ``None``).
644
+ timeout_seconds: Default timeout in seconds (default 300).
645
+
646
+ Returns:
647
+ A governed proxy around *agent*.
648
+
649
+ Example:
650
+ >>> from agent_os.integrations.langchain_adapter import wrap
651
+ >>> governed = wrap(my_chain, policy=GovernancePolicy(max_tokens=5000))
652
+ >>> result = governed.invoke({"input": "hello"})
653
+ """
654
+ return LangChainKernel(policy, timeout_seconds=timeout_seconds).wrap(agent)