kailash 0.1.5__py3-none-any.whl → 0.2.1__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 (77) hide show
  1. kailash/__init__.py +1 -1
  2. kailash/access_control.py +740 -0
  3. kailash/api/__main__.py +6 -0
  4. kailash/api/auth.py +668 -0
  5. kailash/api/custom_nodes.py +285 -0
  6. kailash/api/custom_nodes_secure.py +377 -0
  7. kailash/api/database.py +620 -0
  8. kailash/api/studio.py +915 -0
  9. kailash/api/studio_secure.py +893 -0
  10. kailash/mcp/__init__.py +53 -0
  11. kailash/mcp/__main__.py +13 -0
  12. kailash/mcp/ai_registry_server.py +712 -0
  13. kailash/mcp/client.py +447 -0
  14. kailash/mcp/client_new.py +334 -0
  15. kailash/mcp/server.py +293 -0
  16. kailash/mcp/server_new.py +336 -0
  17. kailash/mcp/servers/__init__.py +12 -0
  18. kailash/mcp/servers/ai_registry.py +289 -0
  19. kailash/nodes/__init__.py +4 -2
  20. kailash/nodes/ai/__init__.py +2 -0
  21. kailash/nodes/ai/a2a.py +714 -67
  22. kailash/nodes/ai/intelligent_agent_orchestrator.py +31 -37
  23. kailash/nodes/ai/iterative_llm_agent.py +1280 -0
  24. kailash/nodes/ai/llm_agent.py +324 -1
  25. kailash/nodes/ai/self_organizing.py +5 -6
  26. kailash/nodes/base.py +15 -2
  27. kailash/nodes/base_async.py +45 -0
  28. kailash/nodes/base_cycle_aware.py +374 -0
  29. kailash/nodes/base_with_acl.py +338 -0
  30. kailash/nodes/code/python.py +135 -27
  31. kailash/nodes/data/__init__.py +1 -2
  32. kailash/nodes/data/readers.py +16 -6
  33. kailash/nodes/data/sql.py +699 -256
  34. kailash/nodes/data/writers.py +16 -6
  35. kailash/nodes/logic/__init__.py +8 -0
  36. kailash/nodes/logic/convergence.py +642 -0
  37. kailash/nodes/logic/loop.py +153 -0
  38. kailash/nodes/logic/operations.py +187 -27
  39. kailash/nodes/mixins/__init__.py +11 -0
  40. kailash/nodes/mixins/mcp.py +228 -0
  41. kailash/nodes/mixins.py +387 -0
  42. kailash/runtime/__init__.py +2 -1
  43. kailash/runtime/access_controlled.py +458 -0
  44. kailash/runtime/local.py +106 -33
  45. kailash/runtime/parallel_cyclic.py +529 -0
  46. kailash/sdk_exceptions.py +90 -5
  47. kailash/security.py +845 -0
  48. kailash/tracking/manager.py +38 -15
  49. kailash/tracking/models.py +1 -1
  50. kailash/tracking/storage/filesystem.py +30 -2
  51. kailash/utils/__init__.py +8 -0
  52. kailash/workflow/__init__.py +18 -0
  53. kailash/workflow/convergence.py +270 -0
  54. kailash/workflow/cycle_analyzer.py +889 -0
  55. kailash/workflow/cycle_builder.py +579 -0
  56. kailash/workflow/cycle_config.py +725 -0
  57. kailash/workflow/cycle_debugger.py +860 -0
  58. kailash/workflow/cycle_exceptions.py +615 -0
  59. kailash/workflow/cycle_profiler.py +741 -0
  60. kailash/workflow/cycle_state.py +338 -0
  61. kailash/workflow/cyclic_runner.py +985 -0
  62. kailash/workflow/graph.py +500 -39
  63. kailash/workflow/migration.py +809 -0
  64. kailash/workflow/safety.py +365 -0
  65. kailash/workflow/templates.py +763 -0
  66. kailash/workflow/validation.py +751 -0
  67. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/METADATA +259 -12
  68. kailash-0.2.1.dist-info/RECORD +125 -0
  69. kailash/nodes/mcp/__init__.py +0 -11
  70. kailash/nodes/mcp/client.py +0 -554
  71. kailash/nodes/mcp/resource.py +0 -682
  72. kailash/nodes/mcp/server.py +0 -577
  73. kailash-0.1.5.dist-info/RECORD +0 -88
  74. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/WHEEL +0 -0
  75. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/entry_points.txt +0 -0
  76. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/licenses/LICENSE +0 -0
  77. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,860 @@
1
+ """
2
+ Comprehensive Debugging and Introspection for Cyclic Workflows.
3
+
4
+ This module provides extensive debugging and introspection capabilities for
5
+ cyclic workflows, enabling developers to understand cycle behavior, analyze
6
+ performance characteristics, and diagnose issues during both development and
7
+ production phases with detailed execution tracking and rich analytics.
8
+
9
+ Examples:
10
+ Basic debugging setup:
11
+
12
+ >>> debugger = CycleDebugger(
13
+ ... debug_level="detailed",
14
+ ... enable_profiling=True
15
+ ... )
16
+ >>> # Start cycle debugging
17
+ >>> trace = debugger.start_cycle(
18
+ ... "optimization_cycle",
19
+ ... "workflow_1",
20
+ ... max_iterations=100
21
+ ... )
22
+
23
+ Iteration tracking:
24
+
25
+ >>> # Track each iteration
26
+ >>> iteration = debugger.start_iteration(trace, input_data)
27
+ >>> # ... cycle execution ...
28
+ >>> debugger.end_iteration(
29
+ ... trace, iteration, output_data,
30
+ ... convergence_value=0.05,
31
+ ... node_executions=["processor", "evaluator"]
32
+ ... )
33
+
34
+ Analysis and reporting:
35
+
36
+ >>> # Complete cycle
37
+ >>> debugger.end_cycle(
38
+ ... trace, converged=True,
39
+ ... termination_reason="convergence",
40
+ ... convergence_iteration=15
41
+ ... )
42
+ >>> # Generate comprehensive report
43
+ >>> report = debugger.generate_report(trace)
44
+ >>> # Export for external analysis
45
+ >>> debugger.export_trace(trace, "debug_output.json", "json")
46
+ """
47
+
48
+ import json
49
+ import logging
50
+ from dataclasses import dataclass, field
51
+ from datetime import datetime
52
+ from typing import Any, Dict, List, Optional, Tuple
53
+
54
+ logger = logging.getLogger(__name__)
55
+
56
+
57
+ @dataclass
58
+ class CycleIteration:
59
+ """
60
+ Represents a single iteration within a cycle execution.
61
+
62
+ This class captures all relevant information about a cycle iteration,
63
+ including input/output data, execution time, memory usage, and any
64
+ errors that occurred during execution.
65
+
66
+ Attributes:
67
+ iteration_number: The iteration count (starting from 1).
68
+ start_time: When this iteration began execution.
69
+ end_time: When this iteration completed.
70
+ execution_time: Duration in seconds.
71
+ input_data: Input data for this iteration.
72
+ output_data: Output data from this iteration.
73
+ memory_usage_mb: Memory usage in megabytes.
74
+ cpu_usage_percent: CPU usage percentage.
75
+ convergence_value: Convergence metric if available.
76
+ error: Error message if iteration failed.
77
+ node_executions: List of nodes executed in this iteration.
78
+ """
79
+
80
+ iteration_number: int
81
+ start_time: datetime
82
+ end_time: Optional[datetime] = None
83
+ execution_time: Optional[float] = None
84
+ input_data: Dict[str, Any] = field(default_factory=dict)
85
+ output_data: Optional[Dict[str, Any]] = None
86
+ memory_usage_mb: Optional[float] = None
87
+ cpu_usage_percent: Optional[float] = None
88
+ convergence_value: Optional[float] = None
89
+ error: Optional[str] = None
90
+ node_executions: List[str] = field(default_factory=list)
91
+
92
+ def complete(
93
+ self, output_data: Dict[str, Any], convergence_value: Optional[float] = None
94
+ ):
95
+ """
96
+ Mark iteration as complete with output data.
97
+
98
+ Args:
99
+ output_data: The output data from this iteration.
100
+ convergence_value: Convergence metric if available.
101
+ """
102
+ self.end_time = datetime.now()
103
+ self.execution_time = (self.end_time - self.start_time).total_seconds()
104
+ self.output_data = output_data
105
+ self.convergence_value = convergence_value
106
+
107
+ def fail(self, error: str):
108
+ """
109
+ Mark iteration as failed with error message.
110
+
111
+ Args:
112
+ error: Error message describing the failure.
113
+ """
114
+ self.end_time = datetime.now()
115
+ self.execution_time = (self.end_time - self.start_time).total_seconds()
116
+ self.error = error
117
+
118
+ def is_completed(self) -> bool:
119
+ """Check if iteration has completed (successfully or with error).
120
+
121
+ Returns:
122
+ True if iteration has completed.
123
+ """
124
+ return self.end_time is not None
125
+
126
+ def is_successful(self) -> bool:
127
+ """Check if iteration completed successfully.
128
+
129
+ Returns:
130
+ True if iteration completed without error.
131
+ """
132
+ return self.end_time is not None and self.error is None
133
+
134
+ def to_dict(self) -> Dict[str, Any]:
135
+ """Convert iteration to dictionary for serialization.
136
+
137
+ Returns:
138
+ Dictionary representation of the iteration.
139
+ """
140
+ return {
141
+ "iteration_number": self.iteration_number,
142
+ "start_time": self.start_time.isoformat(),
143
+ "end_time": self.end_time.isoformat() if self.end_time else None,
144
+ "execution_time": self.execution_time,
145
+ "memory_usage_mb": self.memory_usage_mb,
146
+ "cpu_usage_percent": self.cpu_usage_percent,
147
+ "convergence_value": self.convergence_value,
148
+ "error": self.error,
149
+ "node_executions": self.node_executions,
150
+ "input_size": len(str(self.input_data)) if self.input_data else 0,
151
+ "output_size": len(str(self.output_data)) if self.output_data else 0,
152
+ }
153
+
154
+
155
+ @dataclass
156
+ class CycleExecutionTrace:
157
+ """
158
+ Complete execution trace for a cycle, containing all iterations and metadata.
159
+
160
+ This class provides a comprehensive record of cycle execution, including
161
+ all iterations, overall statistics, convergence analysis, and performance
162
+ metrics. It serves as the primary data structure for cycle debugging.
163
+
164
+ Attributes:
165
+ cycle_id: Unique identifier for the cycle.
166
+ workflow_id: Parent workflow identifier.
167
+ start_time: When cycle execution began.
168
+ end_time: When cycle execution completed.
169
+ total_execution_time: Total duration in seconds.
170
+ iterations: All iterations executed.
171
+ converged: Whether cycle converged successfully.
172
+ convergence_iteration: Iteration where convergence occurred.
173
+ termination_reason: Why the cycle terminated.
174
+ max_iterations_configured: Configured iteration limit.
175
+ timeout_configured: Configured timeout limit.
176
+ convergence_condition: Configured convergence condition.
177
+ """
178
+
179
+ cycle_id: str
180
+ workflow_id: str
181
+ start_time: datetime
182
+ end_time: Optional[datetime] = None
183
+ total_execution_time: Optional[float] = None
184
+ iterations: List[CycleIteration] = field(default_factory=list)
185
+ converged: bool = False
186
+ convergence_iteration: Optional[int] = None
187
+ termination_reason: str = "unknown"
188
+ max_iterations_configured: Optional[int] = None
189
+ timeout_configured: Optional[float] = None
190
+ convergence_condition: Optional[str] = None
191
+ memory_peak_mb: Optional[float] = None
192
+ cpu_peak_percent: Optional[float] = None
193
+
194
+ def add_iteration(self, iteration: CycleIteration):
195
+ """
196
+ Add an iteration to the trace.
197
+
198
+ Args:
199
+ iteration: The iteration to add.
200
+ """
201
+ self.iterations.append(iteration)
202
+
203
+ # Update peak metrics
204
+ if iteration.memory_usage_mb and (
205
+ not self.memory_peak_mb or iteration.memory_usage_mb > self.memory_peak_mb
206
+ ):
207
+ self.memory_peak_mb = iteration.memory_usage_mb
208
+
209
+ if iteration.cpu_usage_percent and (
210
+ not self.cpu_peak_percent
211
+ or iteration.cpu_usage_percent > self.cpu_peak_percent
212
+ ):
213
+ self.cpu_peak_percent = iteration.cpu_usage_percent
214
+
215
+ def complete(
216
+ self,
217
+ converged: bool,
218
+ termination_reason: str,
219
+ convergence_iteration: Optional[int] = None,
220
+ ):
221
+ """
222
+ Mark cycle execution as complete.
223
+
224
+ Args:
225
+ converged: Whether the cycle converged successfully.
226
+ termination_reason: Why the cycle terminated.
227
+ convergence_iteration: Iteration where convergence occurred.
228
+ """
229
+ self.end_time = datetime.now()
230
+ self.total_execution_time = (self.end_time - self.start_time).total_seconds()
231
+ self.converged = converged
232
+ self.termination_reason = termination_reason
233
+ self.convergence_iteration = convergence_iteration
234
+
235
+ def get_statistics(self) -> Dict[str, Any]:
236
+ """
237
+ Get comprehensive statistics about the cycle execution.
238
+
239
+ Returns:
240
+ Statistics including timing, convergence, and performance metrics.
241
+
242
+ Examples:
243
+ >>> stats = trace.get_statistics()
244
+ >>> print(f"Average iteration time: {stats['avg_iteration_time']:.3f}s")
245
+ """
246
+ if not self.iterations:
247
+ return {
248
+ "total_iterations": 0,
249
+ "avg_iteration_time": 0.0,
250
+ "min_iteration_time": 0.0,
251
+ "max_iteration_time": 0.0,
252
+ "convergence_rate": 0.0,
253
+ "efficiency_score": 0.0,
254
+ }
255
+
256
+ # Calculate timing statistics
257
+ iteration_times = [
258
+ iter.execution_time
259
+ for iter in self.iterations
260
+ if iter.execution_time is not None
261
+ ]
262
+
263
+ stats = {
264
+ "total_iterations": len(self.iterations),
265
+ "successful_iterations": len(
266
+ [iter for iter in self.iterations if iter.is_successful()]
267
+ ),
268
+ "failed_iterations": len(
269
+ [iter for iter in self.iterations if iter.error is not None]
270
+ ),
271
+ "avg_iteration_time": (
272
+ sum(iteration_times) / len(iteration_times) if iteration_times else 0.0
273
+ ),
274
+ "min_iteration_time": min(iteration_times) if iteration_times else 0.0,
275
+ "max_iteration_time": max(iteration_times) if iteration_times else 0.0,
276
+ "total_execution_time": self.total_execution_time or 0.0,
277
+ "converged": self.converged,
278
+ "convergence_iteration": self.convergence_iteration,
279
+ "termination_reason": self.termination_reason,
280
+ "memory_peak_mb": self.memory_peak_mb,
281
+ "cpu_peak_percent": self.cpu_peak_percent,
282
+ }
283
+
284
+ # Calculate convergence rate (how quickly it converged relative to max_iterations)
285
+ if (
286
+ self.converged
287
+ and self.convergence_iteration
288
+ and self.max_iterations_configured
289
+ ):
290
+ stats["convergence_rate"] = (
291
+ self.convergence_iteration / self.max_iterations_configured
292
+ )
293
+ else:
294
+ stats["convergence_rate"] = 1.0 if self.converged else 0.0
295
+
296
+ # Calculate efficiency score (0-1, higher is better)
297
+ if self.max_iterations_configured and len(self.iterations) > 0:
298
+ iteration_efficiency = 1.0 - (
299
+ len(self.iterations) / self.max_iterations_configured
300
+ )
301
+ time_efficiency = (
302
+ 1.0
303
+ if not self.timeout_configured
304
+ else max(
305
+ 0.0,
306
+ 1.0 - (self.total_execution_time or 0) / self.timeout_configured,
307
+ )
308
+ )
309
+ convergence_bonus = 0.2 if self.converged else 0.0
310
+ stats["efficiency_score"] = min(
311
+ 1.0,
312
+ iteration_efficiency * 0.5 + time_efficiency * 0.3 + convergence_bonus,
313
+ )
314
+ else:
315
+ stats["efficiency_score"] = 0.5 if self.converged else 0.0
316
+
317
+ return stats
318
+
319
+ def get_convergence_trend(self) -> List[Tuple[int, Optional[float]]]:
320
+ """
321
+ Get convergence values over iterations for trend analysis.
322
+
323
+ Returns:
324
+ List[Tuple[int, Optional[float]]]: List of (iteration_number, convergence_value) pairs
325
+
326
+ Side Effects:
327
+ None - this is a pure calculation method
328
+
329
+ Example:
330
+ >>> trend = trace.get_convergence_trend()
331
+ >>> for iteration, value in trend:
332
+ ... print(f"Iteration {iteration}: {value}")
333
+ """
334
+ return [
335
+ (iter.iteration_number, iter.convergence_value) for iter in self.iterations
336
+ ]
337
+
338
+ def to_dict(self) -> Dict[str, Any]:
339
+ """Convert trace to dictionary for serialization."""
340
+ return {
341
+ "cycle_id": self.cycle_id,
342
+ "workflow_id": self.workflow_id,
343
+ "start_time": self.start_time.isoformat(),
344
+ "end_time": self.end_time.isoformat() if self.end_time else None,
345
+ "total_execution_time": self.total_execution_time,
346
+ "iterations": [iter.to_dict() for iter in self.iterations],
347
+ "converged": self.converged,
348
+ "convergence_iteration": self.convergence_iteration,
349
+ "termination_reason": self.termination_reason,
350
+ "max_iterations_configured": self.max_iterations_configured,
351
+ "timeout_configured": self.timeout_configured,
352
+ "convergence_condition": self.convergence_condition,
353
+ "memory_peak_mb": self.memory_peak_mb,
354
+ "cpu_peak_percent": self.cpu_peak_percent,
355
+ "statistics": self.get_statistics(),
356
+ }
357
+
358
+
359
+ class CycleDebugger:
360
+ """
361
+ Comprehensive debugging tool for cyclic workflow execution.
362
+
363
+ This class provides real-time debugging capabilities for cycles, including
364
+ iteration tracking, performance monitoring, convergence analysis, and
365
+ detailed execution tracing. It integrates with the cycle execution system
366
+ to provide insights into cycle behavior and performance.
367
+
368
+ Design Philosophy:
369
+ Provides non-intrusive debugging that doesn't affect cycle performance
370
+ in production. Offers multiple levels of debugging detail from basic
371
+ tracking to comprehensive profiling with rich analytics.
372
+
373
+ Upstream Dependencies:
374
+ - Used by CyclicWorkflowExecutor when debug mode is enabled
375
+ - Integrates with cycle configuration and execution systems
376
+
377
+ Downstream Consumers:
378
+ - Debug reports and analysis tools
379
+ - Performance optimization recommendations
380
+ - Cycle visualization and monitoring dashboards
381
+
382
+ Usage Patterns:
383
+ 1. Real-time debugging during development
384
+ 2. Performance profiling for optimization
385
+ 3. Production monitoring for cycle health
386
+ 4. Post-execution analysis for troubleshooting
387
+
388
+ Example:
389
+ >>> debugger = CycleDebugger(debug_level="detailed")
390
+ >>> trace = debugger.start_cycle("optimization", "workflow_1")
391
+ >>>
392
+ >>> # During cycle execution
393
+ >>> iteration = debugger.start_iteration(trace, input_data)
394
+ >>> debugger.end_iteration(iteration, output_data)
395
+ >>>
396
+ >>> # After cycle completion
397
+ >>> debugger.end_cycle(trace, converged=True, reason="convergence")
398
+ >>> report = debugger.generate_report(trace)
399
+ """
400
+
401
+ def __init__(self, debug_level: str = "basic", enable_profiling: bool = False):
402
+ """
403
+ Initialize cycle debugger.
404
+
405
+ Args:
406
+ debug_level (str): Level of debugging detail ("basic", "detailed", "verbose")
407
+ enable_profiling (bool): Whether to enable detailed profiling
408
+
409
+ Side Effects:
410
+ Configures logging and profiling settings
411
+ """
412
+ self.debug_level = debug_level
413
+ self.enable_profiling = enable_profiling
414
+ self.active_traces: Dict[str, CycleExecutionTrace] = {}
415
+
416
+ # Configure logging based on debug level
417
+ if debug_level == "verbose":
418
+ logger.setLevel(logging.DEBUG)
419
+ elif debug_level == "detailed":
420
+ logger.setLevel(logging.INFO)
421
+ else:
422
+ logger.setLevel(logging.WARNING)
423
+
424
+ def start_cycle(
425
+ self,
426
+ cycle_id: str,
427
+ workflow_id: str,
428
+ max_iterations: Optional[int] = None,
429
+ timeout: Optional[float] = None,
430
+ convergence_condition: Optional[str] = None,
431
+ ) -> CycleExecutionTrace:
432
+ """
433
+ Start debugging a new cycle execution.
434
+
435
+ Creates a new execution trace and begins tracking cycle execution
436
+ with all configured debugging features enabled.
437
+
438
+ Args:
439
+ cycle_id (str): Unique identifier for the cycle
440
+ workflow_id (str): Parent workflow identifier
441
+ max_iterations (Optional[int]): Configured iteration limit
442
+ timeout (Optional[float]): Configured timeout limit
443
+ convergence_condition (Optional[str]): Convergence condition expression
444
+
445
+ Returns:
446
+ CycleExecutionTrace: New trace object for tracking execution
447
+
448
+ Side Effects:
449
+ Creates new trace and adds to active_traces
450
+ Logs cycle start event
451
+
452
+ Example:
453
+ >>> trace = debugger.start_cycle("opt_cycle", "workflow_1", max_iterations=100)
454
+ """
455
+ trace = CycleExecutionTrace(
456
+ cycle_id=cycle_id,
457
+ workflow_id=workflow_id,
458
+ start_time=datetime.now(),
459
+ max_iterations_configured=max_iterations,
460
+ timeout_configured=timeout,
461
+ convergence_condition=convergence_condition,
462
+ )
463
+
464
+ self.active_traces[cycle_id] = trace
465
+
466
+ logger.info(
467
+ f"Started debugging cycle '{cycle_id}' in workflow '{workflow_id}' "
468
+ f"with max_iterations={max_iterations}, timeout={timeout}"
469
+ )
470
+
471
+ return trace
472
+
473
+ def start_iteration(
474
+ self,
475
+ trace: CycleExecutionTrace,
476
+ input_data: Dict[str, Any],
477
+ iteration_number: Optional[int] = None,
478
+ ) -> CycleIteration:
479
+ """
480
+ Start debugging a new cycle iteration.
481
+
482
+ Creates a new iteration object and begins tracking execution time,
483
+ resource usage, and other iteration-specific metrics.
484
+
485
+ Args:
486
+ trace (CycleExecutionTrace): Parent cycle trace
487
+ input_data (Dict[str, Any]): Input data for this iteration
488
+ iteration_number (Optional[int]): Iteration number (auto-calculated if None)
489
+
490
+ Returns:
491
+ CycleIteration: New iteration object for tracking
492
+
493
+ Side Effects:
494
+ Creates new iteration and adds to trace
495
+ Begins resource monitoring if profiling enabled
496
+
497
+ Example:
498
+ >>> iteration = debugger.start_iteration(trace, {"value": 10})
499
+ """
500
+ if iteration_number is None:
501
+ iteration_number = len(trace.iterations) + 1
502
+
503
+ iteration = CycleIteration(
504
+ iteration_number=iteration_number,
505
+ start_time=datetime.now(),
506
+ input_data=(
507
+ input_data.copy() if self.debug_level in ["detailed", "verbose"] else {}
508
+ ),
509
+ )
510
+
511
+ # Add profiling data if enabled
512
+ if self.enable_profiling:
513
+ try:
514
+ import psutil
515
+
516
+ process = psutil.Process()
517
+ iteration.memory_usage_mb = process.memory_info().rss / 1024 / 1024
518
+ iteration.cpu_usage_percent = process.cpu_percent()
519
+ except ImportError:
520
+ logger.warning(
521
+ "psutil not available for profiling. Install with: pip install psutil"
522
+ )
523
+
524
+ if self.debug_level == "verbose":
525
+ logger.debug(
526
+ f"Started iteration {iteration_number} for cycle '{trace.cycle_id}' "
527
+ f"with input keys: {list(input_data.keys())}"
528
+ )
529
+
530
+ return iteration
531
+
532
+ def end_iteration(
533
+ self,
534
+ trace: CycleExecutionTrace,
535
+ iteration: CycleIteration,
536
+ output_data: Dict[str, Any],
537
+ convergence_value: Optional[float] = None,
538
+ node_executions: Optional[List[str]] = None,
539
+ ):
540
+ """
541
+ Complete iteration tracking with output data and metrics.
542
+
543
+ Finalizes iteration tracking by recording output data, convergence
544
+ metrics, and final resource usage measurements.
545
+
546
+ Args:
547
+ trace (CycleExecutionTrace): Parent cycle trace
548
+ iteration (CycleIteration): Iteration object to complete
549
+ output_data (Dict[str, Any]): Output data from iteration
550
+ convergence_value (Optional[float]): Convergence metric if available
551
+ node_executions (Optional[List[str]]): List of executed nodes
552
+
553
+ Side Effects:
554
+ Completes iteration and adds to trace
555
+ Updates peak resource usage in trace
556
+ Logs iteration completion
557
+
558
+ Example:
559
+ >>> debugger.end_iteration(trace, iteration, {"result": 20}, convergence_value=0.05)
560
+ """
561
+ iteration.complete(
562
+ output_data.copy() if self.debug_level in ["detailed", "verbose"] else {},
563
+ convergence_value,
564
+ )
565
+
566
+ if node_executions:
567
+ iteration.node_executions = node_executions
568
+
569
+ # Update profiling data if enabled
570
+ if self.enable_profiling:
571
+ try:
572
+ import psutil
573
+
574
+ process = psutil.Process()
575
+ end_memory = process.memory_info().rss / 1024 / 1024
576
+ end_cpu = process.cpu_percent()
577
+
578
+ # Use the higher value for this iteration
579
+ iteration.memory_usage_mb = max(
580
+ iteration.memory_usage_mb or 0, end_memory
581
+ )
582
+ iteration.cpu_usage_percent = max(
583
+ iteration.cpu_usage_percent or 0, end_cpu
584
+ )
585
+ except ImportError:
586
+ pass # Already warned during start_iteration
587
+
588
+ trace.add_iteration(iteration)
589
+
590
+ if self.debug_level in ["detailed", "verbose"]:
591
+ logger.info(
592
+ f"Completed iteration {iteration.iteration_number} for cycle '{trace.cycle_id}' "
593
+ f"in {iteration.execution_time:.3f}s, convergence={convergence_value}"
594
+ )
595
+
596
+ def end_cycle(
597
+ self,
598
+ trace: CycleExecutionTrace,
599
+ converged: bool,
600
+ termination_reason: str,
601
+ convergence_iteration: Optional[int] = None,
602
+ ):
603
+ """
604
+ Complete cycle tracking with final results and analysis.
605
+
606
+ Finalizes cycle execution tracking and generates comprehensive
607
+ statistics and analysis for the complete cycle execution.
608
+
609
+ Args:
610
+ trace (CycleExecutionTrace): Cycle trace to complete
611
+ converged (bool): Whether the cycle converged successfully
612
+ termination_reason (str): Why the cycle terminated
613
+ convergence_iteration (Optional[int]): Iteration where convergence occurred
614
+
615
+ Side Effects:
616
+ Completes trace and removes from active_traces
617
+ Logs cycle completion with statistics
618
+
619
+ Example:
620
+ >>> debugger.end_cycle(trace, converged=True, termination_reason="convergence", convergence_iteration=15)
621
+ """
622
+ trace.complete(converged, termination_reason, convergence_iteration)
623
+
624
+ # Remove from active traces
625
+ if trace.cycle_id in self.active_traces:
626
+ del self.active_traces[trace.cycle_id]
627
+
628
+ stats = trace.get_statistics()
629
+ logger.info(
630
+ f"Completed cycle '{trace.cycle_id}' in {trace.total_execution_time:.3f}s "
631
+ f"with {stats['total_iterations']} iterations, "
632
+ f"converged={converged}, efficiency={stats['efficiency_score']:.2f}"
633
+ )
634
+
635
+ def generate_report(self, trace: CycleExecutionTrace) -> Dict[str, Any]:
636
+ """
637
+ Generate comprehensive debugging report for a cycle execution.
638
+
639
+ Creates a detailed report including execution statistics, performance
640
+ analysis, convergence trends, and optimization recommendations based
641
+ on the complete cycle execution trace.
642
+
643
+ Args:
644
+ trace (CycleExecutionTrace): Completed cycle trace to analyze
645
+
646
+ Returns:
647
+ Dict[str, Any]: Comprehensive debugging report
648
+
649
+ Side Effects:
650
+ None - this is a pure analysis method
651
+
652
+ Example:
653
+ >>> report = debugger.generate_report(trace)
654
+ >>> print(f"Efficiency score: {report['performance']['efficiency_score']}")
655
+ """
656
+ stats = trace.get_statistics()
657
+ convergence_trend = trace.get_convergence_trend()
658
+
659
+ # Analyze convergence pattern
660
+ convergence_analysis = self._analyze_convergence(convergence_trend)
661
+
662
+ # Generate optimization recommendations
663
+ recommendations = self._generate_recommendations(trace, stats)
664
+
665
+ # Create performance summary
666
+ performance = {
667
+ "efficiency_score": stats["efficiency_score"],
668
+ "avg_iteration_time": stats["avg_iteration_time"],
669
+ "convergence_rate": stats["convergence_rate"],
670
+ "resource_usage": {
671
+ "memory_peak_mb": trace.memory_peak_mb,
672
+ "cpu_peak_percent": trace.cpu_peak_percent,
673
+ },
674
+ }
675
+
676
+ report = {
677
+ "cycle_info": {
678
+ "cycle_id": trace.cycle_id,
679
+ "workflow_id": trace.workflow_id,
680
+ "execution_time": trace.total_execution_time,
681
+ "converged": trace.converged,
682
+ "termination_reason": trace.termination_reason,
683
+ },
684
+ "statistics": stats,
685
+ "performance": performance,
686
+ "convergence_analysis": convergence_analysis,
687
+ "recommendations": recommendations,
688
+ "trace_data": trace.to_dict() if self.debug_level == "verbose" else None,
689
+ }
690
+
691
+ return report
692
+
693
+ def _analyze_convergence(
694
+ self, convergence_trend: List[Tuple[int, Optional[float]]]
695
+ ) -> Dict[str, Any]:
696
+ """Analyze convergence pattern from trend data."""
697
+ if not convergence_trend or all(
698
+ value is None for _, value in convergence_trend
699
+ ):
700
+ return {"pattern": "no_data", "analysis": "No convergence data available"}
701
+
702
+ # Filter out None values
703
+ valid_points = [
704
+ (iter_num, value)
705
+ for iter_num, value in convergence_trend
706
+ if value is not None
707
+ ]
708
+
709
+ if len(valid_points) < 2:
710
+ return {
711
+ "pattern": "insufficient_data",
712
+ "analysis": "Insufficient convergence data for analysis",
713
+ }
714
+
715
+ # Analyze trend
716
+ values = [value for _, value in valid_points]
717
+
718
+ # Check for improvement pattern
719
+ if len(values) >= 3:
720
+ improving_count = sum(
721
+ 1 for i in range(1, len(values)) if values[i] < values[i - 1]
722
+ )
723
+ improvement_ratio = improving_count / (len(values) - 1)
724
+
725
+ if improvement_ratio > 0.8:
726
+ pattern = "steady_improvement"
727
+ analysis = "Convergence is steadily improving"
728
+ elif improvement_ratio > 0.5:
729
+ pattern = "gradual_improvement"
730
+ analysis = "Convergence is gradually improving with some fluctuation"
731
+ elif improvement_ratio < 0.2:
732
+ pattern = "plateau"
733
+ analysis = "Convergence has plateaued - may need different approach"
734
+ else:
735
+ pattern = "unstable"
736
+ analysis = "Convergence is unstable - check algorithm parameters"
737
+ else:
738
+ pattern = "limited_data"
739
+ analysis = "Limited data for pattern analysis"
740
+
741
+ return {
742
+ "pattern": pattern,
743
+ "analysis": analysis,
744
+ "initial_value": values[0] if values else None,
745
+ "final_value": values[-1] if values else None,
746
+ "improvement": values[0] - values[-1] if len(values) >= 2 else None,
747
+ "data_points": len(valid_points),
748
+ }
749
+
750
+ def _generate_recommendations(
751
+ self, trace: CycleExecutionTrace, stats: Dict[str, Any]
752
+ ) -> List[str]:
753
+ """Generate optimization recommendations based on execution analysis."""
754
+ recommendations = []
755
+
756
+ # Efficiency recommendations
757
+ if stats["efficiency_score"] < 0.5:
758
+ recommendations.append(
759
+ "Consider reducing max_iterations or improving convergence condition"
760
+ )
761
+
762
+ # Convergence recommendations
763
+ if not trace.converged:
764
+ if stats["total_iterations"] >= (trace.max_iterations_configured or 0):
765
+ recommendations.append(
766
+ "Cycle reached max_iterations without converging - increase limit or improve algorithm"
767
+ )
768
+ else:
769
+ recommendations.append(
770
+ "Cycle terminated early - check for errors or timeout issues"
771
+ )
772
+
773
+ # Performance recommendations
774
+ if stats["avg_iteration_time"] > 1.0:
775
+ recommendations.append(
776
+ "Average iteration time is high - consider optimizing node performance"
777
+ )
778
+
779
+ if trace.memory_peak_mb and trace.memory_peak_mb > 1000:
780
+ recommendations.append(
781
+ "High memory usage detected - consider data streaming or chunking"
782
+ )
783
+
784
+ # Convergence pattern recommendations
785
+ convergence_trend = trace.get_convergence_trend()
786
+ convergence_analysis = self._analyze_convergence(convergence_trend)
787
+
788
+ if convergence_analysis["pattern"] == "plateau":
789
+ recommendations.append(
790
+ "Convergence plateaued - try different learning rate or algorithm parameters"
791
+ )
792
+ elif convergence_analysis["pattern"] == "unstable":
793
+ recommendations.append(
794
+ "Unstable convergence - reduce learning rate or add regularization"
795
+ )
796
+
797
+ # Success recommendations
798
+ if trace.converged and stats["efficiency_score"] > 0.8:
799
+ recommendations.append(
800
+ "Excellent cycle performance - consider using as template for similar workflows"
801
+ )
802
+
803
+ return recommendations
804
+
805
+ def export_trace(
806
+ self, trace: CycleExecutionTrace, filepath: str, format: str = "json"
807
+ ):
808
+ """
809
+ Export cycle trace to file for external analysis.
810
+
811
+ Args:
812
+ trace (CycleExecutionTrace): Trace to export
813
+ filepath (str): Output file path
814
+ format (str): Export format ("json", "csv")
815
+
816
+ Side Effects:
817
+ Creates file at specified path with trace data
818
+
819
+ Example:
820
+ >>> debugger.export_trace(trace, "cycle_debug.json", "json")
821
+ """
822
+ trace_data = trace.to_dict()
823
+
824
+ if format == "json":
825
+ with open(filepath, "w") as f:
826
+ json.dump(trace_data, f, indent=2)
827
+ elif format == "csv":
828
+ import csv
829
+
830
+ with open(filepath, "w", newline="") as f:
831
+ writer = csv.writer(f)
832
+
833
+ # Write header
834
+ writer.writerow(
835
+ [
836
+ "iteration",
837
+ "execution_time",
838
+ "memory_mb",
839
+ "cpu_percent",
840
+ "convergence",
841
+ "error",
842
+ ]
843
+ )
844
+
845
+ # Write iteration data
846
+ for iteration in trace.iterations:
847
+ writer.writerow(
848
+ [
849
+ iteration.iteration_number,
850
+ iteration.execution_time,
851
+ iteration.memory_usage_mb,
852
+ iteration.cpu_usage_percent,
853
+ iteration.convergence_value,
854
+ iteration.error or "",
855
+ ]
856
+ )
857
+ else:
858
+ raise ValueError(f"Unsupported export format: {format}")
859
+
860
+ logger.info(f"Exported cycle trace to {filepath} in {format} format")