kailash 0.1.4__py3-none-any.whl → 0.2.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 (83) 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 +38 -0
  21. kailash/nodes/ai/a2a.py +1790 -0
  22. kailash/nodes/ai/agents.py +116 -2
  23. kailash/nodes/ai/ai_providers.py +206 -8
  24. kailash/nodes/ai/intelligent_agent_orchestrator.py +2108 -0
  25. kailash/nodes/ai/iterative_llm_agent.py +1280 -0
  26. kailash/nodes/ai/llm_agent.py +324 -1
  27. kailash/nodes/ai/self_organizing.py +1623 -0
  28. kailash/nodes/api/http.py +106 -25
  29. kailash/nodes/api/rest.py +116 -21
  30. kailash/nodes/base.py +15 -2
  31. kailash/nodes/base_async.py +45 -0
  32. kailash/nodes/base_cycle_aware.py +374 -0
  33. kailash/nodes/base_with_acl.py +338 -0
  34. kailash/nodes/code/python.py +135 -27
  35. kailash/nodes/data/readers.py +116 -53
  36. kailash/nodes/data/writers.py +16 -6
  37. kailash/nodes/logic/__init__.py +8 -0
  38. kailash/nodes/logic/async_operations.py +48 -9
  39. kailash/nodes/logic/convergence.py +642 -0
  40. kailash/nodes/logic/loop.py +153 -0
  41. kailash/nodes/logic/operations.py +212 -27
  42. kailash/nodes/logic/workflow.py +26 -18
  43. kailash/nodes/mixins/__init__.py +11 -0
  44. kailash/nodes/mixins/mcp.py +228 -0
  45. kailash/nodes/mixins.py +387 -0
  46. kailash/nodes/transform/__init__.py +8 -1
  47. kailash/nodes/transform/processors.py +119 -4
  48. kailash/runtime/__init__.py +2 -1
  49. kailash/runtime/access_controlled.py +458 -0
  50. kailash/runtime/local.py +106 -33
  51. kailash/runtime/parallel_cyclic.py +529 -0
  52. kailash/sdk_exceptions.py +90 -5
  53. kailash/security.py +845 -0
  54. kailash/tracking/manager.py +38 -15
  55. kailash/tracking/models.py +1 -1
  56. kailash/tracking/storage/filesystem.py +30 -2
  57. kailash/utils/__init__.py +8 -0
  58. kailash/workflow/__init__.py +18 -0
  59. kailash/workflow/convergence.py +270 -0
  60. kailash/workflow/cycle_analyzer.py +768 -0
  61. kailash/workflow/cycle_builder.py +573 -0
  62. kailash/workflow/cycle_config.py +709 -0
  63. kailash/workflow/cycle_debugger.py +760 -0
  64. kailash/workflow/cycle_exceptions.py +601 -0
  65. kailash/workflow/cycle_profiler.py +671 -0
  66. kailash/workflow/cycle_state.py +338 -0
  67. kailash/workflow/cyclic_runner.py +985 -0
  68. kailash/workflow/graph.py +500 -39
  69. kailash/workflow/migration.py +768 -0
  70. kailash/workflow/safety.py +365 -0
  71. kailash/workflow/templates.py +744 -0
  72. kailash/workflow/validation.py +693 -0
  73. {kailash-0.1.4.dist-info → kailash-0.2.0.dist-info}/METADATA +446 -13
  74. kailash-0.2.0.dist-info/RECORD +125 -0
  75. kailash/nodes/mcp/__init__.py +0 -11
  76. kailash/nodes/mcp/client.py +0 -554
  77. kailash/nodes/mcp/resource.py +0 -682
  78. kailash/nodes/mcp/server.py +0 -577
  79. kailash-0.1.4.dist-info/RECORD +0 -85
  80. {kailash-0.1.4.dist-info → kailash-0.2.0.dist-info}/WHEEL +0 -0
  81. {kailash-0.1.4.dist-info → kailash-0.2.0.dist-info}/entry_points.txt +0 -0
  82. {kailash-0.1.4.dist-info → kailash-0.2.0.dist-info}/licenses/LICENSE +0 -0
  83. {kailash-0.1.4.dist-info → kailash-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,573 @@
1
+ """
2
+ Fluent API for Cyclic Workflow Creation.
3
+
4
+ This module provides an intuitive, chainable API for creating cyclic workflows
5
+ with enhanced developer experience and IDE support. The CycleBuilder pattern
6
+ makes cycle configuration more discoverable and type-safe while maintaining
7
+ full backward compatibility with existing workflow construction methods.
8
+
9
+ Examples:
10
+ Basic cycle creation:
11
+
12
+ >>> workflow = Workflow("optimization", "Optimization Loop")
13
+ >>> cycle = workflow.create_cycle("quality_improvement")
14
+ >>> cycle.connect("processor", "evaluator") \
15
+ ... .max_iterations(50) \
16
+ ... .converge_when("quality > 0.9") \
17
+ ... .timeout(300) \
18
+ ... .build()
19
+
20
+ Advanced configuration:
21
+
22
+ >>> cycle = workflow.create_cycle("complex_optimization") \
23
+ ... .connect("optimizer", "evaluator", {"result": "input_data"}) \
24
+ ... .max_iterations(100) \
25
+ ... .converge_when("improvement < 0.001") \
26
+ ... .timeout(600) \
27
+ ... .memory_limit(2048) \
28
+ ... .when("needs_optimization == True") \
29
+ ... .nested_in("outer_cycle") \
30
+ ... .build()
31
+
32
+ Template integration:
33
+
34
+ >>> from kailash.workflow.cycle_config import CycleTemplates
35
+ >>> config = CycleTemplates.optimization_loop(max_iterations=50)
36
+ >>> cycle = CycleBuilder.from_config(workflow, config) \
37
+ ... .connect("processor", "evaluator") \
38
+ ... .timeout(120) # Override template value
39
+ ... .build()
40
+ """
41
+
42
+ import logging
43
+ from typing import Dict, Optional, TYPE_CHECKING
44
+
45
+ from kailash.sdk_exceptions import WorkflowValidationError
46
+ from kailash.workflow.cycle_exceptions import (
47
+ CycleConfigurationError,
48
+ CycleConnectionError
49
+ )
50
+
51
+ if TYPE_CHECKING:
52
+ from .graph import Workflow
53
+ from .cycle_config import CycleConfig
54
+
55
+ logger = logging.getLogger(__name__)
56
+
57
+
58
+ class CycleBuilder:
59
+ """
60
+ Fluent builder for creating cyclic workflow connections.
61
+
62
+ This class provides an intuitive, chainable API for configuring cyclic
63
+ connections in workflows. It replaces the verbose parameter-heavy approach
64
+ with a more discoverable and type-safe builder pattern.
65
+
66
+ Examples:
67
+ Creating a basic cycle:
68
+
69
+ >>> workflow = Workflow("optimization", "Optimization Loop")
70
+ >>> cycle = workflow.create_cycle("quality_improvement")
71
+ >>> cycle.connect("processor", "evaluator") \
72
+ ... .max_iterations(50) \
73
+ ... .converge_when("quality > 0.9") \
74
+ ... .timeout(300) \
75
+ ... .build()
76
+ """
77
+
78
+ def __init__(self, workflow: "Workflow", cycle_id: Optional[str] = None):
79
+ """
80
+ Initialize a new CycleBuilder.
81
+
82
+ Args:
83
+ workflow: The workflow to add the cycle to.
84
+ cycle_id: Optional identifier for the cycle group.
85
+ """
86
+ self._workflow = workflow
87
+ self._cycle_id = cycle_id
88
+
89
+ # Connection parameters
90
+ self._source_node: Optional[str] = None
91
+ self._target_node: Optional[str] = None
92
+ self._mapping: Optional[Dict[str, str]] = None
93
+
94
+ # Cycle parameters
95
+ self._max_iterations: Optional[int] = None
96
+ self._convergence_check: Optional[str] = None
97
+ self._timeout: Optional[float] = None
98
+ self._memory_limit: Optional[int] = None
99
+ self._condition: Optional[str] = None
100
+ self._parent_cycle: Optional[str] = None
101
+
102
+ def connect(self, source_node: str, target_node: str,
103
+ mapping: Optional[Dict[str, str]] = None) -> "CycleBuilder":
104
+ """
105
+ Configure the source and target nodes for the cycle connection.
106
+
107
+ Establishes which nodes will be connected in a cyclic pattern. The
108
+ mapping parameter defines how outputs from the source node map to
109
+ inputs of the target node.
110
+
111
+ Args:
112
+ source_node: Node ID that produces output for the cycle.
113
+ target_node: Node ID that receives input from the cycle.
114
+ mapping: Output-to-input mapping. Keys are source output fields,
115
+ values are target input fields. If None, attempts automatic
116
+ mapping based on parameter names.
117
+
118
+ Returns:
119
+ Self for method chaining.
120
+
121
+ Raises:
122
+ WorkflowValidationError: If source or target node doesn't exist.
123
+ CycleConfigurationError: If nodes are invalid for cyclic connection.
124
+
125
+ Examples:
126
+ >>> cycle.connect("processor", "evaluator", {"result": "input_data"})
127
+ >>> # Or with automatic mapping
128
+ >>> cycle.connect("node_a", "node_b")
129
+ """
130
+ # Validate nodes exist in workflow
131
+ available_nodes = list(self._workflow.nodes.keys())
132
+
133
+ if source_node not in self._workflow.nodes:
134
+ raise CycleConnectionError(
135
+ f"Source node '{source_node}' not found in workflow",
136
+ source_node=source_node,
137
+ available_nodes=available_nodes,
138
+ error_code="CYCLE_CONN_001"
139
+ )
140
+
141
+ if target_node not in self._workflow.nodes:
142
+ raise CycleConnectionError(
143
+ f"Target node '{target_node}' not found in workflow",
144
+ target_node=target_node,
145
+ available_nodes=available_nodes,
146
+ error_code="CYCLE_CONN_002"
147
+ )
148
+
149
+ self._source_node = source_node
150
+ self._target_node = target_node
151
+ self._mapping = mapping
152
+
153
+ return self
154
+
155
+ def max_iterations(self, iterations: int) -> "CycleBuilder":
156
+ """
157
+ Set the maximum number of cycle iterations for safety.
158
+
159
+ Provides a hard limit on cycle execution to prevent infinite loops.
160
+ This is a critical safety mechanism for production workflows.
161
+
162
+ Args:
163
+ iterations: Maximum number of iterations allowed. Must be positive.
164
+ Recommended range: 10-1000 depending on use case.
165
+
166
+ Returns:
167
+ Self for method chaining.
168
+
169
+ Raises:
170
+ CycleConfigurationError: If iterations is not positive.
171
+
172
+ Examples:
173
+ >>> cycle.max_iterations(100) # Allow up to 100 iterations
174
+ >>> cycle.max_iterations(10) # Quick convergence expected
175
+ """
176
+ if iterations <= 0:
177
+ raise CycleConfigurationError(
178
+ "max_iterations must be positive",
179
+ cycle_id=self._cycle_id,
180
+ invalid_params={"max_iterations": iterations},
181
+ error_code="CYCLE_CONFIG_002",
182
+ suggestions=[
183
+ "Use 10-100 iterations for quick convergence",
184
+ "Use 100-1000 iterations for complex optimization",
185
+ "Consider adding convergence_check for early termination"
186
+ ]
187
+ )
188
+
189
+ self._max_iterations = iterations
190
+ return self
191
+
192
+ def converge_when(self, condition: str) -> "CycleBuilder":
193
+ """
194
+ Set the convergence condition to terminate the cycle early.
195
+
196
+ Defines an expression that, when true, will stop cycle execution
197
+ before reaching max_iterations. This enables efficient early
198
+ termination when the desired result is achieved.
199
+
200
+ Args:
201
+ condition: Python expression evaluated against node outputs.
202
+ Can reference any output field from cycle nodes.
203
+ Examples: "error < 0.01", "quality > 0.9", "improvement < 0.001"
204
+
205
+ Returns:
206
+ Self for method chaining.
207
+
208
+ Raises:
209
+ CycleConfigurationError: If condition syntax is invalid.
210
+
211
+ Examples:
212
+ >>> cycle.converge_when("error < 0.01") # Numerical convergence
213
+ >>> cycle.converge_when("quality > 0.95") # Quality threshold
214
+ >>> cycle.converge_when("improvement < 0.001") # Minimal improvement
215
+ """
216
+ if not condition or not isinstance(condition, str):
217
+ raise CycleConfigurationError(
218
+ "Convergence condition must be a non-empty string expression. "
219
+ "Examples: 'error < 0.01', 'quality > 0.9', 'count >= 10'"
220
+ )
221
+
222
+ # Basic validation - check for dangerous operations
223
+ dangerous_patterns = ['import ', 'exec(', 'eval(', '__']
224
+ for pattern in dangerous_patterns:
225
+ if pattern in condition:
226
+ raise CycleConfigurationError(
227
+ f"Convergence condition contains potentially unsafe operation: '{pattern}'. "
228
+ "Use simple comparison expressions only."
229
+ )
230
+
231
+ self._convergence_check = condition
232
+ return self
233
+
234
+ def timeout(self, seconds: float) -> "CycleBuilder":
235
+ """
236
+ Set a timeout limit for cycle execution.
237
+
238
+ Provides time-based safety limit to prevent cycles from running
239
+ indefinitely. Useful for cycles that might have unpredictable
240
+ convergence times.
241
+
242
+ Args:
243
+ seconds: Maximum execution time in seconds. Must be positive.
244
+ Recommended: 30-3600 seconds.
245
+
246
+ Returns:
247
+ Self for method chaining.
248
+
249
+ Raises:
250
+ CycleConfigurationError: If timeout is not positive.
251
+
252
+ Examples:
253
+ >>> cycle.timeout(300) # 5 minutes maximum
254
+ >>> cycle.timeout(30.5) # 30.5 seconds for quick cycles
255
+ """
256
+ if seconds <= 0:
257
+ raise CycleConfigurationError(
258
+ f"Timeout must be positive, got {seconds}. "
259
+ "Recommendation: Use 30-300 seconds for most cycles, "
260
+ "up to 3600 seconds for long-running optimization."
261
+ )
262
+
263
+ self._timeout = seconds
264
+ return self
265
+
266
+ def memory_limit(self, mb: int) -> "CycleBuilder":
267
+ """
268
+ Set a memory usage limit for cycle execution.
269
+
270
+ Provides memory-based safety limit to prevent cycles from consuming
271
+ excessive memory through data accumulation across iterations.
272
+
273
+ Args:
274
+ mb: Maximum memory usage in megabytes. Must be positive.
275
+ Recommended: 100-10000 MB.
276
+
277
+ Returns:
278
+ Self for method chaining.
279
+
280
+ Raises:
281
+ CycleConfigurationError: If memory limit is not positive.
282
+
283
+ Examples:
284
+ >>> cycle.memory_limit(1024) # 1GB limit
285
+ >>> cycle.memory_limit(512) # 512MB for smaller workflows
286
+ """
287
+ if mb <= 0:
288
+ raise CycleConfigurationError(
289
+ f"Memory limit must be positive, got {mb}. "
290
+ "Recommendation: Use 100-1000 MB for most cycles, "
291
+ "up to 10000 MB for data-intensive processing."
292
+ )
293
+
294
+ self._memory_limit = mb
295
+ return self
296
+
297
+ def when(self, condition: str) -> "CycleBuilder":
298
+ """
299
+ Set a conditional expression for cycle routing.
300
+
301
+ Enables conditional cycle execution where the cycle only runs
302
+ when the specified condition is met. Useful for adaptive workflows.
303
+
304
+ Args:
305
+ condition: Python expression for conditional execution.
306
+ Evaluated before each cycle iteration.
307
+
308
+ Returns:
309
+ Self for method chaining.
310
+
311
+ Raises:
312
+ CycleConfigurationError: If condition syntax is invalid.
313
+
314
+ Examples:
315
+ >>> cycle.when("retry_count < 3") # Retry logic
316
+ >>> cycle.when("needs_optimization") # Conditional optimization
317
+ """
318
+ if not condition or not isinstance(condition, str):
319
+ raise CycleConfigurationError(
320
+ "Condition must be a non-empty string expression. "
321
+ "Examples: 'retry_count < 3', 'needs_improvement == True'"
322
+ )
323
+
324
+ self._condition = condition
325
+ return self
326
+
327
+ def nested_in(self, parent_cycle_id: str) -> "CycleBuilder":
328
+ """
329
+ Make this cycle nested within another cycle.
330
+
331
+ Enables hierarchical cycle structures where one cycle operates
332
+ within the iterations of a parent cycle. Useful for multi-level
333
+ optimization scenarios.
334
+
335
+ Args:
336
+ parent_cycle_id: Identifier of the parent cycle.
337
+
338
+ Returns:
339
+ Self for method chaining.
340
+
341
+ Raises:
342
+ CycleConfigurationError: If parent cycle ID is invalid.
343
+
344
+ Examples:
345
+ >>> cycle.nested_in("outer_optimization") # This cycle runs inside outer_optimization
346
+ """
347
+ if not parent_cycle_id or not isinstance(parent_cycle_id, str):
348
+ raise CycleConfigurationError(
349
+ "Parent cycle ID must be a non-empty string"
350
+ )
351
+
352
+ self._parent_cycle = parent_cycle_id
353
+ return self
354
+
355
+ def build(self) -> None:
356
+ """
357
+ Build and add the configured cycle to the workflow.
358
+
359
+ Validates the cycle configuration and creates the actual cyclic
360
+ connection in the workflow. This finalizes the cycle builder
361
+ pattern and applies all configured settings.
362
+
363
+ Raises:
364
+ CycleConfigurationError: If cycle configuration is incomplete or invalid.
365
+ WorkflowValidationError: If workflow connection fails.
366
+
367
+ Examples:
368
+ >>> cycle.connect("node_a", "node_b") \
369
+ ... .max_iterations(50) \
370
+ ... .converge_when("quality > 0.9") \
371
+ ... .build() # Creates the cycle in the workflow
372
+ """
373
+ # Validate required parameters
374
+ if not self._source_node or not self._target_node:
375
+ raise CycleConfigurationError(
376
+ "Cycle must have source and target nodes configured. "
377
+ "Call connect(source_node, target_node) before build()."
378
+ )
379
+
380
+ # Validate at least one termination condition
381
+ if not self._max_iterations and not self._convergence_check and not self._timeout:
382
+ raise CycleConfigurationError(
383
+ "Cycle must have at least one termination condition. "
384
+ "Add max_iterations(), converge_when(), or timeout() before build(). "
385
+ "Recommendation: Always include max_iterations() as a safety net."
386
+ )
387
+
388
+ # Create the connection using the workflow's connect method
389
+ try:
390
+ self._workflow.connect(
391
+ source_node=self._source_node,
392
+ target_node=self._target_node,
393
+ mapping=self._mapping,
394
+ cycle=True,
395
+ max_iterations=self._max_iterations,
396
+ convergence_check=self._convergence_check,
397
+ cycle_id=self._cycle_id,
398
+ timeout=self._timeout,
399
+ memory_limit=self._memory_limit,
400
+ condition=self._condition,
401
+ parent_cycle=self._parent_cycle
402
+ )
403
+
404
+ logger.info(
405
+ f"Created cycle '{self._cycle_id or 'unnamed'}' from "
406
+ f"{self._source_node} to {self._target_node} with "
407
+ f"max_iterations={self._max_iterations}, "
408
+ f"convergence='{self._convergence_check}'"
409
+ )
410
+
411
+ except Exception as e:
412
+ raise WorkflowValidationError(
413
+ f"Failed to create cycle connection: {e}"
414
+ ) from e
415
+
416
+ @classmethod
417
+ def from_config(cls, workflow: "Workflow", config: "CycleConfig") -> "CycleBuilder":
418
+ """
419
+ Create a CycleBuilder from a CycleConfig instance.
420
+
421
+ Provides an alternative constructor that initializes the builder
422
+ with all configuration from a type-safe CycleConfig object. This
423
+ enables configuration reuse, templating, and structured configuration
424
+ management across multiple cycles.
425
+
426
+ Args:
427
+ workflow: Target workflow for the cycle.
428
+ config: Pre-configured cycle parameters.
429
+
430
+ Returns:
431
+ Builder instance initialized with config values.
432
+
433
+ Raises:
434
+ CycleConfigurationError: If config is invalid.
435
+ ImportError: If CycleConfig module is not available.
436
+
437
+ Examples:
438
+ Using a template:
439
+
440
+ >>> config = CycleTemplates.optimization_loop(max_iterations=50)
441
+ >>> builder = CycleBuilder.from_config(workflow, config)
442
+ >>> builder.connect("optimizer", "evaluator").build()
443
+
444
+ Using custom configuration:
445
+
446
+ >>> config = CycleConfig(max_iterations=100, timeout=300)
447
+ >>> builder = CycleBuilder.from_config(workflow, config)
448
+ >>> builder.connect("processor", "evaluator").build()
449
+ """
450
+ try:
451
+ from kailash.workflow.cycle_config import CycleConfig
452
+ except ImportError as e:
453
+ raise ImportError(
454
+ "CycleConfig not available. Ensure kailash.workflow.cycle_config is installed."
455
+ ) from e
456
+
457
+ # Validate config is correct type
458
+ if not isinstance(config, CycleConfig):
459
+ raise CycleConfigurationError(
460
+ f"Expected CycleConfig instance, got {type(config)}. "
461
+ "Use CycleConfig() or CycleTemplates.* to create valid configuration."
462
+ )
463
+
464
+ # Create builder with config values
465
+ builder = cls(workflow=workflow, cycle_id=config.cycle_id)
466
+
467
+ # Apply configuration parameters
468
+ if config.max_iterations is not None:
469
+ builder._max_iterations = config.max_iterations
470
+
471
+ if config.convergence_check is not None:
472
+ if isinstance(config.convergence_check, str):
473
+ builder._convergence_check = config.convergence_check
474
+ else:
475
+ # For callable convergence checks, convert to description
476
+ builder._convergence_check = "<callable_convergence_check>"
477
+
478
+ if config.timeout is not None:
479
+ builder._timeout = config.timeout
480
+
481
+ if config.memory_limit is not None:
482
+ builder._memory_limit = config.memory_limit
483
+
484
+ if config.condition is not None:
485
+ builder._condition = config.condition
486
+
487
+ if config.parent_cycle is not None:
488
+ builder._parent_cycle = config.parent_cycle
489
+
490
+ return builder
491
+
492
+ def apply_config(self, config: "CycleConfig") -> "CycleBuilder":
493
+ """
494
+ Apply configuration from a CycleConfig instance to this builder.
495
+
496
+ Merges configuration parameters from a CycleConfig object into
497
+ the current builder state. This allows combining fluent builder
498
+ calls with structured configuration objects for maximum flexibility.
499
+
500
+ Args:
501
+ config: Configuration to apply to this builder.
502
+
503
+ Returns:
504
+ Self for method chaining.
505
+
506
+ Raises:
507
+ CycleConfigurationError: If config is invalid.
508
+
509
+ Examples:
510
+ >>> builder = workflow.create_cycle("custom") \
511
+ ... .connect("a", "b") \
512
+ ... .apply_config(CycleTemplates.optimization_loop()) \
513
+ ... .timeout(120) # Override the template timeout
514
+ ... .build()
515
+ """
516
+ try:
517
+ from kailash.workflow.cycle_config import CycleConfig
518
+ except ImportError as e:
519
+ raise ImportError(
520
+ "CycleConfig not available. Ensure kailash.workflow.cycle_config is installed."
521
+ ) from e
522
+
523
+ if not isinstance(config, CycleConfig):
524
+ raise CycleConfigurationError(
525
+ f"Expected CycleConfig instance, got {type(config)}"
526
+ )
527
+
528
+ # Apply non-None configuration values
529
+ if config.max_iterations is not None:
530
+ self._max_iterations = config.max_iterations
531
+
532
+ if config.convergence_check is not None:
533
+ if isinstance(config.convergence_check, str):
534
+ self._convergence_check = config.convergence_check
535
+
536
+ if config.timeout is not None:
537
+ self._timeout = config.timeout
538
+
539
+ if config.memory_limit is not None:
540
+ self._memory_limit = config.memory_limit
541
+
542
+ if config.condition is not None:
543
+ self._condition = config.condition
544
+
545
+ if config.parent_cycle is not None:
546
+ self._parent_cycle = config.parent_cycle
547
+
548
+ # Update cycle_id if specified in config
549
+ if config.cycle_id is not None:
550
+ self._cycle_id = config.cycle_id
551
+
552
+ return self
553
+
554
+ def __repr__(self) -> str:
555
+ """
556
+ Return string representation of the cycle builder configuration.
557
+
558
+ Returns:
559
+ Human-readable representation of current configuration.
560
+
561
+ Examples:
562
+ >>> str(cycle)
563
+ 'CycleBuilder(cycle_id=optimization, source=processor, target=evaluator, max_iterations=50)'
564
+ """
565
+ return (
566
+ f"CycleBuilder("
567
+ f"cycle_id={self._cycle_id}, "
568
+ f"source={self._source_node}, "
569
+ f"target={self._target_node}, "
570
+ f"max_iterations={self._max_iterations}, "
571
+ f"convergence='{self._convergence_check}'"
572
+ f")"
573
+ )