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,410 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """In-memory broker adapter for testing and simple use cases."""
4
+
5
+ import asyncio
6
+ import heapq
7
+ import uuid
8
+ from collections import defaultdict
9
+ from typing import Dict, List, Optional, Set, Tuple
10
+
11
+ from amb_core.broker import BrokerAdapter, MessageHandler
12
+ from amb_core.models import Message, MessagePriority
13
+
14
+ # Priority mapping for heapq (lower number = higher priority)
15
+ PRIORITY_ORDER = {
16
+ MessagePriority.CRITICAL: 0,
17
+ MessagePriority.URGENT: 1,
18
+ MessagePriority.HIGH: 2,
19
+ MessagePriority.NORMAL: 3,
20
+ MessagePriority.LOW: 4,
21
+ MessagePriority.BACKGROUND: 5,
22
+ }
23
+
24
+
25
+ class InMemoryBroker(BrokerAdapter):
26
+ """
27
+ In-memory broker implementation for testing and simple use cases.
28
+
29
+ This broker stores messages in memory and uses anyio for async handling.
30
+ It's suitable for testing, development, and single-process applications.
31
+
32
+ Features:
33
+ - Backpressure: Automatically slows down producers when consumers are overwhelmed
34
+ - Priority lanes: CRITICAL messages jump ahead of BACKGROUND messages
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ max_queue_size: int = 1000,
40
+ backpressure_threshold: float = 0.8,
41
+ backpressure_delay: float = 0.01,
42
+ use_priority_delivery: bool = True
43
+ ):
44
+ """
45
+ Initialize the in-memory broker.
46
+
47
+ Args:
48
+ max_queue_size: Maximum messages per topic before backpressure kicks in
49
+ backpressure_threshold: Queue fill percentage (0.0-1.0) that triggers backpressure
50
+ backpressure_delay: Delay in seconds when backpressure is active
51
+ use_priority_delivery: If True, deliver messages in priority order (slower but respects priority lanes)
52
+ """
53
+ self._connected = False
54
+ self._subscriptions: Dict[str, Dict[str, MessageHandler]] = defaultdict(dict)
55
+
56
+ # Priority queue: List of tuples (priority_value, counter, message)
57
+ self._message_queues: Dict[str, List[Tuple[int, int, Message]]] = defaultdict(list)
58
+ self._message_counter = 0 # For stable sorting within same priority
59
+
60
+ self._response_queues: Dict[str, asyncio.Queue] = {}
61
+ self._request_message_ids: Set[str] = set() # Track request message IDs to avoid self-capture
62
+ self._tasks: Set[asyncio.Task] = set()
63
+
64
+ # Backpressure configuration
65
+ self._max_queue_size = max_queue_size
66
+ self._backpressure_threshold = backpressure_threshold
67
+ self._backpressure_delay = backpressure_delay
68
+ self._use_priority_delivery = use_priority_delivery
69
+
70
+ # Statistics for monitoring
71
+ self._backpressure_events: Dict[str, int] = defaultdict(int)
72
+
73
+ # Background processing tasks for priority delivery
74
+ self._delivery_tasks: Dict[str, asyncio.Task] = {}
75
+
76
+ async def connect(self) -> None:
77
+ """Establish connection (no-op for in-memory broker)."""
78
+ self._connected = True
79
+
80
+ async def disconnect(self) -> None:
81
+ """Close connection and cancel all tasks."""
82
+ self._connected = False
83
+
84
+ # Cancel all delivery tasks
85
+ for task in self._delivery_tasks.values():
86
+ if not task.done():
87
+ task.cancel()
88
+
89
+ if self._delivery_tasks:
90
+ await asyncio.gather(*self._delivery_tasks.values(), return_exceptions=True)
91
+
92
+ # Cancel all running tasks
93
+ for task in self._tasks:
94
+ if not task.done():
95
+ task.cancel()
96
+
97
+ if self._tasks:
98
+ await asyncio.gather(*self._tasks, return_exceptions=True)
99
+
100
+ self._tasks.clear()
101
+ self._delivery_tasks.clear()
102
+ self._subscriptions.clear()
103
+ self._message_queues.clear()
104
+ self._response_queues.clear()
105
+ self._request_message_ids.clear()
106
+ self._backpressure_events.clear()
107
+
108
+ def _check_backpressure(self, topic: str) -> bool:
109
+ """
110
+ Check if backpressure should be applied for a topic.
111
+
112
+ Args:
113
+ topic: The topic to check
114
+
115
+ Returns:
116
+ True if backpressure should be applied
117
+ """
118
+ queue_size = len(self._message_queues[topic])
119
+ threshold = int(self._max_queue_size * self._backpressure_threshold)
120
+ return queue_size >= threshold
121
+
122
+ async def _apply_backpressure(self, topic: str) -> None:
123
+ """
124
+ Apply backpressure delay to slow down the producer.
125
+
126
+ Args:
127
+ topic: The topic experiencing backpressure
128
+ """
129
+ self._backpressure_events[topic] += 1
130
+ await asyncio.sleep(self._backpressure_delay)
131
+
132
+ async def _priority_delivery_worker(self, topic: str) -> None:
133
+ """
134
+ Background worker that delivers messages in priority order.
135
+
136
+ Args:
137
+ topic: The topic to process
138
+ """
139
+ try:
140
+ while self._connected:
141
+ # Check if there are messages to deliver
142
+ if not self._message_queues[topic]:
143
+ await asyncio.sleep(0.001) # Small delay to avoid busy waiting
144
+ continue
145
+
146
+ # Get highest priority message
147
+ _, _, message = heapq.heappop(self._message_queues[topic])
148
+
149
+ # Check if this is a response message
150
+ is_response = (
151
+ message.correlation_id
152
+ and message.correlation_id in self._response_queues
153
+ and message.id not in self._request_message_ids
154
+ )
155
+
156
+ # Deliver to handlers (unless it's a response message)
157
+ if not is_response:
158
+ handlers = self._subscriptions.get(topic, {})
159
+ for handler in handlers.values():
160
+ task = asyncio.create_task(handler(message))
161
+ self._tasks.add(task)
162
+ task.add_done_callback(self._tasks.discard)
163
+
164
+ # Handle response messages
165
+ if is_response:
166
+ await self._response_queues[message.correlation_id].put(message)
167
+
168
+ except asyncio.CancelledError:
169
+ # Worker cancelled during shutdown
170
+ pass
171
+
172
+ async def publish(self, message: Message, wait_for_confirmation: bool = False) -> Optional[str]:
173
+ """
174
+ Publish a message to all subscribers of the topic.
175
+
176
+ Implements backpressure: If the queue is near capacity, the publisher
177
+ is automatically slowed down to prevent overwhelming consumers.
178
+
179
+ Implements priority lanes: CRITICAL messages are processed before
180
+ BACKGROUND messages, allowing urgent tasks to jump the queue.
181
+
182
+ Args:
183
+ message: The message to publish
184
+ wait_for_confirmation: If True, wait for handlers to process
185
+
186
+ Returns:
187
+ Message ID
188
+ """
189
+ if not self._connected:
190
+ raise ConnectionError("Broker not connected")
191
+
192
+ topic = message.topic
193
+
194
+ # Apply backpressure if queue is getting full (reactive streams flow control)
195
+ if self._check_backpressure(topic):
196
+ await self._apply_backpressure(topic)
197
+
198
+ # Check if queue is at max capacity
199
+ if len(self._message_queues[topic]) >= self._max_queue_size:
200
+ # Drop oldest BACKGROUND message, or raise error if no BACKGROUND messages
201
+ dropped = self._drop_background_message(topic)
202
+ if not dropped:
203
+ raise RuntimeError(
204
+ f"Queue for topic '{topic}' is full ({self._max_queue_size} messages). "
205
+ "Producer is overwhelmed. Consider increasing queue size or adding more consumers."
206
+ )
207
+
208
+ # Add message to priority queue
209
+ priority_value = PRIORITY_ORDER.get(message.priority, 3)
210
+ self._message_counter += 1
211
+ heapq.heappush(
212
+ self._message_queues[topic],
213
+ (priority_value, self._message_counter, message)
214
+ )
215
+
216
+ # Start priority delivery worker if not already running
217
+ if self._use_priority_delivery and topic not in self._delivery_tasks:
218
+ task = asyncio.create_task(self._priority_delivery_worker(topic))
219
+ self._delivery_tasks[topic] = task
220
+
221
+ # Check if this is a response message for request-response pattern
222
+ is_response = (
223
+ message.correlation_id
224
+ and message.correlation_id in self._response_queues
225
+ and message.id not in self._request_message_ids
226
+ )
227
+
228
+ # For non-priority mode or when waiting for confirmation, deliver immediately
229
+ if not self._use_priority_delivery:
230
+ # Deliver to subscribers (skip if this is a response message)
231
+ if not is_response:
232
+ handlers = self._subscriptions.get(topic, {})
233
+
234
+ if wait_for_confirmation:
235
+ # Wait for all handlers to complete
236
+ tasks = []
237
+ for handler in handlers.values():
238
+ tasks.append(handler(message))
239
+
240
+ if tasks:
241
+ await asyncio.gather(*tasks, return_exceptions=True)
242
+ else:
243
+ # Fire and forget - schedule handlers without waiting
244
+ for handler in handlers.values():
245
+ task = asyncio.create_task(handler(message))
246
+ self._tasks.add(task)
247
+ task.add_done_callback(self._tasks.discard)
248
+
249
+ # Handle request-response pattern
250
+ # Capture response messages in the response queue
251
+ if is_response:
252
+ await self._response_queues[message.correlation_id].put(message)
253
+
254
+ return message.id
255
+
256
+ def _drop_background_message(self, topic: str) -> bool:
257
+ """
258
+ Drop the oldest BACKGROUND priority message from the queue.
259
+
260
+ Args:
261
+ topic: The topic to drop from
262
+
263
+ Returns:
264
+ True if a message was dropped, False if no BACKGROUND messages found
265
+ """
266
+ queue = self._message_queues[topic]
267
+ background_priority = PRIORITY_ORDER[MessagePriority.BACKGROUND]
268
+
269
+ # Find index of oldest BACKGROUND message (largest counter value for background priority)
270
+ background_idx = None
271
+ max_counter = -1
272
+
273
+ for i, (priority, counter, _msg) in enumerate(queue):
274
+ if priority == background_priority and counter > max_counter:
275
+ background_idx = i
276
+ max_counter = counter
277
+
278
+ if background_idx is not None:
279
+ # Remove the item and re-heapify
280
+ queue[background_idx] = queue[-1]
281
+ queue.pop()
282
+ if background_idx < len(queue):
283
+ heapq.heapify(queue)
284
+ return True
285
+
286
+ return False
287
+
288
+ async def subscribe(self, topic: str, handler: MessageHandler) -> str:
289
+ """
290
+ Subscribe to a topic.
291
+
292
+ Args:
293
+ topic: Topic to subscribe to
294
+ handler: Message handler function
295
+
296
+ Returns:
297
+ Subscription ID
298
+ """
299
+ if not self._connected:
300
+ raise ConnectionError("Broker not connected")
301
+
302
+ subscription_id = str(uuid.uuid4())
303
+ self._subscriptions[topic][subscription_id] = handler
304
+ return subscription_id
305
+
306
+ async def unsubscribe(self, subscription_id: str) -> None:
307
+ """
308
+ Unsubscribe from a topic.
309
+
310
+ Args:
311
+ subscription_id: The subscription ID
312
+ """
313
+ for topic_handlers in self._subscriptions.values():
314
+ if subscription_id in topic_handlers:
315
+ del topic_handlers[subscription_id]
316
+ return
317
+
318
+ async def request(self, message: Message, timeout: float = 30.0) -> Message:
319
+ """
320
+ Send a request and wait for response.
321
+
322
+ Args:
323
+ message: Request message
324
+ timeout: Timeout in seconds
325
+
326
+ Returns:
327
+ Response message
328
+
329
+ Raises:
330
+ TimeoutError: If timeout exceeded
331
+ """
332
+ if not self._connected:
333
+ raise ConnectionError("Broker not connected")
334
+
335
+ # Generate correlation ID if not present
336
+ if not message.correlation_id:
337
+ message.correlation_id = str(uuid.uuid4())
338
+
339
+ # Set up reply queue
340
+ reply_queue: asyncio.Queue = asyncio.Queue()
341
+ self._response_queues[message.correlation_id] = reply_queue
342
+
343
+ # Mark this message ID as a request to avoid self-capture
344
+ self._request_message_ids.add(message.id)
345
+
346
+ try:
347
+ # Publish the request
348
+ await self.publish(message, wait_for_confirmation=False)
349
+
350
+ # Wait for response
351
+ try:
352
+ response = await asyncio.wait_for(reply_queue.get(), timeout=timeout)
353
+ return response
354
+ except asyncio.TimeoutError:
355
+ raise TimeoutError(f"No response received within {timeout} seconds")
356
+
357
+ finally:
358
+ # Clean up
359
+ if message.correlation_id in self._response_queues:
360
+ del self._response_queues[message.correlation_id]
361
+ if message.id in self._request_message_ids:
362
+ self._request_message_ids.discard(message.id)
363
+
364
+ async def get_pending_messages(self, topic: str, limit: int = 10) -> List[Message]:
365
+ """
366
+ Get pending messages from a topic (in priority order).
367
+
368
+ Args:
369
+ topic: Topic to get messages from
370
+ limit: Maximum number of messages
371
+
372
+ Returns:
373
+ List of messages (highest priority first)
374
+ """
375
+ if not self._connected:
376
+ raise ConnectionError("Broker not connected")
377
+
378
+ queue = self._message_queues.get(topic, [])
379
+
380
+ # Sort by priority and return top N messages
381
+ sorted_messages = sorted(queue, key=lambda x: (x[0], x[1]))
382
+ messages = [item[2] for item in sorted_messages[:limit]]
383
+
384
+ return messages
385
+
386
+ def get_backpressure_stats(self, topic: Optional[str] = None) -> Dict[str, int]:
387
+ """
388
+ Get backpressure statistics.
389
+
390
+ Args:
391
+ topic: Optional topic to get stats for. If None, returns all topics.
392
+
393
+ Returns:
394
+ Dictionary mapping topics to backpressure event counts
395
+ """
396
+ if topic:
397
+ return {topic: self._backpressure_events.get(topic, 0)}
398
+ return dict(self._backpressure_events)
399
+
400
+ def get_queue_size(self, topic: str) -> int:
401
+ """
402
+ Get current queue size for a topic.
403
+
404
+ Args:
405
+ topic: The topic to check
406
+
407
+ Returns:
408
+ Number of messages in the queue
409
+ """
410
+ return len(self._message_queues.get(topic, []))
amb_core/models.py ADDED
@@ -0,0 +1,141 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Core message models for AMB."""
4
+
5
+ from enum import Enum, IntEnum
6
+ from typing import Any, Dict, Optional
7
+ from datetime import datetime, timezone
8
+ from pydantic import BaseModel, Field, ConfigDict, field_validator
9
+
10
+
11
+ class MessagePriority(IntEnum):
12
+ """
13
+ Message priority levels.
14
+
15
+ Higher values indicate higher priority. Messages with higher priority
16
+ are processed before lower priority messages when queued.
17
+ """
18
+ BACKGROUND = 0 # Lowest priority for background tasks
19
+ LOW = 1
20
+ NORMAL = 5
21
+ HIGH = 8
22
+ URGENT = 10
23
+ CRITICAL = 15 # For system-critical messages like fraud alerts
24
+
25
+
26
+ class Priority:
27
+ """
28
+ Convenience class for accessing priority levels.
29
+
30
+ Example:
31
+ message = Message(payload=data, priority=Priority.HIGH)
32
+ """
33
+ BACKGROUND = MessagePriority.BACKGROUND
34
+ LOW = MessagePriority.LOW
35
+ NORMAL = MessagePriority.NORMAL
36
+ HIGH = MessagePriority.HIGH
37
+ URGENT = MessagePriority.URGENT
38
+ CRITICAL = MessagePriority.CRITICAL
39
+
40
+
41
+ class MessageStatus(str, Enum):
42
+ """Status of a message in its lifecycle."""
43
+ PENDING = "pending"
44
+ DELIVERED = "delivered"
45
+ ACKNOWLEDGED = "acknowledged"
46
+ FAILED = "failed"
47
+ EXPIRED = "expired"
48
+ DLQ = "dlq" # Moved to dead letter queue
49
+
50
+
51
+ class Message(BaseModel):
52
+ """
53
+ Core message model for the Agent Message Bus.
54
+
55
+ This model represents a message that can be sent through the bus.
56
+ It includes metadata for routing, tracking, handling, and distributed tracing.
57
+
58
+ New in v0.2.0:
59
+ - trace_id: For distributed tracing across agents
60
+ - ttl_seconds: Alias for ttl for clearer API
61
+ - is_expired: Property to check if message has expired
62
+ """
63
+
64
+ id: str = Field(..., description="Unique message identifier")
65
+ topic: str = Field(..., description="Message topic/channel")
66
+ payload: Dict[str, Any] = Field(default_factory=dict, description="Message payload")
67
+ priority: MessagePriority = Field(default=MessagePriority.NORMAL, description="Message priority")
68
+
69
+ # Metadata
70
+ sender: Optional[str] = Field(None, description="Sender identifier")
71
+ correlation_id: Optional[str] = Field(None, description="Correlation ID for request-response patterns")
72
+ reply_to: Optional[str] = Field(None, description="Topic to reply to")
73
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), description="Message timestamp")
74
+
75
+ # TTL and expiration
76
+ ttl: Optional[int] = Field(None, description="Time to live in seconds", alias="ttl_seconds")
77
+
78
+ # Distributed tracing (AMB-004)
79
+ trace_id: Optional[str] = Field(None, description="Distributed trace ID for tracking message flow")
80
+ span_id: Optional[str] = Field(None, description="Span ID within the trace")
81
+ parent_span_id: Optional[str] = Field(None, description="Parent span ID for nested operations")
82
+
83
+ # Additional metadata
84
+ metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
85
+
86
+ model_config = ConfigDict(
87
+ json_encoders={
88
+ datetime: lambda v: v.isoformat()
89
+ },
90
+ populate_by_name=True, # Allow both 'ttl' and 'ttl_seconds'
91
+ )
92
+
93
+ @field_validator('priority', mode='before')
94
+ @classmethod
95
+ def validate_priority(cls, v):
96
+ """Accept both int and MessagePriority."""
97
+ if isinstance(v, int):
98
+ return MessagePriority(v)
99
+ if isinstance(v, str):
100
+ return MessagePriority[v.upper()]
101
+ return v
102
+
103
+ @property
104
+ def ttl_seconds(self) -> Optional[int]:
105
+ """Alias for ttl for clearer API."""
106
+ return self.ttl
107
+
108
+ @property
109
+ def is_expired(self) -> bool:
110
+ """
111
+ Check if the message has expired based on TTL.
112
+
113
+ Returns:
114
+ True if the message has exceeded its TTL, False otherwise
115
+ """
116
+ if self.ttl is None:
117
+ return False
118
+
119
+ now = datetime.now(timezone.utc)
120
+ age_seconds = (now - self.timestamp).total_seconds()
121
+ return age_seconds > self.ttl
122
+
123
+ @property
124
+ def age_seconds(self) -> float:
125
+ """Get the age of the message in seconds."""
126
+ now = datetime.now(timezone.utc)
127
+ return (now - self.timestamp).total_seconds()
128
+
129
+ @property
130
+ def remaining_ttl(self) -> Optional[float]:
131
+ """
132
+ Get remaining TTL in seconds.
133
+
134
+ Returns:
135
+ Remaining TTL, 0 if expired, None if no TTL set
136
+ """
137
+ if self.ttl is None:
138
+ return None
139
+
140
+ remaining = self.ttl - self.age_seconds
141
+ return max(0, remaining)