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