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.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- 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)
|