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
iatp/policy_engine.py
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
IATP Policy Engine - Protocol Layer Policy Validation.
|
|
5
|
+
|
|
6
|
+
This module provides policy validation for IATP capability manifests
|
|
7
|
+
using a standalone, protocol-native implementation.
|
|
8
|
+
|
|
9
|
+
Design Note: IATP is Layer 2 (Infrastructure/Protocol). It defines the
|
|
10
|
+
message format, handshake protocols, and trust scores. Higher layers
|
|
11
|
+
(like agent-control-plane) USE this protocol; this protocol does NOT
|
|
12
|
+
depend on them.
|
|
13
|
+
"""
|
|
14
|
+
from typing import Any, Dict, List, Optional, Protocol, Tuple, runtime_checkable
|
|
15
|
+
|
|
16
|
+
from iatp.models import CapabilityManifest, ReversibilityLevel
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@runtime_checkable
|
|
20
|
+
class PolicyEvaluator(Protocol):
|
|
21
|
+
"""
|
|
22
|
+
Protocol class for policy evaluation.
|
|
23
|
+
|
|
24
|
+
This allows duck typing for any policy evaluator implementation
|
|
25
|
+
without requiring a specific import.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def evaluate(self, context: Dict[str, Any]) -> str:
|
|
29
|
+
"""Evaluate context and return action: 'allow', 'warn', or 'deny'."""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PolicyRule:
|
|
34
|
+
"""
|
|
35
|
+
A single policy rule for manifest validation.
|
|
36
|
+
|
|
37
|
+
Rules are evaluated in order and the first matching rule determines
|
|
38
|
+
the action. Actions can be 'allow', 'warn', or 'deny'.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
name: str,
|
|
44
|
+
action: str,
|
|
45
|
+
conditions: Dict[str, List[Any]],
|
|
46
|
+
description: str = ""
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Initialize a policy rule.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
name: Rule identifier
|
|
53
|
+
action: Action to take ('allow', 'warn', 'deny')
|
|
54
|
+
conditions: Dictionary mapping field names to allowed values
|
|
55
|
+
description: Human-readable description
|
|
56
|
+
"""
|
|
57
|
+
self.name = name
|
|
58
|
+
self.action = action
|
|
59
|
+
self.conditions = conditions
|
|
60
|
+
self.description = description
|
|
61
|
+
|
|
62
|
+
def matches(self, context: Dict[str, Any]) -> bool:
|
|
63
|
+
"""
|
|
64
|
+
Check if this rule matches the given context.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
context: Policy evaluation context
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
True if any condition matches
|
|
71
|
+
"""
|
|
72
|
+
for key, values in self.conditions.items():
|
|
73
|
+
if key in context and context[key] in values:
|
|
74
|
+
return True
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class IATPPolicyEngine:
|
|
79
|
+
"""
|
|
80
|
+
IATP Policy Engine for capability manifest validation.
|
|
81
|
+
|
|
82
|
+
This is a protocol-native policy engine that validates incoming
|
|
83
|
+
agent manifests against configurable policy rules. It uses the
|
|
84
|
+
IATP trust scoring algorithm and provides warn/block decisions.
|
|
85
|
+
|
|
86
|
+
The engine is designed to be:
|
|
87
|
+
- Self-contained (no external dependencies beyond IATP models)
|
|
88
|
+
- Extensible (custom rules can be added)
|
|
89
|
+
- Protocol-compliant (follows IATP trust semantics)
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self):
|
|
93
|
+
"""Initialize the IATP Policy Engine."""
|
|
94
|
+
self.rules: List[PolicyRule] = []
|
|
95
|
+
self._setup_default_policies()
|
|
96
|
+
|
|
97
|
+
def _setup_default_policies(self):
|
|
98
|
+
"""Setup default security policies for IATP."""
|
|
99
|
+
# Default IATP policy rules
|
|
100
|
+
# These augment (not replace) the SecurityValidator checks
|
|
101
|
+
self.rules = [
|
|
102
|
+
PolicyRule(
|
|
103
|
+
name="WarnUntrustedAgent",
|
|
104
|
+
description="Warn when agents are marked as untrusted",
|
|
105
|
+
action="warn",
|
|
106
|
+
conditions={"trust_level": ["untrusted"]}
|
|
107
|
+
),
|
|
108
|
+
PolicyRule(
|
|
109
|
+
name="RequireReversibility",
|
|
110
|
+
description="Warn when agents don't support reversibility",
|
|
111
|
+
action="warn",
|
|
112
|
+
conditions={"reversibility": ["none"]}
|
|
113
|
+
),
|
|
114
|
+
PolicyRule(
|
|
115
|
+
name="AllowEphemeral",
|
|
116
|
+
description="Allow agents with ephemeral data retention",
|
|
117
|
+
action="allow",
|
|
118
|
+
conditions={"retention_policy": ["ephemeral"]}
|
|
119
|
+
),
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
def add_custom_rule(self, rule: Dict[str, Any]):
|
|
123
|
+
"""
|
|
124
|
+
Add a custom policy rule.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
rule: Dictionary defining the policy rule with keys:
|
|
128
|
+
- name: Rule name
|
|
129
|
+
- description: Rule description (optional)
|
|
130
|
+
- action: "allow", "warn", or "deny"
|
|
131
|
+
- conditions: Dictionary of conditions to match
|
|
132
|
+
"""
|
|
133
|
+
policy_rule = PolicyRule(
|
|
134
|
+
name=rule.get("name", "CustomRule"),
|
|
135
|
+
action=rule.get("action", "warn"),
|
|
136
|
+
conditions=rule.get("conditions", {}),
|
|
137
|
+
description=rule.get("description", "")
|
|
138
|
+
)
|
|
139
|
+
# Insert at beginning so custom rules take precedence
|
|
140
|
+
self.rules.insert(0, policy_rule)
|
|
141
|
+
|
|
142
|
+
def validate_manifest(
|
|
143
|
+
self,
|
|
144
|
+
manifest: CapabilityManifest
|
|
145
|
+
) -> Tuple[bool, Optional[str], Optional[str]]:
|
|
146
|
+
"""
|
|
147
|
+
Validate a capability manifest against policies.
|
|
148
|
+
|
|
149
|
+
This is the main integration point that validates incoming
|
|
150
|
+
agent manifests against the configured policy rules.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
manifest: The capability manifest to validate
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Tuple of (is_allowed, error_message, warning_message)
|
|
157
|
+
- is_allowed: True if request should proceed
|
|
158
|
+
- error_message: Blocking error if is_allowed is False
|
|
159
|
+
- warning_message: Warning for user if there are concerns
|
|
160
|
+
"""
|
|
161
|
+
# Convert manifest to policy context
|
|
162
|
+
context = self._manifest_to_context(manifest)
|
|
163
|
+
|
|
164
|
+
# Evaluate against policy rules
|
|
165
|
+
action = self._evaluate_rules(context)
|
|
166
|
+
|
|
167
|
+
# Generate appropriate response
|
|
168
|
+
if action == "deny":
|
|
169
|
+
return False, self._generate_deny_message(manifest, context), None
|
|
170
|
+
elif action == "warn":
|
|
171
|
+
return True, None, self._generate_warn_message(manifest, context)
|
|
172
|
+
else: # allow
|
|
173
|
+
return True, None, None
|
|
174
|
+
|
|
175
|
+
def _evaluate_rules(self, context: Dict[str, Any]) -> str:
|
|
176
|
+
"""
|
|
177
|
+
Evaluate policy rules against context.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
context: Policy evaluation context
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Action string: "allow", "warn", or "deny"
|
|
184
|
+
"""
|
|
185
|
+
for rule in self.rules:
|
|
186
|
+
if rule.matches(context):
|
|
187
|
+
return rule.action
|
|
188
|
+
|
|
189
|
+
# Default to allow if no rules match
|
|
190
|
+
return "allow"
|
|
191
|
+
|
|
192
|
+
def _manifest_to_context(self, manifest: CapabilityManifest) -> Dict[str, Any]:
|
|
193
|
+
"""
|
|
194
|
+
Convert a capability manifest to a policy evaluation context.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
manifest: The capability manifest
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Dictionary context for policy evaluation
|
|
201
|
+
"""
|
|
202
|
+
return {
|
|
203
|
+
"agent_id": manifest.agent_id,
|
|
204
|
+
"trust_level": manifest.trust_level.value,
|
|
205
|
+
"retention_policy": manifest.privacy_contract.retention.value,
|
|
206
|
+
"reversibility": manifest.capabilities.reversibility.value,
|
|
207
|
+
"idempotency": manifest.capabilities.idempotency,
|
|
208
|
+
"human_review": manifest.privacy_contract.human_review,
|
|
209
|
+
"encryption_at_rest": manifest.privacy_contract.encryption_at_rest,
|
|
210
|
+
"encryption_in_transit": manifest.privacy_contract.encryption_in_transit,
|
|
211
|
+
"scopes": manifest.scopes,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
def _generate_deny_message(
|
|
215
|
+
self,
|
|
216
|
+
manifest: CapabilityManifest,
|
|
217
|
+
context: Dict[str, Any]
|
|
218
|
+
) -> str:
|
|
219
|
+
"""
|
|
220
|
+
Generate a denial message for blocked requests.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
manifest: The capability manifest
|
|
224
|
+
context: Policy context
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
User-friendly error message
|
|
228
|
+
"""
|
|
229
|
+
reasons = []
|
|
230
|
+
|
|
231
|
+
if context["retention_policy"] in ["permanent", "forever"]:
|
|
232
|
+
reasons.append(
|
|
233
|
+
f"Agent '{manifest.agent_id}' stores data permanently, "
|
|
234
|
+
"which violates privacy policies"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if context["trust_level"] == "untrusted":
|
|
238
|
+
reasons.append(
|
|
239
|
+
f"Agent '{manifest.agent_id}' is marked as untrusted"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if reasons:
|
|
243
|
+
return "Policy Violation: " + "; ".join(reasons)
|
|
244
|
+
|
|
245
|
+
return f"Policy Violation: Agent '{manifest.agent_id}' failed policy validation"
|
|
246
|
+
|
|
247
|
+
def _generate_warn_message(
|
|
248
|
+
self,
|
|
249
|
+
manifest: CapabilityManifest,
|
|
250
|
+
context: Dict[str, Any]
|
|
251
|
+
) -> str:
|
|
252
|
+
"""
|
|
253
|
+
Generate a warning message for risky requests.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
manifest: The capability manifest
|
|
257
|
+
context: Policy context
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
User-friendly warning message
|
|
261
|
+
"""
|
|
262
|
+
warnings = []
|
|
263
|
+
|
|
264
|
+
if context["reversibility"] == "none":
|
|
265
|
+
warnings.append(
|
|
266
|
+
f"Agent '{manifest.agent_id}' does not support transaction reversal"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if not context["idempotency"]:
|
|
270
|
+
warnings.append(
|
|
271
|
+
f"Agent '{manifest.agent_id}' may not handle duplicate requests safely"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if context["human_review"]:
|
|
275
|
+
warnings.append(
|
|
276
|
+
f"Agent '{manifest.agent_id}' may have humans review your data"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
if warnings:
|
|
280
|
+
return "⚠️ Policy Warning:\n" + "\n".join(f" • {w}" for w in warnings)
|
|
281
|
+
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
def validate_handshake(
|
|
285
|
+
self,
|
|
286
|
+
manifest: CapabilityManifest,
|
|
287
|
+
required_capabilities: Optional[List[str]] = None,
|
|
288
|
+
required_scopes: Optional[List[str]] = None
|
|
289
|
+
) -> Tuple[bool, Optional[str]]:
|
|
290
|
+
"""
|
|
291
|
+
Validate handshake compatibility between agents.
|
|
292
|
+
|
|
293
|
+
This checks if the remote agent's capabilities meet the
|
|
294
|
+
local agent's requirements.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
manifest: Remote agent's capability manifest
|
|
298
|
+
required_capabilities: List of required capability keys
|
|
299
|
+
required_scopes: List of required RBAC scopes (e.g., ['repo:write'])
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Tuple of (is_compatible, error_message)
|
|
303
|
+
"""
|
|
304
|
+
if not required_capabilities:
|
|
305
|
+
required_capabilities = []
|
|
306
|
+
if not required_scopes:
|
|
307
|
+
required_scopes = []
|
|
308
|
+
|
|
309
|
+
# Always validate against base policies first
|
|
310
|
+
is_allowed, error_msg, _ = self.validate_manifest(manifest)
|
|
311
|
+
if not is_allowed:
|
|
312
|
+
return False, error_msg
|
|
313
|
+
|
|
314
|
+
# Check specific capability requirements
|
|
315
|
+
missing = []
|
|
316
|
+
for capability in required_capabilities:
|
|
317
|
+
if capability == "reversibility" and \
|
|
318
|
+
manifest.capabilities.reversibility == ReversibilityLevel.NONE:
|
|
319
|
+
missing.append("reversibility support")
|
|
320
|
+
elif capability == "idempotency" and \
|
|
321
|
+
not manifest.capabilities.idempotency:
|
|
322
|
+
missing.append("idempotency support")
|
|
323
|
+
|
|
324
|
+
if missing:
|
|
325
|
+
return False, f"Agent missing required capabilities: {', '.join(missing)}"
|
|
326
|
+
|
|
327
|
+
# Check RBAC scope requirements
|
|
328
|
+
missing_scopes = []
|
|
329
|
+
agent_scopes = set(manifest.scopes)
|
|
330
|
+
for scope in required_scopes:
|
|
331
|
+
if scope not in agent_scopes:
|
|
332
|
+
missing_scopes.append(scope)
|
|
333
|
+
|
|
334
|
+
if missing_scopes:
|
|
335
|
+
return False, f"Agent missing required scopes: {', '.join(missing_scopes)}"
|
|
336
|
+
|
|
337
|
+
return True, None
|
iatp/py.typed
ADDED
iatp/recovery.py
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Recovery Engine Integration with SCAK (Self-Correcting Agent Kernel).
|
|
5
|
+
|
|
6
|
+
This module wraps the agent_kernel (scak) to provide failure recovery
|
|
7
|
+
and rollback capabilities for IATP.
|
|
8
|
+
"""
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Any, Callable, Dict, Optional
|
|
14
|
+
|
|
15
|
+
from agent_primitives import (
|
|
16
|
+
AgentFailure,
|
|
17
|
+
FailureType,
|
|
18
|
+
FailureSeverity,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from iatp.models import CapabilityManifest
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RecoveryStrategy(str, Enum):
|
|
27
|
+
"""Recovery strategies for IATP."""
|
|
28
|
+
ROLLBACK = "rollback" # Execute compensation transaction
|
|
29
|
+
RETRY = "retry" # Retry the operation
|
|
30
|
+
GIVE_UP = "give_up" # No recovery possible
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class IATPRecoveryEngine:
|
|
34
|
+
"""
|
|
35
|
+
Wrapper around SCAK for IATP failure recovery.
|
|
36
|
+
|
|
37
|
+
This integrates the Self-Correcting Agent Kernel (scak) to provide
|
|
38
|
+
automatic failure detection, triage, and recovery for agent interactions.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
"""Initialize the IATP Recovery Engine."""
|
|
43
|
+
# Note: SelfCorrectingAgentKernel requires specific initialization
|
|
44
|
+
# We'll use the triage and diagnosis functions directly
|
|
45
|
+
self.recovery_history: Dict[str, Any] = {}
|
|
46
|
+
|
|
47
|
+
async def handle_failure(
|
|
48
|
+
self,
|
|
49
|
+
trace_id: str,
|
|
50
|
+
error: Exception,
|
|
51
|
+
manifest: CapabilityManifest,
|
|
52
|
+
payload: Dict[str, Any],
|
|
53
|
+
compensation_callback: Optional[Callable] = None
|
|
54
|
+
) -> Dict[str, Any]:
|
|
55
|
+
"""
|
|
56
|
+
Handle a failure in agent communication.
|
|
57
|
+
|
|
58
|
+
This is the main entry point for failure recovery. It:
|
|
59
|
+
1. Diagnoses the failure using scak
|
|
60
|
+
2. Determines the appropriate recovery strategy
|
|
61
|
+
3. Executes compensation if available
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
trace_id: Unique trace ID for this request
|
|
65
|
+
error: The exception that occurred
|
|
66
|
+
manifest: The agent's capability manifest
|
|
67
|
+
payload: The original request payload
|
|
68
|
+
compensation_callback: Optional callback for rollback
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Dictionary with recovery result and actions taken
|
|
72
|
+
"""
|
|
73
|
+
logger.info(f"[Recovery] Handling failure for trace {trace_id}")
|
|
74
|
+
|
|
75
|
+
# Determine failure type from exception
|
|
76
|
+
error_name = type(error).__name__
|
|
77
|
+
if "Timeout" in error_name:
|
|
78
|
+
failure_type = FailureType.TIMEOUT
|
|
79
|
+
elif "Resource" in error_name or "Limit" in error_name:
|
|
80
|
+
failure_type = FailureType.RESOURCE_EXHAUSTED
|
|
81
|
+
elif "Invalid" in error_name or "Validation" in error_name:
|
|
82
|
+
failure_type = FailureType.INVALID_ACTION
|
|
83
|
+
else:
|
|
84
|
+
failure_type = FailureType.UNKNOWN
|
|
85
|
+
|
|
86
|
+
# Create failure record using scak's AgentFailure model
|
|
87
|
+
failure = AgentFailure(
|
|
88
|
+
agent_id=manifest.agent_id,
|
|
89
|
+
failure_type=failure_type,
|
|
90
|
+
error_message=str(error),
|
|
91
|
+
context={
|
|
92
|
+
"trace_id": trace_id,
|
|
93
|
+
"payload": payload,
|
|
94
|
+
"reversibility": manifest.capabilities.reversibility.value,
|
|
95
|
+
},
|
|
96
|
+
timestamp=datetime.now(timezone.utc)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Determine recovery strategy based on manifest and failure type
|
|
100
|
+
# Note: scak's diagnose_failure requires complex tool traces,
|
|
101
|
+
# so we use a simplified diagnosis based on the failure record
|
|
102
|
+
diagnosis = f"{failure_type.value}: {error_name}"
|
|
103
|
+
|
|
104
|
+
strategy = self._determine_strategy(
|
|
105
|
+
diagnosis,
|
|
106
|
+
manifest,
|
|
107
|
+
compensation_callback is not None
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Execute recovery
|
|
111
|
+
recovery_result = await self._execute_recovery(
|
|
112
|
+
strategy=strategy,
|
|
113
|
+
trace_id=trace_id,
|
|
114
|
+
failure=failure,
|
|
115
|
+
manifest=manifest,
|
|
116
|
+
compensation_callback=compensation_callback
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Record in history
|
|
120
|
+
self.recovery_history[trace_id] = {
|
|
121
|
+
"failure": failure,
|
|
122
|
+
"diagnosis": diagnosis,
|
|
123
|
+
"strategy": strategy,
|
|
124
|
+
"result": recovery_result,
|
|
125
|
+
"timestamp": datetime.now(timezone.utc).isoformat()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return recovery_result
|
|
129
|
+
|
|
130
|
+
def _determine_strategy(
|
|
131
|
+
self,
|
|
132
|
+
diagnosis: str,
|
|
133
|
+
manifest: CapabilityManifest,
|
|
134
|
+
has_compensation: bool
|
|
135
|
+
) -> RecoveryStrategy:
|
|
136
|
+
"""
|
|
137
|
+
Determine the appropriate recovery strategy.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
diagnosis: Failure diagnosis from scak
|
|
141
|
+
manifest: Agent capability manifest
|
|
142
|
+
has_compensation: Whether compensation callback is available
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
RecoveryStrategy to use for recovery
|
|
146
|
+
"""
|
|
147
|
+
# Check if agent supports reversibility
|
|
148
|
+
reversibility = manifest.capabilities.reversibility.value
|
|
149
|
+
|
|
150
|
+
if reversibility in ["full", "partial"] and has_compensation:
|
|
151
|
+
# Agent supports rollback and we have compensation logic
|
|
152
|
+
return RecoveryStrategy.ROLLBACK
|
|
153
|
+
elif reversibility == "partial":
|
|
154
|
+
# Partial rollback - log and warn
|
|
155
|
+
return RecoveryStrategy.RETRY
|
|
156
|
+
elif diagnosis.lower().find("timeout") >= 0:
|
|
157
|
+
# Timeout errors might be transient
|
|
158
|
+
return RecoveryStrategy.RETRY
|
|
159
|
+
else:
|
|
160
|
+
# No recovery possible
|
|
161
|
+
return RecoveryStrategy.GIVE_UP
|
|
162
|
+
|
|
163
|
+
async def _execute_recovery(
|
|
164
|
+
self,
|
|
165
|
+
strategy: RecoveryStrategy,
|
|
166
|
+
trace_id: str,
|
|
167
|
+
failure: AgentFailure,
|
|
168
|
+
manifest: CapabilityManifest,
|
|
169
|
+
compensation_callback: Optional[Callable] = None
|
|
170
|
+
) -> Dict[str, Any]:
|
|
171
|
+
"""
|
|
172
|
+
Execute the determined recovery strategy.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
strategy: Recovery strategy to execute
|
|
176
|
+
trace_id: Trace ID
|
|
177
|
+
failure: Failure details
|
|
178
|
+
manifest: Agent manifest
|
|
179
|
+
compensation_callback: Optional compensation callback
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Dictionary with recovery results
|
|
183
|
+
"""
|
|
184
|
+
result = {
|
|
185
|
+
"strategy": strategy.value,
|
|
186
|
+
"success": False,
|
|
187
|
+
"actions_taken": [],
|
|
188
|
+
"trace_id": trace_id
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if strategy == RecoveryStrategy.ROLLBACK:
|
|
192
|
+
logger.info(f"[Recovery] Executing rollback for {trace_id}")
|
|
193
|
+
result["actions_taken"].append("initiated_rollback")
|
|
194
|
+
|
|
195
|
+
if compensation_callback:
|
|
196
|
+
try:
|
|
197
|
+
# Execute compensation transaction
|
|
198
|
+
if asyncio.iscoroutinefunction(compensation_callback):
|
|
199
|
+
await compensation_callback()
|
|
200
|
+
else:
|
|
201
|
+
compensation_callback()
|
|
202
|
+
|
|
203
|
+
result["success"] = True
|
|
204
|
+
result["actions_taken"].append("compensation_executed")
|
|
205
|
+
logger.info(f"[Recovery] Rollback successful for {trace_id}")
|
|
206
|
+
except Exception as e:
|
|
207
|
+
result["actions_taken"].append(f"compensation_failed: {str(e)}")
|
|
208
|
+
logger.error(f"[Recovery] Rollback failed for {trace_id}: {e}")
|
|
209
|
+
else:
|
|
210
|
+
result["actions_taken"].append("no_compensation_available")
|
|
211
|
+
logger.warning(f"[Recovery] No compensation callback for {trace_id}")
|
|
212
|
+
|
|
213
|
+
elif strategy == RecoveryStrategy.RETRY:
|
|
214
|
+
logger.info(f"[Recovery] Retry recommended for {trace_id}")
|
|
215
|
+
result["actions_taken"].append("retry_recommended")
|
|
216
|
+
result["retry_possible"] = True
|
|
217
|
+
# Note: Actual retry logic would be implemented by the caller
|
|
218
|
+
|
|
219
|
+
else: # GIVE_UP
|
|
220
|
+
logger.info(f"[Recovery] No recovery possible for {trace_id}")
|
|
221
|
+
result["actions_taken"].append("recovery_not_possible")
|
|
222
|
+
result["message"] = (
|
|
223
|
+
f"Agent '{manifest.agent_id}' does not support rollback and "
|
|
224
|
+
"error is not recoverable. Transaction may be in inconsistent state."
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return result
|
|
228
|
+
|
|
229
|
+
def get_recovery_history(self, trace_id: str) -> Optional[Dict[str, Any]]:
|
|
230
|
+
"""
|
|
231
|
+
Get recovery history for a trace ID.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
trace_id: Trace ID to look up
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Recovery history or None if not found
|
|
238
|
+
"""
|
|
239
|
+
return self.recovery_history.get(trace_id)
|
|
240
|
+
|
|
241
|
+
def should_attempt_recovery(
|
|
242
|
+
self,
|
|
243
|
+
error: Exception,
|
|
244
|
+
manifest: CapabilityManifest
|
|
245
|
+
) -> bool:
|
|
246
|
+
"""
|
|
247
|
+
Determine if recovery should be attempted for this error.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
error: The exception that occurred
|
|
251
|
+
manifest: Agent capability manifest
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
True if recovery should be attempted
|
|
255
|
+
"""
|
|
256
|
+
# Always attempt recovery if agent supports reversibility
|
|
257
|
+
if manifest.capabilities.reversibility.value in ["full", "partial"]:
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
# Attempt recovery for certain error types
|
|
261
|
+
error_name = type(error).__name__
|
|
262
|
+
recoverable_errors = [
|
|
263
|
+
"TimeoutError",
|
|
264
|
+
"ConnectionError",
|
|
265
|
+
"HTTPError",
|
|
266
|
+
"ServiceUnavailable"
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
return any(err in error_name for err in recoverable_errors)
|
|
270
|
+
|
|
271
|
+
async def execute_compensation_transaction(
|
|
272
|
+
self,
|
|
273
|
+
trace_id: str,
|
|
274
|
+
manifest: CapabilityManifest,
|
|
275
|
+
compensation_endpoint: str,
|
|
276
|
+
compensation_payload: Dict[str, Any]
|
|
277
|
+
) -> bool:
|
|
278
|
+
"""
|
|
279
|
+
Execute a compensation transaction using the agent's compensation endpoint.
|
|
280
|
+
|
|
281
|
+
This is used when the agent provides a specific compensation/rollback
|
|
282
|
+
endpoint as specified in the handshake.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
trace_id: Trace ID
|
|
286
|
+
manifest: Agent manifest with undo_window info
|
|
287
|
+
compensation_endpoint: URL for compensation
|
|
288
|
+
compensation_payload: Payload for compensation request
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
True if compensation succeeded
|
|
292
|
+
"""
|
|
293
|
+
import httpx
|
|
294
|
+
|
|
295
|
+
logger.info(f"[Recovery] Executing compensation transaction for {trace_id}")
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
async with httpx.AsyncClient() as client:
|
|
299
|
+
response = await client.post(
|
|
300
|
+
compensation_endpoint,
|
|
301
|
+
json=compensation_payload,
|
|
302
|
+
headers={
|
|
303
|
+
"X-Agent-Trace-ID": trace_id,
|
|
304
|
+
"X-Compensation-Request": "true"
|
|
305
|
+
},
|
|
306
|
+
timeout=30.0
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
if 200 <= response.status_code < 300:
|
|
310
|
+
logger.info(f"[Recovery] Compensation successful for {trace_id}")
|
|
311
|
+
return True
|
|
312
|
+
else:
|
|
313
|
+
logger.error(
|
|
314
|
+
f"[Recovery] Compensation failed for {trace_id}: "
|
|
315
|
+
f"status {response.status_code}"
|
|
316
|
+
)
|
|
317
|
+
return False
|
|
318
|
+
|
|
319
|
+
except Exception as e:
|
|
320
|
+
logger.error(f"[Recovery] Compensation exception for {trace_id}: {e}")
|
|
321
|
+
return False
|