empathy-framework 4.6.2__py3-none-any.whl → 4.6.3__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 (53) hide show
  1. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/METADATA +1 -1
  2. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/RECORD +53 -20
  3. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/WHEEL +1 -1
  4. empathy_os/__init__.py +1 -1
  5. empathy_os/cli.py +361 -32
  6. empathy_os/config/xml_config.py +8 -3
  7. empathy_os/core.py +37 -4
  8. empathy_os/leverage_points.py +2 -1
  9. empathy_os/memory/short_term.py +45 -1
  10. empathy_os/meta_workflows/agent_creator 2.py +254 -0
  11. empathy_os/meta_workflows/builtin_templates 2.py +567 -0
  12. empathy_os/meta_workflows/cli_meta_workflows 2.py +1551 -0
  13. empathy_os/meta_workflows/form_engine 2.py +304 -0
  14. empathy_os/meta_workflows/intent_detector 2.py +298 -0
  15. empathy_os/meta_workflows/pattern_learner 2.py +754 -0
  16. empathy_os/meta_workflows/session_context 2.py +398 -0
  17. empathy_os/meta_workflows/template_registry 2.py +229 -0
  18. empathy_os/meta_workflows/workflow 2.py +980 -0
  19. empathy_os/models/token_estimator.py +16 -9
  20. empathy_os/models/validation.py +7 -1
  21. empathy_os/orchestration/pattern_learner 2.py +699 -0
  22. empathy_os/orchestration/real_tools 2.py +938 -0
  23. empathy_os/orchestration/real_tools.py +4 -2
  24. empathy_os/socratic/__init__ 2.py +273 -0
  25. empathy_os/socratic/ab_testing 2.py +969 -0
  26. empathy_os/socratic/blueprint 2.py +532 -0
  27. empathy_os/socratic/cli 2.py +689 -0
  28. empathy_os/socratic/collaboration 2.py +1112 -0
  29. empathy_os/socratic/domain_templates 2.py +916 -0
  30. empathy_os/socratic/embeddings 2.py +734 -0
  31. empathy_os/socratic/engine 2.py +729 -0
  32. empathy_os/socratic/explainer 2.py +663 -0
  33. empathy_os/socratic/feedback 2.py +767 -0
  34. empathy_os/socratic/forms 2.py +624 -0
  35. empathy_os/socratic/generator 2.py +716 -0
  36. empathy_os/socratic/llm_analyzer 2.py +635 -0
  37. empathy_os/socratic/mcp_server 2.py +751 -0
  38. empathy_os/socratic/session 2.py +306 -0
  39. empathy_os/socratic/storage 2.py +635 -0
  40. empathy_os/socratic/storage.py +2 -1
  41. empathy_os/socratic/success 2.py +719 -0
  42. empathy_os/socratic/visual_editor 2.py +812 -0
  43. empathy_os/socratic/web_ui 2.py +925 -0
  44. empathy_os/tier_recommender.py +5 -2
  45. empathy_os/workflow_commands.py +11 -6
  46. empathy_os/workflows/base.py +1 -1
  47. empathy_os/workflows/batch_processing 2.py +310 -0
  48. empathy_os/workflows/release_prep_crew 2.py +968 -0
  49. empathy_os/workflows/test_coverage_boost_crew 2.py +848 -0
  50. empathy_os/workflows/test_maintenance.py +3 -2
  51. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/entry_points.txt +0 -0
  52. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/licenses/LICENSE +0 -0
  53. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,716 @@
1
+ """Agent and Workflow Generator
2
+
3
+ Generates concrete agents and workflows from blueprints.
4
+
5
+ This module transforms abstract blueprints (from Socratic questioning)
6
+ into runnable agent instances and workflow configurations.
7
+
8
+ Copyright 2026 Smart-AI-Memory
9
+ Licensed under Fair Source License 0.9
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ from dataclasses import dataclass, field
16
+ from datetime import datetime
17
+ from typing import TYPE_CHECKING, Any
18
+
19
+ from .blueprint import (
20
+ AgentBlueprint,
21
+ AgentRole,
22
+ AgentSpec,
23
+ StageSpec,
24
+ ToolCategory,
25
+ ToolSpec,
26
+ WorkflowBlueprint,
27
+ )
28
+ from .success import SuccessCriteria
29
+
30
+ if TYPE_CHECKING:
31
+ from ..workflows.xml_enhanced_crew import XMLAgent
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ # =============================================================================
37
+ # AGENT TEMPLATES
38
+ # =============================================================================
39
+
40
+
41
+ @dataclass
42
+ class AgentTemplate:
43
+ """Template for generating specialized agents.
44
+
45
+ Templates provide pre-configured agent specifications that can be
46
+ customized based on Socratic questioning results.
47
+ """
48
+
49
+ id: str
50
+ name: str
51
+ role: AgentRole
52
+ base_goal: str
53
+ base_backstory: str
54
+ default_tools: list[str]
55
+ quality_focus: list[str]
56
+ languages: list[str] # Empty = all languages
57
+ model_tier: str = "capable"
58
+ custom_instructions: list[str] = field(default_factory=list)
59
+
60
+ def create_spec(
61
+ self,
62
+ customizations: dict[str, Any] | None = None,
63
+ ) -> AgentSpec:
64
+ """Create an AgentSpec from this template.
65
+
66
+ Args:
67
+ customizations: Override template defaults
68
+
69
+ Returns:
70
+ AgentSpec with customizations applied
71
+ """
72
+ customizations = customizations or {}
73
+
74
+ # Build goal with customizations
75
+ goal = customizations.get("goal", self.base_goal)
76
+ if "goal_suffix" in customizations:
77
+ goal = f"{goal} {customizations['goal_suffix']}"
78
+
79
+ # Build backstory with customizations
80
+ backstory = customizations.get("backstory", self.base_backstory)
81
+ if "expertise" in customizations:
82
+ backstory = f"{backstory} Specialized in: {', '.join(customizations['expertise'])}."
83
+
84
+ # Merge languages
85
+ languages = customizations.get("languages", self.languages)
86
+
87
+ # Merge quality focus
88
+ quality = list(self.quality_focus)
89
+ if "quality_focus" in customizations:
90
+ quality.extend(customizations["quality_focus"])
91
+ quality = list(dict.fromkeys(quality)) # Dedupe preserving order
92
+
93
+ # Build tools
94
+ tools = self._build_tools(customizations.get("tools", []))
95
+
96
+ return AgentSpec(
97
+ id=customizations.get("id", self.id),
98
+ name=customizations.get("name", self.name),
99
+ role=self.role,
100
+ goal=goal,
101
+ backstory=backstory,
102
+ tools=tools,
103
+ quality_focus=quality,
104
+ model_tier=customizations.get("model_tier", self.model_tier),
105
+ custom_instructions=self.custom_instructions + customizations.get("instructions", []),
106
+ languages=languages,
107
+ )
108
+
109
+ def _build_tools(self, additional_tools: list[str]) -> list[ToolSpec]:
110
+ """Build tool specifications."""
111
+ tools = []
112
+ all_tool_ids = list(dict.fromkeys(self.default_tools + additional_tools))
113
+
114
+ for tool_id in all_tool_ids:
115
+ tool_spec = TOOL_REGISTRY.get(tool_id)
116
+ if tool_spec:
117
+ tools.append(tool_spec)
118
+
119
+ return tools
120
+
121
+
122
+ # =============================================================================
123
+ # TOOL REGISTRY
124
+ # =============================================================================
125
+
126
+
127
+ TOOL_REGISTRY: dict[str, ToolSpec] = {
128
+ "grep_code": ToolSpec(
129
+ id="grep_code",
130
+ name="Code Search",
131
+ category=ToolCategory.CODE_SEARCH,
132
+ description="Search codebase for patterns using regex",
133
+ parameters={
134
+ "pattern": {"type": "string", "required": True},
135
+ "file_glob": {"type": "string", "required": False},
136
+ "case_sensitive": {"type": "boolean", "default": False},
137
+ },
138
+ ),
139
+ "read_file": ToolSpec(
140
+ id="read_file",
141
+ name="Read File",
142
+ category=ToolCategory.CODE_ANALYSIS,
143
+ description="Read file contents",
144
+ parameters={
145
+ "path": {"type": "string", "required": True},
146
+ "start_line": {"type": "integer", "required": False},
147
+ "end_line": {"type": "integer", "required": False},
148
+ },
149
+ ),
150
+ "analyze_ast": ToolSpec(
151
+ id="analyze_ast",
152
+ name="AST Analysis",
153
+ category=ToolCategory.CODE_ANALYSIS,
154
+ description="Parse and analyze code abstract syntax tree",
155
+ parameters={
156
+ "code": {"type": "string", "required": True},
157
+ "language": {"type": "string", "required": True},
158
+ },
159
+ ),
160
+ "security_scan": ToolSpec(
161
+ id="security_scan",
162
+ name="Security Scanner",
163
+ category=ToolCategory.SECURITY,
164
+ description="Run security vulnerability scanner",
165
+ parameters={
166
+ "path": {"type": "string", "required": True},
167
+ "rules": {"type": "array", "required": False},
168
+ },
169
+ cost_tier="moderate",
170
+ ),
171
+ "run_linter": ToolSpec(
172
+ id="run_linter",
173
+ name="Run Linter",
174
+ category=ToolCategory.LINTING,
175
+ description="Run code linter and return issues",
176
+ parameters={
177
+ "path": {"type": "string", "required": True},
178
+ "config": {"type": "string", "required": False},
179
+ },
180
+ ),
181
+ "run_tests": ToolSpec(
182
+ id="run_tests",
183
+ name="Run Tests",
184
+ category=ToolCategory.TESTING,
185
+ description="Execute test suite and return results",
186
+ parameters={
187
+ "path": {"type": "string", "required": False},
188
+ "coverage": {"type": "boolean", "default": True},
189
+ },
190
+ cost_tier="moderate",
191
+ ),
192
+ "edit_file": ToolSpec(
193
+ id="edit_file",
194
+ name="Edit File",
195
+ category=ToolCategory.CODE_MODIFICATION,
196
+ description="Make targeted edits to a file",
197
+ parameters={
198
+ "path": {"type": "string", "required": True},
199
+ "old_text": {"type": "string", "required": True},
200
+ "new_text": {"type": "string", "required": True},
201
+ },
202
+ is_mutating=True,
203
+ requires_confirmation=True,
204
+ ),
205
+ "query_patterns": ToolSpec(
206
+ id="query_patterns",
207
+ name="Query Pattern Library",
208
+ category=ToolCategory.KNOWLEDGE,
209
+ description="Search learned patterns for similar issues",
210
+ parameters={
211
+ "query": {"type": "string", "required": True},
212
+ "limit": {"type": "integer", "default": 5},
213
+ },
214
+ ),
215
+ "complexity_analysis": ToolSpec(
216
+ id="complexity_analysis",
217
+ name="Complexity Analysis",
218
+ category=ToolCategory.CODE_ANALYSIS,
219
+ description="Calculate code complexity metrics",
220
+ parameters={
221
+ "path": {"type": "string", "required": True},
222
+ },
223
+ ),
224
+ }
225
+
226
+
227
+ # =============================================================================
228
+ # AGENT TEMPLATE REGISTRY
229
+ # =============================================================================
230
+
231
+
232
+ AGENT_TEMPLATES: dict[str, AgentTemplate] = {
233
+ "security_reviewer": AgentTemplate(
234
+ id="security_reviewer",
235
+ name="Security Reviewer",
236
+ role=AgentRole.AUDITOR,
237
+ base_goal="Identify security vulnerabilities and recommend mitigations",
238
+ base_backstory=(
239
+ "Expert security analyst with deep knowledge of OWASP Top 10, "
240
+ "secure coding practices, and common vulnerability patterns."
241
+ ),
242
+ default_tools=["grep_code", "read_file", "security_scan", "query_patterns"],
243
+ quality_focus=["security"],
244
+ languages=[],
245
+ model_tier="capable",
246
+ custom_instructions=[
247
+ "Prioritize critical and high severity issues",
248
+ "Provide specific code locations for each finding",
249
+ "Include remediation recommendations",
250
+ ],
251
+ ),
252
+ "code_quality_reviewer": AgentTemplate(
253
+ id="code_quality_reviewer",
254
+ name="Code Quality Reviewer",
255
+ role=AgentRole.REVIEWER,
256
+ base_goal="Assess code quality, maintainability, and adherence to best practices",
257
+ base_backstory=(
258
+ "Experienced code reviewer with expertise in clean code principles, "
259
+ "design patterns, and maintainability best practices."
260
+ ),
261
+ default_tools=["grep_code", "read_file", "run_linter", "complexity_analysis"],
262
+ quality_focus=["maintainability", "reliability"],
263
+ languages=[],
264
+ model_tier="capable",
265
+ ),
266
+ "performance_analyzer": AgentTemplate(
267
+ id="performance_analyzer",
268
+ name="Performance Analyzer",
269
+ role=AgentRole.ANALYZER,
270
+ base_goal="Identify performance bottlenecks and optimization opportunities",
271
+ base_backstory=(
272
+ "Performance optimization specialist with expertise in algorithmic "
273
+ "complexity, memory management, and scalability patterns."
274
+ ),
275
+ default_tools=["grep_code", "read_file", "complexity_analysis", "analyze_ast"],
276
+ quality_focus=["performance"],
277
+ languages=[],
278
+ model_tier="capable",
279
+ ),
280
+ "test_generator": AgentTemplate(
281
+ id="test_generator",
282
+ name="Test Generator",
283
+ role=AgentRole.GENERATOR,
284
+ base_goal="Generate comprehensive test cases for untested code",
285
+ base_backstory=(
286
+ "Testing expert skilled in unit testing, integration testing, "
287
+ "and test-driven development methodologies."
288
+ ),
289
+ default_tools=["read_file", "analyze_ast", "run_tests", "edit_file"],
290
+ quality_focus=["testability", "reliability"],
291
+ languages=[],
292
+ model_tier="capable",
293
+ custom_instructions=[
294
+ "Generate both happy path and edge case tests",
295
+ "Follow the existing test patterns in the codebase",
296
+ "Ensure tests are deterministic and isolated",
297
+ ],
298
+ ),
299
+ "documentation_writer": AgentTemplate(
300
+ id="documentation_writer",
301
+ name="Documentation Writer",
302
+ role=AgentRole.GENERATOR,
303
+ base_goal="Generate clear, comprehensive documentation",
304
+ base_backstory=(
305
+ "Technical writer with expertise in API documentation, "
306
+ "code comments, and developer guides."
307
+ ),
308
+ default_tools=["read_file", "analyze_ast", "grep_code"],
309
+ quality_focus=["maintainability"],
310
+ languages=[],
311
+ model_tier="cheap",
312
+ ),
313
+ "style_enforcer": AgentTemplate(
314
+ id="style_enforcer",
315
+ name="Style Enforcer",
316
+ role=AgentRole.REVIEWER,
317
+ base_goal="Ensure code follows team style guidelines",
318
+ base_backstory=(
319
+ "Code style expert with knowledge of language-specific "
320
+ "conventions and formatting standards."
321
+ ),
322
+ default_tools=["run_linter", "read_file"],
323
+ quality_focus=["maintainability"],
324
+ languages=[],
325
+ model_tier="cheap",
326
+ ),
327
+ "result_synthesizer": AgentTemplate(
328
+ id="result_synthesizer",
329
+ name="Result Synthesizer",
330
+ role=AgentRole.REPORTER,
331
+ base_goal="Synthesize findings into clear, actionable reports",
332
+ base_backstory=(
333
+ "Technical communicator skilled at translating complex "
334
+ "findings into understandable recommendations."
335
+ ),
336
+ default_tools=["query_patterns"],
337
+ quality_focus=[],
338
+ languages=[],
339
+ model_tier="cheap",
340
+ ),
341
+ }
342
+
343
+
344
+ # =============================================================================
345
+ # AGENT GENERATOR
346
+ # =============================================================================
347
+
348
+
349
+ class AgentGenerator:
350
+ """Generates agents and workflows from blueprints.
351
+
352
+ Example:
353
+ >>> generator = AgentGenerator()
354
+ >>>
355
+ >>> # Generate from blueprint
356
+ >>> blueprint = WorkflowBlueprint(...)
357
+ >>> workflow = generator.generate_workflow(blueprint)
358
+ >>>
359
+ >>> # Generate from template
360
+ >>> agent = generator.generate_agent_from_template(
361
+ ... "security_reviewer",
362
+ ... customizations={"languages": ["python"]}
363
+ ... )
364
+ """
365
+
366
+ def __init__(self):
367
+ """Initialize the generator."""
368
+ self.templates = AGENT_TEMPLATES.copy()
369
+ self.tools = TOOL_REGISTRY.copy()
370
+
371
+ def register_template(self, template: AgentTemplate) -> None:
372
+ """Register a custom agent template."""
373
+ self.templates[template.id] = template
374
+
375
+ def register_tool(self, tool: ToolSpec) -> None:
376
+ """Register a custom tool."""
377
+ self.tools[tool.id] = tool
378
+
379
+ def generate_agent_from_template(
380
+ self,
381
+ template_id: str,
382
+ customizations: dict[str, Any] | None = None,
383
+ ) -> AgentBlueprint:
384
+ """Generate an agent blueprint from a template.
385
+
386
+ Args:
387
+ template_id: ID of the template to use
388
+ customizations: Override template defaults
389
+
390
+ Returns:
391
+ AgentBlueprint ready for instantiation
392
+
393
+ Raises:
394
+ ValueError: If template not found
395
+ """
396
+ template = self.templates.get(template_id)
397
+ if not template:
398
+ raise ValueError(f"Unknown template: {template_id}")
399
+
400
+ spec = template.create_spec(customizations)
401
+
402
+ return AgentBlueprint(
403
+ spec=spec,
404
+ generated_from="template",
405
+ template_id=template_id,
406
+ customizations=customizations or {},
407
+ )
408
+
409
+ def generate_agents_for_requirements(
410
+ self,
411
+ requirements: dict[str, Any],
412
+ ) -> list[AgentBlueprint]:
413
+ """Generate appropriate agents based on requirements.
414
+
415
+ This is the core intelligent generation that maps requirements
416
+ (from Socratic questioning) to agent configurations.
417
+
418
+ Args:
419
+ requirements: Requirements gathered from Socratic session
420
+ - quality_focus: list of quality attributes
421
+ - languages: list of programming languages
422
+ - automation_level: advisory/semi_auto/fully_auto
423
+ - domain: domain (e.g., "code_review", "testing")
424
+
425
+ Returns:
426
+ List of AgentBlueprints for a complete team
427
+ """
428
+ agents: list[AgentBlueprint] = []
429
+ quality_focus = requirements.get("quality_focus", [])
430
+ languages = requirements.get("languages", [])
431
+ automation = requirements.get("automation_level", "semi_auto")
432
+
433
+ # Map quality focus to agent templates
434
+ quality_to_templates = {
435
+ "security": ["security_reviewer"],
436
+ "performance": ["performance_analyzer"],
437
+ "maintainability": ["code_quality_reviewer", "documentation_writer"],
438
+ "reliability": ["code_quality_reviewer", "test_generator"],
439
+ "testability": ["test_generator"],
440
+ }
441
+
442
+ # Collect needed templates
443
+ needed_templates: set[str] = set()
444
+ for quality in quality_focus:
445
+ templates = quality_to_templates.get(quality, [])
446
+ needed_templates.update(templates)
447
+
448
+ # Default to basic code review if no specific focus
449
+ if not needed_templates:
450
+ needed_templates.add("code_quality_reviewer")
451
+
452
+ # Add synthesizer for results aggregation
453
+ if len(needed_templates) > 1:
454
+ needed_templates.add("result_synthesizer")
455
+
456
+ # Generate agent for each template
457
+ for template_id in needed_templates:
458
+ customizations = {
459
+ "languages": languages,
460
+ "quality_focus": quality_focus,
461
+ }
462
+
463
+ # Adjust for automation level
464
+ if automation == "fully_auto":
465
+ customizations["instructions"] = [
466
+ "Apply fixes automatically where safe",
467
+ "Minimize human review requirements",
468
+ ]
469
+ elif automation == "advisory":
470
+ customizations["instructions"] = [
471
+ "Provide recommendations only",
472
+ "Do not modify any files",
473
+ ]
474
+
475
+ try:
476
+ agent = self.generate_agent_from_template(template_id, customizations)
477
+ agents.append(agent)
478
+ except ValueError:
479
+ logger.warning(f"Template not found: {template_id}")
480
+
481
+ return agents
482
+
483
+ def generate_workflow(
484
+ self,
485
+ blueprint: WorkflowBlueprint,
486
+ ) -> GeneratedWorkflow:
487
+ """Generate a complete workflow from a blueprint.
488
+
489
+ Args:
490
+ blueprint: The workflow blueprint to generate from
491
+
492
+ Returns:
493
+ GeneratedWorkflow ready for execution
494
+ """
495
+ # Validate blueprint
496
+ is_valid, errors = blueprint.validate()
497
+ if not is_valid:
498
+ raise ValueError(f"Invalid blueprint: {'; '.join(errors)}")
499
+
500
+ # Generate XML agents from blueprints
501
+ xml_agents = []
502
+ for agent_bp in blueprint.agents:
503
+ xml_agent = self._create_xml_agent(agent_bp.spec)
504
+ xml_agents.append(xml_agent)
505
+
506
+ # Build stage configuration
507
+ stages_config = []
508
+ for stage in blueprint.stages:
509
+ stages_config.append({
510
+ "id": stage.id,
511
+ "name": stage.name,
512
+ "agents": stage.agent_ids,
513
+ "parallel": stage.parallel,
514
+ "depends_on": stage.depends_on,
515
+ "timeout": stage.timeout,
516
+ })
517
+
518
+ return GeneratedWorkflow(
519
+ blueprint=blueprint,
520
+ agents=xml_agents,
521
+ stages=stages_config,
522
+ generated_at=datetime.now().isoformat(),
523
+ )
524
+
525
+ def _create_xml_agent(self, spec: AgentSpec) -> XMLAgent:
526
+ """Create an XMLAgent from a spec."""
527
+ from ..workflows.xml_enhanced_crew import XMLAgent
528
+
529
+ return XMLAgent(
530
+ role=spec.name,
531
+ goal=spec.goal,
532
+ backstory=spec.backstory,
533
+ expertise_level="expert" if spec.model_tier != "cheap" else "competent",
534
+ custom_instructions=spec.custom_instructions,
535
+ )
536
+
537
+ def create_workflow_blueprint(
538
+ self,
539
+ name: str,
540
+ description: str,
541
+ agents: list[AgentBlueprint],
542
+ quality_focus: list[str],
543
+ automation_level: str,
544
+ success_criteria: SuccessCriteria | None = None,
545
+ ) -> WorkflowBlueprint:
546
+ """Create a workflow blueprint with automatic staging.
547
+
548
+ Args:
549
+ name: Workflow name
550
+ description: Workflow description
551
+ agents: Agent blueprints to include
552
+ quality_focus: Quality attributes to optimize for
553
+ automation_level: Level of automation
554
+ success_criteria: Optional success criteria
555
+
556
+ Returns:
557
+ Complete WorkflowBlueprint
558
+ """
559
+ # Group agents by role for staging
560
+ analyzers = [a for a in agents if a.spec.role in (AgentRole.ANALYZER, AgentRole.REVIEWER, AgentRole.AUDITOR)]
561
+ generators = [a for a in agents if a.spec.role == AgentRole.GENERATOR]
562
+ reporters = [a for a in agents if a.spec.role == AgentRole.REPORTER]
563
+
564
+ stages = []
565
+
566
+ # Stage 1: Analysis (parallel)
567
+ if analyzers:
568
+ stages.append(StageSpec(
569
+ id="analysis",
570
+ name="Analysis",
571
+ description="Analyze code and identify issues",
572
+ agent_ids=[a.spec.id for a in analyzers],
573
+ parallel=True,
574
+ output_aggregation="merge",
575
+ ))
576
+
577
+ # Stage 2: Generation (sequential, depends on analysis)
578
+ if generators:
579
+ stages.append(StageSpec(
580
+ id="generation",
581
+ name="Generation",
582
+ description="Generate fixes and improvements",
583
+ agent_ids=[a.spec.id for a in generators],
584
+ parallel=False,
585
+ depends_on=["analysis"] if analyzers else [],
586
+ ))
587
+
588
+ # Stage 3: Synthesis (always last)
589
+ if reporters:
590
+ depends = []
591
+ if analyzers:
592
+ depends.append("analysis")
593
+ if generators:
594
+ depends.append("generation")
595
+
596
+ stages.append(StageSpec(
597
+ id="synthesis",
598
+ name="Synthesis",
599
+ description="Synthesize findings into report",
600
+ agent_ids=[a.spec.id for a in reporters],
601
+ parallel=False,
602
+ depends_on=depends,
603
+ ))
604
+
605
+ return WorkflowBlueprint(
606
+ name=name,
607
+ description=description,
608
+ agents=agents,
609
+ stages=stages,
610
+ quality_focus=quality_focus,
611
+ automation_level=automation_level,
612
+ success_criteria=success_criteria,
613
+ generated_at=datetime.now().isoformat(),
614
+ )
615
+
616
+
617
+ @dataclass
618
+ class GeneratedWorkflow:
619
+ """A generated, runnable workflow.
620
+
621
+ Contains all the components needed to execute the workflow.
622
+ """
623
+
624
+ # Source blueprint
625
+ blueprint: WorkflowBlueprint
626
+
627
+ # Generated XMLAgent instances
628
+ agents: list[Any] # XMLAgent
629
+
630
+ # Stage configuration
631
+ stages: list[dict[str, Any]]
632
+
633
+ # Generation timestamp
634
+ generated_at: str = ""
635
+
636
+ # Whether workflow has been validated
637
+ validated: bool = False
638
+
639
+ async def execute(
640
+ self,
641
+ input_data: dict[str, Any],
642
+ progress_callback: Any = None,
643
+ ) -> dict[str, Any]:
644
+ """Execute the workflow.
645
+
646
+ Args:
647
+ input_data: Input data for the workflow
648
+ progress_callback: Optional progress callback
649
+
650
+ Returns:
651
+ Workflow results
652
+ """
653
+ # This is a simplified execution - real implementation would
654
+ # integrate with BaseWorkflow
655
+ results: dict[str, Any] = {
656
+ "stages": {},
657
+ "agents": {},
658
+ "final_output": None,
659
+ "success": False,
660
+ }
661
+
662
+ for stage_config in self.stages:
663
+ stage_id = stage_config["id"]
664
+ agent_ids = stage_config["agents"]
665
+
666
+ stage_results = []
667
+ for agent_id in agent_ids:
668
+ # Find agent
669
+ agent = next(
670
+ (a for a in self.agents if hasattr(a, "role") and self._match_agent(a, agent_id)),
671
+ None
672
+ )
673
+ if agent:
674
+ # Execute agent (simplified)
675
+ result = {
676
+ "agent_id": agent_id,
677
+ "status": "completed",
678
+ "output": f"Agent {agent_id} completed",
679
+ }
680
+ stage_results.append(result)
681
+
682
+ results["stages"][stage_id] = stage_results
683
+
684
+ results["success"] = True
685
+ results["final_output"] = results["stages"]
686
+
687
+ return results
688
+
689
+ def _match_agent(self, agent: Any, agent_id: str) -> bool:
690
+ """Check if an agent matches an ID."""
691
+ # Match by role name (simplified)
692
+ if hasattr(agent, "role"):
693
+ return agent_id.replace("_", " ").lower() in agent.role.lower()
694
+ return False
695
+
696
+ def describe(self) -> str:
697
+ """Get human-readable description of the workflow."""
698
+ lines = [
699
+ f"Workflow: {self.blueprint.name}",
700
+ f"Description: {self.blueprint.description}",
701
+ "",
702
+ f"Agents ({len(self.agents)}):",
703
+ ]
704
+
705
+ for agent in self.agents:
706
+ if hasattr(agent, "role"):
707
+ lines.append(f" - {agent.role}: {getattr(agent, 'goal', 'N/A')}")
708
+
709
+ lines.append("")
710
+ lines.append(f"Stages ({len(self.stages)}):")
711
+
712
+ for stage in self.stages:
713
+ parallel = "parallel" if stage.get("parallel") else "sequential"
714
+ lines.append(f" - {stage['name']} ({parallel}): {', '.join(stage['agents'])}")
715
+
716
+ return "\n".join(lines)