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,394 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Guardrails AI Bridge for Agent-OS
|
|
5
|
+
===================================
|
|
6
|
+
|
|
7
|
+
Bridges Guardrails AI validators with Agent-OS policy enforcement.
|
|
8
|
+
|
|
9
|
+
Agent-OS enforces your Guardrails AI validators at the kernel level —
|
|
10
|
+
policy violations trigger Agent-OS signals (SIGKILL, SIGPOLICYVIOLATION).
|
|
11
|
+
|
|
12
|
+
Works without importing guardrails — uses a Protocol interface so you can
|
|
13
|
+
plug in any validator that implements ``validate(value) -> ValidationOutcome``.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> from agent_os.integrations.guardrails_adapter import GuardrailsKernel
|
|
17
|
+
>>>
|
|
18
|
+
>>> kernel = GuardrailsKernel(
|
|
19
|
+
... validators=[PIIValidator(), ToxicityValidator()],
|
|
20
|
+
... on_fail="block", # or "warn", "fix"
|
|
21
|
+
... )
|
|
22
|
+
>>>
|
|
23
|
+
>>> result = kernel.validate_input("My SSN is 123-45-6789")
|
|
24
|
+
>>> assert not result.passed # PII detected
|
|
25
|
+
>>>
|
|
26
|
+
>>> result = kernel.validate_output("Safe response text")
|
|
27
|
+
>>> assert result.passed
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
import time
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
from enum import Enum
|
|
36
|
+
from typing import Any, Callable, Protocol, runtime_checkable
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ------------------------------------------------------------------
|
|
42
|
+
# Validator Protocol (no guardrails import required)
|
|
43
|
+
# ------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FailAction(str, Enum):
|
|
47
|
+
"""What to do when a validator fails."""
|
|
48
|
+
|
|
49
|
+
BLOCK = "block"
|
|
50
|
+
WARN = "warn"
|
|
51
|
+
FIX = "fix"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@runtime_checkable
|
|
55
|
+
class ValidatorProtocol(Protocol):
|
|
56
|
+
"""
|
|
57
|
+
Protocol for Guardrails AI validators (or any compatible validator).
|
|
58
|
+
|
|
59
|
+
A validator must implement ``validate(value, metadata)`` and return
|
|
60
|
+
a ``ValidationResult``-like object with ``outcome`` and ``error_message``.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def name(self) -> str: ...
|
|
65
|
+
|
|
66
|
+
def validate(self, value: str, metadata: dict[str, Any] | None = None) -> Any: ...
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class ValidationOutcome:
|
|
71
|
+
"""Result of a single validator check."""
|
|
72
|
+
|
|
73
|
+
validator_name: str
|
|
74
|
+
passed: bool
|
|
75
|
+
error_message: str = ""
|
|
76
|
+
fixed_value: str | None = None
|
|
77
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
78
|
+
|
|
79
|
+
def to_dict(self) -> dict[str, Any]:
|
|
80
|
+
"""Serialise this outcome to a plain dictionary.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
A dict with validator, passed, and optionally error
|
|
84
|
+
and fixed_value keys.
|
|
85
|
+
"""
|
|
86
|
+
d: dict[str, Any] = {
|
|
87
|
+
"validator": self.validator_name,
|
|
88
|
+
"passed": self.passed,
|
|
89
|
+
}
|
|
90
|
+
if self.error_message:
|
|
91
|
+
d["error"] = self.error_message
|
|
92
|
+
if self.fixed_value is not None:
|
|
93
|
+
d["fixed_value"] = self.fixed_value
|
|
94
|
+
return d
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class ValidationResult:
|
|
99
|
+
"""Aggregated result across all validators."""
|
|
100
|
+
|
|
101
|
+
passed: bool
|
|
102
|
+
outcomes: list[ValidationOutcome]
|
|
103
|
+
original_value: str
|
|
104
|
+
final_value: str
|
|
105
|
+
action_taken: FailAction
|
|
106
|
+
timestamp: float = field(default_factory=time.time)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def failed_validators(self) -> list[str]:
|
|
110
|
+
"""Return the names of all validators that did not pass.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of validator name strings where passed is False.
|
|
114
|
+
"""
|
|
115
|
+
return [o.validator_name for o in self.outcomes if not o.passed]
|
|
116
|
+
|
|
117
|
+
def to_dict(self) -> dict[str, Any]:
|
|
118
|
+
"""Serialise this aggregated result to a plain dictionary.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
A dict with passed, action, outcomes, and failed_validators keys.
|
|
122
|
+
"""
|
|
123
|
+
return {
|
|
124
|
+
"passed": self.passed,
|
|
125
|
+
"action": self.action_taken.value,
|
|
126
|
+
"outcomes": [o.to_dict() for o in self.outcomes],
|
|
127
|
+
"failed_validators": self.failed_validators,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ------------------------------------------------------------------
|
|
132
|
+
# Built-in simple validators (no guardrails dependency)
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class RegexValidator:
|
|
137
|
+
"""Block content matching regex patterns."""
|
|
138
|
+
|
|
139
|
+
def __init__(self, patterns: list[str], validator_name: str = "regex"):
|
|
140
|
+
import re
|
|
141
|
+
|
|
142
|
+
self._patterns = [re.compile(p, re.IGNORECASE) for p in patterns]
|
|
143
|
+
self._name = validator_name
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def name(self) -> str:
|
|
147
|
+
"""Return the human-readable name of this regex validator.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
The validator name string used in audit logs and outcomes.
|
|
151
|
+
"""
|
|
152
|
+
return self._name
|
|
153
|
+
|
|
154
|
+
def validate(self, value: str, metadata: dict[str, Any] | None = None) -> ValidationOutcome:
|
|
155
|
+
"""Validate a string by checking it against blocked regex patterns.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
value: The text to scan.
|
|
159
|
+
metadata: Optional dict of additional context (unused).
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
ValidationOutcome indicating pass or fail.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
for pattern in self._patterns:
|
|
166
|
+
match = pattern.search(value)
|
|
167
|
+
if match:
|
|
168
|
+
return ValidationOutcome(
|
|
169
|
+
validator_name=self._name,
|
|
170
|
+
passed=False,
|
|
171
|
+
error_message=f"Content matches blocked pattern: {match.group()}",
|
|
172
|
+
)
|
|
173
|
+
return ValidationOutcome(validator_name=self._name, passed=True)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class LengthValidator:
|
|
177
|
+
"""Enforce content length limits."""
|
|
178
|
+
|
|
179
|
+
def __init__(self, max_length: int = 10000, validator_name: str = "length"):
|
|
180
|
+
self._max_length = max_length
|
|
181
|
+
self._name = validator_name
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def name(self) -> str:
|
|
185
|
+
"""Return the human-readable name of this length validator.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
The validator name string used in audit logs and outcomes.
|
|
189
|
+
"""
|
|
190
|
+
return self._name
|
|
191
|
+
|
|
192
|
+
def validate(self, value: str, metadata: dict[str, Any] | None = None) -> ValidationOutcome:
|
|
193
|
+
"""Validate that a string does not exceed the configured max length.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
value: The text to check.
|
|
197
|
+
metadata: Optional dict of additional context (unused).
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
ValidationOutcome with a fixed_value truncated to max_length on fail.
|
|
201
|
+
"""
|
|
202
|
+
if len(value) > self._max_length:
|
|
203
|
+
return ValidationOutcome(
|
|
204
|
+
validator_name=self._name,
|
|
205
|
+
passed=False,
|
|
206
|
+
error_message=f"Content length {len(value)} exceeds max {self._max_length}",
|
|
207
|
+
fixed_value=value[: self._max_length],
|
|
208
|
+
)
|
|
209
|
+
return ValidationOutcome(validator_name=self._name, passed=True)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class KeywordValidator:
|
|
213
|
+
"""Block content containing specific keywords."""
|
|
214
|
+
|
|
215
|
+
def __init__(self, blocked_keywords: list[str], validator_name: str = "keywords"):
|
|
216
|
+
self._keywords = [k.lower() for k in blocked_keywords]
|
|
217
|
+
self._name = validator_name
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def name(self) -> str:
|
|
221
|
+
"""Return the human-readable name of this keyword validator.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
The validator name string used in audit logs and outcomes.
|
|
225
|
+
"""
|
|
226
|
+
return self._name
|
|
227
|
+
|
|
228
|
+
def validate(self, value: str, metadata: dict[str, Any] | None = None) -> ValidationOutcome:
|
|
229
|
+
"""Validate that a string contains none of the blocked keywords.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
value: The text to scan (case-insensitive).
|
|
233
|
+
metadata: Optional dict of additional context (unused).
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
ValidationOutcome indicating pass or fail.
|
|
237
|
+
"""
|
|
238
|
+
value_lower = value.lower()
|
|
239
|
+
for kw in self._keywords:
|
|
240
|
+
if kw in value_lower:
|
|
241
|
+
return ValidationOutcome(
|
|
242
|
+
validator_name=self._name,
|
|
243
|
+
passed=False,
|
|
244
|
+
error_message=f"Content contains blocked keyword: '{kw}'",
|
|
245
|
+
)
|
|
246
|
+
return ValidationOutcome(validator_name=self._name, passed=True)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# ------------------------------------------------------------------
|
|
250
|
+
# Guardrails Kernel
|
|
251
|
+
# ------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class GuardrailsKernel:
|
|
255
|
+
"""
|
|
256
|
+
Agent-OS governance kernel backed by Guardrails AI validators.
|
|
257
|
+
|
|
258
|
+
Validates inputs and outputs against a chain of validators.
|
|
259
|
+
Failed validations are recorded and trigger configurable actions.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def __init__(
|
|
263
|
+
self,
|
|
264
|
+
validators: list[Any] | None = None,
|
|
265
|
+
on_fail: str = "block",
|
|
266
|
+
on_violation: Callable[[ValidationResult], None] | None = None,
|
|
267
|
+
):
|
|
268
|
+
self._validators: list[Any] = validators or []
|
|
269
|
+
self.on_fail = FailAction(on_fail)
|
|
270
|
+
self.on_violation = on_violation or self._default_violation_handler
|
|
271
|
+
self._history: list[ValidationResult] = []
|
|
272
|
+
|
|
273
|
+
def _default_violation_handler(self, result: ValidationResult) -> None:
|
|
274
|
+
"""Default handler called when one or more validators fail.
|
|
275
|
+
|
|
276
|
+
Logs a warning for each failed validator name. Override by
|
|
277
|
+
passing a custom on_violation callable to GuardrailsKernel.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
result: The aggregated ValidationResult.
|
|
281
|
+
"""
|
|
282
|
+
for name in result.failed_validators:
|
|
283
|
+
logger.warning(f"Guardrail violation: {name}")
|
|
284
|
+
|
|
285
|
+
def add_validator(self, validator: Any) -> None:
|
|
286
|
+
"""Add a validator to the chain."""
|
|
287
|
+
self._validators.append(validator)
|
|
288
|
+
|
|
289
|
+
def _run_validators(self, value: str) -> list[ValidationOutcome]:
|
|
290
|
+
"""Run all validators against a value."""
|
|
291
|
+
outcomes = []
|
|
292
|
+
for v in self._validators:
|
|
293
|
+
try:
|
|
294
|
+
result = v.validate(value)
|
|
295
|
+
# Handle both our ValidationOutcome and Guardrails AI objects
|
|
296
|
+
if isinstance(result, ValidationOutcome):
|
|
297
|
+
outcomes.append(result)
|
|
298
|
+
else:
|
|
299
|
+
# Duck-type: expect .outcome / .validated_output / .error_message
|
|
300
|
+
passed = getattr(result, "outcome", "pass") == "pass"
|
|
301
|
+
error_msg = getattr(result, "error_message", "")
|
|
302
|
+
fixed = getattr(result, "validated_output", None)
|
|
303
|
+
outcomes.append(
|
|
304
|
+
ValidationOutcome(
|
|
305
|
+
validator_name=getattr(v, "name", type(v).__name__),
|
|
306
|
+
passed=passed,
|
|
307
|
+
error_message=str(error_msg) if error_msg else "",
|
|
308
|
+
fixed_value=fixed,
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
except Exception as e:
|
|
312
|
+
outcomes.append(
|
|
313
|
+
ValidationOutcome(
|
|
314
|
+
validator_name=getattr(v, "name", type(v).__name__),
|
|
315
|
+
passed=False,
|
|
316
|
+
error_message=f"Validator error: {e}",
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
return outcomes
|
|
320
|
+
|
|
321
|
+
def validate(self, value: str) -> ValidationResult:
|
|
322
|
+
"""
|
|
323
|
+
Validate a value against all validators.
|
|
324
|
+
|
|
325
|
+
Returns a ValidationResult with aggregated outcomes and the action taken.
|
|
326
|
+
"""
|
|
327
|
+
outcomes = self._run_validators(value)
|
|
328
|
+
all_passed = all(o.passed for o in outcomes)
|
|
329
|
+
final_value = value
|
|
330
|
+
|
|
331
|
+
action = FailAction.BLOCK # default
|
|
332
|
+
if all_passed:
|
|
333
|
+
action = FailAction.BLOCK # no action needed
|
|
334
|
+
else:
|
|
335
|
+
action = self.on_fail
|
|
336
|
+
if action == FailAction.FIX:
|
|
337
|
+
# Apply fixes from validators that provide them
|
|
338
|
+
for o in outcomes:
|
|
339
|
+
if not o.passed and o.fixed_value is not None:
|
|
340
|
+
final_value = o.fixed_value
|
|
341
|
+
|
|
342
|
+
result = ValidationResult(
|
|
343
|
+
passed=all_passed,
|
|
344
|
+
outcomes=outcomes,
|
|
345
|
+
original_value=value,
|
|
346
|
+
final_value=final_value,
|
|
347
|
+
action_taken=action if not all_passed else FailAction.BLOCK,
|
|
348
|
+
)
|
|
349
|
+
self._history.append(result)
|
|
350
|
+
|
|
351
|
+
if not all_passed:
|
|
352
|
+
self.on_violation(result)
|
|
353
|
+
|
|
354
|
+
return result
|
|
355
|
+
|
|
356
|
+
def validate_input(self, text: str) -> ValidationResult:
|
|
357
|
+
"""Validate agent input (user query, tool arguments, etc.)."""
|
|
358
|
+
return self.validate(text)
|
|
359
|
+
|
|
360
|
+
def validate_output(self, text: str) -> ValidationResult:
|
|
361
|
+
"""Validate agent output (response text, tool results, etc.)."""
|
|
362
|
+
return self.validate(text)
|
|
363
|
+
|
|
364
|
+
def get_history(self) -> list[ValidationResult]:
|
|
365
|
+
"""Return all validation results."""
|
|
366
|
+
return list(self._history)
|
|
367
|
+
|
|
368
|
+
def get_stats(self) -> dict[str, Any]:
|
|
369
|
+
"""Return guardrails statistics."""
|
|
370
|
+
total = len(self._history)
|
|
371
|
+
passed = sum(1 for r in self._history if r.passed)
|
|
372
|
+
return {
|
|
373
|
+
"total_validations": total,
|
|
374
|
+
"passed": passed,
|
|
375
|
+
"failed": total - passed,
|
|
376
|
+
"pass_rate": passed / total if total > 0 else 1.0,
|
|
377
|
+
"validators": [getattr(v, "name", type(v).__name__) for v in self._validators],
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
def reset(self) -> None:
|
|
381
|
+
"""Clear validation history."""
|
|
382
|
+
self._history.clear()
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
__all__ = [
|
|
386
|
+
"GuardrailsKernel",
|
|
387
|
+
"ValidationResult",
|
|
388
|
+
"ValidationOutcome",
|
|
389
|
+
"FailAction",
|
|
390
|
+
"ValidatorProtocol",
|
|
391
|
+
"RegexValidator",
|
|
392
|
+
"LengthValidator",
|
|
393
|
+
"KeywordValidator",
|
|
394
|
+
]
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Health Check Endpoints for K8s Readiness/Liveness Probes
|
|
5
|
+
|
|
6
|
+
Thread-safe health checker with configurable component checks,
|
|
7
|
+
JSON-serializable reports, and aggregate status computation.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Callable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HealthStatus(Enum):
|
|
19
|
+
"""Possible health states for a component or the overall system."""
|
|
20
|
+
HEALTHY = "healthy"
|
|
21
|
+
DEGRADED = "degraded"
|
|
22
|
+
UNHEALTHY = "unhealthy"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class ComponentHealth:
|
|
27
|
+
"""Health result for a single component."""
|
|
28
|
+
name: str
|
|
29
|
+
status: HealthStatus
|
|
30
|
+
message: str = ""
|
|
31
|
+
latency_ms: float = 0.0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class HealthReport:
|
|
36
|
+
"""Aggregate health report for all registered components."""
|
|
37
|
+
status: HealthStatus
|
|
38
|
+
components: dict[str, ComponentHealth]
|
|
39
|
+
timestamp: str
|
|
40
|
+
version: str
|
|
41
|
+
uptime_seconds: float
|
|
42
|
+
|
|
43
|
+
def to_dict(self) -> dict:
|
|
44
|
+
"""Return a JSON-serializable dictionary."""
|
|
45
|
+
return {
|
|
46
|
+
"status": self.status.value,
|
|
47
|
+
"components": {
|
|
48
|
+
name: {
|
|
49
|
+
"name": comp.name,
|
|
50
|
+
"status": comp.status.value,
|
|
51
|
+
"message": comp.message,
|
|
52
|
+
"latency_ms": comp.latency_ms,
|
|
53
|
+
}
|
|
54
|
+
for name, comp in self.components.items()
|
|
55
|
+
},
|
|
56
|
+
"timestamp": self.timestamp,
|
|
57
|
+
"version": self.version,
|
|
58
|
+
"uptime_seconds": self.uptime_seconds,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def is_healthy(self) -> bool:
|
|
62
|
+
"""True when aggregate status is HEALTHY."""
|
|
63
|
+
return self.status == HealthStatus.HEALTHY
|
|
64
|
+
|
|
65
|
+
def is_ready(self) -> bool:
|
|
66
|
+
"""True when the system is ready to serve (not UNHEALTHY)."""
|
|
67
|
+
return self.status != HealthStatus.UNHEALTHY
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class HealthChecker:
|
|
71
|
+
"""Thread-safe health checker with pluggable component checks.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
version: Application version string included in reports.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, version: str = "1.0.0") -> None:
|
|
78
|
+
self._checks: dict[str, Callable[[], ComponentHealth]] = {}
|
|
79
|
+
self._start_time = datetime.now(timezone.utc)
|
|
80
|
+
self._version = version
|
|
81
|
+
self._lock = threading.Lock()
|
|
82
|
+
|
|
83
|
+
# -- registration ------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
def register_check(
|
|
86
|
+
self, name: str, check_fn: Callable[[], ComponentHealth]
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Register a named health check function (thread-safe)."""
|
|
89
|
+
with self._lock:
|
|
90
|
+
self._checks[name] = check_fn
|
|
91
|
+
|
|
92
|
+
# -- probes ------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
def check_health(self) -> HealthReport:
|
|
95
|
+
"""Run **all** registered checks and return a full report."""
|
|
96
|
+
with self._lock:
|
|
97
|
+
checks = dict(self._checks)
|
|
98
|
+
|
|
99
|
+
components: dict[str, ComponentHealth] = {}
|
|
100
|
+
for name, fn in checks.items():
|
|
101
|
+
start = time.monotonic()
|
|
102
|
+
try:
|
|
103
|
+
result = fn()
|
|
104
|
+
except Exception as exc:
|
|
105
|
+
result = ComponentHealth(
|
|
106
|
+
name=name,
|
|
107
|
+
status=HealthStatus.UNHEALTHY,
|
|
108
|
+
message=str(exc),
|
|
109
|
+
)
|
|
110
|
+
elapsed_ms = (time.monotonic() - start) * 1000.0
|
|
111
|
+
# Preserve check-reported latency if non-zero, else use measured.
|
|
112
|
+
latency = result.latency_ms if result.latency_ms else elapsed_ms
|
|
113
|
+
components[name] = ComponentHealth(
|
|
114
|
+
name=result.name,
|
|
115
|
+
status=result.status,
|
|
116
|
+
message=result.message,
|
|
117
|
+
latency_ms=latency,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return self._build_report(components)
|
|
121
|
+
|
|
122
|
+
def check_ready(self) -> HealthReport:
|
|
123
|
+
"""Readiness probe — same as full health check."""
|
|
124
|
+
return self.check_health()
|
|
125
|
+
|
|
126
|
+
def check_live(self) -> HealthReport:
|
|
127
|
+
"""Liveness probe — lightweight; returns HEALTHY if the process is up."""
|
|
128
|
+
components: dict[str, ComponentHealth] = {
|
|
129
|
+
"process": ComponentHealth(
|
|
130
|
+
name="process",
|
|
131
|
+
status=HealthStatus.HEALTHY,
|
|
132
|
+
message="alive",
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
return self._build_report(components)
|
|
136
|
+
|
|
137
|
+
# -- built-in checks ---------------------------------------------------
|
|
138
|
+
|
|
139
|
+
def _check_policy_engine(self) -> ComponentHealth:
|
|
140
|
+
"""Built-in check that validates the policy engine can create a policy."""
|
|
141
|
+
from .base import GovernancePolicy
|
|
142
|
+
|
|
143
|
+
start = time.monotonic()
|
|
144
|
+
try:
|
|
145
|
+
GovernancePolicy(name="health-probe")
|
|
146
|
+
elapsed = (time.monotonic() - start) * 1000.0
|
|
147
|
+
return ComponentHealth(
|
|
148
|
+
name="policy_engine",
|
|
149
|
+
status=HealthStatus.HEALTHY,
|
|
150
|
+
message="policy engine operational",
|
|
151
|
+
latency_ms=elapsed,
|
|
152
|
+
)
|
|
153
|
+
except Exception as exc:
|
|
154
|
+
elapsed = (time.monotonic() - start) * 1000.0
|
|
155
|
+
return ComponentHealth(
|
|
156
|
+
name="policy_engine",
|
|
157
|
+
status=HealthStatus.UNHEALTHY,
|
|
158
|
+
message=str(exc),
|
|
159
|
+
latency_ms=elapsed,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def _check_audit_backend(self) -> ComponentHealth:
|
|
163
|
+
"""Built-in check stub for the audit backend."""
|
|
164
|
+
return ComponentHealth(
|
|
165
|
+
name="audit_backend",
|
|
166
|
+
status=HealthStatus.HEALTHY,
|
|
167
|
+
message="audit backend reachable",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# -- helpers -----------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
def _build_report(
|
|
173
|
+
self, components: dict[str, ComponentHealth]
|
|
174
|
+
) -> HealthReport:
|
|
175
|
+
status = self._aggregate_status(components)
|
|
176
|
+
now = datetime.now(timezone.utc)
|
|
177
|
+
uptime = (now - self._start_time).total_seconds()
|
|
178
|
+
return HealthReport(
|
|
179
|
+
status=status,
|
|
180
|
+
components=components,
|
|
181
|
+
timestamp=now.isoformat() + "Z",
|
|
182
|
+
version=self._version,
|
|
183
|
+
uptime_seconds=uptime,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def _aggregate_status(
|
|
188
|
+
components: dict[str, ComponentHealth],
|
|
189
|
+
) -> HealthStatus:
|
|
190
|
+
if not components:
|
|
191
|
+
return HealthStatus.HEALTHY
|
|
192
|
+
statuses = {c.status for c in components.values()}
|
|
193
|
+
if HealthStatus.UNHEALTHY in statuses:
|
|
194
|
+
return HealthStatus.UNHEALTHY
|
|
195
|
+
if HealthStatus.DEGRADED in statuses:
|
|
196
|
+
return HealthStatus.DEGRADED
|
|
197
|
+
return HealthStatus.HEALTHY
|