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,1623 @@
1
+ """Self-Organizing Agent Pool nodes for autonomous team formation and problem solving.
2
+
3
+ This module implements nodes that enable agents to self-organize into teams,
4
+ collaborate dynamically, and solve problems without centralized orchestration.
5
+ """
6
+
7
+ import json
8
+ import random
9
+ import time
10
+ import uuid
11
+ from collections import defaultdict, deque
12
+ from enum import Enum
13
+ from typing import Any, Dict, List, Set
14
+
15
+ from kailash.nodes.ai.a2a import A2AAgentNode
16
+ from kailash.nodes.base import Node, NodeParameter, register_node
17
+
18
+
19
+ class TeamFormationStrategy(Enum):
20
+ """Strategies for forming agent teams."""
21
+
22
+ CAPABILITY_MATCHING = "capability_matching"
23
+ SWARM_BASED = "swarm_based"
24
+ MARKET_BASED = "market_based"
25
+ HIERARCHICAL = "hierarchical"
26
+ RANDOM = "random"
27
+
28
+
29
+ class AgentStatus(Enum):
30
+ """Status of an agent in the pool."""
31
+
32
+ AVAILABLE = "available"
33
+ BUSY = "busy"
34
+ FORMING_TEAM = "forming_team"
35
+ INACTIVE = "inactive"
36
+
37
+
38
+ @register_node()
39
+ class AgentPoolManagerNode(Node):
40
+ """
41
+ Manages a pool of self-organizing agents with capability tracking,
42
+ performance monitoring, and dynamic availability management.
43
+
44
+ This node serves as the registry and coordinator for a dynamic pool of agents,
45
+ tracking their capabilities, availability, performance metrics, and collaboration
46
+ patterns. It enables efficient agent discovery, team formation, and performance-based
47
+ agent selection for complex multi-agent workflows.
48
+
49
+ Design Philosophy:
50
+ The AgentPoolManagerNode embodies decentralized management principles, allowing
51
+ agents to join and leave dynamically while maintaining global visibility into
52
+ pool capabilities. It facilitates emergence of specialized teams based on task
53
+ requirements and historical performance, creating an adaptive workforce that
54
+ improves over time through tracked metrics and learned collaboration patterns.
55
+
56
+ Upstream Dependencies:
57
+ - OrchestrationManagerNode: Provides agent registration requests
58
+ - TeamFormationNode: Queries for available agents with specific capabilities
59
+ - A2ACoordinatorNode: Updates agent status during task execution
60
+ - Performance monitoring systems: Supply metrics updates
61
+
62
+ Downstream Consumers:
63
+ - TeamFormationNode: Uses agent registry for team composition
64
+ - ProblemAnalyzerNode: Queries capabilities for feasibility analysis
65
+ - SolutionEvaluatorNode: Accesses performance history
66
+ - Reporting systems: Aggregate pool analytics
67
+
68
+ Configuration:
69
+ No static configuration required. The pool adapts dynamically based on
70
+ registered agents and their evolving performance metrics. Default performance
71
+ thresholds can be adjusted at runtime.
72
+
73
+ Implementation Details:
74
+ - Maintains in-memory registry with O(1) agent lookup
75
+ - Indexes agents by capability for fast searching
76
+ - Tracks performance metrics with exponential moving averages
77
+ - Records collaboration history for team affinity analysis
78
+ - Implements status transitions with validation
79
+ - Supports bulk operations for efficiency
80
+ - Thread-safe for concurrent access
81
+
82
+ Error Handling:
83
+ - Validates agent IDs to prevent duplicates
84
+ - Handles missing agents gracefully in queries
85
+ - Returns empty results rather than errors for searches
86
+ - Validates status transitions
87
+
88
+ Side Effects:
89
+ - Maintains persistent agent registry across calls
90
+ - Updates performance metrics incrementally
91
+ - Records team formation history
92
+ - May affect agent availability for other tasks
93
+
94
+ Examples:
95
+ >>> # Create agent pool manager
96
+ >>> pool_manager = AgentPoolManagerNode()
97
+ >>>
98
+ >>> # Test basic structure
99
+ >>> params = pool_manager.get_parameters()
100
+ >>> assert "action" in params
101
+ >>> assert "agent_id" in params
102
+ >>>
103
+ >>> # Test simple registration
104
+ >>> result = pool_manager.run(
105
+ ... action="register",
106
+ ... agent_id="test_agent",
107
+ ... capabilities=["analysis"]
108
+ ... )
109
+ >>> assert result["success"] == True
110
+ """
111
+
112
+ def __init__(self):
113
+ super().__init__()
114
+ self.agent_registry = {}
115
+ self.availability_tracker = {}
116
+ self.performance_metrics = defaultdict(
117
+ lambda: {
118
+ "tasks_completed": 0,
119
+ "success_rate": 0.8,
120
+ "avg_contribution_score": 0.7,
121
+ "specializations": {},
122
+ "collaboration_history": [],
123
+ }
124
+ )
125
+ self.capability_index = defaultdict(set)
126
+ self.team_history = deque(maxlen=100)
127
+
128
+ def get_parameters(self) -> Dict[str, NodeParameter]:
129
+ return {
130
+ "action": NodeParameter(
131
+ name="action",
132
+ type=str,
133
+ required=False,
134
+ default="list",
135
+ description="Action: 'register', 'unregister', 'find_by_capability', 'update_status', 'get_metrics', 'list'",
136
+ ),
137
+ "agent_id": NodeParameter(
138
+ name="agent_id", type=str, required=False, description="ID of the agent"
139
+ ),
140
+ "capabilities": NodeParameter(
141
+ name="capabilities",
142
+ type=list,
143
+ required=False,
144
+ default=[],
145
+ description="List of agent capabilities",
146
+ ),
147
+ "metadata": NodeParameter(
148
+ name="metadata",
149
+ type=dict,
150
+ required=False,
151
+ default={},
152
+ description="Additional agent metadata",
153
+ ),
154
+ "required_capabilities": NodeParameter(
155
+ name="required_capabilities",
156
+ type=list,
157
+ required=False,
158
+ default=[],
159
+ description="Capabilities required for search",
160
+ ),
161
+ "min_performance": NodeParameter(
162
+ name="min_performance",
163
+ type=float,
164
+ required=False,
165
+ default=0.7,
166
+ description="Minimum performance score",
167
+ ),
168
+ "status": NodeParameter(
169
+ name="status",
170
+ type=str,
171
+ required=False,
172
+ description="New status for agent",
173
+ ),
174
+ "performance_update": NodeParameter(
175
+ name="performance_update",
176
+ type=dict,
177
+ required=False,
178
+ description="Performance metrics to update",
179
+ ),
180
+ }
181
+
182
+ def run(self, **kwargs) -> Dict[str, Any]:
183
+ """Execute pool management action."""
184
+ action = kwargs.get("action", "list")
185
+
186
+ if action == "register":
187
+ return self._register_agent(kwargs)
188
+ elif action == "unregister":
189
+ return self._unregister_agent(kwargs)
190
+ elif action == "find_by_capability":
191
+ return self._find_by_capability(kwargs)
192
+ elif action == "update_status":
193
+ return self._update_status(kwargs)
194
+ elif action == "get_metrics":
195
+ return self._get_metrics(kwargs)
196
+ elif action == "list":
197
+ return self._list_agents()
198
+ else:
199
+ return {"success": False, "error": f"Unknown action: {action}"}
200
+
201
+ def _register_agent(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
202
+ """Register a new agent in the pool."""
203
+ agent_id = kwargs.get("agent_id")
204
+ if not agent_id:
205
+ agent_id = f"agent_{uuid.uuid4().hex[:8]}"
206
+
207
+ capabilities = kwargs.get("capabilities", [])
208
+ metadata = kwargs.get("metadata", {})
209
+
210
+ # Register agent
211
+ self.agent_registry[agent_id] = {
212
+ "id": agent_id,
213
+ "capabilities": capabilities,
214
+ "metadata": metadata,
215
+ "registered_at": time.time(),
216
+ "last_active": time.time(),
217
+ }
218
+
219
+ # Update availability
220
+ self.availability_tracker[agent_id] = AgentStatus.AVAILABLE.value
221
+
222
+ # Index capabilities
223
+ for capability in capabilities:
224
+ self.capability_index[capability].add(agent_id)
225
+
226
+ # Initialize performance metrics if provided
227
+ if "performance_history" in metadata:
228
+ self.performance_metrics[agent_id].update(metadata["performance_history"])
229
+
230
+ return {
231
+ "success": True,
232
+ "agent_id": agent_id,
233
+ "status": "registered",
234
+ "capabilities": capabilities,
235
+ "pool_size": len(self.agent_registry),
236
+ }
237
+
238
+ def _unregister_agent(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
239
+ """Remove an agent from the pool."""
240
+ agent_id = kwargs.get("agent_id")
241
+
242
+ if agent_id not in self.agent_registry:
243
+ return {"success": False, "error": f"Agent {agent_id} not found"}
244
+
245
+ # Remove from indices
246
+ agent_data = self.agent_registry[agent_id]
247
+ for capability in agent_data["capabilities"]:
248
+ self.capability_index[capability].discard(agent_id)
249
+
250
+ # Remove from registry
251
+ del self.agent_registry[agent_id]
252
+ del self.availability_tracker[agent_id]
253
+
254
+ return {
255
+ "success": True,
256
+ "agent_id": agent_id,
257
+ "status": "unregistered",
258
+ "pool_size": len(self.agent_registry),
259
+ }
260
+
261
+ def _find_by_capability(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
262
+ """Find agents matching required capabilities."""
263
+ required_capabilities = set(kwargs.get("required_capabilities", []))
264
+ min_performance = kwargs.get("min_performance", 0.7)
265
+
266
+ if not required_capabilities:
267
+ return {
268
+ "success": True,
269
+ "agents": list(self.agent_registry.keys()),
270
+ "count": len(self.agent_registry),
271
+ }
272
+
273
+ # Find agents with all required capabilities
274
+ matching_agents = None
275
+ for capability in required_capabilities:
276
+ agents_with_capability = self.capability_index.get(capability, set())
277
+ if matching_agents is None:
278
+ matching_agents = agents_with_capability.copy()
279
+ else:
280
+ matching_agents &= agents_with_capability
281
+
282
+ if not matching_agents:
283
+ return {"success": True, "agents": [], "count": 0}
284
+
285
+ # Filter by performance and availability
286
+ qualified_agents = []
287
+ for agent_id in matching_agents:
288
+ if self.availability_tracker.get(agent_id) != AgentStatus.AVAILABLE.value:
289
+ continue
290
+
291
+ performance = self.performance_metrics[agent_id]["success_rate"]
292
+ if performance >= min_performance:
293
+ agent_info = self.agent_registry[agent_id].copy()
294
+ agent_info["performance"] = performance
295
+ agent_info["status"] = self.availability_tracker[agent_id]
296
+ qualified_agents.append(agent_info)
297
+
298
+ # Sort by performance
299
+ qualified_agents.sort(key=lambda x: x["performance"], reverse=True)
300
+
301
+ return {
302
+ "success": True,
303
+ "agents": qualified_agents,
304
+ "count": len(qualified_agents),
305
+ "total_pool_size": len(self.agent_registry),
306
+ }
307
+
308
+ def _update_status(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
309
+ """Update agent status."""
310
+ agent_id = kwargs.get("agent_id")
311
+ new_status = kwargs.get("status")
312
+
313
+ if agent_id not in self.agent_registry:
314
+ return {"success": False, "error": f"Agent {agent_id} not found"}
315
+
316
+ # Validate status
317
+ valid_statuses = [s.value for s in AgentStatus]
318
+ if new_status not in valid_statuses:
319
+ return {"success": False, "error": f"Invalid status: {new_status}"}
320
+
321
+ old_status = self.availability_tracker.get(agent_id)
322
+ self.availability_tracker[agent_id] = new_status
323
+ self.agent_registry[agent_id]["last_active"] = time.time()
324
+
325
+ # Update performance if provided
326
+ if "performance_update" in kwargs:
327
+ perf_update = kwargs["performance_update"]
328
+ metrics = self.performance_metrics[agent_id]
329
+
330
+ if "task_completed" in perf_update:
331
+ metrics["tasks_completed"] += 1
332
+ success = perf_update.get("success", True)
333
+ # Update success rate with exponential moving average
334
+ alpha = 0.2
335
+ metrics["success_rate"] = (
336
+ alpha * (1.0 if success else 0.0)
337
+ + (1 - alpha) * metrics["success_rate"]
338
+ )
339
+
340
+ if "contribution_score" in perf_update:
341
+ score = perf_update["contribution_score"]
342
+ metrics["avg_contribution_score"] = (
343
+ alpha * score + (1 - alpha) * metrics["avg_contribution_score"]
344
+ )
345
+
346
+ if "specialization" in perf_update:
347
+ spec = perf_update["specialization"]
348
+ if spec not in metrics["specializations"]:
349
+ metrics["specializations"][spec] = 0
350
+ metrics["specializations"][spec] += 1
351
+
352
+ return {
353
+ "success": True,
354
+ "agent_id": agent_id,
355
+ "old_status": old_status,
356
+ "new_status": new_status,
357
+ "last_active": self.agent_registry[agent_id]["last_active"],
358
+ }
359
+
360
+ def _get_metrics(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
361
+ """Get performance metrics for an agent or all agents."""
362
+ agent_id = kwargs.get("agent_id")
363
+
364
+ if agent_id:
365
+ if agent_id not in self.agent_registry:
366
+ return {"success": False, "error": f"Agent {agent_id} not found"}
367
+
368
+ return {
369
+ "success": True,
370
+ "agent_id": agent_id,
371
+ "metrics": self.performance_metrics[agent_id],
372
+ "registration_info": self.agent_registry[agent_id],
373
+ }
374
+ else:
375
+ # Aggregate metrics
376
+ total_agents = len(self.agent_registry)
377
+ available_agents = sum(
378
+ 1
379
+ for status in self.availability_tracker.values()
380
+ if status == AgentStatus.AVAILABLE.value
381
+ )
382
+
383
+ avg_success_rate = sum(
384
+ m["success_rate"] for m in self.performance_metrics.values()
385
+ ) / max(total_agents, 1)
386
+
387
+ capability_distribution = {}
388
+ for capability, agents in self.capability_index.items():
389
+ capability_distribution[capability] = len(agents)
390
+
391
+ return {
392
+ "success": True,
393
+ "pool_metrics": {
394
+ "total_agents": total_agents,
395
+ "available_agents": available_agents,
396
+ "avg_success_rate": avg_success_rate,
397
+ "capability_distribution": capability_distribution,
398
+ "status_distribution": dict(
399
+ (
400
+ status,
401
+ sum(
402
+ 1
403
+ for s in self.availability_tracker.values()
404
+ if s == status
405
+ ),
406
+ )
407
+ for status in [s.value for s in AgentStatus]
408
+ ),
409
+ },
410
+ }
411
+
412
+ def _list_agents(self) -> Dict[str, Any]:
413
+ """List all agents in the pool."""
414
+ agents = []
415
+ for agent_id, agent_data in self.agent_registry.items():
416
+ agent_info = agent_data.copy()
417
+ agent_info["status"] = self.availability_tracker.get(agent_id)
418
+ agent_info["performance"] = self.performance_metrics[agent_id][
419
+ "success_rate"
420
+ ]
421
+ agents.append(agent_info)
422
+
423
+ return {"success": True, "agents": agents, "count": len(agents)}
424
+
425
+
426
+ @register_node()
427
+ class ProblemAnalyzerNode(Node):
428
+ """
429
+ Analyzes problems to determine required capabilities, complexity,
430
+ and optimal team composition.
431
+
432
+ This node performs intelligent problem decomposition and requirement analysis,
433
+ identifying the specific capabilities needed to solve a problem, estimating its
434
+ complexity, and suggesting optimal team configurations. It uses pattern matching,
435
+ keyword analysis, and domain heuristics to create actionable problem specifications.
436
+
437
+ Design Philosophy:
438
+ The ProblemAnalyzerNode acts as the strategic planner in self-organizing systems,
439
+ translating high-level problem descriptions into concrete capability requirements
440
+ and team specifications. It embodies the principle that effective problem solving
441
+ begins with thorough understanding and proper decomposition of the challenge.
442
+
443
+ Upstream Dependencies:
444
+ - User interfaces or APIs providing problem descriptions
445
+ - OrchestrationManagerNode: Supplies problem context
446
+ - Domain configuration systems: Provide capability mappings
447
+
448
+ Downstream Consumers:
449
+ - TeamFormationNode: Uses capability requirements for team assembly
450
+ - AgentPoolManagerNode: Queries based on required capabilities
451
+ - SolutionEvaluatorNode: References problem analysis for validation
452
+ - Resource planning systems: Use complexity estimates
453
+
454
+ Configuration:
455
+ The analyzer uses predefined capability patterns that can be extended
456
+ for domain-specific problems. Analysis depth and decomposition strategies
457
+ can be configured at runtime.
458
+
459
+ Implementation Details:
460
+ - Pattern-based capability extraction from problem text
461
+ - Keyword-driven complexity scoring
462
+ - Multi-factor team size estimation
463
+ - Hierarchical problem decomposition
464
+ - Context-aware requirement adjustment
465
+ - Priority-based capability ranking
466
+ - Time and resource estimation
467
+
468
+ Error Handling:
469
+ - Handles vague problem descriptions with default analysis
470
+ - Validates decomposition strategies
471
+ - Returns minimal requirements for unrecognized problems
472
+ - Never fails - always provides best-effort analysis
473
+
474
+ Side Effects:
475
+ - No persistent side effects
476
+ - Pure analysis function
477
+ - May influence downstream team formation
478
+
479
+ Examples:
480
+ >>> # Test parameter structure without constructor validation
481
+ >>> analyzer = ProblemAnalyzerNode.__new__(ProblemAnalyzerNode)
482
+ >>> params = analyzer.get_parameters()
483
+ >>> assert "problem_description" in params
484
+ >>> assert "decomposition_strategy" in params
485
+ """
486
+
487
+ def __init__(self):
488
+ super().__init__()
489
+ self.capability_patterns = {
490
+ "data": ["data_collection", "data_cleaning", "data_validation"],
491
+ "analysis": [
492
+ "statistical_analysis",
493
+ "data_analysis",
494
+ "pattern_recognition",
495
+ ],
496
+ "model": ["machine_learning", "predictive_modeling", "model_validation"],
497
+ "research": ["literature_review", "research", "synthesis"],
498
+ "visualization": ["data_visualization", "reporting", "presentation"],
499
+ "domain": ["domain_expertise", "validation", "interpretation"],
500
+ }
501
+
502
+ def get_parameters(self) -> Dict[str, NodeParameter]:
503
+ return {
504
+ "problem_description": NodeParameter(
505
+ name="problem_description",
506
+ type=str,
507
+ required=True,
508
+ description="Description of the problem to solve",
509
+ ),
510
+ "context": NodeParameter(
511
+ name="context",
512
+ type=dict,
513
+ required=False,
514
+ default={},
515
+ description="Additional context about the problem",
516
+ ),
517
+ "decomposition_strategy": NodeParameter(
518
+ name="decomposition_strategy",
519
+ type=str,
520
+ required=False,
521
+ default="hierarchical",
522
+ description="Strategy for decomposing the problem",
523
+ ),
524
+ "analysis_depth": NodeParameter(
525
+ name="analysis_depth",
526
+ type=str,
527
+ required=False,
528
+ default="standard",
529
+ description="Depth of analysis: 'quick', 'standard', 'comprehensive'",
530
+ ),
531
+ }
532
+
533
+ def run(self, **kwargs) -> Dict[str, Any]:
534
+ """Analyze the problem to determine requirements."""
535
+ problem_description = kwargs["problem_description"]
536
+ context = kwargs.get("context", {})
537
+ strategy = kwargs.get("decomposition_strategy", "hierarchical")
538
+ kwargs.get("analysis_depth", "standard")
539
+
540
+ # Extract key terms and requirements
541
+ problem_lower = problem_description.lower()
542
+ required_capabilities = set()
543
+
544
+ # Pattern matching for capabilities
545
+ for pattern, caps in self.capability_patterns.items():
546
+ if pattern in problem_lower:
547
+ required_capabilities.update(caps)
548
+
549
+ # Add specific capabilities based on keywords
550
+ keyword_capabilities = {
551
+ "predict": ["predictive_modeling", "machine_learning"],
552
+ "forecast": ["time_series_analysis", "predictive_modeling"],
553
+ "analyze": ["data_analysis", "statistical_analysis"],
554
+ "visualize": ["data_visualization", "reporting"],
555
+ "research": ["research", "literature_review"],
556
+ "optimize": ["optimization", "algorithm_design"],
557
+ "classify": ["classification", "machine_learning"],
558
+ "cluster": ["clustering", "unsupervised_learning"],
559
+ }
560
+
561
+ for keyword, caps in keyword_capabilities.items():
562
+ if keyword in problem_lower:
563
+ required_capabilities.update(caps)
564
+
565
+ # Determine complexity
566
+ complexity_factors = {
567
+ "simple_keywords": ["basic", "simple", "straightforward"],
568
+ "complex_keywords": ["complex", "advanced", "sophisticated", "multi"],
569
+ "scale_keywords": ["large", "massive", "big data", "scalable"],
570
+ }
571
+
572
+ complexity_score = 0.5 # Base complexity
573
+
574
+ for keyword in complexity_factors["simple_keywords"]:
575
+ if keyword in problem_lower:
576
+ complexity_score -= 0.1
577
+
578
+ for keyword in complexity_factors["complex_keywords"]:
579
+ if keyword in problem_lower:
580
+ complexity_score += 0.2
581
+
582
+ for keyword in complexity_factors["scale_keywords"]:
583
+ if keyword in problem_lower:
584
+ complexity_score += 0.1
585
+
586
+ complexity_score = max(0.1, min(1.0, complexity_score))
587
+
588
+ # Estimate team size based on complexity and capabilities
589
+ base_team_size = len(required_capabilities) // 3 + 1
590
+ complexity_multiplier = 1 + complexity_score
591
+ estimated_agents = int(base_team_size * complexity_multiplier)
592
+
593
+ # Decompose problem
594
+ if strategy == "hierarchical":
595
+ decomposition = self._hierarchical_decomposition(
596
+ problem_description, required_capabilities
597
+ )
598
+ else:
599
+ decomposition = self._simple_decomposition(
600
+ problem_description, required_capabilities
601
+ )
602
+
603
+ # Determine quality threshold based on context
604
+ quality_threshold = 0.8 # Default
605
+ if context.get("urgency") == "high":
606
+ quality_threshold = 0.7
607
+ elif context.get("criticality") == "high":
608
+ quality_threshold = 0.9
609
+
610
+ return {
611
+ "success": True,
612
+ "analysis": {
613
+ "problem": problem_description,
614
+ "required_capabilities": list(required_capabilities),
615
+ "complexity_score": complexity_score,
616
+ "estimated_agents": estimated_agents,
617
+ "quality_threshold": quality_threshold,
618
+ "time_estimate": self._estimate_time(
619
+ complexity_score, estimated_agents
620
+ ),
621
+ "decomposition": decomposition,
622
+ "priority_capabilities": self._prioritize_capabilities(
623
+ required_capabilities, problem_description
624
+ ),
625
+ "context_factors": context,
626
+ },
627
+ }
628
+
629
+ def _hierarchical_decomposition(
630
+ self, problem: str, capabilities: Set[str]
631
+ ) -> List[Dict]:
632
+ """Decompose problem hierarchically."""
633
+ # Simple heuristic decomposition
634
+ phases = []
635
+
636
+ # Phase 1: Data/Research
637
+ if any(cap in capabilities for cap in ["data_collection", "research"]):
638
+ phases.append(
639
+ {
640
+ "phase": "data_gathering",
641
+ "subtasks": [
642
+ {"name": "collect_data", "capabilities": ["data_collection"]},
643
+ {"name": "validate_data", "capabilities": ["data_validation"]},
644
+ ],
645
+ "priority": 1,
646
+ }
647
+ )
648
+
649
+ # Phase 2: Analysis
650
+ if any(
651
+ cap in capabilities for cap in ["data_analysis", "statistical_analysis"]
652
+ ):
653
+ phases.append(
654
+ {
655
+ "phase": "analysis",
656
+ "subtasks": [
657
+ {
658
+ "name": "exploratory_analysis",
659
+ "capabilities": ["data_analysis"],
660
+ },
661
+ {
662
+ "name": "statistical_testing",
663
+ "capabilities": ["statistical_analysis"],
664
+ },
665
+ ],
666
+ "priority": 2,
667
+ }
668
+ )
669
+
670
+ # Phase 3: Modeling
671
+ if any(
672
+ cap in capabilities for cap in ["machine_learning", "predictive_modeling"]
673
+ ):
674
+ phases.append(
675
+ {
676
+ "phase": "modeling",
677
+ "subtasks": [
678
+ {
679
+ "name": "model_development",
680
+ "capabilities": ["machine_learning"],
681
+ },
682
+ {
683
+ "name": "model_validation",
684
+ "capabilities": ["model_validation"],
685
+ },
686
+ ],
687
+ "priority": 3,
688
+ }
689
+ )
690
+
691
+ # Phase 4: Reporting
692
+ phases.append(
693
+ {
694
+ "phase": "reporting",
695
+ "subtasks": [
696
+ {
697
+ "name": "create_visualizations",
698
+ "capabilities": ["data_visualization"],
699
+ },
700
+ {
701
+ "name": "write_report",
702
+ "capabilities": ["reporting", "synthesis"],
703
+ },
704
+ ],
705
+ "priority": 4,
706
+ }
707
+ )
708
+
709
+ return phases
710
+
711
+ def _simple_decomposition(self, problem: str, capabilities: Set[str]) -> List[Dict]:
712
+ """Simple task decomposition."""
713
+ tasks = []
714
+ for i, cap in enumerate(capabilities):
715
+ tasks.append(
716
+ {
717
+ "task_id": f"task_{i+1}",
718
+ "description": f"Apply {cap} to problem",
719
+ "required_capability": cap,
720
+ "estimated_duration": 30, # minutes
721
+ }
722
+ )
723
+ return tasks
724
+
725
+ def _estimate_time(self, complexity: float, agents: int) -> int:
726
+ """Estimate time in minutes."""
727
+ base_time = 60 # Base 1 hour
728
+ complexity_factor = 1 + complexity * 2 # Up to 3x for complex
729
+ parallelization_factor = 1 / (1 + agents * 0.1) # Diminishing returns
730
+
731
+ return int(base_time * complexity_factor * parallelization_factor)
732
+
733
+ def _prioritize_capabilities(
734
+ self, capabilities: Set[str], problem: str
735
+ ) -> List[str]:
736
+ """Prioritize capabilities based on problem."""
737
+ # Simple prioritization based on problem keywords
738
+ priority_map = {
739
+ "urgent": ["data_analysis", "reporting"],
740
+ "predict": ["machine_learning", "predictive_modeling"],
741
+ "research": ["research", "literature_review"],
742
+ "optimize": ["optimization", "algorithm_design"],
743
+ }
744
+
745
+ prioritized = []
746
+ problem_lower = problem.lower()
747
+
748
+ for keyword, priority_caps in priority_map.items():
749
+ if keyword in problem_lower:
750
+ for cap in priority_caps:
751
+ if cap in capabilities and cap not in prioritized:
752
+ prioritized.append(cap)
753
+
754
+ # Add remaining capabilities
755
+ for cap in capabilities:
756
+ if cap not in prioritized:
757
+ prioritized.append(cap)
758
+
759
+ return prioritized
760
+
761
+
762
+ @register_node()
763
+ class TeamFormationNode(Node):
764
+ """
765
+ Forms optimal teams based on problem requirements and agent capabilities.
766
+
767
+ Supports multiple formation strategies including capability matching,
768
+ swarm-based organization, market-based auctions, and hierarchical structures.
769
+
770
+ Examples:
771
+ >>> formation_engine = TeamFormationNode()
772
+ >>>
773
+ >>> result = formation_engine.run(
774
+ ... problem_analysis={
775
+ ... "required_capabilities": ["data_analysis", "machine_learning"],
776
+ ... "complexity_score": 0.8,
777
+ ... "estimated_agents": 4
778
+ ... },
779
+ ... available_agents=[
780
+ ... {"id": "agent1", "capabilities": ["data_analysis"], "performance": 0.9},
781
+ ... {"id": "agent2", "capabilities": ["machine_learning"], "performance": 0.85}
782
+ ... ],
783
+ ... formation_strategy="capability_matching"
784
+ ... )
785
+ """
786
+
787
+ def __init__(self):
788
+ super().__init__()
789
+ self.formation_history = deque(maxlen=50)
790
+ self.team_performance_cache = {}
791
+
792
+ def get_parameters(self) -> Dict[str, NodeParameter]:
793
+ return {
794
+ "problem_analysis": NodeParameter(
795
+ name="problem_analysis",
796
+ type=dict,
797
+ required=False,
798
+ default={},
799
+ description="Analysis of the problem from ProblemAnalyzerNode",
800
+ ),
801
+ "available_agents": NodeParameter(
802
+ name="available_agents",
803
+ type=list,
804
+ required=False,
805
+ default=[],
806
+ description="List of available agents with their capabilities",
807
+ ),
808
+ "formation_strategy": NodeParameter(
809
+ name="formation_strategy",
810
+ type=str,
811
+ required=False,
812
+ default="capability_matching",
813
+ description="Team formation strategy",
814
+ ),
815
+ "constraints": NodeParameter(
816
+ name="constraints",
817
+ type=dict,
818
+ required=False,
819
+ default={},
820
+ description="Constraints for team formation",
821
+ ),
822
+ "optimization_rounds": NodeParameter(
823
+ name="optimization_rounds",
824
+ type=int,
825
+ required=False,
826
+ default=3,
827
+ description="Number of optimization iterations",
828
+ ),
829
+ "diversity_weight": NodeParameter(
830
+ name="diversity_weight",
831
+ type=float,
832
+ required=False,
833
+ default=0.2,
834
+ description="Weight for team diversity (0-1)",
835
+ ),
836
+ }
837
+
838
+ def run(self, **kwargs) -> Dict[str, Any]:
839
+ """Form an optimal team."""
840
+ problem_analysis = kwargs.get("problem_analysis", {})
841
+ available_agents = kwargs.get("available_agents", [])
842
+ strategy = kwargs.get("formation_strategy", "capability_matching")
843
+ constraints = kwargs.get("constraints", {})
844
+ optimization_rounds = kwargs.get("optimization_rounds", 3)
845
+
846
+ if not available_agents:
847
+ return {"success": False, "error": "No available agents", "team": []}
848
+
849
+ # Form initial team based on strategy
850
+ if strategy == TeamFormationStrategy.CAPABILITY_MATCHING.value:
851
+ team = self._capability_matching_formation(
852
+ problem_analysis, available_agents, constraints
853
+ )
854
+ elif strategy == TeamFormationStrategy.SWARM_BASED.value:
855
+ team = self._swarm_based_formation(
856
+ problem_analysis, available_agents, constraints
857
+ )
858
+ elif strategy == TeamFormationStrategy.MARKET_BASED.value:
859
+ team = self._market_based_formation(
860
+ problem_analysis, available_agents, constraints
861
+ )
862
+ elif strategy == TeamFormationStrategy.HIERARCHICAL.value:
863
+ team = self._hierarchical_formation(
864
+ problem_analysis, available_agents, constraints
865
+ )
866
+ else:
867
+ team = self._random_formation(
868
+ problem_analysis, available_agents, constraints
869
+ )
870
+
871
+ # Optimize team composition
872
+ for round in range(optimization_rounds):
873
+ optimization_result = self._optimize_team(
874
+ team, problem_analysis, available_agents
875
+ )
876
+ if optimization_result["improved"]:
877
+ team = optimization_result["team"]
878
+ else:
879
+ break
880
+
881
+ # Calculate team metrics
882
+ team_metrics = self._calculate_team_metrics(team, problem_analysis)
883
+
884
+ # Record formation
885
+ self.formation_history.append(
886
+ {
887
+ "timestamp": time.time(),
888
+ "problem": problem_analysis.get("problem", "unknown"),
889
+ "strategy": strategy,
890
+ "team_size": len(team),
891
+ "fitness_score": team_metrics["fitness_score"],
892
+ }
893
+ )
894
+
895
+ return {
896
+ "success": True,
897
+ "team": team,
898
+ "team_metrics": team_metrics,
899
+ "formation_strategy": strategy,
900
+ "optimization_rounds_used": round + 1,
901
+ }
902
+
903
+ def _capability_matching_formation(
904
+ self, problem: Dict, agents: List[Dict], constraints: Dict
905
+ ) -> List[Dict]:
906
+ """Form team by matching capabilities to requirements."""
907
+ required_capabilities = set(problem.get("required_capabilities", []))
908
+ selected_agents = []
909
+ covered_capabilities = set()
910
+
911
+ # Sort agents by performance and capability coverage
912
+ agent_scores = []
913
+ for agent in agents:
914
+ agent_caps = set(agent.get("capabilities", []))
915
+ overlap = agent_caps & required_capabilities
916
+ uncovered = overlap - covered_capabilities
917
+
918
+ score = (
919
+ len(uncovered) * 2 # Prioritize new capabilities
920
+ + len(overlap) # Total relevant capabilities
921
+ + agent.get("performance", 0.8) * 2 # Performance weight
922
+ )
923
+
924
+ agent_scores.append((score, agent))
925
+
926
+ # Sort by score
927
+ agent_scores.sort(key=lambda x: x[0], reverse=True)
928
+
929
+ # Select agents
930
+ max_team_size = constraints.get("max_team_size", 10)
931
+ min_team_size = constraints.get("min_team_size", 2)
932
+
933
+ for score, agent in agent_scores:
934
+ if len(selected_agents) >= max_team_size:
935
+ break
936
+
937
+ agent_caps = set(agent.get("capabilities", []))
938
+ if agent_caps & required_capabilities: # Has relevant capabilities
939
+ selected_agents.append(agent)
940
+ covered_capabilities.update(agent_caps)
941
+
942
+ # Check if all required capabilities are covered
943
+ if covered_capabilities >= required_capabilities:
944
+ if len(selected_agents) >= min_team_size:
945
+ break
946
+
947
+ return selected_agents
948
+
949
+ def _swarm_based_formation(
950
+ self, problem: Dict, agents: List[Dict], constraints: Dict
951
+ ) -> List[Dict]:
952
+ """Form team using swarm intelligence principles."""
953
+ required_capabilities = set(problem.get("required_capabilities", []))
954
+ problem.get("complexity_score", 0.5)
955
+
956
+ # Calculate attraction scores between agents
957
+ attraction_matrix = {}
958
+ for i, agent1 in enumerate(agents):
959
+ for j, agent2 in enumerate(agents):
960
+ if i != j:
961
+ # Attraction based on complementary capabilities
962
+ caps1 = set(agent1.get("capabilities", []))
963
+ caps2 = set(agent2.get("capabilities", []))
964
+
965
+ complementarity = len((caps1 | caps2) & required_capabilities)
966
+ overlap = len(caps1 & caps2)
967
+
968
+ # High complementarity, low overlap is good
969
+ attraction = (
970
+ complementarity / max(len(required_capabilities), 1)
971
+ - overlap * 0.1
972
+ )
973
+ attraction_matrix[(i, j)] = max(0, attraction)
974
+
975
+ # Form clusters using attraction
976
+ clusters = []
977
+ unassigned = set(range(len(agents)))
978
+
979
+ while unassigned and len(clusters) < 5: # Max 5 clusters
980
+ # Start new cluster with highest performance unassigned agent
981
+ seed_idx = max(unassigned, key=lambda i: agents[i].get("performance", 0.8))
982
+ cluster = [seed_idx]
983
+ unassigned.remove(seed_idx)
984
+
985
+ # Grow cluster based on attraction
986
+ while len(cluster) < len(agents) // 3: # Max cluster size
987
+ best_candidate = None
988
+ best_attraction = 0
989
+
990
+ for candidate in unassigned:
991
+ # Average attraction to cluster members
992
+ avg_attraction = sum(
993
+ attraction_matrix.get((member, candidate), 0)
994
+ for member in cluster
995
+ ) / len(cluster)
996
+
997
+ if avg_attraction > best_attraction:
998
+ best_attraction = avg_attraction
999
+ best_candidate = candidate
1000
+
1001
+ if best_candidate and best_attraction > 0.3:
1002
+ cluster.append(best_candidate)
1003
+ unassigned.remove(best_candidate)
1004
+ else:
1005
+ break
1006
+
1007
+ clusters.append(cluster)
1008
+
1009
+ # Select best cluster based on capability coverage
1010
+ best_cluster = []
1011
+ best_coverage = 0
1012
+
1013
+ for cluster in clusters:
1014
+ cluster_agents = [agents[i] for i in cluster]
1015
+ cluster_caps = set()
1016
+ for agent in cluster_agents:
1017
+ cluster_caps.update(agent.get("capabilities", []))
1018
+
1019
+ coverage = len(cluster_caps & required_capabilities)
1020
+ if coverage > best_coverage:
1021
+ best_coverage = coverage
1022
+ best_cluster = cluster_agents
1023
+
1024
+ return best_cluster
1025
+
1026
+ def _market_based_formation(
1027
+ self, problem: Dict, agents: List[Dict], constraints: Dict
1028
+ ) -> List[Dict]:
1029
+ """Form team using market-based auction mechanism."""
1030
+ required_capabilities = problem.get("required_capabilities", [])
1031
+ budget = constraints.get("budget", 100)
1032
+
1033
+ # Agents bid for participation
1034
+ bids = []
1035
+ for agent in agents:
1036
+ agent_caps = set(agent.get("capabilities", []))
1037
+ relevant_caps = agent_caps & set(required_capabilities)
1038
+
1039
+ if relevant_caps:
1040
+ # Calculate bid based on capability match and performance
1041
+ capability_value = len(relevant_caps) / max(
1042
+ len(required_capabilities), 1
1043
+ )
1044
+ performance = agent.get("performance", 0.8)
1045
+
1046
+ # Lower bid = higher chance of selection (inverse auction)
1047
+ bid_amount = (2 - capability_value - performance) * 10
1048
+
1049
+ bids.append(
1050
+ {
1051
+ "agent": agent,
1052
+ "bid": bid_amount,
1053
+ "value": capability_value * performance,
1054
+ }
1055
+ )
1056
+
1057
+ # Sort by value/cost ratio
1058
+ bids.sort(key=lambda x: x["value"] / x["bid"], reverse=True)
1059
+
1060
+ # Select agents within budget
1061
+ selected_agents = []
1062
+ total_cost = 0
1063
+
1064
+ for bid in bids:
1065
+ if total_cost + bid["bid"] <= budget:
1066
+ selected_agents.append(bid["agent"])
1067
+ total_cost += bid["bid"]
1068
+
1069
+ return selected_agents
1070
+
1071
+ def _hierarchical_formation(
1072
+ self, problem: Dict, agents: List[Dict], constraints: Dict
1073
+ ) -> List[Dict]:
1074
+ """Form team with hierarchical structure."""
1075
+ required_capabilities = problem.get("required_capabilities", [])
1076
+
1077
+ # Identify potential leaders (high performance, multiple capabilities)
1078
+ leader_candidates = []
1079
+ for agent in agents:
1080
+ caps = agent.get("capabilities", [])
1081
+ if len(caps) >= 3 and agent.get("performance", 0) > 0.85:
1082
+ leader_candidates.append(agent)
1083
+
1084
+ # Select leader
1085
+ if leader_candidates:
1086
+ leader = max(leader_candidates, key=lambda a: a.get("performance", 0))
1087
+ else:
1088
+ leader = max(agents, key=lambda a: a.get("performance", 0))
1089
+
1090
+ team = [leader]
1091
+ remaining_agents = [a for a in agents if a != leader]
1092
+
1093
+ # Leader selects team members based on complementary skills
1094
+ leader_caps = set(leader.get("capabilities", []))
1095
+ needed_caps = set(required_capabilities) - leader_caps
1096
+
1097
+ for cap in needed_caps:
1098
+ # Find best agent for each needed capability
1099
+ candidates = [
1100
+ a for a in remaining_agents if cap in a.get("capabilities", [])
1101
+ ]
1102
+
1103
+ if candidates:
1104
+ best = max(candidates, key=lambda a: a.get("performance", 0))
1105
+ team.append(best)
1106
+ remaining_agents.remove(best)
1107
+
1108
+ return team
1109
+
1110
+ def _random_formation(
1111
+ self, problem: Dict, agents: List[Dict], constraints: Dict
1112
+ ) -> List[Dict]:
1113
+ """Random team formation for baseline comparison."""
1114
+ team_size = min(
1115
+ problem.get("estimated_agents", 5),
1116
+ len(agents),
1117
+ constraints.get("max_team_size", 10),
1118
+ )
1119
+
1120
+ return random.sample(agents, team_size)
1121
+
1122
+ def _optimize_team(
1123
+ self, team: List[Dict], problem: Dict, all_agents: List[Dict]
1124
+ ) -> Dict[str, Any]:
1125
+ """Optimize team composition."""
1126
+ current_fitness = self._calculate_team_fitness(team, problem)
1127
+
1128
+ # Try swapping team members
1129
+ non_team_agents = [a for a in all_agents if a not in team]
1130
+
1131
+ if not non_team_agents:
1132
+ return {"improved": False, "team": team}
1133
+
1134
+ best_team = team.copy()
1135
+ best_fitness = current_fitness
1136
+
1137
+ for i, member in enumerate(team):
1138
+ for candidate in non_team_agents:
1139
+ # Try swapping
1140
+ new_team = team.copy()
1141
+ new_team[i] = candidate
1142
+
1143
+ new_fitness = self._calculate_team_fitness(new_team, problem)
1144
+
1145
+ if new_fitness > best_fitness:
1146
+ best_fitness = new_fitness
1147
+ best_team = new_team.copy()
1148
+
1149
+ improved = best_fitness > current_fitness
1150
+
1151
+ return {
1152
+ "improved": improved,
1153
+ "team": best_team,
1154
+ "fitness_improvement": best_fitness - current_fitness,
1155
+ }
1156
+
1157
+ def _calculate_team_fitness(self, team: List[Dict], problem: Dict) -> float:
1158
+ """Calculate how well a team matches problem requirements."""
1159
+ required_capabilities = set(problem.get("required_capabilities", []))
1160
+
1161
+ # Capability coverage
1162
+ team_capabilities = set()
1163
+ for agent in team:
1164
+ team_capabilities.update(agent.get("capabilities", []))
1165
+
1166
+ coverage = len(team_capabilities & required_capabilities) / max(
1167
+ len(required_capabilities), 1
1168
+ )
1169
+
1170
+ # Average performance
1171
+ avg_performance = sum(a.get("performance", 0.8) for a in team) / max(
1172
+ len(team), 1
1173
+ )
1174
+
1175
+ # Team size efficiency
1176
+ target_size = problem.get("estimated_agents", 5)
1177
+ size_penalty = abs(len(team) - target_size) / max(target_size, 1) * 0.2
1178
+
1179
+ # Diversity bonus
1180
+ unique_capabilities = len(team_capabilities)
1181
+ diversity_bonus = min(unique_capabilities / max(len(team) * 3, 1), 0.2)
1182
+
1183
+ fitness = (
1184
+ coverage * 0.5 + avg_performance * 0.3 + diversity_bonus - size_penalty
1185
+ )
1186
+
1187
+ return max(0, min(1, fitness))
1188
+
1189
+ def _calculate_team_metrics(
1190
+ self, team: List[Dict], problem: Dict
1191
+ ) -> Dict[str, Any]:
1192
+ """Calculate comprehensive team metrics."""
1193
+ required_capabilities = set(problem.get("required_capabilities", []))
1194
+ team_capabilities = set()
1195
+
1196
+ for agent in team:
1197
+ team_capabilities.update(agent.get("capabilities", []))
1198
+
1199
+ return {
1200
+ "team_size": len(team),
1201
+ "capability_coverage": len(team_capabilities & required_capabilities)
1202
+ / max(len(required_capabilities), 1),
1203
+ "total_capabilities": len(team_capabilities),
1204
+ "avg_performance": sum(a.get("performance", 0.8) for a in team)
1205
+ / max(len(team), 1),
1206
+ "fitness_score": self._calculate_team_fitness(team, problem),
1207
+ "missing_capabilities": list(required_capabilities - team_capabilities),
1208
+ "redundant_capabilities": list(team_capabilities - required_capabilities),
1209
+ }
1210
+
1211
+
1212
+ @register_node()
1213
+ class SelfOrganizingAgentNode(A2AAgentNode):
1214
+ """
1215
+ Self-organizing agent that can autonomously join teams, collaborate,
1216
+ and adapt its behavior based on team dynamics.
1217
+
1218
+ Examples:
1219
+ >>> # Create self-organizing agent
1220
+ >>> agent = SelfOrganizingAgentNode()
1221
+ >>>
1222
+ >>> # Test basic structure
1223
+ >>> params = agent.get_parameters()
1224
+ >>> assert "agent_id" in params
1225
+ >>> assert "capabilities" in params
1226
+ """
1227
+
1228
+ def __init__(self):
1229
+ super().__init__()
1230
+ self.team_memberships = {}
1231
+ self.collaboration_history = deque(maxlen=50)
1232
+ self.skill_adaptations = defaultdict(float)
1233
+
1234
+ def get_parameters(self) -> Dict[str, NodeParameter]:
1235
+ params = super().get_parameters()
1236
+
1237
+ # Add self-organization specific parameters
1238
+ params.update(
1239
+ {
1240
+ "capabilities": NodeParameter(
1241
+ name="capabilities",
1242
+ type=list,
1243
+ required=False,
1244
+ default=[],
1245
+ description="Agent's capabilities",
1246
+ ),
1247
+ "team_context": NodeParameter(
1248
+ name="team_context",
1249
+ type=dict,
1250
+ required=False,
1251
+ default={},
1252
+ description="Current team information",
1253
+ ),
1254
+ "collaboration_mode": NodeParameter(
1255
+ name="collaboration_mode",
1256
+ type=str,
1257
+ required=False,
1258
+ default="cooperative",
1259
+ description="Mode: 'cooperative', 'competitive', 'mixed'",
1260
+ ),
1261
+ "adaptation_rate": NodeParameter(
1262
+ name="adaptation_rate",
1263
+ type=float,
1264
+ required=False,
1265
+ default=0.1,
1266
+ description="How quickly agent adapts behavior (0-1)",
1267
+ ),
1268
+ "task": NodeParameter(
1269
+ name="task",
1270
+ type=str,
1271
+ required=False,
1272
+ description="Specific task for the agent",
1273
+ ),
1274
+ "autonomy_level": NodeParameter(
1275
+ name="autonomy_level",
1276
+ type=float,
1277
+ required=False,
1278
+ default=0.8,
1279
+ description="Level of autonomous decision making (0-1)",
1280
+ ),
1281
+ }
1282
+ )
1283
+
1284
+ return params
1285
+
1286
+ def run(self, **kwargs) -> Dict[str, Any]:
1287
+ """Execute self-organizing agent behavior."""
1288
+ agent_id = kwargs.get("agent_id")
1289
+ capabilities = kwargs.get("capabilities", [])
1290
+ team_context = kwargs.get("team_context", {})
1291
+ collaboration_mode = kwargs.get("collaboration_mode", "cooperative")
1292
+ task = kwargs.get("task", "")
1293
+
1294
+ # Adapt behavior based on team context
1295
+ if team_context:
1296
+ self._adapt_to_team(agent_id, team_context, collaboration_mode)
1297
+
1298
+ # Enhance task with self-organization context
1299
+ if task:
1300
+ enhanced_task = self._enhance_task_with_context(
1301
+ task, team_context, capabilities
1302
+ )
1303
+ kwargs["messages"] = kwargs.get("messages", [])
1304
+ kwargs["messages"].append({"role": "user", "content": enhanced_task})
1305
+
1306
+ # Add self-organization instructions to system prompt
1307
+ so_prompt = f"""You are a self-organizing agent with capabilities: {', '.join(capabilities)}.
1308
+
1309
+ Current team context: {json.dumps(team_context, indent=2)}
1310
+ Collaboration mode: {collaboration_mode}
1311
+
1312
+ Guidelines:
1313
+ 1. Leverage your specific capabilities to contribute to the team goal
1314
+ 2. Coordinate with other team members when mentioned
1315
+ 3. Adapt your approach based on team dynamics
1316
+ 4. Share insights that others can build upon
1317
+ 5. Be proactive in identifying how you can help
1318
+
1319
+ {kwargs.get('system_prompt', '')}"""
1320
+
1321
+ kwargs["system_prompt"] = so_prompt
1322
+
1323
+ # Execute base A2A agent
1324
+ result = super().run(**kwargs)
1325
+
1326
+ # Track collaboration
1327
+ if result.get("success"):
1328
+ self._track_collaboration(agent_id, team_context, task, result)
1329
+
1330
+ # Add self-organization metadata
1331
+ result["self_organization"] = {
1332
+ "agent_id": agent_id,
1333
+ "capabilities": capabilities,
1334
+ "team_memberships": list(self.team_memberships.keys()),
1335
+ "adaptations": dict(self.skill_adaptations),
1336
+ "collaboration_mode": collaboration_mode,
1337
+ "task": task,
1338
+ }
1339
+
1340
+ return result
1341
+
1342
+ def _adapt_to_team(self, agent_id: str, team_context: Dict, mode: str):
1343
+ """Adapt behavior to team dynamics."""
1344
+ team_id = team_context.get("team_id")
1345
+ if not team_id:
1346
+ return
1347
+
1348
+ # Track team membership
1349
+ if team_id not in self.team_memberships:
1350
+ self.team_memberships[team_id] = {
1351
+ "joined_at": time.time(),
1352
+ "contributions": 0,
1353
+ "role": "member",
1354
+ }
1355
+
1356
+ # Adapt based on other members' capabilities
1357
+ other_members = team_context.get("other_members", [])
1358
+ if other_members:
1359
+ # In cooperative mode, focus on complementary skills
1360
+ if mode == "cooperative":
1361
+ # Increase weight on unique capabilities
1362
+ for cap in self.skill_adaptations:
1363
+ self.skill_adaptations[cap] *= 0.9 # Decay
1364
+
1365
+ # In competitive mode, enhance overlapping skills
1366
+ elif mode == "competitive":
1367
+ for cap in self.skill_adaptations:
1368
+ self.skill_adaptations[cap] *= 1.1 # Enhance
1369
+
1370
+ def _enhance_task_with_context(
1371
+ self, task: str, team_context: Dict, capabilities: List[str]
1372
+ ) -> str:
1373
+ """Enhance task description with team context."""
1374
+ enhanced = task
1375
+
1376
+ if team_context.get("team_goal"):
1377
+ enhanced = f"Team Goal: {team_context['team_goal']}\n\nYour Task: {task}"
1378
+
1379
+ if team_context.get("other_members"):
1380
+ enhanced += (
1381
+ f"\n\nOther team members: {', '.join(team_context['other_members'])}"
1382
+ )
1383
+ enhanced += "\nCoordinate and build upon their work as needed."
1384
+
1385
+ enhanced += (
1386
+ f"\n\nYour unique capabilities to leverage: {', '.join(capabilities)}"
1387
+ )
1388
+
1389
+ return enhanced
1390
+
1391
+ def _track_collaboration(
1392
+ self, agent_id: str, team_context: Dict, task: str, result: Dict
1393
+ ):
1394
+ """Track collaboration history and performance."""
1395
+ team_id = team_context.get("team_id", "unknown")
1396
+
1397
+ collaboration_entry = {
1398
+ "timestamp": time.time(),
1399
+ "agent_id": agent_id,
1400
+ "team_id": team_id,
1401
+ "task": task,
1402
+ "success": result.get("success", False),
1403
+ "insights_generated": result.get("a2a_metadata", {}).get(
1404
+ "insights_generated", 0
1405
+ ),
1406
+ }
1407
+
1408
+ self.collaboration_history.append(collaboration_entry)
1409
+
1410
+ # Update team membership stats
1411
+ if team_id in self.team_memberships:
1412
+ self.team_memberships[team_id]["contributions"] += 1
1413
+
1414
+
1415
+ @register_node()
1416
+ class SolutionEvaluatorNode(Node):
1417
+ """
1418
+ Evaluates solutions produced by agent teams and determines if
1419
+ iteration is needed.
1420
+
1421
+ Examples:
1422
+ >>> evaluator = SolutionEvaluatorNode()
1423
+ >>>
1424
+ >>> result = evaluator.run(
1425
+ ... solution={
1426
+ ... "approach": "Clustering analysis",
1427
+ ... "findings": ["3 distinct customer segments identified"],
1428
+ ... "confidence": 0.85
1429
+ ... },
1430
+ ... problem_requirements={
1431
+ ... "quality_threshold": 0.8,
1432
+ ... "required_outputs": ["segmentation", "recommendations"]
1433
+ ... },
1434
+ ... team_performance={
1435
+ ... "collaboration_score": 0.9,
1436
+ ... "time_taken": 45
1437
+ ... }
1438
+ ... )
1439
+ """
1440
+
1441
+ def __init__(self):
1442
+ super().__init__()
1443
+ self.evaluation_history = deque(maxlen=100)
1444
+
1445
+ def get_parameters(self) -> Dict[str, NodeParameter]:
1446
+ return {
1447
+ "solution": NodeParameter(
1448
+ name="solution",
1449
+ type=dict,
1450
+ required=False,
1451
+ default={},
1452
+ description="Solution to evaluate",
1453
+ ),
1454
+ "problem_requirements": NodeParameter(
1455
+ name="problem_requirements",
1456
+ type=dict,
1457
+ required=False,
1458
+ default={},
1459
+ description="Original problem requirements",
1460
+ ),
1461
+ "team_performance": NodeParameter(
1462
+ name="team_performance",
1463
+ type=dict,
1464
+ required=False,
1465
+ default={},
1466
+ description="Team performance metrics",
1467
+ ),
1468
+ "evaluation_criteria": NodeParameter(
1469
+ name="evaluation_criteria",
1470
+ type=dict,
1471
+ required=False,
1472
+ default={},
1473
+ description="Custom evaluation criteria",
1474
+ ),
1475
+ "iteration_count": NodeParameter(
1476
+ name="iteration_count",
1477
+ type=int,
1478
+ required=False,
1479
+ default=0,
1480
+ description="Current iteration number",
1481
+ ),
1482
+ }
1483
+
1484
+ def run(self, **kwargs) -> Dict[str, Any]:
1485
+ """Evaluate solution quality."""
1486
+ solution = kwargs.get("solution", {})
1487
+ requirements = kwargs.get("problem_requirements", {})
1488
+ team_performance = kwargs.get("team_performance", {})
1489
+ criteria = kwargs.get("evaluation_criteria", {})
1490
+ iteration = kwargs.get("iteration_count", 0)
1491
+
1492
+ # Evaluate different aspects
1493
+ quality_scores = {}
1494
+
1495
+ # 1. Completeness
1496
+ required_outputs = requirements.get("required_outputs", [])
1497
+ if required_outputs:
1498
+ outputs_found = sum(
1499
+ 1
1500
+ for output in required_outputs
1501
+ if output.lower() in str(solution).lower()
1502
+ )
1503
+ quality_scores["completeness"] = outputs_found / len(required_outputs)
1504
+ else:
1505
+ quality_scores["completeness"] = 0.8 # Default if not specified
1506
+
1507
+ # 2. Confidence/Certainty
1508
+ confidence = solution.get("confidence", 0.7)
1509
+ quality_scores["confidence"] = confidence
1510
+
1511
+ # 3. Innovation (based on solution complexity/uniqueness)
1512
+ solution_text = json.dumps(solution)
1513
+ quality_scores["innovation"] = min(len(set(solution_text.split())) / 100, 1.0)
1514
+
1515
+ # 4. Team collaboration
1516
+ collab_score = team_performance.get("collaboration_score", 0.8)
1517
+ quality_scores["collaboration"] = collab_score
1518
+
1519
+ # 5. Efficiency
1520
+ time_taken = team_performance.get("time_taken", 60)
1521
+ time_limit = requirements.get("time_estimate", 60)
1522
+ efficiency = min(time_limit / max(time_taken, 1), 1.0)
1523
+ quality_scores["efficiency"] = efficiency
1524
+
1525
+ # Calculate overall score
1526
+ weights = criteria.get(
1527
+ "weights",
1528
+ {
1529
+ "completeness": 0.3,
1530
+ "confidence": 0.25,
1531
+ "innovation": 0.15,
1532
+ "collaboration": 0.15,
1533
+ "efficiency": 0.15,
1534
+ },
1535
+ )
1536
+
1537
+ overall_score = sum(
1538
+ quality_scores.get(aspect, 0) * weight for aspect, weight in weights.items()
1539
+ )
1540
+
1541
+ # Determine if iteration needed
1542
+ quality_threshold = requirements.get("quality_threshold", 0.8)
1543
+ needs_iteration = overall_score < quality_threshold and iteration < 3
1544
+
1545
+ # Generate feedback for improvement
1546
+ feedback = self._generate_feedback(quality_scores, requirements, overall_score)
1547
+
1548
+ # Record evaluation
1549
+ self.evaluation_history.append(
1550
+ {
1551
+ "timestamp": time.time(),
1552
+ "overall_score": overall_score,
1553
+ "quality_scores": quality_scores,
1554
+ "iteration": iteration,
1555
+ "needs_iteration": needs_iteration,
1556
+ }
1557
+ )
1558
+
1559
+ return {
1560
+ "success": True,
1561
+ "overall_score": overall_score,
1562
+ "quality_scores": quality_scores,
1563
+ "meets_threshold": overall_score >= quality_threshold,
1564
+ "needs_iteration": needs_iteration,
1565
+ "feedback": feedback,
1566
+ "recommended_actions": self._recommend_actions(
1567
+ quality_scores, feedback, iteration
1568
+ ),
1569
+ }
1570
+
1571
+ def _generate_feedback(
1572
+ self, scores: Dict[str, float], requirements: Dict, overall: float
1573
+ ) -> Dict[str, Any]:
1574
+ """Generate specific feedback for improvement."""
1575
+ feedback = {"strengths": [], "weaknesses": [], "suggestions": []}
1576
+
1577
+ # Identify strengths and weaknesses
1578
+ for aspect, score in scores.items():
1579
+ if score >= 0.8:
1580
+ feedback["strengths"].append(f"Strong {aspect} (score: {score:.2f})")
1581
+ elif score < 0.6:
1582
+ feedback["weaknesses"].append(f"Weak {aspect} (score: {score:.2f})")
1583
+
1584
+ # Generate suggestions
1585
+ if scores.get("completeness", 1) < 0.8:
1586
+ feedback["suggestions"].append("Ensure all required outputs are addressed")
1587
+
1588
+ if scores.get("confidence", 1) < 0.7:
1589
+ feedback["suggestions"].append("Gather more evidence or validate findings")
1590
+
1591
+ if scores.get("collaboration", 1) < 0.7:
1592
+ feedback["suggestions"].append(
1593
+ "Improve team coordination and information sharing"
1594
+ )
1595
+
1596
+ return feedback
1597
+
1598
+ def _recommend_actions(
1599
+ self, scores: Dict[str, float], feedback: Dict, iteration: int
1600
+ ) -> List[str]:
1601
+ """Recommend specific actions for improvement."""
1602
+ actions = []
1603
+
1604
+ # Based on weakest areas
1605
+ weakest_aspect = min(scores.items(), key=lambda x: x[1])[0]
1606
+
1607
+ if weakest_aspect == "completeness":
1608
+ actions.append("Add specialists for missing output areas")
1609
+ elif weakest_aspect == "confidence":
1610
+ actions.append("Add validation or domain expert agents")
1611
+ elif weakest_aspect == "innovation":
1612
+ actions.append("Encourage diverse solution approaches")
1613
+ elif weakest_aspect == "collaboration":
1614
+ actions.append("Improve communication protocols")
1615
+ elif weakest_aspect == "efficiency":
1616
+ actions.append("Parallelize tasks or reduce team size")
1617
+
1618
+ # Iteration-specific recommendations
1619
+ if iteration > 1:
1620
+ actions.append("Consider alternative team composition")
1621
+ actions.append("Try different problem decomposition")
1622
+
1623
+ return actions