hexdag 0.5.0.dev1__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 (261) hide show
  1. hexdag/__init__.py +116 -0
  2. hexdag/__main__.py +30 -0
  3. hexdag/adapters/executors/__init__.py +5 -0
  4. hexdag/adapters/executors/local_executor.py +316 -0
  5. hexdag/builtin/__init__.py +6 -0
  6. hexdag/builtin/adapters/__init__.py +51 -0
  7. hexdag/builtin/adapters/anthropic/__init__.py +5 -0
  8. hexdag/builtin/adapters/anthropic/anthropic_adapter.py +151 -0
  9. hexdag/builtin/adapters/database/__init__.py +6 -0
  10. hexdag/builtin/adapters/database/csv/csv_adapter.py +249 -0
  11. hexdag/builtin/adapters/database/pgvector/__init__.py +5 -0
  12. hexdag/builtin/adapters/database/pgvector/pgvector_adapter.py +478 -0
  13. hexdag/builtin/adapters/database/sqlalchemy/sqlalchemy_adapter.py +252 -0
  14. hexdag/builtin/adapters/database/sqlite/__init__.py +5 -0
  15. hexdag/builtin/adapters/database/sqlite/sqlite_adapter.py +410 -0
  16. hexdag/builtin/adapters/local/README.md +59 -0
  17. hexdag/builtin/adapters/local/__init__.py +7 -0
  18. hexdag/builtin/adapters/local/local_observer_manager.py +696 -0
  19. hexdag/builtin/adapters/memory/__init__.py +47 -0
  20. hexdag/builtin/adapters/memory/file_memory_adapter.py +297 -0
  21. hexdag/builtin/adapters/memory/in_memory_memory.py +216 -0
  22. hexdag/builtin/adapters/memory/schemas.py +57 -0
  23. hexdag/builtin/adapters/memory/session_memory.py +178 -0
  24. hexdag/builtin/adapters/memory/sqlite_memory_adapter.py +215 -0
  25. hexdag/builtin/adapters/memory/state_memory.py +280 -0
  26. hexdag/builtin/adapters/mock/README.md +89 -0
  27. hexdag/builtin/adapters/mock/__init__.py +15 -0
  28. hexdag/builtin/adapters/mock/hexdag.toml +50 -0
  29. hexdag/builtin/adapters/mock/mock_database.py +225 -0
  30. hexdag/builtin/adapters/mock/mock_embedding.py +223 -0
  31. hexdag/builtin/adapters/mock/mock_llm.py +177 -0
  32. hexdag/builtin/adapters/mock/mock_tool_adapter.py +192 -0
  33. hexdag/builtin/adapters/mock/mock_tool_router.py +232 -0
  34. hexdag/builtin/adapters/openai/__init__.py +5 -0
  35. hexdag/builtin/adapters/openai/openai_adapter.py +634 -0
  36. hexdag/builtin/adapters/secret/__init__.py +7 -0
  37. hexdag/builtin/adapters/secret/local_secret_adapter.py +248 -0
  38. hexdag/builtin/adapters/unified_tool_router.py +280 -0
  39. hexdag/builtin/macros/__init__.py +17 -0
  40. hexdag/builtin/macros/conversation_agent.py +390 -0
  41. hexdag/builtin/macros/llm_macro.py +151 -0
  42. hexdag/builtin/macros/reasoning_agent.py +423 -0
  43. hexdag/builtin/macros/tool_macro.py +380 -0
  44. hexdag/builtin/nodes/__init__.py +38 -0
  45. hexdag/builtin/nodes/_discovery.py +123 -0
  46. hexdag/builtin/nodes/agent_node.py +696 -0
  47. hexdag/builtin/nodes/base_node_factory.py +242 -0
  48. hexdag/builtin/nodes/composite_node.py +926 -0
  49. hexdag/builtin/nodes/data_node.py +201 -0
  50. hexdag/builtin/nodes/expression_node.py +487 -0
  51. hexdag/builtin/nodes/function_node.py +454 -0
  52. hexdag/builtin/nodes/llm_node.py +491 -0
  53. hexdag/builtin/nodes/loop_node.py +920 -0
  54. hexdag/builtin/nodes/mapped_input.py +518 -0
  55. hexdag/builtin/nodes/port_call_node.py +269 -0
  56. hexdag/builtin/nodes/tool_call_node.py +195 -0
  57. hexdag/builtin/nodes/tool_utils.py +390 -0
  58. hexdag/builtin/prompts/__init__.py +68 -0
  59. hexdag/builtin/prompts/base.py +422 -0
  60. hexdag/builtin/prompts/chat_prompts.py +303 -0
  61. hexdag/builtin/prompts/error_correction_prompts.py +320 -0
  62. hexdag/builtin/prompts/tool_prompts.py +160 -0
  63. hexdag/builtin/tools/builtin_tools.py +84 -0
  64. hexdag/builtin/tools/database_tools.py +164 -0
  65. hexdag/cli/__init__.py +17 -0
  66. hexdag/cli/__main__.py +7 -0
  67. hexdag/cli/commands/__init__.py +27 -0
  68. hexdag/cli/commands/build_cmd.py +812 -0
  69. hexdag/cli/commands/create_cmd.py +208 -0
  70. hexdag/cli/commands/docs_cmd.py +293 -0
  71. hexdag/cli/commands/generate_types_cmd.py +252 -0
  72. hexdag/cli/commands/init_cmd.py +188 -0
  73. hexdag/cli/commands/pipeline_cmd.py +494 -0
  74. hexdag/cli/commands/plugin_dev_cmd.py +529 -0
  75. hexdag/cli/commands/plugins_cmd.py +441 -0
  76. hexdag/cli/commands/studio_cmd.py +101 -0
  77. hexdag/cli/commands/validate_cmd.py +221 -0
  78. hexdag/cli/main.py +84 -0
  79. hexdag/core/__init__.py +83 -0
  80. hexdag/core/config/__init__.py +20 -0
  81. hexdag/core/config/loader.py +479 -0
  82. hexdag/core/config/models.py +150 -0
  83. hexdag/core/configurable.py +294 -0
  84. hexdag/core/context/__init__.py +37 -0
  85. hexdag/core/context/execution_context.py +378 -0
  86. hexdag/core/docs/__init__.py +26 -0
  87. hexdag/core/docs/extractors.py +678 -0
  88. hexdag/core/docs/generators.py +890 -0
  89. hexdag/core/docs/models.py +120 -0
  90. hexdag/core/domain/__init__.py +10 -0
  91. hexdag/core/domain/dag.py +1225 -0
  92. hexdag/core/exceptions.py +234 -0
  93. hexdag/core/expression_parser.py +569 -0
  94. hexdag/core/logging.py +449 -0
  95. hexdag/core/models/__init__.py +17 -0
  96. hexdag/core/models/base.py +138 -0
  97. hexdag/core/orchestration/__init__.py +46 -0
  98. hexdag/core/orchestration/body_executor.py +481 -0
  99. hexdag/core/orchestration/components/__init__.py +97 -0
  100. hexdag/core/orchestration/components/adapter_lifecycle_manager.py +113 -0
  101. hexdag/core/orchestration/components/checkpoint_manager.py +134 -0
  102. hexdag/core/orchestration/components/execution_coordinator.py +360 -0
  103. hexdag/core/orchestration/components/health_check_manager.py +176 -0
  104. hexdag/core/orchestration/components/input_mapper.py +143 -0
  105. hexdag/core/orchestration/components/lifecycle_manager.py +583 -0
  106. hexdag/core/orchestration/components/node_executor.py +377 -0
  107. hexdag/core/orchestration/components/secret_manager.py +202 -0
  108. hexdag/core/orchestration/components/wave_executor.py +158 -0
  109. hexdag/core/orchestration/constants.py +17 -0
  110. hexdag/core/orchestration/events/README.md +312 -0
  111. hexdag/core/orchestration/events/__init__.py +104 -0
  112. hexdag/core/orchestration/events/batching.py +330 -0
  113. hexdag/core/orchestration/events/decorators.py +139 -0
  114. hexdag/core/orchestration/events/events.py +573 -0
  115. hexdag/core/orchestration/events/observers/__init__.py +30 -0
  116. hexdag/core/orchestration/events/observers/core_observers.py +690 -0
  117. hexdag/core/orchestration/events/observers/models.py +111 -0
  118. hexdag/core/orchestration/events/taxonomy.py +269 -0
  119. hexdag/core/orchestration/hook_context.py +237 -0
  120. hexdag/core/orchestration/hooks.py +437 -0
  121. hexdag/core/orchestration/models.py +418 -0
  122. hexdag/core/orchestration/orchestrator.py +910 -0
  123. hexdag/core/orchestration/orchestrator_factory.py +275 -0
  124. hexdag/core/orchestration/port_wrappers.py +327 -0
  125. hexdag/core/orchestration/prompt/__init__.py +32 -0
  126. hexdag/core/orchestration/prompt/template.py +332 -0
  127. hexdag/core/pipeline_builder/__init__.py +21 -0
  128. hexdag/core/pipeline_builder/component_instantiator.py +386 -0
  129. hexdag/core/pipeline_builder/include_tag.py +265 -0
  130. hexdag/core/pipeline_builder/pipeline_config.py +133 -0
  131. hexdag/core/pipeline_builder/py_tag.py +223 -0
  132. hexdag/core/pipeline_builder/tag_discovery.py +268 -0
  133. hexdag/core/pipeline_builder/yaml_builder.py +1196 -0
  134. hexdag/core/pipeline_builder/yaml_validator.py +569 -0
  135. hexdag/core/ports/__init__.py +65 -0
  136. hexdag/core/ports/api_call.py +133 -0
  137. hexdag/core/ports/database.py +489 -0
  138. hexdag/core/ports/embedding.py +215 -0
  139. hexdag/core/ports/executor.py +237 -0
  140. hexdag/core/ports/file_storage.py +117 -0
  141. hexdag/core/ports/healthcheck.py +87 -0
  142. hexdag/core/ports/llm.py +551 -0
  143. hexdag/core/ports/memory.py +70 -0
  144. hexdag/core/ports/observer_manager.py +130 -0
  145. hexdag/core/ports/secret.py +145 -0
  146. hexdag/core/ports/tool_router.py +94 -0
  147. hexdag/core/ports_builder.py +623 -0
  148. hexdag/core/protocols.py +273 -0
  149. hexdag/core/resolver.py +304 -0
  150. hexdag/core/schema/__init__.py +9 -0
  151. hexdag/core/schema/generator.py +742 -0
  152. hexdag/core/secrets.py +242 -0
  153. hexdag/core/types.py +413 -0
  154. hexdag/core/utils/async_warnings.py +206 -0
  155. hexdag/core/utils/schema_conversion.py +78 -0
  156. hexdag/core/utils/sql_validation.py +86 -0
  157. hexdag/core/validation/secure_json.py +148 -0
  158. hexdag/core/yaml_macro.py +517 -0
  159. hexdag/mcp_server.py +3120 -0
  160. hexdag/studio/__init__.py +10 -0
  161. hexdag/studio/build_ui.py +92 -0
  162. hexdag/studio/server/__init__.py +1 -0
  163. hexdag/studio/server/main.py +100 -0
  164. hexdag/studio/server/routes/__init__.py +9 -0
  165. hexdag/studio/server/routes/execute.py +208 -0
  166. hexdag/studio/server/routes/export.py +558 -0
  167. hexdag/studio/server/routes/files.py +207 -0
  168. hexdag/studio/server/routes/plugins.py +419 -0
  169. hexdag/studio/server/routes/validate.py +220 -0
  170. hexdag/studio/ui/index.html +13 -0
  171. hexdag/studio/ui/package-lock.json +2992 -0
  172. hexdag/studio/ui/package.json +31 -0
  173. hexdag/studio/ui/postcss.config.js +6 -0
  174. hexdag/studio/ui/public/hexdag.svg +5 -0
  175. hexdag/studio/ui/src/App.tsx +251 -0
  176. hexdag/studio/ui/src/components/Canvas.tsx +408 -0
  177. hexdag/studio/ui/src/components/ContextMenu.tsx +187 -0
  178. hexdag/studio/ui/src/components/FileBrowser.tsx +123 -0
  179. hexdag/studio/ui/src/components/Header.tsx +181 -0
  180. hexdag/studio/ui/src/components/HexdagNode.tsx +193 -0
  181. hexdag/studio/ui/src/components/NodeInspector.tsx +512 -0
  182. hexdag/studio/ui/src/components/NodePalette.tsx +262 -0
  183. hexdag/studio/ui/src/components/NodePortsSection.tsx +403 -0
  184. hexdag/studio/ui/src/components/PluginManager.tsx +347 -0
  185. hexdag/studio/ui/src/components/PortsEditor.tsx +481 -0
  186. hexdag/studio/ui/src/components/PythonEditor.tsx +195 -0
  187. hexdag/studio/ui/src/components/ValidationPanel.tsx +105 -0
  188. hexdag/studio/ui/src/components/YamlEditor.tsx +196 -0
  189. hexdag/studio/ui/src/components/index.ts +8 -0
  190. hexdag/studio/ui/src/index.css +92 -0
  191. hexdag/studio/ui/src/main.tsx +10 -0
  192. hexdag/studio/ui/src/types/index.ts +123 -0
  193. hexdag/studio/ui/src/vite-env.d.ts +1 -0
  194. hexdag/studio/ui/tailwind.config.js +29 -0
  195. hexdag/studio/ui/tsconfig.json +37 -0
  196. hexdag/studio/ui/tsconfig.node.json +13 -0
  197. hexdag/studio/ui/vite.config.ts +35 -0
  198. hexdag/visualization/__init__.py +69 -0
  199. hexdag/visualization/dag_visualizer.py +1020 -0
  200. hexdag-0.5.0.dev1.dist-info/METADATA +369 -0
  201. hexdag-0.5.0.dev1.dist-info/RECORD +261 -0
  202. hexdag-0.5.0.dev1.dist-info/WHEEL +4 -0
  203. hexdag-0.5.0.dev1.dist-info/entry_points.txt +4 -0
  204. hexdag-0.5.0.dev1.dist-info/licenses/LICENSE +190 -0
  205. hexdag_plugins/.gitignore +43 -0
  206. hexdag_plugins/README.md +73 -0
  207. hexdag_plugins/__init__.py +1 -0
  208. hexdag_plugins/azure/LICENSE +21 -0
  209. hexdag_plugins/azure/README.md +414 -0
  210. hexdag_plugins/azure/__init__.py +21 -0
  211. hexdag_plugins/azure/azure_blob_adapter.py +450 -0
  212. hexdag_plugins/azure/azure_cosmos_adapter.py +383 -0
  213. hexdag_plugins/azure/azure_keyvault_adapter.py +314 -0
  214. hexdag_plugins/azure/azure_openai_adapter.py +415 -0
  215. hexdag_plugins/azure/pyproject.toml +107 -0
  216. hexdag_plugins/azure/tests/__init__.py +1 -0
  217. hexdag_plugins/azure/tests/test_azure_blob_adapter.py +350 -0
  218. hexdag_plugins/azure/tests/test_azure_cosmos_adapter.py +323 -0
  219. hexdag_plugins/azure/tests/test_azure_keyvault_adapter.py +330 -0
  220. hexdag_plugins/azure/tests/test_azure_openai_adapter.py +329 -0
  221. hexdag_plugins/hexdag_etl/README.md +168 -0
  222. hexdag_plugins/hexdag_etl/__init__.py +53 -0
  223. hexdag_plugins/hexdag_etl/examples/01_simple_pandas_transform.py +270 -0
  224. hexdag_plugins/hexdag_etl/examples/02_simple_pandas_only.py +149 -0
  225. hexdag_plugins/hexdag_etl/examples/03_file_io_pipeline.py +109 -0
  226. hexdag_plugins/hexdag_etl/examples/test_pandas_transform.py +84 -0
  227. hexdag_plugins/hexdag_etl/hexdag.toml +25 -0
  228. hexdag_plugins/hexdag_etl/hexdag_etl/__init__.py +48 -0
  229. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/__init__.py +13 -0
  230. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/api_extract.py +230 -0
  231. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/base_node_factory.py +181 -0
  232. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/file_io.py +415 -0
  233. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/outlook.py +492 -0
  234. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/pandas_transform.py +563 -0
  235. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/sql_extract_load.py +112 -0
  236. hexdag_plugins/hexdag_etl/pyproject.toml +82 -0
  237. hexdag_plugins/hexdag_etl/test_transform.py +54 -0
  238. hexdag_plugins/hexdag_etl/tests/test_plugin_integration.py +62 -0
  239. hexdag_plugins/mysql_adapter/LICENSE +21 -0
  240. hexdag_plugins/mysql_adapter/README.md +224 -0
  241. hexdag_plugins/mysql_adapter/__init__.py +6 -0
  242. hexdag_plugins/mysql_adapter/mysql_adapter.py +408 -0
  243. hexdag_plugins/mysql_adapter/pyproject.toml +93 -0
  244. hexdag_plugins/mysql_adapter/tests/test_mysql_adapter.py +259 -0
  245. hexdag_plugins/storage/README.md +184 -0
  246. hexdag_plugins/storage/__init__.py +19 -0
  247. hexdag_plugins/storage/file/__init__.py +5 -0
  248. hexdag_plugins/storage/file/local.py +325 -0
  249. hexdag_plugins/storage/ports/__init__.py +5 -0
  250. hexdag_plugins/storage/ports/vector_store.py +236 -0
  251. hexdag_plugins/storage/sql/__init__.py +7 -0
  252. hexdag_plugins/storage/sql/base.py +187 -0
  253. hexdag_plugins/storage/sql/mysql.py +27 -0
  254. hexdag_plugins/storage/sql/postgresql.py +27 -0
  255. hexdag_plugins/storage/tests/__init__.py +1 -0
  256. hexdag_plugins/storage/tests/test_local_file_storage.py +161 -0
  257. hexdag_plugins/storage/tests/test_sql_adapters.py +212 -0
  258. hexdag_plugins/storage/vector/__init__.py +7 -0
  259. hexdag_plugins/storage/vector/chromadb.py +223 -0
  260. hexdag_plugins/storage/vector/in_memory.py +285 -0
  261. hexdag_plugins/storage/vector/pgvector.py +502 -0
@@ -0,0 +1,573 @@
1
+ """Simple event data classes and registry metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime
7
+ from typing import Any
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class EventSpec:
12
+ """Specification defining taxonomy metadata for an event class."""
13
+
14
+ event_type: str
15
+ envelope_fields: dict[str, str]
16
+ attr_fields: tuple[str, ...] | None = None
17
+
18
+
19
+ @dataclass
20
+ class Event:
21
+ """Base class for all events - provides timestamp."""
22
+
23
+ timestamp: datetime = field(default_factory=datetime.now, init=False)
24
+
25
+ def log_message(self) -> str:
26
+ """Get a formatted log message for this event.
27
+
28
+ Override in subclasses to provide custom formatting.
29
+
30
+ Returns
31
+ -------
32
+ str
33
+ A formatted string suitable for logging
34
+ """
35
+ return f"{self.__class__.__name__} at {self.timestamp.isoformat()}"
36
+
37
+
38
+ EVENT_REGISTRY: dict[str, EventSpec] = {
39
+ "PipelineStarted": EventSpec(
40
+ "pipeline:started",
41
+ {"pipeline": "name"},
42
+ ("total_waves", "total_nodes"),
43
+ ),
44
+ "PipelineCompleted": EventSpec(
45
+ "pipeline:completed",
46
+ {"pipeline": "name"},
47
+ ("duration_ms", "node_results"),
48
+ ),
49
+ "NodeStarted": EventSpec(
50
+ "node:started",
51
+ {"node": "name", "wave": "wave_index"},
52
+ ("dependencies",),
53
+ ),
54
+ "NodeCompleted": EventSpec(
55
+ "node:completed",
56
+ {"node": "name", "wave": "wave_index"},
57
+ ("result", "duration_ms"),
58
+ ),
59
+ "NodeFailed": EventSpec(
60
+ "node:failed",
61
+ {"node": "name", "wave": "wave_index"},
62
+ ("error",),
63
+ ),
64
+ "WaveStarted": EventSpec(
65
+ "wave:started",
66
+ {"wave": "wave_index"},
67
+ ("nodes",),
68
+ ),
69
+ "WaveCompleted": EventSpec(
70
+ "wave:completed",
71
+ {"wave": "wave_index"},
72
+ ("duration_ms",),
73
+ ),
74
+ "LLMPromptSent": EventSpec(
75
+ "llm:prompt",
76
+ {"node": "node_name"},
77
+ ("messages",),
78
+ ),
79
+ "LLMResponseReceived": EventSpec(
80
+ "llm:response",
81
+ {"node": "node_name"},
82
+ ("response", "duration_ms"),
83
+ ),
84
+ "ToolCalled": EventSpec(
85
+ "tool:called",
86
+ {"node": "node_name"},
87
+ ("tool_name", "params"),
88
+ ),
89
+ "ToolCompleted": EventSpec(
90
+ "tool:completed",
91
+ {"node": "node_name"},
92
+ ("tool_name", "result", "duration_ms"),
93
+ ),
94
+ }
95
+
96
+
97
+ # Node events
98
+ @dataclass(slots=True)
99
+ class NodeStarted(Event):
100
+ """A node has started execution."""
101
+
102
+ name: str
103
+ wave_index: int
104
+ dependencies: tuple[str, ...] | list[str] = field(default_factory=tuple)
105
+
106
+ def log_message(self) -> str:
107
+ """Format log message for node start event.
108
+
109
+ Returns
110
+ -------
111
+ str
112
+ Formatted log message for node start
113
+ """
114
+ deps = f" (deps: {', '.join(self.dependencies)})" if self.dependencies else ""
115
+ return f"🚀 Node '{self.name}' started in wave {self.wave_index}{deps}"
116
+
117
+
118
+ @dataclass(slots=True)
119
+ class NodeCompleted(Event):
120
+ """A node has completed successfully."""
121
+
122
+ name: str
123
+ wave_index: int
124
+ result: Any
125
+ duration_ms: float
126
+
127
+ def log_message(self) -> str:
128
+ """Format log message for node completion event.
129
+
130
+ Returns
131
+ -------
132
+ str
133
+ Formatted log message for node completion
134
+ """
135
+ return f"✅ Node '{self.name}' completed in {self.duration_ms / 1000:.2f}s"
136
+
137
+
138
+ @dataclass(slots=True)
139
+ class NodeFailed(Event):
140
+ """A node has failed."""
141
+
142
+ name: str
143
+ wave_index: int
144
+ error: Exception
145
+
146
+ def log_message(self) -> str:
147
+ """Format log message for node failure event.
148
+
149
+ Returns
150
+ -------
151
+ str
152
+ Formatted log message for node failure
153
+ """
154
+ return f"❌ Node '{self.name}' failed: {self.error}"
155
+
156
+
157
+ @dataclass(slots=True)
158
+ class NodeCancelled(Event):
159
+ """A node execution was cancelled.
160
+
161
+ Attributes
162
+ ----------
163
+ name : str
164
+ Node name
165
+ wave_index : int
166
+ Wave index where node was executing
167
+ reason : str | None
168
+ Reason for cancellation (e.g., "timeout", "user_cancel")
169
+ """
170
+
171
+ name: str
172
+ wave_index: int
173
+ reason: str | None = None
174
+
175
+ def log_message(self) -> str:
176
+ """Format log message for node cancellation event."""
177
+ return f"đŸšĢ Node '{self.name}' cancelled: {self.reason or 'unknown'}"
178
+
179
+
180
+ @dataclass(slots=True)
181
+ class NodeSkipped(Event):
182
+ """A node execution was skipped due to when clause evaluation.
183
+
184
+ Attributes
185
+ ----------
186
+ name : str
187
+ Node name
188
+ wave_index : int
189
+ Wave index where node was scheduled
190
+ reason : str | None
191
+ Reason for skipping (e.g., "when clause 'status == active' evaluated to False")
192
+ """
193
+
194
+ name: str
195
+ wave_index: int
196
+ reason: str | None = None
197
+
198
+ def log_message(self) -> str:
199
+ """Format log message for node skip event."""
200
+ return f"â­ī¸ Node '{self.name}' skipped: {self.reason or 'unknown'}"
201
+
202
+
203
+ # Wave events
204
+ @dataclass(slots=True)
205
+ class WaveStarted(Event):
206
+ """A wave of parallel nodes has started."""
207
+
208
+ wave_index: int
209
+ nodes: list[str]
210
+
211
+ def log_message(self) -> str:
212
+ """Format log message for wave start event.
213
+
214
+ Returns
215
+ -------
216
+ str
217
+ Formatted log message for wave start
218
+ """
219
+ return f"🌊 Wave {self.wave_index} started with {len(self.nodes)} nodes"
220
+
221
+
222
+ @dataclass(slots=True)
223
+ class WaveCompleted(Event):
224
+ """A wave has completed."""
225
+
226
+ wave_index: int
227
+ duration_ms: float
228
+
229
+ def log_message(self) -> str:
230
+ """Format log message for wave completion event.
231
+
232
+ Returns
233
+ -------
234
+ str
235
+ Formatted log message for wave completion
236
+ """
237
+ return f"✅ Wave {self.wave_index} completed in {self.duration_ms / 1000:.2f}s"
238
+
239
+
240
+ # Pipeline events
241
+ @dataclass(slots=True)
242
+ class PipelineStarted(Event):
243
+ """Pipeline execution has started."""
244
+
245
+ name: str
246
+ total_waves: int
247
+ total_nodes: int
248
+
249
+ def log_message(self) -> str:
250
+ """Format log message for pipeline start event.
251
+
252
+ Returns
253
+ -------
254
+ str
255
+ Formatted log message for pipeline start
256
+ """
257
+ return (
258
+ f"đŸŽŦ Pipeline '{self.name}' started "
259
+ f"({self.total_nodes} nodes, {self.total_waves} waves)"
260
+ )
261
+
262
+
263
+ @dataclass(slots=True)
264
+ class PipelineCompleted(Event):
265
+ """Pipeline has completed successfully."""
266
+
267
+ name: str
268
+ duration_ms: float
269
+ node_results: dict[str, Any] = field(default_factory=dict)
270
+
271
+ def log_message(self) -> str:
272
+ """Format log message for pipeline completion event.
273
+
274
+ Returns
275
+ -------
276
+ str
277
+ Formatted log message for pipeline completion
278
+ """
279
+ return f"🎉 Pipeline '{self.name}' completed in {self.duration_ms / 1000:.2f}s"
280
+
281
+
282
+ @dataclass(slots=True)
283
+ class PipelineCancelled(Event):
284
+ """Pipeline execution was cancelled.
285
+
286
+ Attributes
287
+ ----------
288
+ name : str
289
+ Pipeline name
290
+ duration_ms : float
291
+ Duration until cancellation in milliseconds
292
+ reason : str | None
293
+ Reason for cancellation (e.g., "timeout", "user_cancel")
294
+ partial_results : dict[str, Any]
295
+ Results from nodes that completed before cancellation
296
+ """
297
+
298
+ name: str
299
+ duration_ms: float
300
+ reason: str | None = None
301
+ partial_results: dict[str, Any] = field(default_factory=dict)
302
+
303
+ def log_message(self) -> str:
304
+ """Format log message for pipeline cancellation event."""
305
+ completed_nodes = len(self.partial_results)
306
+ return (
307
+ f"🛑 Pipeline '{self.name}' cancelled after {self.duration_ms / 1000:.2f}s "
308
+ f"({completed_nodes} nodes completed): {self.reason or 'unknown'}"
309
+ )
310
+
311
+
312
+ # LLM events
313
+ @dataclass(slots=True)
314
+ class LLMPromptSent(Event):
315
+ """LLM prompt has been sent."""
316
+
317
+ node_name: str
318
+ messages: list[dict[str, str]]
319
+
320
+ def log_message(self) -> str:
321
+ """Format log message for LLM prompt event.
322
+
323
+ Returns
324
+ -------
325
+ str
326
+ Formatted log message for LLM prompt
327
+ """
328
+ return f"💭 LLM prompt sent from '{self.node_name}' ({len(self.messages)} messages)"
329
+
330
+
331
+ @dataclass(slots=True)
332
+ class LLMResponseReceived(Event):
333
+ """LLM response has been received."""
334
+
335
+ node_name: str
336
+ response: str
337
+ duration_ms: float
338
+
339
+ def log_message(self) -> str:
340
+ """Format log message for LLM response event.
341
+
342
+ Returns
343
+ -------
344
+ str
345
+ Formatted log message for LLM response
346
+ """
347
+ return f"🤖 LLM response for '{self.node_name}' in {self.duration_ms / 1000:.2f}s"
348
+
349
+
350
+ # Tool events
351
+ @dataclass(slots=True)
352
+ class ToolCalled(Event):
353
+ """A tool has been invoked."""
354
+
355
+ node_name: str
356
+ tool_name: str
357
+ params: dict[str, Any]
358
+
359
+ def log_message(self) -> str:
360
+ """Format log message for tool call event.
361
+
362
+ Returns
363
+ -------
364
+ str
365
+ Formatted log message for tool call
366
+ """
367
+ return f"🔧 Tool '{self.tool_name}' called from '{self.node_name}'"
368
+
369
+
370
+ @dataclass(slots=True)
371
+ class ToolCompleted(Event):
372
+ """A tool has completed."""
373
+
374
+ node_name: str
375
+ tool_name: str
376
+ result: Any
377
+ duration_ms: float
378
+
379
+ def log_message(self) -> str:
380
+ """Format log message for tool completion event.
381
+
382
+ Returns
383
+ -------
384
+ str
385
+ Formatted log message for tool completion
386
+ """
387
+ return f"✅ Tool '{self.tool_name}' completed in {self.duration_ms / 1000:.2f}s"
388
+
389
+
390
+ # Policy-related events for tracking policy evaluations and decisions
391
+ @dataclass(slots=True)
392
+ class PolicyEvaluated(Event):
393
+ """Event emitted after a policy has been evaluated."""
394
+
395
+ policy_name: str
396
+ signal: str # The signal returned by the policy
397
+ duration_ms: float
398
+ context_node: str | None = None
399
+
400
+ def log_message(self) -> str:
401
+ """Format log message for policy evaluation."""
402
+ return (
403
+ f"📋 Policy '{self.policy_name}' evaluated -> {self.signal} ({self.duration_ms:.1f}ms)"
404
+ )
405
+
406
+
407
+ @dataclass(slots=True)
408
+ class PolicyTriggered(Event):
409
+ """Event emitted when a policy's condition is triggered."""
410
+
411
+ policy_name: str
412
+ trigger_reason: str
413
+ context_node: str | None = None
414
+
415
+ def log_message(self) -> str:
416
+ """Format log message for policy trigger."""
417
+ node_info = f" for node '{self.context_node}'" if self.context_node else ""
418
+ return f"đŸŽ¯ Policy '{self.policy_name}' triggered{node_info}: {self.trigger_reason}"
419
+
420
+
421
+ @dataclass(slots=True)
422
+ class PolicySkipped(Event):
423
+ """Event emitted when a policy causes a node to be skipped."""
424
+
425
+ policy_name: str
426
+ node_name: str
427
+ reason: str | None = None
428
+
429
+ def log_message(self) -> str:
430
+ """Format log message for policy skip."""
431
+ reason_info = f": {self.reason}" if self.reason else ""
432
+ return f"â­ī¸ Policy '{self.policy_name}' skipped node '{self.node_name}'{reason_info}"
433
+
434
+
435
+ @dataclass(slots=True)
436
+ class PolicyFallback(Event):
437
+ """Event emitted when a policy provides a fallback value."""
438
+
439
+ policy_name: str
440
+ node_name: str
441
+ fallback_value: Any
442
+ reason: str | None = None
443
+
444
+ def log_message(self) -> str:
445
+ """Format log message for policy fallback."""
446
+ reason_info = f": {self.reason}" if self.reason else ""
447
+ return (
448
+ f"🔄 Policy '{self.policy_name}' provided fallback for '{self.node_name}'{reason_info}"
449
+ )
450
+
451
+
452
+ @dataclass(slots=True)
453
+ class PolicyRetry(Event):
454
+ """Event emitted when a policy triggers a retry."""
455
+
456
+ policy_name: str
457
+ node_name: str
458
+ attempt: int
459
+ delay_ms: float | None = None
460
+ max_attempts: int | None = None
461
+
462
+ def log_message(self) -> str:
463
+ """Format log message for policy retry."""
464
+ attempt_info = f" (attempt {self.attempt}"
465
+ if self.max_attempts:
466
+ attempt_info += f"/{self.max_attempts}"
467
+ attempt_info += ")"
468
+ delay_info = f" with {self.delay_ms:.0f}ms delay" if self.delay_ms else ""
469
+ return (
470
+ f"🔁 Policy '{self.policy_name}' retrying node "
471
+ f"'{self.node_name}'{attempt_info}{delay_info}"
472
+ )
473
+
474
+
475
+ # Checkpoint events
476
+ @dataclass(slots=True)
477
+ class CheckpointSaved(Event):
478
+ """Emitted when a checkpoint is saved.
479
+
480
+ Attributes
481
+ ----------
482
+ run_id : str
483
+ The unique run identifier
484
+ dag_id : str
485
+ The DAG identifier
486
+ node_id : str
487
+ The node that triggered the checkpoint
488
+ checkpoint_type : str
489
+ Type of checkpoint ("node_completed", "wave_completed", "pipeline_paused")
490
+ data : dict[str, Any]
491
+ Additional checkpoint metadata
492
+ """
493
+
494
+ run_id: str
495
+ dag_id: str
496
+ node_id: str
497
+ checkpoint_type: str
498
+ data: dict[str, Any] = field(default_factory=dict)
499
+
500
+ def log_message(self) -> str:
501
+ """Format log message for checkpoint save event."""
502
+ return (
503
+ f"💾 Checkpoint saved for run '{self.run_id}' "
504
+ f"(node: {self.node_id}, type: {self.checkpoint_type})"
505
+ )
506
+
507
+
508
+ @dataclass(slots=True)
509
+ class CheckpointRestored(Event):
510
+ """Emitted when execution resumes from a checkpoint.
511
+
512
+ Attributes
513
+ ----------
514
+ run_id : str
515
+ The unique run identifier
516
+ dag_id : str
517
+ The DAG identifier
518
+ node_id : str
519
+ The node where execution is resuming
520
+ restored_nodes : list[str]
521
+ List of nodes already completed (skipped on resume)
522
+ restored_at : str
523
+ ISO timestamp of restore operation
524
+ """
525
+
526
+ run_id: str
527
+ dag_id: str
528
+ node_id: str
529
+ restored_nodes: list[str]
530
+ restored_at: str
531
+
532
+ def log_message(self) -> str:
533
+ """Format log message for checkpoint restore event."""
534
+ return (
535
+ f"🔄 Checkpoint restored for run '{self.run_id}' "
536
+ f"({len(self.restored_nodes)} nodes already completed)"
537
+ )
538
+
539
+
540
+ # Health check events
541
+ @dataclass(slots=True)
542
+ class HealthCheckCompleted(Event):
543
+ """Adapter health check completed.
544
+
545
+ Emitted when an adapter's ahealth_check() method completes during pre-DAG hooks.
546
+
547
+ Attributes
548
+ ----------
549
+ adapter_name : str
550
+ Name of the adapter that was checked
551
+ port_name : str
552
+ Name of the port this adapter implements
553
+ status : HealthStatus
554
+ Health check result with status and details
555
+ """
556
+
557
+ adapter_name: str
558
+ port_name: str
559
+ status: Any # HealthStatus, but using Any to avoid circular import
560
+
561
+ def log_message(self) -> str:
562
+ """Format log message for health check event."""
563
+ status_emoji = {
564
+ "healthy": "✅",
565
+ "degraded": "âš ī¸",
566
+ "unhealthy": "❌",
567
+ }.get(getattr(self.status, "status", "unknown"), "â„šī¸")
568
+
569
+ latency = ""
570
+ if hasattr(self.status, "latency_ms") and self.status.latency_ms:
571
+ latency = f" ({self.status.latency_ms:.1f}ms)"
572
+
573
+ return f"{status_emoji} Health check [{self.port_name}]: {self.status.status}{latency}"
@@ -0,0 +1,30 @@
1
+ """Observer implementations for the hexDAG event system.
2
+
3
+ This module provides core observer implementations for common monitoring
4
+ and observability use cases.
5
+ """
6
+
7
+ from .core_observers import (
8
+ AlertingObserver,
9
+ DataQualityObserver,
10
+ ExecutionTracerObserver,
11
+ PerformanceMetricsObserver,
12
+ ResourceMonitorObserver,
13
+ SimpleLoggingObserver,
14
+ )
15
+ from .models import Alert, AlertSeverity, AlertType, NodeMetrics
16
+
17
+ __all__ = [
18
+ # Core Observers
19
+ "PerformanceMetricsObserver",
20
+ "AlertingObserver",
21
+ "ExecutionTracerObserver",
22
+ "SimpleLoggingObserver",
23
+ "ResourceMonitorObserver",
24
+ "DataQualityObserver",
25
+ # Observer Models
26
+ "Alert",
27
+ "AlertType",
28
+ "AlertSeverity",
29
+ "NodeMetrics",
30
+ ]