agent_os_kernel 3.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. agent_control_plane/__init__.py +662 -0
  2. agent_control_plane/a2a_adapter.py +543 -0
  3. agent_control_plane/adapter.py +417 -0
  4. agent_control_plane/agent_hibernation.py +394 -0
  5. agent_control_plane/agent_kernel.py +470 -0
  6. agent_control_plane/compliance.py +720 -0
  7. agent_control_plane/constraint_graphs.py +478 -0
  8. agent_control_plane/control_plane.py +854 -0
  9. agent_control_plane/example_executors.py +195 -0
  10. agent_control_plane/execution_engine.py +231 -0
  11. agent_control_plane/flight_recorder.py +846 -0
  12. agent_control_plane/governance_layer.py +435 -0
  13. agent_control_plane/hf_utils.py +563 -0
  14. agent_control_plane/interfaces/__init__.py +55 -0
  15. agent_control_plane/interfaces/kernel_interface.py +361 -0
  16. agent_control_plane/interfaces/plugin_interface.py +497 -0
  17. agent_control_plane/interfaces/protocol_interfaces.py +387 -0
  18. agent_control_plane/kernel_space.py +1009 -0
  19. agent_control_plane/langchain_adapter.py +424 -0
  20. agent_control_plane/lifecycle.py +3113 -0
  21. agent_control_plane/mcp_adapter.py +653 -0
  22. agent_control_plane/ml_safety.py +563 -0
  23. agent_control_plane/multimodal.py +727 -0
  24. agent_control_plane/mute_agent.py +422 -0
  25. agent_control_plane/observability.py +787 -0
  26. agent_control_plane/orchestrator.py +482 -0
  27. agent_control_plane/plugin_registry.py +750 -0
  28. agent_control_plane/policy_engine.py +954 -0
  29. agent_control_plane/process_isolation.py +777 -0
  30. agent_control_plane/shadow_mode.py +310 -0
  31. agent_control_plane/signals.py +493 -0
  32. agent_control_plane/supervisor_agents.py +430 -0
  33. agent_control_plane/time_travel_debugger.py +557 -0
  34. agent_control_plane/tool_registry.py +452 -0
  35. agent_control_plane/vfs.py +697 -0
  36. agent_kernel/__init__.py +69 -0
  37. agent_kernel/analyzer.py +435 -0
  38. agent_kernel/auditor.py +36 -0
  39. agent_kernel/completeness_auditor.py +237 -0
  40. agent_kernel/detector.py +203 -0
  41. agent_kernel/kernel.py +744 -0
  42. agent_kernel/memory_manager.py +85 -0
  43. agent_kernel/models.py +374 -0
  44. agent_kernel/nudge_mechanism.py +263 -0
  45. agent_kernel/outcome_analyzer.py +338 -0
  46. agent_kernel/patcher.py +582 -0
  47. agent_kernel/semantic_analyzer.py +316 -0
  48. agent_kernel/semantic_purge.py +349 -0
  49. agent_kernel/simulator.py +449 -0
  50. agent_kernel/teacher.py +85 -0
  51. agent_kernel/triage.py +152 -0
  52. agent_os/__init__.py +409 -0
  53. agent_os/_adversarial_impl.py +200 -0
  54. agent_os/_circuit_breaker_impl.py +232 -0
  55. agent_os/_mcp_metrics.py +193 -0
  56. agent_os/adversarial.py +20 -0
  57. agent_os/agents_compat.py +490 -0
  58. agent_os/audit_logger.py +135 -0
  59. agent_os/base_agent.py +651 -0
  60. agent_os/circuit_breaker.py +34 -0
  61. agent_os/cli/__init__.py +659 -0
  62. agent_os/cli/cmd_audit.py +128 -0
  63. agent_os/cli/cmd_init.py +152 -0
  64. agent_os/cli/cmd_policy.py +41 -0
  65. agent_os/cli/cmd_policy_gen.py +180 -0
  66. agent_os/cli/cmd_validate.py +258 -0
  67. agent_os/cli/mcp_scan.py +265 -0
  68. agent_os/cli/output.py +192 -0
  69. agent_os/cli/policy_checker.py +330 -0
  70. agent_os/compat.py +74 -0
  71. agent_os/constraint_graph.py +234 -0
  72. agent_os/content_governance.py +140 -0
  73. agent_os/context_budget.py +305 -0
  74. agent_os/credential_redactor.py +224 -0
  75. agent_os/diff_policy.py +89 -0
  76. agent_os/egress_policy.py +159 -0
  77. agent_os/escalation.py +276 -0
  78. agent_os/event_bus.py +124 -0
  79. agent_os/exceptions.py +180 -0
  80. agent_os/execution_context_policy.py +141 -0
  81. agent_os/github_enterprise.py +96 -0
  82. agent_os/health.py +20 -0
  83. agent_os/integrations/__init__.py +279 -0
  84. agent_os/integrations/a2a_adapter.py +279 -0
  85. agent_os/integrations/agent_lightning/__init__.py +30 -0
  86. agent_os/integrations/anthropic_adapter.py +420 -0
  87. agent_os/integrations/autogen_adapter.py +620 -0
  88. agent_os/integrations/base.py +1137 -0
  89. agent_os/integrations/compat.py +229 -0
  90. agent_os/integrations/config.py +98 -0
  91. agent_os/integrations/conversation_guardian.py +957 -0
  92. agent_os/integrations/crewai_adapter.py +467 -0
  93. agent_os/integrations/drift_detector.py +425 -0
  94. agent_os/integrations/dry_run.py +124 -0
  95. agent_os/integrations/escalation.py +582 -0
  96. agent_os/integrations/gemini_adapter.py +364 -0
  97. agent_os/integrations/google_adk_adapter.py +633 -0
  98. agent_os/integrations/guardrails_adapter.py +394 -0
  99. agent_os/integrations/health.py +197 -0
  100. agent_os/integrations/langchain_adapter.py +654 -0
  101. agent_os/integrations/llamafirewall.py +343 -0
  102. agent_os/integrations/llamaindex_adapter.py +188 -0
  103. agent_os/integrations/logging.py +191 -0
  104. agent_os/integrations/maf_adapter.py +631 -0
  105. agent_os/integrations/mistral_adapter.py +365 -0
  106. agent_os/integrations/openai_adapter.py +816 -0
  107. agent_os/integrations/openai_agents_sdk.py +406 -0
  108. agent_os/integrations/policy_compose.py +171 -0
  109. agent_os/integrations/profiling.py +144 -0
  110. agent_os/integrations/pydantic_ai_adapter.py +420 -0
  111. agent_os/integrations/rate_limiter.py +130 -0
  112. agent_os/integrations/rbac.py +143 -0
  113. agent_os/integrations/registry.py +113 -0
  114. agent_os/integrations/scope_guard.py +303 -0
  115. agent_os/integrations/semantic_kernel_adapter.py +769 -0
  116. agent_os/integrations/smolagents_adapter.py +629 -0
  117. agent_os/integrations/templates.py +178 -0
  118. agent_os/integrations/token_budget.py +134 -0
  119. agent_os/integrations/tool_aliases.py +190 -0
  120. agent_os/integrations/webhooks.py +177 -0
  121. agent_os/lite.py +208 -0
  122. agent_os/mcp_gateway.py +385 -0
  123. agent_os/mcp_message_signer.py +273 -0
  124. agent_os/mcp_protocols.py +161 -0
  125. agent_os/mcp_response_scanner.py +232 -0
  126. agent_os/mcp_security.py +924 -0
  127. agent_os/mcp_session_auth.py +231 -0
  128. agent_os/mcp_sliding_rate_limiter.py +184 -0
  129. agent_os/memory_guard.py +409 -0
  130. agent_os/metrics.py +134 -0
  131. agent_os/mute.py +428 -0
  132. agent_os/mute_agent.py +209 -0
  133. agent_os/policies/__init__.py +77 -0
  134. agent_os/policies/async_evaluator.py +275 -0
  135. agent_os/policies/backends.py +670 -0
  136. agent_os/policies/bridge.py +169 -0
  137. agent_os/policies/budget.py +85 -0
  138. agent_os/policies/cli.py +294 -0
  139. agent_os/policies/conflict_resolution.py +270 -0
  140. agent_os/policies/data_classification.py +252 -0
  141. agent_os/policies/evaluator.py +239 -0
  142. agent_os/policies/policy_schema.json +228 -0
  143. agent_os/policies/rate_limiting.py +145 -0
  144. agent_os/policies/schema.py +115 -0
  145. agent_os/policies/shared.py +331 -0
  146. agent_os/prompt_injection.py +694 -0
  147. agent_os/providers.py +182 -0
  148. agent_os/py.typed +0 -0
  149. agent_os/retry.py +81 -0
  150. agent_os/reversibility.py +251 -0
  151. agent_os/sandbox.py +432 -0
  152. agent_os/sandbox_provider.py +140 -0
  153. agent_os/secure_codegen.py +525 -0
  154. agent_os/security_skills.py +538 -0
  155. agent_os/semantic_policy.py +422 -0
  156. agent_os/server/__init__.py +15 -0
  157. agent_os/server/__main__.py +25 -0
  158. agent_os/server/app.py +277 -0
  159. agent_os/server/models.py +104 -0
  160. agent_os/shift_left_metrics.py +130 -0
  161. agent_os/stateless.py +742 -0
  162. agent_os/supervisor.py +148 -0
  163. agent_os/task_outcome.py +148 -0
  164. agent_os/transparency.py +181 -0
  165. agent_os/trust_root.py +128 -0
  166. agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
  167. agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
  168. agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
  169. agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
  170. agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
  171. agent_os_observability/__init__.py +27 -0
  172. agent_os_observability/dashboards.py +898 -0
  173. agent_os_observability/metrics.py +398 -0
  174. agent_os_observability/server.py +223 -0
  175. agent_os_observability/tracer.py +232 -0
  176. agent_primitives/__init__.py +24 -0
  177. agent_primitives/failures.py +84 -0
  178. agent_primitives/py.typed +0 -0
  179. amb_core/__init__.py +177 -0
  180. amb_core/adapters/__init__.py +57 -0
  181. amb_core/adapters/aws_sqs_broker.py +376 -0
  182. amb_core/adapters/azure_servicebus_broker.py +340 -0
  183. amb_core/adapters/kafka_broker.py +260 -0
  184. amb_core/adapters/nats_broker.py +285 -0
  185. amb_core/adapters/rabbitmq_broker.py +235 -0
  186. amb_core/adapters/redis_broker.py +262 -0
  187. amb_core/broker.py +145 -0
  188. amb_core/bus.py +481 -0
  189. amb_core/cloudevents.py +509 -0
  190. amb_core/dlq.py +345 -0
  191. amb_core/hf_utils.py +536 -0
  192. amb_core/memory_broker.py +410 -0
  193. amb_core/models.py +141 -0
  194. amb_core/persistence.py +529 -0
  195. amb_core/schema.py +294 -0
  196. amb_core/tracing.py +358 -0
  197. atr/__init__.py +640 -0
  198. atr/access.py +348 -0
  199. atr/composition.py +645 -0
  200. atr/decorator.py +357 -0
  201. atr/executor.py +384 -0
  202. atr/health.py +557 -0
  203. atr/hf_utils.py +449 -0
  204. atr/injection.py +422 -0
  205. atr/metrics.py +440 -0
  206. atr/policies.py +403 -0
  207. atr/py.typed +2 -0
  208. atr/registry.py +452 -0
  209. atr/schema.py +480 -0
  210. atr/tools/safe/__init__.py +75 -0
  211. atr/tools/safe/calculator.py +467 -0
  212. atr/tools/safe/datetime_tool.py +443 -0
  213. atr/tools/safe/file_reader.py +402 -0
  214. atr/tools/safe/http_client.py +316 -0
  215. atr/tools/safe/json_parser.py +374 -0
  216. atr/tools/safe/text_tool.py +537 -0
  217. atr/tools/safe/toolkit.py +175 -0
  218. caas/__init__.py +162 -0
  219. caas/api/__init__.py +7 -0
  220. caas/api/server.py +1328 -0
  221. caas/caching.py +834 -0
  222. caas/cli.py +210 -0
  223. caas/conversation.py +223 -0
  224. caas/decay.py +72 -0
  225. caas/detection/__init__.py +9 -0
  226. caas/detection/detector.py +238 -0
  227. caas/enrichment.py +130 -0
  228. caas/gateway/__init__.py +27 -0
  229. caas/gateway/trust_gateway.py +474 -0
  230. caas/hf_utils.py +479 -0
  231. caas/ingestion/__init__.py +23 -0
  232. caas/ingestion/processors.py +253 -0
  233. caas/ingestion/structure_parser.py +188 -0
  234. caas/models.py +356 -0
  235. caas/pragmatic_truth.py +444 -0
  236. caas/routing/__init__.py +10 -0
  237. caas/routing/heuristic_router.py +58 -0
  238. caas/storage/__init__.py +9 -0
  239. caas/storage/store.py +389 -0
  240. caas/triad.py +213 -0
  241. caas/tuning/__init__.py +9 -0
  242. caas/tuning/tuner.py +329 -0
  243. caas/vfs/__init__.py +14 -0
  244. caas/vfs/filesystem.py +452 -0
  245. cmvk/__init__.py +218 -0
  246. cmvk/audit.py +402 -0
  247. cmvk/benchmarks.py +478 -0
  248. cmvk/constitutional.py +904 -0
  249. cmvk/hf_utils.py +301 -0
  250. cmvk/metrics.py +473 -0
  251. cmvk/profiles.py +300 -0
  252. cmvk/py.typed +0 -0
  253. cmvk/types.py +12 -0
  254. cmvk/verification.py +956 -0
  255. emk/__init__.py +89 -0
  256. emk/causal.py +352 -0
  257. emk/hf_utils.py +421 -0
  258. emk/indexer.py +83 -0
  259. emk/py.typed +0 -0
  260. emk/schema.py +204 -0
  261. emk/sleep_cycle.py +347 -0
  262. emk/store.py +281 -0
  263. iatp/__init__.py +166 -0
  264. iatp/attestation.py +461 -0
  265. iatp/cli.py +317 -0
  266. iatp/hf_utils.py +472 -0
  267. iatp/ipc_pipes.py +580 -0
  268. iatp/main.py +412 -0
  269. iatp/models/__init__.py +447 -0
  270. iatp/policy_engine.py +337 -0
  271. iatp/py.typed +2 -0
  272. iatp/recovery.py +321 -0
  273. iatp/security/__init__.py +270 -0
  274. iatp/sidecar/__init__.py +519 -0
  275. iatp/telemetry/__init__.py +164 -0
  276. iatp/tests/__init__.py +1 -0
  277. iatp/tests/test_attestation.py +370 -0
  278. iatp/tests/test_cli.py +131 -0
  279. iatp/tests/test_ed25519_attestation.py +211 -0
  280. iatp/tests/test_models.py +130 -0
  281. iatp/tests/test_policy_engine.py +347 -0
  282. iatp/tests/test_recovery.py +281 -0
  283. iatp/tests/test_security.py +222 -0
  284. iatp/tests/test_sidecar.py +167 -0
  285. iatp/tests/test_telemetry.py +175 -0
  286. mcp_kernel_server/__init__.py +28 -0
  287. mcp_kernel_server/cli.py +274 -0
  288. mcp_kernel_server/resources.py +217 -0
  289. mcp_kernel_server/server.py +564 -0
  290. mcp_kernel_server/tools.py +1174 -0
  291. mute_agent/__init__.py +68 -0
  292. mute_agent/core/__init__.py +1 -0
  293. mute_agent/core/execution_agent.py +166 -0
  294. mute_agent/core/handshake_protocol.py +201 -0
  295. mute_agent/core/reasoning_agent.py +238 -0
  296. mute_agent/knowledge_graph/__init__.py +1 -0
  297. mute_agent/knowledge_graph/graph_elements.py +65 -0
  298. mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
  299. mute_agent/knowledge_graph/subgraph.py +224 -0
  300. mute_agent/listener/__init__.py +43 -0
  301. mute_agent/listener/adapters/__init__.py +31 -0
  302. mute_agent/listener/adapters/base_adapter.py +189 -0
  303. mute_agent/listener/adapters/caas_adapter.py +344 -0
  304. mute_agent/listener/adapters/control_plane_adapter.py +436 -0
  305. mute_agent/listener/adapters/iatp_adapter.py +332 -0
  306. mute_agent/listener/adapters/scak_adapter.py +251 -0
  307. mute_agent/listener/listener.py +610 -0
  308. mute_agent/listener/state_observer.py +436 -0
  309. mute_agent/listener/threshold_config.py +313 -0
  310. mute_agent/super_system/__init__.py +1 -0
  311. mute_agent/super_system/router.py +204 -0
  312. mute_agent/visualization/__init__.py +10 -0
  313. mute_agent/visualization/graph_debugger.py +502 -0
  314. nexus/README.md +60 -0
  315. nexus/__init__.py +51 -0
  316. nexus/arbiter.py +359 -0
  317. nexus/client.py +466 -0
  318. nexus/dmz.py +444 -0
  319. nexus/escrow.py +430 -0
  320. nexus/exceptions.py +286 -0
  321. nexus/pyproject.toml +36 -0
  322. nexus/registry.py +393 -0
  323. nexus/reputation.py +425 -0
  324. nexus/schemas/__init__.py +51 -0
  325. nexus/schemas/compliance.py +276 -0
  326. nexus/schemas/escrow.py +251 -0
  327. nexus/schemas/manifest.py +225 -0
  328. nexus/schemas/receipt.py +208 -0
  329. nexus/tests/__init__.py +0 -0
  330. nexus/tests/conftest.py +146 -0
  331. nexus/tests/test_arbiter.py +192 -0
  332. nexus/tests/test_dmz.py +194 -0
  333. nexus/tests/test_escrow.py +276 -0
  334. nexus/tests/test_exceptions.py +225 -0
  335. nexus/tests/test_registry.py +232 -0
  336. nexus/tests/test_reputation.py +328 -0
  337. nexus/tests/test_schemas.py +295 -0
agent_os/stateless.py ADDED
@@ -0,0 +1,742 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Stateless Kernel — June 2026 MCP-compliant design.
4
+
5
+ This module implements a fully stateless execution kernel that complies with
6
+ the Model Context Protocol (MCP) specification targeted for June 2026. The
7
+ stateless architecture enables horizontal scaling: any kernel instance can
8
+ handle any request because no session state is stored in-process.
9
+
10
+ Architecture overview:
11
+ ┌──────────────┐ ┌────────────────┐ ┌──────────────┐
12
+ │ Client / │────▶│ StatelessKernel │────▶│ StateBackend │
13
+ │ MCP Host │◀────│ (any instance) │◀────│ (Redis, etc) │
14
+ └──────────────┘ └────────────────┘ └──────────────┘
15
+
16
+ Key design principles:
17
+ - **No session state in kernel**: Every request carries its own
18
+ ``ExecutionContext`` with agent identity, policy list, and history.
19
+ - **All context passed per request**: The kernel never looks up prior
20
+ requests; the caller is responsible for threading context.
21
+ - **Pluggable state backends**: State that must persist (e.g. agent
22
+ working memory) is stored in an external backend implementing the
23
+ ``StateBackend`` protocol. Built-in backends:
24
+
25
+ - ``MemoryBackend``: In-memory dict with TTL support (dev/test only).
26
+ - ``RedisBackend``: Production-grade backend with connection pooling,
27
+ configurable timeouts, and optional ``RedisConfig``.
28
+
29
+ - **Horizontally scalable**: Because kernels are stateless, you can
30
+ run N replicas behind a load balancer with no sticky sessions.
31
+
32
+ State serialization format:
33
+ All state values are serialized as JSON via ``json.dumps`` / ``json.loads``.
34
+ Keys are prefixed with a configurable namespace (default ``"agent-os:"``)
35
+ to avoid collisions in shared Redis instances. A ``SerializationError``
36
+ is raised if a value cannot be round-tripped through JSON.
37
+
38
+ Resilience:
39
+ Backend calls are wrapped in a circuit breaker (see ``CircuitBreaker``)
40
+ that opens after repeated failures, preventing cascade failures when
41
+ the backend is unavailable.
42
+
43
+ Observability:
44
+ When OpenTelemetry is installed, the kernel emits spans for every
45
+ ``execute()`` call and backend operation, annotated with action name,
46
+ agent ID, and backend type.
47
+
48
+ Example:
49
+ >>> from agent_os.stateless import StatelessKernel, ExecutionContext
50
+ >>> kernel = StatelessKernel()
51
+ >>> ctx = ExecutionContext(agent_id="a1", policies=["read_only"])
52
+ >>> result = await kernel.execute("database_query", {"query": "SELECT 1"}, ctx)
53
+ >>> assert result.success
54
+ """
55
+
56
+ from __future__ import annotations
57
+
58
+ import hashlib
59
+ import json
60
+ import logging
61
+ import time
62
+ from dataclasses import dataclass, field
63
+ from datetime import datetime, timezone
64
+ from typing import Any, Protocol
65
+
66
+ from agent_os.circuit_breaker import CircuitBreaker, CircuitBreakerConfig, CircuitBreakerOpen
67
+ from agent_os.exceptions import SerializationError
68
+
69
+ logger = logging.getLogger(__name__)
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # Optional OpenTelemetry support
73
+ # Design decision: OTel is opt-in to avoid adding a hard dependency.
74
+ # When present, every kernel.execute() and backend call emits a trace span
75
+ # so operators can correlate latency across services.
76
+ # ---------------------------------------------------------------------------
77
+ try:
78
+ from opentelemetry import context as _otel_context
79
+ from opentelemetry import trace as _otel_trace
80
+
81
+ _HAS_OTEL = True
82
+ except ImportError: # pragma: no cover
83
+ _otel_trace = None # type: ignore[assignment]
84
+ _otel_context = None # type: ignore[assignment]
85
+ _HAS_OTEL = False
86
+
87
+
88
+ # =============================================================================
89
+ # State Backend Protocol
90
+ # Design decision: Using typing.Protocol (structural subtyping) instead of
91
+ # an ABC so that any object with get/set/delete methods satisfies the
92
+ # contract without explicit inheritance. This makes it easy to adapt
93
+ # third-party clients (e.g. DynamoDB, Cosmos DB) as backends.
94
+ # =============================================================================
95
+
96
+ class StateBackend(Protocol):
97
+ """Protocol for external state storage.
98
+
99
+ Any object implementing ``get``, ``set``, and ``delete`` as async
100
+ methods satisfies this protocol via structural subtyping — no
101
+ explicit inheritance required.
102
+
103
+ All values are JSON-serializable dictionaries. Keys are plain strings
104
+ (the backend may add its own prefix for namespacing).
105
+
106
+ Args:
107
+ key: A unique string identifying the state entry.
108
+ value: A JSON-serializable dictionary to store.
109
+ ttl: Optional time-to-live in seconds. After expiry the entry
110
+ should be treated as deleted.
111
+ """
112
+
113
+ async def get(self, key: str) -> dict[str, Any] | None:
114
+ """Get state by key."""
115
+ ...
116
+
117
+ async def set(self, key: str, value: dict[str, Any], ttl: int | None = None) -> None:
118
+ """Set state with optional TTL."""
119
+ ...
120
+
121
+ async def delete(self, key: str) -> None:
122
+ """Delete state."""
123
+ ...
124
+
125
+
126
+ class MemoryBackend:
127
+ """In-memory state backend for testing and development.
128
+
129
+ Stores state as ``{key: (value_dict, expires_at)}`` tuples in a plain
130
+ Python dictionary. TTL expiry is checked lazily on ``get()``; expired
131
+ entries are removed on access rather than via a background sweep.
132
+
133
+ Warning:
134
+ Not suitable for production. State is lost on process restart and
135
+ is not shared across kernel replicas. Use ``RedisBackend`` for
136
+ production deployments.
137
+ """
138
+
139
+ def __init__(self) -> None:
140
+ # Store maps key -> (value_dict, optional_expiry_monotonic_time).
141
+ # Using monotonic clock for TTL avoids issues with wall-clock jumps.
142
+ self._store: dict[str, tuple[dict[str, Any], float | None]] = {}
143
+ self._debug = False
144
+
145
+ async def get(self, key: str) -> dict[str, Any] | None:
146
+ entry = self._store.get(key)
147
+ if entry is None:
148
+ return None
149
+ value, expires_at = entry
150
+ if expires_at is not None and time.monotonic() >= expires_at:
151
+ del self._store[key]
152
+ return None
153
+ return value
154
+
155
+ async def set(self, key: str, value: dict[str, Any], ttl: int | None = None) -> None:
156
+ expires_at = (time.monotonic() + ttl) if ttl is not None else None
157
+ self._store[key] = (value, expires_at)
158
+
159
+ async def delete(self, key: str) -> None:
160
+ self._store.pop(key, None)
161
+
162
+
163
+ @dataclass
164
+ class RedisConfig:
165
+ """Configuration for Redis connection pooling and timeouts.
166
+
167
+ Args:
168
+ host: Redis server hostname.
169
+ port: Redis server port.
170
+ db: Redis database number.
171
+ password: Optional authentication password.
172
+ pool_size: Maximum number of connections in the pool.
173
+ connect_timeout: Timeout in seconds for establishing a connection.
174
+ read_timeout: Timeout in seconds for reading a response.
175
+ retry_on_timeout: Whether to retry commands that time out.
176
+ """
177
+
178
+ host: str = "localhost"
179
+ port: int = 6379
180
+ db: int = 0
181
+ password: str | None = None
182
+ pool_size: int = 10
183
+ connect_timeout: float = 5.0
184
+ read_timeout: float = 10.0
185
+ retry_on_timeout: bool = True
186
+
187
+ def to_url(self) -> str:
188
+ """Build a Redis URL from host/port/db."""
189
+ auth = f":{self.password}@" if self.password else ""
190
+ return f"redis://{auth}{self.host}:{self.port}/{self.db}"
191
+
192
+
193
+ class RedisBackend:
194
+ """Redis state backend (for production).
195
+
196
+ Supports connection pooling and configurable timeouts via ``RedisConfig``.
197
+ When no config is provided the legacy ``url`` parameter is used with
198
+ default timeout/pool behaviour for backward compatibility.
199
+ """
200
+
201
+ def __init__(
202
+ self,
203
+ url: str = "redis://localhost:6379",
204
+ key_prefix: str = "agent-os:",
205
+ config: RedisConfig | None = None,
206
+ ):
207
+ if not isinstance(key_prefix, str):
208
+ raise TypeError(f"key_prefix must be str, got {type(key_prefix).__name__}")
209
+ self._config = config
210
+ self.url = config.to_url() if config else url
211
+ self._client = None
212
+ self._pool = None
213
+ self._prefix = key_prefix
214
+
215
+ async def _get_client(self):
216
+ if self._client is None:
217
+ import redis.asyncio as aioredis
218
+
219
+ if self._config is not None:
220
+ self._pool = aioredis.ConnectionPool.from_url(
221
+ self.url,
222
+ max_connections=self._config.pool_size,
223
+ socket_connect_timeout=self._config.connect_timeout,
224
+ socket_timeout=self._config.read_timeout,
225
+ retry_on_timeout=self._config.retry_on_timeout,
226
+ )
227
+ self._client = aioredis.Redis(connection_pool=self._pool)
228
+ else:
229
+ self._client = aioredis.from_url(self.url)
230
+ return self._client
231
+
232
+ async def get(self, key: str) -> dict[str, Any] | None:
233
+ client = await self._get_client()
234
+ data = await client.get(f"{self._prefix}{key}")
235
+ if not data:
236
+ return None
237
+ try:
238
+ return json.loads(data)
239
+ except (json.JSONDecodeError, TypeError) as exc:
240
+ logger.error(
241
+ "Deserialization failed: key=%s error=%s",
242
+ key,
243
+ str(exc),
244
+ )
245
+ raise SerializationError(
246
+ f"Failed to deserialize state for key '{key}': {exc}",
247
+ details={"key": key, "original_error": str(exc)},
248
+ ) from exc
249
+
250
+ async def set(self, key: str, value: dict[str, Any], ttl: int | None = None) -> None:
251
+ client = await self._get_client()
252
+ try:
253
+ serialized = json.dumps(value)
254
+ except (TypeError, ValueError) as exc:
255
+ logger.error(
256
+ "Serialization failed: key=%s value_type=%s error=%s",
257
+ key,
258
+ type(value).__name__,
259
+ str(exc),
260
+ )
261
+ raise SerializationError(
262
+ f"Failed to serialize state for key '{key}': {exc}",
263
+ details={
264
+ "key": key,
265
+ "value_type": type(value).__name__,
266
+ "original_error": str(exc),
267
+ },
268
+ ) from exc
269
+ await client.set(f"{self._prefix}{key}", serialized, ex=ttl)
270
+
271
+ async def delete(self, key: str) -> None:
272
+ client = await self._get_client()
273
+ await client.delete(f"{self._prefix}{key}")
274
+
275
+
276
+ # =============================================================================
277
+ # Stateless Request/Response Types
278
+ # Design decision: Using dataclasses (not Pydantic) for request/response
279
+ # types to keep the core kernel dependency-free. Pydantic is used in the
280
+ # integrations layer where richer validation is needed.
281
+ # =============================================================================
282
+
283
+ @dataclass
284
+ class ExecutionContext:
285
+ """Complete context for a stateless execution request.
286
+
287
+ All state needed for a request is passed here — the kernel never
288
+ maintains session state internally. Callers are responsible for
289
+ threading the ``updated_context`` from one ``ExecutionResult`` into
290
+ the next request to maintain conversational continuity.
291
+
292
+ Args:
293
+ agent_id: Unique identifier of the requesting agent.
294
+ policies: List of policy names to enforce (e.g. ``["read_only"]``).
295
+ Policy definitions are resolved from ``StatelessKernel.policies``.
296
+ history: Chronological list of previous actions in this session,
297
+ each a dict with ``action``, ``timestamp``, and ``success`` keys.
298
+ state_ref: Optional key referencing externalized state in the
299
+ backend. When present, the kernel loads this state before
300
+ execution and persists updates afterward.
301
+ metadata: Arbitrary metadata passed through to the result.
302
+ """
303
+ agent_id: str
304
+ policies: list[str] = field(default_factory=list)
305
+ history: list[dict[str, Any]] = field(default_factory=list)
306
+ state_ref: str | None = None # Reference to external state
307
+ metadata: dict[str, Any] = field(default_factory=dict)
308
+
309
+ def to_dict(self) -> dict[str, Any]:
310
+ return {
311
+ "agent_id": self.agent_id,
312
+ "policies": self.policies,
313
+ "history": self.history,
314
+ "state_ref": self.state_ref,
315
+ "metadata": self.metadata
316
+ }
317
+
318
+
319
+ @dataclass
320
+ class ExecutionRequest:
321
+ """Internal representation of a stateless execution request.
322
+
323
+ Created by ``StatelessKernel.execute()`` from the caller-supplied
324
+ action, params, and context. The ``request_id`` is auto-generated as
325
+ a truncated SHA-256 hash to enable correlation in logs without
326
+ requiring the caller to supply an ID.
327
+ """
328
+ action: str
329
+ params: dict[str, Any]
330
+ context: ExecutionContext
331
+ request_id: str | None = None
332
+
333
+ def __post_init__(self):
334
+ if self.request_id is None:
335
+ self.request_id = hashlib.sha256(
336
+ f"{self.context.agent_id}:{self.action}:{datetime.now(timezone.utc).isoformat()}".encode()
337
+ ).hexdigest()[:16]
338
+
339
+
340
+ @dataclass
341
+ class ExecutionResult:
342
+ """Result of a stateless kernel execution.
343
+
344
+ Attributes:
345
+ success: ``True`` if the action completed without policy violation
346
+ or execution error.
347
+ data: The action's return value (arbitrary type). ``None`` on
348
+ failure.
349
+ error: Human-readable error message when ``success`` is ``False``.
350
+ signal: Kernel signal emitted on failure — ``"SIGKILL"`` for policy
351
+ violations, ``"SIGTERM"`` for execution errors.
352
+ updated_context: A new ``ExecutionContext`` reflecting the latest
353
+ history and state reference. Callers should use this as the
354
+ context for subsequent requests.
355
+ metadata: Request metadata including ``request_id`` and timestamp.
356
+ """
357
+ success: bool
358
+ data: Any
359
+ error: str | None = None
360
+ signal: str | None = None
361
+ updated_context: ExecutionContext | None = None
362
+ metadata: dict[str, Any] = field(default_factory=dict)
363
+
364
+
365
+ # =============================================================================
366
+ # Stateless Kernel
367
+ # Design decision: The kernel is intentionally thin — it delegates policy
368
+ # checking, state persistence, and action execution to composable
369
+ # components. This keeps the kernel testable and allows swapping backends
370
+ # or policy engines without changing core logic.
371
+ # =============================================================================
372
+
373
+ class StatelessKernel:
374
+ """
375
+ Stateless kernel for MCP June 2026 compliance.
376
+
377
+ Design principles:
378
+ - Every request is self-contained
379
+ - State stored in external backend
380
+ - Kernel can run on any instance (horizontal scaling)
381
+ - No agent registration required
382
+
383
+ Usage:
384
+ kernel = StatelessKernel(backend=RedisBackend())
385
+
386
+ result = await kernel.execute(
387
+ action="database_query",
388
+ params={"query": "SELECT * FROM users"},
389
+ context=ExecutionContext(
390
+ agent_id="analyst-001",
391
+ policies=["read_only", "no_pii"]
392
+ )
393
+ )
394
+ """
395
+
396
+ # Default policy rules
397
+ DEFAULT_POLICIES = {
398
+ "read_only": {
399
+ "blocked_actions": ["file_write", "database_write", "send_email"],
400
+ "constraints": {"database_query": {"mode": "read"}}
401
+ },
402
+ "no_pii": {
403
+ "blocked_patterns": ["ssn", "social_security", "credit_card", "password"]
404
+ },
405
+ "strict": {
406
+ "require_approval": ["send_email", "file_write", "code_execution"]
407
+ }
408
+ }
409
+
410
+ def __init__(
411
+ self,
412
+ backend: StateBackend | None = None,
413
+ policies: dict[str, Any] | None = None,
414
+ enable_tracing: bool = False,
415
+ circuit_breaker_config: CircuitBreakerConfig | None = None,
416
+ ):
417
+ self.backend = backend or MemoryBackend()
418
+ self.policies = {**self.DEFAULT_POLICIES, **(policies or {})}
419
+ self.enable_tracing = enable_tracing and _HAS_OTEL
420
+ self._tracer = (
421
+ _otel_trace.get_tracer("agent_os.stateless") if self.enable_tracing else None
422
+ )
423
+ self._backend_type = type(self.backend).__name__
424
+ self.circuit_breaker = CircuitBreaker(circuit_breaker_config)
425
+
426
+ async def execute(
427
+ self,
428
+ action: str,
429
+ params: dict[str, Any],
430
+ context: ExecutionContext
431
+ ) -> ExecutionResult:
432
+ """
433
+ Execute an action statelessly with full policy governance.
434
+
435
+ This is the main entry point. Every request is self-contained:
436
+ policies are checked, the action is executed, state is updated
437
+ externally, and an updated context is returned.
438
+
439
+ Args:
440
+ action: Action to execute (e.g., "database_query", "file_write", "chat")
441
+ params: Action parameters (passed to handler and checked against policies)
442
+ context: Complete execution context including agent_id, policies, and history
443
+
444
+ Returns:
445
+ ExecutionResult with:
446
+ - success=True, data=result, updated_context (on success)
447
+ - success=False, error=reason, signal="SIGKILL" (on policy violation)
448
+ - success=False, error=str(e), signal="SIGTERM" (on execution error)
449
+
450
+ Example:
451
+ >>> result = await kernel.execute(
452
+ ... action="database_query",
453
+ ... params={"query": "SELECT * FROM users"},
454
+ ... context=ExecutionContext(agent_id="a1", policies=["read_only"])
455
+ ... )
456
+ >>> if result.success:
457
+ ... print(result.data)
458
+ ... else:
459
+ ... print(f"Blocked: {result.error}")
460
+ """
461
+ request = ExecutionRequest(action=action, params=params, context=context)
462
+
463
+ span_ctx = self._start_span("kernel.execute", {
464
+ "operation": "execute",
465
+ "action": action,
466
+ "agent_id": context.agent_id,
467
+ "backend_type": self._backend_type,
468
+ })
469
+ try:
470
+ return await self._execute_inner(request, action, params, context)
471
+ finally:
472
+ self._end_span(span_ctx)
473
+
474
+ async def _execute_inner(
475
+ self,
476
+ request: ExecutionRequest,
477
+ action: str,
478
+ params: dict[str, Any],
479
+ context: ExecutionContext,
480
+ ) -> ExecutionResult:
481
+ """Core execute logic, called inside an optional tracing span."""
482
+ # 1. Load external state if referenced
483
+ external_state: dict[str, Any] = {}
484
+ if context.state_ref:
485
+ external_state = await self._backend_get(context.state_ref) or {}
486
+
487
+ # 2. Check policies
488
+ policy_result = self._check_policies(action, params, context.policies)
489
+ if not policy_result["allowed"]:
490
+ return ExecutionResult(
491
+ success=False,
492
+ data=None,
493
+ error=policy_result["reason"],
494
+ signal="SIGKILL",
495
+ metadata={
496
+ "request_id": request.request_id,
497
+ "violation": policy_result["reason"],
498
+ "timestamp": datetime.now(timezone.utc).isoformat()
499
+ }
500
+ )
501
+
502
+ # 3. Execute action
503
+ try:
504
+ result = await self._execute_action(action, params, external_state)
505
+ except Exception as e:
506
+ return ExecutionResult(
507
+ success=False,
508
+ data=None,
509
+ error=str(e),
510
+ signal="SIGTERM",
511
+ metadata={"request_id": request.request_id}
512
+ )
513
+
514
+ # 4. Update external state if needed
515
+ new_state_ref = context.state_ref
516
+ if result.get("state_update"):
517
+ new_state = {**external_state, **result["state_update"]}
518
+ new_state_ref = new_state_ref or f"state:{context.agent_id}"
519
+ await self._backend_set(new_state_ref, new_state)
520
+
521
+ # 5. Build updated context
522
+ updated_context = ExecutionContext(
523
+ agent_id=context.agent_id,
524
+ policies=context.policies,
525
+ history=context.history + [{
526
+ "action": action,
527
+ "timestamp": datetime.now(timezone.utc).isoformat(),
528
+ "success": True
529
+ }],
530
+ state_ref=new_state_ref,
531
+ metadata=context.metadata
532
+ )
533
+
534
+ return ExecutionResult(
535
+ success=True,
536
+ data=result.get("data"),
537
+ updated_context=updated_context,
538
+ metadata={
539
+ "request_id": request.request_id,
540
+ "timestamp": datetime.now(timezone.utc).isoformat()
541
+ }
542
+ )
543
+
544
+ def _check_policies(
545
+ self,
546
+ action: str,
547
+ params: dict[str, Any],
548
+ policy_names: list[str]
549
+ ) -> dict[str, Any]:
550
+ """Check if action is allowed under policies.
551
+
552
+ Args:
553
+ action: The action being attempted (e.g., "database_query", "file_write")
554
+ params: Parameters for the action
555
+ policy_names: List of policy names to check against
556
+
557
+ Returns:
558
+ Dict with 'allowed' (bool) and 'reason' (str) keys.
559
+ When blocked, includes 'suggestion' with actionable fix.
560
+ """
561
+ for policy_name in policy_names:
562
+ policy = self.policies.get(policy_name)
563
+ if not policy:
564
+ continue
565
+
566
+ # Check blocked actions
567
+ if action in policy.get("blocked_actions", []):
568
+ allowed_actions = [a for a in ["read", "query", "list"]
569
+ if a not in policy.get("blocked_actions", [])]
570
+ suggestion = (f"Try a read-only action instead (e.g., {', '.join(allowed_actions[:3])})"
571
+ if allowed_actions else "Request policy exception from administrator")
572
+ return {
573
+ "allowed": False,
574
+ "reason": f"Action '{action}' blocked by '{policy_name}' policy. {suggestion}."
575
+ }
576
+
577
+ # Check blocked patterns in params
578
+ params_str = json.dumps(params).lower()
579
+ for pattern in policy.get("blocked_patterns", []):
580
+ if pattern.lower() in params_str:
581
+ return {
582
+ "allowed": False,
583
+ "reason": (
584
+ f"Content blocked: '{pattern}' detected in request parameters. "
585
+ f"Policy '{policy_name}' prohibits this pattern. "
586
+ f"Remove the sensitive content and retry."
587
+ )
588
+ }
589
+
590
+ # Check requires approval
591
+ if action in policy.get("require_approval", []):
592
+ if not params.get("approved"):
593
+ return {
594
+ "allowed": False,
595
+ "reason": (
596
+ f"Action '{action}' requires approval. "
597
+ f"Add approved=True to params after getting authorization, "
598
+ f"or use a non-restricted action instead."
599
+ )
600
+ }
601
+
602
+ return {"allowed": True, "reason": None}
603
+
604
+ async def _execute_action(
605
+ self,
606
+ action: str,
607
+ params: dict[str, Any],
608
+ state: dict[str, Any]
609
+ ) -> dict[str, Any]:
610
+ """Execute action (stub - real impl dispatches to handlers)."""
611
+ return {
612
+ "data": {
613
+ "status": "executed",
614
+ "action": action,
615
+ "result": f"Action '{action}' executed successfully"
616
+ }
617
+ }
618
+
619
+ # -----------------------------------------------------------------
620
+ # Backend wrappers (circuit breaker + tracing)
621
+ # Design decision: All backend calls go through the circuit breaker
622
+ # to prevent cascading failures when the backend (e.g. Redis) is
623
+ # down. The breaker opens after repeated failures and returns
624
+ # CircuitBreakerOpen without hitting the backend, giving it time
625
+ # to recover.
626
+ # -----------------------------------------------------------------
627
+
628
+ async def _backend_get(self, key: str) -> dict[str, Any] | None:
629
+ """Get from backend through circuit breaker with tracing."""
630
+ span_ctx = self._start_span("kernel.backend.get", {
631
+ "operation": "get",
632
+ "key": key,
633
+ "backend_type": self._backend_type,
634
+ })
635
+ try:
636
+ return await self.circuit_breaker.call(self.backend.get, key)
637
+ except CircuitBreakerOpen:
638
+ raise
639
+ finally:
640
+ self._end_span(span_ctx)
641
+
642
+ async def _backend_set(
643
+ self, key: str, value: dict[str, Any], ttl: int | None = None
644
+ ) -> None:
645
+ """Set in backend through circuit breaker with tracing."""
646
+ span_ctx = self._start_span("kernel.backend.set", {
647
+ "operation": "set",
648
+ "key": key,
649
+ "backend_type": self._backend_type,
650
+ })
651
+ try:
652
+ await self.circuit_breaker.call(self.backend.set, key, value, ttl)
653
+ except CircuitBreakerOpen:
654
+ raise
655
+ finally:
656
+ self._end_span(span_ctx)
657
+
658
+ async def _backend_delete(self, key: str) -> None:
659
+ """Delete from backend through circuit breaker with tracing."""
660
+ span_ctx = self._start_span("kernel.backend.delete", {
661
+ "operation": "delete",
662
+ "key": key,
663
+ "backend_type": self._backend_type,
664
+ })
665
+ try:
666
+ await self.circuit_breaker.call(self.backend.delete, key)
667
+ except CircuitBreakerOpen:
668
+ raise
669
+ finally:
670
+ self._end_span(span_ctx)
671
+
672
+ # -----------------------------------------------------------------
673
+ # OpenTelemetry helpers
674
+ # -----------------------------------------------------------------
675
+
676
+ def _start_span(
677
+ self, name: str, attributes: dict[str, str]
678
+ ) -> Any | None:
679
+ """Start an OTel span if tracing is enabled. Returns a context token."""
680
+ if not self._tracer:
681
+ return None
682
+ span = self._tracer.start_span(name, attributes=attributes)
683
+ ctx = _otel_trace.set_span_in_context(span)
684
+ token = _otel_context.attach(ctx)
685
+ return (span, token)
686
+
687
+ @staticmethod
688
+ def _end_span(span_ctx: Any | None) -> None:
689
+ """End the OTel span if present."""
690
+ if span_ctx is None:
691
+ return
692
+ span, token = span_ctx
693
+ span.end()
694
+ _otel_context.detach(token)
695
+
696
+
697
+ # =============================================================================
698
+ # Helper Functions
699
+ # =============================================================================
700
+
701
+ async def stateless_execute(
702
+ action: str,
703
+ params: dict,
704
+ agent_id: str,
705
+ policies: list[str] | None = None,
706
+ history: list[dict] | None = None,
707
+ backend: StateBackend | None = None
708
+ ) -> ExecutionResult:
709
+ """Convenience function for one-shot stateless execution.
710
+
711
+ Creates an ephemeral ``StatelessKernel`` and ``ExecutionContext``,
712
+ executes the action, and returns the result. Useful for simple
713
+ scripts and tests where managing a kernel instance is unnecessary.
714
+
715
+ Args:
716
+ action: Action to execute (e.g. ``"database_query"``).
717
+ params: Action parameters.
718
+ agent_id: Identifier of the requesting agent.
719
+ policies: Policy names to enforce. Defaults to ``[]``.
720
+ history: Prior action history. Defaults to ``[]``.
721
+ backend: Optional ``StateBackend``. Defaults to ``MemoryBackend``.
722
+
723
+ Returns:
724
+ An ``ExecutionResult`` with the outcome of the action.
725
+
726
+ Example:
727
+ >>> result = await stateless_execute(
728
+ ... action="database_query",
729
+ ... params={"query": "SELECT * FROM users"},
730
+ ... agent_id="analyst-001",
731
+ ... policies=["read_only"],
732
+ ... )
733
+ >>> print(result.success)
734
+ True
735
+ """
736
+ kernel = StatelessKernel(backend=backend)
737
+ context = ExecutionContext(
738
+ agent_id=agent_id,
739
+ policies=policies or [],
740
+ history=history or []
741
+ )
742
+ return await kernel.execute(action, params, context)