nc1709 1.15.4__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 (86) hide show
  1. nc1709/__init__.py +13 -0
  2. nc1709/agent/__init__.py +36 -0
  3. nc1709/agent/core.py +505 -0
  4. nc1709/agent/mcp_bridge.py +245 -0
  5. nc1709/agent/permissions.py +298 -0
  6. nc1709/agent/tools/__init__.py +21 -0
  7. nc1709/agent/tools/base.py +440 -0
  8. nc1709/agent/tools/bash_tool.py +367 -0
  9. nc1709/agent/tools/file_tools.py +454 -0
  10. nc1709/agent/tools/notebook_tools.py +516 -0
  11. nc1709/agent/tools/search_tools.py +322 -0
  12. nc1709/agent/tools/task_tool.py +284 -0
  13. nc1709/agent/tools/web_tools.py +555 -0
  14. nc1709/agents/__init__.py +17 -0
  15. nc1709/agents/auto_fix.py +506 -0
  16. nc1709/agents/test_generator.py +507 -0
  17. nc1709/checkpoints.py +372 -0
  18. nc1709/cli.py +3380 -0
  19. nc1709/cli_ui.py +1080 -0
  20. nc1709/cognitive/__init__.py +149 -0
  21. nc1709/cognitive/anticipation.py +594 -0
  22. nc1709/cognitive/context_engine.py +1046 -0
  23. nc1709/cognitive/council.py +824 -0
  24. nc1709/cognitive/learning.py +761 -0
  25. nc1709/cognitive/router.py +583 -0
  26. nc1709/cognitive/system.py +519 -0
  27. nc1709/config.py +155 -0
  28. nc1709/custom_commands.py +300 -0
  29. nc1709/executor.py +333 -0
  30. nc1709/file_controller.py +354 -0
  31. nc1709/git_integration.py +308 -0
  32. nc1709/github_integration.py +477 -0
  33. nc1709/image_input.py +446 -0
  34. nc1709/linting.py +519 -0
  35. nc1709/llm_adapter.py +667 -0
  36. nc1709/logger.py +192 -0
  37. nc1709/mcp/__init__.py +18 -0
  38. nc1709/mcp/client.py +370 -0
  39. nc1709/mcp/manager.py +407 -0
  40. nc1709/mcp/protocol.py +210 -0
  41. nc1709/mcp/server.py +473 -0
  42. nc1709/memory/__init__.py +20 -0
  43. nc1709/memory/embeddings.py +325 -0
  44. nc1709/memory/indexer.py +474 -0
  45. nc1709/memory/sessions.py +432 -0
  46. nc1709/memory/vector_store.py +451 -0
  47. nc1709/models/__init__.py +86 -0
  48. nc1709/models/detector.py +377 -0
  49. nc1709/models/formats.py +315 -0
  50. nc1709/models/manager.py +438 -0
  51. nc1709/models/registry.py +497 -0
  52. nc1709/performance/__init__.py +343 -0
  53. nc1709/performance/cache.py +705 -0
  54. nc1709/performance/pipeline.py +611 -0
  55. nc1709/performance/tiering.py +543 -0
  56. nc1709/plan_mode.py +362 -0
  57. nc1709/plugins/__init__.py +17 -0
  58. nc1709/plugins/agents/__init__.py +18 -0
  59. nc1709/plugins/agents/django_agent.py +912 -0
  60. nc1709/plugins/agents/docker_agent.py +623 -0
  61. nc1709/plugins/agents/fastapi_agent.py +887 -0
  62. nc1709/plugins/agents/git_agent.py +731 -0
  63. nc1709/plugins/agents/nextjs_agent.py +867 -0
  64. nc1709/plugins/base.py +359 -0
  65. nc1709/plugins/manager.py +411 -0
  66. nc1709/plugins/registry.py +337 -0
  67. nc1709/progress.py +443 -0
  68. nc1709/prompts/__init__.py +22 -0
  69. nc1709/prompts/agent_system.py +180 -0
  70. nc1709/prompts/task_prompts.py +340 -0
  71. nc1709/prompts/unified_prompt.py +133 -0
  72. nc1709/reasoning_engine.py +541 -0
  73. nc1709/remote_client.py +266 -0
  74. nc1709/shell_completions.py +349 -0
  75. nc1709/slash_commands.py +649 -0
  76. nc1709/task_classifier.py +408 -0
  77. nc1709/version_check.py +177 -0
  78. nc1709/web/__init__.py +8 -0
  79. nc1709/web/server.py +950 -0
  80. nc1709/web/templates/index.html +1127 -0
  81. nc1709-1.15.4.dist-info/METADATA +858 -0
  82. nc1709-1.15.4.dist-info/RECORD +86 -0
  83. nc1709-1.15.4.dist-info/WHEEL +5 -0
  84. nc1709-1.15.4.dist-info/entry_points.txt +2 -0
  85. nc1709-1.15.4.dist-info/licenses/LICENSE +9 -0
  86. nc1709-1.15.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,824 @@
1
+ """
2
+ Layer 3: Multi-Agent Council
3
+
4
+ Orchestrates multiple AI agents that specialize in different aspects:
5
+ - Implementer: Writes clean, efficient code
6
+ - Architect: Designs system structure and patterns
7
+ - Reviewer: Finds bugs, security issues, improvements
8
+ - Debugger: Diagnoses and fixes issues
9
+ - Optimizer: Improves performance
10
+ - Security: Analyzes security implications
11
+ - Documentation: Writes clear documentation
12
+
13
+ The council convenes for complex tasks (complexity > threshold) and
14
+ produces a consensus response that combines multiple perspectives.
15
+
16
+ This layer answers: "What do our AI experts think about this?"
17
+ """
18
+
19
+ import logging
20
+ from dataclasses import dataclass, field
21
+ from typing import Dict, List, Optional, Any, Callable, Set
22
+ from enum import Enum
23
+ from datetime import datetime
24
+ import asyncio
25
+ import json
26
+ import threading
27
+ from concurrent.futures import ThreadPoolExecutor, as_completed
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class AgentRole(Enum):
33
+ """Specialized agent roles in the council"""
34
+ IMPLEMENTER = "implementer"
35
+ ARCHITECT = "architect"
36
+ REVIEWER = "reviewer"
37
+ DEBUGGER = "debugger"
38
+ OPTIMIZER = "optimizer"
39
+ SECURITY = "security"
40
+ DOCUMENTATION = "documentation"
41
+ TESTER = "tester"
42
+ DEVOPS = "devops"
43
+
44
+
45
+ @dataclass
46
+ class AgentPersona:
47
+ """Configuration for an AI agent's behavior and expertise"""
48
+ role: AgentRole
49
+ name: str
50
+ description: str
51
+ system_prompt: str
52
+ focus_areas: List[str]
53
+ weight: float = 1.0 # Weight in consensus voting
54
+ preferred_model: Optional[str] = None
55
+ temperature: float = 0.7
56
+ max_tokens: int = 2000
57
+
58
+
59
+ # Default agent personas
60
+ DEFAULT_AGENTS: Dict[AgentRole, AgentPersona] = {
61
+ AgentRole.IMPLEMENTER: AgentPersona(
62
+ role=AgentRole.IMPLEMENTER,
63
+ name="Implementer",
64
+ description="Expert at writing clean, efficient, maintainable code",
65
+ system_prompt="""You are an expert software implementer. Your focus is on:
66
+ - Writing clean, readable, and maintainable code
67
+ - Following language idioms and best practices
68
+ - Implementing features correctly and completely
69
+ - Using appropriate data structures and algorithms
70
+ - Writing code that is easy to test and debug
71
+
72
+ When analyzing or writing code, prioritize correctness, clarity, and simplicity.
73
+ Suggest concrete implementations with actual code when relevant.""",
74
+ focus_areas=["code_quality", "implementation", "best_practices", "readability"],
75
+ weight=1.0,
76
+ ),
77
+
78
+ AgentRole.ARCHITECT: AgentPersona(
79
+ role=AgentRole.ARCHITECT,
80
+ name="Architect",
81
+ description="Designs system structure, patterns, and high-level solutions",
82
+ system_prompt="""You are a software architect. Your focus is on:
83
+ - System design and architecture patterns
84
+ - Code organization and module structure
85
+ - Scalability and maintainability concerns
86
+ - Design patterns and their appropriate use
87
+ - API design and interfaces
88
+ - Managing complexity through abstraction
89
+
90
+ When analyzing code, think about the bigger picture: how components interact,
91
+ where boundaries should be, and what patterns would improve the design.""",
92
+ focus_areas=["architecture", "design_patterns", "scalability", "modularity"],
93
+ weight=1.2, # Slightly higher weight for complex decisions
94
+ ),
95
+
96
+ AgentRole.REVIEWER: AgentPersona(
97
+ role=AgentRole.REVIEWER,
98
+ name="Reviewer",
99
+ description="Reviews code for bugs, issues, and improvements",
100
+ system_prompt="""You are an expert code reviewer. Your focus is on:
101
+ - Finding bugs, logic errors, and edge cases
102
+ - Identifying code smells and anti-patterns
103
+ - Suggesting improvements and refactoring opportunities
104
+ - Checking for consistency with project style
105
+ - Ensuring error handling is comprehensive
106
+ - Validating that tests cover important cases
107
+
108
+ Be thorough but constructive. Point out issues with specific suggestions for fixes.""",
109
+ focus_areas=["bugs", "code_quality", "improvements", "consistency"],
110
+ weight=1.0,
111
+ ),
112
+
113
+ AgentRole.DEBUGGER: AgentPersona(
114
+ role=AgentRole.DEBUGGER,
115
+ name="Debugger",
116
+ description="Expert at diagnosing and fixing bugs",
117
+ system_prompt="""You are an expert debugger. Your focus is on:
118
+ - Analyzing error messages and stack traces
119
+ - Identifying root causes of bugs
120
+ - Understanding unexpected behavior
121
+ - Tracing execution flow and state
122
+ - Suggesting targeted fixes with minimal side effects
123
+ - Adding logging/debugging aids when helpful
124
+
125
+ When debugging, be systematic: hypothesize, verify, fix. Explain your reasoning.""",
126
+ focus_areas=["debugging", "error_analysis", "root_cause", "fixes"],
127
+ weight=1.1, # Higher weight for debugging tasks
128
+ ),
129
+
130
+ AgentRole.OPTIMIZER: AgentPersona(
131
+ role=AgentRole.OPTIMIZER,
132
+ name="Optimizer",
133
+ description="Focuses on performance and efficiency improvements",
134
+ system_prompt="""You are a performance optimization expert. Your focus is on:
135
+ - Identifying performance bottlenecks
136
+ - Algorithm and data structure optimization
137
+ - Memory usage and allocation patterns
138
+ - I/O and database query optimization
139
+ - Caching strategies and memoization
140
+ - Profiling and benchmarking suggestions
141
+
142
+ When optimizing, consider trade-offs between readability and performance.
143
+ Only suggest optimizations that provide meaningful improvements.""",
144
+ focus_areas=["performance", "optimization", "efficiency", "memory"],
145
+ weight=0.9,
146
+ ),
147
+
148
+ AgentRole.SECURITY: AgentPersona(
149
+ role=AgentRole.SECURITY,
150
+ name="Security Analyst",
151
+ description="Analyzes security implications and vulnerabilities",
152
+ system_prompt="""You are a security expert. Your focus is on:
153
+ - Identifying security vulnerabilities (OWASP Top 10, etc.)
154
+ - Input validation and sanitization
155
+ - Authentication and authorization issues
156
+ - Injection attacks (SQL, XSS, command injection)
157
+ - Secrets management and exposure
158
+ - Secure coding practices
159
+
160
+ When reviewing code, think like an attacker. Point out vulnerabilities with
161
+ severity levels and specific remediation steps.""",
162
+ focus_areas=["security", "vulnerabilities", "authentication", "validation"],
163
+ weight=1.3, # High weight for security concerns
164
+ ),
165
+
166
+ AgentRole.DOCUMENTATION: AgentPersona(
167
+ role=AgentRole.DOCUMENTATION,
168
+ name="Documentarian",
169
+ description="Creates clear documentation and explanations",
170
+ system_prompt="""You are a technical writer and documentation expert. Your focus is on:
171
+ - Writing clear, comprehensive documentation
172
+ - Creating helpful docstrings and comments
173
+ - Explaining complex code in simple terms
174
+ - Writing README files and guides
175
+ - API documentation with examples
176
+ - Architecture decision records
177
+
178
+ When documenting, think about the reader: what do they need to know?
179
+ Provide examples and context, not just descriptions.""",
180
+ focus_areas=["documentation", "clarity", "examples", "explanations"],
181
+ weight=0.8,
182
+ ),
183
+
184
+ AgentRole.TESTER: AgentPersona(
185
+ role=AgentRole.TESTER,
186
+ name="Tester",
187
+ description="Expert at testing strategies and test implementation",
188
+ system_prompt="""You are a testing expert. Your focus is on:
189
+ - Designing comprehensive test strategies
190
+ - Writing unit, integration, and e2e tests
191
+ - Identifying edge cases and boundary conditions
192
+ - Test coverage and quality metrics
193
+ - Mocking and test isolation
194
+ - Test-driven development practices
195
+
196
+ When analyzing code, think about how to test it effectively.
197
+ Suggest specific test cases that would catch bugs.""",
198
+ focus_areas=["testing", "test_cases", "coverage", "quality"],
199
+ weight=0.9,
200
+ ),
201
+
202
+ AgentRole.DEVOPS: AgentPersona(
203
+ role=AgentRole.DEVOPS,
204
+ name="DevOps Engineer",
205
+ description="Expert in deployment, infrastructure, and operations",
206
+ system_prompt="""You are a DevOps expert. Your focus is on:
207
+ - CI/CD pipelines and deployment automation
208
+ - Infrastructure as code
209
+ - Container orchestration (Docker, Kubernetes)
210
+ - Monitoring, logging, and observability
211
+ - Environment configuration and secrets
212
+ - Scalability and reliability concerns
213
+
214
+ When analyzing code, consider operational aspects: how will this run in production?
215
+ What monitoring and deployment considerations apply?""",
216
+ focus_areas=["devops", "deployment", "infrastructure", "monitoring"],
217
+ weight=0.8,
218
+ ),
219
+ }
220
+
221
+
222
+ @dataclass
223
+ class AgentResponse:
224
+ """Response from a single agent"""
225
+ agent_role: AgentRole
226
+ content: str
227
+ confidence: float # 0.0 to 1.0
228
+ key_points: List[str] = field(default_factory=list)
229
+ suggestions: List[str] = field(default_factory=list)
230
+ concerns: List[str] = field(default_factory=list)
231
+ code_snippets: List[str] = field(default_factory=list)
232
+ timestamp: datetime = field(default_factory=datetime.now)
233
+ metadata: Dict[str, Any] = field(default_factory=dict)
234
+
235
+
236
+ @dataclass
237
+ class CouncilSession:
238
+ """A council session with multiple agent responses"""
239
+ session_id: str
240
+ task_description: str
241
+ agents_consulted: List[AgentRole]
242
+ responses: Dict[AgentRole, AgentResponse] = field(default_factory=dict)
243
+ consensus: Optional[str] = None
244
+ consensus_confidence: float = 0.0
245
+ disagreements: List[str] = field(default_factory=list)
246
+ started_at: datetime = field(default_factory=datetime.now)
247
+ completed_at: Optional[datetime] = None
248
+ metadata: Dict[str, Any] = field(default_factory=dict)
249
+
250
+
251
+ class AgentSelector:
252
+ """Selects which agents should participate based on task type"""
253
+
254
+ # Task type to agent role mapping
255
+ TASK_AGENT_MAP: Dict[str, List[AgentRole]] = {
256
+ "code_generation": [AgentRole.IMPLEMENTER, AgentRole.ARCHITECT],
257
+ "code_modification": [AgentRole.IMPLEMENTER, AgentRole.REVIEWER],
258
+ "debugging": [AgentRole.DEBUGGER, AgentRole.IMPLEMENTER],
259
+ "code_review": [AgentRole.REVIEWER, AgentRole.SECURITY],
260
+ "security": [AgentRole.SECURITY, AgentRole.REVIEWER],
261
+ "performance": [AgentRole.OPTIMIZER, AgentRole.ARCHITECT],
262
+ "refactoring": [AgentRole.ARCHITECT, AgentRole.REVIEWER, AgentRole.IMPLEMENTER],
263
+ "testing": [AgentRole.TESTER, AgentRole.REVIEWER],
264
+ "documentation": [AgentRole.DOCUMENTATION, AgentRole.IMPLEMENTER],
265
+ "devops": [AgentRole.DEVOPS, AgentRole.SECURITY],
266
+ "explanation": [AgentRole.DOCUMENTATION, AgentRole.ARCHITECT],
267
+ "reasoning": [AgentRole.ARCHITECT, AgentRole.REVIEWER],
268
+ "project_setup": [AgentRole.ARCHITECT, AgentRole.DEVOPS],
269
+ }
270
+
271
+ # Minimum and maximum agents per session
272
+ MIN_AGENTS = 2
273
+ MAX_AGENTS = 4
274
+
275
+ @classmethod
276
+ def select_agents(
277
+ cls,
278
+ task_category: str,
279
+ complexity: float,
280
+ explicit_roles: Optional[List[str]] = None
281
+ ) -> List[AgentRole]:
282
+ """
283
+ Select agents for a task based on category and complexity
284
+
285
+ Args:
286
+ task_category: Type of task (from TaskCategory)
287
+ complexity: Task complexity score (0.0 to 1.0)
288
+ explicit_roles: Optional list of specific roles to include
289
+
290
+ Returns:
291
+ List of AgentRole to consult
292
+ """
293
+ selected: Set[AgentRole] = set()
294
+
295
+ # Add explicitly requested roles
296
+ if explicit_roles:
297
+ for role_name in explicit_roles:
298
+ try:
299
+ role = AgentRole(role_name.lower())
300
+ selected.add(role)
301
+ except ValueError:
302
+ logger.warning(f"Unknown agent role: {role_name}")
303
+
304
+ # Add roles based on task type
305
+ task_key = task_category.lower().replace("_", "_")
306
+ if task_key in cls.TASK_AGENT_MAP:
307
+ for role in cls.TASK_AGENT_MAP[task_key]:
308
+ selected.add(role)
309
+
310
+ # For high complexity, add more perspectives
311
+ if complexity > 0.8 and len(selected) < cls.MAX_AGENTS:
312
+ # Add architect for complex design decisions
313
+ selected.add(AgentRole.ARCHITECT)
314
+ # Add reviewer for quality assurance
315
+ selected.add(AgentRole.REVIEWER)
316
+
317
+ # For security-sensitive tasks, always include security
318
+ security_keywords = ["auth", "login", "password", "token", "secret", "credential", "encrypt"]
319
+ if any(kw in task_category.lower() for kw in security_keywords):
320
+ selected.add(AgentRole.SECURITY)
321
+
322
+ # Ensure minimum agents
323
+ if len(selected) < cls.MIN_AGENTS:
324
+ # Default to implementer and reviewer
325
+ selected.add(AgentRole.IMPLEMENTER)
326
+ selected.add(AgentRole.REVIEWER)
327
+
328
+ # Limit to max agents
329
+ result = list(selected)[:cls.MAX_AGENTS]
330
+
331
+ return result
332
+
333
+
334
+ class ConsensusBuilder:
335
+ """Builds consensus from multiple agent responses"""
336
+
337
+ @staticmethod
338
+ def build_consensus(
339
+ responses: Dict[AgentRole, AgentResponse],
340
+ agent_personas: Dict[AgentRole, AgentPersona]
341
+ ) -> tuple[str, float, List[str]]:
342
+ """
343
+ Build consensus from multiple agent responses
344
+
345
+ Args:
346
+ responses: Dict of agent role to response
347
+ agent_personas: Dict of agent role to persona (for weights)
348
+
349
+ Returns:
350
+ Tuple of (consensus_text, confidence, disagreements)
351
+ """
352
+ if not responses:
353
+ return "", 0.0, []
354
+
355
+ # Collect all key points and suggestions with weights
356
+ weighted_points: Dict[str, float] = {}
357
+ weighted_suggestions: Dict[str, float] = {}
358
+ all_concerns: List[str] = []
359
+ all_code_snippets: List[str] = []
360
+
361
+ total_weight = 0.0
362
+ total_confidence = 0.0
363
+
364
+ for role, response in responses.items():
365
+ persona = agent_personas.get(role)
366
+ weight = persona.weight if persona else 1.0
367
+ total_weight += weight
368
+ total_confidence += response.confidence * weight
369
+
370
+ # Weight key points
371
+ for point in response.key_points:
372
+ point_lower = point.lower().strip()
373
+ if point_lower not in weighted_points:
374
+ weighted_points[point_lower] = 0.0
375
+ weighted_points[point_lower] += weight * response.confidence
376
+
377
+ # Weight suggestions
378
+ for suggestion in response.suggestions:
379
+ sug_lower = suggestion.lower().strip()
380
+ if sug_lower not in weighted_suggestions:
381
+ weighted_suggestions[sug_lower] = 0.0
382
+ weighted_suggestions[sug_lower] += weight * response.confidence
383
+
384
+ # Collect concerns
385
+ all_concerns.extend(response.concerns)
386
+
387
+ # Collect code snippets (prefer from implementer)
388
+ if role == AgentRole.IMPLEMENTER:
389
+ all_code_snippets = response.code_snippets + all_code_snippets
390
+ else:
391
+ all_code_snippets.extend(response.code_snippets)
392
+
393
+ # Calculate overall confidence
394
+ consensus_confidence = total_confidence / total_weight if total_weight > 0 else 0.0
395
+
396
+ # Find disagreements (points mentioned by some but not others)
397
+ disagreements = []
398
+ for point, weight in weighted_points.items():
399
+ # If a point has low weight relative to total, it's a potential disagreement
400
+ agreement_ratio = weight / total_weight
401
+ if 0.2 < agreement_ratio < 0.6: # Partial agreement
402
+ disagreements.append(f"Partial agreement on: {point}")
403
+
404
+ # Build consensus text
405
+ consensus_parts = []
406
+
407
+ # Add top key points
408
+ sorted_points = sorted(weighted_points.items(), key=lambda x: x[1], reverse=True)
409
+ if sorted_points:
410
+ consensus_parts.append("**Key Points:**")
411
+ for point, _ in sorted_points[:5]:
412
+ consensus_parts.append(f"- {point}")
413
+
414
+ # Add top suggestions
415
+ sorted_suggestions = sorted(weighted_suggestions.items(), key=lambda x: x[1], reverse=True)
416
+ if sorted_suggestions:
417
+ consensus_parts.append("\n**Recommendations:**")
418
+ for suggestion, _ in sorted_suggestions[:5]:
419
+ consensus_parts.append(f"- {suggestion}")
420
+
421
+ # Add concerns if any
422
+ unique_concerns = list(set(all_concerns))
423
+ if unique_concerns:
424
+ consensus_parts.append("\n**Concerns Raised:**")
425
+ for concern in unique_concerns[:3]:
426
+ consensus_parts.append(f"- {concern}")
427
+
428
+ # Add code snippets if any
429
+ if all_code_snippets:
430
+ consensus_parts.append("\n**Code:**")
431
+ for snippet in all_code_snippets[:2]: # Limit code snippets
432
+ consensus_parts.append(f"```\n{snippet}\n```")
433
+
434
+ consensus_text = "\n".join(consensus_parts)
435
+
436
+ return consensus_text, consensus_confidence, disagreements
437
+
438
+
439
+ class MultiAgentCouncil:
440
+ """
441
+ Layer 3: Multi-Agent Council
442
+
443
+ Orchestrates multiple AI agents to collaborate on complex tasks.
444
+ Each agent brings a specialized perspective, and the council
445
+ produces a consensus response.
446
+ """
447
+
448
+ def __init__(
449
+ self,
450
+ llm_adapter: Optional[Any] = None,
451
+ agent_personas: Optional[Dict[AgentRole, AgentPersona]] = None,
452
+ max_parallel_agents: int = 3,
453
+ council_threshold: float = 0.75
454
+ ):
455
+ """
456
+ Initialize the council
457
+
458
+ Args:
459
+ llm_adapter: LLM adapter for agent completions
460
+ agent_personas: Custom agent personas (defaults used if not provided)
461
+ max_parallel_agents: Maximum agents to run in parallel
462
+ council_threshold: Complexity threshold to convene full council
463
+ """
464
+ self._llm_adapter = llm_adapter
465
+ self.agent_personas = agent_personas or DEFAULT_AGENTS
466
+ self.max_parallel_agents = max_parallel_agents
467
+ self.council_threshold = council_threshold
468
+ self._executor = ThreadPoolExecutor(max_workers=max_parallel_agents)
469
+ self._session_history: List[CouncilSession] = []
470
+ self._lock = threading.Lock()
471
+
472
+ @property
473
+ def llm_adapter(self):
474
+ """Lazy load LLM adapter"""
475
+ if self._llm_adapter is None:
476
+ try:
477
+ from ..llm_adapter import get_llm_adapter
478
+ self._llm_adapter = get_llm_adapter()
479
+ except Exception as e:
480
+ logger.warning(f"Could not load LLM adapter: {e}")
481
+ return self._llm_adapter
482
+
483
+ def _generate_session_id(self) -> str:
484
+ """Generate unique session ID"""
485
+ import uuid
486
+ return f"council_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}"
487
+
488
+ def _create_agent_prompt(
489
+ self,
490
+ persona: AgentPersona,
491
+ task_description: str,
492
+ context: Optional[Dict[str, Any]] = None
493
+ ) -> str:
494
+ """Create the full prompt for an agent"""
495
+ parts = []
496
+
497
+ # Add context if available
498
+ if context:
499
+ if context.get("code"):
500
+ parts.append(f"**Code to analyze:**\n```\n{context['code']}\n```\n")
501
+ if context.get("error"):
502
+ parts.append(f"**Error:**\n{context['error']}\n")
503
+ if context.get("files"):
504
+ parts.append(f"**Relevant files:** {', '.join(context['files'])}\n")
505
+
506
+ parts.append(f"**Task:**\n{task_description}")
507
+
508
+ parts.append("""
509
+ Please provide your analysis as a structured response:
510
+ 1. KEY POINTS: Main observations or findings (bullet points)
511
+ 2. SUGGESTIONS: Recommended actions or improvements
512
+ 3. CONCERNS: Any issues or risks identified
513
+ 4. CODE: Any code snippets (if applicable)
514
+
515
+ Be concise but thorough. Focus on your area of expertise.""")
516
+
517
+ return "\n\n".join(parts)
518
+
519
+ def _parse_agent_response(self, raw_response: str, role: AgentRole) -> AgentResponse:
520
+ """Parse raw LLM response into structured AgentResponse"""
521
+ key_points = []
522
+ suggestions = []
523
+ concerns = []
524
+ code_snippets = []
525
+
526
+ # Simple parsing of structured response
527
+ current_section = None
528
+ current_content = []
529
+
530
+ for line in raw_response.split('\n'):
531
+ line_lower = line.lower().strip()
532
+
533
+ if 'key point' in line_lower or line_lower.startswith('1.'):
534
+ if current_section and current_content:
535
+ self._add_to_section(current_section, current_content, key_points, suggestions, concerns)
536
+ current_section = 'key_points'
537
+ current_content = []
538
+ elif 'suggestion' in line_lower or 'recommend' in line_lower or line_lower.startswith('2.'):
539
+ if current_section and current_content:
540
+ self._add_to_section(current_section, current_content, key_points, suggestions, concerns)
541
+ current_section = 'suggestions'
542
+ current_content = []
543
+ elif 'concern' in line_lower or 'issue' in line_lower or 'risk' in line_lower or line_lower.startswith('3.'):
544
+ if current_section and current_content:
545
+ self._add_to_section(current_section, current_content, key_points, suggestions, concerns)
546
+ current_section = 'concerns'
547
+ current_content = []
548
+ elif line.startswith('```'):
549
+ # Handle code block
550
+ if current_section == 'code':
551
+ code_snippets.append('\n'.join(current_content))
552
+ current_section = None
553
+ current_content = []
554
+ else:
555
+ current_section = 'code'
556
+ current_content = []
557
+ elif line.strip().startswith('-') or line.strip().startswith('•'):
558
+ # Bullet point
559
+ content = line.strip().lstrip('-•').strip()
560
+ if content:
561
+ current_content.append(content)
562
+ elif line.strip():
563
+ current_content.append(line.strip())
564
+
565
+ # Handle last section
566
+ if current_section and current_content:
567
+ if current_section == 'code':
568
+ code_snippets.append('\n'.join(current_content))
569
+ else:
570
+ self._add_to_section(current_section, current_content, key_points, suggestions, concerns)
571
+
572
+ # If no structure found, use raw response as key points
573
+ if not key_points and not suggestions and not concerns:
574
+ key_points = [raw_response[:500]] # Truncate if too long
575
+
576
+ # Calculate confidence based on response quality
577
+ confidence = 0.7 # Base confidence
578
+ if key_points:
579
+ confidence += 0.1
580
+ if suggestions:
581
+ confidence += 0.1
582
+ if code_snippets:
583
+ confidence += 0.1
584
+
585
+ return AgentResponse(
586
+ agent_role=role,
587
+ content=raw_response,
588
+ confidence=min(1.0, confidence),
589
+ key_points=key_points[:5],
590
+ suggestions=suggestions[:5],
591
+ concerns=concerns[:3],
592
+ code_snippets=code_snippets[:2],
593
+ )
594
+
595
+ def _add_to_section(
596
+ self,
597
+ section: str,
598
+ content: List[str],
599
+ key_points: List[str],
600
+ suggestions: List[str],
601
+ concerns: List[str]
602
+ ):
603
+ """Add content to appropriate section"""
604
+ joined = ' '.join(content)
605
+ if section == 'key_points':
606
+ key_points.extend(content)
607
+ elif section == 'suggestions':
608
+ suggestions.extend(content)
609
+ elif section == 'concerns':
610
+ concerns.extend(content)
611
+
612
+ def _consult_agent(
613
+ self,
614
+ persona: AgentPersona,
615
+ task_description: str,
616
+ context: Optional[Dict[str, Any]] = None
617
+ ) -> AgentResponse:
618
+ """
619
+ Consult a single agent
620
+
621
+ Args:
622
+ persona: Agent's persona configuration
623
+ task_description: Task to analyze
624
+ context: Additional context
625
+
626
+ Returns:
627
+ AgentResponse with the agent's analysis
628
+ """
629
+ prompt = self._create_agent_prompt(persona, task_description, context)
630
+
631
+ try:
632
+ if self.llm_adapter:
633
+ response = self.llm_adapter.complete(
634
+ prompt=prompt,
635
+ system_prompt=persona.system_prompt,
636
+ model=persona.preferred_model,
637
+ temperature=persona.temperature,
638
+ max_tokens=persona.max_tokens,
639
+ )
640
+ else:
641
+ # Fallback: return a placeholder response
642
+ response = f"[{persona.name}] Analysis placeholder for: {task_description[:100]}..."
643
+
644
+ return self._parse_agent_response(response, persona.role)
645
+
646
+ except Exception as e:
647
+ logger.error(f"Error consulting {persona.name}: {e}")
648
+ return AgentResponse(
649
+ agent_role=persona.role,
650
+ content=f"Error: {e}",
651
+ confidence=0.0,
652
+ concerns=[f"Agent error: {e}"],
653
+ )
654
+
655
+ def convene(
656
+ self,
657
+ task_description: str,
658
+ task_category: str = "general",
659
+ complexity: float = 0.5,
660
+ context: Optional[Dict[str, Any]] = None,
661
+ agents: Optional[List[str]] = None,
662
+ parallel: bool = True
663
+ ) -> CouncilSession:
664
+ """
665
+ Convene the council for a task
666
+
667
+ Args:
668
+ task_description: Description of the task
669
+ task_category: Category of the task
670
+ complexity: Task complexity (0.0 to 1.0)
671
+ context: Additional context (code, files, errors)
672
+ agents: Specific agents to consult
673
+ parallel: Run agents in parallel
674
+
675
+ Returns:
676
+ CouncilSession with all responses and consensus
677
+ """
678
+ # Select agents
679
+ agent_roles = AgentSelector.select_agents(
680
+ task_category=task_category,
681
+ complexity=complexity,
682
+ explicit_roles=agents
683
+ )
684
+
685
+ session = CouncilSession(
686
+ session_id=self._generate_session_id(),
687
+ task_description=task_description,
688
+ agents_consulted=agent_roles,
689
+ metadata={
690
+ "category": task_category,
691
+ "complexity": complexity,
692
+ }
693
+ )
694
+
695
+ logger.info(f"Council convened: {session.session_id} with {[r.value for r in agent_roles]}")
696
+
697
+ # Consult agents
698
+ if parallel and len(agent_roles) > 1:
699
+ # Parallel consultation
700
+ futures = {}
701
+ for role in agent_roles:
702
+ persona = self.agent_personas.get(role)
703
+ if persona:
704
+ future = self._executor.submit(
705
+ self._consult_agent, persona, task_description, context
706
+ )
707
+ futures[future] = role
708
+
709
+ for future in as_completed(futures):
710
+ role = futures[future]
711
+ try:
712
+ response = future.result()
713
+ session.responses[role] = response
714
+ except Exception as e:
715
+ logger.error(f"Error from {role.value}: {e}")
716
+
717
+ else:
718
+ # Sequential consultation
719
+ for role in agent_roles:
720
+ persona = self.agent_personas.get(role)
721
+ if persona:
722
+ response = self._consult_agent(persona, task_description, context)
723
+ session.responses[role] = response
724
+
725
+ # Build consensus
726
+ if session.responses:
727
+ consensus, confidence, disagreements = ConsensusBuilder.build_consensus(
728
+ session.responses,
729
+ self.agent_personas
730
+ )
731
+ session.consensus = consensus
732
+ session.consensus_confidence = confidence
733
+ session.disagreements = disagreements
734
+
735
+ session.completed_at = datetime.now()
736
+
737
+ # Store session
738
+ with self._lock:
739
+ self._session_history.append(session)
740
+ # Keep last 100 sessions
741
+ if len(self._session_history) > 100:
742
+ self._session_history = self._session_history[-100:]
743
+
744
+ return session
745
+
746
+ async def convene_async(
747
+ self,
748
+ task_description: str,
749
+ task_category: str = "general",
750
+ complexity: float = 0.5,
751
+ context: Optional[Dict[str, Any]] = None,
752
+ agents: Optional[List[str]] = None
753
+ ) -> CouncilSession:
754
+ """Async version of convene"""
755
+ loop = asyncio.get_event_loop()
756
+ return await loop.run_in_executor(
757
+ None,
758
+ lambda: self.convene(
759
+ task_description=task_description,
760
+ task_category=task_category,
761
+ complexity=complexity,
762
+ context=context,
763
+ agents=agents,
764
+ parallel=True
765
+ )
766
+ )
767
+
768
+ def quick_consult(
769
+ self,
770
+ task_description: str,
771
+ role: AgentRole
772
+ ) -> AgentResponse:
773
+ """
774
+ Quick consultation with a single agent
775
+
776
+ Args:
777
+ task_description: Task to analyze
778
+ role: Specific agent role
779
+
780
+ Returns:
781
+ AgentResponse from the agent
782
+ """
783
+ persona = self.agent_personas.get(role)
784
+ if not persona:
785
+ raise ValueError(f"Unknown agent role: {role}")
786
+
787
+ return self._consult_agent(persona, task_description)
788
+
789
+ def get_session_history(self, limit: int = 10) -> List[CouncilSession]:
790
+ """Get recent council sessions"""
791
+ with self._lock:
792
+ return self._session_history[-limit:]
793
+
794
+ def get_available_agents(self) -> List[Dict[str, Any]]:
795
+ """Get list of available agents with descriptions"""
796
+ return [
797
+ {
798
+ "role": role.value,
799
+ "name": persona.name,
800
+ "description": persona.description,
801
+ "focus_areas": persona.focus_areas,
802
+ }
803
+ for role, persona in self.agent_personas.items()
804
+ ]
805
+
806
+
807
+ # Convenience functions
808
+ def get_council(llm_adapter: Optional[Any] = None) -> MultiAgentCouncil:
809
+ """Get or create council instance"""
810
+ return MultiAgentCouncil(llm_adapter=llm_adapter)
811
+
812
+
813
+ def quick_council(
814
+ task: str,
815
+ category: str = "general",
816
+ complexity: float = 0.5
817
+ ) -> CouncilSession:
818
+ """Quickly convene council for a task"""
819
+ council = get_council()
820
+ return council.convene(
821
+ task_description=task,
822
+ task_category=category,
823
+ complexity=complexity
824
+ )