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/base_agent.py
ADDED
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Base Agent Module - Reusable base classes for Agent OS agents.
|
|
5
|
+
|
|
6
|
+
Provides a consistent pattern for building agents that run under
|
|
7
|
+
the Agent OS kernel with policy governance, audit logging, and
|
|
8
|
+
tool integration.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
>>> from agent_os.base_agent import BaseAgent, AgentConfig
|
|
12
|
+
>>>
|
|
13
|
+
>>> class MyAgent(BaseAgent):
|
|
14
|
+
... async def run(self, task: str) -> ExecutionResult:
|
|
15
|
+
... return await self._execute("process", {"task": task})
|
|
16
|
+
>>>
|
|
17
|
+
>>> agent = MyAgent(AgentConfig(agent_id="my-agent", policies=["read_only"]))
|
|
18
|
+
>>> result = await agent.run("hello")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import asyncio
|
|
24
|
+
import copy
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import re
|
|
28
|
+
import sys
|
|
29
|
+
import time
|
|
30
|
+
from abc import ABC, abstractmethod
|
|
31
|
+
from dataclasses import dataclass, field
|
|
32
|
+
from datetime import datetime, timezone
|
|
33
|
+
from enum import Enum
|
|
34
|
+
from typing import Any, Callable, Generic, TypeVar
|
|
35
|
+
from uuid import uuid4
|
|
36
|
+
|
|
37
|
+
from agent_os.stateless import (
|
|
38
|
+
ExecutionContext,
|
|
39
|
+
ExecutionResult,
|
|
40
|
+
MemoryBackend,
|
|
41
|
+
StateBackend,
|
|
42
|
+
StatelessKernel,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class PolicyDecision(Enum):
|
|
47
|
+
"""Result of policy evaluation."""
|
|
48
|
+
ALLOW = "allow"
|
|
49
|
+
DENY = "deny"
|
|
50
|
+
AUDIT = "audit" # Allow but log for review
|
|
51
|
+
ESCALATE = "escalate" # Route to human reviewer
|
|
52
|
+
DEFER = "defer" # Async policy evaluation with callback
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class EscalationRequest:
|
|
57
|
+
"""A request for human review of a policy decision.
|
|
58
|
+
|
|
59
|
+
Attributes:
|
|
60
|
+
action: The action that triggered escalation
|
|
61
|
+
reason: Why the action was escalated
|
|
62
|
+
requested_by: Agent ID that requested the escalation
|
|
63
|
+
timestamp: When the escalation was created
|
|
64
|
+
status: Current status (pending/approved/rejected)
|
|
65
|
+
"""
|
|
66
|
+
action: str
|
|
67
|
+
reason: str
|
|
68
|
+
requested_by: str
|
|
69
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
70
|
+
status: str = "pending"
|
|
71
|
+
|
|
72
|
+
def __post_init__(self) -> None:
|
|
73
|
+
if self.status not in ("pending", "approved", "rejected"):
|
|
74
|
+
raise ValueError(f"Invalid status: {self.status!r}")
|
|
75
|
+
|
|
76
|
+
def approve(self) -> None:
|
|
77
|
+
"""Mark escalation as approved."""
|
|
78
|
+
self.status = "approved"
|
|
79
|
+
|
|
80
|
+
def reject(self) -> None:
|
|
81
|
+
"""Mark escalation as rejected."""
|
|
82
|
+
self.status = "rejected"
|
|
83
|
+
|
|
84
|
+
def to_dict(self) -> dict[str, Any]:
|
|
85
|
+
return {
|
|
86
|
+
"action": self.action,
|
|
87
|
+
"reason": self.reason,
|
|
88
|
+
"requested_by": self.requested_by,
|
|
89
|
+
"timestamp": self.timestamp.isoformat(),
|
|
90
|
+
"status": self.status,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class AgentConfig:
|
|
96
|
+
"""Configuration for an agent instance.
|
|
97
|
+
|
|
98
|
+
Attributes:
|
|
99
|
+
agent_id: Unique identifier for this agent instance
|
|
100
|
+
policies: List of policy names to apply (e.g., ["read_only", "no_pii"])
|
|
101
|
+
metadata: Additional metadata for the agent
|
|
102
|
+
state_backend: Optional custom state backend (defaults to in-memory)
|
|
103
|
+
max_audit_log_size: Maximum number of audit log entries to retain
|
|
104
|
+
max_metadata_size_bytes: Maximum size in bytes for metadata values
|
|
105
|
+
"""
|
|
106
|
+
agent_id: str
|
|
107
|
+
policies: list[str] = field(default_factory=list)
|
|
108
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
109
|
+
state_backend: StateBackend | None = None
|
|
110
|
+
max_audit_log_size: int = 10000
|
|
111
|
+
max_metadata_size_bytes: int = 1_048_576 # 1 MB
|
|
112
|
+
|
|
113
|
+
_AGENT_ID_RE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9-]{2,63}$")
|
|
114
|
+
|
|
115
|
+
def __post_init__(self) -> None:
|
|
116
|
+
if not self._AGENT_ID_RE.match(self.agent_id):
|
|
117
|
+
raise ValueError(
|
|
118
|
+
f"Invalid agent_id {self.agent_id!r}. "
|
|
119
|
+
"Must be 3-64 chars, alphanumeric with dashes, "
|
|
120
|
+
"starting with an alphanumeric character."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def from_file(cls, path: str) -> AgentConfig:
|
|
125
|
+
"""Load agent configuration from a YAML or JSON file.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
path: Path to a .yaml, .yml, or .json config file
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
AgentConfig populated from the file
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
FileNotFoundError: If the file does not exist
|
|
135
|
+
ValueError: If the file extension is not supported
|
|
136
|
+
"""
|
|
137
|
+
if not os.path.isfile(path):
|
|
138
|
+
raise FileNotFoundError(f"Config file not found: {path}")
|
|
139
|
+
|
|
140
|
+
ext = os.path.splitext(path)[1].lower()
|
|
141
|
+
with open(path, encoding="utf-8") as fh:
|
|
142
|
+
if ext in (".yaml", ".yml"):
|
|
143
|
+
try:
|
|
144
|
+
import yaml
|
|
145
|
+
except ImportError as exc:
|
|
146
|
+
raise ImportError(
|
|
147
|
+
"PyYAML is required for YAML config: pip install pyyaml"
|
|
148
|
+
) from exc
|
|
149
|
+
data = yaml.safe_load(fh) or {}
|
|
150
|
+
elif ext == ".json":
|
|
151
|
+
data = json.load(fh)
|
|
152
|
+
else:
|
|
153
|
+
raise ValueError(f"Unsupported config format: {ext}")
|
|
154
|
+
|
|
155
|
+
return cls(
|
|
156
|
+
agent_id=data.get("agent_id", data.get("agentId", "agent")),
|
|
157
|
+
policies=data.get("policies", []),
|
|
158
|
+
metadata=data.get("metadata", {}),
|
|
159
|
+
max_audit_log_size=data.get("max_audit_log_size", 10000),
|
|
160
|
+
max_metadata_size_bytes=data.get("max_metadata_size_bytes", 1_048_576),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def __repr__(self) -> str:
|
|
164
|
+
return f"AgentConfig(agent_id={self.agent_id!r}, policies={self.policies!r})"
|
|
165
|
+
|
|
166
|
+
def to_dict(self) -> dict[str, Any]:
|
|
167
|
+
"""Serialize configuration to a dictionary."""
|
|
168
|
+
return {
|
|
169
|
+
"agent_id": self.agent_id,
|
|
170
|
+
"policies": self.policies,
|
|
171
|
+
"metadata": self.metadata,
|
|
172
|
+
"max_audit_log_size": self.max_audit_log_size,
|
|
173
|
+
"max_metadata_size_bytes": self.max_metadata_size_bytes,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def from_dict(cls, data: dict[str, Any]) -> AgentConfig:
|
|
178
|
+
"""Deserialize an AgentConfig from a dictionary.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
data: Dictionary as produced by ``to_dict()``.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Reconstructed AgentConfig instance.
|
|
185
|
+
"""
|
|
186
|
+
return cls(
|
|
187
|
+
agent_id=data["agent_id"],
|
|
188
|
+
policies=data.get("policies", []),
|
|
189
|
+
metadata=data.get("metadata", {}),
|
|
190
|
+
max_audit_log_size=data.get("max_audit_log_size", 10000),
|
|
191
|
+
max_metadata_size_bytes=data.get("max_metadata_size_bytes", 1_048_576),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass
|
|
196
|
+
class AuditEntry:
|
|
197
|
+
"""An entry in the agent's audit log."""
|
|
198
|
+
timestamp: datetime
|
|
199
|
+
agent_id: str
|
|
200
|
+
request_id: str
|
|
201
|
+
action: str
|
|
202
|
+
params: dict[str, Any]
|
|
203
|
+
decision: PolicyDecision
|
|
204
|
+
result_success: bool | None = None
|
|
205
|
+
error: str | None = None
|
|
206
|
+
execution_time_ms: float | None = None
|
|
207
|
+
|
|
208
|
+
def __repr__(self) -> str:
|
|
209
|
+
return (
|
|
210
|
+
f"AuditEntry(agent_id={self.agent_id!r}, action={self.action!r}, "
|
|
211
|
+
f"decision={self.decision!r})"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def to_dict(self) -> dict[str, Any]:
|
|
215
|
+
return {
|
|
216
|
+
"timestamp": self.timestamp.isoformat(),
|
|
217
|
+
"agent_id": self.agent_id,
|
|
218
|
+
"request_id": self.request_id,
|
|
219
|
+
"action": self.action,
|
|
220
|
+
"params_keys": list(self.params.keys()), # Don't log full params
|
|
221
|
+
"decision": self.decision.value,
|
|
222
|
+
"result_success": self.result_success,
|
|
223
|
+
"error": self.error,
|
|
224
|
+
"execution_time_ms": self.execution_time_ms,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def from_dict(cls, data: dict[str, Any]) -> AuditEntry:
|
|
229
|
+
"""Deserialize an AuditEntry from a dictionary.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
data: Dictionary as produced by ``to_dict()``.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Reconstructed AuditEntry instance.
|
|
236
|
+
"""
|
|
237
|
+
return cls(
|
|
238
|
+
timestamp=datetime.fromisoformat(data["timestamp"]),
|
|
239
|
+
agent_id=data["agent_id"],
|
|
240
|
+
request_id=data["request_id"],
|
|
241
|
+
action=data["action"],
|
|
242
|
+
params=dict.fromkeys(data.get("params_keys", [])),
|
|
243
|
+
decision=PolicyDecision(data["decision"]),
|
|
244
|
+
result_success=data.get("result_success"),
|
|
245
|
+
error=data.get("error"),
|
|
246
|
+
execution_time_ms=data.get("execution_time_ms"),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class BaseAgent(ABC):
|
|
251
|
+
"""Abstract base class for Agent OS agents.
|
|
252
|
+
|
|
253
|
+
Provides:
|
|
254
|
+
- Kernel integration with policy enforcement
|
|
255
|
+
- Execution context management
|
|
256
|
+
- Audit logging
|
|
257
|
+
- Common helper methods
|
|
258
|
+
|
|
259
|
+
Subclasses must implement the `run` method which defines
|
|
260
|
+
the agent's main task.
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
>>> class GreeterAgent(BaseAgent):
|
|
264
|
+
... async def run(self, name: str) -> ExecutionResult:
|
|
265
|
+
... result = await self._execute(
|
|
266
|
+
... action="greet",
|
|
267
|
+
... params={"name": name, "output": f"Hello, {name}!"}
|
|
268
|
+
... )
|
|
269
|
+
... return result
|
|
270
|
+
>>>
|
|
271
|
+
>>> agent = GreeterAgent(AgentConfig(agent_id="greeter"))
|
|
272
|
+
>>> result = await agent.run("World")
|
|
273
|
+
>>> print(result.data) # "Hello, World!"
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
def __init__(
|
|
277
|
+
self,
|
|
278
|
+
config: AgentConfig,
|
|
279
|
+
defer_timeout: float = 30.0,
|
|
280
|
+
):
|
|
281
|
+
"""Initialize the agent.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
config: Agent configuration including ID, policies, and backend
|
|
285
|
+
defer_timeout: Timeout in seconds for DEFER async callbacks (default 30s)
|
|
286
|
+
"""
|
|
287
|
+
self._config = config
|
|
288
|
+
self._kernel = StatelessKernel(
|
|
289
|
+
backend=config.state_backend or MemoryBackend()
|
|
290
|
+
)
|
|
291
|
+
self._audit_log: list[AuditEntry] = []
|
|
292
|
+
self._max_audit_entries = config.max_audit_log_size
|
|
293
|
+
self._escalation_queue: list[EscalationRequest] = []
|
|
294
|
+
self._defer_timeout = defer_timeout
|
|
295
|
+
self._defer_callback: Callable[[str, dict[str, Any]], asyncio.Future[PolicyDecision]] | None = None
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def agent_id(self) -> str:
|
|
299
|
+
"""Get the agent's unique identifier."""
|
|
300
|
+
return self._config.agent_id
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def policies(self) -> list[str]:
|
|
304
|
+
"""Get the agent's active policies."""
|
|
305
|
+
return self._config.policies.copy()
|
|
306
|
+
|
|
307
|
+
def _new_context(self, **extra_metadata: Any) -> ExecutionContext:
|
|
308
|
+
"""Create a new execution context for a request.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
**extra_metadata: Additional metadata to include
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Fresh ExecutionContext with agent's default settings
|
|
315
|
+
|
|
316
|
+
Raises:
|
|
317
|
+
ValueError: If any metadata value exceeds max_metadata_size_bytes
|
|
318
|
+
"""
|
|
319
|
+
metadata = {**self._config.metadata, **extra_metadata}
|
|
320
|
+
max_size = self._config.max_metadata_size_bytes
|
|
321
|
+
for key, value in metadata.items():
|
|
322
|
+
size = sys.getsizeof(value)
|
|
323
|
+
if size > max_size:
|
|
324
|
+
raise ValueError(
|
|
325
|
+
f"Metadata key {key!r} value size ({size} bytes) "
|
|
326
|
+
f"exceeds limit ({max_size} bytes)"
|
|
327
|
+
)
|
|
328
|
+
metadata = copy.deepcopy(metadata)
|
|
329
|
+
return ExecutionContext(
|
|
330
|
+
agent_id=self._config.agent_id,
|
|
331
|
+
policies=self._config.policies.copy(),
|
|
332
|
+
metadata=metadata,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def set_defer_callback(
|
|
336
|
+
self,
|
|
337
|
+
callback: Callable[[str, dict[str, Any]], asyncio.Future[PolicyDecision]],
|
|
338
|
+
) -> None:
|
|
339
|
+
"""Register an async callback for DEFER policy decisions.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
callback: Async callable receiving (action, params) and returning
|
|
343
|
+
a Future that resolves to a PolicyDecision.
|
|
344
|
+
"""
|
|
345
|
+
self._defer_callback = callback
|
|
346
|
+
|
|
347
|
+
async def _enforce_policy(
|
|
348
|
+
self,
|
|
349
|
+
decision: PolicyDecision,
|
|
350
|
+
action: str,
|
|
351
|
+
params: dict[str, Any],
|
|
352
|
+
reason: str = "",
|
|
353
|
+
) -> ExecutionResult:
|
|
354
|
+
"""Handle a policy decision, including ESCALATE and DEFER.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
decision: The PolicyDecision to enforce
|
|
358
|
+
action: Name of the action under evaluation
|
|
359
|
+
params: Parameters for the action
|
|
360
|
+
reason: Human-readable reason for the decision
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
ExecutionResult representing the enforcement outcome
|
|
364
|
+
"""
|
|
365
|
+
if decision == PolicyDecision.ESCALATE:
|
|
366
|
+
escalation = EscalationRequest(
|
|
367
|
+
action=action,
|
|
368
|
+
reason=reason or f"Action '{action}' escalated for human review",
|
|
369
|
+
requested_by=self._config.agent_id,
|
|
370
|
+
)
|
|
371
|
+
self._escalation_queue.append(escalation)
|
|
372
|
+
return ExecutionResult(
|
|
373
|
+
success=False,
|
|
374
|
+
data=escalation.to_dict(),
|
|
375
|
+
error=None,
|
|
376
|
+
signal="ESCALATE",
|
|
377
|
+
metadata={"pending_review": True},
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
if decision == PolicyDecision.DEFER:
|
|
381
|
+
if self._defer_callback is None:
|
|
382
|
+
return ExecutionResult(
|
|
383
|
+
success=False,
|
|
384
|
+
data=None,
|
|
385
|
+
error="DEFER requested but no callback registered",
|
|
386
|
+
signal="DEFER",
|
|
387
|
+
)
|
|
388
|
+
try:
|
|
389
|
+
future = self._defer_callback(action, params)
|
|
390
|
+
resolved = await asyncio.wait_for(
|
|
391
|
+
future, timeout=self._defer_timeout
|
|
392
|
+
)
|
|
393
|
+
if resolved == PolicyDecision.ALLOW:
|
|
394
|
+
return ExecutionResult(success=True, data=None)
|
|
395
|
+
return ExecutionResult(
|
|
396
|
+
success=False,
|
|
397
|
+
data=None,
|
|
398
|
+
error=f"Deferred evaluation resolved to {resolved.value}",
|
|
399
|
+
signal=resolved.value.upper(),
|
|
400
|
+
)
|
|
401
|
+
except asyncio.TimeoutError:
|
|
402
|
+
return ExecutionResult(
|
|
403
|
+
success=False,
|
|
404
|
+
data=None,
|
|
405
|
+
error=(
|
|
406
|
+
f"DEFER timeout after {self._defer_timeout}s "
|
|
407
|
+
f"for action '{action}'"
|
|
408
|
+
),
|
|
409
|
+
signal="DEFER_TIMEOUT",
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
if decision == PolicyDecision.DENY:
|
|
413
|
+
return ExecutionResult(
|
|
414
|
+
success=False,
|
|
415
|
+
data=None,
|
|
416
|
+
error=reason or f"Action '{action}' denied by policy",
|
|
417
|
+
signal="SIGKILL",
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
# ALLOW / AUDIT — no blocking
|
|
421
|
+
return ExecutionResult(success=True, data=None)
|
|
422
|
+
|
|
423
|
+
async def _execute(
|
|
424
|
+
self,
|
|
425
|
+
action: str,
|
|
426
|
+
params: dict[str, Any],
|
|
427
|
+
context: ExecutionContext | None = None,
|
|
428
|
+
) -> ExecutionResult:
|
|
429
|
+
"""Execute an action through the kernel with policy checks.
|
|
430
|
+
|
|
431
|
+
This is the primary method for agents to perform actions.
|
|
432
|
+
All actions are:
|
|
433
|
+
1. Checked against configured policies
|
|
434
|
+
2. Logged for audit
|
|
435
|
+
3. Executed through the kernel
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
action: Name of the action to execute
|
|
439
|
+
params: Parameters for the action
|
|
440
|
+
context: Optional custom context (uses default if None)
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
ExecutionResult with success status, data, and any errors
|
|
444
|
+
"""
|
|
445
|
+
ctx = context or self._new_context()
|
|
446
|
+
request_id = str(uuid4())[:16]
|
|
447
|
+
|
|
448
|
+
# Create audit entry
|
|
449
|
+
audit = AuditEntry(
|
|
450
|
+
timestamp=datetime.now(timezone.utc),
|
|
451
|
+
agent_id=self._config.agent_id,
|
|
452
|
+
request_id=request_id,
|
|
453
|
+
action=action,
|
|
454
|
+
params=params,
|
|
455
|
+
decision=PolicyDecision.ALLOW, # Will be updated
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
# Execute through kernel with timing
|
|
459
|
+
t0 = time.monotonic()
|
|
460
|
+
result = await self._kernel.execute(action, params, ctx)
|
|
461
|
+
elapsed_ms = (time.monotonic() - t0) * 1000.0
|
|
462
|
+
|
|
463
|
+
# Update audit entry with result
|
|
464
|
+
if result.signal == "SIGKILL":
|
|
465
|
+
audit.decision = PolicyDecision.DENY
|
|
466
|
+
elif result.signal == "ESCALATE":
|
|
467
|
+
audit.decision = PolicyDecision.ESCALATE
|
|
468
|
+
elif result.signal in ("DEFER", "DEFER_TIMEOUT"):
|
|
469
|
+
audit.decision = PolicyDecision.DEFER
|
|
470
|
+
audit.result_success = result.success
|
|
471
|
+
audit.error = result.error
|
|
472
|
+
audit.execution_time_ms = elapsed_ms
|
|
473
|
+
|
|
474
|
+
self._audit_log.append(audit)
|
|
475
|
+
if len(self._audit_log) > self._max_audit_entries:
|
|
476
|
+
self._audit_log = self._audit_log[-self._max_audit_entries:]
|
|
477
|
+
|
|
478
|
+
return result
|
|
479
|
+
|
|
480
|
+
def get_audit_log(self) -> list[dict[str, Any]]:
|
|
481
|
+
"""Get the agent's audit log.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
List of audit entries as dictionaries
|
|
485
|
+
"""
|
|
486
|
+
return [entry.to_dict() for entry in self._audit_log]
|
|
487
|
+
|
|
488
|
+
def clear_audit_log(self) -> None:
|
|
489
|
+
"""Clear the agent's audit log."""
|
|
490
|
+
self._audit_log.clear()
|
|
491
|
+
|
|
492
|
+
def get_execution_stats(self) -> dict[str, Any]:
|
|
493
|
+
"""Return execution time statistics from audit log entries.
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
Dictionary with avg, min, max, p99 execution times in milliseconds,
|
|
497
|
+
and the total count of timed entries.
|
|
498
|
+
"""
|
|
499
|
+
times = [
|
|
500
|
+
e.execution_time_ms
|
|
501
|
+
for e in self._audit_log
|
|
502
|
+
if e.execution_time_ms is not None
|
|
503
|
+
]
|
|
504
|
+
if not times:
|
|
505
|
+
return {"count": 0, "avg_ms": 0.0, "min_ms": 0.0, "max_ms": 0.0, "p99_ms": 0.0}
|
|
506
|
+
times_sorted = sorted(times)
|
|
507
|
+
count = len(times_sorted)
|
|
508
|
+
p99_idx = min(int(count * 0.99), count - 1)
|
|
509
|
+
return {
|
|
510
|
+
"count": count,
|
|
511
|
+
"avg_ms": sum(times_sorted) / count,
|
|
512
|
+
"min_ms": times_sorted[0],
|
|
513
|
+
"max_ms": times_sorted[-1],
|
|
514
|
+
"p99_ms": times_sorted[p99_idx],
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
def query_audit_log(
|
|
518
|
+
self,
|
|
519
|
+
action: str | None = None,
|
|
520
|
+
decision: str | None = None,
|
|
521
|
+
since: datetime | None = None,
|
|
522
|
+
limit: int | None = None,
|
|
523
|
+
offset: int = 0,
|
|
524
|
+
) -> list[dict[str, Any]]:
|
|
525
|
+
"""Query audit log with optional filters.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
action: Filter by action name (exact match).
|
|
529
|
+
decision: Filter by decision value (e.g. "allow", "deny").
|
|
530
|
+
since: Only include entries at or after this timestamp.
|
|
531
|
+
limit: Maximum number of entries to return.
|
|
532
|
+
offset: Number of matching entries to skip (for pagination).
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
List of matching audit entries as dictionaries.
|
|
536
|
+
"""
|
|
537
|
+
results: list[AuditEntry] = self._audit_log
|
|
538
|
+
if action is not None:
|
|
539
|
+
results = [e for e in results if e.action == action]
|
|
540
|
+
if decision is not None:
|
|
541
|
+
results = [e for e in results if e.decision.value == decision]
|
|
542
|
+
if since is not None:
|
|
543
|
+
results = [e for e in results if e.timestamp >= since]
|
|
544
|
+
results = results[offset:]
|
|
545
|
+
if limit is not None:
|
|
546
|
+
results = results[:limit]
|
|
547
|
+
return [e.to_dict() for e in results]
|
|
548
|
+
|
|
549
|
+
def get_escalation_queue(self) -> list[EscalationRequest]:
|
|
550
|
+
"""Get pending escalation requests."""
|
|
551
|
+
return [e for e in self._escalation_queue if e.status == "pending"]
|
|
552
|
+
|
|
553
|
+
@abstractmethod
|
|
554
|
+
async def run(self, *args, **kwargs) -> ExecutionResult:
|
|
555
|
+
"""Run the agent's main task.
|
|
556
|
+
|
|
557
|
+
Subclasses must implement this method to define the agent's
|
|
558
|
+
primary functionality.
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
ExecutionResult with the outcome of the task
|
|
562
|
+
"""
|
|
563
|
+
pass
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
class ToolUsingAgent(BaseAgent):
|
|
567
|
+
"""Base class for agents that use registered tools from ATR.
|
|
568
|
+
|
|
569
|
+
Extends BaseAgent with tool discovery and execution capabilities.
|
|
570
|
+
Tools are executed through the kernel for policy enforcement.
|
|
571
|
+
|
|
572
|
+
Example:
|
|
573
|
+
>>> class AnalysisAgent(ToolUsingAgent):
|
|
574
|
+
... async def run(self, data: str) -> ExecutionResult:
|
|
575
|
+
... # Use registered tools
|
|
576
|
+
... parsed = await self._use_tool("json_parser", {"text": data})
|
|
577
|
+
... return parsed
|
|
578
|
+
"""
|
|
579
|
+
|
|
580
|
+
def __init__(self, config: AgentConfig, tools: list[str] | None = None):
|
|
581
|
+
"""Initialize the tool-using agent.
|
|
582
|
+
|
|
583
|
+
Args:
|
|
584
|
+
config: Agent configuration
|
|
585
|
+
tools: Optional list of tool names this agent is allowed to use
|
|
586
|
+
"""
|
|
587
|
+
super().__init__(config)
|
|
588
|
+
self._allowed_tools = set(tools) if tools else None
|
|
589
|
+
|
|
590
|
+
async def _use_tool(
|
|
591
|
+
self,
|
|
592
|
+
tool_name: str,
|
|
593
|
+
params: dict[str, Any],
|
|
594
|
+
) -> ExecutionResult:
|
|
595
|
+
"""Use a registered tool through the kernel.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
tool_name: Name of the tool to use
|
|
599
|
+
params: Parameters to pass to the tool
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
ExecutionResult from tool execution
|
|
603
|
+
"""
|
|
604
|
+
# Check tool allowlist if configured
|
|
605
|
+
if self._allowed_tools and tool_name not in self._allowed_tools:
|
|
606
|
+
return ExecutionResult(
|
|
607
|
+
success=False,
|
|
608
|
+
data=None,
|
|
609
|
+
error=f"Tool '{tool_name}' not in allowed tools list",
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
# Execute through kernel
|
|
613
|
+
return await self._execute(
|
|
614
|
+
action=f"tool:{tool_name}",
|
|
615
|
+
params=params,
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
def list_allowed_tools(self) -> list[str] | None:
|
|
619
|
+
"""Get list of allowed tools, or None if all tools allowed."""
|
|
620
|
+
return list(self._allowed_tools) if self._allowed_tools else None
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
# Type variable for generic agent results
|
|
624
|
+
T = TypeVar("T")
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
@dataclass
|
|
628
|
+
class TypedResult(Generic[T]):
|
|
629
|
+
"""A typed wrapper for execution results.
|
|
630
|
+
|
|
631
|
+
Useful when you want type hints on the result data.
|
|
632
|
+
"""
|
|
633
|
+
success: bool
|
|
634
|
+
data: T | None = None
|
|
635
|
+
error: str | None = None
|
|
636
|
+
|
|
637
|
+
@classmethod
|
|
638
|
+
def from_execution_result(
|
|
639
|
+
cls,
|
|
640
|
+
result: ExecutionResult,
|
|
641
|
+
transform: Callable[[Any], T] | None = None,
|
|
642
|
+
) -> TypedResult[T]:
|
|
643
|
+
"""Create from an ExecutionResult with optional transformation."""
|
|
644
|
+
data = None
|
|
645
|
+
if result.success and result.data is not None:
|
|
646
|
+
data = transform(result.data) if transform else result.data
|
|
647
|
+
return cls(
|
|
648
|
+
success=result.success,
|
|
649
|
+
data=data,
|
|
650
|
+
error=result.error,
|
|
651
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Circuit Breaker — backward-compatibility shim.
|
|
4
|
+
|
|
5
|
+
Attempts to import the canonical implementation from
|
|
6
|
+
``agent_sre.cascade.circuit_breaker``. When ``agent_sre`` is not
|
|
7
|
+
installed the standalone fallback in ``agent_os._circuit_breaker_impl``
|
|
8
|
+
is re-exported so that ``agent_os`` (and in particular ``stateless.py``)
|
|
9
|
+
continues to work without requiring the optional SRE package.
|
|
10
|
+
|
|
11
|
+
.. deprecated::
|
|
12
|
+
Prefer importing directly from ``agent_sre.cascade.circuit_breaker``.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from agent_sre.cascade.circuit_breaker import ( # noqa: F401
|
|
19
|
+
CascadeDetector,
|
|
20
|
+
CircuitBreaker,
|
|
21
|
+
CircuitBreakerConfig,
|
|
22
|
+
CircuitBreakerOpen,
|
|
23
|
+
CircuitOpenError,
|
|
24
|
+
CircuitState,
|
|
25
|
+
)
|
|
26
|
+
except ImportError:
|
|
27
|
+
from agent_os._circuit_breaker_impl import ( # noqa: F401
|
|
28
|
+
CascadeDetector,
|
|
29
|
+
CircuitBreaker,
|
|
30
|
+
CircuitBreakerConfig,
|
|
31
|
+
CircuitBreakerOpen,
|
|
32
|
+
CircuitOpenError,
|
|
33
|
+
CircuitState,
|
|
34
|
+
)
|