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,143 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Role-Based Access Control (RBAC) for Agent OS.
5
+
6
+ Provides role assignment, policy lookup, and permission checking for agents.
7
+ """
8
+
9
+ from enum import Enum
10
+
11
+ import yaml
12
+
13
+ from agent_os.integrations.base import GovernancePolicy
14
+
15
+
16
+ class Role(Enum):
17
+ """Standard roles for agent access control."""
18
+ READER = "reader"
19
+ WRITER = "writer"
20
+ ADMIN = "admin"
21
+ AUDITOR = "auditor"
22
+
23
+
24
+ # Action permissions per role
25
+ _ROLE_PERMISSIONS: dict[Role, set[str]] = {
26
+ Role.READER: {"read"},
27
+ Role.WRITER: {"read", "write", "search"},
28
+ Role.ADMIN: {"read", "write", "search", "admin", "delete", "audit"},
29
+ Role.AUDITOR: {"read", "search", "audit"},
30
+ }
31
+
32
+ # Default policy templates per role
33
+ _DEFAULT_POLICIES: dict[Role, GovernancePolicy] = {
34
+ Role.READER: GovernancePolicy(
35
+ max_tool_calls=0,
36
+ allowed_tools=[],
37
+ require_human_approval=True,
38
+ ),
39
+ Role.WRITER: GovernancePolicy(
40
+ max_tool_calls=5,
41
+ allowed_tools=["read", "write", "search"],
42
+ require_human_approval=False,
43
+ ),
44
+ Role.ADMIN: GovernancePolicy(
45
+ max_tool_calls=50,
46
+ allowed_tools=[],
47
+ max_tokens=16384,
48
+ require_human_approval=False,
49
+ ),
50
+ Role.AUDITOR: GovernancePolicy(
51
+ max_tool_calls=5,
52
+ allowed_tools=["read", "search", "audit"],
53
+ log_all_calls=True,
54
+ require_human_approval=False,
55
+ ),
56
+ }
57
+
58
+ DEFAULT_ROLE = Role.READER
59
+
60
+
61
+ class RBACManager:
62
+ """Manages role-based access control for agents.
63
+
64
+ Assigns roles to agents, resolves governance policies per role,
65
+ and checks action permissions. Unknown agents receive the READER role.
66
+ """
67
+
68
+ def __init__(self) -> None:
69
+ self._roles: dict[str, Role] = {}
70
+ self._custom_policies: dict[Role, GovernancePolicy] = {}
71
+ self._custom_permissions: dict[Role, set[str]] = {}
72
+
73
+ def assign_role(self, agent_id: str, role: Role) -> None:
74
+ """Assign a role to an agent."""
75
+ self._roles[agent_id] = role
76
+
77
+ def get_role(self, agent_id: str) -> Role:
78
+ """Return the role for an agent, defaulting to READER."""
79
+ return self._roles.get(agent_id, DEFAULT_ROLE)
80
+
81
+ def get_policy(self, agent_id: str) -> GovernancePolicy:
82
+ """Return the governance policy template for an agent's role."""
83
+ role = self.get_role(agent_id)
84
+ if role in self._custom_policies:
85
+ return self._custom_policies[role]
86
+ return _DEFAULT_POLICIES[role]
87
+
88
+ def has_permission(self, agent_id: str, action: str) -> bool:
89
+ """Check whether an agent is permitted to perform an action."""
90
+ role = self.get_role(agent_id)
91
+ perms = self._custom_permissions.get(role, _ROLE_PERMISSIONS.get(role, set()))
92
+ return action in perms
93
+
94
+ def remove_role(self, agent_id: str) -> None:
95
+ """Remove a role assignment, reverting the agent to the default role."""
96
+ self._roles.pop(agent_id, None)
97
+
98
+ # ── YAML serialisation ────────────────────────────────────
99
+
100
+ def to_yaml(self, path: str) -> None:
101
+ """Save current role assignments and custom definitions to a YAML file."""
102
+ data: dict[str, object] = {
103
+ "assignments": {aid: role.value for aid, role in self._roles.items()},
104
+ }
105
+ if self._custom_policies:
106
+ data["custom_policies"] = {
107
+ role.value: yaml.safe_load(policy.to_yaml())
108
+ for role, policy in self._custom_policies.items()
109
+ }
110
+ if self._custom_permissions:
111
+ data["custom_permissions"] = {
112
+ role.value: sorted(perms)
113
+ for role, perms in self._custom_permissions.items()
114
+ }
115
+ with open(path, "w", encoding="utf-8") as f:
116
+ yaml.dump(data, f, default_flow_style=False, sort_keys=False)
117
+
118
+ @classmethod
119
+ def from_yaml(cls, path: str) -> "RBACManager":
120
+ """Load an RBACManager from a YAML file."""
121
+ with open(path, encoding="utf-8") as f:
122
+ data = yaml.safe_load(f)
123
+ if not isinstance(data, dict):
124
+ raise ValueError(f"Expected a YAML mapping, got {type(data).__name__}")
125
+
126
+ mgr = cls()
127
+
128
+ # Role assignments
129
+ for agent_id, role_value in data.get("assignments", {}).items():
130
+ mgr.assign_role(agent_id, Role(role_value))
131
+
132
+ # Custom policies
133
+ for role_value, policy_dict in data.get("custom_policies", {}).items():
134
+ role = Role(role_value)
135
+ yaml_str = yaml.dump(policy_dict, default_flow_style=False)
136
+ mgr._custom_policies[role] = GovernancePolicy.from_yaml(yaml_str)
137
+
138
+ # Custom permissions
139
+ for role_value, perms_list in data.get("custom_permissions", {}).items():
140
+ role = Role(role_value)
141
+ mgr._custom_permissions[role] = set(perms_list)
142
+
143
+ return mgr
@@ -0,0 +1,113 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Adapter Registry with Auto-Discovery
5
+
6
+ Provides a central registry for framework adapters, with support for
7
+ manual registration, decorator-based registration, and automatic
8
+ discovery of BaseIntegration subclasses.
9
+ """
10
+
11
+ import importlib
12
+ import inspect
13
+ import logging
14
+ import pkgutil
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ from .base import BaseIntegration
19
+
20
+
21
+ class AdapterRegistry:
22
+ """Singleton registry for framework adapters."""
23
+
24
+ _instance: "AdapterRegistry | None" = None
25
+ _adapters: dict[str, type[BaseIntegration]]
26
+
27
+ def __new__(cls) -> "AdapterRegistry":
28
+ if cls._instance is None:
29
+ cls._instance = super().__new__(cls)
30
+ cls._instance._adapters = {}
31
+ return cls._instance
32
+
33
+ def register(self, name: str, adapter_class: type[BaseIntegration]) -> None:
34
+ """Register an adapter class under the given name.
35
+
36
+ Raises:
37
+ ValueError: If *name* is already registered.
38
+ TypeError: If *adapter_class* is not a BaseIntegration subclass.
39
+ """
40
+ if not (isinstance(adapter_class, type) and issubclass(adapter_class, BaseIntegration)):
41
+ raise TypeError(
42
+ f"adapter_class must be a subclass of BaseIntegration, "
43
+ f"got {adapter_class!r}"
44
+ )
45
+ if name in self._adapters:
46
+ raise ValueError(f"Adapter '{name}' is already registered")
47
+ self._adapters[name] = adapter_class
48
+
49
+ def get(self, name: str) -> type[BaseIntegration]:
50
+ """Return the adapter class registered under *name*.
51
+
52
+ Raises:
53
+ KeyError: If no adapter is registered with that name.
54
+ """
55
+ try:
56
+ return self._adapters[name]
57
+ except KeyError:
58
+ raise KeyError(f"No adapter registered with name '{name}'") from None
59
+
60
+ def list_adapters(self) -> list[str]:
61
+ """Return sorted list of registered adapter names."""
62
+ return sorted(self._adapters)
63
+
64
+ def clear(self) -> None:
65
+ """Remove all registered adapters (useful for testing)."""
66
+ self._adapters.clear()
67
+
68
+ @classmethod
69
+ def auto_discover(cls) -> "AdapterRegistry":
70
+ """Scan the integrations package and register all BaseIntegration subclasses.
71
+
72
+ Each subclass is registered under its class name. Returns the
73
+ (singleton) registry instance.
74
+ """
75
+ registry = cls()
76
+ package = importlib.import_module("agent_os.integrations")
77
+ package_path = package.__path__
78
+
79
+ for _importer, modname, _ispkg in pkgutil.iter_modules(package_path):
80
+ if modname.startswith("_"):
81
+ continue
82
+ full_name = f"agent_os.integrations.{modname}"
83
+ try:
84
+ mod = importlib.import_module(full_name)
85
+ except Exception: # noqa: BLE001 — optional adapter may not be installed
86
+ logger.debug("Failed to import adapter module %s", full_name, exc_info=True)
87
+ continue
88
+ for _attr_name, obj in inspect.getmembers(mod, inspect.isclass):
89
+ if (
90
+ issubclass(obj, BaseIntegration)
91
+ and obj is not BaseIntegration
92
+ and obj.__name__ not in registry._adapters
93
+ ):
94
+ registry._adapters[obj.__name__] = obj
95
+
96
+ return registry
97
+
98
+
99
+ def register_adapter(name: str):
100
+ """Class decorator that registers an adapter in the global registry.
101
+
102
+ Usage::
103
+
104
+ @register_adapter("my_framework")
105
+ class MyAdapter(BaseIntegration):
106
+ ...
107
+ """
108
+
109
+ def decorator(cls: type[BaseIntegration]) -> type[BaseIntegration]:
110
+ AdapterRegistry().register(name, cls)
111
+ return cls
112
+
113
+ return decorator
@@ -0,0 +1,303 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Scope Guard — prevents agent actions from exceeding configured scope limits.
4
+
5
+ Evaluates file count, line count, and scope drift against per-agent
6
+ configuration to produce PASS / SOFT_FAIL / HARD_FAIL decisions.
7
+ Integrates with the PolicyEngine for governance audit trails.
8
+
9
+ Usage::
10
+
11
+ from agent_os.integrations.scope_guard import ScopeGuard, ScopeConfig
12
+
13
+ config = ScopeConfig(max_files=10, max_lines=500)
14
+ guard = ScopeGuard()
15
+ result = guard.evaluate(
16
+ agent_id="implementer",
17
+ config=config,
18
+ changed_files=["src/main.py", "tests/test_main.py"],
19
+ insertions=120,
20
+ deletions=30,
21
+ )
22
+ print(result.decision) # "PASS"
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import logging
28
+ import subprocess
29
+ from dataclasses import dataclass, field
30
+ from typing import Any, Optional
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ @dataclass
36
+ class ScopeConfig:
37
+ """Per-agent scope configuration.
38
+
39
+ Attributes:
40
+ max_files: Maximum number of files an agent may change.
41
+ max_lines: Maximum total lines (insertions + deletions) allowed.
42
+ mode: Guard mode — ``"on"`` (default) to enforce, ``"off"`` to skip.
43
+ drift_detection: Whether to evaluate drift indicators.
44
+ """
45
+
46
+ max_files: int = 10
47
+ max_lines: int = 500
48
+ mode: str = "on"
49
+ drift_detection: bool = True
50
+
51
+
52
+ @dataclass
53
+ class ScopeEvaluation:
54
+ """Result of a scope guard evaluation.
55
+
56
+ Attributes:
57
+ decision: One of ``"PASS"``, ``"SOFT_FAIL"``, ``"HARD_FAIL"``.
58
+ files_changed: Number of files changed.
59
+ lines_changed: Total lines changed (insertions + deletions).
60
+ max_files: Configured file limit.
61
+ max_lines: Configured line limit.
62
+ drift_indicators: Drift indicator dicts passed in for audit.
63
+ reason: Human-readable explanation of the decision.
64
+ excess_files: File paths that exceed the file limit.
65
+ """
66
+
67
+ decision: str
68
+ files_changed: int
69
+ lines_changed: int
70
+ max_files: int
71
+ max_lines: int
72
+ drift_indicators: list[dict[str, Any]] = field(default_factory=list)
73
+ reason: str = ""
74
+ excess_files: list[str] = field(default_factory=list)
75
+
76
+
77
+ def _escalate(current: str, proposed: str) -> str:
78
+ """Return the more severe of two decisions."""
79
+ severity = {"PASS": 0, "SOFT_FAIL": 1, "HARD_FAIL": 2}
80
+ if severity.get(proposed, 0) > severity.get(current, 0):
81
+ return proposed
82
+ return current
83
+
84
+
85
+ def _get_diff_stats(
86
+ repo_path: str, base_branch: str = "main"
87
+ ) -> tuple[list[str], int, int]:
88
+ """Return ``(changed_files, insertions, deletions)`` via ``git diff --numstat``.
89
+
90
+ Args:
91
+ repo_path: Path to a git repository or worktree.
92
+ base_branch: Branch to diff against.
93
+
94
+ Returns:
95
+ Tuple of (file paths, total insertions, total deletions).
96
+ """
97
+ try:
98
+ result = subprocess.run(
99
+ ["git", "diff", "--numstat", base_branch],
100
+ cwd=repo_path,
101
+ capture_output=True,
102
+ text=True,
103
+ timeout=30,
104
+ )
105
+ except (subprocess.TimeoutExpired, FileNotFoundError) as exc:
106
+ logger.warning("_get_diff_stats failed: %s", exc)
107
+ return [], 0, 0
108
+
109
+ files: list[str] = []
110
+ insertions = 0
111
+ deletions = 0
112
+ for line in result.stdout.strip().splitlines():
113
+ parts = line.split("\t")
114
+ if len(parts) == 3:
115
+ ins = int(parts[0]) if parts[0] != "-" else 0
116
+ dels = int(parts[1]) if parts[1] != "-" else 0
117
+ insertions += ins
118
+ deletions += dels
119
+ files.append(parts[2])
120
+ return files, insertions, deletions
121
+
122
+
123
+ class ScopeGuard:
124
+ """Evaluates whether agent changes are within configured scope limits.
125
+
126
+ Optionally records governance audit events through a *policy_engine*.
127
+ The policy engine, when provided, must expose a
128
+ ``record_event(event: dict)`` method.
129
+
130
+ Args:
131
+ policy_engine: Optional governance policy engine for audit trails.
132
+ """
133
+
134
+ def __init__(self, policy_engine: Optional[Any] = None) -> None:
135
+ self._policy_engine = policy_engine
136
+
137
+ # ── public API ──────────────────────────────────────────
138
+
139
+ def evaluate(
140
+ self,
141
+ agent_id: str,
142
+ config: ScopeConfig,
143
+ changed_files: list[str],
144
+ insertions: int,
145
+ deletions: int,
146
+ drift_indicators: Optional[list[dict[str, Any]]] = None,
147
+ ) -> ScopeEvaluation:
148
+ """Evaluate whether an agent's changes are within scope.
149
+
150
+ Decision logic:
151
+ 1. ``config.mode == "off"`` → always ``PASS``.
152
+ 2. files > ``max_files × 2`` **or** lines > ``max_lines × 2`` → ``HARD_FAIL``.
153
+ 3. files > ``max_files`` **or** lines > ``max_lines`` → ``SOFT_FAIL``.
154
+ 4. Any drift indicator with ``severity == "warning"`` → ``SOFT_FAIL``.
155
+ 5. Otherwise → ``PASS``.
156
+
157
+ Args:
158
+ agent_id: Unique agent identifier.
159
+ config: Scope configuration for this agent.
160
+ changed_files: List of changed file paths.
161
+ insertions: Total lines added.
162
+ deletions: Total lines removed.
163
+ drift_indicators: Optional list of drift indicator dicts. Each
164
+ dict may contain ``severity`` (``"info"`` | ``"warning"``).
165
+
166
+ Returns:
167
+ A :class:`ScopeEvaluation` with the decision and supporting data.
168
+ """
169
+ max_files = config.max_files
170
+ max_lines = config.max_lines
171
+ files_changed = len(changed_files)
172
+ lines_changed = insertions + deletions
173
+ drift_indicators = drift_indicators or []
174
+
175
+ # Mode "off" → always pass
176
+ if config.mode == "off":
177
+ evaluation = ScopeEvaluation(
178
+ decision="PASS",
179
+ files_changed=files_changed,
180
+ lines_changed=lines_changed,
181
+ max_files=max_files,
182
+ max_lines=max_lines,
183
+ reason="Scope guard disabled (mode=off)",
184
+ )
185
+ self._record(agent_id, evaluation)
186
+ return evaluation
187
+
188
+ reasons: list[str] = []
189
+ decision = "PASS"
190
+
191
+ # Check file count
192
+ if max_files > 0 and files_changed > max_files:
193
+ if files_changed > max_files * 2:
194
+ decision = "HARD_FAIL"
195
+ reasons.append(
196
+ f"files changed ({files_changed}) exceeds 2× limit "
197
+ f"({max_files * 2})"
198
+ )
199
+ else:
200
+ decision = _escalate(decision, "SOFT_FAIL")
201
+ reasons.append(
202
+ f"files changed ({files_changed}) exceeds limit ({max_files})"
203
+ )
204
+
205
+ # Check line count
206
+ if max_lines > 0 and lines_changed > max_lines:
207
+ if lines_changed > max_lines * 2:
208
+ decision = "HARD_FAIL"
209
+ reasons.append(
210
+ f"lines changed ({lines_changed}) exceeds 2× limit "
211
+ f"({max_lines * 2})"
212
+ )
213
+ else:
214
+ decision = _escalate(decision, "SOFT_FAIL")
215
+ reasons.append(
216
+ f"lines changed ({lines_changed}) exceeds limit ({max_lines})"
217
+ )
218
+
219
+ # Check drift indicators
220
+ if config.drift_detection and drift_indicators:
221
+ warnings = [
222
+ d for d in drift_indicators if d.get("severity") == "warning"
223
+ ]
224
+ if warnings:
225
+ decision = _escalate(decision, "SOFT_FAIL")
226
+ reasons.append(
227
+ f"{len(warnings)} scope drift warning(s) detected"
228
+ )
229
+
230
+ # Excess files for downstream remediation
231
+ excess_files: list[str] = []
232
+ if max_files > 0 and files_changed > max_files:
233
+ excess_files = changed_files[max_files:]
234
+
235
+ reason = "; ".join(reasons) if reasons else "All scope checks passed"
236
+
237
+ evaluation = ScopeEvaluation(
238
+ decision=decision,
239
+ files_changed=files_changed,
240
+ lines_changed=lines_changed,
241
+ max_files=max_files,
242
+ max_lines=max_lines,
243
+ drift_indicators=drift_indicators,
244
+ reason=reason,
245
+ excess_files=excess_files,
246
+ )
247
+
248
+ self._record(agent_id, evaluation)
249
+ return evaluation
250
+
251
+ def evaluate_from_git(
252
+ self,
253
+ agent_id: str,
254
+ config: ScopeConfig,
255
+ repo_path: str,
256
+ base_branch: str = "main",
257
+ drift_indicators: Optional[list[dict[str, Any]]] = None,
258
+ ) -> ScopeEvaluation:
259
+ """Convenience wrapper that reads diff stats from *repo_path*.
260
+
261
+ Args:
262
+ agent_id: Unique agent identifier.
263
+ config: Scope configuration.
264
+ repo_path: Path to the git repository.
265
+ base_branch: Branch to diff against (default ``"main"``).
266
+ drift_indicators: Optional drift indicator dicts.
267
+
268
+ Returns:
269
+ A :class:`ScopeEvaluation`.
270
+ """
271
+ changed_files, insertions, deletions = _get_diff_stats(
272
+ repo_path, base_branch
273
+ )
274
+ return self.evaluate(
275
+ agent_id=agent_id,
276
+ config=config,
277
+ changed_files=changed_files,
278
+ insertions=insertions,
279
+ deletions=deletions,
280
+ drift_indicators=drift_indicators,
281
+ )
282
+
283
+ # ── internal helpers ────────────────────────────────────
284
+
285
+ def _record(self, agent_id: str, evaluation: ScopeEvaluation) -> None:
286
+ """Record the evaluation as an audit event if a policy engine is set."""
287
+ if self._policy_engine is None:
288
+ return
289
+ try:
290
+ self._policy_engine.record_event(
291
+ {
292
+ "type": "scope_evaluation",
293
+ "agent_id": agent_id,
294
+ "decision": evaluation.decision,
295
+ "files_changed": evaluation.files_changed,
296
+ "max_files": evaluation.max_files,
297
+ "lines_changed": evaluation.lines_changed,
298
+ "max_lines": evaluation.max_lines,
299
+ "reason": evaluation.reason,
300
+ }
301
+ )
302
+ except Exception: # pragma: no cover — best-effort audit
303
+ logger.exception("Failed to record scope evaluation event")