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
agent_os/escalation.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Human-in-the-loop escalation workflows for AI agent governance.
|
|
4
|
+
|
|
5
|
+
Provides approval gates, timeout escalation, and configurable escalation
|
|
6
|
+
policies so agents can't take high-risk actions without human sign-off.
|
|
7
|
+
|
|
8
|
+
This directly addresses the criticism that AGT has "no human escalation
|
|
9
|
+
primitives baked in." Now it does.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from agent_os.escalation import EscalationManager, EscalationPolicy
|
|
13
|
+
|
|
14
|
+
policy = EscalationPolicy(
|
|
15
|
+
actions_requiring_approval=["delete_file", "deploy", "send_email"],
|
|
16
|
+
timeout_seconds=300,
|
|
17
|
+
default_on_timeout="deny",
|
|
18
|
+
)
|
|
19
|
+
manager = EscalationManager(policy)
|
|
20
|
+
|
|
21
|
+
decision = await manager.request_approval(
|
|
22
|
+
agent_id="agent-1",
|
|
23
|
+
action="deploy",
|
|
24
|
+
context={"target": "production", "version": "2.1.0"},
|
|
25
|
+
)
|
|
26
|
+
if decision.approved:
|
|
27
|
+
# proceed
|
|
28
|
+
...
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import asyncio
|
|
34
|
+
import uuid
|
|
35
|
+
from datetime import datetime, timedelta, timezone
|
|
36
|
+
from enum import Enum
|
|
37
|
+
from typing import Any, Callable, Awaitable
|
|
38
|
+
|
|
39
|
+
from pydantic import BaseModel, Field
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class EscalationOutcome(str, Enum):
|
|
43
|
+
"""Outcome of an escalation request."""
|
|
44
|
+
APPROVED = "approved"
|
|
45
|
+
DENIED = "denied"
|
|
46
|
+
TIMED_OUT = "timed_out"
|
|
47
|
+
PENDING = "pending"
|
|
48
|
+
AUTO_APPROVED = "auto_approved"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class EscalationRequest(BaseModel):
|
|
52
|
+
"""A pending human approval request."""
|
|
53
|
+
request_id: str = Field(default_factory=lambda: uuid.uuid4().hex[:12])
|
|
54
|
+
agent_id: str
|
|
55
|
+
action: str
|
|
56
|
+
context: dict[str, Any] = Field(default_factory=dict)
|
|
57
|
+
reason: str = ""
|
|
58
|
+
urgency: str = Field(default="normal", description="low, normal, high, critical")
|
|
59
|
+
requested_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
60
|
+
expires_at: datetime | None = None
|
|
61
|
+
outcome: EscalationOutcome = EscalationOutcome.PENDING
|
|
62
|
+
decided_by: str | None = None
|
|
63
|
+
decided_at: datetime | None = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class EscalationDecision(BaseModel):
|
|
67
|
+
"""Result of an escalation request."""
|
|
68
|
+
request_id: str
|
|
69
|
+
approved: bool
|
|
70
|
+
outcome: EscalationOutcome
|
|
71
|
+
decided_by: str
|
|
72
|
+
decided_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
73
|
+
reason: str = ""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class EscalationPolicy(BaseModel):
|
|
77
|
+
"""Policy governing when and how actions are escalated to humans."""
|
|
78
|
+
|
|
79
|
+
actions_requiring_approval: list[str] = Field(
|
|
80
|
+
default_factory=list,
|
|
81
|
+
description="Actions that always require human approval",
|
|
82
|
+
)
|
|
83
|
+
action_patterns_requiring_approval: list[str] = Field(
|
|
84
|
+
default_factory=list,
|
|
85
|
+
description="Regex patterns for actions requiring approval",
|
|
86
|
+
)
|
|
87
|
+
classifications_requiring_approval: list[str] = Field(
|
|
88
|
+
default_factory=list,
|
|
89
|
+
description="Data classifications that trigger escalation (e.g., RESTRICTED, TOP_SECRET)",
|
|
90
|
+
)
|
|
91
|
+
timeout_seconds: int = Field(
|
|
92
|
+
default=300,
|
|
93
|
+
description="Seconds to wait for human response before timeout action",
|
|
94
|
+
)
|
|
95
|
+
default_on_timeout: str = Field(
|
|
96
|
+
default="deny",
|
|
97
|
+
description="Action when timeout: 'deny' (safe default) or 'approve'",
|
|
98
|
+
)
|
|
99
|
+
max_auto_approvals_per_hour: int = Field(
|
|
100
|
+
default=0,
|
|
101
|
+
description="Max actions auto-approved per hour (0 = never auto-approve)",
|
|
102
|
+
)
|
|
103
|
+
escalation_chain: list[str] = Field(
|
|
104
|
+
default_factory=list,
|
|
105
|
+
description="Ordered list of approvers. If first doesn't respond, escalate to next.",
|
|
106
|
+
)
|
|
107
|
+
notify_on_timeout: bool = Field(
|
|
108
|
+
default=True,
|
|
109
|
+
description="Send notification when escalation times out",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class EscalationManager:
|
|
114
|
+
"""Manages human-in-the-loop approval workflows.
|
|
115
|
+
|
|
116
|
+
When an agent requests a high-risk action, the manager:
|
|
117
|
+
1. Checks if the action requires approval (per policy)
|
|
118
|
+
2. Creates an EscalationRequest
|
|
119
|
+
3. Notifies the approval handler (webhook, UI, Slack, etc.)
|
|
120
|
+
4. Waits for human response or timeout
|
|
121
|
+
5. Returns the decision
|
|
122
|
+
|
|
123
|
+
The approval handler is pluggable — implement your own notification
|
|
124
|
+
system (Slack bot, email, Teams, dashboard, etc.)
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def __init__(
|
|
128
|
+
self,
|
|
129
|
+
policy: EscalationPolicy,
|
|
130
|
+
approval_handler: Callable[[EscalationRequest], Awaitable[None]] | None = None,
|
|
131
|
+
timeout_handler: Callable[[EscalationRequest], Awaitable[None]] | None = None,
|
|
132
|
+
) -> None:
|
|
133
|
+
self.policy = policy
|
|
134
|
+
self._pending: dict[str, EscalationRequest] = {}
|
|
135
|
+
self._approval_handler = approval_handler
|
|
136
|
+
self._timeout_handler = timeout_handler
|
|
137
|
+
self._events: list[dict[str, Any]] = []
|
|
138
|
+
|
|
139
|
+
def requires_approval(self, action: str, **context: Any) -> bool:
|
|
140
|
+
"""Check if an action requires human approval per policy."""
|
|
141
|
+
import re
|
|
142
|
+
|
|
143
|
+
if action in self.policy.actions_requiring_approval:
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
for pattern in self.policy.action_patterns_requiring_approval:
|
|
147
|
+
if re.search(pattern, action):
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
classification = context.get("classification", "")
|
|
151
|
+
if classification in self.policy.classifications_requiring_approval:
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
async def request_approval(
|
|
157
|
+
self,
|
|
158
|
+
agent_id: str,
|
|
159
|
+
action: str,
|
|
160
|
+
context: dict[str, Any] | None = None,
|
|
161
|
+
reason: str = "",
|
|
162
|
+
urgency: str = "normal",
|
|
163
|
+
) -> EscalationDecision:
|
|
164
|
+
"""Request human approval for an action.
|
|
165
|
+
|
|
166
|
+
If the action doesn't require approval, returns auto-approved.
|
|
167
|
+
Otherwise, creates an escalation request and waits for response.
|
|
168
|
+
"""
|
|
169
|
+
if not self.requires_approval(action, **(context or {})):
|
|
170
|
+
decision = EscalationDecision(
|
|
171
|
+
request_id="auto",
|
|
172
|
+
approved=True,
|
|
173
|
+
outcome=EscalationOutcome.AUTO_APPROVED,
|
|
174
|
+
decided_by="policy",
|
|
175
|
+
reason="Action does not require approval",
|
|
176
|
+
)
|
|
177
|
+
self._record_event("auto_approved", agent_id, action, decision)
|
|
178
|
+
return decision
|
|
179
|
+
|
|
180
|
+
now = datetime.now(timezone.utc)
|
|
181
|
+
request = EscalationRequest(
|
|
182
|
+
agent_id=agent_id,
|
|
183
|
+
action=action,
|
|
184
|
+
context=context or {},
|
|
185
|
+
reason=reason,
|
|
186
|
+
urgency=urgency,
|
|
187
|
+
expires_at=now + timedelta(seconds=self.policy.timeout_seconds),
|
|
188
|
+
)
|
|
189
|
+
self._pending[request.request_id] = request
|
|
190
|
+
|
|
191
|
+
# Notify approval handler
|
|
192
|
+
if self._approval_handler:
|
|
193
|
+
await self._approval_handler(request)
|
|
194
|
+
|
|
195
|
+
self._record_event("escalated", agent_id, action, request)
|
|
196
|
+
|
|
197
|
+
# Wait for response or timeout
|
|
198
|
+
deadline = request.expires_at
|
|
199
|
+
while datetime.now(timezone.utc) < deadline:
|
|
200
|
+
if request.outcome != EscalationOutcome.PENDING:
|
|
201
|
+
break
|
|
202
|
+
await asyncio.sleep(0.1)
|
|
203
|
+
|
|
204
|
+
# Handle timeout
|
|
205
|
+
if request.outcome == EscalationOutcome.PENDING:
|
|
206
|
+
if self.policy.default_on_timeout == "approve":
|
|
207
|
+
request.outcome = EscalationOutcome.TIMED_OUT
|
|
208
|
+
approved = True
|
|
209
|
+
else:
|
|
210
|
+
request.outcome = EscalationOutcome.TIMED_OUT
|
|
211
|
+
approved = False
|
|
212
|
+
|
|
213
|
+
request.decided_by = "timeout"
|
|
214
|
+
request.decided_at = datetime.now(timezone.utc)
|
|
215
|
+
|
|
216
|
+
if self._timeout_handler and self.policy.notify_on_timeout:
|
|
217
|
+
await self._timeout_handler(request)
|
|
218
|
+
|
|
219
|
+
decision = EscalationDecision(
|
|
220
|
+
request_id=request.request_id,
|
|
221
|
+
approved=approved,
|
|
222
|
+
outcome=EscalationOutcome.TIMED_OUT,
|
|
223
|
+
decided_by="timeout",
|
|
224
|
+
reason=f"Timed out after {self.policy.timeout_seconds}s — default: {self.policy.default_on_timeout}",
|
|
225
|
+
)
|
|
226
|
+
else:
|
|
227
|
+
decision = EscalationDecision(
|
|
228
|
+
request_id=request.request_id,
|
|
229
|
+
approved=request.outcome == EscalationOutcome.APPROVED,
|
|
230
|
+
outcome=request.outcome,
|
|
231
|
+
decided_by=request.decided_by or "unknown",
|
|
232
|
+
decided_at=request.decided_at or datetime.now(timezone.utc),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
del self._pending[request.request_id]
|
|
236
|
+
self._record_event("decided", agent_id, action, decision)
|
|
237
|
+
return decision
|
|
238
|
+
|
|
239
|
+
def approve(self, request_id: str, decided_by: str = "human", reason: str = "") -> bool:
|
|
240
|
+
"""Approve a pending escalation request (called by human/UI)."""
|
|
241
|
+
request = self._pending.get(request_id)
|
|
242
|
+
if not request or request.outcome != EscalationOutcome.PENDING:
|
|
243
|
+
return False
|
|
244
|
+
request.outcome = EscalationOutcome.APPROVED
|
|
245
|
+
request.decided_by = decided_by
|
|
246
|
+
request.decided_at = datetime.now(timezone.utc)
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
def deny(self, request_id: str, decided_by: str = "human", reason: str = "") -> bool:
|
|
250
|
+
"""Deny a pending escalation request (called by human/UI)."""
|
|
251
|
+
request = self._pending.get(request_id)
|
|
252
|
+
if not request or request.outcome != EscalationOutcome.PENDING:
|
|
253
|
+
return False
|
|
254
|
+
request.outcome = EscalationOutcome.DENIED
|
|
255
|
+
request.decided_by = decided_by
|
|
256
|
+
request.decided_at = datetime.now(timezone.utc)
|
|
257
|
+
return True
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def pending_requests(self) -> list[EscalationRequest]:
|
|
261
|
+
"""Get all pending escalation requests."""
|
|
262
|
+
return [r for r in self._pending.values() if r.outcome == EscalationOutcome.PENDING]
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def audit_trail(self) -> list[dict[str, Any]]:
|
|
266
|
+
"""Get the escalation audit trail."""
|
|
267
|
+
return list(self._events)
|
|
268
|
+
|
|
269
|
+
def _record_event(self, event_type: str, agent_id: str, action: str, data: Any) -> None:
|
|
270
|
+
self._events.append({
|
|
271
|
+
"event_type": event_type,
|
|
272
|
+
"agent_id": agent_id,
|
|
273
|
+
"action": action,
|
|
274
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
275
|
+
"data": data.model_dump(mode="json") if hasattr(data, "model_dump") else str(data),
|
|
276
|
+
})
|
agent_os/event_bus.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Cross-gate event bus for governance layer composition.
|
|
4
|
+
|
|
5
|
+
Enables governance gates (PolicyEvaluator, TrustGate, CircuitBreaker,
|
|
6
|
+
ConversationGuardian) to communicate via events without tight coupling.
|
|
7
|
+
|
|
8
|
+
Example::
|
|
9
|
+
|
|
10
|
+
bus = GovernanceEventBus()
|
|
11
|
+
bus.subscribe("policy.violation", on_violation)
|
|
12
|
+
bus.publish("policy.violation", agent_id="a1", action="delete_db")
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from collections import defaultdict
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
from typing import Any, Callable
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
EventHandler = Callable[["GovernanceEvent"], None]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class GovernanceEvent:
|
|
30
|
+
"""A governance event emitted by any gate."""
|
|
31
|
+
|
|
32
|
+
event_type: str
|
|
33
|
+
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
|
34
|
+
source: str = ""
|
|
35
|
+
agent_id: str = ""
|
|
36
|
+
data: dict[str, Any] = field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Standard event types
|
|
40
|
+
POLICY_VIOLATION = "policy.violation"
|
|
41
|
+
POLICY_ALLOW = "policy.allow"
|
|
42
|
+
TRUST_PENALTY = "trust.penalty"
|
|
43
|
+
TRUST_BOOST = "trust.boost"
|
|
44
|
+
CIRCUIT_OPEN = "circuit.open"
|
|
45
|
+
CIRCUIT_CLOSE = "circuit.close"
|
|
46
|
+
ESCALATION = "governance.escalation"
|
|
47
|
+
BUDGET_EXCEEDED = "budget.exceeded"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class GovernanceEventBus:
|
|
51
|
+
"""Publish/subscribe event bus for governance gate composition.
|
|
52
|
+
|
|
53
|
+
Example::
|
|
54
|
+
|
|
55
|
+
bus = GovernanceEventBus()
|
|
56
|
+
|
|
57
|
+
# Trust gate penalizes on policy violations
|
|
58
|
+
def on_violation(event):
|
|
59
|
+
trust_engine.penalize(event.agent_id, severity=0.3)
|
|
60
|
+
bus.subscribe("policy.violation", on_violation)
|
|
61
|
+
|
|
62
|
+
# Circuit breaker trips on trust penalties
|
|
63
|
+
def on_trust_penalty(event):
|
|
64
|
+
if event.data.get("new_score", 1.0) < 0.3:
|
|
65
|
+
circuit_breaker.trip(event.agent_id)
|
|
66
|
+
bus.subscribe("trust.penalty", on_trust_penalty)
|
|
67
|
+
|
|
68
|
+
# Policy engine publishes violation
|
|
69
|
+
bus.publish("policy.violation", agent_id="agent-1", action="drop_table")
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self) -> None:
|
|
73
|
+
self._handlers: dict[str, list[EventHandler]] = defaultdict(list)
|
|
74
|
+
self._history: list[GovernanceEvent] = []
|
|
75
|
+
self._max_history: int = 1000
|
|
76
|
+
|
|
77
|
+
def subscribe(self, event_type: str, handler: EventHandler) -> None:
|
|
78
|
+
"""Subscribe a handler to an event type."""
|
|
79
|
+
self._handlers[event_type].append(handler)
|
|
80
|
+
|
|
81
|
+
def unsubscribe(self, event_type: str, handler: EventHandler) -> None:
|
|
82
|
+
"""Remove a handler from an event type."""
|
|
83
|
+
if event_type in self._handlers:
|
|
84
|
+
self._handlers[event_type] = [
|
|
85
|
+
h for h in self._handlers[event_type] if h is not handler
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
def publish(self, event_type: str, source: str = "", agent_id: str = "", **data: Any) -> GovernanceEvent:
|
|
89
|
+
"""Publish an event to all subscribers."""
|
|
90
|
+
event = GovernanceEvent(
|
|
91
|
+
event_type=event_type,
|
|
92
|
+
source=source,
|
|
93
|
+
agent_id=agent_id,
|
|
94
|
+
data=data,
|
|
95
|
+
)
|
|
96
|
+
self._history.append(event)
|
|
97
|
+
if len(self._history) > self._max_history:
|
|
98
|
+
self._history = self._history[-self._max_history:]
|
|
99
|
+
|
|
100
|
+
for handler in self._handlers.get(event_type, []):
|
|
101
|
+
try:
|
|
102
|
+
handler(event)
|
|
103
|
+
except Exception:
|
|
104
|
+
logger.exception("Event handler failed for %s", event_type)
|
|
105
|
+
|
|
106
|
+
# Wildcard subscribers
|
|
107
|
+
for handler in self._handlers.get("*", []):
|
|
108
|
+
try:
|
|
109
|
+
handler(event)
|
|
110
|
+
except Exception:
|
|
111
|
+
logger.exception("Wildcard handler failed for %s", event_type)
|
|
112
|
+
|
|
113
|
+
return event
|
|
114
|
+
|
|
115
|
+
def get_history(self, event_type: str | None = None, limit: int = 100) -> list[GovernanceEvent]:
|
|
116
|
+
"""Get recent events, optionally filtered by type."""
|
|
117
|
+
events = self._history
|
|
118
|
+
if event_type:
|
|
119
|
+
events = [e for e in events if e.event_type == event_type]
|
|
120
|
+
return events[-limit:]
|
|
121
|
+
|
|
122
|
+
def clear_history(self) -> None:
|
|
123
|
+
"""Clear event history."""
|
|
124
|
+
self._history.clear()
|
agent_os/exceptions.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Agent OS Exception Hierarchy
|
|
5
|
+
|
|
6
|
+
Standardized exceptions with error codes for all Agent OS components.
|
|
7
|
+
Each exception carries an error_code, optional details dict, and timestamp
|
|
8
|
+
for structured error handling and logging.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AgentOSError(Exception):
|
|
15
|
+
"""Base exception for all Agent OS errors."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, message, error_code=None, details=None):
|
|
18
|
+
super().__init__(message)
|
|
19
|
+
self.error_code = error_code or "AGENT_OS_ERROR"
|
|
20
|
+
self.details = details or {}
|
|
21
|
+
self.timestamp = datetime.now(timezone.utc).isoformat()
|
|
22
|
+
|
|
23
|
+
def to_dict(self):
|
|
24
|
+
return {
|
|
25
|
+
"error": self.error_code,
|
|
26
|
+
"message": str(self),
|
|
27
|
+
"details": self.details,
|
|
28
|
+
"timestamp": self.timestamp,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# --- Policy errors ---
|
|
33
|
+
|
|
34
|
+
class PolicyError(AgentOSError):
|
|
35
|
+
"""Base exception for policy-related errors."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, message, error_code=None, details=None):
|
|
38
|
+
super().__init__(message, error_code or "POLICY_ERROR", details)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PolicyViolationError(PolicyError):
|
|
42
|
+
"""Raised when a governance policy check fails."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, message, error_code=None, details=None):
|
|
45
|
+
super().__init__(message, error_code or "POLICY_VIOLATION", details)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PolicyDeniedError(PolicyError):
|
|
49
|
+
"""Raised when a policy explicitly denies an action."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, message, error_code=None, details=None):
|
|
52
|
+
super().__init__(message, error_code or "POLICY_DENIED", details)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class PolicyTimeoutError(PolicyError):
|
|
56
|
+
"""Raised when a policy evaluation times out."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, message, error_code=None, details=None):
|
|
59
|
+
super().__init__(message, error_code or "POLICY_TIMEOUT", details)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# --- Budget errors ---
|
|
63
|
+
|
|
64
|
+
class BudgetError(AgentOSError):
|
|
65
|
+
"""Base exception for budget-related errors."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, message, error_code=None, details=None):
|
|
68
|
+
super().__init__(message, error_code or "BUDGET_ERROR", details)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class BudgetExceededError(BudgetError):
|
|
72
|
+
"""Raised when a budget limit is exceeded."""
|
|
73
|
+
|
|
74
|
+
def __init__(self, message, error_code=None, details=None):
|
|
75
|
+
super().__init__(message, error_code or "BUDGET_EXCEEDED", details)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class BudgetWarningError(BudgetError):
|
|
79
|
+
"""Raised when approaching a budget limit."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, message, error_code=None, details=None):
|
|
82
|
+
super().__init__(message, error_code or "BUDGET_WARNING", details)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# --- Identity errors ---
|
|
86
|
+
|
|
87
|
+
class IdentityError(AgentOSError):
|
|
88
|
+
"""Base exception for identity-related errors."""
|
|
89
|
+
|
|
90
|
+
def __init__(self, message, error_code=None, details=None):
|
|
91
|
+
super().__init__(message, error_code or "IDENTITY_ERROR", details)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class IdentityVerificationError(IdentityError):
|
|
95
|
+
"""Raised when identity verification fails."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, message, error_code=None, details=None):
|
|
98
|
+
super().__init__(message, error_code or "IDENTITY_VERIFICATION_FAILED", details)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class CredentialExpiredError(IdentityError):
|
|
102
|
+
"""Raised when credentials have expired."""
|
|
103
|
+
|
|
104
|
+
def __init__(self, message, error_code=None, details=None):
|
|
105
|
+
super().__init__(message, error_code or "CREDENTIAL_EXPIRED", details)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# --- Integration errors ---
|
|
109
|
+
|
|
110
|
+
class IntegrationError(AgentOSError):
|
|
111
|
+
"""Base exception for integration-related errors."""
|
|
112
|
+
|
|
113
|
+
def __init__(self, message, error_code=None, details=None):
|
|
114
|
+
super().__init__(message, error_code or "INTEGRATION_ERROR", details)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class AdapterNotFoundError(IntegrationError):
|
|
118
|
+
"""Raised when a requested adapter is not found."""
|
|
119
|
+
|
|
120
|
+
def __init__(self, message, error_code=None, details=None):
|
|
121
|
+
super().__init__(message, error_code or "ADAPTER_NOT_FOUND", details)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class AdapterTimeoutError(IntegrationError):
|
|
125
|
+
"""Raised when an adapter operation times out."""
|
|
126
|
+
|
|
127
|
+
def __init__(self, message, error_code=None, details=None):
|
|
128
|
+
super().__init__(message, error_code or "ADAPTER_TIMEOUT", details)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# --- Configuration errors ---
|
|
132
|
+
|
|
133
|
+
class ConfigurationError(AgentOSError):
|
|
134
|
+
"""Base exception for configuration-related errors."""
|
|
135
|
+
|
|
136
|
+
def __init__(self, message, error_code=None, details=None):
|
|
137
|
+
super().__init__(message, error_code or "CONFIGURATION_ERROR", details)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class InvalidPolicyError(ConfigurationError):
|
|
141
|
+
"""Raised when a policy definition is invalid."""
|
|
142
|
+
|
|
143
|
+
def __init__(self, message, error_code=None, details=None):
|
|
144
|
+
super().__init__(message, error_code or "INVALID_POLICY", details)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class MissingConfigError(ConfigurationError):
|
|
148
|
+
"""Raised when required configuration is missing."""
|
|
149
|
+
|
|
150
|
+
def __init__(self, message, error_code=None, details=None):
|
|
151
|
+
super().__init__(message, error_code or "MISSING_CONFIG", details)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# --- Rate limit errors ---
|
|
155
|
+
|
|
156
|
+
class RateLimitError(AgentOSError):
|
|
157
|
+
"""Raised when a rate limit is hit."""
|
|
158
|
+
|
|
159
|
+
def __init__(self, message, error_code=None, details=None):
|
|
160
|
+
super().__init__(message, error_code or "RATE_LIMIT_EXCEEDED", details)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# --- Security errors ---
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class SecurityError(AgentOSError):
|
|
167
|
+
"""Raised when a sandbox security violation is detected."""
|
|
168
|
+
|
|
169
|
+
def __init__(self, message, error_code=None, details=None):
|
|
170
|
+
super().__init__(message, error_code or "SECURITY_VIOLATION", details)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# --- Serialization errors ---
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class SerializationError(AgentOSError):
|
|
177
|
+
"""Raised when state serialization or deserialization fails."""
|
|
178
|
+
|
|
179
|
+
def __init__(self, message, error_code=None, details=None):
|
|
180
|
+
super().__init__(message, error_code or "SERIALIZATION_ERROR", details)
|