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,1280 @@
1
+ """Iterative LLM Agent with progressive MCP discovery and execution capabilities."""
2
+
3
+ import time
4
+ from dataclasses import dataclass, field
5
+ from datetime import datetime
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ from kailash.nodes.ai.llm_agent import LLMAgentNode
9
+ from kailash.nodes.base import NodeParameter, register_node
10
+
11
+
12
+ @dataclass
13
+ class IterationState:
14
+ """State tracking for a single iteration."""
15
+
16
+ iteration: int
17
+ phase: str # discovery, planning, execution, reflection, convergence, synthesis
18
+ start_time: float
19
+ end_time: Optional[float] = None
20
+ discoveries: Dict[str, Any] = field(default_factory=dict)
21
+ plan: Dict[str, Any] = field(default_factory=dict)
22
+ execution_results: Dict[str, Any] = field(default_factory=dict)
23
+ reflection: Dict[str, Any] = field(default_factory=dict)
24
+ convergence_decision: Dict[str, Any] = field(default_factory=dict)
25
+ success: bool = False
26
+ error: Optional[str] = None
27
+
28
+ def to_dict(self) -> Dict[str, Any]:
29
+ """Convert to dictionary for serialization."""
30
+ return {
31
+ "iteration": self.iteration,
32
+ "phase": self.phase,
33
+ "start_time": self.start_time,
34
+ "end_time": self.end_time,
35
+ "duration": (self.end_time - self.start_time) if self.end_time else None,
36
+ "discoveries": self.discoveries,
37
+ "plan": self.plan,
38
+ "execution_results": self.execution_results,
39
+ "reflection": self.reflection,
40
+ "convergence_decision": self.convergence_decision,
41
+ "success": self.success,
42
+ "error": self.error,
43
+ }
44
+
45
+
46
+ @dataclass
47
+ class MCPToolCapability:
48
+ """Semantic understanding of an MCP tool's capabilities."""
49
+
50
+ name: str
51
+ description: str
52
+ primary_function: str
53
+ input_requirements: List[str]
54
+ output_format: str
55
+ domain: str
56
+ complexity: str # simple, medium, complex
57
+ dependencies: List[str]
58
+ confidence: float
59
+ server_source: str
60
+
61
+ def to_dict(self) -> Dict[str, Any]:
62
+ return {
63
+ "name": self.name,
64
+ "description": self.description,
65
+ "primary_function": self.primary_function,
66
+ "input_requirements": self.input_requirements,
67
+ "output_format": self.output_format,
68
+ "domain": self.domain,
69
+ "complexity": self.complexity,
70
+ "dependencies": self.dependencies,
71
+ "confidence": self.confidence,
72
+ "server_source": self.server_source,
73
+ }
74
+
75
+
76
+ @register_node()
77
+ class IterativeLLMAgentNode(LLMAgentNode):
78
+ """
79
+ Iterative LLM Agent with progressive MCP discovery and execution.
80
+
81
+ This agent can discover MCP tools and resources dynamically, plan and execute
82
+ multi-step processes, reflect on results, and converge when goals are met
83
+ or iteration limits are reached.
84
+
85
+ Key Features:
86
+ - Progressive MCP discovery without pre-configuration
87
+ - 6-phase iterative process (Discovery → Planning → Execution → Reflection → Convergence → Synthesis)
88
+ - Semantic tool understanding and capability mapping
89
+ - Adaptive strategy based on iteration results
90
+ - Smart convergence criteria and resource management
91
+
92
+ Examples:
93
+ >>> # Basic iterative agent
94
+ >>> agent = IterativeLLMAgentNode()
95
+ >>> result = agent.run(
96
+ ... messages=[{"role": "user", "content": "Find and analyze healthcare AI trends"}],
97
+ ... mcp_servers=["http://localhost:8080"],
98
+ ... max_iterations=3
99
+ ... )
100
+
101
+ >>> # Advanced iterative agent with custom convergence
102
+ >>> result = agent.run(
103
+ ... messages=[{"role": "user", "content": "Research and recommend AI implementation strategy"}],
104
+ ... mcp_servers=["http://ai-registry:8080", "http://knowledge-base:8081"],
105
+ ... max_iterations=5,
106
+ ... discovery_mode="semantic",
107
+ ... convergence_criteria={
108
+ ... "goal_satisfaction": {"threshold": 0.9},
109
+ ... "diminishing_returns": {"min_improvement": 0.1}
110
+ ... }
111
+ ... )
112
+ """
113
+
114
+ def get_parameters(self) -> Dict[str, NodeParameter]:
115
+ """Get parameters for iterative LLM agent configuration."""
116
+ base_params = super().get_parameters()
117
+
118
+ iterative_params = {
119
+ # Iteration Control
120
+ "max_iterations": NodeParameter(
121
+ name="max_iterations",
122
+ type=int,
123
+ required=False,
124
+ default=5,
125
+ description="Maximum number of discovery-execution cycles",
126
+ ),
127
+ "convergence_criteria": NodeParameter(
128
+ name="convergence_criteria",
129
+ type=dict,
130
+ required=False,
131
+ default={},
132
+ description="""Criteria for determining when to stop iterating. Supports:
133
+ - goal_satisfaction: {"threshold": 0.8} - Stop when confidence >= threshold
134
+ - early_satisfaction: {"enabled": True, "threshold": 0.85, "custom_check": callable} - Early stopping with optional custom function
135
+ - diminishing_returns: {"enabled": True, "min_improvement": 0.05} - Stop when improvement < threshold
136
+ - quality_gates: {"min_confidence": 0.7, "custom_validator": callable} - Quality checks with optional custom validator
137
+ - resource_limits: {"max_cost": 1.0, "max_time": 300} - Hard resource limits
138
+ - custom_criteria: [{"name": "my_check", "function": callable, "weight": 0.5}] - User-defined criteria
139
+ """,
140
+ ),
141
+ # Discovery Configuration
142
+ "discovery_mode": NodeParameter(
143
+ name="discovery_mode",
144
+ type=str,
145
+ required=False,
146
+ default="progressive",
147
+ description="Discovery strategy: progressive, exhaustive, semantic",
148
+ ),
149
+ "discovery_budget": NodeParameter(
150
+ name="discovery_budget",
151
+ type=dict,
152
+ required=False,
153
+ default={"max_servers": 5, "max_tools": 20, "max_resources": 50},
154
+ description="Limits for discovery process",
155
+ ),
156
+ # Iterative Configuration
157
+ "reflection_enabled": NodeParameter(
158
+ name="reflection_enabled",
159
+ type=bool,
160
+ required=False,
161
+ default=True,
162
+ description="Enable reflection phase between iterations",
163
+ ),
164
+ "adaptation_strategy": NodeParameter(
165
+ name="adaptation_strategy",
166
+ type=str,
167
+ required=False,
168
+ default="dynamic",
169
+ description="How to adapt strategy: static, dynamic, ml_guided",
170
+ ),
171
+ # Performance and Monitoring
172
+ "enable_detailed_logging": NodeParameter(
173
+ name="enable_detailed_logging",
174
+ type=bool,
175
+ required=False,
176
+ default=True,
177
+ description="Enable detailed iteration logging for debugging",
178
+ ),
179
+ "iteration_timeout": NodeParameter(
180
+ name="iteration_timeout",
181
+ type=int,
182
+ required=False,
183
+ default=300,
184
+ description="Timeout for each iteration in seconds",
185
+ ),
186
+ }
187
+
188
+ # Merge base parameters with iterative parameters
189
+ base_params.update(iterative_params)
190
+ return base_params
191
+
192
+ def run(self, **kwargs) -> Dict[str, Any]:
193
+ """
194
+ Execute iterative LLM agent with 6-phase process.
195
+
196
+ Args:
197
+ **kwargs: All parameters from get_parameters() plus inherited LLMAgentNode params
198
+
199
+ Returns:
200
+ Dict containing:
201
+ success (bool): Whether the iterative process completed successfully
202
+ final_response (str): Synthesized final response
203
+ iterations (List[Dict]): Detailed log of all iterations
204
+ discoveries (Dict): All discovered MCP capabilities
205
+ convergence_reason (str): Why the process stopped
206
+ total_duration (float): Total execution time
207
+ resource_usage (Dict): Resource consumption metrics
208
+ """
209
+ # Extract iterative-specific parameters
210
+ max_iterations = kwargs.get("max_iterations", 5)
211
+ convergence_criteria = kwargs.get("convergence_criteria", {})
212
+ discovery_mode = kwargs.get("discovery_mode", "progressive")
213
+ discovery_budget = kwargs.get(
214
+ "discovery_budget", {"max_servers": 5, "max_tools": 20, "max_resources": 50}
215
+ )
216
+ reflection_enabled = kwargs.get("reflection_enabled", True)
217
+ adaptation_strategy = kwargs.get("adaptation_strategy", "dynamic")
218
+ enable_detailed_logging = kwargs.get("enable_detailed_logging", True)
219
+ kwargs.get("iteration_timeout", 300)
220
+
221
+ # Initialize iterative execution state
222
+ start_time = time.time()
223
+ iterations: List[IterationState] = []
224
+ global_discoveries = {
225
+ "servers": {},
226
+ "tools": {},
227
+ "resources": {},
228
+ "capabilities": {},
229
+ }
230
+ converged = False
231
+ convergence_reason = "max_iterations_reached"
232
+
233
+ try:
234
+ # Main iterative loop
235
+ for iteration_num in range(1, max_iterations + 1):
236
+ iteration_state = IterationState(
237
+ iteration=iteration_num, phase="discovery", start_time=time.time()
238
+ )
239
+
240
+ if enable_detailed_logging:
241
+ self.logger.info(
242
+ f"Starting iteration {iteration_num}/{max_iterations}"
243
+ )
244
+
245
+ try:
246
+ # Phase 1: Discovery
247
+ iteration_state.discoveries = self._phase_discovery(
248
+ kwargs, global_discoveries, discovery_mode, discovery_budget
249
+ )
250
+
251
+ # Phase 2: Planning
252
+ iteration_state.phase = "planning"
253
+ iteration_state.plan = self._phase_planning(
254
+ kwargs,
255
+ iteration_state.discoveries,
256
+ global_discoveries,
257
+ iterations,
258
+ )
259
+
260
+ # Phase 3: Execution
261
+ iteration_state.phase = "execution"
262
+ iteration_state.execution_results = self._phase_execution(
263
+ kwargs, iteration_state.plan, iteration_state.discoveries
264
+ )
265
+
266
+ # Phase 4: Reflection (if enabled)
267
+ if reflection_enabled:
268
+ iteration_state.phase = "reflection"
269
+ iteration_state.reflection = self._phase_reflection(
270
+ kwargs, iteration_state.execution_results, iterations
271
+ )
272
+
273
+ # Phase 5: Convergence
274
+ iteration_state.phase = "convergence"
275
+ convergence_result = self._phase_convergence(
276
+ kwargs,
277
+ iteration_state,
278
+ iterations,
279
+ convergence_criteria,
280
+ global_discoveries,
281
+ )
282
+ iteration_state.convergence_decision = convergence_result
283
+
284
+ if convergence_result["should_stop"]:
285
+ converged = True
286
+ convergence_reason = convergence_result["reason"]
287
+
288
+ # Update global discoveries
289
+ self._update_global_discoveries(
290
+ global_discoveries, iteration_state.discoveries
291
+ )
292
+
293
+ iteration_state.success = True
294
+ iteration_state.end_time = time.time()
295
+
296
+ except Exception as e:
297
+ iteration_state.error = str(e)
298
+ iteration_state.success = False
299
+ iteration_state.end_time = time.time()
300
+
301
+ if enable_detailed_logging:
302
+ self.logger.error(f"Iteration {iteration_num} failed: {e}")
303
+
304
+ iterations.append(iteration_state)
305
+
306
+ # Check if we should stop
307
+ if converged:
308
+ break
309
+
310
+ # Adapt strategy for next iteration if enabled
311
+ if adaptation_strategy == "dynamic" and iteration_state.success:
312
+ self._adapt_strategy(kwargs, iteration_state, iterations)
313
+
314
+ # Phase 6: Synthesis
315
+ final_response = self._phase_synthesis(
316
+ kwargs, iterations, global_discoveries
317
+ )
318
+
319
+ total_duration = time.time() - start_time
320
+
321
+ return {
322
+ "success": True,
323
+ "final_response": final_response,
324
+ "iterations": [iter_state.to_dict() for iter_state in iterations],
325
+ "discoveries": global_discoveries,
326
+ "convergence_reason": convergence_reason,
327
+ "total_iterations": len(iterations),
328
+ "total_duration": total_duration,
329
+ "resource_usage": self._calculate_resource_usage(iterations),
330
+ "metadata": {
331
+ "max_iterations": max_iterations,
332
+ "discovery_mode": discovery_mode,
333
+ "reflection_enabled": reflection_enabled,
334
+ "adaptation_strategy": adaptation_strategy,
335
+ },
336
+ }
337
+
338
+ except Exception as e:
339
+ return {
340
+ "success": False,
341
+ "error": str(e),
342
+ "error_type": type(e).__name__,
343
+ "iterations": [iter_state.to_dict() for iter_state in iterations],
344
+ "discoveries": global_discoveries,
345
+ "total_duration": time.time() - start_time,
346
+ "convergence_reason": "error_occurred",
347
+ "recovery_suggestions": [
348
+ "Check MCP server connectivity",
349
+ "Verify discovery budget limits",
350
+ "Review convergence criteria configuration",
351
+ "Check iteration timeout settings",
352
+ ],
353
+ }
354
+
355
+ def _phase_discovery(
356
+ self,
357
+ kwargs: Dict[str, Any],
358
+ global_discoveries: Dict[str, Any],
359
+ discovery_mode: str,
360
+ discovery_budget: Dict[str, Any],
361
+ ) -> Dict[str, Any]:
362
+ """
363
+ Phase 1: Discover MCP servers, tools, and resources.
364
+
365
+ Args:
366
+ kwargs: Original run parameters
367
+ global_discoveries: Accumulated discoveries from previous iterations
368
+ discovery_mode: Discovery strategy
369
+ discovery_budget: Resource limits for discovery
370
+
371
+ Returns:
372
+ Dictionary containing new discoveries in this iteration
373
+ """
374
+ discoveries = {
375
+ "new_servers": [],
376
+ "new_tools": [],
377
+ "new_resources": [],
378
+ "tool_capabilities": [],
379
+ }
380
+
381
+ mcp_servers = kwargs.get("mcp_servers", [])
382
+
383
+ # Discover from each MCP server
384
+ for server_config in mcp_servers:
385
+ server_id = (
386
+ server_config
387
+ if isinstance(server_config, str)
388
+ else server_config.get("url", "unknown")
389
+ )
390
+
391
+ # Skip if already discovered and not in exhaustive mode
392
+ if (
393
+ discovery_mode != "exhaustive"
394
+ and server_id in global_discoveries["servers"]
395
+ ):
396
+ continue
397
+
398
+ try:
399
+ # Discover tools from this server
400
+ server_tools = self._discover_server_tools(
401
+ server_config, discovery_budget
402
+ )
403
+ self.logger.info(
404
+ f"Discovered {len(server_tools)} tools from server {server_id}"
405
+ )
406
+ discoveries["new_tools"].extend(server_tools)
407
+
408
+ # Discover resources from this server
409
+ server_resources = self._discover_server_resources(
410
+ server_config, discovery_budget
411
+ )
412
+ self.logger.info(
413
+ f"Discovered {len(server_resources)} resources from server {server_id}"
414
+ )
415
+ discoveries["new_resources"].extend(server_resources)
416
+
417
+ # Analyze tool capabilities if in semantic mode
418
+ if discovery_mode == "semantic":
419
+ for tool in server_tools:
420
+ capability = self._analyze_tool_capability(tool, server_id)
421
+ discoveries["tool_capabilities"].append(capability.to_dict())
422
+
423
+ discoveries["new_servers"].append(
424
+ {
425
+ "id": server_id,
426
+ "config": server_config,
427
+ "discovered_at": datetime.now().isoformat(),
428
+ "tools_count": len(server_tools),
429
+ "resources_count": len(server_resources),
430
+ }
431
+ )
432
+
433
+ self.logger.info(
434
+ f"Server {server_id} discovery complete: {len(server_tools)} tools, {len(server_resources)} resources"
435
+ )
436
+
437
+ except Exception as e:
438
+ self.logger.debug(f"Discovery failed for server {server_id}: {e}")
439
+ discoveries["new_servers"].append(
440
+ {
441
+ "id": server_id,
442
+ "config": server_config,
443
+ "discovered_at": datetime.now().isoformat(),
444
+ "error": str(e),
445
+ "tools_count": 0,
446
+ "resources_count": 0,
447
+ }
448
+ )
449
+
450
+ return discoveries
451
+
452
+ def _discover_server_tools(
453
+ self, server_config: Any, budget: Dict[str, Any]
454
+ ) -> List[Dict[str, Any]]:
455
+ """Discover tools from a specific MCP server."""
456
+ # Use existing MCP tool discovery from parent class
457
+ try:
458
+ # Ensure MCP client is initialized
459
+ if not hasattr(self, "_mcp_client"):
460
+ from kailash.mcp import MCPClient
461
+
462
+ self._mcp_client = MCPClient()
463
+
464
+ # Call parent class method which returns OpenAI function format
465
+ discovered_tools_openai_format = self._discover_mcp_tools(
466
+ [server_config]
467
+ if not isinstance(server_config, list)
468
+ else server_config
469
+ )
470
+
471
+ # Convert from OpenAI function format to simple format for iterative agent
472
+ discovered_tools = []
473
+ for tool in discovered_tools_openai_format:
474
+ if isinstance(tool, dict) and "function" in tool:
475
+ func = tool["function"]
476
+ discovered_tools.append(
477
+ {
478
+ "name": func.get("name", "unknown"),
479
+ "description": func.get("description", ""),
480
+ "parameters": func.get("parameters", {}),
481
+ "mcp_server": func.get("mcp_server", "unknown"),
482
+ "mcp_server_config": func.get(
483
+ "mcp_server_config", server_config
484
+ ),
485
+ }
486
+ )
487
+ else:
488
+ # Handle direct format
489
+ discovered_tools.append(tool)
490
+
491
+ # Apply budget limits
492
+ max_tools = budget.get("max_tools", 20)
493
+ return discovered_tools[:max_tools]
494
+
495
+ except Exception as e:
496
+ self.logger.debug(f"Tool discovery failed: {e}")
497
+ return []
498
+
499
+ def _discover_server_resources(
500
+ self, server_config: Any, budget: Dict[str, Any]
501
+ ) -> List[Dict[str, Any]]:
502
+ """Discover resources from a specific MCP server."""
503
+ # Mock implementation - in real version would use MCP resource discovery
504
+ try:
505
+ server_id = (
506
+ server_config
507
+ if isinstance(server_config, str)
508
+ else server_config.get("url", "unknown")
509
+ )
510
+ max_resources = budget.get("max_resources", 50)
511
+
512
+ # Mock discovered resources
513
+ mock_resources = [
514
+ {
515
+ "uri": f"{server_id}/resource/data/overview",
516
+ "name": "Data Overview",
517
+ "type": "data",
518
+ "description": "Overview of available data sources",
519
+ },
520
+ {
521
+ "uri": f"{server_id}/resource/templates/analysis",
522
+ "name": "Analysis Templates",
523
+ "type": "template",
524
+ "description": "Pre-built analysis templates",
525
+ },
526
+ ]
527
+
528
+ return mock_resources[:max_resources]
529
+
530
+ except Exception as e:
531
+ self.logger.debug(f"Resource discovery failed: {e}")
532
+ return []
533
+
534
+ def _analyze_tool_capability(
535
+ self, tool: Dict[str, Any], server_id: str
536
+ ) -> MCPToolCapability:
537
+ """Analyze tool description to understand semantic capabilities."""
538
+ # Extract tool information
539
+ if isinstance(tool, dict) and "function" in tool:
540
+ func = tool["function"]
541
+ name = func.get("name", "unknown")
542
+ description = func.get("description", "")
543
+ else:
544
+ name = tool.get("name", "unknown")
545
+ description = tool.get("description", "")
546
+
547
+ # Simple semantic analysis (in real implementation, use LLM for analysis)
548
+ primary_function = "data_processing"
549
+ if "search" in description.lower():
550
+ primary_function = "search"
551
+ elif "analyze" in description.lower():
552
+ primary_function = "analysis"
553
+ elif "create" in description.lower() or "generate" in description.lower():
554
+ primary_function = "generation"
555
+
556
+ # Determine domain
557
+ domain = "general"
558
+ if any(
559
+ keyword in description.lower()
560
+ for keyword in ["health", "medical", "clinical"]
561
+ ):
562
+ domain = "healthcare"
563
+ elif any(
564
+ keyword in description.lower()
565
+ for keyword in ["finance", "banking", "investment"]
566
+ ):
567
+ domain = "finance"
568
+ elif any(
569
+ keyword in description.lower()
570
+ for keyword in ["data", "analytics", "statistics"]
571
+ ):
572
+ domain = "data_science"
573
+
574
+ # Determine complexity
575
+ complexity = "simple"
576
+ if len(description) > 100 or "complex" in description.lower():
577
+ complexity = "complex"
578
+ elif len(description) > 50:
579
+ complexity = "medium"
580
+
581
+ return MCPToolCapability(
582
+ name=name,
583
+ description=description,
584
+ primary_function=primary_function,
585
+ input_requirements=["query"] if "search" in primary_function else ["data"],
586
+ output_format="text",
587
+ domain=domain,
588
+ complexity=complexity,
589
+ dependencies=[],
590
+ confidence=0.8, # Mock confidence
591
+ server_source=server_id,
592
+ )
593
+
594
+ def _phase_planning(
595
+ self,
596
+ kwargs: Dict[str, Any],
597
+ discoveries: Dict[str, Any],
598
+ global_discoveries: Dict[str, Any],
599
+ previous_iterations: List[IterationState],
600
+ ) -> Dict[str, Any]:
601
+ """Phase 2: Create execution plan based on discoveries."""
602
+ messages = kwargs.get("messages", [])
603
+ user_query = ""
604
+
605
+ # Extract user intent
606
+ for msg in reversed(messages):
607
+ if msg.get("role") == "user":
608
+ user_query = msg.get("content", "")
609
+ break
610
+
611
+ # Analyze available tools and create plan
612
+ available_tools = discoveries.get("new_tools", []) + list(
613
+ global_discoveries.get("tools", {}).values()
614
+ )
615
+
616
+ # Simple planning logic (in real implementation, use LLM for planning)
617
+ plan = {
618
+ "user_query": user_query,
619
+ "selected_tools": [],
620
+ "execution_steps": [],
621
+ "expected_outcomes": [],
622
+ "resource_requirements": {},
623
+ "success_criteria": {},
624
+ }
625
+
626
+ # Select relevant tools
627
+ for tool in available_tools[:3]: # Limit to top 3 tools
628
+ if isinstance(tool, dict) and "function" in tool:
629
+ plan["selected_tools"].append(tool["function"]["name"])
630
+ elif isinstance(tool, dict):
631
+ plan["selected_tools"].append(tool.get("name", "unknown"))
632
+
633
+ # Create execution steps
634
+ if "analyze" in user_query.lower():
635
+ plan["execution_steps"] = [
636
+ {
637
+ "step": 1,
638
+ "action": "gather_data",
639
+ "tools": plan["selected_tools"][:1],
640
+ },
641
+ {
642
+ "step": 2,
643
+ "action": "perform_analysis",
644
+ "tools": plan["selected_tools"][1:2],
645
+ },
646
+ {
647
+ "step": 3,
648
+ "action": "generate_insights",
649
+ "tools": plan["selected_tools"][2:3],
650
+ },
651
+ ]
652
+ else:
653
+ plan["execution_steps"] = [
654
+ {"step": 1, "action": "execute_query", "tools": plan["selected_tools"]}
655
+ ]
656
+
657
+ plan["expected_outcomes"] = ["analysis_results", "insights", "recommendations"]
658
+
659
+ return plan
660
+
661
+ def _phase_execution(
662
+ self, kwargs: Dict[str, Any], plan: Dict[str, Any], discoveries: Dict[str, Any]
663
+ ) -> Dict[str, Any]:
664
+ """Phase 3: Execute the planned actions."""
665
+ execution_results = {
666
+ "steps_completed": [],
667
+ "tool_outputs": {},
668
+ "intermediate_results": [],
669
+ "success": True,
670
+ "errors": [],
671
+ }
672
+
673
+ # Execute each step in the plan
674
+ for step in plan.get("execution_steps", []):
675
+ step_num = step.get("step", 0)
676
+ action = step.get("action", "unknown")
677
+ tools = step.get("tools", [])
678
+
679
+ try:
680
+ # Mock tool execution (in real implementation, call actual tools)
681
+ step_result = {
682
+ "step": step_num,
683
+ "action": action,
684
+ "tools_used": tools,
685
+ "output": f"Mock execution result for {action} using tools: {', '.join(tools)}",
686
+ "success": True,
687
+ "duration": 1.5,
688
+ }
689
+
690
+ execution_results["steps_completed"].append(step_result)
691
+ execution_results["intermediate_results"].append(step_result["output"])
692
+
693
+ # Store tool outputs
694
+ for tool in tools:
695
+ execution_results["tool_outputs"][tool] = f"Output from {tool}"
696
+
697
+ except Exception as e:
698
+ error_result = {
699
+ "step": step_num,
700
+ "action": action,
701
+ "tools_used": tools,
702
+ "error": str(e),
703
+ "success": False,
704
+ }
705
+ execution_results["steps_completed"].append(error_result)
706
+ execution_results["errors"].append(str(e))
707
+ execution_results["success"] = False
708
+
709
+ return execution_results
710
+
711
+ def _phase_reflection(
712
+ self,
713
+ kwargs: Dict[str, Any],
714
+ execution_results: Dict[str, Any],
715
+ previous_iterations: List[IterationState],
716
+ ) -> Dict[str, Any]:
717
+ """Phase 4: Reflect on execution results and assess progress."""
718
+ reflection = {
719
+ "quality_assessment": {},
720
+ "goal_progress": {},
721
+ "areas_for_improvement": [],
722
+ "next_iteration_suggestions": [],
723
+ "confidence_score": 0.0,
724
+ }
725
+
726
+ # Assess execution quality
727
+ total_steps = len(execution_results.get("steps_completed", []))
728
+ successful_steps = sum(
729
+ 1
730
+ for step in execution_results.get("steps_completed", [])
731
+ if step.get("success", False)
732
+ )
733
+
734
+ reflection["quality_assessment"] = {
735
+ "execution_success_rate": successful_steps / max(total_steps, 1),
736
+ "errors_encountered": len(execution_results.get("errors", [])),
737
+ "tools_utilized": len(execution_results.get("tool_outputs", {})),
738
+ "output_quality": (
739
+ "good" if successful_steps > total_steps * 0.7 else "poor"
740
+ ),
741
+ }
742
+
743
+ # Assess progress toward goal
744
+ messages = kwargs.get("messages", [])
745
+ user_query = ""
746
+ for msg in reversed(messages):
747
+ if msg.get("role") == "user":
748
+ user_query = msg.get("content", "")
749
+ break
750
+
751
+ # Simple goal progress assessment
752
+ has_analysis = any(
753
+ "analyze" in result.lower()
754
+ for result in execution_results.get("intermediate_results", [])
755
+ )
756
+ has_data = len(execution_results.get("tool_outputs", {})) > 0
757
+
758
+ progress_score = 0.5 # Base score
759
+ if has_analysis:
760
+ progress_score += 0.3
761
+ if has_data:
762
+ progress_score += 0.2
763
+
764
+ reflection["goal_progress"] = {
765
+ "estimated_completion": min(progress_score, 1.0),
766
+ "goals_achieved": ["data_gathering"] if has_data else [],
767
+ "goals_remaining": ["analysis"] if not has_analysis else [],
768
+ "quality_threshold_met": progress_score > 0.7,
769
+ }
770
+
771
+ # Suggest improvements
772
+ if execution_results.get("errors"):
773
+ reflection["areas_for_improvement"].append(
774
+ "Error handling and tool reliability"
775
+ )
776
+ if successful_steps < total_steps:
777
+ reflection["areas_for_improvement"].append(
778
+ "Tool selection and configuration"
779
+ )
780
+
781
+ # Suggestions for next iteration
782
+ if progress_score < 0.8:
783
+ reflection["next_iteration_suggestions"].append(
784
+ "Explore additional tools or data sources"
785
+ )
786
+ if not has_analysis and "analyze" in user_query.lower():
787
+ reflection["next_iteration_suggestions"].append(
788
+ "Focus on analysis and insight generation"
789
+ )
790
+
791
+ reflection["confidence_score"] = progress_score
792
+
793
+ return reflection
794
+
795
+ def _phase_convergence(
796
+ self,
797
+ kwargs: Dict[str, Any],
798
+ iteration_state: IterationState,
799
+ previous_iterations: List[IterationState],
800
+ convergence_criteria: Dict[str, Any],
801
+ global_discoveries: Dict[str, Any],
802
+ ) -> Dict[str, Any]:
803
+ """Phase 5: Decide whether to continue iterating or stop."""
804
+ convergence_result = {
805
+ "should_stop": False,
806
+ "reason": "",
807
+ "confidence": 0.0,
808
+ "criteria_met": {},
809
+ "recommendations": [],
810
+ }
811
+
812
+ # Default convergence criteria
813
+ default_criteria = {
814
+ "goal_satisfaction": {"threshold": 0.8},
815
+ "diminishing_returns": {
816
+ "enabled": True,
817
+ "min_improvement": 0.05,
818
+ "lookback_window": 2,
819
+ },
820
+ "resource_limits": {"max_cost": 1.0, "max_time": 300},
821
+ "quality_gates": {"min_confidence": 0.7},
822
+ "early_satisfaction": {
823
+ "enabled": True,
824
+ "threshold": 0.85,
825
+ }, # Stop early if very confident
826
+ }
827
+
828
+ # Merge with provided criteria
829
+ criteria = {**default_criteria, **convergence_criteria}
830
+
831
+ # Extract user query for context
832
+ messages = kwargs.get("messages", [])
833
+ user_query = ""
834
+ for msg in reversed(messages):
835
+ if msg.get("role") == "user":
836
+ user_query = msg.get("content", "")
837
+ break
838
+
839
+ # Analyze current execution results for satisfaction
840
+ execution_results = iteration_state.execution_results
841
+ reflection = iteration_state.reflection
842
+
843
+ # Enhanced goal satisfaction analysis
844
+ if reflection and "confidence_score" in reflection:
845
+ confidence_score = reflection["confidence_score"]
846
+ goal_threshold = criteria.get("goal_satisfaction", {}).get("threshold", 0.8)
847
+
848
+ # Check if we have sufficient data and analysis
849
+ has_sufficient_data = len(execution_results.get("tool_outputs", {})) >= 1
850
+ has_analysis_content = any(
851
+ len(result) > 50
852
+ for result in execution_results.get("intermediate_results", [])
853
+ )
854
+ execution_success_rate = reflection.get("quality_assessment", {}).get(
855
+ "execution_success_rate", 0
856
+ )
857
+
858
+ # Enhanced satisfaction calculation
859
+ satisfaction_score = confidence_score
860
+
861
+ # Boost score if we have good data and analysis
862
+ if (
863
+ has_sufficient_data
864
+ and has_analysis_content
865
+ and execution_success_rate > 0.8
866
+ ):
867
+ satisfaction_score += 0.1
868
+
869
+ # Boost score if user query seems simple and we have a good response
870
+ simple_query_indicators = ["what", "how", "analyze", "explain"]
871
+ if any(
872
+ indicator in user_query.lower() for indicator in simple_query_indicators
873
+ ):
874
+ if has_analysis_content:
875
+ satisfaction_score += 0.1
876
+
877
+ # Apply early satisfaction check
878
+ early_config = criteria.get("early_satisfaction", {})
879
+ early_threshold = early_config.get("threshold", 0.85)
880
+ if early_config.get("enabled", True):
881
+ # Check built-in early satisfaction criteria
882
+ meets_builtin_criteria = (
883
+ satisfaction_score >= early_threshold and has_sufficient_data
884
+ )
885
+
886
+ # Check user-defined custom early stopping function
887
+ custom_check_func = early_config.get("custom_check")
888
+ meets_custom_criteria = True
889
+
890
+ if custom_check_func and callable(custom_check_func):
891
+ try:
892
+ # Call user-defined function with current state
893
+ custom_result = custom_check_func(
894
+ {
895
+ "iteration_state": iteration_state,
896
+ "satisfaction_score": satisfaction_score,
897
+ "execution_results": execution_results,
898
+ "reflection": reflection,
899
+ "user_query": user_query,
900
+ "previous_iterations": previous_iterations,
901
+ "global_discoveries": kwargs.get(
902
+ "_global_discoveries", {}
903
+ ),
904
+ }
905
+ )
906
+ meets_custom_criteria = bool(custom_result)
907
+
908
+ if isinstance(custom_result, dict):
909
+ # Custom function can return detailed result
910
+ meets_custom_criteria = custom_result.get(
911
+ "should_stop", False
912
+ )
913
+ if "reason" in custom_result:
914
+ convergence_result["reason"] = (
915
+ f"custom_early_stop: {custom_result['reason']}"
916
+ )
917
+ if "confidence" in custom_result:
918
+ convergence_result["confidence"] = custom_result[
919
+ "confidence"
920
+ ]
921
+ except Exception as e:
922
+ self.logger.warning(
923
+ f"Custom early stopping function failed: {e}"
924
+ )
925
+ meets_custom_criteria = True # Fall back to built-in criteria
926
+
927
+ if meets_builtin_criteria and meets_custom_criteria:
928
+ convergence_result["should_stop"] = True
929
+ if not convergence_result[
930
+ "reason"
931
+ ]: # Only set if custom didn't provide one
932
+ convergence_result["reason"] = "early_satisfaction_achieved"
933
+ if not convergence_result[
934
+ "confidence"
935
+ ]: # Only set if custom didn't provide one
936
+ convergence_result["confidence"] = satisfaction_score
937
+ convergence_result["criteria_met"]["early_satisfaction"] = True
938
+ return convergence_result
939
+
940
+ # Standard goal satisfaction check
941
+ goal_satisfaction = satisfaction_score >= goal_threshold
942
+ convergence_result["criteria_met"]["goal_satisfaction"] = goal_satisfaction
943
+
944
+ if goal_satisfaction:
945
+ convergence_result["should_stop"] = True
946
+ convergence_result["reason"] = "goal_satisfaction_achieved"
947
+ convergence_result["confidence"] = satisfaction_score
948
+
949
+ # Check if first iteration was already very successful
950
+ if len(previous_iterations) == 0: # This is iteration 1
951
+ if execution_results.get("success", False):
952
+ success_rate = reflection.get("quality_assessment", {}).get(
953
+ "execution_success_rate", 0
954
+ )
955
+ confidence = reflection.get("confidence_score", 0)
956
+
957
+ # If first iteration was highly successful and confident, consider stopping
958
+ if success_rate >= 0.9 and confidence >= 0.8:
959
+ convergence_result["should_stop"] = True
960
+ convergence_result["reason"] = "first_iteration_highly_successful"
961
+ convergence_result["confidence"] = confidence
962
+ convergence_result["criteria_met"]["first_iteration_success"] = True
963
+ return convergence_result
964
+
965
+ # Check diminishing returns (only if we've had multiple iterations)
966
+ if len(previous_iterations) >= 1 and criteria.get(
967
+ "diminishing_returns", {}
968
+ ).get("enabled", True):
969
+ lookback = criteria.get("diminishing_returns", {}).get("lookback_window", 2)
970
+ min_improvement = criteria.get("diminishing_returns", {}).get(
971
+ "min_improvement", 0.05
972
+ )
973
+
974
+ # Get recent confidence scores
975
+ recent_scores = []
976
+ if reflection and "confidence_score" in reflection:
977
+ recent_scores.append(reflection["confidence_score"])
978
+
979
+ for prev_iter in previous_iterations[-lookback:]:
980
+ if prev_iter.reflection and "confidence_score" in prev_iter.reflection:
981
+ recent_scores.append(prev_iter.reflection["confidence_score"])
982
+
983
+ if len(recent_scores) >= 2:
984
+ # Compare current with previous iteration
985
+ current_score = recent_scores[0]
986
+ previous_score = recent_scores[1]
987
+ improvement = current_score - previous_score
988
+
989
+ diminishing_returns = improvement < min_improvement
990
+ convergence_result["criteria_met"][
991
+ "diminishing_returns"
992
+ ] = diminishing_returns
993
+
994
+ # Only stop for diminishing returns if we already have decent confidence
995
+ if (
996
+ diminishing_returns
997
+ and current_score >= 0.7
998
+ and not convergence_result["should_stop"]
999
+ ):
1000
+ convergence_result["should_stop"] = True
1001
+ convergence_result["reason"] = "diminishing_returns_detected"
1002
+ convergence_result["confidence"] = current_score
1003
+
1004
+ # Check quality gates
1005
+ quality_threshold = criteria.get("quality_gates", {}).get("min_confidence", 0.7)
1006
+ if reflection and reflection.get("confidence_score", 0) >= quality_threshold:
1007
+ convergence_result["criteria_met"]["quality_gates"] = True
1008
+
1009
+ # If we meet quality gates and have good execution, consider stopping
1010
+ execution_quality = reflection.get("quality_assessment", {}).get(
1011
+ "execution_success_rate", 0
1012
+ )
1013
+
1014
+ # Only stop for quality gates if we've actually discovered and used tools
1015
+ has_real_discoveries = len(global_discoveries.get("tools", {})) > 0
1016
+ tools_actually_used = len(execution_results.get("tool_outputs", {})) > 0
1017
+
1018
+ if (
1019
+ execution_quality >= 0.8
1020
+ and not convergence_result["should_stop"]
1021
+ and has_real_discoveries
1022
+ and tools_actually_used
1023
+ ):
1024
+ convergence_result["should_stop"] = True
1025
+ convergence_result["reason"] = "quality_gates_satisfied"
1026
+ convergence_result["confidence"] = reflection["confidence_score"]
1027
+
1028
+ # Resource limits check
1029
+ resource_limits = criteria.get("resource_limits", {})
1030
+ total_time = sum(
1031
+ (iter_state.end_time - iter_state.start_time)
1032
+ for iter_state in previous_iterations + [iteration_state]
1033
+ if iter_state.end_time
1034
+ )
1035
+
1036
+ if total_time > resource_limits.get("max_time", 300):
1037
+ convergence_result["should_stop"] = True
1038
+ convergence_result["reason"] = "time_limit_exceeded"
1039
+ convergence_result["confidence"] = (
1040
+ reflection.get("confidence_score", 0.5) if reflection else 0.5
1041
+ )
1042
+
1043
+ # Check custom convergence criteria
1044
+ custom_criteria = criteria.get("custom_criteria", [])
1045
+ if custom_criteria and not convergence_result["should_stop"]:
1046
+ total_custom_weight = 0
1047
+ custom_stop_score = 0
1048
+
1049
+ for custom_criterion in custom_criteria:
1050
+ if not isinstance(custom_criterion, dict):
1051
+ continue
1052
+
1053
+ criterion_func = custom_criterion.get("function")
1054
+ criterion_weight = custom_criterion.get("weight", 1.0)
1055
+ criterion_name = custom_criterion.get("name", "unnamed_custom")
1056
+
1057
+ if criterion_func and callable(criterion_func):
1058
+ try:
1059
+ # Call custom convergence function
1060
+ custom_result = criterion_func(
1061
+ {
1062
+ "iteration_state": iteration_state,
1063
+ "previous_iterations": previous_iterations,
1064
+ "execution_results": execution_results,
1065
+ "reflection": reflection,
1066
+ "user_query": user_query,
1067
+ "global_discoveries": kwargs.get(
1068
+ "_global_discoveries", {}
1069
+ ),
1070
+ "total_duration": sum(
1071
+ (iter_state.end_time - iter_state.start_time)
1072
+ for iter_state in previous_iterations
1073
+ + [iteration_state]
1074
+ if iter_state.end_time
1075
+ ),
1076
+ }
1077
+ )
1078
+
1079
+ # Handle different return types
1080
+ if isinstance(custom_result, bool):
1081
+ criterion_score = 1.0 if custom_result else 0.0
1082
+ elif isinstance(custom_result, (int, float)):
1083
+ criterion_score = float(custom_result)
1084
+ elif isinstance(custom_result, dict):
1085
+ criterion_score = custom_result.get("score", 0.0)
1086
+ # If custom function says stop immediately
1087
+ if custom_result.get("stop_immediately", False):
1088
+ convergence_result["should_stop"] = True
1089
+ convergence_result["reason"] = (
1090
+ f"custom_criterion_{criterion_name}_immediate_stop"
1091
+ )
1092
+ convergence_result["confidence"] = custom_result.get(
1093
+ "confidence", 0.8
1094
+ )
1095
+ convergence_result["criteria_met"][
1096
+ f"custom_{criterion_name}"
1097
+ ] = True
1098
+ return convergence_result
1099
+ else:
1100
+ criterion_score = 0.0
1101
+
1102
+ # Accumulate weighted score
1103
+ custom_stop_score += criterion_score * criterion_weight
1104
+ total_custom_weight += criterion_weight
1105
+ convergence_result["criteria_met"][
1106
+ f"custom_{criterion_name}"
1107
+ ] = (criterion_score > 0.5)
1108
+
1109
+ except Exception as e:
1110
+ self.logger.warning(
1111
+ f"Custom convergence criterion '{criterion_name}' failed: {e}"
1112
+ )
1113
+
1114
+ # Check if weighted custom criteria suggest stopping
1115
+ if total_custom_weight > 0:
1116
+ avg_custom_score = custom_stop_score / total_custom_weight
1117
+ if avg_custom_score >= 0.8: # High confidence from custom criteria
1118
+ convergence_result["should_stop"] = True
1119
+ convergence_result["reason"] = "custom_criteria_consensus"
1120
+ convergence_result["confidence"] = avg_custom_score
1121
+ return convergence_result
1122
+
1123
+ # Add recommendations for next iteration if not stopping
1124
+ if not convergence_result["should_stop"] and reflection:
1125
+ confidence = reflection.get("confidence_score", 0)
1126
+ if confidence < 0.6:
1127
+ convergence_result["recommendations"].append(
1128
+ "Focus on gathering more comprehensive data"
1129
+ )
1130
+ if execution_results.get("errors"):
1131
+ convergence_result["recommendations"].append(
1132
+ "Improve tool selection and error handling"
1133
+ )
1134
+ if len(execution_results.get("intermediate_results", [])) < 2:
1135
+ convergence_result["recommendations"].append(
1136
+ "Execute more analysis steps for thoroughness"
1137
+ )
1138
+
1139
+ return convergence_result
1140
+
1141
+ def _phase_synthesis(
1142
+ self,
1143
+ kwargs: Dict[str, Any],
1144
+ iterations: List[IterationState],
1145
+ global_discoveries: Dict[str, Any],
1146
+ ) -> str:
1147
+ """Phase 6: Synthesize results from all iterations into final response."""
1148
+ messages = kwargs.get("messages", [])
1149
+ user_query = ""
1150
+ for msg in reversed(messages):
1151
+ if msg.get("role") == "user":
1152
+ user_query = msg.get("content", "")
1153
+ break
1154
+
1155
+ # Collect all execution results
1156
+ all_results = []
1157
+ all_insights = []
1158
+
1159
+ for iteration in iterations:
1160
+ if iteration.success and iteration.execution_results:
1161
+ results = iteration.execution_results.get("intermediate_results", [])
1162
+ all_results.extend(results)
1163
+
1164
+ if iteration.reflection:
1165
+ goals_achieved = iteration.reflection.get("goal_progress", {}).get(
1166
+ "goals_achieved", []
1167
+ )
1168
+ all_insights.extend(goals_achieved)
1169
+
1170
+ # Create synthesized response
1171
+ synthesis = f"## Analysis Results for: {user_query}\n\n"
1172
+
1173
+ if all_results:
1174
+ synthesis += "### Key Findings:\n"
1175
+ for i, result in enumerate(all_results[:5], 1): # Limit to top 5 results
1176
+ synthesis += f"{i}. {result}\n"
1177
+ synthesis += "\n"
1178
+
1179
+ # Add iteration summary
1180
+ synthesis += "### Process Summary:\n"
1181
+ synthesis += f"- Completed {len(iterations)} iterations\n"
1182
+ synthesis += f"- Discovered {len(global_discoveries.get('tools', {}))} tools and {len(global_discoveries.get('resources', {}))} resources\n"
1183
+
1184
+ successful_iterations = sum(1 for it in iterations if it.success)
1185
+ synthesis += (
1186
+ f"- {successful_iterations}/{len(iterations)} iterations successful\n\n"
1187
+ )
1188
+
1189
+ # Add confidence and evidence
1190
+ final_confidence = 0.8 # Mock final confidence
1191
+ synthesis += f"### Confidence: {final_confidence:.1%}\n"
1192
+ synthesis += f"Based on analysis using {len(global_discoveries.get('tools', {}))} MCP tools and comprehensive iterative processing.\n\n"
1193
+
1194
+ # Add recommendations if analysis-focused
1195
+ if "analyze" in user_query.lower() or "recommend" in user_query.lower():
1196
+ synthesis += "### Recommendations:\n"
1197
+ synthesis += (
1198
+ "1. Continue monitoring key metrics identified in this analysis\n"
1199
+ )
1200
+ synthesis += "2. Consider implementing suggested improvements\n"
1201
+ synthesis += "3. Review findings with stakeholders for validation\n"
1202
+
1203
+ return synthesis
1204
+
1205
+ def _update_global_discoveries(
1206
+ self, global_discoveries: Dict[str, Any], new_discoveries: Dict[str, Any]
1207
+ ) -> None:
1208
+ """Update global discoveries with new findings."""
1209
+ # Update servers
1210
+ for server in new_discoveries.get("new_servers", []):
1211
+ global_discoveries["servers"][server["id"]] = server
1212
+
1213
+ # Update tools
1214
+ for tool in new_discoveries.get("new_tools", []):
1215
+ tool_name = tool.get("name") if isinstance(tool, dict) else str(tool)
1216
+ if isinstance(tool, dict) and "function" in tool:
1217
+ tool_name = tool["function"].get("name", tool_name)
1218
+ global_discoveries["tools"][tool_name] = tool
1219
+
1220
+ # Update resources
1221
+ for resource in new_discoveries.get("new_resources", []):
1222
+ resource_uri = resource.get("uri", str(resource))
1223
+ global_discoveries["resources"][resource_uri] = resource
1224
+
1225
+ # Update capabilities
1226
+ for capability in new_discoveries.get("tool_capabilities", []):
1227
+ cap_name = capability.get("name", "unknown")
1228
+ global_discoveries["capabilities"][cap_name] = capability
1229
+
1230
+ def _adapt_strategy(
1231
+ self,
1232
+ kwargs: Dict[str, Any],
1233
+ iteration_state: IterationState,
1234
+ previous_iterations: List[IterationState],
1235
+ ) -> None:
1236
+ """Adapt strategy for next iteration based on results."""
1237
+ # Simple adaptation logic (in real implementation, use more sophisticated ML)
1238
+ if iteration_state.reflection:
1239
+ confidence = iteration_state.reflection.get("confidence_score", 0.5)
1240
+
1241
+ # If confidence is low, suggest more thorough discovery
1242
+ if confidence < 0.6:
1243
+ kwargs["discovery_mode"] = "exhaustive"
1244
+
1245
+ # If errors occurred, reduce timeout for faster iteration
1246
+ if iteration_state.execution_results.get("errors"):
1247
+ kwargs["iteration_timeout"] = min(
1248
+ kwargs.get("iteration_timeout", 300), 180
1249
+ )
1250
+
1251
+ def _calculate_resource_usage(
1252
+ self, iterations: List[IterationState]
1253
+ ) -> Dict[str, Any]:
1254
+ """Calculate resource usage across all iterations."""
1255
+ total_duration = sum(
1256
+ (iter_state.end_time - iter_state.start_time)
1257
+ for iter_state in iterations
1258
+ if iter_state.end_time
1259
+ )
1260
+
1261
+ total_tools_used = 0
1262
+ total_api_calls = 0
1263
+
1264
+ for iteration in iterations:
1265
+ if iteration.execution_results:
1266
+ total_tools_used += len(
1267
+ iteration.execution_results.get("tool_outputs", {})
1268
+ )
1269
+ total_api_calls += len(
1270
+ iteration.execution_results.get("steps_completed", [])
1271
+ )
1272
+
1273
+ return {
1274
+ "total_duration_seconds": total_duration,
1275
+ "total_iterations": len(iterations),
1276
+ "total_tools_used": total_tools_used,
1277
+ "total_api_calls": total_api_calls,
1278
+ "average_iteration_time": total_duration / max(len(iterations), 1),
1279
+ "estimated_cost_usd": total_api_calls * 0.01, # Mock cost calculation
1280
+ }