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,670 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ External policy backends for Agent-OS governance.
5
+
6
+ Provides a pluggable interface for evaluating policies written in
7
+ external policy languages (OPA/Rego, Cedar) alongside the native
8
+ YAML/JSON PolicyDocument engine.
9
+
10
+ Usage:
11
+ from agent_os.policies.backends import OPABackend, CedarBackend
12
+
13
+ evaluator = PolicyEvaluator()
14
+ evaluator.load_policies("policies/")
15
+
16
+ # Add OPA/Rego policies
17
+ evaluator.add_backend(OPABackend(rego_path="policies/agent.rego"))
18
+
19
+ # Add Cedar policies
20
+ evaluator.add_backend(CedarBackend(policy_path="policies/agent.cedar"))
21
+
22
+ # evaluate() checks YAML rules first, then external backends
23
+ decision = evaluator.evaluate(context)
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import json
29
+ import logging
30
+ import shutil
31
+ import subprocess
32
+ import tempfile
33
+ from dataclasses import dataclass
34
+ from datetime import datetime, timezone
35
+ from pathlib import Path
36
+ from typing import Any, Literal, Optional, Protocol, runtime_checkable
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+
41
+ # ── Protocol ──────────────────────────────────────────────────
42
+
43
+
44
+ @runtime_checkable
45
+ class ExternalPolicyBackend(Protocol):
46
+ """Interface for external policy evaluation backends.
47
+
48
+ Implementations translate between the toolkit's execution context
49
+ and an external policy language (OPA/Rego, Cedar, etc.), returning
50
+ a normalized decision.
51
+ """
52
+
53
+ @property
54
+ def name(self) -> str:
55
+ """Human-readable backend name (e.g., ``"opa"``, ``"cedar"``)."""
56
+ ...
57
+
58
+ def evaluate(self, context: dict[str, Any]) -> BackendDecision:
59
+ """Evaluate the external policy against the given context.
60
+
61
+ Args:
62
+ context: Execution context dict with fields like
63
+ ``tool_name``, ``agent_id``, ``token_count``, etc.
64
+
65
+ Returns:
66
+ A ``BackendDecision`` with the result.
67
+ """
68
+ ...
69
+
70
+
71
+ @dataclass
72
+ class BackendDecision:
73
+ """Normalized result from an external policy backend."""
74
+
75
+ allowed: bool
76
+ action: str = "allow"
77
+ reason: str = ""
78
+ backend: str = ""
79
+ raw_result: Any = None
80
+ evaluation_ms: float = 0.0
81
+ error: Optional[str] = None
82
+
83
+
84
+ # ── OPA/Rego Backend ─────────────────────────────────────────
85
+
86
+
87
+ class OPABackend:
88
+ """Evaluate OPA/Rego policies for Agent-OS.
89
+
90
+ Supports three modes:
91
+ 1. **Remote OPA server** — POST to ``http://host:8181/v1/data/...``
92
+ 2. **Local ``opa eval`` CLI** — subprocess call
93
+ 3. **Built-in fallback** — parses simple Rego patterns without external deps
94
+
95
+ Args:
96
+ mode: ``"remote"`` or ``"local"`` (default).
97
+ opa_url: Base URL for remote OPA server.
98
+ rego_path: Path to a ``.rego`` file.
99
+ rego_content: Inline Rego policy string.
100
+ package: Rego package name for query construction.
101
+ query: Explicit Rego query (overrides package-based construction).
102
+ timeout_seconds: Max evaluation time.
103
+
104
+ Example:
105
+ >>> backend = OPABackend(rego_content='''
106
+ ... package agentos
107
+ ... default allow = false
108
+ ... allow { input.tool_name != "file_delete" }
109
+ ... ''')
110
+ >>> decision = backend.evaluate({"tool_name": "file_read"})
111
+ >>> decision.allowed
112
+ True
113
+ """
114
+
115
+ def __init__(
116
+ self,
117
+ mode: Literal["remote", "local"] = "local",
118
+ opa_url: str = "http://localhost:8181",
119
+ rego_path: Optional[str] = None,
120
+ rego_content: Optional[str] = None,
121
+ package: str = "agentos",
122
+ query: Optional[str] = None,
123
+ timeout_seconds: float = 5.0,
124
+ ) -> None:
125
+ self._mode = mode
126
+ self._opa_url = opa_url.rstrip("/")
127
+ self._rego_path = rego_path
128
+ self._rego_content = rego_content
129
+ self._package = package
130
+ self._query = query or f"data.{package}.allow"
131
+ self._timeout = timeout_seconds
132
+ self._opa_available = shutil.which("opa") is not None
133
+
134
+ # Eagerly load rego content from file
135
+ if rego_path and not rego_content and Path(rego_path).exists():
136
+ self._rego_content = Path(rego_path).read_text()
137
+
138
+ @property
139
+ def name(self) -> str:
140
+ return "opa"
141
+
142
+ def evaluate(self, context: dict[str, Any]) -> BackendDecision:
143
+ start = datetime.now(timezone.utc)
144
+ try:
145
+ if self._mode == "remote":
146
+ result = self._evaluate_remote(context)
147
+ else:
148
+ result = self._evaluate_local(context)
149
+ result.evaluation_ms = (
150
+ datetime.now(timezone.utc) - start
151
+ ).total_seconds() * 1000
152
+ return result
153
+ except Exception as e:
154
+ elapsed = (datetime.now(timezone.utc) - start).total_seconds() * 1000
155
+ logger.error("OPA evaluation failed: %s", e)
156
+ return BackendDecision(
157
+ allowed=False,
158
+ action="deny",
159
+ reason=f"OPA evaluation error: {e}",
160
+ backend="opa",
161
+ evaluation_ms=elapsed,
162
+ error=str(e),
163
+ )
164
+
165
+ def _evaluate_remote(self, context: dict[str, Any]) -> BackendDecision:
166
+ import urllib.request
167
+
168
+ path_parts = (
169
+ self._query.replace("data.", "", 1).replace(".", "/")
170
+ if self._query.startswith("data.")
171
+ else self._query.replace(".", "/")
172
+ )
173
+ url = f"{self._opa_url}/v1/data/{path_parts}"
174
+ payload = json.dumps({"input": context}).encode()
175
+ req = urllib.request.Request(
176
+ url,
177
+ data=payload,
178
+ headers={"Content-Type": "application/json"},
179
+ method="POST",
180
+ )
181
+ try:
182
+ with urllib.request.urlopen(req, timeout=self._timeout) as resp:
183
+ body = json.loads(resp.read().decode())
184
+ result_value = body.get("result", False)
185
+ allowed = bool(result_value)
186
+ return BackendDecision(
187
+ allowed=allowed,
188
+ action="allow" if allowed else "deny",
189
+ reason=f"OPA remote ({self._package}): {'allowed' if allowed else 'denied'}",
190
+ backend="opa",
191
+ raw_result=body,
192
+ )
193
+ except Exception as e:
194
+ return BackendDecision(
195
+ allowed=False,
196
+ action="deny",
197
+ reason=f"OPA server error: {e}",
198
+ backend="opa",
199
+ error=str(e),
200
+ )
201
+
202
+ def _evaluate_local(self, context: dict[str, Any]) -> BackendDecision:
203
+ if self._opa_available and self._rego_content:
204
+ return self._evaluate_cli(context)
205
+ if self._rego_content:
206
+ logger.warning(
207
+ "OPA CLI not available — falling back to built-in regex evaluation. "
208
+ "Install OPA CLI for full policy evaluation: https://www.openpolicyagent.org/docs/latest/#running-opa"
209
+ )
210
+ return self._evaluate_builtin(context)
211
+ return BackendDecision(
212
+ allowed=False,
213
+ action="deny",
214
+ reason="No Rego content or OPA CLI available",
215
+ backend="opa",
216
+ error="No rego file or OPA CLI available",
217
+ )
218
+
219
+ def _evaluate_cli(self, context: dict[str, Any]) -> BackendDecision:
220
+ input_json = json.dumps(context)
221
+ with tempfile.NamedTemporaryFile(
222
+ mode="w", suffix=".rego", delete=False
223
+ ) as f:
224
+ f.write(self._rego_content)
225
+ rego_file = f.name
226
+
227
+ cmd = [
228
+ "opa", "eval", "--format", "json",
229
+ "--input", "/dev/stdin",
230
+ "--data", rego_file,
231
+ self._query,
232
+ ]
233
+ try:
234
+ proc = subprocess.run(
235
+ cmd,
236
+ input=input_json,
237
+ capture_output=True,
238
+ text=True,
239
+ timeout=self._timeout,
240
+ )
241
+ if proc.returncode != 0:
242
+ return BackendDecision(
243
+ allowed=False,
244
+ action="deny",
245
+ reason=f"opa eval failed: {proc.stderr.strip()}",
246
+ backend="opa",
247
+ error=proc.stderr.strip(),
248
+ )
249
+ result = json.loads(proc.stdout)
250
+ expressions = result.get("result", [{}])[0].get("expressions", [{}])
251
+ value = expressions[0].get("value", False) if expressions else False
252
+ allowed = bool(value)
253
+ return BackendDecision(
254
+ allowed=allowed,
255
+ action="allow" if allowed else "deny",
256
+ reason=f"OPA local ({self._package}): {'allowed' if allowed else 'denied'}",
257
+ backend="opa",
258
+ raw_result=result,
259
+ )
260
+ except subprocess.TimeoutExpired:
261
+ return BackendDecision(
262
+ allowed=False,
263
+ action="deny",
264
+ reason="OPA eval timed out",
265
+ backend="opa",
266
+ error="timeout",
267
+ )
268
+ finally:
269
+ Path(rego_file).unlink(missing_ok=True)
270
+
271
+ def _evaluate_builtin(self, context: dict[str, Any]) -> BackendDecision:
272
+ """Built-in simple Rego evaluator for common patterns."""
273
+ target_rule = self._query.split(".")[-1]
274
+
275
+ # Parse defaults
276
+ defaults: dict[str, bool] = {}
277
+ for line in self._rego_content.split("\n"):
278
+ stripped = line.strip()
279
+ if stripped.startswith("default "):
280
+ parts = stripped.replace("default ", "").split("=")
281
+ if len(parts) == 2:
282
+ key = parts[0].strip()
283
+ val = parts[1].strip().lower()
284
+ defaults[key] = val == "true"
285
+
286
+ result = defaults.get(target_rule, False)
287
+ in_rule = False
288
+ rule_conditions: list[str] = []
289
+
290
+ for line in self._rego_content.split("\n"):
291
+ stripped = line.strip()
292
+ if stripped.startswith(f"{target_rule} {{"):
293
+ if stripped.endswith("}"):
294
+ body = stripped[len(target_rule) + 2 : -1].strip()
295
+ if self._eval_condition(body, context):
296
+ result = True
297
+ else:
298
+ in_rule = True
299
+ rule_conditions = []
300
+ continue
301
+ if in_rule:
302
+ if stripped == "}":
303
+ if rule_conditions and all(
304
+ self._eval_condition(c, context) for c in rule_conditions
305
+ ):
306
+ result = True
307
+ in_rule = False
308
+ rule_conditions = []
309
+ elif stripped and not stripped.startswith("#"):
310
+ rule_conditions.append(stripped)
311
+
312
+ allowed = bool(result)
313
+ return BackendDecision(
314
+ allowed=allowed,
315
+ action="allow" if allowed else "deny",
316
+ reason=f"OPA builtin ({self._package}): {'allowed' if allowed else 'denied'}",
317
+ backend="opa",
318
+ raw_result={"parsed": True},
319
+ )
320
+
321
+ def _eval_condition(self, condition: str, ctx: dict[str, Any]) -> bool:
322
+ condition = condition.strip().rstrip(";")
323
+ if condition.startswith("not "):
324
+ return not self._eval_condition(condition[4:], ctx)
325
+ if "==" in condition:
326
+ left, right = [x.strip() for x in condition.split("==", 1)]
327
+ left_val = self._resolve_path(left, ctx)
328
+ right_val = right.strip('"').strip("'")
329
+ if right_val == "true":
330
+ return left_val is True
331
+ if right_val == "false":
332
+ return left_val is False
333
+ return str(left_val) == right_val
334
+ if "!=" in condition:
335
+ left, right = [x.strip() for x in condition.split("!=", 1)]
336
+ left_val = self._resolve_path(left, ctx)
337
+ right_val = right.strip('"').strip("'")
338
+ return str(left_val) != right_val
339
+ val = self._resolve_path(condition, ctx)
340
+ return bool(val)
341
+
342
+ @staticmethod
343
+ def _resolve_path(path: str, data: dict[str, Any]) -> Any:
344
+ parts = path.split(".")
345
+ current: Any = data
346
+ for part in parts:
347
+ if part == "input":
348
+ continue
349
+ if isinstance(current, dict):
350
+ current = current.get(part)
351
+ else:
352
+ return None
353
+ return current
354
+
355
+
356
+ # ── Cedar Backend ─────────────────────────────────────────────
357
+
358
+
359
+ class CedarBackend:
360
+ """Evaluate Cedar policies for Agent-OS.
361
+
362
+ Cedar is AWS's authorization policy language. This backend lets
363
+ enterprises that standardize on Cedar reuse their existing policies
364
+ for agent governance.
365
+
366
+ Supports three modes:
367
+ 1. **cedarpy** — Python bindings to the Rust Cedar engine (fastest)
368
+ 2. **CLI** — ``cedar`` CLI subprocess
369
+ 3. **Built-in** — simple pattern matcher for common Cedar patterns
370
+
371
+ Args:
372
+ policy_path: Path to a ``.cedar`` policy file.
373
+ policy_content: Inline Cedar policy string.
374
+ entities_path: Path to Cedar entities JSON file.
375
+ entities: Entities list for authorization context.
376
+ schema_path: Path to Cedar schema file.
377
+ mode: ``"auto"`` tries cedarpy → CLI → builtin.
378
+ timeout_seconds: Max evaluation time.
379
+
380
+ Example:
381
+ >>> backend = CedarBackend(policy_content='''
382
+ ... permit(
383
+ ... principal,
384
+ ... action == Action::"ReadData",
385
+ ... resource
386
+ ... );
387
+ ... forbid(
388
+ ... principal,
389
+ ... action == Action::"DeleteFile",
390
+ ... resource
391
+ ... );
392
+ ... ''')
393
+ >>> decision = backend.evaluate({
394
+ ... "tool_name": "read_data",
395
+ ... "agent_id": "agent-1",
396
+ ... })
397
+ >>> decision.allowed
398
+ True
399
+ """
400
+
401
+ def __init__(
402
+ self,
403
+ policy_path: Optional[str] = None,
404
+ policy_content: Optional[str] = None,
405
+ entities_path: Optional[str] = None,
406
+ entities: Optional[list[dict[str, Any]]] = None,
407
+ schema_path: Optional[str] = None,
408
+ mode: Literal["auto", "cedarpy", "cli", "builtin"] = "auto",
409
+ timeout_seconds: float = 5.0,
410
+ ) -> None:
411
+ self._policy_path = policy_path
412
+ self._policy_content = policy_content
413
+ self._entities_path = entities_path
414
+ self._entities = entities or []
415
+ self._schema_path = schema_path
416
+ self._mode = mode
417
+ self._timeout = timeout_seconds
418
+
419
+ # Eagerly load policy content from file
420
+ if policy_path and not policy_content and Path(policy_path).exists():
421
+ self._policy_content = Path(policy_path).read_text()
422
+
423
+ # Eagerly load entities from file
424
+ if entities_path and not entities and Path(entities_path).exists():
425
+ self._entities = json.loads(Path(entities_path).read_text())
426
+
427
+ # Detect available engines
428
+ self._cedarpy_available = self._check_cedarpy()
429
+ self._cli_available = shutil.which("cedar") is not None
430
+
431
+ @staticmethod
432
+ def _check_cedarpy() -> bool:
433
+ try:
434
+ import cedarpy # noqa: F401
435
+ return True
436
+ except ImportError:
437
+ return False
438
+
439
+ @property
440
+ def name(self) -> str:
441
+ return "cedar"
442
+
443
+ def evaluate(self, context: dict[str, Any]) -> BackendDecision:
444
+ start = datetime.now(timezone.utc)
445
+ try:
446
+ if self._mode == "cedarpy" or (
447
+ self._mode == "auto" and self._cedarpy_available
448
+ ):
449
+ result = self._evaluate_cedarpy(context)
450
+ elif self._mode == "cli" or (
451
+ self._mode == "auto" and self._cli_available
452
+ ):
453
+ result = self._evaluate_cli(context)
454
+ else:
455
+ logger.warning(
456
+ "Neither cedarpy nor Cedar CLI available — falling back to built-in "
457
+ "pattern evaluation. Install cedar-py or the Cedar CLI for full evaluation."
458
+ )
459
+ result = self._evaluate_builtin(context)
460
+ result.evaluation_ms = (
461
+ datetime.now(timezone.utc) - start
462
+ ).total_seconds() * 1000
463
+ return result
464
+ except Exception as e:
465
+ elapsed = (datetime.now(timezone.utc) - start).total_seconds() * 1000
466
+ logger.error("Cedar evaluation failed: %s", e)
467
+ return BackendDecision(
468
+ allowed=False,
469
+ action="deny",
470
+ reason=f"Cedar evaluation error: {e}",
471
+ backend="cedar",
472
+ evaluation_ms=elapsed,
473
+ error=str(e),
474
+ )
475
+
476
+ def _build_cedar_request(self, context: dict[str, Any]) -> dict[str, Any]:
477
+ """Build a Cedar authorization request from execution context."""
478
+ agent_id = context.get("agent_id", "Agent::\"anonymous\"")
479
+ tool_name = context.get("tool_name", "unknown")
480
+ resource = context.get("resource", "Resource::\"default\"")
481
+
482
+ # Normalize to Cedar entity format
483
+ if "::" not in str(agent_id):
484
+ agent_id = f'Agent::"{agent_id}"'
485
+ if "::" not in str(resource):
486
+ resource = f'Resource::"{resource}"'
487
+
488
+ # Map tool_name to Cedar action
489
+ action_name = _tool_to_cedar_action(tool_name)
490
+
491
+ return {
492
+ "principal": agent_id,
493
+ "action": f'Action::"{action_name}"',
494
+ "resource": resource,
495
+ "context": {k: v for k, v in context.items()
496
+ if k not in ("agent_id", "tool_name", "resource")},
497
+ }
498
+
499
+ def _evaluate_cedarpy(self, context: dict[str, Any]) -> BackendDecision:
500
+ """Evaluate via cedarpy Python bindings."""
501
+ import cedarpy
502
+
503
+ request = self._build_cedar_request(context)
504
+ response = cedarpy.is_authorized(
505
+ request=cedarpy.AuthorizationRequest(
506
+ principal=request["principal"],
507
+ action=request["action"],
508
+ resource=request["resource"],
509
+ context=request["context"],
510
+ ),
511
+ policies=self._policy_content or "",
512
+ entities=self._entities,
513
+ )
514
+ allowed = response.decision == cedarpy.Decision.ALLOW
515
+ return BackendDecision(
516
+ allowed=allowed,
517
+ action="allow" if allowed else "deny",
518
+ reason=f"Cedar (cedarpy): {'allowed' if allowed else 'denied'}",
519
+ backend="cedar",
520
+ raw_result={
521
+ "decision": str(response.decision),
522
+ "diagnostics": str(response.diagnostics) if hasattr(response, "diagnostics") else None,
523
+ },
524
+ )
525
+
526
+ def _evaluate_cli(self, context: dict[str, Any]) -> BackendDecision:
527
+ """Evaluate via cedar CLI subprocess."""
528
+ request = self._build_cedar_request(context)
529
+
530
+ with tempfile.TemporaryDirectory() as tmpdir:
531
+ policy_file = Path(tmpdir) / "policy.cedar"
532
+ policy_file.write_text(self._policy_content or "")
533
+
534
+ entities_file = Path(tmpdir) / "entities.json"
535
+ entities_file.write_text(json.dumps(self._entities))
536
+
537
+ request_file = Path(tmpdir) / "request.json"
538
+ request_file.write_text(json.dumps(request))
539
+
540
+ cmd = [
541
+ "cedar", "authorize",
542
+ "--policies", str(policy_file),
543
+ "--entities", str(entities_file),
544
+ "--request-json", str(request_file),
545
+ ]
546
+ if self._schema_path:
547
+ cmd.extend(["--schema", self._schema_path])
548
+
549
+ try:
550
+ proc = subprocess.run(
551
+ cmd,
552
+ capture_output=True,
553
+ text=True,
554
+ timeout=self._timeout,
555
+ )
556
+ output = proc.stdout.strip().lower()
557
+ allowed = "allow" in output and "deny" not in output
558
+ return BackendDecision(
559
+ allowed=allowed,
560
+ action="allow" if allowed else "deny",
561
+ reason=f"Cedar CLI: {proc.stdout.strip()}",
562
+ backend="cedar",
563
+ raw_result={"stdout": proc.stdout, "stderr": proc.stderr},
564
+ )
565
+ except subprocess.TimeoutExpired:
566
+ return BackendDecision(
567
+ allowed=False,
568
+ action="deny",
569
+ reason="Cedar CLI timed out",
570
+ backend="cedar",
571
+ error="timeout",
572
+ )
573
+
574
+ def _evaluate_builtin(self, context: dict[str, Any]) -> BackendDecision:
575
+ """Built-in Cedar pattern evaluator for common permit/forbid rules.
576
+
577
+ Parses simple Cedar policy patterns:
578
+ - permit(principal, action == Action::"X", resource);
579
+ - forbid(principal, action == Action::"X", resource);
580
+ - permit(principal, action, resource); // catch-all allow
581
+ """
582
+ if not self._policy_content:
583
+ return BackendDecision(
584
+ allowed=False,
585
+ action="deny",
586
+ reason="No Cedar policy content",
587
+ backend="cedar",
588
+ error="No policy content",
589
+ )
590
+
591
+ request = self._build_cedar_request(context)
592
+ action_str = request["action"]
593
+
594
+ # Parse all permit/forbid statements
595
+ statements = _parse_cedar_statements(self._policy_content)
596
+
597
+ # Cedar semantics: default deny, any forbid overrides permit
598
+ has_permit = False
599
+
600
+ for stmt in statements:
601
+ if stmt["action_constraint"] and stmt["action_constraint"] != action_str:
602
+ continue # Action doesn't match this statement
603
+
604
+ # Statement applies to this action
605
+ if stmt["effect"] == "forbid":
606
+ return BackendDecision(
607
+ allowed=False,
608
+ action="deny",
609
+ reason=f"Cedar builtin: forbid matched for {action_str}",
610
+ backend="cedar",
611
+ raw_result={"matched_statement": stmt},
612
+ )
613
+ elif stmt["effect"] == "permit":
614
+ has_permit = True
615
+
616
+ allowed = has_permit
617
+ return BackendDecision(
618
+ allowed=allowed,
619
+ action="allow" if allowed else "deny",
620
+ reason=f"Cedar builtin: {'permit matched' if allowed else 'no permit matched (default deny)'}",
621
+ backend="cedar",
622
+ raw_result={"statements_checked": len(statements)},
623
+ )
624
+
625
+
626
+ # ── Cedar helpers ─────────────────────────────────────────────
627
+
628
+
629
+ def _tool_to_cedar_action(tool_name: str) -> str:
630
+ """Map a toolkit tool_name to a Cedar action identifier.
631
+
632
+ Converts snake_case tool names to PascalCase Cedar actions:
633
+ ``file_read`` → ``FileRead``, ``execute_code`` → ``ExecuteCode``.
634
+ """
635
+ return "".join(part.capitalize() for part in tool_name.split("_"))
636
+
637
+
638
+ def _parse_cedar_statements(content: str) -> list[dict[str, Any]]:
639
+ """Parse Cedar permit/forbid statements from policy content.
640
+
641
+ Returns a list of dicts with keys: effect, action_constraint.
642
+ """
643
+ import re
644
+
645
+ statements: list[dict[str, Any]] = []
646
+ # Match permit(...) or forbid(...) blocks including multiline
647
+ pattern = re.compile(
648
+ r'(permit|forbid)\s*\((.*?)\)\s*;',
649
+ re.DOTALL,
650
+ )
651
+
652
+ for match in pattern.finditer(content):
653
+ effect = match.group(1)
654
+ body = match.group(2)
655
+
656
+ # Extract action constraint: action == Action::"SomeThing"
657
+ action_match = re.search(
658
+ r'action\s*==\s*Action::"([^"]+)"', body
659
+ )
660
+ action_constraint = (
661
+ f'Action::"{action_match.group(1)}"' if action_match else None
662
+ )
663
+
664
+ statements.append({
665
+ "effect": effect,
666
+ "action_constraint": action_constraint,
667
+ "raw": match.group(0),
668
+ })
669
+
670
+ return statements