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,169 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Bridge between legacy GovernancePolicy and declarative PolicyDocument.
|
|
5
|
+
|
|
6
|
+
Provides bidirectional conversion so existing code continues to work while
|
|
7
|
+
new code can use the declarative policy format.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from ..integrations.base import GovernancePolicy, PatternType
|
|
13
|
+
from .schema import (
|
|
14
|
+
PolicyAction,
|
|
15
|
+
PolicyCondition,
|
|
16
|
+
PolicyDefaults,
|
|
17
|
+
PolicyDocument,
|
|
18
|
+
PolicyOperator,
|
|
19
|
+
PolicyRule,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def governance_to_document(policy: GovernancePolicy) -> PolicyDocument:
|
|
24
|
+
"""Convert an existing GovernancePolicy to a declarative PolicyDocument."""
|
|
25
|
+
rules: list[PolicyRule] = []
|
|
26
|
+
priority = 100
|
|
27
|
+
|
|
28
|
+
# Token limit rule
|
|
29
|
+
rules.append(
|
|
30
|
+
PolicyRule(
|
|
31
|
+
name="max_tokens",
|
|
32
|
+
condition=PolicyCondition(
|
|
33
|
+
field="token_count",
|
|
34
|
+
operator=PolicyOperator.GT,
|
|
35
|
+
value=policy.max_tokens,
|
|
36
|
+
),
|
|
37
|
+
action=PolicyAction.DENY,
|
|
38
|
+
priority=priority,
|
|
39
|
+
message=f"Token count exceeds limit of {policy.max_tokens}",
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
priority -= 1
|
|
43
|
+
|
|
44
|
+
# Tool call limit rule
|
|
45
|
+
rules.append(
|
|
46
|
+
PolicyRule(
|
|
47
|
+
name="max_tool_calls",
|
|
48
|
+
condition=PolicyCondition(
|
|
49
|
+
field="tool_call_count",
|
|
50
|
+
operator=PolicyOperator.GT,
|
|
51
|
+
value=policy.max_tool_calls,
|
|
52
|
+
),
|
|
53
|
+
action=PolicyAction.DENY,
|
|
54
|
+
priority=priority,
|
|
55
|
+
message=f"Tool call count exceeds limit of {policy.max_tool_calls}",
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
priority -= 1
|
|
59
|
+
|
|
60
|
+
# Allowed tools rule (if a tool is not in the allowed list, deny)
|
|
61
|
+
if policy.allowed_tools:
|
|
62
|
+
rules.append(
|
|
63
|
+
PolicyRule(
|
|
64
|
+
name="allowed_tools",
|
|
65
|
+
condition=PolicyCondition(
|
|
66
|
+
field="tool_name",
|
|
67
|
+
operator=PolicyOperator.IN,
|
|
68
|
+
value=policy.allowed_tools,
|
|
69
|
+
),
|
|
70
|
+
action=PolicyAction.ALLOW,
|
|
71
|
+
priority=priority,
|
|
72
|
+
message="Tool is in the allowed list",
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
priority -= 1
|
|
76
|
+
|
|
77
|
+
# Blocked patterns
|
|
78
|
+
for i, pattern in enumerate(policy.blocked_patterns):
|
|
79
|
+
if isinstance(pattern, str):
|
|
80
|
+
pat_str = pattern
|
|
81
|
+
operator = PolicyOperator.CONTAINS
|
|
82
|
+
else:
|
|
83
|
+
pat_str, pat_type = pattern
|
|
84
|
+
if pat_type == PatternType.REGEX:
|
|
85
|
+
operator = PolicyOperator.MATCHES
|
|
86
|
+
elif pat_type == PatternType.GLOB:
|
|
87
|
+
operator = PolicyOperator.MATCHES
|
|
88
|
+
else:
|
|
89
|
+
operator = PolicyOperator.CONTAINS
|
|
90
|
+
|
|
91
|
+
rules.append(
|
|
92
|
+
PolicyRule(
|
|
93
|
+
name=f"blocked_pattern_{i}",
|
|
94
|
+
condition=PolicyCondition(
|
|
95
|
+
field="content",
|
|
96
|
+
operator=operator,
|
|
97
|
+
value=pat_str,
|
|
98
|
+
),
|
|
99
|
+
action=PolicyAction.BLOCK,
|
|
100
|
+
priority=priority,
|
|
101
|
+
message=f"Content matches blocked pattern: {pat_str}",
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
priority -= 1
|
|
105
|
+
|
|
106
|
+
# Confidence threshold rule
|
|
107
|
+
rules.append(
|
|
108
|
+
PolicyRule(
|
|
109
|
+
name="confidence_threshold",
|
|
110
|
+
condition=PolicyCondition(
|
|
111
|
+
field="confidence",
|
|
112
|
+
operator=PolicyOperator.LT,
|
|
113
|
+
value=policy.confidence_threshold,
|
|
114
|
+
),
|
|
115
|
+
action=PolicyAction.DENY,
|
|
116
|
+
priority=priority,
|
|
117
|
+
message=f"Confidence below threshold of {policy.confidence_threshold}",
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return PolicyDocument(
|
|
122
|
+
version=policy.version,
|
|
123
|
+
name=policy.name,
|
|
124
|
+
description=f"Auto-converted from GovernancePolicy '{policy.name}'",
|
|
125
|
+
rules=rules,
|
|
126
|
+
defaults=PolicyDefaults(
|
|
127
|
+
action=PolicyAction.ALLOW,
|
|
128
|
+
max_tokens=policy.max_tokens,
|
|
129
|
+
max_tool_calls=policy.max_tool_calls,
|
|
130
|
+
confidence_threshold=policy.confidence_threshold,
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def document_to_governance(doc: PolicyDocument) -> GovernancePolicy:
|
|
136
|
+
"""Convert a declarative PolicyDocument back to a GovernancePolicy."""
|
|
137
|
+
max_tokens = doc.defaults.max_tokens
|
|
138
|
+
max_tool_calls = doc.defaults.max_tool_calls
|
|
139
|
+
confidence_threshold = doc.defaults.confidence_threshold
|
|
140
|
+
allowed_tools: list[str] = []
|
|
141
|
+
blocked_patterns: list[str | tuple[str, PatternType]] = []
|
|
142
|
+
|
|
143
|
+
for rule in doc.rules:
|
|
144
|
+
cond = rule.condition
|
|
145
|
+
|
|
146
|
+
if rule.name == "max_tokens" and cond.field == "token_count":
|
|
147
|
+
max_tokens = int(cond.value)
|
|
148
|
+
elif rule.name == "max_tool_calls" and cond.field == "tool_call_count":
|
|
149
|
+
max_tool_calls = int(cond.value)
|
|
150
|
+
elif rule.name == "allowed_tools" and cond.field == "tool_name":
|
|
151
|
+
if isinstance(cond.value, list):
|
|
152
|
+
allowed_tools = list(cond.value)
|
|
153
|
+
elif rule.name.startswith("blocked_pattern_") and cond.field == "content":
|
|
154
|
+
if cond.operator == PolicyOperator.MATCHES:
|
|
155
|
+
blocked_patterns.append((str(cond.value), PatternType.REGEX))
|
|
156
|
+
elif cond.operator == PolicyOperator.CONTAINS:
|
|
157
|
+
blocked_patterns.append(str(cond.value))
|
|
158
|
+
elif rule.name == "confidence_threshold" and cond.field == "confidence":
|
|
159
|
+
confidence_threshold = float(cond.value)
|
|
160
|
+
|
|
161
|
+
return GovernancePolicy(
|
|
162
|
+
name=doc.name,
|
|
163
|
+
max_tokens=max_tokens,
|
|
164
|
+
max_tool_calls=max_tool_calls,
|
|
165
|
+
allowed_tools=allowed_tools,
|
|
166
|
+
blocked_patterns=blocked_patterns,
|
|
167
|
+
confidence_threshold=confidence_threshold,
|
|
168
|
+
version=doc.version,
|
|
169
|
+
)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Budget policy rules for token, cost, and tool-call limits."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class BudgetPolicy:
|
|
13
|
+
"""Resource consumption limits for a governed task."""
|
|
14
|
+
|
|
15
|
+
max_tokens: Optional[int] = None
|
|
16
|
+
max_tool_calls: Optional[int] = None
|
|
17
|
+
max_cost_usd: Optional[float] = None
|
|
18
|
+
max_duration_seconds: Optional[float] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class BudgetTracker:
|
|
23
|
+
"""Tracks resource consumption against a BudgetPolicy.
|
|
24
|
+
|
|
25
|
+
Example::
|
|
26
|
+
|
|
27
|
+
policy = BudgetPolicy(max_tokens=8000, max_tool_calls=20)
|
|
28
|
+
tracker = BudgetTracker(policy)
|
|
29
|
+
tracker.record_tokens(1500)
|
|
30
|
+
tracker.record_tool_call()
|
|
31
|
+
if tracker.is_exceeded():
|
|
32
|
+
print(tracker.exceeded_reasons())
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
policy: BudgetPolicy
|
|
36
|
+
tokens_used: int = 0
|
|
37
|
+
tool_calls_used: int = 0
|
|
38
|
+
cost_usd_used: float = 0.0
|
|
39
|
+
duration_seconds_used: float = 0.0
|
|
40
|
+
|
|
41
|
+
def record_tokens(self, count: int) -> None:
|
|
42
|
+
self.tokens_used += count
|
|
43
|
+
|
|
44
|
+
def record_tool_call(self) -> None:
|
|
45
|
+
self.tool_calls_used += 1
|
|
46
|
+
|
|
47
|
+
def record_cost(self, amount: float) -> None:
|
|
48
|
+
self.cost_usd_used += amount
|
|
49
|
+
|
|
50
|
+
def record_duration(self, seconds: float) -> None:
|
|
51
|
+
self.duration_seconds_used += seconds
|
|
52
|
+
|
|
53
|
+
def is_exceeded(self) -> bool:
|
|
54
|
+
return len(self.exceeded_reasons()) > 0
|
|
55
|
+
|
|
56
|
+
def exceeded_reasons(self) -> list[str]:
|
|
57
|
+
reasons = []
|
|
58
|
+
p = self.policy
|
|
59
|
+
if p.max_tokens is not None and self.tokens_used > p.max_tokens:
|
|
60
|
+
reasons.append(f"tokens: {self.tokens_used}/{p.max_tokens}")
|
|
61
|
+
if p.max_tool_calls is not None and self.tool_calls_used > p.max_tool_calls:
|
|
62
|
+
reasons.append(f"tool_calls: {self.tool_calls_used}/{p.max_tool_calls}")
|
|
63
|
+
if p.max_cost_usd is not None and self.cost_usd_used > p.max_cost_usd:
|
|
64
|
+
reasons.append(f"cost: ${self.cost_usd_used:.2f}/${p.max_cost_usd:.2f}")
|
|
65
|
+
if p.max_duration_seconds is not None and self.duration_seconds_used > p.max_duration_seconds:
|
|
66
|
+
reasons.append(f"duration: {self.duration_seconds_used:.1f}s/{p.max_duration_seconds:.1f}s")
|
|
67
|
+
return reasons
|
|
68
|
+
|
|
69
|
+
def remaining(self) -> dict[str, float | int | None]:
|
|
70
|
+
p = self.policy
|
|
71
|
+
return {
|
|
72
|
+
"tokens": (p.max_tokens - self.tokens_used) if p.max_tokens else None,
|
|
73
|
+
"tool_calls": (p.max_tool_calls - self.tool_calls_used) if p.max_tool_calls else None,
|
|
74
|
+
"cost_usd": round(p.max_cost_usd - self.cost_usd_used, 4) if p.max_cost_usd else None,
|
|
75
|
+
"duration_seconds": round(p.max_duration_seconds - self.duration_seconds_used, 1) if p.max_duration_seconds else None,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
def utilization(self) -> dict[str, float | None]:
|
|
79
|
+
p = self.policy
|
|
80
|
+
return {
|
|
81
|
+
"tokens": round(self.tokens_used / p.max_tokens, 3) if p.max_tokens else None,
|
|
82
|
+
"tool_calls": round(self.tool_calls_used / p.max_tool_calls, 3) if p.max_tool_calls else None,
|
|
83
|
+
"cost_usd": round(self.cost_usd_used / p.max_cost_usd, 3) if p.max_cost_usd else None,
|
|
84
|
+
"duration_seconds": round(self.duration_seconds_used / p.max_duration_seconds, 3) if p.max_duration_seconds else None,
|
|
85
|
+
}
|
agent_os/policies/cli.py
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Policy CLI - Manage and validate security policies.
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
- validate: Check a policy file against the PolicyDocument schema
|
|
8
|
+
- test: Run security scenarios against policy definitions
|
|
9
|
+
- diff: Compare two policy versions structurally
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, List
|
|
20
|
+
|
|
21
|
+
import yaml
|
|
22
|
+
from pydantic import ValidationError
|
|
23
|
+
|
|
24
|
+
from agent_os.policies.schema import PolicyDocument
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Helpers
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _load_file(path: Path) -> dict:
|
|
33
|
+
"""Load a YAML or JSON file and return the parsed dict."""
|
|
34
|
+
text = path.read_text(encoding="utf-8")
|
|
35
|
+
if path.suffix == ".json":
|
|
36
|
+
return json.loads(text)
|
|
37
|
+
return yaml.safe_load(text)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _evaluate_condition(condition: dict, context: dict) -> bool:
|
|
41
|
+
"""Evaluate a single policy condition against a context dict."""
|
|
42
|
+
field = condition.get("field", "")
|
|
43
|
+
operator = condition.get("operator", "eq")
|
|
44
|
+
value = condition.get("value")
|
|
45
|
+
|
|
46
|
+
if field not in context:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
ctx_value = context[field]
|
|
50
|
+
|
|
51
|
+
if operator == "eq":
|
|
52
|
+
return ctx_value == value
|
|
53
|
+
if operator == "ne":
|
|
54
|
+
return ctx_value != value
|
|
55
|
+
if operator == "gt":
|
|
56
|
+
return ctx_value > value
|
|
57
|
+
if operator == "lt":
|
|
58
|
+
return ctx_value < value
|
|
59
|
+
if operator == "gte":
|
|
60
|
+
return ctx_value >= value
|
|
61
|
+
if operator == "lte":
|
|
62
|
+
return ctx_value <= value
|
|
63
|
+
if operator == "in":
|
|
64
|
+
return ctx_value in value
|
|
65
|
+
if operator == "contains":
|
|
66
|
+
return value in ctx_value
|
|
67
|
+
if operator == "matches":
|
|
68
|
+
return bool(re.match(str(value), str(ctx_value)))
|
|
69
|
+
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _resolve_action(rules: list[dict], defaults: dict | None, context: dict) -> str:
|
|
74
|
+
"""Find the first matching rule (highest priority) and return its action."""
|
|
75
|
+
sorted_rules = sorted(rules, key=lambda r: r.get("priority", 0), reverse=True)
|
|
76
|
+
|
|
77
|
+
for rule in sorted_rules:
|
|
78
|
+
condition = rule.get("condition", {})
|
|
79
|
+
if _evaluate_condition(condition, context):
|
|
80
|
+
return rule.get("action", "allow")
|
|
81
|
+
|
|
82
|
+
if defaults:
|
|
83
|
+
return defaults.get("action", "allow")
|
|
84
|
+
return "allow"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# Sub-commands
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _cmd_validate(args: argparse.Namespace) -> int:
|
|
93
|
+
"""Validate a policy file against the PolicyDocument Pydantic schema."""
|
|
94
|
+
path = Path(args.file)
|
|
95
|
+
|
|
96
|
+
if not path.exists():
|
|
97
|
+
print("ERROR: File not found: " + str(path), file=sys.stderr)
|
|
98
|
+
return 2
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
data = _load_file(path)
|
|
102
|
+
except Exception:
|
|
103
|
+
print("ERROR: Could not parse file: " + str(path), file=sys.stderr)
|
|
104
|
+
return 2
|
|
105
|
+
|
|
106
|
+
if data is None:
|
|
107
|
+
data = {}
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
PolicyDocument.model_validate(data)
|
|
111
|
+
except ValidationError as exc:
|
|
112
|
+
print("FAIL: " + str(exc), file=sys.stderr)
|
|
113
|
+
return 1
|
|
114
|
+
|
|
115
|
+
print("OK")
|
|
116
|
+
return 0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _cmd_test(args: argparse.Namespace) -> int:
|
|
120
|
+
"""Run test scenarios against a policy."""
|
|
121
|
+
policy_path = Path(args.policy)
|
|
122
|
+
scenarios_path = Path(args.scenarios)
|
|
123
|
+
|
|
124
|
+
if not policy_path.exists():
|
|
125
|
+
print("ERROR: Policy file not found: " + str(policy_path), file=sys.stderr)
|
|
126
|
+
return 2
|
|
127
|
+
|
|
128
|
+
if not scenarios_path.exists():
|
|
129
|
+
print("ERROR: Scenarios file not found: " + str(scenarios_path), file=sys.stderr)
|
|
130
|
+
return 2
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
policy_data = _load_file(policy_path)
|
|
134
|
+
scenarios_data = _load_file(scenarios_path)
|
|
135
|
+
except Exception:
|
|
136
|
+
print("ERROR: Could not parse files", file=sys.stderr)
|
|
137
|
+
return 2
|
|
138
|
+
|
|
139
|
+
rules = (policy_data or {}).get("rules", [])
|
|
140
|
+
defaults = (policy_data or {}).get("defaults", {})
|
|
141
|
+
scenarios: list[dict[str, Any]] = (scenarios_data or {}).get("scenarios", [])
|
|
142
|
+
|
|
143
|
+
if not scenarios:
|
|
144
|
+
print("ERROR: No scenarios provided", file=sys.stderr)
|
|
145
|
+
return 2
|
|
146
|
+
|
|
147
|
+
passed = 0
|
|
148
|
+
total = len(scenarios)
|
|
149
|
+
|
|
150
|
+
for scenario in scenarios:
|
|
151
|
+
context = scenario.get("context", {})
|
|
152
|
+
expected_action = scenario.get("expected_action")
|
|
153
|
+
expected_allowed = scenario.get("expected_allowed")
|
|
154
|
+
|
|
155
|
+
actual_action = _resolve_action(rules, defaults, context)
|
|
156
|
+
actual_allowed = actual_action == "allow"
|
|
157
|
+
|
|
158
|
+
ok = True
|
|
159
|
+
if expected_action is not None and actual_action != expected_action:
|
|
160
|
+
ok = False
|
|
161
|
+
if expected_allowed is not None and actual_allowed != expected_allowed:
|
|
162
|
+
ok = False
|
|
163
|
+
|
|
164
|
+
if ok:
|
|
165
|
+
passed += 1
|
|
166
|
+
else:
|
|
167
|
+
print(
|
|
168
|
+
f"FAIL: {scenario.get('name', 'unnamed')}: "
|
|
169
|
+
f"expected {expected_action}, got {actual_action}",
|
|
170
|
+
file=sys.stderr,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
print(f"{passed}/{total} scenarios passed")
|
|
174
|
+
return 0 if passed == total else 1
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _cmd_diff(args: argparse.Namespace) -> int:
|
|
178
|
+
"""Compare two policy files structurally."""
|
|
179
|
+
base_path = Path(args.base)
|
|
180
|
+
target_path = Path(args.target)
|
|
181
|
+
|
|
182
|
+
if not base_path.exists():
|
|
183
|
+
print("ERROR: File not found: " + str(base_path), file=sys.stderr)
|
|
184
|
+
return 2
|
|
185
|
+
if not target_path.exists():
|
|
186
|
+
print("ERROR: File not found: " + str(target_path), file=sys.stderr)
|
|
187
|
+
return 2
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
base_data = _load_file(base_path)
|
|
191
|
+
target_data = _load_file(target_path)
|
|
192
|
+
except Exception:
|
|
193
|
+
print("ERROR: Could not parse files", file=sys.stderr)
|
|
194
|
+
return 2
|
|
195
|
+
|
|
196
|
+
if base_data == target_data:
|
|
197
|
+
print("No differences")
|
|
198
|
+
return 0
|
|
199
|
+
|
|
200
|
+
diffs: list[str] = []
|
|
201
|
+
|
|
202
|
+
# --- rules ---
|
|
203
|
+
base_rules = {r["name"]: r for r in (base_data.get("rules") or [])}
|
|
204
|
+
target_rules = {r["name"]: r for r in (target_data.get("rules") or [])}
|
|
205
|
+
|
|
206
|
+
for name in target_rules:
|
|
207
|
+
if name not in base_rules:
|
|
208
|
+
diffs.append(f"rule added: {name}")
|
|
209
|
+
|
|
210
|
+
for name in base_rules:
|
|
211
|
+
if name not in target_rules:
|
|
212
|
+
diffs.append(f"rule removed: {name}")
|
|
213
|
+
|
|
214
|
+
for name in base_rules:
|
|
215
|
+
if name in target_rules:
|
|
216
|
+
br = base_rules[name]
|
|
217
|
+
tr = target_rules[name]
|
|
218
|
+
all_keys = set(br.keys()) | set(tr.keys())
|
|
219
|
+
for key in sorted(all_keys):
|
|
220
|
+
if br.get(key) != tr.get(key):
|
|
221
|
+
diffs.append(f"rule {name}: {key}: {br.get(key)} -> {tr.get(key)}")
|
|
222
|
+
|
|
223
|
+
# --- defaults ---
|
|
224
|
+
base_defaults = (base_data.get("defaults") or {})
|
|
225
|
+
target_defaults = (target_data.get("defaults") or {})
|
|
226
|
+
all_def_keys = set(base_defaults.keys()) | set(target_defaults.keys())
|
|
227
|
+
for key in sorted(all_def_keys):
|
|
228
|
+
if base_defaults.get(key) != target_defaults.get(key):
|
|
229
|
+
diffs.append(f"defaults: {key}: {base_defaults.get(key)} -> {target_defaults.get(key)}")
|
|
230
|
+
|
|
231
|
+
# --- other top-level fields ---
|
|
232
|
+
for key in sorted(set(base_data.keys()) | set(target_data.keys())):
|
|
233
|
+
if key in ("rules", "defaults"):
|
|
234
|
+
continue
|
|
235
|
+
if base_data.get(key) != target_data.get(key):
|
|
236
|
+
diffs.append(f"{key}: {base_data.get(key)} -> {target_data.get(key)}")
|
|
237
|
+
|
|
238
|
+
for d in diffs:
|
|
239
|
+
print(d)
|
|
240
|
+
|
|
241
|
+
return 1 if diffs else 0
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# ---------------------------------------------------------------------------
|
|
245
|
+
# Parser / entry-point
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
250
|
+
"""Build the argument parser."""
|
|
251
|
+
parser = argparse.ArgumentParser(
|
|
252
|
+
prog="policies-cli",
|
|
253
|
+
description="Agent OS Policy Management Tools",
|
|
254
|
+
)
|
|
255
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
256
|
+
|
|
257
|
+
# -- validate -----------------------------------------------------------
|
|
258
|
+
val_parser = subparsers.add_parser("validate", help="Validate a policy file")
|
|
259
|
+
val_parser.add_argument("file", help="Policy file to validate")
|
|
260
|
+
|
|
261
|
+
# -- test ---------------------------------------------------------------
|
|
262
|
+
test_parser = subparsers.add_parser("test", help="Test policy against scenarios")
|
|
263
|
+
test_parser.add_argument("policy", help="Policy file to test")
|
|
264
|
+
test_parser.add_argument("scenarios", help="Scenarios file (YAML/JSON)")
|
|
265
|
+
|
|
266
|
+
# -- diff ---------------------------------------------------------------
|
|
267
|
+
diff_parser = subparsers.add_parser("diff", help="Compare two policy files")
|
|
268
|
+
diff_parser.add_argument("base", help="Base policy file")
|
|
269
|
+
diff_parser.add_argument("target", help="Target policy file")
|
|
270
|
+
|
|
271
|
+
return parser
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def main(argv: List[str] | None = None) -> int:
|
|
275
|
+
"""CLI entry point."""
|
|
276
|
+
parser = build_parser()
|
|
277
|
+
args = parser.parse_args(argv)
|
|
278
|
+
|
|
279
|
+
if not args.command:
|
|
280
|
+
parser.print_help()
|
|
281
|
+
return 2
|
|
282
|
+
|
|
283
|
+
if args.command == "validate":
|
|
284
|
+
return _cmd_validate(args)
|
|
285
|
+
if args.command == "test":
|
|
286
|
+
return _cmd_test(args)
|
|
287
|
+
if args.command == "diff":
|
|
288
|
+
return _cmd_diff(args)
|
|
289
|
+
|
|
290
|
+
return 2
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
if __name__ == "__main__":
|
|
294
|
+
sys.exit(main())
|