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,338 @@
1
+ """Cycle state management for workflow iterations."""
2
+
3
+ import logging
4
+ import time
5
+ from datetime import datetime, timezone
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class CycleState:
12
+ """Manages state across cycle iterations."""
13
+
14
+ def __init__(self, cycle_id: str = "default"):
15
+ """Initialize cycle state.
16
+
17
+ Args:
18
+ cycle_id: Identifier for this cycle group
19
+ """
20
+ self.cycle_id = cycle_id
21
+ self.iteration = 0
22
+ self.history: List[Dict[str, Any]] = []
23
+ self.metadata: Dict[str, Any] = {}
24
+ self.start_time = time.time()
25
+ self.last_update_time = self.start_time
26
+ self.node_states: Dict[str, Any] = {} # Per-node state storage
27
+
28
+ @property
29
+ def elapsed_time(self) -> float:
30
+ """Get elapsed time since cycle started."""
31
+ return time.time() - self.start_time
32
+
33
+ @property
34
+ def iteration_time(self) -> float:
35
+ """Get time since last iteration."""
36
+ return time.time() - self.last_update_time
37
+
38
+ def update(self, results: Dict[str, Any], iteration: Optional[int] = None) -> None:
39
+ """Update state with iteration results.
40
+
41
+ Args:
42
+ results: Results from current iteration
43
+ iteration: Optional iteration number (auto-incremented if not provided)
44
+ """
45
+ if iteration is not None:
46
+ self.iteration = iteration
47
+ else:
48
+ self.iteration += 1
49
+
50
+ # Record iteration history
51
+ history_entry = {
52
+ "iteration": self.iteration,
53
+ "results": results,
54
+ "timestamp": datetime.now(timezone.utc).isoformat(),
55
+ "elapsed_time": self.elapsed_time,
56
+ "iteration_time": self.iteration_time,
57
+ }
58
+ self.history.append(history_entry)
59
+
60
+ # Update timing
61
+ self.last_update_time = time.time()
62
+
63
+ # Update node states if provided
64
+ for node_id, node_result in results.items():
65
+ if isinstance(node_result, dict) and "_cycle_state" in node_result:
66
+ self.node_states[node_id] = node_result["_cycle_state"]
67
+
68
+ logger.debug(
69
+ f"Cycle {self.cycle_id} updated: iteration={self.iteration}, "
70
+ f"elapsed_time={self.elapsed_time:.2f}s"
71
+ )
72
+
73
+ def get_node_state(self, node_id: str) -> Any:
74
+ """Get state for specific node.
75
+
76
+ Args:
77
+ node_id: Node identifier
78
+
79
+ Returns:
80
+ Node state or None if not found
81
+ """
82
+ return self.node_states.get(node_id)
83
+
84
+ def set_node_state(self, node_id: str, state: Any) -> None:
85
+ """Set state for specific node.
86
+
87
+ Args:
88
+ node_id: Node identifier
89
+ state: State to store
90
+ """
91
+ self.node_states[node_id] = state
92
+
93
+ def get_convergence_context(self) -> Dict[str, Any]:
94
+ """Get context for convergence evaluation.
95
+
96
+ Returns:
97
+ Dict with iteration info, history, and trends
98
+ """
99
+ context = {
100
+ "iteration": self.iteration,
101
+ "history": self.history,
102
+ "elapsed_time": self.elapsed_time,
103
+ "metadata": self.metadata,
104
+ "node_states": self.node_states,
105
+ }
106
+
107
+ # Add trend analysis if we have history
108
+ if len(self.history) >= 2:
109
+ context["trend"] = self.calculate_trend()
110
+
111
+ return context
112
+
113
+ def calculate_trend(self) -> Dict[str, Any]:
114
+ """Calculate trends from iteration history.
115
+
116
+ Returns:
117
+ Dict with trend information
118
+ """
119
+ if len(self.history) < 2:
120
+ return {}
121
+
122
+ trends = {
123
+ "iteration_times": [],
124
+ "numeric_trends": {},
125
+ }
126
+
127
+ # Calculate iteration times
128
+ for i in range(1, len(self.history)):
129
+ trends["iteration_times"].append(self.history[i]["iteration_time"])
130
+
131
+ # Find numeric values and calculate trends
132
+ all_keys = set()
133
+ for entry in self.history:
134
+ all_keys.update(self._extract_numeric_keys(entry["results"]))
135
+
136
+ for key in all_keys:
137
+ values = []
138
+ for entry in self.history:
139
+ value = self._extract_value(entry["results"], key)
140
+ if value is not None and isinstance(value, (int, float)):
141
+ values.append(value)
142
+
143
+ if len(values) >= 2:
144
+ # Calculate simple trend metrics
145
+ trends["numeric_trends"][key] = {
146
+ "values": values,
147
+ "latest": values[-1],
148
+ "previous": values[-2],
149
+ "change": values[-1] - values[-2],
150
+ "change_percent": (
151
+ (values[-1] - values[-2]) / abs(values[-2]) * 100
152
+ if values[-2] != 0
153
+ else 0
154
+ ),
155
+ "average": sum(values) / len(values),
156
+ "min": min(values),
157
+ "max": max(values),
158
+ }
159
+
160
+ return trends
161
+
162
+ def _extract_numeric_keys(self, obj: Any, prefix: str = "") -> List[str]:
163
+ """Extract all numeric value keys from nested dict."""
164
+ keys = []
165
+
166
+ if isinstance(obj, dict):
167
+ for key, value in obj.items():
168
+ full_key = f"{prefix}.{key}" if prefix else key
169
+ if isinstance(value, (int, float)):
170
+ keys.append(full_key)
171
+ elif isinstance(value, dict):
172
+ keys.extend(self._extract_numeric_keys(value, full_key))
173
+
174
+ return keys
175
+
176
+ def _extract_value(self, obj: Any, key_path: str) -> Any:
177
+ """Extract value from nested dict using dot notation."""
178
+ keys = key_path.split(".")
179
+ value = obj
180
+
181
+ for key in keys:
182
+ if isinstance(value, dict) and key in value:
183
+ value = value[key]
184
+ else:
185
+ return None
186
+
187
+ return value
188
+
189
+ def get_summary(self) -> Dict[str, Any]:
190
+ """Get summary of cycle execution.
191
+
192
+ Returns:
193
+ Dict with summary statistics
194
+ """
195
+ summary = {
196
+ "cycle_id": self.cycle_id,
197
+ "iterations": self.iteration,
198
+ "elapsed_time": self.elapsed_time,
199
+ "start_time": datetime.fromtimestamp(
200
+ self.start_time, timezone.utc
201
+ ).isoformat(),
202
+ "history_length": len(self.history),
203
+ }
204
+
205
+ if self.history:
206
+ summary["first_iteration"] = self.history[0]["timestamp"]
207
+ summary["last_iteration"] = self.history[-1]["timestamp"]
208
+ summary["average_iteration_time"] = sum(
209
+ h.get("iteration_time", 0) for h in self.history[1:]
210
+ ) / max(1, len(self.history) - 1)
211
+
212
+ # Add trend summary if available
213
+ trends = self.calculate_trend()
214
+ if trends.get("numeric_trends"):
215
+ summary["trends"] = {
216
+ key: {
217
+ "latest": data["latest"],
218
+ "change": data["change"],
219
+ "change_percent": data["change_percent"],
220
+ }
221
+ for key, data in trends["numeric_trends"].items()
222
+ }
223
+
224
+ return summary
225
+
226
+ def to_dict(self) -> Dict[str, Any]:
227
+ """Serialize state to dictionary.
228
+
229
+ Returns:
230
+ Dict representation of state
231
+ """
232
+ return {
233
+ "cycle_id": self.cycle_id,
234
+ "iteration": self.iteration,
235
+ "history": self.history,
236
+ "metadata": self.metadata,
237
+ "start_time": self.start_time,
238
+ "last_update_time": self.last_update_time,
239
+ "node_states": self.node_states,
240
+ }
241
+
242
+ @classmethod
243
+ def from_dict(cls, data: Dict[str, Any]) -> "CycleState":
244
+ """Create CycleState from dictionary.
245
+
246
+ Args:
247
+ data: Dictionary representation
248
+
249
+ Returns:
250
+ CycleState instance
251
+ """
252
+ state = cls(data.get("cycle_id", "default"))
253
+ state.iteration = data.get("iteration", 0)
254
+ state.history = data.get("history", [])
255
+ state.metadata = data.get("metadata", {})
256
+ state.start_time = data.get("start_time", time.time())
257
+ state.last_update_time = data.get("last_update_time", state.start_time)
258
+ state.node_states = data.get("node_states", {})
259
+ return state
260
+
261
+
262
+ class CycleStateManager:
263
+ """Manages multiple cycle states for nested cycles."""
264
+
265
+ def __init__(self):
266
+ """Initialize cycle state manager."""
267
+ self.states: Dict[str, CycleState] = {}
268
+ self.active_cycles: List[str] = []
269
+
270
+ def get_or_create_state(self, cycle_id: str) -> CycleState:
271
+ """Get existing state or create new one.
272
+
273
+ Args:
274
+ cycle_id: Cycle identifier
275
+
276
+ Returns:
277
+ CycleState instance
278
+ """
279
+ if cycle_id not in self.states:
280
+ self.states[cycle_id] = CycleState(cycle_id)
281
+ logger.info(f"Created new cycle state for: {cycle_id}")
282
+
283
+ return self.states[cycle_id]
284
+
285
+ def push_cycle(self, cycle_id: str) -> None:
286
+ """Push cycle onto active stack (for nested cycles).
287
+
288
+ Args:
289
+ cycle_id: Cycle identifier
290
+ """
291
+ self.active_cycles.append(cycle_id)
292
+ logger.debug(f"Pushed cycle: {cycle_id}, stack: {self.active_cycles}")
293
+
294
+ def pop_cycle(self) -> Optional[str]:
295
+ """Pop cycle from active stack.
296
+
297
+ Returns:
298
+ Popped cycle ID or None
299
+ """
300
+ if self.active_cycles:
301
+ cycle_id = self.active_cycles.pop()
302
+ logger.debug(f"Popped cycle: {cycle_id}, stack: {self.active_cycles}")
303
+ return cycle_id
304
+ return None
305
+
306
+ def get_active_cycle(self) -> Optional[str]:
307
+ """Get currently active cycle ID.
308
+
309
+ Returns:
310
+ Active cycle ID or None
311
+ """
312
+ return self.active_cycles[-1] if self.active_cycles else None
313
+
314
+ def get_all_summaries(self) -> Dict[str, Dict[str, Any]]:
315
+ """Get summaries for all cycles.
316
+
317
+ Returns:
318
+ Dict mapping cycle_id to summary
319
+ """
320
+ return {
321
+ cycle_id: state.get_summary() for cycle_id, state in self.states.items()
322
+ }
323
+
324
+ def clear(self, cycle_id: Optional[str] = None) -> None:
325
+ """Clear cycle state(s).
326
+
327
+ Args:
328
+ cycle_id: Specific cycle to clear, or None to clear all
329
+ """
330
+ if cycle_id:
331
+ if cycle_id in self.states:
332
+ del self.states[cycle_id]
333
+ self.active_cycles = [c for c in self.active_cycles if c != cycle_id]
334
+ logger.info(f"Cleared cycle state: {cycle_id}")
335
+ else:
336
+ self.states.clear()
337
+ self.active_cycles.clear()
338
+ logger.info("Cleared all cycle states")