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,232 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Standalone circuit breaker implementation.
|
|
4
|
+
|
|
5
|
+
This module provides a self-contained circuit breaker that requires no
|
|
6
|
+
external packages beyond the Python standard library. It is used as a
|
|
7
|
+
fallback by ``agent_os.circuit_breaker`` when ``agent_sre`` is not installed.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import inspect
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CircuitState(str, Enum):
|
|
21
|
+
CLOSED = "CLOSED"
|
|
22
|
+
OPEN = "OPEN"
|
|
23
|
+
HALF_OPEN = "HALF_OPEN"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CircuitOpenError(Exception):
|
|
27
|
+
"""Raised when a call is attempted on an open circuit."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, agent_id: str, retry_after: float) -> None:
|
|
30
|
+
self.agent_id = agent_id
|
|
31
|
+
self.retry_after = retry_after
|
|
32
|
+
super().__init__(
|
|
33
|
+
f"Circuit breaker OPEN for agent '{agent_id}'. "
|
|
34
|
+
f"Retry after {retry_after:.1f}s."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
CircuitBreakerOpen = CircuitOpenError
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(init=False)
|
|
42
|
+
class CircuitBreakerConfig:
|
|
43
|
+
"""Configuration for a circuit breaker instance."""
|
|
44
|
+
|
|
45
|
+
failure_threshold: int = 5
|
|
46
|
+
recovery_timeout_seconds: float = 30.0
|
|
47
|
+
half_open_max_calls: int = 1
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
failure_threshold: int = 5,
|
|
52
|
+
recovery_timeout_seconds: float | None = None,
|
|
53
|
+
half_open_max_calls: int = 1,
|
|
54
|
+
reset_timeout_seconds: float | None = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
if (
|
|
57
|
+
recovery_timeout_seconds is not None
|
|
58
|
+
and reset_timeout_seconds is not None
|
|
59
|
+
and recovery_timeout_seconds != reset_timeout_seconds
|
|
60
|
+
):
|
|
61
|
+
raise ValueError(
|
|
62
|
+
"recovery_timeout_seconds and reset_timeout_seconds must match"
|
|
63
|
+
)
|
|
64
|
+
timeout = recovery_timeout_seconds
|
|
65
|
+
if timeout is None:
|
|
66
|
+
timeout = reset_timeout_seconds
|
|
67
|
+
if timeout is None:
|
|
68
|
+
timeout = 30.0
|
|
69
|
+
self.failure_threshold = failure_threshold
|
|
70
|
+
self.recovery_timeout_seconds = timeout
|
|
71
|
+
self.half_open_max_calls = half_open_max_calls
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def reset_timeout_seconds(self) -> float:
|
|
75
|
+
return self.recovery_timeout_seconds
|
|
76
|
+
|
|
77
|
+
@reset_timeout_seconds.setter
|
|
78
|
+
def reset_timeout_seconds(self, value: float) -> None:
|
|
79
|
+
self.recovery_timeout_seconds = value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class CircuitBreaker:
|
|
83
|
+
"""Standalone circuit breaker (used when agent_sre is not installed)."""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
agent_id: str | CircuitBreakerConfig | None = None,
|
|
88
|
+
config: CircuitBreakerConfig | None = None,
|
|
89
|
+
) -> None:
|
|
90
|
+
if isinstance(agent_id, CircuitBreakerConfig) and config is None:
|
|
91
|
+
config = agent_id
|
|
92
|
+
agent_id = None
|
|
93
|
+
self.agent_id = agent_id or "legacy"
|
|
94
|
+
self.config = config or CircuitBreakerConfig()
|
|
95
|
+
self._config = self.config
|
|
96
|
+
self._state = CircuitState.CLOSED
|
|
97
|
+
self._failure_count = 0
|
|
98
|
+
self._success_count = 0
|
|
99
|
+
self._half_open_calls = 0
|
|
100
|
+
self._last_failure_time = 0.0
|
|
101
|
+
self._lock = threading.Lock()
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def state(self) -> str:
|
|
105
|
+
return self.get_state().value
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def failure_count(self) -> int:
|
|
109
|
+
return self._failure_count
|
|
110
|
+
|
|
111
|
+
def get_state(self) -> CircuitState:
|
|
112
|
+
with self._lock:
|
|
113
|
+
self._maybe_transition_to_half_open()
|
|
114
|
+
return self._state
|
|
115
|
+
|
|
116
|
+
def call(self, func: Any, *args: Any, fallback: Any = None, **kwargs: Any) -> Any:
|
|
117
|
+
retry_after = self._prepare_call()
|
|
118
|
+
if retry_after is not None:
|
|
119
|
+
if fallback is not None:
|
|
120
|
+
return fallback
|
|
121
|
+
raise CircuitOpenError(self.agent_id, retry_after)
|
|
122
|
+
try:
|
|
123
|
+
result = func(*args, **kwargs)
|
|
124
|
+
except Exception:
|
|
125
|
+
self.record_failure()
|
|
126
|
+
raise
|
|
127
|
+
if inspect.isawaitable(result):
|
|
128
|
+
async def _await_result() -> Any:
|
|
129
|
+
try:
|
|
130
|
+
value = await result
|
|
131
|
+
except Exception:
|
|
132
|
+
self.record_failure()
|
|
133
|
+
raise
|
|
134
|
+
self.record_success()
|
|
135
|
+
return value
|
|
136
|
+
return _await_result()
|
|
137
|
+
self.record_success()
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
def record_success(self) -> None:
|
|
141
|
+
with self._lock:
|
|
142
|
+
if self._state is CircuitState.HALF_OPEN:
|
|
143
|
+
self._transition(CircuitState.CLOSED)
|
|
144
|
+
self._failure_count = 0
|
|
145
|
+
self._success_count += 1
|
|
146
|
+
self._half_open_calls = 0
|
|
147
|
+
|
|
148
|
+
def record_failure(self) -> None:
|
|
149
|
+
with self._lock:
|
|
150
|
+
self._failure_count += 1
|
|
151
|
+
self._last_failure_time = time.monotonic()
|
|
152
|
+
if self._state is CircuitState.HALF_OPEN:
|
|
153
|
+
self._transition(CircuitState.OPEN)
|
|
154
|
+
self._half_open_calls = 0
|
|
155
|
+
elif self._failure_count >= self.config.failure_threshold:
|
|
156
|
+
self._transition(CircuitState.OPEN)
|
|
157
|
+
|
|
158
|
+
def reset(self) -> None:
|
|
159
|
+
with self._lock:
|
|
160
|
+
self._transition(CircuitState.CLOSED)
|
|
161
|
+
self._failure_count = 0
|
|
162
|
+
self._success_count = 0
|
|
163
|
+
self._half_open_calls = 0
|
|
164
|
+
self._last_failure_time = 0.0
|
|
165
|
+
|
|
166
|
+
def _prepare_call(self) -> float | None:
|
|
167
|
+
with self._lock:
|
|
168
|
+
self._maybe_transition_to_half_open()
|
|
169
|
+
if self._state is CircuitState.OPEN:
|
|
170
|
+
return self._time_until_recovery()
|
|
171
|
+
if self._state is CircuitState.HALF_OPEN:
|
|
172
|
+
if self._half_open_calls >= self.config.half_open_max_calls:
|
|
173
|
+
return self._time_until_recovery()
|
|
174
|
+
self._half_open_calls += 1
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
def _maybe_transition_to_half_open(self) -> None:
|
|
178
|
+
if self._state is CircuitState.OPEN:
|
|
179
|
+
elapsed = time.monotonic() - self._last_failure_time
|
|
180
|
+
if elapsed >= self.config.recovery_timeout_seconds:
|
|
181
|
+
self._transition(CircuitState.HALF_OPEN)
|
|
182
|
+
|
|
183
|
+
def _transition(self, new_state: CircuitState) -> None:
|
|
184
|
+
self._state = new_state
|
|
185
|
+
if new_state is CircuitState.HALF_OPEN:
|
|
186
|
+
self._half_open_calls = 0
|
|
187
|
+
|
|
188
|
+
def _time_until_recovery(self) -> float:
|
|
189
|
+
elapsed = time.monotonic() - self._last_failure_time
|
|
190
|
+
return max(0.0, self.config.recovery_timeout_seconds - elapsed)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class CascadeDetector:
|
|
194
|
+
"""Detects cascading failures across multiple agents."""
|
|
195
|
+
|
|
196
|
+
def __init__(
|
|
197
|
+
self,
|
|
198
|
+
agents: list[str],
|
|
199
|
+
cascade_threshold: int = 3,
|
|
200
|
+
config: CircuitBreakerConfig | None = None,
|
|
201
|
+
) -> None:
|
|
202
|
+
self.cascade_threshold = cascade_threshold
|
|
203
|
+
self._breakers: dict[str, CircuitBreaker] = {
|
|
204
|
+
agent_id: CircuitBreaker(agent_id, config) for agent_id in agents
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
def get_breaker(self, agent_id: str) -> CircuitBreaker | None:
|
|
208
|
+
return self._breakers.get(agent_id)
|
|
209
|
+
|
|
210
|
+
def check_cascade(self) -> bool:
|
|
211
|
+
return len(self.get_affected_agents()) >= self.cascade_threshold
|
|
212
|
+
|
|
213
|
+
def get_affected_agents(self) -> list[str]:
|
|
214
|
+
return [
|
|
215
|
+
agent_id
|
|
216
|
+
for agent_id, breaker in self._breakers.items()
|
|
217
|
+
if breaker.state == CircuitState.OPEN.value
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
def reset_all(self) -> None:
|
|
221
|
+
for breaker in self._breakers.values():
|
|
222
|
+
breaker.reset()
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
__all__ = [
|
|
226
|
+
"CascadeDetector",
|
|
227
|
+
"CircuitBreaker",
|
|
228
|
+
"CircuitBreakerConfig",
|
|
229
|
+
"CircuitBreakerOpen",
|
|
230
|
+
"CircuitOpenError",
|
|
231
|
+
"CircuitState",
|
|
232
|
+
]
|
agent_os/_mcp_metrics.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""OpenTelemetry-friendly metrics helpers for MCP governance components."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Protocol
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from opentelemetry import metrics as _otel_metrics
|
|
14
|
+
|
|
15
|
+
_HAS_OTEL = True
|
|
16
|
+
except ImportError: # pragma: no cover
|
|
17
|
+
_otel_metrics = None # type: ignore[assignment]
|
|
18
|
+
_HAS_OTEL = False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MCPMetricsRecorder(Protocol):
|
|
22
|
+
"""Protocol for MCP governance metric emission."""
|
|
23
|
+
|
|
24
|
+
def record_decision(
|
|
25
|
+
self,
|
|
26
|
+
*,
|
|
27
|
+
allowed: bool,
|
|
28
|
+
agent_id: str,
|
|
29
|
+
tool_name: str,
|
|
30
|
+
stage: str,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Record an allow or deny decision."""
|
|
33
|
+
|
|
34
|
+
def record_threats_detected(
|
|
35
|
+
self,
|
|
36
|
+
count: int,
|
|
37
|
+
*,
|
|
38
|
+
tool_name: str,
|
|
39
|
+
server_name: str,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Record detected threats from an MCP scan."""
|
|
42
|
+
|
|
43
|
+
def record_rate_limit_hit(self, *, agent_id: str, tool_name: str) -> None:
|
|
44
|
+
"""Record a rate limit rejection."""
|
|
45
|
+
|
|
46
|
+
def record_scan(
|
|
47
|
+
self,
|
|
48
|
+
*,
|
|
49
|
+
operation: str,
|
|
50
|
+
tool_name: str,
|
|
51
|
+
server_name: str,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Record an MCP scan invocation."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class NoOpMCPMetrics:
|
|
57
|
+
"""No-op metrics recorder used when OTel is unavailable."""
|
|
58
|
+
|
|
59
|
+
def record_decision(
|
|
60
|
+
self,
|
|
61
|
+
*,
|
|
62
|
+
allowed: bool,
|
|
63
|
+
agent_id: str,
|
|
64
|
+
tool_name: str,
|
|
65
|
+
stage: str,
|
|
66
|
+
) -> None:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
def record_threats_detected(
|
|
70
|
+
self,
|
|
71
|
+
count: int,
|
|
72
|
+
*,
|
|
73
|
+
tool_name: str,
|
|
74
|
+
server_name: str,
|
|
75
|
+
) -> None:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
def record_rate_limit_hit(self, *, agent_id: str, tool_name: str) -> None:
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
def record_scan(
|
|
82
|
+
self,
|
|
83
|
+
*,
|
|
84
|
+
operation: str,
|
|
85
|
+
tool_name: str,
|
|
86
|
+
server_name: str,
|
|
87
|
+
) -> None:
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class MCPMetrics(NoOpMCPMetrics):
|
|
92
|
+
"""MCP governance counters backed by OpenTelemetry when available."""
|
|
93
|
+
|
|
94
|
+
def __init__(self, meter_provider: Any | None = None) -> None:
|
|
95
|
+
self._enabled = _HAS_OTEL
|
|
96
|
+
self._decisions = None
|
|
97
|
+
self._threats_detected = None
|
|
98
|
+
self._rate_limit_hits = None
|
|
99
|
+
self._scans = None
|
|
100
|
+
if not _HAS_OTEL:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
if meter_provider is not None:
|
|
105
|
+
meter = meter_provider.get_meter("agent_os.mcp", version="3.1.0")
|
|
106
|
+
else:
|
|
107
|
+
meter = _otel_metrics.get_meter("agent_os.mcp", version="3.1.0")
|
|
108
|
+
|
|
109
|
+
self._decisions = meter.create_counter(
|
|
110
|
+
"mcp_decisions",
|
|
111
|
+
description="Total MCP gateway allow and deny decisions.",
|
|
112
|
+
)
|
|
113
|
+
self._threats_detected = meter.create_counter(
|
|
114
|
+
"mcp_threats_detected",
|
|
115
|
+
description="Threats detected by MCP scanners.",
|
|
116
|
+
)
|
|
117
|
+
self._rate_limit_hits = meter.create_counter(
|
|
118
|
+
"mcp_rate_limit_hits",
|
|
119
|
+
description="MCP requests denied by rate limiting.",
|
|
120
|
+
)
|
|
121
|
+
self._scans = meter.create_counter(
|
|
122
|
+
"mcp_scans",
|
|
123
|
+
description="MCP scan operations performed.",
|
|
124
|
+
)
|
|
125
|
+
except Exception: # pragma: no cover - defensive opt-in path
|
|
126
|
+
logger.debug("Failed to initialize MCP OpenTelemetry counters", exc_info=True)
|
|
127
|
+
self._enabled = False
|
|
128
|
+
|
|
129
|
+
def record_decision(
|
|
130
|
+
self,
|
|
131
|
+
*,
|
|
132
|
+
allowed: bool,
|
|
133
|
+
agent_id: str,
|
|
134
|
+
tool_name: str,
|
|
135
|
+
stage: str,
|
|
136
|
+
) -> None:
|
|
137
|
+
if not self._enabled or self._decisions is None:
|
|
138
|
+
return
|
|
139
|
+
self._decisions.add(
|
|
140
|
+
1,
|
|
141
|
+
{
|
|
142
|
+
"agent_id": agent_id,
|
|
143
|
+
"tool_name": tool_name,
|
|
144
|
+
"decision": "allow" if allowed else "deny",
|
|
145
|
+
"stage": stage,
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def record_threats_detected(
|
|
150
|
+
self,
|
|
151
|
+
count: int,
|
|
152
|
+
*,
|
|
153
|
+
tool_name: str,
|
|
154
|
+
server_name: str,
|
|
155
|
+
) -> None:
|
|
156
|
+
if count <= 0 or not self._enabled or self._threats_detected is None:
|
|
157
|
+
return
|
|
158
|
+
self._threats_detected.add(
|
|
159
|
+
count,
|
|
160
|
+
{
|
|
161
|
+
"tool_name": tool_name,
|
|
162
|
+
"server_name": server_name,
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def record_rate_limit_hit(self, *, agent_id: str, tool_name: str) -> None:
|
|
167
|
+
if not self._enabled or self._rate_limit_hits is None:
|
|
168
|
+
return
|
|
169
|
+
self._rate_limit_hits.add(
|
|
170
|
+
1,
|
|
171
|
+
{
|
|
172
|
+
"agent_id": agent_id,
|
|
173
|
+
"tool_name": tool_name,
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def record_scan(
|
|
178
|
+
self,
|
|
179
|
+
*,
|
|
180
|
+
operation: str,
|
|
181
|
+
tool_name: str,
|
|
182
|
+
server_name: str,
|
|
183
|
+
) -> None:
|
|
184
|
+
if not self._enabled or self._scans is None:
|
|
185
|
+
return
|
|
186
|
+
self._scans.add(
|
|
187
|
+
1,
|
|
188
|
+
{
|
|
189
|
+
"operation": operation,
|
|
190
|
+
"tool_name": tool_name,
|
|
191
|
+
"server_name": server_name,
|
|
192
|
+
},
|
|
193
|
+
)
|
agent_os/adversarial.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Adversarial Evaluation — backward-compatibility shim.
|
|
4
|
+
|
|
5
|
+
Attempts to import the canonical implementation from
|
|
6
|
+
``agent_sre.chaos.adversarial_policy``. When ``agent_sre`` is not
|
|
7
|
+
installed the standalone fallback in ``agent_os._adversarial_impl`` is
|
|
8
|
+
re-exported so that ``agent_os`` continues to work without requiring the
|
|
9
|
+
optional SRE package.
|
|
10
|
+
|
|
11
|
+
.. deprecated::
|
|
12
|
+
Import from ``agent_sre.chaos.adversarial_policy`` instead.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from agent_sre.chaos.adversarial_policy import * # noqa: F401,F403
|
|
19
|
+
except ImportError:
|
|
20
|
+
from agent_os._adversarial_impl import * # noqa: F401,F403
|