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
agent_os/cli/output.py ADDED
@@ -0,0 +1,192 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Terminal output helpers for the Agent OS CLI.
4
+
5
+ Provides ANSI colour formatting, error/warning formatters, and the
6
+ module-level ``Colors`` singleton used across all CLI sub-commands.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import logging
14
+ import os
15
+ import pathlib
16
+ import sys
17
+ import traceback
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # ============================================================================
22
+ # Terminal Colour Support
23
+ # ============================================================================
24
+
25
+
26
+ def supports_color() -> bool:
27
+ """Check if terminal supports colors."""
28
+ if os.environ.get('NO_COLOR') or os.environ.get('CI'):
29
+ return False
30
+ return sys.stdout.isatty()
31
+
32
+
33
+ class Colors:
34
+ """ANSI color codes for terminal output.
35
+
36
+ Uses instance attributes so that ``disable()`` does not mutate shared
37
+ class state. A module-level singleton is created below; import and use
38
+ that instead of the class directly.
39
+ """
40
+
41
+ _DEFAULTS: dict[str, str] = {
42
+ 'RED': '\033[91m',
43
+ 'GREEN': '\033[92m',
44
+ 'YELLOW': '\033[93m',
45
+ 'BLUE': '\033[94m',
46
+ 'MAGENTA': '\033[95m',
47
+ 'CYAN': '\033[96m',
48
+ 'WHITE': '\033[97m',
49
+ 'BOLD': '\033[1m',
50
+ 'DIM': '\033[2m',
51
+ 'RESET': '\033[0m',
52
+ }
53
+
54
+ def __init__(self, enabled: bool | None = None) -> None:
55
+ if enabled is None:
56
+ enabled = supports_color()
57
+ self._enabled = enabled
58
+ self._apply(enabled)
59
+
60
+ def _apply(self, enabled: bool) -> None:
61
+ for name, code in self._DEFAULTS.items():
62
+ setattr(self, name, code if enabled else '')
63
+
64
+ def disable(self) -> None:
65
+ """Disable colors on *this* instance."""
66
+ self._enabled = False
67
+ self._apply(False)
68
+
69
+ def enable(self) -> None:
70
+ """Enable colors on *this* instance."""
71
+ self._enabled = True
72
+ self._apply(True)
73
+
74
+ @property
75
+ def enabled(self) -> bool:
76
+ return self._enabled
77
+
78
+
79
+ # Module-level singleton – every import shares this instance.
80
+ Colors = Colors() # type: ignore[misc]
81
+
82
+
83
+ # ============================================================================
84
+ # Output Format
85
+ # ============================================================================
86
+
87
+
88
+ def get_output_format(args: argparse.Namespace) -> str:
89
+ """Determine the output format from CLI arguments."""
90
+ if getattr(args, "json", False):
91
+ return "json"
92
+ return getattr(args, "format", "text")
93
+
94
+
95
+ def get_config_path(args_path: str | None = None) -> "pathlib.Path":
96
+ """Resolve the config path from args or AGENTOS_CONFIG env var."""
97
+ from pathlib import Path as _Path
98
+
99
+ if args_path:
100
+ return _Path(args_path)
101
+ env_config = os.environ.get("AGENTOS_CONFIG")
102
+ if env_config:
103
+ return _Path(env_config)
104
+ return _Path(".")
105
+
106
+
107
+ # ============================================================================
108
+ # CLI Error Formatting
109
+ # ============================================================================
110
+
111
+ DOCS_URL = "https://github.com/microsoft/agent-governance-toolkit/blob/main/docs"
112
+
113
+ AVAILABLE_POLICIES = ("strict", "permissive", "audit")
114
+
115
+
116
+ def _difflib_best_match(word: str, candidates: list[str]) -> str | None:
117
+ """Return the closest match from *candidates*, or ``None``."""
118
+ import difflib
119
+
120
+ matches = difflib.get_close_matches(word, candidates, n=1, cutoff=0.5)
121
+ return matches[0] if matches else None
122
+
123
+
124
+ def format_error(message: str, suggestion: str | None = None,
125
+ docs_path: str | None = None) -> str:
126
+ """Return a colorized error string with an optional suggestion and docs link."""
127
+ parts = [f"{Colors.RED}{Colors.BOLD}Error:{Colors.RESET} {message}"]
128
+ if suggestion:
129
+ parts.append(f" {Colors.GREEN}💡 Suggestion:{Colors.RESET} {suggestion}")
130
+ if docs_path:
131
+ parts.append(f" {Colors.DIM}📖 Docs: {DOCS_URL}/{docs_path}{Colors.RESET}")
132
+ return "\n".join(parts)
133
+
134
+
135
+ def handle_cli_error(e: Exception, args: argparse.Namespace) -> int:
136
+ """Centralized error handler for Agent OS CLI."""
137
+ # Sanitize exception message to avoid leaking internal details
138
+ is_known_error = isinstance(e, (FileNotFoundError, ValueError, PermissionError))
139
+ error_msg = "A file, value, or permission error occurred." if is_known_error else "An internal error occurred."
140
+
141
+ if getattr(args, "json", False) or (hasattr(args, "format") and args.format == "json"):
142
+ print(json.dumps({
143
+ "status": "error",
144
+ "message": error_msg,
145
+ "error_type": "ValidationError" if is_known_error else "InternalError"
146
+ }, indent=2))
147
+ else:
148
+ print(format_error(error_msg))
149
+ if os.environ.get("AGENTOS_DEBUG"):
150
+ traceback.print_exc()
151
+ return 1
152
+
153
+
154
+ def handle_missing_config(path: str = ".") -> str:
155
+ """Error message for a missing ``.agents/`` config directory."""
156
+ return format_error(
157
+ f"Config directory not found: {path}/.agents/",
158
+ suggestion="Did you mean to create one? Run: agentos init",
159
+ docs_path="getting-started.md",
160
+ )
161
+
162
+
163
+ def handle_invalid_policy(name: str) -> str:
164
+ """Error message for an unrecognised policy template name."""
165
+ available = ", ".join(AVAILABLE_POLICIES)
166
+ suggestion = f"Available policies: {available}"
167
+ match = _difflib_best_match(name, list(AVAILABLE_POLICIES))
168
+ if match:
169
+ suggestion += f". Did you mean '{match}'?"
170
+ return format_error(
171
+ f"Unknown policy template: '{name}'",
172
+ suggestion=suggestion,
173
+ docs_path="security-spec.md",
174
+ )
175
+
176
+
177
+ def handle_missing_dependency(package: str, extra: str = "") -> str:
178
+ """Error message when an optional dependency is missing."""
179
+ install_cmd = f"pip install agent-os-kernel[{extra}]" if extra else f"pip install {package}"
180
+ return format_error(
181
+ f"Required package not installed: {package}",
182
+ suggestion=f"Install with: {install_cmd}",
183
+ docs_path="installation.md",
184
+ )
185
+
186
+
187
+ def handle_connection_error(host: str, port: int) -> str:
188
+ """Error message for a connection failure."""
189
+ return format_error(
190
+ f"Could not connect to {host}:{port}",
191
+ suggestion=f"Check that the service is running on {host}:{port}",
192
+ )
@@ -0,0 +1,330 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Local-first code policy checker for the Agent OS CLI.
4
+
5
+ Defines ``PolicyViolation``, ``PolicyChecker``, and the helper
6
+ ``load_cli_policy_rules()`` used by the ``agentos check`` and
7
+ ``agentos review`` commands.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import re
14
+ import subprocess
15
+ import warnings
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+
20
+ # ============================================================================
21
+ # PolicyViolation
22
+ # ============================================================================
23
+
24
+
25
+ class PolicyViolation:
26
+ """Represents a policy violation found in code."""
27
+
28
+ def __init__(self, line: int, code: str, violation: str, policy: str,
29
+ severity: str = 'high', suggestion: str | None = None) -> None:
30
+ self.line = line
31
+ self.code = code
32
+ self.violation = violation
33
+ self.policy = policy
34
+ self.severity = severity
35
+ self.suggestion = suggestion
36
+
37
+ def to_dict(self) -> dict[str, Any]:
38
+ """Convert violation to dictionary for JSON output."""
39
+ return {
40
+ "line": self.line,
41
+ "code": self.code,
42
+ "violation": self.violation,
43
+ "policy": self.policy,
44
+ "severity": self.severity,
45
+ "suggestion": self.suggestion
46
+ }
47
+
48
+
49
+ # ============================================================================
50
+ # Rule Loading
51
+ # ============================================================================
52
+
53
+
54
+ def load_cli_policy_rules(path: str) -> list[dict[str, Any]]:
55
+ """Load CLI policy checker rules from a YAML file.
56
+
57
+ Args:
58
+ path: Path to a YAML file with a ``rules`` section.
59
+
60
+ Returns:
61
+ List of rule dicts suitable for ``PolicyChecker``.
62
+
63
+ Raises:
64
+ FileNotFoundError: If the config file does not exist.
65
+ ValueError: If the YAML is missing the ``rules`` section.
66
+ """
67
+ import yaml
68
+
69
+ if not os.path.exists(path):
70
+ raise FileNotFoundError(f"CLI policy rules config not found: {path}")
71
+
72
+ with open(path, "r", encoding="utf-8") as fh:
73
+ data = yaml.safe_load(fh.read())
74
+
75
+ if not isinstance(data, dict) or "rules" not in data:
76
+ raise ValueError(f"YAML file must contain a 'rules' section: {path}")
77
+
78
+ return data["rules"]
79
+
80
+
81
+ # ============================================================================
82
+ # PolicyChecker
83
+ # ============================================================================
84
+
85
+
86
+ class PolicyChecker:
87
+ """Local-first code policy checker."""
88
+
89
+ def __init__(self, rules: list[dict[str, Any]] | None = None) -> None:
90
+ if rules is not None:
91
+ self.rules = rules
92
+ else:
93
+ self.rules = self._load_default_rules()
94
+
95
+ def _load_default_rules(self) -> list[dict[str, Any]]:
96
+ """Load default safety rules.
97
+
98
+ .. deprecated::
99
+ Uses built-in sample rules. For production use, load an explicit
100
+ config with ``load_cli_policy_rules()``.
101
+ """
102
+ warnings.warn(
103
+ "PolicyChecker._load_default_rules() uses built-in sample rules that may not "
104
+ "cover all security violations. For production use, load an "
105
+ "explicit config with load_cli_policy_rules(). "
106
+ "See examples/policies/cli-security-rules.yaml for a sample configuration.",
107
+ stacklevel=2,
108
+ )
109
+ return [
110
+ # Destructive SQL
111
+ {
112
+ 'name': 'block-destructive-sql',
113
+ 'pattern': r'\bDROP\s+(TABLE|DATABASE|SCHEMA|INDEX)\s+',
114
+ 'message': 'Destructive SQL: DROP operation detected',
115
+ 'severity': 'critical',
116
+ 'suggestion': '-- Consider using soft delete or archiving instead',
117
+ 'languages': ['sql', 'python', 'javascript', 'typescript', 'php', 'ruby', 'java']
118
+ },
119
+ {
120
+ 'name': 'block-destructive-sql',
121
+ 'pattern': r'\bDELETE\s+FROM\s+\w+\s*(;|$|WHERE\s+1\s*=\s*1)',
122
+ 'message': 'Destructive SQL: DELETE without proper WHERE clause',
123
+ 'severity': 'critical',
124
+ 'suggestion': '-- Add a specific WHERE clause to limit deletion',
125
+ 'languages': ['sql', 'python', 'javascript', 'typescript', 'php', 'ruby', 'java']
126
+ },
127
+ {
128
+ 'name': 'block-destructive-sql',
129
+ 'pattern': r'\bTRUNCATE\s+TABLE\s+',
130
+ 'message': 'Destructive SQL: TRUNCATE operation detected',
131
+ 'severity': 'critical',
132
+ 'suggestion': '-- Consider archiving data before truncating',
133
+ 'languages': ['sql', 'python', 'javascript', 'typescript', 'php', 'ruby', 'java']
134
+ },
135
+ # File deletion
136
+ {
137
+ 'name': 'block-file-deletes',
138
+ 'pattern': r'\brm\s+(-rf|-fr|--recursive\s+--force)\s+',
139
+ 'message': 'Destructive operation: Recursive force delete (rm -rf)',
140
+ 'severity': 'critical',
141
+ 'suggestion': '# Use safer alternatives like trash-cli or move to backup',
142
+ 'languages': ['bash', 'shell', 'sh', 'zsh']
143
+ },
144
+ {
145
+ 'name': 'block-file-deletes',
146
+ 'pattern': r'\bshutil\s*\.\s*rmtree\s*\(',
147
+ 'message': 'Recursive directory deletion (shutil.rmtree)',
148
+ 'severity': 'high',
149
+ 'suggestion': '# Consider using send2trash for safer deletion',
150
+ 'languages': ['python']
151
+ },
152
+ {
153
+ 'name': 'block-file-deletes',
154
+ 'pattern': r'\bos\s*\.\s*(remove|unlink|rmdir)\s*\(',
155
+ 'message': 'File/directory deletion operation detected',
156
+ 'severity': 'medium',
157
+ 'languages': ['python']
158
+ },
159
+ # Secret exposure
160
+ {
161
+ 'name': 'block-secret-exposure',
162
+ 'pattern': r'(api[_-]?key|apikey|api[_-]?secret)\s*[=:]\s*["\'][a-zA-Z0-9_-]{20,}["\']',
163
+ 'message': 'Hardcoded API key detected',
164
+ 'severity': 'critical',
165
+ 'suggestion': '# Use environment variables: os.environ["API_KEY"]',
166
+ 'languages': None # All languages
167
+ },
168
+ {
169
+ 'name': 'block-secret-exposure',
170
+ 'pattern': r'(password|passwd|pwd)\s*[=:]\s*["\'][^"\']+["\']',
171
+ 'message': 'Hardcoded password detected',
172
+ 'severity': 'critical',
173
+ 'suggestion': '# Use environment variables or a secrets manager',
174
+ 'languages': None
175
+ },
176
+ {
177
+ 'name': 'block-secret-exposure',
178
+ 'pattern': r'AKIA[0-9A-Z]{16}',
179
+ 'message': 'AWS Access Key ID detected in code',
180
+ 'severity': 'critical',
181
+ 'languages': None
182
+ },
183
+ {
184
+ 'name': 'block-secret-exposure',
185
+ 'pattern': r'-----BEGIN\s+(RSA|DSA|EC|OPENSSH)\s+PRIVATE\s+KEY-----',
186
+ 'message': 'Private key detected in code',
187
+ 'severity': 'critical',
188
+ 'languages': None
189
+ },
190
+ {
191
+ 'name': 'block-secret-exposure',
192
+ 'pattern': r'gh[pousr]_[A-Za-z0-9_]{36,}',
193
+ 'message': 'GitHub token detected in code',
194
+ 'severity': 'critical',
195
+ 'languages': None
196
+ },
197
+ # Privilege escalation
198
+ {
199
+ 'name': 'block-privilege-escalation',
200
+ 'pattern': r'\bsudo\s+',
201
+ 'message': 'Privilege escalation: sudo command detected',
202
+ 'severity': 'high',
203
+ 'suggestion': '# Avoid sudo in scripts - run with appropriate permissions',
204
+ 'languages': ['bash', 'shell', 'sh', 'zsh']
205
+ },
206
+ {
207
+ 'name': 'block-privilege-escalation',
208
+ 'pattern': r'\bchmod\s+777\s+',
209
+ 'message': 'Insecure permissions: chmod 777 detected',
210
+ 'severity': 'high',
211
+ 'suggestion': '# Use more restrictive permissions: chmod 755 or chmod 644',
212
+ 'languages': ['bash', 'shell', 'sh', 'zsh']
213
+ },
214
+ # Code injection
215
+ {
216
+ 'name': 'block-arbitrary-exec',
217
+ 'pattern': r'\beval\s*\(',
218
+ 'message': 'Code injection risk: eval() usage detected',
219
+ 'severity': 'high',
220
+ 'suggestion': '# Remove eval() and use safer alternatives',
221
+ 'languages': ['python', 'javascript', 'typescript', 'php', 'ruby']
222
+ },
223
+ {
224
+ 'name': 'block-arbitrary-exec',
225
+ 'pattern': r'\bos\s*\.\s*system\s*\([^)]*(\+|%|\.format|f["\'])',
226
+ 'message': 'Command injection risk: os.system with dynamic input',
227
+ 'severity': 'critical',
228
+ 'suggestion': '# Use subprocess with shell=False and proper argument handling',
229
+ 'languages': ['python']
230
+ },
231
+ {
232
+ 'name': 'block-arbitrary-exec',
233
+ 'pattern': r'\bexec\s*\(',
234
+ 'message': 'Code injection risk: exec() usage detected',
235
+ 'severity': 'high',
236
+ 'suggestion': '# Remove exec() and use safer alternatives',
237
+ 'languages': ['python']
238
+ },
239
+ # SQL injection
240
+ {
241
+ 'name': 'block-sql-injection',
242
+ 'pattern': r'["\']\s*\+\s*[^"\']+\s*\+\s*["\'].*(?:SELECT|INSERT|UPDATE|DELETE)',
243
+ 'message': 'SQL injection risk: String concatenation in SQL query',
244
+ 'severity': 'high',
245
+ 'suggestion': '# Use parameterized queries instead',
246
+ 'languages': ['python', 'javascript', 'typescript', 'php', 'ruby', 'java']
247
+ },
248
+ # XSS
249
+ {
250
+ 'name': 'block-xss',
251
+ 'pattern': r'\.innerHTML\s*=',
252
+ 'message': 'XSS risk: innerHTML assignment detected',
253
+ 'severity': 'medium',
254
+ 'suggestion': '// Use textContent or a sanitization library',
255
+ 'languages': ['javascript', 'typescript']
256
+ },
257
+ ]
258
+
259
+ def _get_language(self, filepath: str) -> str:
260
+ """Detect language from file extension."""
261
+ ext_map = {
262
+ '.py': 'python',
263
+ '.js': 'javascript',
264
+ '.ts': 'typescript',
265
+ '.jsx': 'javascript',
266
+ '.tsx': 'typescript',
267
+ '.sql': 'sql',
268
+ '.sh': 'shell',
269
+ '.bash': 'bash',
270
+ '.zsh': 'zsh',
271
+ '.php': 'php',
272
+ '.rb': 'ruby',
273
+ '.java': 'java',
274
+ '.cs': 'csharp',
275
+ '.go': 'go',
276
+ }
277
+ ext = Path(filepath).suffix.lower()
278
+ return ext_map.get(ext, 'unknown')
279
+
280
+ def check_file(self, filepath: str) -> list[PolicyViolation]:
281
+ """Check a file for policy violations."""
282
+ path = Path(filepath)
283
+ if not path.exists():
284
+ raise FileNotFoundError(f"File not found: {filepath}")
285
+
286
+ language = self._get_language(filepath)
287
+ content = path.read_text(encoding='utf-8', errors='ignore')
288
+ lines = content.split('\n')
289
+
290
+ violations = []
291
+
292
+ for rule in self.rules:
293
+ # Check language filter
294
+ if rule['languages'] and language not in rule['languages']:
295
+ continue
296
+
297
+ pattern = re.compile(rule['pattern'], re.IGNORECASE)
298
+
299
+ for i, line in enumerate(lines, 1):
300
+ if pattern.search(line):
301
+ violations.append(PolicyViolation(
302
+ line=i,
303
+ code=line.strip(),
304
+ violation=rule['message'],
305
+ policy=rule['name'],
306
+ severity=rule['severity'],
307
+ suggestion=rule.get('suggestion')
308
+ ))
309
+
310
+ return violations
311
+
312
+ def check_staged_files(self) -> dict[str, list[PolicyViolation]]:
313
+ """Check all staged git files for violations."""
314
+ try:
315
+ result = subprocess.run(
316
+ ['git', 'diff', '--cached', '--name-only'],
317
+ capture_output=True, text=True, check=True
318
+ )
319
+ files = [f for f in result.stdout.strip().split('\n') if f]
320
+ except subprocess.CalledProcessError:
321
+ return {}
322
+
323
+ all_violations = {}
324
+ for filepath in files:
325
+ if Path(filepath).exists():
326
+ violations = self.check_file(filepath)
327
+ if violations:
328
+ all_violations[filepath] = violations
329
+
330
+ return all_violations
agent_os/compat.py ADDED
@@ -0,0 +1,74 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Graceful degradation helpers for optional toolkit dependencies.
4
+
5
+ Provides no-op fallbacks so consumers can optionally depend on the
6
+ toolkit without try/except import boilerplate.
7
+
8
+ Usage::
9
+
10
+ from agent_os.compat import PolicyEvaluator, get_evaluator
11
+
12
+ # Real class if agent-os-kernel installed, no-op otherwise.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from typing import Any
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ try:
23
+ from agent_os.policies.evaluator import PolicyEvaluator as _RealEvaluator
24
+
25
+ TOOLKIT_AVAILABLE = True
26
+ except ImportError:
27
+ TOOLKIT_AVAILABLE = False
28
+ _RealEvaluator = None # type: ignore[assignment, misc]
29
+
30
+
31
+ class _AllowDecision:
32
+ allowed = True
33
+ reason = "no-op"
34
+ matched_rule = None
35
+
36
+
37
+ class NoOpPolicyEvaluator:
38
+ """No-op policy evaluator — allows all actions."""
39
+
40
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
41
+ logger.debug("NoOpPolicyEvaluator: toolkit not installed, all actions allowed")
42
+
43
+ def evaluate(self, *args: Any, **kwargs: Any) -> _AllowDecision:
44
+ return _AllowDecision()
45
+
46
+ def load_policies(self, *args: Any, **kwargs: Any) -> None:
47
+ pass
48
+
49
+ def add_backend(self, *args: Any, **kwargs: Any) -> None:
50
+ pass
51
+
52
+
53
+ class NoOpGovernanceMiddleware:
54
+ """No-op governance middleware — passes all calls through."""
55
+
56
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
57
+ logger.debug("NoOpGovernanceMiddleware: toolkit not installed")
58
+
59
+ def __call__(self, func: Any) -> Any:
60
+ return func
61
+
62
+ def wrap(self, func: Any) -> Any:
63
+ return func
64
+
65
+
66
+ def get_evaluator(**kwargs: Any) -> Any:
67
+ """Get a PolicyEvaluator if available, otherwise a no-op."""
68
+ if TOOLKIT_AVAILABLE and _RealEvaluator is not None:
69
+ return _RealEvaluator(**kwargs)
70
+ return NoOpPolicyEvaluator(**kwargs)
71
+
72
+
73
+ PolicyEvaluator = _RealEvaluator if TOOLKIT_AVAILABLE else NoOpPolicyEvaluator # type: ignore[assignment]
74
+ GovernanceMiddleware = NoOpGovernanceMiddleware