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.
Files changed (337) hide show
  1. agent_control_plane/__init__.py +662 -0
  2. agent_control_plane/a2a_adapter.py +543 -0
  3. agent_control_plane/adapter.py +417 -0
  4. agent_control_plane/agent_hibernation.py +394 -0
  5. agent_control_plane/agent_kernel.py +470 -0
  6. agent_control_plane/compliance.py +720 -0
  7. agent_control_plane/constraint_graphs.py +478 -0
  8. agent_control_plane/control_plane.py +854 -0
  9. agent_control_plane/example_executors.py +195 -0
  10. agent_control_plane/execution_engine.py +231 -0
  11. agent_control_plane/flight_recorder.py +846 -0
  12. agent_control_plane/governance_layer.py +435 -0
  13. agent_control_plane/hf_utils.py +563 -0
  14. agent_control_plane/interfaces/__init__.py +55 -0
  15. agent_control_plane/interfaces/kernel_interface.py +361 -0
  16. agent_control_plane/interfaces/plugin_interface.py +497 -0
  17. agent_control_plane/interfaces/protocol_interfaces.py +387 -0
  18. agent_control_plane/kernel_space.py +1009 -0
  19. agent_control_plane/langchain_adapter.py +424 -0
  20. agent_control_plane/lifecycle.py +3113 -0
  21. agent_control_plane/mcp_adapter.py +653 -0
  22. agent_control_plane/ml_safety.py +563 -0
  23. agent_control_plane/multimodal.py +727 -0
  24. agent_control_plane/mute_agent.py +422 -0
  25. agent_control_plane/observability.py +787 -0
  26. agent_control_plane/orchestrator.py +482 -0
  27. agent_control_plane/plugin_registry.py +750 -0
  28. agent_control_plane/policy_engine.py +954 -0
  29. agent_control_plane/process_isolation.py +777 -0
  30. agent_control_plane/shadow_mode.py +310 -0
  31. agent_control_plane/signals.py +493 -0
  32. agent_control_plane/supervisor_agents.py +430 -0
  33. agent_control_plane/time_travel_debugger.py +557 -0
  34. agent_control_plane/tool_registry.py +452 -0
  35. agent_control_plane/vfs.py +697 -0
  36. agent_kernel/__init__.py +69 -0
  37. agent_kernel/analyzer.py +435 -0
  38. agent_kernel/auditor.py +36 -0
  39. agent_kernel/completeness_auditor.py +237 -0
  40. agent_kernel/detector.py +203 -0
  41. agent_kernel/kernel.py +744 -0
  42. agent_kernel/memory_manager.py +85 -0
  43. agent_kernel/models.py +374 -0
  44. agent_kernel/nudge_mechanism.py +263 -0
  45. agent_kernel/outcome_analyzer.py +338 -0
  46. agent_kernel/patcher.py +582 -0
  47. agent_kernel/semantic_analyzer.py +316 -0
  48. agent_kernel/semantic_purge.py +349 -0
  49. agent_kernel/simulator.py +449 -0
  50. agent_kernel/teacher.py +85 -0
  51. agent_kernel/triage.py +152 -0
  52. agent_os/__init__.py +409 -0
  53. agent_os/_adversarial_impl.py +200 -0
  54. agent_os/_circuit_breaker_impl.py +232 -0
  55. agent_os/_mcp_metrics.py +193 -0
  56. agent_os/adversarial.py +20 -0
  57. agent_os/agents_compat.py +490 -0
  58. agent_os/audit_logger.py +135 -0
  59. agent_os/base_agent.py +651 -0
  60. agent_os/circuit_breaker.py +34 -0
  61. agent_os/cli/__init__.py +659 -0
  62. agent_os/cli/cmd_audit.py +128 -0
  63. agent_os/cli/cmd_init.py +152 -0
  64. agent_os/cli/cmd_policy.py +41 -0
  65. agent_os/cli/cmd_policy_gen.py +180 -0
  66. agent_os/cli/cmd_validate.py +258 -0
  67. agent_os/cli/mcp_scan.py +265 -0
  68. agent_os/cli/output.py +192 -0
  69. agent_os/cli/policy_checker.py +330 -0
  70. agent_os/compat.py +74 -0
  71. agent_os/constraint_graph.py +234 -0
  72. agent_os/content_governance.py +140 -0
  73. agent_os/context_budget.py +305 -0
  74. agent_os/credential_redactor.py +224 -0
  75. agent_os/diff_policy.py +89 -0
  76. agent_os/egress_policy.py +159 -0
  77. agent_os/escalation.py +276 -0
  78. agent_os/event_bus.py +124 -0
  79. agent_os/exceptions.py +180 -0
  80. agent_os/execution_context_policy.py +141 -0
  81. agent_os/github_enterprise.py +96 -0
  82. agent_os/health.py +20 -0
  83. agent_os/integrations/__init__.py +279 -0
  84. agent_os/integrations/a2a_adapter.py +279 -0
  85. agent_os/integrations/agent_lightning/__init__.py +30 -0
  86. agent_os/integrations/anthropic_adapter.py +420 -0
  87. agent_os/integrations/autogen_adapter.py +620 -0
  88. agent_os/integrations/base.py +1137 -0
  89. agent_os/integrations/compat.py +229 -0
  90. agent_os/integrations/config.py +98 -0
  91. agent_os/integrations/conversation_guardian.py +957 -0
  92. agent_os/integrations/crewai_adapter.py +467 -0
  93. agent_os/integrations/drift_detector.py +425 -0
  94. agent_os/integrations/dry_run.py +124 -0
  95. agent_os/integrations/escalation.py +582 -0
  96. agent_os/integrations/gemini_adapter.py +364 -0
  97. agent_os/integrations/google_adk_adapter.py +633 -0
  98. agent_os/integrations/guardrails_adapter.py +394 -0
  99. agent_os/integrations/health.py +197 -0
  100. agent_os/integrations/langchain_adapter.py +654 -0
  101. agent_os/integrations/llamafirewall.py +343 -0
  102. agent_os/integrations/llamaindex_adapter.py +188 -0
  103. agent_os/integrations/logging.py +191 -0
  104. agent_os/integrations/maf_adapter.py +631 -0
  105. agent_os/integrations/mistral_adapter.py +365 -0
  106. agent_os/integrations/openai_adapter.py +816 -0
  107. agent_os/integrations/openai_agents_sdk.py +406 -0
  108. agent_os/integrations/policy_compose.py +171 -0
  109. agent_os/integrations/profiling.py +144 -0
  110. agent_os/integrations/pydantic_ai_adapter.py +420 -0
  111. agent_os/integrations/rate_limiter.py +130 -0
  112. agent_os/integrations/rbac.py +143 -0
  113. agent_os/integrations/registry.py +113 -0
  114. agent_os/integrations/scope_guard.py +303 -0
  115. agent_os/integrations/semantic_kernel_adapter.py +769 -0
  116. agent_os/integrations/smolagents_adapter.py +629 -0
  117. agent_os/integrations/templates.py +178 -0
  118. agent_os/integrations/token_budget.py +134 -0
  119. agent_os/integrations/tool_aliases.py +190 -0
  120. agent_os/integrations/webhooks.py +177 -0
  121. agent_os/lite.py +208 -0
  122. agent_os/mcp_gateway.py +385 -0
  123. agent_os/mcp_message_signer.py +273 -0
  124. agent_os/mcp_protocols.py +161 -0
  125. agent_os/mcp_response_scanner.py +232 -0
  126. agent_os/mcp_security.py +924 -0
  127. agent_os/mcp_session_auth.py +231 -0
  128. agent_os/mcp_sliding_rate_limiter.py +184 -0
  129. agent_os/memory_guard.py +409 -0
  130. agent_os/metrics.py +134 -0
  131. agent_os/mute.py +428 -0
  132. agent_os/mute_agent.py +209 -0
  133. agent_os/policies/__init__.py +77 -0
  134. agent_os/policies/async_evaluator.py +275 -0
  135. agent_os/policies/backends.py +670 -0
  136. agent_os/policies/bridge.py +169 -0
  137. agent_os/policies/budget.py +85 -0
  138. agent_os/policies/cli.py +294 -0
  139. agent_os/policies/conflict_resolution.py +270 -0
  140. agent_os/policies/data_classification.py +252 -0
  141. agent_os/policies/evaluator.py +239 -0
  142. agent_os/policies/policy_schema.json +228 -0
  143. agent_os/policies/rate_limiting.py +145 -0
  144. agent_os/policies/schema.py +115 -0
  145. agent_os/policies/shared.py +331 -0
  146. agent_os/prompt_injection.py +694 -0
  147. agent_os/providers.py +182 -0
  148. agent_os/py.typed +0 -0
  149. agent_os/retry.py +81 -0
  150. agent_os/reversibility.py +251 -0
  151. agent_os/sandbox.py +432 -0
  152. agent_os/sandbox_provider.py +140 -0
  153. agent_os/secure_codegen.py +525 -0
  154. agent_os/security_skills.py +538 -0
  155. agent_os/semantic_policy.py +422 -0
  156. agent_os/server/__init__.py +15 -0
  157. agent_os/server/__main__.py +25 -0
  158. agent_os/server/app.py +277 -0
  159. agent_os/server/models.py +104 -0
  160. agent_os/shift_left_metrics.py +130 -0
  161. agent_os/stateless.py +742 -0
  162. agent_os/supervisor.py +148 -0
  163. agent_os/task_outcome.py +148 -0
  164. agent_os/transparency.py +181 -0
  165. agent_os/trust_root.py +128 -0
  166. agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
  167. agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
  168. agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
  169. agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
  170. agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
  171. agent_os_observability/__init__.py +27 -0
  172. agent_os_observability/dashboards.py +898 -0
  173. agent_os_observability/metrics.py +398 -0
  174. agent_os_observability/server.py +223 -0
  175. agent_os_observability/tracer.py +232 -0
  176. agent_primitives/__init__.py +24 -0
  177. agent_primitives/failures.py +84 -0
  178. agent_primitives/py.typed +0 -0
  179. amb_core/__init__.py +177 -0
  180. amb_core/adapters/__init__.py +57 -0
  181. amb_core/adapters/aws_sqs_broker.py +376 -0
  182. amb_core/adapters/azure_servicebus_broker.py +340 -0
  183. amb_core/adapters/kafka_broker.py +260 -0
  184. amb_core/adapters/nats_broker.py +285 -0
  185. amb_core/adapters/rabbitmq_broker.py +235 -0
  186. amb_core/adapters/redis_broker.py +262 -0
  187. amb_core/broker.py +145 -0
  188. amb_core/bus.py +481 -0
  189. amb_core/cloudevents.py +509 -0
  190. amb_core/dlq.py +345 -0
  191. amb_core/hf_utils.py +536 -0
  192. amb_core/memory_broker.py +410 -0
  193. amb_core/models.py +141 -0
  194. amb_core/persistence.py +529 -0
  195. amb_core/schema.py +294 -0
  196. amb_core/tracing.py +358 -0
  197. atr/__init__.py +640 -0
  198. atr/access.py +348 -0
  199. atr/composition.py +645 -0
  200. atr/decorator.py +357 -0
  201. atr/executor.py +384 -0
  202. atr/health.py +557 -0
  203. atr/hf_utils.py +449 -0
  204. atr/injection.py +422 -0
  205. atr/metrics.py +440 -0
  206. atr/policies.py +403 -0
  207. atr/py.typed +2 -0
  208. atr/registry.py +452 -0
  209. atr/schema.py +480 -0
  210. atr/tools/safe/__init__.py +75 -0
  211. atr/tools/safe/calculator.py +467 -0
  212. atr/tools/safe/datetime_tool.py +443 -0
  213. atr/tools/safe/file_reader.py +402 -0
  214. atr/tools/safe/http_client.py +316 -0
  215. atr/tools/safe/json_parser.py +374 -0
  216. atr/tools/safe/text_tool.py +537 -0
  217. atr/tools/safe/toolkit.py +175 -0
  218. caas/__init__.py +162 -0
  219. caas/api/__init__.py +7 -0
  220. caas/api/server.py +1328 -0
  221. caas/caching.py +834 -0
  222. caas/cli.py +210 -0
  223. caas/conversation.py +223 -0
  224. caas/decay.py +72 -0
  225. caas/detection/__init__.py +9 -0
  226. caas/detection/detector.py +238 -0
  227. caas/enrichment.py +130 -0
  228. caas/gateway/__init__.py +27 -0
  229. caas/gateway/trust_gateway.py +474 -0
  230. caas/hf_utils.py +479 -0
  231. caas/ingestion/__init__.py +23 -0
  232. caas/ingestion/processors.py +253 -0
  233. caas/ingestion/structure_parser.py +188 -0
  234. caas/models.py +356 -0
  235. caas/pragmatic_truth.py +444 -0
  236. caas/routing/__init__.py +10 -0
  237. caas/routing/heuristic_router.py +58 -0
  238. caas/storage/__init__.py +9 -0
  239. caas/storage/store.py +389 -0
  240. caas/triad.py +213 -0
  241. caas/tuning/__init__.py +9 -0
  242. caas/tuning/tuner.py +329 -0
  243. caas/vfs/__init__.py +14 -0
  244. caas/vfs/filesystem.py +452 -0
  245. cmvk/__init__.py +218 -0
  246. cmvk/audit.py +402 -0
  247. cmvk/benchmarks.py +478 -0
  248. cmvk/constitutional.py +904 -0
  249. cmvk/hf_utils.py +301 -0
  250. cmvk/metrics.py +473 -0
  251. cmvk/profiles.py +300 -0
  252. cmvk/py.typed +0 -0
  253. cmvk/types.py +12 -0
  254. cmvk/verification.py +956 -0
  255. emk/__init__.py +89 -0
  256. emk/causal.py +352 -0
  257. emk/hf_utils.py +421 -0
  258. emk/indexer.py +83 -0
  259. emk/py.typed +0 -0
  260. emk/schema.py +204 -0
  261. emk/sleep_cycle.py +347 -0
  262. emk/store.py +281 -0
  263. iatp/__init__.py +166 -0
  264. iatp/attestation.py +461 -0
  265. iatp/cli.py +317 -0
  266. iatp/hf_utils.py +472 -0
  267. iatp/ipc_pipes.py +580 -0
  268. iatp/main.py +412 -0
  269. iatp/models/__init__.py +447 -0
  270. iatp/policy_engine.py +337 -0
  271. iatp/py.typed +2 -0
  272. iatp/recovery.py +321 -0
  273. iatp/security/__init__.py +270 -0
  274. iatp/sidecar/__init__.py +519 -0
  275. iatp/telemetry/__init__.py +164 -0
  276. iatp/tests/__init__.py +1 -0
  277. iatp/tests/test_attestation.py +370 -0
  278. iatp/tests/test_cli.py +131 -0
  279. iatp/tests/test_ed25519_attestation.py +211 -0
  280. iatp/tests/test_models.py +130 -0
  281. iatp/tests/test_policy_engine.py +347 -0
  282. iatp/tests/test_recovery.py +281 -0
  283. iatp/tests/test_security.py +222 -0
  284. iatp/tests/test_sidecar.py +167 -0
  285. iatp/tests/test_telemetry.py +175 -0
  286. mcp_kernel_server/__init__.py +28 -0
  287. mcp_kernel_server/cli.py +274 -0
  288. mcp_kernel_server/resources.py +217 -0
  289. mcp_kernel_server/server.py +564 -0
  290. mcp_kernel_server/tools.py +1174 -0
  291. mute_agent/__init__.py +68 -0
  292. mute_agent/core/__init__.py +1 -0
  293. mute_agent/core/execution_agent.py +166 -0
  294. mute_agent/core/handshake_protocol.py +201 -0
  295. mute_agent/core/reasoning_agent.py +238 -0
  296. mute_agent/knowledge_graph/__init__.py +1 -0
  297. mute_agent/knowledge_graph/graph_elements.py +65 -0
  298. mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
  299. mute_agent/knowledge_graph/subgraph.py +224 -0
  300. mute_agent/listener/__init__.py +43 -0
  301. mute_agent/listener/adapters/__init__.py +31 -0
  302. mute_agent/listener/adapters/base_adapter.py +189 -0
  303. mute_agent/listener/adapters/caas_adapter.py +344 -0
  304. mute_agent/listener/adapters/control_plane_adapter.py +436 -0
  305. mute_agent/listener/adapters/iatp_adapter.py +332 -0
  306. mute_agent/listener/adapters/scak_adapter.py +251 -0
  307. mute_agent/listener/listener.py +610 -0
  308. mute_agent/listener/state_observer.py +436 -0
  309. mute_agent/listener/threshold_config.py +313 -0
  310. mute_agent/super_system/__init__.py +1 -0
  311. mute_agent/super_system/router.py +204 -0
  312. mute_agent/visualization/__init__.py +10 -0
  313. mute_agent/visualization/graph_debugger.py +502 -0
  314. nexus/README.md +60 -0
  315. nexus/__init__.py +51 -0
  316. nexus/arbiter.py +359 -0
  317. nexus/client.py +466 -0
  318. nexus/dmz.py +444 -0
  319. nexus/escrow.py +430 -0
  320. nexus/exceptions.py +286 -0
  321. nexus/pyproject.toml +36 -0
  322. nexus/registry.py +393 -0
  323. nexus/reputation.py +425 -0
  324. nexus/schemas/__init__.py +51 -0
  325. nexus/schemas/compliance.py +276 -0
  326. nexus/schemas/escrow.py +251 -0
  327. nexus/schemas/manifest.py +225 -0
  328. nexus/schemas/receipt.py +208 -0
  329. nexus/tests/__init__.py +0 -0
  330. nexus/tests/conftest.py +146 -0
  331. nexus/tests/test_arbiter.py +192 -0
  332. nexus/tests/test_dmz.py +194 -0
  333. nexus/tests/test_escrow.py +276 -0
  334. nexus/tests/test_exceptions.py +225 -0
  335. nexus/tests/test_registry.py +232 -0
  336. nexus/tests/test_reputation.py +328 -0
  337. 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
+ }
@@ -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())