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
amb_core/schema.py ADDED
@@ -0,0 +1,294 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Schema validation for AMB messages.
4
+
5
+ This module provides schema registry and validation capabilities
6
+ to ensure message payloads conform to expected schemas.
7
+ """
8
+
9
+ from abc import ABC, abstractmethod
10
+ from typing import Any, Dict, Optional, Type, Union, Callable
11
+ from pydantic import BaseModel, ValidationError
12
+ import json
13
+
14
+
15
+ class SchemaValidationError(Exception):
16
+ """Raised when message schema validation fails."""
17
+
18
+ def __init__(self, topic: str, errors: list, payload: Dict[str, Any]):
19
+ self.topic = topic
20
+ self.errors = errors
21
+ self.payload = payload
22
+ super().__init__(f"Schema validation failed for topic '{topic}': {errors}")
23
+
24
+
25
+ class Schema(ABC):
26
+ """Abstract base class for message schemas."""
27
+
28
+ @abstractmethod
29
+ def validate(self, payload: Dict[str, Any]) -> Dict[str, Any]:
30
+ """
31
+ Validate a payload against the schema.
32
+
33
+ Args:
34
+ payload: The payload to validate
35
+
36
+ Returns:
37
+ The validated payload (potentially coerced/normalized)
38
+
39
+ Raises:
40
+ SchemaValidationError: If validation fails
41
+ """
42
+ pass
43
+
44
+
45
+ class PydanticSchema(Schema):
46
+ """Schema backed by a Pydantic model."""
47
+
48
+ def __init__(self, model: Type[BaseModel]):
49
+ """
50
+ Initialize with a Pydantic model.
51
+
52
+ Args:
53
+ model: Pydantic model class to use for validation
54
+ """
55
+ self._model = model
56
+
57
+ def validate(self, payload: Dict[str, Any]) -> Dict[str, Any]:
58
+ """Validate payload using Pydantic model."""
59
+ try:
60
+ validated = self._model.model_validate(payload)
61
+ return validated.model_dump()
62
+ except ValidationError as e:
63
+ raise SchemaValidationError(
64
+ topic="unknown",
65
+ errors=e.errors(),
66
+ payload=payload
67
+ )
68
+
69
+
70
+ class DictSchema(Schema):
71
+ """Schema based on a dictionary specification with type checking."""
72
+
73
+ def __init__(self, spec: Dict[str, type], strict: bool = False):
74
+ """
75
+ Initialize with a dictionary specification.
76
+
77
+ Args:
78
+ spec: Dict mapping field names to expected types
79
+ strict: If True, reject payloads with extra fields
80
+
81
+ Example:
82
+ schema = DictSchema({
83
+ "user_id": str,
84
+ "amount": float,
85
+ "timestamp": str
86
+ })
87
+ """
88
+ self._spec = spec
89
+ self._strict = strict
90
+
91
+ def validate(self, payload: Dict[str, Any]) -> Dict[str, Any]:
92
+ """Validate payload against dictionary specification."""
93
+ errors = []
94
+
95
+ # Check required fields
96
+ for field, expected_type in self._spec.items():
97
+ if field not in payload:
98
+ errors.append({
99
+ "type": "missing",
100
+ "loc": [field],
101
+ "msg": f"Field '{field}' is required"
102
+ })
103
+ elif not isinstance(payload[field], expected_type):
104
+ errors.append({
105
+ "type": "type_error",
106
+ "loc": [field],
107
+ "msg": f"Expected {expected_type.__name__}, got {type(payload[field]).__name__}"
108
+ })
109
+
110
+ # Check for extra fields if strict
111
+ if self._strict:
112
+ extra_fields = set(payload.keys()) - set(self._spec.keys())
113
+ for field in extra_fields:
114
+ errors.append({
115
+ "type": "extra_forbidden",
116
+ "loc": [field],
117
+ "msg": f"Extra field '{field}' not allowed"
118
+ })
119
+
120
+ if errors:
121
+ raise SchemaValidationError(
122
+ topic="unknown",
123
+ errors=errors,
124
+ payload=payload
125
+ )
126
+
127
+ return payload
128
+
129
+
130
+ class CallableSchema(Schema):
131
+ """Schema using a custom validation function."""
132
+
133
+ def __init__(self, validator: Callable[[Dict[str, Any]], Dict[str, Any]]):
134
+ """
135
+ Initialize with a validation function.
136
+
137
+ Args:
138
+ validator: Function that takes payload and returns validated payload,
139
+ or raises an exception on validation failure
140
+ """
141
+ self._validator = validator
142
+
143
+ def validate(self, payload: Dict[str, Any]) -> Dict[str, Any]:
144
+ """Validate using custom function."""
145
+ try:
146
+ return self._validator(payload)
147
+ except Exception as e:
148
+ raise SchemaValidationError(
149
+ topic="unknown",
150
+ errors=[{"type": "validation_error", "msg": str(e)}],
151
+ payload=payload
152
+ )
153
+
154
+
155
+ class SchemaRegistry:
156
+ """
157
+ Registry for message schemas.
158
+
159
+ Provides centralized schema management for topic validation.
160
+ Supports multiple schema types: Pydantic models, dict specs, and custom validators.
161
+
162
+ Example:
163
+ from pydantic import BaseModel
164
+
165
+ class FraudAlertPayload(BaseModel):
166
+ transaction_id: str
167
+ amount: float
168
+ risk_score: float
169
+
170
+ registry = SchemaRegistry()
171
+ registry.register("fraud.alerts", FraudAlertPayload)
172
+
173
+ # Or with dict schema
174
+ registry.register("user.events", {
175
+ "user_id": str,
176
+ "event_type": str
177
+ })
178
+ """
179
+
180
+ def __init__(self, strict: bool = True):
181
+ """
182
+ Initialize the schema registry.
183
+
184
+ Args:
185
+ strict: If True, require schema for all topics when validating
186
+ """
187
+ self._schemas: Dict[str, Schema] = {}
188
+ self._strict = strict
189
+
190
+ def register(
191
+ self,
192
+ topic: str,
193
+ schema: Union[Type[BaseModel], Dict[str, type], Schema, Callable]
194
+ ) -> None:
195
+ """
196
+ Register a schema for a topic.
197
+
198
+ Args:
199
+ topic: Topic pattern to register schema for
200
+ schema: Schema specification (Pydantic model, dict, Schema instance, or callable)
201
+
202
+ Example:
203
+ # Pydantic model
204
+ registry.register("fraud.alerts", FraudAlertSchema)
205
+
206
+ # Dict specification
207
+ registry.register("user.events", {"user_id": str, "event": str})
208
+
209
+ # Custom Schema instance
210
+ registry.register("custom.topic", MyCustomSchema())
211
+
212
+ # Callable validator
213
+ registry.register("validated.topic", lambda p: p if p.get("valid") else raise_error())
214
+ """
215
+ if isinstance(schema, type) and issubclass(schema, BaseModel):
216
+ self._schemas[topic] = PydanticSchema(schema)
217
+ elif isinstance(schema, dict):
218
+ self._schemas[topic] = DictSchema(schema)
219
+ elif isinstance(schema, Schema):
220
+ self._schemas[topic] = schema
221
+ elif callable(schema):
222
+ self._schemas[topic] = CallableSchema(schema)
223
+ else:
224
+ raise TypeError(
225
+ f"Schema must be a Pydantic model, dict, Schema instance, or callable. "
226
+ f"Got {type(schema)}"
227
+ )
228
+
229
+ def unregister(self, topic: str) -> bool:
230
+ """
231
+ Unregister a schema for a topic.
232
+
233
+ Args:
234
+ topic: Topic to unregister
235
+
236
+ Returns:
237
+ True if schema was removed, False if topic wasn't registered
238
+ """
239
+ if topic in self._schemas:
240
+ del self._schemas[topic]
241
+ return True
242
+ return False
243
+
244
+ def has_schema(self, topic: str) -> bool:
245
+ """Check if a topic has a registered schema."""
246
+ return topic in self._schemas
247
+
248
+ def get_schema(self, topic: str) -> Optional[Schema]:
249
+ """Get the schema for a topic."""
250
+ return self._schemas.get(topic)
251
+
252
+ def validate(self, topic: str, payload: Dict[str, Any]) -> Dict[str, Any]:
253
+ """
254
+ Validate a payload for a given topic.
255
+
256
+ Args:
257
+ topic: Topic the message is for
258
+ payload: Message payload to validate
259
+
260
+ Returns:
261
+ Validated payload (potentially normalized)
262
+
263
+ Raises:
264
+ SchemaValidationError: If validation fails
265
+ ValueError: If strict mode and no schema registered for topic
266
+ """
267
+ schema = self._schemas.get(topic)
268
+
269
+ if schema is None:
270
+ if self._strict:
271
+ raise ValueError(f"No schema registered for topic '{topic}'")
272
+ return payload
273
+
274
+ try:
275
+ return schema.validate(payload)
276
+ except SchemaValidationError as e:
277
+ # Update error with correct topic
278
+ raise SchemaValidationError(
279
+ topic=topic,
280
+ errors=e.errors,
281
+ payload=e.payload
282
+ )
283
+
284
+ def list_topics(self) -> list:
285
+ """Get list of topics with registered schemas."""
286
+ return list(self._schemas.keys())
287
+
288
+ def __len__(self) -> int:
289
+ """Get number of registered schemas."""
290
+ return len(self._schemas)
291
+
292
+ def __contains__(self, topic: str) -> bool:
293
+ """Check if topic has a schema registered."""
294
+ return topic in self._schemas
amb_core/tracing.py ADDED
@@ -0,0 +1,358 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Distributed tracing for AMB.
4
+
5
+ This module provides distributed tracing capabilities for tracking
6
+ message flow across agents and services.
7
+ """
8
+
9
+ from typing import Any, Dict, Optional, List
10
+ from datetime import datetime, timezone
11
+ from dataclasses import dataclass, field
12
+ from uuid import uuid4
13
+ from contextvars import ContextVar
14
+ import json
15
+
16
+
17
+ # Context variable for current trace
18
+ _current_trace: ContextVar[Optional["TraceContext"]] = ContextVar(
19
+ "current_trace", default=None
20
+ )
21
+
22
+
23
+ @dataclass
24
+ class TraceSpan:
25
+ """
26
+ A span within a trace representing a single operation.
27
+ """
28
+ span_id: str
29
+ operation_name: str
30
+ trace_id: str
31
+ parent_span_id: Optional[str] = None
32
+ start_time: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
33
+ end_time: Optional[datetime] = None
34
+ tags: Dict[str, Any] = field(default_factory=dict)
35
+ logs: List[Dict[str, Any]] = field(default_factory=list)
36
+ status: str = "in_progress" # in_progress, success, error
37
+
38
+ def finish(self, status: str = "success") -> None:
39
+ """Mark span as finished."""
40
+ self.end_time = datetime.now(timezone.utc)
41
+ self.status = status
42
+
43
+ def log(self, event: str, **kwargs) -> None:
44
+ """Add a log entry to the span."""
45
+ self.logs.append({
46
+ "timestamp": datetime.now(timezone.utc).isoformat(),
47
+ "event": event,
48
+ **kwargs
49
+ })
50
+
51
+ def set_tag(self, key: str, value: Any) -> None:
52
+ """Set a tag on the span."""
53
+ self.tags[key] = value
54
+
55
+ @property
56
+ def duration_ms(self) -> Optional[float]:
57
+ """Get duration in milliseconds."""
58
+ if not self.end_time:
59
+ return None
60
+ delta = self.end_time - self.start_time
61
+ return delta.total_seconds() * 1000
62
+
63
+ def to_dict(self) -> Dict[str, Any]:
64
+ """Convert to dictionary."""
65
+ return {
66
+ "span_id": self.span_id,
67
+ "operation_name": self.operation_name,
68
+ "trace_id": self.trace_id,
69
+ "parent_span_id": self.parent_span_id,
70
+ "start_time": self.start_time.isoformat(),
71
+ "end_time": self.end_time.isoformat() if self.end_time else None,
72
+ "duration_ms": self.duration_ms,
73
+ "tags": self.tags,
74
+ "logs": self.logs,
75
+ "status": self.status
76
+ }
77
+
78
+
79
+ @dataclass
80
+ class TraceContext:
81
+ """
82
+ Context for distributed tracing across message flows.
83
+
84
+ TraceContext maintains trace and span IDs that propagate with messages,
85
+ allowing you to track the full journey of a message through the system.
86
+
87
+ Example:
88
+ # Start a new trace
89
+ with TraceContext.start("process_order") as ctx:
90
+ await bus.publish("orders.new", payload, trace_id=ctx.trace_id)
91
+
92
+ # Continue an existing trace
93
+ with TraceContext.from_message(message) as ctx:
94
+ ctx.log("Processing started")
95
+ # ... process message
96
+ ctx.log("Processing complete")
97
+ """
98
+ trace_id: str
99
+ span_id: str
100
+ parent_span_id: Optional[str] = None
101
+ baggage: Dict[str, str] = field(default_factory=dict)
102
+ spans: List[TraceSpan] = field(default_factory=list)
103
+ _current_span: Optional[TraceSpan] = field(default=None, repr=False)
104
+
105
+ @classmethod
106
+ def new(cls, operation_name: str = "root") -> "TraceContext":
107
+ """
108
+ Create a new trace context.
109
+
110
+ Args:
111
+ operation_name: Name of the root operation
112
+
113
+ Returns:
114
+ New TraceContext
115
+ """
116
+ trace_id = str(uuid4())
117
+ span_id = str(uuid4())
118
+
119
+ ctx = cls(
120
+ trace_id=trace_id,
121
+ span_id=span_id,
122
+ parent_span_id=None
123
+ )
124
+
125
+ # Create root span
126
+ root_span = TraceSpan(
127
+ span_id=span_id,
128
+ operation_name=operation_name,
129
+ trace_id=trace_id
130
+ )
131
+ ctx.spans.append(root_span)
132
+ ctx._current_span = root_span
133
+
134
+ return ctx
135
+
136
+ @classmethod
137
+ def from_headers(cls, headers: Dict[str, str]) -> "TraceContext":
138
+ """
139
+ Create trace context from message headers.
140
+
141
+ Args:
142
+ headers: Headers containing trace information
143
+
144
+ Returns:
145
+ TraceContext (new or continued)
146
+ """
147
+ trace_id = headers.get("x-trace-id")
148
+ parent_span_id = headers.get("x-span-id")
149
+ baggage_json = headers.get("x-trace-baggage", "{}")
150
+
151
+ if not trace_id:
152
+ return cls.new()
153
+
154
+ try:
155
+ baggage = json.loads(baggage_json)
156
+ except json.JSONDecodeError:
157
+ baggage = {}
158
+
159
+ span_id = str(uuid4())
160
+
161
+ return cls(
162
+ trace_id=trace_id,
163
+ span_id=span_id,
164
+ parent_span_id=parent_span_id,
165
+ baggage=baggage
166
+ )
167
+
168
+ @classmethod
169
+ def from_message(cls, message: "Message") -> "TraceContext": # noqa: F821
170
+ """
171
+ Extract trace context from a message.
172
+
173
+ Args:
174
+ message: Message with trace metadata
175
+
176
+ Returns:
177
+ TraceContext (new or continued)
178
+ """
179
+ trace_id = message.metadata.get("trace_id")
180
+ parent_span_id = message.metadata.get("span_id")
181
+ baggage = message.metadata.get("trace_baggage", {})
182
+
183
+ if not trace_id:
184
+ return cls.new()
185
+
186
+ span_id = str(uuid4())
187
+
188
+ return cls(
189
+ trace_id=trace_id,
190
+ span_id=span_id,
191
+ parent_span_id=parent_span_id,
192
+ baggage=baggage if isinstance(baggage, dict) else {}
193
+ )
194
+
195
+ @classmethod
196
+ def current(cls) -> Optional["TraceContext"]:
197
+ """Get current trace context from context var."""
198
+ return _current_trace.get()
199
+
200
+ @classmethod
201
+ def start(cls, operation_name: str = "root") -> "TraceContext":
202
+ """
203
+ Start a new trace and set as current.
204
+
205
+ Args:
206
+ operation_name: Name of the root operation
207
+
208
+ Returns:
209
+ New TraceContext
210
+ """
211
+ ctx = cls.new(operation_name)
212
+ _current_trace.set(ctx)
213
+ return ctx
214
+
215
+ def start_span(self, operation_name: str) -> TraceSpan:
216
+ """
217
+ Start a new child span.
218
+
219
+ Args:
220
+ operation_name: Name of the operation
221
+
222
+ Returns:
223
+ New TraceSpan
224
+ """
225
+ parent_id = self._current_span.span_id if self._current_span else self.span_id
226
+
227
+ span = TraceSpan(
228
+ span_id=str(uuid4()),
229
+ operation_name=operation_name,
230
+ trace_id=self.trace_id,
231
+ parent_span_id=parent_id
232
+ )
233
+
234
+ self.spans.append(span)
235
+ self._current_span = span
236
+ return span
237
+
238
+ def finish_span(self, status: str = "success") -> None:
239
+ """Finish the current span."""
240
+ if self._current_span:
241
+ self._current_span.finish(status)
242
+
243
+ # Find parent span
244
+ if self._current_span.parent_span_id:
245
+ for span in self.spans:
246
+ if span.span_id == self._current_span.parent_span_id:
247
+ self._current_span = span
248
+ return
249
+
250
+ self._current_span = None
251
+
252
+ def log(self, event: str, **kwargs) -> None:
253
+ """Log an event to the current span."""
254
+ if self._current_span:
255
+ self._current_span.log(event, **kwargs)
256
+
257
+ def set_tag(self, key: str, value: Any) -> None:
258
+ """Set a tag on the current span."""
259
+ if self._current_span:
260
+ self._current_span.set_tag(key, value)
261
+
262
+ def set_baggage(self, key: str, value: str) -> None:
263
+ """
264
+ Set baggage item that propagates with the trace.
265
+
266
+ Baggage items are key-value pairs that travel with the trace
267
+ across all services.
268
+ """
269
+ self.baggage[key] = value
270
+
271
+ def get_baggage(self, key: str) -> Optional[str]:
272
+ """Get a baggage item."""
273
+ return self.baggage.get(key)
274
+
275
+ def to_headers(self) -> Dict[str, str]:
276
+ """
277
+ Convert trace context to headers for propagation.
278
+
279
+ Returns:
280
+ Headers dict
281
+ """
282
+ return {
283
+ "x-trace-id": self.trace_id,
284
+ "x-span-id": self.span_id,
285
+ "x-parent-span-id": self.parent_span_id or "",
286
+ "x-trace-baggage": json.dumps(self.baggage)
287
+ }
288
+
289
+ def to_message_metadata(self) -> Dict[str, Any]:
290
+ """
291
+ Convert trace context to message metadata.
292
+
293
+ Returns:
294
+ Metadata dict to add to message
295
+ """
296
+ return {
297
+ "trace_id": self.trace_id,
298
+ "span_id": self.span_id,
299
+ "parent_span_id": self.parent_span_id,
300
+ "trace_baggage": self.baggage
301
+ }
302
+
303
+ def __enter__(self) -> "TraceContext":
304
+ """Context manager entry."""
305
+ _current_trace.set(self)
306
+ return self
307
+
308
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
309
+ """Context manager exit."""
310
+ if exc_type:
311
+ self.finish_span(status="error")
312
+ else:
313
+ self.finish_span(status="success")
314
+ _current_trace.set(None)
315
+
316
+ def to_dict(self) -> Dict[str, Any]:
317
+ """Convert to dictionary."""
318
+ return {
319
+ "trace_id": self.trace_id,
320
+ "span_id": self.span_id,
321
+ "parent_span_id": self.parent_span_id,
322
+ "baggage": self.baggage,
323
+ "spans": [s.to_dict() for s in self.spans]
324
+ }
325
+
326
+
327
+ def get_current_trace() -> Optional[TraceContext]:
328
+ """Get the current trace context."""
329
+ return _current_trace.get()
330
+
331
+
332
+ def inject_trace(message: "Message") -> "Message": # noqa: F821
333
+ """
334
+ Inject current trace context into a message.
335
+
336
+ Args:
337
+ message: Message to inject trace into
338
+
339
+ Returns:
340
+ Message with trace metadata
341
+ """
342
+ ctx = get_current_trace()
343
+ if ctx:
344
+ message.metadata.update(ctx.to_message_metadata())
345
+ return message
346
+
347
+
348
+ def extract_trace(message: "Message") -> TraceContext: # noqa: F821
349
+ """
350
+ Extract trace context from a message.
351
+
352
+ Args:
353
+ message: Message to extract trace from
354
+
355
+ Returns:
356
+ TraceContext (new or continued)
357
+ """
358
+ return TraceContext.from_message(message)