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,816 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ OpenAI Assistants Integration
5
+
6
+ Wraps OpenAI Assistants API with Agent OS governance.
7
+
8
+ Usage:
9
+ from agent_os.integrations import OpenAIKernel
10
+ from openai import OpenAI
11
+
12
+ client = OpenAI()
13
+ kernel = OpenAIKernel(policy="strict")
14
+
15
+ # Create assistant as normal
16
+ assistant = client.beta.assistants.create(
17
+ name="Trading Bot",
18
+ instructions="You analyze market data",
19
+ model="gpt-4-turbo"
20
+ )
21
+
22
+ # Wrap for governance
23
+ governed_assistant = kernel.wrap(assistant, client)
24
+
25
+ # All runs are now governed!
26
+ thread = governed_assistant.create_thread()
27
+ governed_assistant.add_message(thread.id, "Analyze AAPL")
28
+ run = governed_assistant.run(thread.id) # Governed execution
29
+
30
+ Features:
31
+ - Pre-execution policy checks
32
+ - Tool call interception and validation
33
+ - Real-time run monitoring
34
+ - SIGKILL support (cancel run on violation)
35
+ - Full audit trail
36
+ """
37
+
38
+ import json
39
+ import logging
40
+ import random
41
+ import time
42
+ from collections.abc import Generator
43
+ from dataclasses import dataclass, field
44
+ from datetime import datetime
45
+ from typing import Any, Callable, Optional
46
+
47
+ from .base import BaseIntegration, ExecutionContext, GovernancePolicy
48
+
49
+ logger = logging.getLogger("agent_os.openai")
50
+
51
+
52
+ @dataclass
53
+ class AssistantContext(ExecutionContext):
54
+ """Extended execution context for OpenAI Assistants.
55
+
56
+ Tracks assistant-specific state including thread IDs, run IDs,
57
+ function call history, and cumulative token usage for governance
58
+ enforcement.
59
+
60
+ Attributes:
61
+ assistant_id: The OpenAI assistant identifier.
62
+ thread_ids: List of thread IDs created during this session.
63
+ run_ids: List of run IDs executed during this session.
64
+ function_calls: History of function/tool calls made by the assistant.
65
+ prompt_tokens: Cumulative prompt tokens consumed across all runs.
66
+ completion_tokens: Cumulative completion tokens consumed across all runs.
67
+ """
68
+
69
+ assistant_id: str = ""
70
+ thread_ids: list[str] = field(default_factory=list)
71
+ run_ids: list[str] = field(default_factory=list)
72
+ function_calls: list[dict] = field(default_factory=list)
73
+
74
+ # Token tracking
75
+ prompt_tokens: int = 0
76
+ completion_tokens: int = 0
77
+
78
+
79
+ # Transient error base classes for retry detection
80
+ _TRANSIENT_ERROR_NAMES = ("RateLimitError", "APIConnectionError", "Timeout", "APITimeoutError")
81
+
82
+
83
+ def _is_transient(exc: Exception) -> bool:
84
+ """Return True if the exception is a transient OpenAI error."""
85
+ return type(exc).__name__ in _TRANSIENT_ERROR_NAMES
86
+
87
+
88
+ def retry_with_backoff(
89
+ fn: Callable[..., Any],
90
+ *args: Any,
91
+ max_retries: int = 3,
92
+ base_delay: float = 1.0,
93
+ max_delay: float = 30.0,
94
+ **kwargs: Any,
95
+ ) -> Any:
96
+ """Call *fn* with exponential backoff + jitter on transient errors.
97
+
98
+ Args:
99
+ fn: Callable to invoke.
100
+ max_retries: Number of retry attempts after the initial call.
101
+ base_delay: Base delay in seconds for backoff calculation.
102
+ max_delay: Upper bound for the computed delay.
103
+
104
+ Returns:
105
+ The return value of *fn*.
106
+
107
+ Raises:
108
+ The last caught exception if all retries are exhausted.
109
+ """
110
+ last_exc: Optional[Exception] = None
111
+ for attempt in range(max_retries + 1):
112
+ try:
113
+ return fn(*args, **kwargs)
114
+ except Exception as exc:
115
+ if not _is_transient(exc) or attempt == max_retries:
116
+ raise
117
+ last_exc = exc
118
+ delay = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
119
+ logger.warning(
120
+ "Retry %d/%d for %s after %s (delay=%.2fs)",
121
+ attempt + 1,
122
+ max_retries,
123
+ fn.__name__ if hasattr(fn, "__name__") else str(fn),
124
+ type(exc).__name__,
125
+ delay,
126
+ )
127
+ time.sleep(delay)
128
+ raise last_exc # type: ignore[misc]
129
+
130
+
131
+ class OpenAIKernel(BaseIntegration):
132
+ """
133
+ OpenAI Assistants adapter for Agent OS.
134
+
135
+ Provides governance for:
136
+ - Assistant creation/modification
137
+ - Thread management
138
+ - Run execution
139
+ - Tool/function calls
140
+ - File operations
141
+
142
+ Example:
143
+ kernel = OpenAIKernel(policy=GovernancePolicy(
144
+ max_tokens=10000,
145
+ allowed_tools=["code_interpreter", "retrieval"],
146
+ blocked_patterns=["password", "api_key", "secret"]
147
+ ))
148
+
149
+ governed = kernel.wrap(assistant, client)
150
+ result = governed.run(thread_id)
151
+ """
152
+
153
+ def __init__(
154
+ self,
155
+ policy: Optional[GovernancePolicy] = None,
156
+ max_retries: int = 3,
157
+ timeout_seconds: float = 300.0,
158
+ ):
159
+ """Initialise the OpenAI governance kernel.
160
+
161
+ Args:
162
+ policy: Governance policy to enforce. When ``None`` the default
163
+ ``GovernancePolicy`` is used.
164
+ max_retries: Maximum number of retry attempts for transient
165
+ OpenAI errors (default 3).
166
+ timeout_seconds: Default timeout in seconds for operations
167
+ (default 300).
168
+ """
169
+ super().__init__(policy)
170
+ self.max_retries = max_retries
171
+ self.timeout_seconds = timeout_seconds
172
+ self._wrapped_assistants: dict[str, Any] = {} # assistant_id -> original
173
+ self._clients: dict[str, Any] = {} # assistant_id -> client
174
+ self._cancelled_runs: set[str] = set()
175
+ self._start_time = time.monotonic()
176
+ self._last_error: Optional[str] = None
177
+
178
+ def wrap(self, agent: Any, client: Any = None) -> "GovernedAssistant":
179
+ """Wrap an OpenAI Assistant with governance.
180
+
181
+ This is the primary wrapping method, consistent with all other
182
+ adapters. OpenAI Assistants require both an assistant object
183
+ **and** a client, so ``client`` must be provided.
184
+
185
+ Args:
186
+ agent: OpenAI Assistant object.
187
+ client: OpenAI client instance (required).
188
+
189
+ Returns:
190
+ GovernedAssistant with full governance.
191
+
192
+ Raises:
193
+ TypeError: If *client* is not provided.
194
+ """
195
+ if client is None:
196
+ raise TypeError(
197
+ "OpenAIKernel.wrap() requires a 'client' argument: "
198
+ "kernel.wrap(assistant, client)"
199
+ )
200
+ assistant_id = agent.id
201
+ ctx = AssistantContext(
202
+ agent_id=assistant_id,
203
+ session_id=f"oai-{int(time.time())}",
204
+ policy=self.policy,
205
+ assistant_id=assistant_id
206
+ )
207
+ self.contexts[assistant_id] = ctx
208
+ self._wrapped_assistants[assistant_id] = agent
209
+ self._clients[assistant_id] = client
210
+
211
+ return GovernedAssistant(
212
+ assistant=agent,
213
+ client=client,
214
+ kernel=self,
215
+ ctx=ctx
216
+ )
217
+
218
+ def wrap_assistant(self, assistant: Any, client: Any) -> "GovernedAssistant":
219
+ """Wrap an OpenAI Assistant with governance.
220
+
221
+ .. deprecated::
222
+ Use :meth:`wrap` instead::
223
+
224
+ governed = kernel.wrap(assistant, client)
225
+
226
+ Args:
227
+ assistant: OpenAI Assistant object.
228
+ client: OpenAI client instance.
229
+
230
+ Returns:
231
+ GovernedAssistant with full governance.
232
+ """
233
+ import warnings
234
+ warnings.warn(
235
+ "wrap_assistant() is deprecated, use wrap(assistant, client) instead.",
236
+ DeprecationWarning,
237
+ stacklevel=2,
238
+ )
239
+ return self.wrap(assistant, client)
240
+
241
+ def unwrap(self, governed_agent: Any) -> Any:
242
+ """Retrieve the original unwrapped assistant.
243
+
244
+ Args:
245
+ governed_agent: A ``GovernedAssistant`` or any object.
246
+
247
+ Returns:
248
+ The original OpenAI assistant object if *governed_agent* is a
249
+ ``GovernedAssistant``; otherwise returns *governed_agent* as-is.
250
+ """
251
+ if isinstance(governed_agent, GovernedAssistant):
252
+ return governed_agent._assistant
253
+ return governed_agent
254
+
255
+ def cancel_run(self, thread_id: str, run_id: str, client: Any):
256
+ """Cancel a run (SIGKILL equivalent).
257
+
258
+ Immediately marks the run as cancelled locally and issues a cancel
259
+ request to the OpenAI API. If the API call fails (e.g. the run
260
+ has already completed), the error is silently ignored.
261
+
262
+ Args:
263
+ thread_id: The thread the run belongs to.
264
+ run_id: The run to cancel.
265
+ client: OpenAI client used to issue the cancellation.
266
+ """
267
+ self._cancelled_runs.add(run_id)
268
+ try:
269
+ client.beta.threads.runs.cancel(
270
+ thread_id=thread_id,
271
+ run_id=run_id
272
+ )
273
+ except Exception: # noqa: BLE001 — best-effort cancel, run may already be complete
274
+ logger.warning("Run cancel failed (may already be complete): thread=%s run=%s", thread_id, run_id, exc_info=True)
275
+
276
+ def is_cancelled(self, run_id: str) -> bool:
277
+ """Check whether a run has been cancelled via :meth:`cancel_run`.
278
+
279
+ Args:
280
+ run_id: The run identifier to check.
281
+
282
+ Returns:
283
+ ``True`` if the run was previously cancelled.
284
+ """
285
+ return run_id in self._cancelled_runs
286
+
287
+ def health_check(self) -> dict[str, Any]:
288
+ """Return adapter health status.
289
+
290
+ Returns:
291
+ A dict with ``status``, ``backend``, ``last_error``, and
292
+ ``uptime_seconds`` keys.
293
+ """
294
+ uptime = time.monotonic() - self._start_time
295
+ has_clients = bool(self._clients)
296
+ if self._last_error:
297
+ status = "degraded"
298
+ elif not has_clients:
299
+ status = "healthy"
300
+ else:
301
+ status = "healthy"
302
+ return {
303
+ "status": status,
304
+ "backend": "openai",
305
+ "backend_connected": has_clients,
306
+ "last_error": self._last_error,
307
+ "uptime_seconds": round(uptime, 2),
308
+ }
309
+
310
+
311
+ class GovernedAssistant:
312
+ """
313
+ OpenAI Assistant wrapped with Agent OS governance.
314
+
315
+ All API calls are intercepted for policy enforcement.
316
+ """
317
+
318
+ def __init__(
319
+ self,
320
+ assistant: Any,
321
+ client: Any,
322
+ kernel: OpenAIKernel,
323
+ ctx: AssistantContext
324
+ ):
325
+ self._assistant = assistant
326
+ self._client = client
327
+ self._kernel = kernel
328
+ self._ctx = ctx
329
+ self._tool_registry: dict[str, Callable] = {}
330
+
331
+ def register_tool(self, name: str, func: Callable) -> None:
332
+ """Register a tool function for automatic execution."""
333
+ self._tool_registry[name] = func
334
+
335
+ @property
336
+ def id(self) -> str:
337
+ """Assistant ID"""
338
+ return self._assistant.id
339
+
340
+ @property
341
+ def name(self) -> str:
342
+ """Assistant name"""
343
+ return self._assistant.name
344
+
345
+ # =========================================================================
346
+ # Thread Management
347
+ # =========================================================================
348
+
349
+ def create_thread(self, **kwargs) -> Any:
350
+ """Create a new conversation thread.
351
+
352
+ The thread ID is automatically recorded in the execution context
353
+ for audit purposes.
354
+
355
+ Args:
356
+ **kwargs: Forwarded to ``client.beta.threads.create()``.
357
+
358
+ Returns:
359
+ The newly created OpenAI thread object.
360
+ """
361
+ thread = self._client.beta.threads.create(**kwargs)
362
+ self._ctx.thread_ids.append(thread.id)
363
+ return thread
364
+
365
+ def get_thread(self, thread_id: str) -> Any:
366
+ """Retrieve an existing thread by ID.
367
+
368
+ Args:
369
+ thread_id: The thread to retrieve.
370
+
371
+ Returns:
372
+ The OpenAI thread object.
373
+ """
374
+ return self._client.beta.threads.retrieve(thread_id)
375
+
376
+ def delete_thread(self, thread_id: str) -> bool:
377
+ """Delete a thread and remove it from the execution context.
378
+
379
+ Args:
380
+ thread_id: The thread to delete.
381
+
382
+ Returns:
383
+ ``True`` if the thread was successfully deleted.
384
+ """
385
+ result = self._client.beta.threads.delete(thread_id)
386
+ if thread_id in self._ctx.thread_ids:
387
+ self._ctx.thread_ids.remove(thread_id)
388
+ return result.deleted
389
+
390
+ # =========================================================================
391
+ # Message Management
392
+ # =========================================================================
393
+
394
+ def add_message(
395
+ self,
396
+ thread_id: str,
397
+ content: str,
398
+ role: str = "user",
399
+ **kwargs
400
+ ) -> Any:
401
+ """Add a message to a thread with pre-execution policy checks.
402
+
403
+ The message content is validated against ``blocked_patterns`` before
404
+ being sent to the OpenAI API.
405
+
406
+ Args:
407
+ thread_id: Target thread.
408
+ content: Message text.
409
+ role: Message role (default ``"user"``).
410
+ **kwargs: Additional parameters forwarded to the API.
411
+
412
+ Returns:
413
+ The created OpenAI message object.
414
+
415
+ Raises:
416
+ PolicyViolationError: If the content matches a blocked pattern.
417
+ """
418
+ # Pre-check: blocked patterns
419
+ allowed, reason = self._kernel.pre_execute(self._ctx, content)
420
+ if not allowed:
421
+ raise PolicyViolationError(f"Message blocked: {reason}")
422
+
423
+ message = self._client.beta.threads.messages.create(
424
+ thread_id=thread_id,
425
+ role=role,
426
+ content=content,
427
+ **kwargs
428
+ )
429
+ return message
430
+
431
+ def list_messages(self, thread_id: str, **kwargs) -> list:
432
+ """List messages in a thread.
433
+
434
+ Args:
435
+ thread_id: The thread whose messages to list.
436
+ **kwargs: Additional parameters (e.g. ``limit``, ``order``).
437
+
438
+ Returns:
439
+ A list of message objects in the thread.
440
+ """
441
+ return self._client.beta.threads.messages.list(
442
+ thread_id=thread_id,
443
+ **kwargs
444
+ )
445
+
446
+ # =========================================================================
447
+ # Run Execution (Core Governance)
448
+ # =========================================================================
449
+
450
+ def run(
451
+ self,
452
+ thread_id: str,
453
+ instructions: Optional[str] = None,
454
+ tools: Optional[list] = None,
455
+ poll_interval: float = 1.0,
456
+ **kwargs
457
+ ) -> Any:
458
+ """
459
+ Execute a governed run.
460
+
461
+ This is the primary method for executing the assistant.
462
+ All tool calls and outputs are validated against policy.
463
+
464
+ Args:
465
+ thread_id: Thread to run on
466
+ instructions: Optional override instructions
467
+ tools: Optional tools to enable
468
+ poll_interval: How often to check run status
469
+ **kwargs: Additional run parameters
470
+
471
+ Returns:
472
+ Completed run object
473
+
474
+ Raises:
475
+ PolicyViolationError: If policy is violated
476
+ RunCancelledException: If run was SIGKILL'd
477
+ """
478
+ # Pre-check
479
+ if instructions:
480
+ allowed, reason = self._kernel.pre_execute(self._ctx, instructions)
481
+ if not allowed:
482
+ raise PolicyViolationError(f"Instructions blocked: {reason}")
483
+
484
+ # Validate tools against policy
485
+ if tools:
486
+ self._validate_tools(tools)
487
+
488
+ # Create run
489
+ run_kwargs = {
490
+ "thread_id": thread_id,
491
+ "assistant_id": self._assistant.id,
492
+ **kwargs
493
+ }
494
+ if instructions:
495
+ run_kwargs["instructions"] = instructions
496
+ if tools:
497
+ run_kwargs["tools"] = tools
498
+
499
+ run = self._client.beta.threads.runs.create(**run_kwargs)
500
+ self._ctx.run_ids.append(run.id)
501
+
502
+ # Poll until complete (with governance checks)
503
+ return self._poll_run(thread_id, run.id, poll_interval)
504
+
505
+ def run_stream(
506
+ self,
507
+ thread_id: str,
508
+ instructions: Optional[str] = None,
509
+ **kwargs
510
+ ) -> Generator:
511
+ """
512
+ Stream a governed run.
513
+
514
+ Yields events as they arrive, with real-time policy checks.
515
+ """
516
+ # Pre-check
517
+ if instructions:
518
+ allowed, reason = self._kernel.pre_execute(self._ctx, instructions)
519
+ if not allowed:
520
+ raise PolicyViolationError(f"Instructions blocked: {reason}")
521
+
522
+ # Create streaming run
523
+ with self._client.beta.threads.runs.stream(
524
+ thread_id=thread_id,
525
+ assistant_id=self._assistant.id,
526
+ instructions=instructions,
527
+ **kwargs
528
+ ) as stream:
529
+ for event in stream:
530
+ # Check for cancellation
531
+ if hasattr(event, 'data') and hasattr(event.data, 'id'):
532
+ if self._kernel.is_cancelled(event.data.id):
533
+ raise RunCancelledException("Run was cancelled (SIGKILL)")
534
+
535
+ # Yield event
536
+ yield event
537
+
538
+ def _poll_run(
539
+ self,
540
+ thread_id: str,
541
+ run_id: str,
542
+ poll_interval: float
543
+ ) -> Any:
544
+ """
545
+ Poll run status with governance checks.
546
+ """
547
+ while True:
548
+ # Check for SIGKILL
549
+ if self._kernel.is_cancelled(run_id):
550
+ raise RunCancelledException("Run was cancelled (SIGKILL)")
551
+
552
+ run = self._client.beta.threads.runs.retrieve(
553
+ thread_id=thread_id,
554
+ run_id=run_id
555
+ )
556
+
557
+ # Update token counts
558
+ if hasattr(run, 'usage') and run.usage:
559
+ self._ctx.prompt_tokens += run.usage.prompt_tokens or 0
560
+ self._ctx.completion_tokens += run.usage.completion_tokens or 0
561
+
562
+ # Check token limit
563
+ total = self._ctx.prompt_tokens + self._ctx.completion_tokens
564
+ if total > self._kernel.policy.max_tokens:
565
+ self._kernel.cancel_run(thread_id, run_id, self._client)
566
+ raise PolicyViolationError(
567
+ f"Token limit exceeded: {total} > {self._kernel.policy.max_tokens}"
568
+ )
569
+
570
+ # Handle different statuses
571
+ if run.status == "completed":
572
+ self._kernel.post_execute(self._ctx, run)
573
+ return run
574
+
575
+ elif run.status == "requires_action":
576
+ # Tool calls need approval
577
+ run = self._handle_tool_calls(thread_id, run)
578
+
579
+ elif run.status in ["failed", "cancelled", "expired"]:
580
+ return run
581
+
582
+ elif run.status in ["queued", "in_progress"]:
583
+ time.sleep(poll_interval)
584
+
585
+ else:
586
+ # Unknown status
587
+ time.sleep(poll_interval)
588
+
589
+ def _handle_tool_calls(self, thread_id: str, run: Any) -> Any:
590
+ """
591
+ Handle tool calls with policy validation.
592
+ """
593
+ tool_calls = run.required_action.submit_tool_outputs.tool_calls
594
+ tool_outputs = []
595
+
596
+ for tool_call in tool_calls:
597
+ # Record tool call
598
+ call_info = {
599
+ "id": tool_call.id,
600
+ "type": tool_call.type,
601
+ "function": tool_call.function.name if hasattr(tool_call, 'function') else None,
602
+ "arguments": tool_call.function.arguments if hasattr(tool_call, 'function') else None,
603
+ "timestamp": datetime.now().isoformat()
604
+ }
605
+ self._ctx.function_calls.append(call_info)
606
+ self._ctx.tool_calls.append(call_info)
607
+
608
+ # Check tool call count
609
+ if len(self._ctx.tool_calls) > self._kernel.policy.max_tool_calls:
610
+ self._kernel.cancel_run(thread_id, run.id, self._client)
611
+ raise PolicyViolationError(
612
+ f"Tool call limit exceeded: {len(self._ctx.tool_calls)} > {self._kernel.policy.max_tool_calls}"
613
+ )
614
+
615
+ # Validate function name
616
+ if hasattr(tool_call, 'function'):
617
+ func_name = tool_call.function.name
618
+ if self._kernel.policy.allowed_tools:
619
+ if func_name not in self._kernel.policy.allowed_tools:
620
+ self._kernel.cancel_run(thread_id, run.id, self._client)
621
+ raise PolicyViolationError(
622
+ f"Tool not allowed: {func_name}"
623
+ )
624
+
625
+ # Check human approval requirement
626
+ if self._kernel.policy.require_human_approval:
627
+ tool_outputs.append({
628
+ "tool_call_id": tool_call.id,
629
+ "output": json.dumps({
630
+ "status": "requires_approval",
631
+ "function": func_name if hasattr(tool_call, 'function') else "unknown",
632
+ "message": "Tool execution requires human approval per governance policy"
633
+ })
634
+ })
635
+ continue
636
+
637
+ # Execute via tool registry if available
638
+ output = None
639
+ if hasattr(self, '_tool_registry') and self._tool_registry:
640
+ func_name_exec = tool_call.function.name if hasattr(tool_call, 'function') else None
641
+ if func_name_exec and func_name_exec in self._tool_registry:
642
+ try:
643
+ args = json.loads(tool_call.function.arguments) if hasattr(tool_call, 'function') else {}
644
+ result = self._tool_registry[func_name_exec](**args)
645
+ output = json.dumps(result) if not isinstance(result, str) else result
646
+ except Exception as e:
647
+ logger.warning("Tool execution failed for %s", func_name_exec, exc_info=True)
648
+ output = json.dumps({"status": "error", "message": str(e)})
649
+
650
+ if output is None:
651
+ output = json.dumps({
652
+ "status": "no_executor",
653
+ "function": tool_call.function.name if hasattr(tool_call, 'function') else "unknown",
654
+ "message": "No tool executor registered for this function"
655
+ })
656
+
657
+ tool_outputs.append({
658
+ "tool_call_id": tool_call.id,
659
+ "output": output
660
+ })
661
+
662
+ # Submit outputs
663
+ return self._client.beta.threads.runs.submit_tool_outputs(
664
+ thread_id=thread_id,
665
+ run_id=run.id,
666
+ tool_outputs=tool_outputs
667
+ )
668
+
669
+ def _validate_tools(self, tools: list):
670
+ """Validate tools against policy"""
671
+ if not self._kernel.policy.allowed_tools:
672
+ return # No restrictions
673
+
674
+ for tool in tools:
675
+ tool_type = tool.get("type") if isinstance(tool, dict) else getattr(tool, "type", None)
676
+ if tool_type and tool_type not in self._kernel.policy.allowed_tools:
677
+ raise PolicyViolationError(f"Tool type not allowed: {tool_type}")
678
+
679
+ # =========================================================================
680
+ # Signal Handling
681
+ # =========================================================================
682
+
683
+ def sigkill(self, thread_id: str, run_id: str):
684
+ """Send SIGKILL to a running assistant — immediately cancels the run.
685
+
686
+ This is the primary mechanism for forcibly stopping a governed
687
+ assistant that has violated policy or needs emergency termination.
688
+
689
+ Args:
690
+ thread_id: Thread containing the run.
691
+ run_id: The run to kill.
692
+
693
+ Example:
694
+ >>> governed.sigkill(thread_id="thread_abc", run_id="run_xyz")
695
+ """
696
+ self._kernel.cancel_run(thread_id, run_id, self._client)
697
+
698
+ def sigstop(self, thread_id: str, run_id: str):
699
+ """Send SIGSTOP to a running assistant.
700
+
701
+ .. note::
702
+
703
+ The OpenAI Assistants API does not support pausing a run, so
704
+ this behaves identically to :meth:`sigkill` (cancels the run).
705
+
706
+ Args:
707
+ thread_id: Thread containing the run.
708
+ run_id: The run to stop.
709
+ """
710
+ self._kernel.cancel_run(thread_id, run_id, self._client)
711
+
712
+ # =========================================================================
713
+ # Utility
714
+ # =========================================================================
715
+
716
+ def get_context(self) -> AssistantContext:
717
+ """Return the execution context containing the full audit trail.
718
+
719
+ Returns:
720
+ The ``AssistantContext`` for this governed assistant, including
721
+ thread IDs, run IDs, function call history, and token usage.
722
+ """
723
+ return self._ctx
724
+
725
+ def get_token_usage(self) -> dict:
726
+ """Return cumulative token usage statistics.
727
+
728
+ Returns:
729
+ A dict with keys ``prompt_tokens``, ``completion_tokens``,
730
+ ``total_tokens``, and ``limit``.
731
+
732
+ Example:
733
+ >>> governed.get_token_usage()
734
+ {'prompt_tokens': 120, 'completion_tokens': 80, 'total_tokens': 200, 'limit': 10000}
735
+ """
736
+ return {
737
+ "prompt_tokens": self._ctx.prompt_tokens,
738
+ "completion_tokens": self._ctx.completion_tokens,
739
+ "total_tokens": self._ctx.prompt_tokens + self._ctx.completion_tokens,
740
+ "limit": self._kernel.policy.max_tokens
741
+ }
742
+
743
+ def __getattr__(self, name):
744
+ """Proxy attribute access to the underlying OpenAI assistant.
745
+
746
+ Allows transparent access to assistant properties (e.g. ``model``,
747
+ ``instructions``) that are not explicitly overridden by this wrapper.
748
+ """
749
+ return getattr(self._assistant, name)
750
+
751
+
752
+ class PolicyViolationError(Exception):
753
+ """Raised when an assistant action violates governance policy.
754
+
755
+ Contains a human-readable reason describing which policy was violated.
756
+ """
757
+
758
+ pass
759
+
760
+
761
+ class RunCancelledException(Exception):
762
+ """Raised when a run is forcibly cancelled via SIGKILL.
763
+
764
+ Indicates that ``cancel_run`` (or ``sigkill``) was invoked, either
765
+ directly or automatically by the governance layer (e.g. token limit
766
+ exceeded, disallowed tool call).
767
+ """
768
+
769
+ pass
770
+
771
+
772
+ # ============================================================================
773
+ # Convenience Functions
774
+ # ============================================================================
775
+
776
+ def wrap(
777
+ assistant: Any,
778
+ client: Any,
779
+ policy: Optional[GovernancePolicy] = None,
780
+ max_retries: int = 3,
781
+ timeout_seconds: float = 300.0,
782
+ ) -> GovernedAssistant:
783
+ """Quick wrapper for OpenAI Assistants.
784
+
785
+ Example::
786
+
787
+ from agent_os.integrations.openai_adapter import wrap
788
+
789
+ governed = wrap(my_assistant, openai_client)
790
+ result = governed.run(thread_id)
791
+ """
792
+ return OpenAIKernel(
793
+ policy, max_retries=max_retries, timeout_seconds=timeout_seconds
794
+ ).wrap(assistant, client)
795
+
796
+
797
+ def wrap_assistant(
798
+ assistant: Any,
799
+ client: Any,
800
+ policy: Optional[GovernancePolicy] = None,
801
+ max_retries: int = 3,
802
+ timeout_seconds: float = 300.0,
803
+ ) -> GovernedAssistant:
804
+ """Quick wrapper for OpenAI Assistants.
805
+
806
+ .. deprecated::
807
+ Use :func:`wrap` instead.
808
+ """
809
+ import warnings
810
+ warnings.warn(
811
+ "wrap_assistant() is deprecated, use wrap() instead.",
812
+ DeprecationWarning,
813
+ stacklevel=2,
814
+ )
815
+ return wrap(assistant, client, policy=policy, max_retries=max_retries,
816
+ timeout_seconds=timeout_seconds)