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,374 @@
1
+ """
2
+ Base class for cycle-aware nodes that provides built-in helpers for cyclic workflow patterns.
3
+
4
+ This module provides CycleAwareNode, a base class that eliminates boilerplate code
5
+ for nodes that need to work with cyclic workflows. It provides convenient methods
6
+ for accessing cycle information, managing state across iterations, and implementing
7
+ common cycle patterns.
8
+
9
+ Design Philosophy:
10
+ CycleAwareNode abstracts away the complexity of managing state and iteration
11
+ tracking in cyclic workflows. It provides a clean API for nodes to focus on
12
+ their business logic while the base class handles cycle mechanics.
13
+
14
+ Example usage:
15
+ >>> from kailash.nodes.base_cycle_aware import CycleAwareNode
16
+ >>> from kailash.nodes.base import NodeParameter
17
+ >>>
18
+ >>> class MyProcessorNode(CycleAwareNode):
19
+ ... def get_parameters(self):
20
+ ... return {
21
+ ... "data": NodeParameter(name="data", type=list, required=True)
22
+ ... }
23
+ ...
24
+ ... def run(self, context, **kwargs):
25
+ ... iteration = self.get_iteration(context)
26
+ ... is_first = self.is_first_iteration(context)
27
+ ... prev_results = self.get_previous_state(context)
28
+ ...
29
+ ... # Process data with cycle awareness
30
+ ... processed = self.process_with_history(kwargs["data"], prev_results)
31
+ ...
32
+ ... # Return result with state for next iteration
33
+ ... return {
34
+ ... "result": processed,
35
+ ... **self.set_cycle_state({"last_result": processed})
36
+ ... }
37
+ """
38
+
39
+ import time
40
+ from typing import Any, Dict
41
+
42
+ from .base import Node
43
+
44
+
45
+ class CycleAwareNode(Node):
46
+ """
47
+ Base class for nodes that are cycle-aware with built-in helpers.
48
+
49
+ This class provides convenient methods for working with cyclic workflows,
50
+ eliminating common boilerplate code for cycle information access and
51
+ state management across iterations.
52
+
53
+ Design Philosophy:
54
+ CycleAwareNode is designed to make cyclic workflows as simple to write
55
+ as regular nodes. It handles all the complexity of iteration tracking,
56
+ state persistence, and cycle information management, allowing developers
57
+ to focus on the iterative logic.
58
+
59
+ Upstream Dependencies:
60
+ - Node: Base class that provides core node functionality
61
+ - CyclicWorkflowExecutor: Provides cycle context in execution
62
+ - Workflow: Must be configured with cycle=True connections
63
+
64
+ Downstream Consumers:
65
+ - ConvergenceCheckerNode: Uses cycle helpers for convergence detection
66
+ - A2ACoordinatorNode: Tracks agent performance across iterations
67
+ - Any custom nodes needing cycle-aware behavior
68
+
69
+ Configuration:
70
+ No specific configuration required. Inherit from this class and use
71
+ the provided helper methods in your run() implementation.
72
+
73
+ Implementation Details:
74
+ - Extracts cycle information from execution context
75
+ - Provides safe accessors with sensible defaults
76
+ - Manages state persistence through _cycle_state convention
77
+ - Offers utility methods for common patterns
78
+
79
+ Error Handling:
80
+ - Returns default values if cycle information is missing
81
+ - Handles missing state gracefully with empty dicts
82
+ - Safe for use in non-cyclic contexts (acts as regular node)
83
+
84
+ Side Effects:
85
+ - Logs cycle progress when log_cycle_info() is called
86
+ - No other external side effects
87
+
88
+ Examples:
89
+ >>> class QualityImproverNode(CycleAwareNode):
90
+ ... def run(self, context, **kwargs):
91
+ ... iteration = self.get_iteration(context)
92
+ ... quality = kwargs.get("quality", 0.0)
93
+ ...
94
+ ... if self.is_first_iteration(context):
95
+ ... print("Starting quality improvement process")
96
+ ...
97
+ ... # Improve quality based on iteration
98
+ ... improved_quality = quality + (0.1 * iteration)
99
+ ...
100
+ ... return {
101
+ ... "quality": improved_quality,
102
+ ... **self.set_cycle_state({"last_quality": improved_quality})
103
+ ... }
104
+ """
105
+
106
+ def get_cycle_info(self, context: Dict[str, Any]) -> Dict[str, Any]:
107
+ """
108
+ Get cycle information with sensible defaults.
109
+
110
+ Extracts cycle information from the execution context, providing
111
+ default values for missing fields to prevent KeyError exceptions.
112
+
113
+ Args:
114
+ context: Execution context containing cycle information
115
+
116
+ Returns:
117
+ Dictionary containing cycle information with guaranteed fields:
118
+ - iteration: Current iteration number (default: 0)
119
+ - elapsed_time: Time elapsed in seconds (default: 0.0)
120
+ - cycle_id: Unique cycle identifier (default: "default")
121
+ - max_iterations: Maximum allowed iterations (default: 100)
122
+ - start_time: Cycle start timestamp (default: current time)
123
+
124
+ Example:
125
+ >>> cycle_info = self.get_cycle_info(context)
126
+ >>> print(f"Iteration {cycle_info['iteration']} of {cycle_info['max_iterations']}")
127
+ """
128
+ cycle_info = context.get("cycle", {})
129
+ current_time = time.time()
130
+
131
+ return {
132
+ "iteration": cycle_info.get("iteration", 0),
133
+ "elapsed_time": cycle_info.get("elapsed_time", 0.0),
134
+ "cycle_id": cycle_info.get("cycle_id", "default"),
135
+ "max_iterations": cycle_info.get("max_iterations", 100),
136
+ "start_time": cycle_info.get("start_time", current_time),
137
+ "convergence_check": cycle_info.get("convergence_check"),
138
+ **cycle_info, # Include any additional cycle information
139
+ }
140
+
141
+ def get_iteration(self, context: Dict[str, Any]) -> int:
142
+ """
143
+ Get current iteration number.
144
+
145
+ Args:
146
+ context: Execution context
147
+
148
+ Returns:
149
+ Current iteration number (0-based)
150
+
151
+ Example:
152
+ >>> iteration = self.get_iteration(context)
153
+ >>> if iteration > 10:
154
+ ... print("Long-running cycle detected")
155
+ """
156
+ return self.get_cycle_info(context)["iteration"]
157
+
158
+ def is_first_iteration(self, context: Dict[str, Any]) -> bool:
159
+ """
160
+ Check if this is the first iteration of the cycle.
161
+
162
+ Args:
163
+ context: Execution context
164
+
165
+ Returns:
166
+ True if this is iteration 0, False otherwise
167
+
168
+ Example:
169
+ >>> if self.is_first_iteration(context):
170
+ ... print("Initializing cycle state")
171
+ ... return self.initialize_state()
172
+ """
173
+ return self.get_iteration(context) == 0
174
+
175
+ def is_last_iteration(self, context: Dict[str, Any]) -> bool:
176
+ """
177
+ Check if this is the last iteration of the cycle.
178
+
179
+ Args:
180
+ context: Execution context
181
+
182
+ Returns:
183
+ True if this is the final iteration, False otherwise
184
+
185
+ Example:
186
+ >>> if self.is_last_iteration(context):
187
+ ... print("Performing final cleanup")
188
+ """
189
+ cycle_info = self.get_cycle_info(context)
190
+ return cycle_info["iteration"] >= cycle_info["max_iterations"] - 1
191
+
192
+ def get_previous_state(self, context: Dict[str, Any]) -> Dict[str, Any]:
193
+ """
194
+ Get previous iteration state safely.
195
+
196
+ Retrieves state that was persisted from the previous iteration
197
+ using set_cycle_state(). Returns empty dict if no state exists.
198
+
199
+ Args:
200
+ context: Execution context
201
+
202
+ Returns:
203
+ Dictionary containing state from previous iteration
204
+
205
+ Example:
206
+ >>> prev_state = self.get_previous_state(context)
207
+ >>> history = prev_state.get("value_history", [])
208
+ >>> print(f"Previous values: {history}")
209
+ """
210
+ return self.get_cycle_info(context).get("node_state", {})
211
+
212
+ def set_cycle_state(self, state: Dict[str, Any]) -> Dict[str, Any]:
213
+ """
214
+ Set state to persist to next iteration.
215
+
216
+ Creates the special _cycle_state return value that the cycle
217
+ executor will persist and make available in the next iteration
218
+ via get_previous_state().
219
+
220
+ Args:
221
+ state: Dictionary of state to persist
222
+
223
+ Returns:
224
+ Dictionary with _cycle_state key for return from run()
225
+
226
+ Example:
227
+ >>> # In run() method:
228
+ >>> current_values = [1, 2, 3]
229
+ >>> return {
230
+ ... "result": processed_data,
231
+ ... **self.set_cycle_state({"values": current_values})
232
+ ... }
233
+ """
234
+ return {"_cycle_state": state}
235
+
236
+ def get_cycle_progress(self, context: Dict[str, Any]) -> float:
237
+ """
238
+ Get cycle progress as a percentage.
239
+
240
+ Args:
241
+ context: Execution context
242
+
243
+ Returns:
244
+ Progress percentage (0.0 to 1.0)
245
+
246
+ Example:
247
+ >>> progress = self.get_cycle_progress(context)
248
+ >>> print(f"Cycle {progress*100:.1f}% complete")
249
+ """
250
+ cycle_info = self.get_cycle_info(context)
251
+ iteration = cycle_info["iteration"]
252
+ max_iterations = cycle_info["max_iterations"]
253
+
254
+ if max_iterations <= 0:
255
+ return 1.0
256
+
257
+ return min(iteration / max_iterations, 1.0)
258
+
259
+ def log_cycle_info(self, context: Dict[str, Any], message: str = "") -> None:
260
+ """
261
+ Log cycle information for debugging.
262
+
263
+ Convenient method to log current cycle state with optional message.
264
+
265
+ Args:
266
+ context: Execution context
267
+ message: Optional message to include in log
268
+
269
+ Example:
270
+ >>> self.log_cycle_info(context, "Processing batch")
271
+ # Output: [Cycle default] Iteration 3/10 (30.0%): Processing batch
272
+ """
273
+ cycle_info = self.get_cycle_info(context)
274
+ progress = self.get_cycle_progress(context)
275
+
276
+ log_msg = (
277
+ f"[Cycle {cycle_info['cycle_id']}] "
278
+ f"Iteration {cycle_info['iteration']}/{cycle_info['max_iterations']} "
279
+ f"({progress*100:.1f}%)"
280
+ )
281
+
282
+ if message:
283
+ log_msg += f": {message}"
284
+
285
+ print(log_msg)
286
+
287
+ def should_continue_cycle(self, context: Dict[str, Any], **kwargs) -> bool:
288
+ """
289
+ Helper method to determine if cycle should continue.
290
+
291
+ This is a convenience method that can be overridden by subclasses
292
+ to implement custom continuation logic. Default implementation
293
+ checks if max iterations reached.
294
+
295
+ Args:
296
+ context: Execution context
297
+ **kwargs: Additional parameters for decision making
298
+
299
+ Returns:
300
+ True if cycle should continue, False otherwise
301
+
302
+ Example:
303
+ >>> def should_continue_cycle(self, context, **kwargs):
304
+ ... quality = kwargs.get("quality", 0.0)
305
+ ... return quality < 0.95 and not self.is_last_iteration(context)
306
+ """
307
+ return not self.is_last_iteration(context)
308
+
309
+ def accumulate_values(
310
+ self, context: Dict[str, Any], key: str, value: Any, max_history: int = 100
311
+ ) -> list:
312
+ """
313
+ Accumulate values across iterations with automatic history management.
314
+
315
+ Convenience method for maintaining a list of values across iterations
316
+ with automatic size management to prevent memory growth.
317
+
318
+ Args:
319
+ context: Execution context
320
+ key: State key for the value list
321
+ value: Value to add to the list
322
+ max_history: Maximum number of values to keep
323
+
324
+ Returns:
325
+ Updated list of values
326
+
327
+ Example:
328
+ >>> quality_history = self.accumulate_values(context, "quality", current_quality)
329
+ >>> avg_quality = sum(quality_history) / len(quality_history)
330
+ """
331
+ prev_state = self.get_previous_state(context)
332
+ values = prev_state.get(key, [])
333
+ values.append(value)
334
+
335
+ # Keep only recent values to prevent memory growth
336
+ if len(values) > max_history:
337
+ values = values[-max_history:]
338
+
339
+ return values
340
+
341
+ def detect_convergence_trend(
342
+ self,
343
+ context: Dict[str, Any],
344
+ key: str,
345
+ threshold: float = 0.01,
346
+ window: int = 3,
347
+ ) -> bool:
348
+ """
349
+ Detect if values are converging (becoming stable).
350
+
351
+ Analyzes recent values to determine if they are converging to a stable value.
352
+
353
+ Args:
354
+ context: Execution context
355
+ key: State key containing value history
356
+ threshold: Maximum variance for convergence
357
+ window: Number of recent values to analyze
358
+
359
+ Returns:
360
+ True if values are converging, False otherwise
361
+
362
+ Example:
363
+ >>> if self.detect_convergence_trend(context, "error_rate", 0.001):
364
+ ... return {"converged": True, "reason": "error_rate_stable"}
365
+ """
366
+ prev_state = self.get_previous_state(context)
367
+ values = prev_state.get(key, [])
368
+
369
+ if len(values) < window:
370
+ return False
371
+
372
+ recent_values = values[-window:]
373
+ variance = max(recent_values) - min(recent_values)
374
+ return variance <= threshold