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,234 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Constraint Graph — DAG-based resource access control.
4
+
5
+ The constraint graph is the **only** path through which agents may access
6
+ resources (tools, APIs, data). Every resource access request is resolved
7
+ by traversing a directed acyclic graph of constraint edges that encode
8
+ allow/deny rules with optional conditions.
9
+
10
+ Architecture:
11
+ Agent ──▶ ConstraintGraph.resolve() ──▶ allow / deny
12
+
13
+ ├─ match agent_pattern against agent_id
14
+ ├─ match resource name
15
+ └─ evaluate conditions (time, role, etc.)
16
+
17
+ Integration:
18
+ ``ConstraintGraphEnforcer`` implements the same ``intercept()`` protocol
19
+ as ``PolicyInterceptor`` so it can be composed via ``CompositeInterceptor``.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import fnmatch
25
+ import logging
26
+ from dataclasses import dataclass, field
27
+ from enum import Enum
28
+ from typing import Any
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Data models
35
+ # ---------------------------------------------------------------------------
36
+
37
+ class Permission(Enum):
38
+ """Permission type for a constraint edge."""
39
+ ALLOW = "allow"
40
+ DENY = "deny"
41
+
42
+
43
+ class ResourceType(Enum):
44
+ """Classification of a governed resource."""
45
+ TOOL = "tool"
46
+ API = "api"
47
+ DATA = "data"
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class ResourceNode:
52
+ """A resource (tool, API, data) in the constraint graph.
53
+
54
+ Attributes:
55
+ name: Unique resource identifier (e.g. ``"database_query"``).
56
+ resource_type: Classification of the resource.
57
+ metadata: Arbitrary key/value metadata for condition evaluation.
58
+ """
59
+ name: str
60
+ resource_type: ResourceType = ResourceType.TOOL
61
+ metadata: dict[str, Any] = field(default_factory=dict, hash=False)
62
+
63
+ def __hash__(self) -> int:
64
+ return hash((self.name, self.resource_type))
65
+
66
+ def __eq__(self, other: object) -> bool:
67
+ if not isinstance(other, ResourceNode):
68
+ return NotImplemented
69
+ return self.name == other.name and self.resource_type == other.resource_type
70
+
71
+
72
+ @dataclass
73
+ class ConstraintEdge:
74
+ """A constraint linking an agent pattern to a resource permission.
75
+
76
+ Attributes:
77
+ agent_pattern: Glob pattern matched against agent IDs.
78
+ resource: The resource this constraint governs.
79
+ permission: Whether to allow or deny access.
80
+ conditions: Optional key/value conditions that must all be satisfied
81
+ for this edge to apply (e.g. ``{"role": "admin"}``).
82
+ priority: Higher-priority edges take precedence during resolution.
83
+ """
84
+ agent_pattern: str
85
+ resource: str
86
+ permission: Permission
87
+ conditions: dict[str, Any] = field(default_factory=dict)
88
+ priority: int = 0
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # Constraint Graph
93
+ # ---------------------------------------------------------------------------
94
+
95
+ class ConstraintGraph:
96
+ """DAG of resource constraints.
97
+
98
+ Edges are evaluated in priority order (highest first). The first matching
99
+ edge determines the outcome. If no edge matches, access is **denied** by
100
+ default (deny-by-default posture).
101
+ """
102
+
103
+ def __init__(self) -> None:
104
+ self._nodes: dict[str, ResourceNode] = {}
105
+ self._edges: list[ConstraintEdge] = []
106
+
107
+ # -- mutators -----------------------------------------------------------
108
+
109
+ def add_resource(self, node: ResourceNode) -> None:
110
+ """Register a resource node."""
111
+ self._nodes[node.name] = node
112
+
113
+ def add_constraint(self, edge: ConstraintEdge) -> None:
114
+ """Add a constraint edge and re-sort by descending priority."""
115
+ self._edges.append(edge)
116
+ self._edges.sort(key=lambda e: e.priority, reverse=True)
117
+
118
+ # -- query --------------------------------------------------------------
119
+
120
+ @property
121
+ def resources(self) -> dict[str, ResourceNode]:
122
+ """Read-only view of registered resources."""
123
+ return dict(self._nodes)
124
+
125
+ @property
126
+ def edges(self) -> list[ConstraintEdge]:
127
+ """Read-only copy of constraint edges (sorted by priority)."""
128
+ return list(self._edges)
129
+
130
+ # -- resolution ---------------------------------------------------------
131
+
132
+ def resolve(
133
+ self,
134
+ agent_id: str,
135
+ resource: str,
136
+ context: dict[str, Any] | None = None,
137
+ ) -> bool:
138
+ """Check whether *agent_id* may access *resource*.
139
+
140
+ Args:
141
+ agent_id: The requesting agent's identifier.
142
+ resource: Name of the target resource.
143
+ context: Runtime context used for condition evaluation.
144
+
145
+ Returns:
146
+ ``True`` if access is allowed, ``False`` otherwise.
147
+ """
148
+ context = context or {}
149
+
150
+ for edge in self._edges:
151
+ if not fnmatch.fnmatch(agent_id, edge.agent_pattern):
152
+ continue
153
+ if not fnmatch.fnmatch(resource, edge.resource):
154
+ continue
155
+ if not self._conditions_met(edge.conditions, context):
156
+ continue
157
+
158
+ allowed = edge.permission == Permission.ALLOW
159
+ logger.debug(
160
+ "constraint resolved: agent=%s resource=%s -> %s (priority=%d)",
161
+ agent_id,
162
+ resource,
163
+ edge.permission.value,
164
+ edge.priority,
165
+ )
166
+ return allowed
167
+
168
+ # Deny by default
169
+ logger.debug(
170
+ "constraint resolved: agent=%s resource=%s -> deny (no matching edge)",
171
+ agent_id,
172
+ resource,
173
+ )
174
+ return False
175
+
176
+ # -- internals ----------------------------------------------------------
177
+
178
+ @staticmethod
179
+ def _conditions_met(
180
+ conditions: dict[str, Any],
181
+ context: dict[str, Any],
182
+ ) -> bool:
183
+ """Return ``True`` if every condition key/value is present in *context*."""
184
+ return all(context.get(k) == v for k, v in conditions.items())
185
+
186
+
187
+ # ---------------------------------------------------------------------------
188
+ # Enforcer (PolicyInterceptor-compatible)
189
+ # ---------------------------------------------------------------------------
190
+
191
+ class ConstraintGraphEnforcer:
192
+ """Intercepts tool calls and enforces the constraint graph.
193
+
194
+ Implements the same ``intercept(request) -> result`` protocol used by
195
+ ``PolicyInterceptor`` and ``CompositeInterceptor`` so it can be plugged
196
+ into the existing governance pipeline.
197
+ """
198
+
199
+ def __init__(
200
+ self,
201
+ graph: ConstraintGraph,
202
+ context: dict[str, Any] | None = None,
203
+ ) -> None:
204
+ self.graph = graph
205
+ self.context = context or {}
206
+
207
+ def intercept(self, request: Any) -> Any:
208
+ """Enforce constraint graph for a ``ToolCallRequest``.
209
+
210
+ Imports are deferred to avoid circular dependency with
211
+ ``integrations.base``.
212
+ """
213
+ from agent_os.integrations.base import ToolCallResult
214
+
215
+ agent_id = getattr(request, "agent_id", "") or ""
216
+ tool_name = getattr(request, "tool_name", "") or ""
217
+
218
+ if not agent_id:
219
+ return ToolCallResult(
220
+ allowed=False,
221
+ reason="Constraint graph requires agent_id on the request",
222
+ )
223
+
224
+ allowed = self.graph.resolve(agent_id, tool_name, self.context)
225
+ if not allowed:
226
+ return ToolCallResult(
227
+ allowed=False,
228
+ reason=(
229
+ f"Constraint graph denied agent '{agent_id}' "
230
+ f"access to resource '{tool_name}'"
231
+ ),
232
+ )
233
+
234
+ return ToolCallResult(allowed=True)
@@ -0,0 +1,140 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Content and knowledge quality governance for AI agents.
4
+
5
+ Runtime governance answers 'is the agent behavior safe?'
6
+ Content governance answers 'is the agent output accurate, well-structured,
7
+ and meeting quality standards?'
8
+ """
9
+ from __future__ import annotations
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from typing import Any
13
+
14
+
15
+ class ContentDimension(str, Enum):
16
+ """Dimensions for content quality evaluation."""
17
+ ACCURACY = "accuracy"
18
+ COMPLETENESS = "completeness"
19
+ FRESHNESS = "freshness"
20
+ STRUCTURE = "structure"
21
+ RELEVANCE = "relevance"
22
+ CONSISTENCY = "consistency"
23
+
24
+
25
+ class QualityGate(str, Enum):
26
+ """Content quality gate decisions."""
27
+ PASS = "pass"
28
+ WARN = "warn"
29
+ FAIL = "fail"
30
+
31
+
32
+ @dataclass
33
+ class ContentQualityRule:
34
+ """A rule for evaluating content quality."""
35
+ name: str
36
+ dimension: ContentDimension
37
+ threshold: float # 0.0 to 1.0
38
+ gate: QualityGate = QualityGate.WARN
39
+ description: str = ""
40
+
41
+
42
+ @dataclass
43
+ class ContentEvaluation:
44
+ """Result of evaluating content against quality rules."""
45
+ dimension: ContentDimension
46
+ score: float
47
+ gate_result: QualityGate
48
+ rule_name: str
49
+ details: str = ""
50
+
51
+
52
+ @dataclass
53
+ class ContentQualityReport:
54
+ """Aggregated quality report for agent output."""
55
+ agent_id: str
56
+ content_id: str
57
+ evaluations: list[ContentEvaluation] = field(default_factory=list)
58
+
59
+ @property
60
+ def passed(self) -> bool:
61
+ return all(e.gate_result != QualityGate.FAIL for e in self.evaluations)
62
+
63
+ @property
64
+ def overall_score(self) -> float:
65
+ if not self.evaluations:
66
+ return 0.0
67
+ return sum(e.score for e in self.evaluations) / len(self.evaluations)
68
+
69
+ @property
70
+ def warnings(self) -> list[ContentEvaluation]:
71
+ return [e for e in self.evaluations if e.gate_result == QualityGate.WARN]
72
+
73
+ @property
74
+ def failures(self) -> list[ContentEvaluation]:
75
+ return [e for e in self.evaluations if e.gate_result == QualityGate.FAIL]
76
+
77
+
78
+ class ContentQualityEvaluator:
79
+ """Evaluates agent output against content quality rules.
80
+
81
+ Usage:
82
+ evaluator = ContentQualityEvaluator()
83
+ evaluator.add_rule(ContentQualityRule(
84
+ name="min-accuracy",
85
+ dimension=ContentDimension.ACCURACY,
86
+ threshold=0.8,
87
+ gate=QualityGate.FAIL,
88
+ ))
89
+
90
+ report = evaluator.evaluate(
91
+ agent_id="agent-1",
92
+ content_id="response-123",
93
+ scores={ContentDimension.ACCURACY: 0.75},
94
+ )
95
+ assert not report.passed # Below 0.8 threshold
96
+ """
97
+
98
+ def __init__(self) -> None:
99
+ self._rules: list[ContentQualityRule] = []
100
+
101
+ def add_rule(self, rule: ContentQualityRule) -> None:
102
+ self._rules.append(rule)
103
+
104
+ def load_rules(self, rules: list[dict[str, Any]]) -> None:
105
+ """Load rules from a list of dicts (e.g., from YAML config)."""
106
+ for r in rules:
107
+ self._rules.append(ContentQualityRule(
108
+ name=r["name"],
109
+ dimension=ContentDimension(r["dimension"]),
110
+ threshold=float(r["threshold"]),
111
+ gate=QualityGate(r.get("gate", "warn")),
112
+ description=r.get("description", ""),
113
+ ))
114
+
115
+ def evaluate(
116
+ self,
117
+ agent_id: str,
118
+ content_id: str,
119
+ scores: dict[ContentDimension, float],
120
+ ) -> ContentQualityReport:
121
+ """Evaluate content scores against configured rules."""
122
+ evaluations = []
123
+ for rule in self._rules:
124
+ score = scores.get(rule.dimension, 0.0)
125
+ if score >= rule.threshold:
126
+ gate_result = QualityGate.PASS
127
+ else:
128
+ gate_result = rule.gate
129
+ evaluations.append(ContentEvaluation(
130
+ dimension=rule.dimension,
131
+ score=score,
132
+ gate_result=gate_result,
133
+ rule_name=rule.name,
134
+ details=f"Score {score:.2f} vs threshold {rule.threshold:.2f}",
135
+ ))
136
+ return ContentQualityReport(
137
+ agent_id=agent_id,
138
+ content_id=content_id,
139
+ evaluations=evaluations,
140
+ )
@@ -0,0 +1,305 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Context Budget Scheduler — token budget as a kernel primitive.
5
+
6
+ Makes the "Scale by Subtraction" philosophy (90 % lookup, 10 % reasoning)
7
+ concrete and enforced. The kernel owns the budget; agents cannot exceed it.
8
+
9
+ Closes #207.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import time
15
+ from dataclasses import dataclass, field
16
+ from enum import Enum, auto
17
+ from typing import Any, Callable
18
+
19
+ # ---------------------------------------------------------------------------
20
+ # Signals
21
+ # ---------------------------------------------------------------------------
22
+
23
+ class AgentSignal(Enum):
24
+ """Kernel signals for context budget enforcement."""
25
+
26
+ SIGSTOP = auto() # Budget exceeded — halt the agent
27
+ SIGWARN = auto() # Budget nearing limit
28
+ SIGRESUME = auto() # Budget replenished
29
+
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Data structures
33
+ # ---------------------------------------------------------------------------
34
+
35
+ @dataclass(frozen=True)
36
+ class ContextWindow:
37
+ """An allocated context window for an agent task."""
38
+
39
+ agent_id: str
40
+ task: str
41
+ lookup_budget: int # tokens for retrieval / facts
42
+ reasoning_budget: int # tokens for LLM reasoning
43
+ total: int # lookup + reasoning
44
+ created_at: float = field(default_factory=time.time)
45
+
46
+ @property
47
+ def lookup_ratio(self) -> float:
48
+ return self.lookup_budget / self.total if self.total else 0.0
49
+
50
+ @property
51
+ def reasoning_ratio(self) -> float:
52
+ return self.reasoning_budget / self.total if self.total else 0.0
53
+
54
+
55
+ class ContextPriority(Enum):
56
+ """Task priority levels for context allocation."""
57
+
58
+ CRITICAL = 3 # Gets full allocation even if pool is tight
59
+ HIGH = 2
60
+ NORMAL = 1
61
+ LOW = 0 # Smallest possible allocation
62
+
63
+
64
+ @dataclass
65
+ class UsageRecord:
66
+ """Tracks actual token usage by an agent."""
67
+
68
+ agent_id: str
69
+ window: ContextWindow
70
+ lookup_used: int = 0
71
+ reasoning_used: int = 0
72
+ started_at: float = field(default_factory=time.time)
73
+ stopped: bool = False
74
+ stop_reason: str | None = None
75
+
76
+ @property
77
+ def total_used(self) -> int:
78
+ return self.lookup_used + self.reasoning_used
79
+
80
+ @property
81
+ def remaining(self) -> int:
82
+ return max(0, self.window.total - self.total_used)
83
+
84
+ @property
85
+ def utilization(self) -> float:
86
+ return self.total_used / self.window.total if self.window.total else 0.0
87
+
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # Budget Exceeded Error
91
+ # ---------------------------------------------------------------------------
92
+
93
+ class BudgetExceeded(Exception):
94
+ """Raised when an agent exceeds its context budget."""
95
+
96
+ def __init__(self, agent_id: str, budget: int, used: int) -> None:
97
+ self.agent_id = agent_id
98
+ self.budget = budget
99
+ self.used = used
100
+ super().__init__(
101
+ f"Agent {agent_id} exceeded context budget: {used}/{budget} tokens"
102
+ )
103
+
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # Context Budget Scheduler
107
+ # ---------------------------------------------------------------------------
108
+
109
+ # Default minimum context sizes per priority (tokens)
110
+ _MIN_CONTEXT: dict[ContextPriority, int] = {
111
+ ContextPriority.CRITICAL: 4000,
112
+ ContextPriority.HIGH: 2000,
113
+ ContextPriority.NORMAL: 1000,
114
+ ContextPriority.LOW: 500,
115
+ }
116
+
117
+
118
+ class ContextScheduler:
119
+ """
120
+ Kernel primitive that governs how context budget is allocated.
121
+
122
+ Like CPU scheduling but for token budgets. Enforces the 90/10
123
+ (lookup/reasoning) split and emits SIGSTOP when an agent goes
124
+ over budget.
125
+
126
+ Parameters
127
+ ----------
128
+ total_budget : int
129
+ Global token pool (shared across all active agents).
130
+ lookup_ratio : float
131
+ Fraction of each allocation devoted to lookup (default 0.90).
132
+ warn_threshold : float
133
+ Fraction of budget at which SIGWARN fires (default 0.85).
134
+ """
135
+
136
+ def __init__(
137
+ self,
138
+ total_budget: int = 8000,
139
+ lookup_ratio: float = 0.90,
140
+ warn_threshold: float = 0.85,
141
+ ) -> None:
142
+ if not 0.0 < lookup_ratio < 1.0:
143
+ raise ValueError("lookup_ratio must be between 0 and 1 exclusive")
144
+ if total_budget < 1:
145
+ raise ValueError("total_budget must be positive")
146
+
147
+ self.total_budget = total_budget
148
+ self.lookup_ratio = lookup_ratio
149
+ self.warn_threshold = warn_threshold
150
+
151
+ self._active: dict[str, UsageRecord] = {}
152
+ self._history: list[UsageRecord] = []
153
+ self._signal_handlers: dict[AgentSignal, list[Callable]] = {
154
+ s: [] for s in AgentSignal
155
+ }
156
+
157
+ # -- Allocation -----------------------------------------------------------
158
+
159
+ def allocate(
160
+ self,
161
+ agent_id: str,
162
+ task: str,
163
+ priority: ContextPriority = ContextPriority.NORMAL,
164
+ max_tokens: int | None = None,
165
+ ) -> ContextWindow:
166
+ """
167
+ Allocate a context window for *agent_id*.
168
+
169
+ The scheduler decides the actual size based on remaining pool
170
+ capacity and the task priority.
171
+ """
172
+ available = self._available_tokens()
173
+ minimum = _MIN_CONTEXT[priority]
174
+
175
+ if max_tokens is not None:
176
+ desired = min(max_tokens, available)
177
+ else:
178
+ # Scale by priority
179
+ desired = min(
180
+ int(self.total_budget * (0.25 + 0.25 * priority.value)),
181
+ available,
182
+ )
183
+
184
+ # Ensure at least the minimum (or whatever is left),
185
+ # but never exceed an explicit max_tokens cap.
186
+ if max_tokens is not None:
187
+ allocated = min(desired, max_tokens)
188
+ else:
189
+ allocated = max(minimum, desired) if available >= minimum else available
190
+
191
+ lookup = int(allocated * self.lookup_ratio)
192
+ reasoning = allocated - lookup
193
+
194
+ window = ContextWindow(
195
+ agent_id=agent_id,
196
+ task=task,
197
+ lookup_budget=lookup,
198
+ reasoning_budget=reasoning,
199
+ total=allocated,
200
+ )
201
+
202
+ self._active[agent_id] = UsageRecord(agent_id=agent_id, window=window)
203
+ return window
204
+
205
+ # -- Usage tracking -------------------------------------------------------
206
+
207
+ def record_usage(
208
+ self,
209
+ agent_id: str,
210
+ lookup_tokens: int = 0,
211
+ reasoning_tokens: int = 0,
212
+ ) -> UsageRecord:
213
+ """
214
+ Record token usage for an active allocation.
215
+
216
+ Emits SIGWARN or SIGSTOP as thresholds are crossed.
217
+ """
218
+ rec = self._active.get(agent_id)
219
+ if rec is None:
220
+ raise KeyError(f"No active allocation for agent {agent_id}")
221
+ if rec.stopped:
222
+ raise BudgetExceeded(agent_id, rec.window.total, rec.total_used)
223
+
224
+ rec.lookup_used += lookup_tokens
225
+ rec.reasoning_used += reasoning_tokens
226
+
227
+ # Check thresholds
228
+ utilization = rec.utilization
229
+ if utilization >= 1.0:
230
+ rec.stopped = True
231
+ rec.stop_reason = "budget_exceeded"
232
+ self._emit(AgentSignal.SIGSTOP, agent_id)
233
+ raise BudgetExceeded(agent_id, rec.window.total, rec.total_used)
234
+ elif utilization >= self.warn_threshold:
235
+ self._emit(AgentSignal.SIGWARN, agent_id)
236
+
237
+ return rec
238
+
239
+ def release(self, agent_id: str) -> UsageRecord | None:
240
+ """Release an allocation and move it to history."""
241
+ rec = self._active.pop(agent_id, None)
242
+ if rec is not None:
243
+ self._history.append(rec)
244
+ return rec
245
+
246
+ # -- Queries --------------------------------------------------------------
247
+
248
+ def get_usage(self, agent_id: str) -> UsageRecord | None:
249
+ return self._active.get(agent_id)
250
+
251
+ @property
252
+ def active_agents(self) -> list[str]:
253
+ return list(self._active.keys())
254
+
255
+ @property
256
+ def active_count(self) -> int:
257
+ return len(self._active)
258
+
259
+ def _available_tokens(self) -> int:
260
+ used = sum(r.window.total for r in self._active.values())
261
+ return max(0, self.total_budget - used)
262
+
263
+ @property
264
+ def available_tokens(self) -> int:
265
+ return self._available_tokens()
266
+
267
+ @property
268
+ def utilization(self) -> float:
269
+ """Global pool utilization (0.0 – 1.0)."""
270
+ allocated = sum(r.window.total for r in self._active.values())
271
+ return allocated / self.total_budget if self.total_budget else 0.0
272
+
273
+ def get_health_report(self) -> dict[str, Any]:
274
+ """Return a summary of scheduler state."""
275
+ return {
276
+ "total_budget": self.total_budget,
277
+ "available": self._available_tokens(),
278
+ "utilization": round(self.utilization, 3),
279
+ "active_agents": self.active_count,
280
+ "lookup_ratio": self.lookup_ratio,
281
+ "agents": {
282
+ aid: {
283
+ "task": r.window.task,
284
+ "allocated": r.window.total,
285
+ "used": r.total_used,
286
+ "remaining": r.remaining,
287
+ "stopped": r.stopped,
288
+ }
289
+ for aid, r in self._active.items()
290
+ },
291
+ "history_count": len(self._history),
292
+ }
293
+
294
+ # -- Signal system --------------------------------------------------------
295
+
296
+ def on_signal(self, signal: AgentSignal, handler: Callable) -> None:
297
+ """Register a handler for *signal*."""
298
+ self._signal_handlers[signal].append(handler)
299
+
300
+ def _emit(self, signal: AgentSignal, agent_id: str) -> None:
301
+ for handler in self._signal_handlers[signal]:
302
+ try:
303
+ handler(agent_id, signal)
304
+ except Exception:
305
+ pass