agent_os_kernel 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- nexus/tests/test_schemas.py +295 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Standalone policy evaluator for Agent-OS governance.
|
|
5
|
+
|
|
6
|
+
Evaluates declarative PolicyDocuments against an execution context dict,
|
|
7
|
+
returning a PolicyDecision with matched rule, action, and audit information.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import re
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, Field
|
|
19
|
+
|
|
20
|
+
from .schema import PolicyAction, PolicyDocument, PolicyOperator, PolicyRule
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PolicyDecision(BaseModel):
|
|
26
|
+
"""Result of evaluating policies against an execution context."""
|
|
27
|
+
|
|
28
|
+
allowed: bool = True
|
|
29
|
+
matched_rule: str | None = None
|
|
30
|
+
action: str = "allow"
|
|
31
|
+
reason: str = "No rules matched; default action applied"
|
|
32
|
+
audit_entry: dict[str, Any] = Field(default_factory=dict)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PolicyEvaluator:
|
|
36
|
+
"""Evaluates a set of PolicyDocuments against execution contexts.
|
|
37
|
+
|
|
38
|
+
Supports external policy backends (OPA/Rego, Cedar) alongside the
|
|
39
|
+
native YAML/JSON engine. YAML rules are evaluated first; if no YAML
|
|
40
|
+
rule matches, external backends are consulted in registration order.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, policies: list[PolicyDocument] | None = None) -> None:
|
|
44
|
+
self.policies: list[PolicyDocument] = policies or []
|
|
45
|
+
self._backends: list[Any] = []
|
|
46
|
+
|
|
47
|
+
def load_policies(self, directory: str | Path) -> None:
|
|
48
|
+
"""Load all YAML policy files from a directory."""
|
|
49
|
+
directory = Path(directory)
|
|
50
|
+
for path in sorted(directory.glob("*.yaml")):
|
|
51
|
+
self.policies.append(PolicyDocument.from_yaml(path))
|
|
52
|
+
for path in sorted(directory.glob("*.yml")):
|
|
53
|
+
self.policies.append(PolicyDocument.from_yaml(path))
|
|
54
|
+
|
|
55
|
+
def add_backend(self, backend: Any) -> None:
|
|
56
|
+
"""Register an external policy backend (OPA, Cedar, etc.).
|
|
57
|
+
|
|
58
|
+
Backends are consulted in registration order when no YAML rule
|
|
59
|
+
matches. Each backend must implement ``evaluate(context) ->
|
|
60
|
+
BackendDecision`` and a ``name`` property.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
backend: An ``ExternalPolicyBackend`` implementation such as
|
|
64
|
+
``OPABackend`` or ``CedarBackend`` from
|
|
65
|
+
``agent_os.policies.backends``.
|
|
66
|
+
"""
|
|
67
|
+
self._backends.append(backend)
|
|
68
|
+
|
|
69
|
+
def load_rego(
|
|
70
|
+
self,
|
|
71
|
+
rego_path: str | None = None,
|
|
72
|
+
rego_content: str | None = None,
|
|
73
|
+
package: str = "agentos",
|
|
74
|
+
) -> Any:
|
|
75
|
+
"""Convenience: register an OPA/Rego backend.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
rego_path: Path to a ``.rego`` file.
|
|
79
|
+
rego_content: Inline Rego policy string.
|
|
80
|
+
package: Rego package name for query construction.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
The ``OPABackend`` instance.
|
|
84
|
+
"""
|
|
85
|
+
from .backends import OPABackend
|
|
86
|
+
|
|
87
|
+
backend = OPABackend(
|
|
88
|
+
rego_path=rego_path, rego_content=rego_content, package=package
|
|
89
|
+
)
|
|
90
|
+
self.add_backend(backend)
|
|
91
|
+
return backend
|
|
92
|
+
|
|
93
|
+
def load_cedar(
|
|
94
|
+
self,
|
|
95
|
+
policy_path: str | None = None,
|
|
96
|
+
policy_content: str | None = None,
|
|
97
|
+
entities: list[dict[str, Any]] | None = None,
|
|
98
|
+
) -> Any:
|
|
99
|
+
"""Convenience: register a Cedar backend.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
policy_path: Path to a ``.cedar`` policy file.
|
|
103
|
+
policy_content: Inline Cedar policy string.
|
|
104
|
+
entities: Cedar entities for authorization context.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The ``CedarBackend`` instance.
|
|
108
|
+
"""
|
|
109
|
+
from .backends import CedarBackend
|
|
110
|
+
|
|
111
|
+
backend = CedarBackend(
|
|
112
|
+
policy_path=policy_path,
|
|
113
|
+
policy_content=policy_content,
|
|
114
|
+
entities=entities,
|
|
115
|
+
)
|
|
116
|
+
self.add_backend(backend)
|
|
117
|
+
return backend
|
|
118
|
+
|
|
119
|
+
def evaluate(self, context: dict[str, Any]) -> PolicyDecision:
|
|
120
|
+
"""Evaluate all loaded policy rules against the given context.
|
|
121
|
+
|
|
122
|
+
Rules are sorted by priority (descending). The first matching rule
|
|
123
|
+
determines the decision. If no YAML rule matches and external
|
|
124
|
+
backends are registered, they are consulted in order. If nothing
|
|
125
|
+
matches, the default action from the first policy (or global allow)
|
|
126
|
+
is used.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
all_rules: list[tuple[PolicyRule, PolicyDocument]] = []
|
|
130
|
+
for doc in self.policies:
|
|
131
|
+
for rule in doc.rules:
|
|
132
|
+
all_rules.append((rule, doc))
|
|
133
|
+
|
|
134
|
+
# Sort by priority descending so highest priority is checked first
|
|
135
|
+
all_rules.sort(key=lambda pair: pair[0].priority, reverse=True)
|
|
136
|
+
|
|
137
|
+
for rule, doc in all_rules:
|
|
138
|
+
if _match_condition(rule.condition, context):
|
|
139
|
+
allowed = rule.action in (PolicyAction.ALLOW, PolicyAction.AUDIT)
|
|
140
|
+
return PolicyDecision(
|
|
141
|
+
allowed=allowed,
|
|
142
|
+
matched_rule=rule.name,
|
|
143
|
+
action=rule.action.value,
|
|
144
|
+
reason=rule.message or f"Matched rule '{rule.name}'",
|
|
145
|
+
audit_entry={
|
|
146
|
+
"policy": doc.name,
|
|
147
|
+
"rule": rule.name,
|
|
148
|
+
"action": rule.action.value,
|
|
149
|
+
"context_snapshot": context,
|
|
150
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# No YAML rule matched — consult external backends
|
|
155
|
+
for backend in self._backends:
|
|
156
|
+
result = backend.evaluate(context)
|
|
157
|
+
if result.error is None:
|
|
158
|
+
return PolicyDecision(
|
|
159
|
+
allowed=result.allowed,
|
|
160
|
+
matched_rule=None,
|
|
161
|
+
action=result.action,
|
|
162
|
+
reason=result.reason,
|
|
163
|
+
audit_entry={
|
|
164
|
+
"policy": f"external:{backend.name}",
|
|
165
|
+
"rule": None,
|
|
166
|
+
"action": result.action,
|
|
167
|
+
"backend": backend.name,
|
|
168
|
+
"evaluation_ms": result.evaluation_ms,
|
|
169
|
+
"context_snapshot": context,
|
|
170
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
171
|
+
},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# No rule matched — apply defaults
|
|
175
|
+
default_action = PolicyAction.ALLOW
|
|
176
|
+
if self.policies:
|
|
177
|
+
default_action = self.policies[0].defaults.action
|
|
178
|
+
allowed = default_action in (PolicyAction.ALLOW, PolicyAction.AUDIT)
|
|
179
|
+
return PolicyDecision(
|
|
180
|
+
allowed=allowed,
|
|
181
|
+
action=default_action.value,
|
|
182
|
+
reason="No rules matched; default action applied",
|
|
183
|
+
audit_entry={
|
|
184
|
+
"policy": self.policies[0].name if self.policies else None,
|
|
185
|
+
"rule": None,
|
|
186
|
+
"action": default_action.value,
|
|
187
|
+
"context_snapshot": context,
|
|
188
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
189
|
+
},
|
|
190
|
+
)
|
|
191
|
+
except Exception:
|
|
192
|
+
logger.error(
|
|
193
|
+
"Policy evaluation error — denying access (fail closed)",
|
|
194
|
+
exc_info=True,
|
|
195
|
+
)
|
|
196
|
+
return PolicyDecision(
|
|
197
|
+
allowed=False,
|
|
198
|
+
action="deny",
|
|
199
|
+
reason="Policy evaluation error — access denied (fail closed)",
|
|
200
|
+
audit_entry={
|
|
201
|
+
"policy": None,
|
|
202
|
+
"rule": None,
|
|
203
|
+
"action": "deny",
|
|
204
|
+
"context_snapshot": context,
|
|
205
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
206
|
+
"error": True,
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _match_condition(condition: Any, context: dict[str, Any]) -> bool:
|
|
212
|
+
"""Check whether a single PolicyCondition matches the context."""
|
|
213
|
+
ctx_value = context.get(condition.field)
|
|
214
|
+
if ctx_value is None:
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
op = condition.operator
|
|
218
|
+
target = condition.value
|
|
219
|
+
|
|
220
|
+
if op == PolicyOperator.EQ:
|
|
221
|
+
return ctx_value == target
|
|
222
|
+
if op == PolicyOperator.NE:
|
|
223
|
+
return ctx_value != target
|
|
224
|
+
if op == PolicyOperator.GT:
|
|
225
|
+
return ctx_value > target
|
|
226
|
+
if op == PolicyOperator.LT:
|
|
227
|
+
return ctx_value < target
|
|
228
|
+
if op == PolicyOperator.GTE:
|
|
229
|
+
return ctx_value >= target
|
|
230
|
+
if op == PolicyOperator.LTE:
|
|
231
|
+
return ctx_value <= target
|
|
232
|
+
if op == PolicyOperator.IN:
|
|
233
|
+
return ctx_value in target
|
|
234
|
+
if op == PolicyOperator.CONTAINS:
|
|
235
|
+
return target in ctx_value
|
|
236
|
+
if op == PolicyOperator.MATCHES:
|
|
237
|
+
return bool(re.search(str(target), str(ctx_value)))
|
|
238
|
+
|
|
239
|
+
return False
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/microsoft/agent-governance-toolkit/policy-schema/v1.0",
|
|
4
|
+
"title": "Agent-OS Policy Document",
|
|
5
|
+
"description": "JSON Schema for Agent-OS declarative governance policy documents.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"version": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Schema version identifier.",
|
|
11
|
+
"default": "1.0"
|
|
12
|
+
},
|
|
13
|
+
"name": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Human-readable policy name.",
|
|
16
|
+
"default": "unnamed"
|
|
17
|
+
},
|
|
18
|
+
"description": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "Detailed description of the policy purpose.",
|
|
21
|
+
"default": ""
|
|
22
|
+
},
|
|
23
|
+
"rules": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"description": "Ordered list of governance rules.",
|
|
26
|
+
"items": {
|
|
27
|
+
"$ref": "#/definitions/PolicyRule"
|
|
28
|
+
},
|
|
29
|
+
"default": []
|
|
30
|
+
},
|
|
31
|
+
"defaults": {
|
|
32
|
+
"$ref": "#/definitions/PolicyDefaults",
|
|
33
|
+
"description": "Default settings applied when no rule matches."
|
|
34
|
+
},
|
|
35
|
+
"a2a_conversation_policy": {
|
|
36
|
+
"$ref": "#/definitions/A2AConversationPolicy",
|
|
37
|
+
"description": "Policy for A2A conversation monitoring and feedback loop prevention."
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"additionalProperties": false,
|
|
41
|
+
"definitions": {
|
|
42
|
+
"PolicyOperator": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Comparison operator for policy conditions.",
|
|
45
|
+
"enum": ["eq", "ne", "gt", "lt", "gte", "lte", "in", "matches", "contains"]
|
|
46
|
+
},
|
|
47
|
+
"PolicyAction": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"description": "Action a policy rule prescribes.",
|
|
50
|
+
"enum": ["allow", "deny", "audit", "block"]
|
|
51
|
+
},
|
|
52
|
+
"PolicyCondition": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"description": "A single condition evaluated against execution context.",
|
|
55
|
+
"properties": {
|
|
56
|
+
"field": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "Context field name, e.g. 'tool_name', 'token_count'."
|
|
59
|
+
},
|
|
60
|
+
"operator": {
|
|
61
|
+
"$ref": "#/definitions/PolicyOperator",
|
|
62
|
+
"description": "Comparison operator."
|
|
63
|
+
},
|
|
64
|
+
"value": {
|
|
65
|
+
"description": "Value to compare against. Type depends on the operator."
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"required": ["field", "operator", "value"],
|
|
69
|
+
"additionalProperties": false
|
|
70
|
+
},
|
|
71
|
+
"PolicyRule": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"description": "A single governance rule within a policy document.",
|
|
74
|
+
"properties": {
|
|
75
|
+
"name": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "Unique rule identifier."
|
|
78
|
+
},
|
|
79
|
+
"condition": {
|
|
80
|
+
"$ref": "#/definitions/PolicyCondition",
|
|
81
|
+
"description": "Condition that triggers this rule."
|
|
82
|
+
},
|
|
83
|
+
"action": {
|
|
84
|
+
"$ref": "#/definitions/PolicyAction",
|
|
85
|
+
"description": "Action to take when the condition matches."
|
|
86
|
+
},
|
|
87
|
+
"priority": {
|
|
88
|
+
"type": "integer",
|
|
89
|
+
"description": "Higher priority rules are evaluated first.",
|
|
90
|
+
"default": 0
|
|
91
|
+
},
|
|
92
|
+
"message": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"description": "Human-readable explanation of the rule.",
|
|
95
|
+
"default": ""
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"required": ["name", "condition", "action"],
|
|
99
|
+
"additionalProperties": false
|
|
100
|
+
},
|
|
101
|
+
"PolicyDefaults": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"description": "Default settings applied when no rule matches.",
|
|
104
|
+
"properties": {
|
|
105
|
+
"action": {
|
|
106
|
+
"$ref": "#/definitions/PolicyAction",
|
|
107
|
+
"description": "Default action when no rule matches.",
|
|
108
|
+
"default": "allow"
|
|
109
|
+
},
|
|
110
|
+
"max_tokens": {
|
|
111
|
+
"type": "integer",
|
|
112
|
+
"description": "Maximum token budget.",
|
|
113
|
+
"default": 4096
|
|
114
|
+
},
|
|
115
|
+
"max_tool_calls": {
|
|
116
|
+
"type": "integer",
|
|
117
|
+
"description": "Maximum number of tool calls allowed.",
|
|
118
|
+
"default": 10
|
|
119
|
+
},
|
|
120
|
+
"confidence_threshold": {
|
|
121
|
+
"type": "number",
|
|
122
|
+
"description": "Minimum confidence score required.",
|
|
123
|
+
"default": 0.8,
|
|
124
|
+
"minimum": 0.0,
|
|
125
|
+
"maximum": 1.0
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"additionalProperties": false
|
|
129
|
+
},
|
|
130
|
+
"A2AConversationPolicy": {
|
|
131
|
+
"type": "object",
|
|
132
|
+
"description": "Policy for monitoring agent-to-agent conversations (OWASP ASI-8/10).",
|
|
133
|
+
"properties": {
|
|
134
|
+
"enabled": {
|
|
135
|
+
"type": "boolean",
|
|
136
|
+
"description": "Enable conversation guardian on A2A messages.",
|
|
137
|
+
"default": true
|
|
138
|
+
},
|
|
139
|
+
"escalation_score_threshold": {
|
|
140
|
+
"type": "number",
|
|
141
|
+
"description": "Escalation score that triggers PAUSE action.",
|
|
142
|
+
"default": 0.6,
|
|
143
|
+
"minimum": 0.0,
|
|
144
|
+
"maximum": 1.0
|
|
145
|
+
},
|
|
146
|
+
"escalation_warn_threshold": {
|
|
147
|
+
"type": "number",
|
|
148
|
+
"description": "Escalation score that triggers WARN action.",
|
|
149
|
+
"default": 0.4,
|
|
150
|
+
"minimum": 0.0,
|
|
151
|
+
"maximum": 1.0
|
|
152
|
+
},
|
|
153
|
+
"offensive_score_threshold": {
|
|
154
|
+
"type": "number",
|
|
155
|
+
"description": "Offensive intent score that triggers PAUSE action.",
|
|
156
|
+
"default": 0.5,
|
|
157
|
+
"minimum": 0.0,
|
|
158
|
+
"maximum": 1.0
|
|
159
|
+
},
|
|
160
|
+
"offensive_critical_threshold": {
|
|
161
|
+
"type": "number",
|
|
162
|
+
"description": "Offensive intent score that triggers QUARANTINE action.",
|
|
163
|
+
"default": 0.8,
|
|
164
|
+
"minimum": 0.0,
|
|
165
|
+
"maximum": 1.0
|
|
166
|
+
},
|
|
167
|
+
"max_retry_cycles": {
|
|
168
|
+
"type": "integer",
|
|
169
|
+
"description": "Max error-retry cycles before BREAK.",
|
|
170
|
+
"default": 3,
|
|
171
|
+
"minimum": 1
|
|
172
|
+
},
|
|
173
|
+
"max_conversation_turns": {
|
|
174
|
+
"type": "integer",
|
|
175
|
+
"description": "Max turns in a conversation before BREAK.",
|
|
176
|
+
"default": 30,
|
|
177
|
+
"minimum": 1
|
|
178
|
+
},
|
|
179
|
+
"on_escalation_detected": {
|
|
180
|
+
"type": "string",
|
|
181
|
+
"description": "Action when escalation is detected.",
|
|
182
|
+
"enum": ["warn", "pause", "break", "quarantine"],
|
|
183
|
+
"default": "warn"
|
|
184
|
+
},
|
|
185
|
+
"on_offensive_detected": {
|
|
186
|
+
"type": "string",
|
|
187
|
+
"description": "Action when offensive intent is detected.",
|
|
188
|
+
"enum": ["warn", "pause", "break", "quarantine"],
|
|
189
|
+
"default": "break"
|
|
190
|
+
},
|
|
191
|
+
"on_feedback_loop": {
|
|
192
|
+
"type": "string",
|
|
193
|
+
"description": "Action when feedback loop is detected.",
|
|
194
|
+
"enum": ["warn", "pause", "break", "quarantine"],
|
|
195
|
+
"default": "break"
|
|
196
|
+
},
|
|
197
|
+
"require_human_approval_on": {
|
|
198
|
+
"type": "array",
|
|
199
|
+
"description": "Conditions requiring human approval before proceeding.",
|
|
200
|
+
"items": {
|
|
201
|
+
"type": "string",
|
|
202
|
+
"enum": ["escalation_above_threshold", "offensive_intent_detected", "feedback_loop_detected"]
|
|
203
|
+
},
|
|
204
|
+
"default": []
|
|
205
|
+
},
|
|
206
|
+
"audit": {
|
|
207
|
+
"type": "object",
|
|
208
|
+
"description": "Audit settings for conversation monitoring.",
|
|
209
|
+
"properties": {
|
|
210
|
+
"capture_full_transcript": {
|
|
211
|
+
"type": "boolean",
|
|
212
|
+
"description": "Log full message content in audit trail.",
|
|
213
|
+
"default": true
|
|
214
|
+
},
|
|
215
|
+
"retention_days": {
|
|
216
|
+
"type": "integer",
|
|
217
|
+
"description": "Days to retain conversation audit logs. EU AI Act Art. 26(6) requires minimum 180 days for high-risk systems.",
|
|
218
|
+
"default": 180,
|
|
219
|
+
"minimum": 30
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
"additionalProperties": false
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
"additionalProperties": false
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Shared rate limiting primitives for toolkit policy layers.
|
|
4
|
+
|
|
5
|
+
These primitives provide a canonical token-bucket foundation without collapsing
|
|
6
|
+
layer-specific limiters that serve different architectural boundaries.
|
|
7
|
+
|
|
8
|
+
See also:
|
|
9
|
+
- hypervisor.security.rate_limiter: runtime-layer per-agent/per-ring limits.
|
|
10
|
+
- agent_os.integrations.rate_limiter: tool-call policy limits in Agent OS.
|
|
11
|
+
- agentmesh.services.rate_limiter: service/proxy-level limits in Agent Mesh.
|
|
12
|
+
- agentmesh.services.rate_limit_middleware: HTTP edge middleware in Agent Mesh.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RateLimitExceeded(Exception):
|
|
24
|
+
"""Raised when a rate-limited operation cannot proceed."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class RateLimitConfig:
|
|
29
|
+
"""Basic token-bucket configuration.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
capacity: Maximum burst size (the most tokens the bucket may hold).
|
|
33
|
+
refill_rate: Tokens replenished per second.
|
|
34
|
+
initial_tokens: Optional initial token count. Defaults to ``capacity``.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
capacity: float
|
|
38
|
+
refill_rate: float
|
|
39
|
+
initial_tokens: float | None = None
|
|
40
|
+
|
|
41
|
+
def __post_init__(self) -> None:
|
|
42
|
+
if self.capacity <= 0:
|
|
43
|
+
raise ValueError("capacity must be positive")
|
|
44
|
+
if self.refill_rate < 0:
|
|
45
|
+
raise ValueError("refill_rate must be non-negative")
|
|
46
|
+
if self.initial_tokens is not None and not 0 <= self.initial_tokens <= self.capacity:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
"initial_tokens must be between 0 and capacity"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def rate(self) -> float:
|
|
53
|
+
"""Alias for ``refill_rate`` for callers that prefer ``rate`` terminology."""
|
|
54
|
+
return self.refill_rate
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class TokenBucket:
|
|
59
|
+
"""Thread-safe token bucket for rate limiting."""
|
|
60
|
+
|
|
61
|
+
capacity: float
|
|
62
|
+
tokens: float
|
|
63
|
+
refill_rate: float
|
|
64
|
+
last_refill: float = field(default_factory=time.monotonic)
|
|
65
|
+
_lock: Any = field(
|
|
66
|
+
default_factory=threading.Lock,
|
|
67
|
+
init=False,
|
|
68
|
+
repr=False,
|
|
69
|
+
compare=False,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def __post_init__(self) -> None:
|
|
73
|
+
if self.capacity <= 0:
|
|
74
|
+
raise ValueError("capacity must be positive")
|
|
75
|
+
if self.refill_rate < 0:
|
|
76
|
+
raise ValueError("refill_rate must be non-negative")
|
|
77
|
+
if not 0 <= self.tokens <= self.capacity:
|
|
78
|
+
raise ValueError("tokens must be between 0 and capacity")
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_config(cls, config: RateLimitConfig) -> "TokenBucket":
|
|
82
|
+
"""Build a token bucket from a :class:`RateLimitConfig`."""
|
|
83
|
+
initial_tokens = (
|
|
84
|
+
config.capacity if config.initial_tokens is None else config.initial_tokens
|
|
85
|
+
)
|
|
86
|
+
return cls(
|
|
87
|
+
capacity=config.capacity,
|
|
88
|
+
tokens=initial_tokens,
|
|
89
|
+
refill_rate=config.refill_rate,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def _refill_unlocked(self, now: float | None = None) -> None:
|
|
93
|
+
current = time.monotonic() if now is None else now
|
|
94
|
+
elapsed = current - self.last_refill
|
|
95
|
+
if elapsed <= 0:
|
|
96
|
+
return
|
|
97
|
+
self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
|
|
98
|
+
self.last_refill = current
|
|
99
|
+
|
|
100
|
+
def consume(self, tokens: float = 1.0) -> bool:
|
|
101
|
+
"""Try to consume *tokens*. Returns ``True`` when enough tokens exist."""
|
|
102
|
+
if tokens <= 0:
|
|
103
|
+
raise ValueError("tokens must be positive")
|
|
104
|
+
with self._lock:
|
|
105
|
+
self._refill_unlocked()
|
|
106
|
+
if self.tokens >= tokens:
|
|
107
|
+
self.tokens -= tokens
|
|
108
|
+
return True
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def available(self) -> float:
|
|
113
|
+
"""Current token count after refilling for elapsed time."""
|
|
114
|
+
with self._lock:
|
|
115
|
+
self._refill_unlocked()
|
|
116
|
+
return self.tokens
|
|
117
|
+
|
|
118
|
+
def tokens_available(self) -> float:
|
|
119
|
+
"""Method alias for callers that prefer a function over a property."""
|
|
120
|
+
return self.available
|
|
121
|
+
|
|
122
|
+
def time_until_available(self, tokens: float = 1.0) -> float:
|
|
123
|
+
"""Return seconds until *tokens* are available."""
|
|
124
|
+
if tokens <= 0:
|
|
125
|
+
raise ValueError("tokens must be positive")
|
|
126
|
+
with self._lock:
|
|
127
|
+
self._refill_unlocked()
|
|
128
|
+
if self.tokens >= tokens:
|
|
129
|
+
return 0.0
|
|
130
|
+
if self.refill_rate == 0:
|
|
131
|
+
return float("inf")
|
|
132
|
+
deficit = tokens - self.tokens
|
|
133
|
+
return deficit / self.refill_rate
|
|
134
|
+
|
|
135
|
+
def reset(self, tokens: float | None = None) -> None:
|
|
136
|
+
"""Reset the bucket to *tokens* or back to full capacity."""
|
|
137
|
+
target_tokens = self.capacity if tokens is None else tokens
|
|
138
|
+
if not 0 <= target_tokens <= self.capacity:
|
|
139
|
+
raise ValueError("tokens must be between 0 and capacity")
|
|
140
|
+
with self._lock:
|
|
141
|
+
self.tokens = target_tokens
|
|
142
|
+
self.last_refill = time.monotonic()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
__all__ = ["RateLimitConfig", "RateLimitExceeded", "TokenBucket"]
|