kailash 0.1.5__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 (75) 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/readers.py +16 -6
  32. kailash/nodes/data/writers.py +16 -6
  33. kailash/nodes/logic/__init__.py +8 -0
  34. kailash/nodes/logic/convergence.py +642 -0
  35. kailash/nodes/logic/loop.py +153 -0
  36. kailash/nodes/logic/operations.py +187 -27
  37. kailash/nodes/mixins/__init__.py +11 -0
  38. kailash/nodes/mixins/mcp.py +228 -0
  39. kailash/nodes/mixins.py +387 -0
  40. kailash/runtime/__init__.py +2 -1
  41. kailash/runtime/access_controlled.py +458 -0
  42. kailash/runtime/local.py +106 -33
  43. kailash/runtime/parallel_cyclic.py +529 -0
  44. kailash/sdk_exceptions.py +90 -5
  45. kailash/security.py +845 -0
  46. kailash/tracking/manager.py +38 -15
  47. kailash/tracking/models.py +1 -1
  48. kailash/tracking/storage/filesystem.py +30 -2
  49. kailash/utils/__init__.py +8 -0
  50. kailash/workflow/__init__.py +18 -0
  51. kailash/workflow/convergence.py +270 -0
  52. kailash/workflow/cycle_analyzer.py +768 -0
  53. kailash/workflow/cycle_builder.py +573 -0
  54. kailash/workflow/cycle_config.py +709 -0
  55. kailash/workflow/cycle_debugger.py +760 -0
  56. kailash/workflow/cycle_exceptions.py +601 -0
  57. kailash/workflow/cycle_profiler.py +671 -0
  58. kailash/workflow/cycle_state.py +338 -0
  59. kailash/workflow/cyclic_runner.py +985 -0
  60. kailash/workflow/graph.py +500 -39
  61. kailash/workflow/migration.py +768 -0
  62. kailash/workflow/safety.py +365 -0
  63. kailash/workflow/templates.py +744 -0
  64. kailash/workflow/validation.py +693 -0
  65. {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/METADATA +256 -12
  66. kailash-0.2.0.dist-info/RECORD +125 -0
  67. kailash/nodes/mcp/__init__.py +0 -11
  68. kailash/nodes/mcp/client.py +0 -554
  69. kailash/nodes/mcp/resource.py +0 -682
  70. kailash/nodes/mcp/server.py +0 -577
  71. kailash-0.1.5.dist-info/RECORD +0 -88
  72. {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/WHEEL +0 -0
  73. {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/entry_points.txt +0 -0
  74. {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/licenses/LICENSE +0 -0
  75. {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,365 @@
1
+ """Cycle safety and resource management framework."""
2
+
3
+ import logging
4
+ import threading
5
+ import time
6
+ from contextlib import contextmanager
7
+ from typing import Dict, Optional, Set
8
+
9
+ import psutil
10
+
11
+ from kailash.sdk_exceptions import WorkflowExecutionError
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class CycleSafetyManager:
17
+ """Manages cycle execution safety and resource limits."""
18
+
19
+ def __init__(self):
20
+ """Initialize cycle safety manager."""
21
+ self.active_cycles: Dict[str, "CycleMonitor"] = {}
22
+ self.global_memory_limit = None # MB
23
+ self.global_timeout = None # seconds
24
+ self._lock = threading.Lock()
25
+
26
+ def set_global_limits(
27
+ self, memory_limit: Optional[int] = None, timeout: Optional[float] = None
28
+ ) -> None:
29
+ """Set global resource limits.
30
+
31
+ Args:
32
+ memory_limit: Global memory limit in MB
33
+ timeout: Global timeout in seconds
34
+ """
35
+ self.global_memory_limit = memory_limit
36
+ self.global_timeout = timeout
37
+
38
+ def start_monitoring(
39
+ self,
40
+ cycle_id: str,
41
+ max_iterations: Optional[int] = None,
42
+ timeout: Optional[float] = None,
43
+ memory_limit: Optional[int] = None,
44
+ ) -> "CycleMonitor":
45
+ """Start monitoring a cycle.
46
+
47
+ Args:
48
+ cycle_id: Cycle identifier
49
+ max_iterations: Maximum iterations allowed
50
+ timeout: Timeout in seconds
51
+ memory_limit: Memory limit in MB
52
+
53
+ Returns:
54
+ CycleMonitor instance
55
+ """
56
+ with self._lock:
57
+ if cycle_id in self.active_cycles:
58
+ logger.warning(f"Cycle {cycle_id} already being monitored")
59
+ return self.active_cycles[cycle_id]
60
+
61
+ # Use global limits if not specified
62
+ if timeout is None:
63
+ timeout = self.global_timeout
64
+ if memory_limit is None:
65
+ memory_limit = self.global_memory_limit
66
+
67
+ monitor = CycleMonitor(
68
+ cycle_id=cycle_id,
69
+ max_iterations=max_iterations,
70
+ timeout=timeout,
71
+ memory_limit=memory_limit,
72
+ )
73
+
74
+ self.active_cycles[cycle_id] = monitor
75
+ monitor.start()
76
+
77
+ return monitor
78
+
79
+ def stop_monitoring(self, cycle_id: str) -> None:
80
+ """Stop monitoring a cycle.
81
+
82
+ Args:
83
+ cycle_id: Cycle identifier
84
+ """
85
+ with self._lock:
86
+ if cycle_id in self.active_cycles:
87
+ monitor = self.active_cycles[cycle_id]
88
+ monitor.stop()
89
+ del self.active_cycles[cycle_id]
90
+
91
+ def check_all_cycles(self) -> Dict[str, bool]:
92
+ """Check all active cycles for violations.
93
+
94
+ Returns:
95
+ Dict mapping cycle_id to violation status
96
+ """
97
+ violations = {}
98
+
99
+ with self._lock:
100
+ for cycle_id, monitor in self.active_cycles.items():
101
+ violations[cycle_id] = monitor.check_violations()
102
+
103
+ return violations
104
+
105
+ def get_cycle_status(self, cycle_id: str) -> Optional[Dict[str, any]]:
106
+ """Get status of a specific cycle.
107
+
108
+ Args:
109
+ cycle_id: Cycle identifier
110
+
111
+ Returns:
112
+ Status dict or None if not found
113
+ """
114
+ with self._lock:
115
+ if cycle_id in self.active_cycles:
116
+ return self.active_cycles[cycle_id].get_status()
117
+ return None
118
+
119
+ def detect_deadlocks(self) -> Set[str]:
120
+ """Detect potential deadlocks in active cycles.
121
+
122
+ Returns:
123
+ Set of cycle IDs that may be deadlocked
124
+ """
125
+ deadlocked = set()
126
+
127
+ with self._lock:
128
+ for cycle_id, monitor in self.active_cycles.items():
129
+ if monitor.is_potentially_deadlocked():
130
+ deadlocked.add(cycle_id)
131
+
132
+ return deadlocked
133
+
134
+
135
+ class CycleMonitor:
136
+ """Monitors a single cycle for safety violations."""
137
+
138
+ def __init__(
139
+ self,
140
+ cycle_id: str,
141
+ max_iterations: Optional[int] = None,
142
+ timeout: Optional[float] = None,
143
+ memory_limit: Optional[int] = None,
144
+ ):
145
+ """Initialize cycle monitor.
146
+
147
+ Args:
148
+ cycle_id: Cycle identifier
149
+ max_iterations: Maximum iterations allowed
150
+ timeout: Timeout in seconds
151
+ memory_limit: Memory limit in MB
152
+ """
153
+ self.cycle_id = cycle_id
154
+ self.max_iterations = max_iterations or float("inf")
155
+ self.timeout = timeout
156
+ self.memory_limit = memory_limit
157
+
158
+ self.start_time = None
159
+ self.end_time = None
160
+ self.iteration_count = 0
161
+ self.last_progress_time = None
162
+ self.initial_memory = None
163
+ self.peak_memory = 0
164
+ self.violations = []
165
+ self.is_active = False
166
+
167
+ self._monitor_thread = None
168
+ self._stop_event = threading.Event()
169
+
170
+ def start(self) -> None:
171
+ """Start monitoring."""
172
+ self.start_time = time.time()
173
+ self.last_progress_time = self.start_time
174
+ self.is_active = True
175
+
176
+ # Get initial memory usage
177
+ process = psutil.Process()
178
+ self.initial_memory = process.memory_info().rss / 1024 / 1024 # MB
179
+
180
+ # Start monitoring thread if we have limits
181
+ if self.timeout or self.memory_limit:
182
+ self._monitor_thread = threading.Thread(
183
+ target=self._monitor_loop, daemon=True
184
+ )
185
+ self._monitor_thread.start()
186
+
187
+ logger.info(f"Started monitoring cycle: {self.cycle_id}")
188
+
189
+ def stop(self) -> None:
190
+ """Stop monitoring."""
191
+ self.end_time = time.time()
192
+ self.is_active = False
193
+ self._stop_event.set()
194
+
195
+ if self._monitor_thread:
196
+ self._monitor_thread.join(timeout=1)
197
+
198
+ logger.info(f"Stopped monitoring cycle: {self.cycle_id}")
199
+
200
+ def record_iteration(self) -> None:
201
+ """Record that an iteration occurred."""
202
+ self.iteration_count += 1
203
+ self.last_progress_time = time.time()
204
+
205
+ # Check iteration limit
206
+ if self.iteration_count > self.max_iterations:
207
+ violation = f"Exceeded max iterations: {self.iteration_count} > {self.max_iterations}"
208
+ self.violations.append(violation)
209
+ raise WorkflowExecutionError(f"Cycle {self.cycle_id}: {violation}")
210
+
211
+ def check_violations(self) -> bool:
212
+ """Check for any safety violations.
213
+
214
+ Returns:
215
+ True if violations detected
216
+ """
217
+ if not self.is_active:
218
+ return False
219
+
220
+ current_time = time.time()
221
+
222
+ # Check timeout
223
+ if self.timeout and (current_time - self.start_time) > self.timeout:
224
+ violation = f"Timeout exceeded: {current_time - self.start_time:.1f}s > {self.timeout}s"
225
+ self.violations.append(violation)
226
+ return True
227
+
228
+ # Check memory limit
229
+ if self.memory_limit:
230
+ process = psutil.Process()
231
+ current_memory = process.memory_info().rss / 1024 / 1024 # MB
232
+ memory_increase = current_memory - self.initial_memory
233
+
234
+ if memory_increase > self.memory_limit:
235
+ violation = f"Memory limit exceeded: {memory_increase:.1f}MB > {self.memory_limit}MB"
236
+ self.violations.append(violation)
237
+ return True
238
+
239
+ # Track peak memory
240
+ self.peak_memory = max(self.peak_memory, memory_increase)
241
+
242
+ return False
243
+
244
+ def is_potentially_deadlocked(self, stall_threshold: float = 60.0) -> bool:
245
+ """Check if cycle might be deadlocked.
246
+
247
+ Args:
248
+ stall_threshold: Seconds without progress to consider deadlock
249
+
250
+ Returns:
251
+ True if potentially deadlocked
252
+ """
253
+ if not self.is_active or not self.last_progress_time:
254
+ return False
255
+
256
+ time_since_progress = time.time() - self.last_progress_time
257
+ return time_since_progress > stall_threshold
258
+
259
+ def get_status(self) -> Dict[str, any]:
260
+ """Get current monitor status.
261
+
262
+ Returns:
263
+ Status dictionary
264
+ """
265
+ status = {
266
+ "cycle_id": self.cycle_id,
267
+ "is_active": self.is_active,
268
+ "iteration_count": self.iteration_count,
269
+ "elapsed_time": time.time() - self.start_time if self.start_time else 0,
270
+ "violations": self.violations,
271
+ }
272
+
273
+ if self.timeout:
274
+ status["timeout"] = self.timeout
275
+ status["time_remaining"] = max(0, self.timeout - status["elapsed_time"])
276
+
277
+ if self.memory_limit:
278
+ process = psutil.Process()
279
+ current_memory = process.memory_info().rss / 1024 / 1024 # MB
280
+ memory_increase = current_memory - self.initial_memory
281
+
282
+ status["memory_limit"] = self.memory_limit
283
+ status["memory_used"] = memory_increase
284
+ status["memory_remaining"] = max(0, self.memory_limit - memory_increase)
285
+ status["peak_memory"] = self.peak_memory
286
+
287
+ if self.max_iterations != float("inf"):
288
+ status["max_iterations"] = self.max_iterations
289
+ status["iterations_remaining"] = max(
290
+ 0, self.max_iterations - self.iteration_count
291
+ )
292
+
293
+ return status
294
+
295
+ def _monitor_loop(self) -> None:
296
+ """Background monitoring loop."""
297
+ check_interval = 1.0 # seconds
298
+
299
+ while not self._stop_event.is_set() and self.is_active:
300
+ try:
301
+ if self.check_violations():
302
+ logger.error(
303
+ f"Cycle {self.cycle_id} safety violation: {self.violations[-1]}"
304
+ )
305
+ # Could implement automatic termination here
306
+
307
+ self._stop_event.wait(check_interval)
308
+
309
+ except Exception as e:
310
+ logger.error(f"Error in monitor loop for cycle {self.cycle_id}: {e}")
311
+
312
+
313
+ @contextmanager
314
+ def monitored_cycle(safety_manager: CycleSafetyManager, cycle_id: str, **limits):
315
+ """Context manager for monitored cycle execution.
316
+
317
+ Args:
318
+ safety_manager: CycleSafetyManager instance
319
+ cycle_id: Cycle identifier
320
+ **limits: Resource limits (max_iterations, timeout, memory_limit)
321
+
322
+ Yields:
323
+ CycleMonitor instance
324
+ """
325
+ monitor = safety_manager.start_monitoring(cycle_id, **limits)
326
+
327
+ try:
328
+ yield monitor
329
+ finally:
330
+ safety_manager.stop_monitoring(cycle_id)
331
+
332
+
333
+ class TimeoutHandler:
334
+ """Handles timeout for cycle execution."""
335
+
336
+ def __init__(self, timeout: float):
337
+ """Initialize timeout handler.
338
+
339
+ Args:
340
+ timeout: Timeout in seconds
341
+ """
342
+ self.timeout = timeout
343
+ self.timer = None
344
+ self.timed_out = False
345
+
346
+ def __enter__(self):
347
+ """Start timeout timer."""
348
+
349
+ def timeout_handler():
350
+ self.timed_out = True
351
+ logger.error(f"Cycle execution timed out after {self.timeout}s")
352
+
353
+ self.timer = threading.Timer(self.timeout, timeout_handler)
354
+ self.timer.start()
355
+ return self
356
+
357
+ def __exit__(self, exc_type, exc_val, exc_tb):
358
+ """Cancel timeout timer."""
359
+ if self.timer:
360
+ self.timer.cancel()
361
+
362
+ if self.timed_out and exc_type is None:
363
+ raise WorkflowExecutionError(
364
+ f"Cycle execution timed out after {self.timeout}s"
365
+ )