attune-ai 2.1.5__py3-none-any.whl → 2.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. attune/cli/__init__.py +3 -59
  2. attune/cli/commands/batch.py +4 -12
  3. attune/cli/commands/cache.py +8 -16
  4. attune/cli/commands/provider.py +17 -0
  5. attune/cli/commands/routing.py +3 -1
  6. attune/cli/commands/setup.py +122 -0
  7. attune/cli/commands/tier.py +1 -3
  8. attune/cli/commands/workflow.py +31 -0
  9. attune/cli/parsers/cache.py +1 -0
  10. attune/cli/parsers/help.py +1 -3
  11. attune/cli/parsers/provider.py +7 -0
  12. attune/cli/parsers/routing.py +1 -3
  13. attune/cli/parsers/setup.py +7 -0
  14. attune/cli/parsers/status.py +1 -3
  15. attune/cli/parsers/tier.py +1 -3
  16. attune/cli_minimal.py +9 -3
  17. attune/cli_router.py +9 -7
  18. attune/cli_unified.py +3 -0
  19. attune/dashboard/app.py +3 -1
  20. attune/dashboard/simple_server.py +3 -1
  21. attune/dashboard/standalone_server.py +7 -3
  22. attune/mcp/server.py +54 -102
  23. attune/memory/long_term.py +0 -2
  24. attune/memory/short_term/__init__.py +84 -0
  25. attune/memory/short_term/base.py +465 -0
  26. attune/memory/short_term/batch.py +219 -0
  27. attune/memory/short_term/caching.py +227 -0
  28. attune/memory/short_term/conflicts.py +265 -0
  29. attune/memory/short_term/cross_session.py +122 -0
  30. attune/memory/short_term/facade.py +653 -0
  31. attune/memory/short_term/pagination.py +207 -0
  32. attune/memory/short_term/patterns.py +271 -0
  33. attune/memory/short_term/pubsub.py +286 -0
  34. attune/memory/short_term/queues.py +244 -0
  35. attune/memory/short_term/security.py +300 -0
  36. attune/memory/short_term/sessions.py +250 -0
  37. attune/memory/short_term/streams.py +242 -0
  38. attune/memory/short_term/timelines.py +234 -0
  39. attune/memory/short_term/transactions.py +184 -0
  40. attune/memory/short_term/working.py +252 -0
  41. attune/meta_workflows/cli_commands/__init__.py +3 -0
  42. attune/meta_workflows/cli_commands/agent_commands.py +0 -4
  43. attune/meta_workflows/cli_commands/analytics_commands.py +0 -6
  44. attune/meta_workflows/cli_commands/config_commands.py +0 -5
  45. attune/meta_workflows/cli_commands/memory_commands.py +0 -5
  46. attune/meta_workflows/cli_commands/template_commands.py +0 -5
  47. attune/meta_workflows/cli_commands/workflow_commands.py +0 -6
  48. attune/meta_workflows/plan_generator.py +2 -4
  49. attune/models/adaptive_routing.py +4 -8
  50. attune/models/auth_cli.py +3 -9
  51. attune/models/auth_strategy.py +2 -4
  52. attune/models/telemetry/analytics.py +0 -2
  53. attune/models/telemetry/backend.py +0 -3
  54. attune/models/telemetry/storage.py +0 -2
  55. attune/monitoring/alerts.py +6 -10
  56. attune/orchestration/_strategies/__init__.py +156 -0
  57. attune/orchestration/_strategies/base.py +227 -0
  58. attune/orchestration/_strategies/conditional_strategies.py +365 -0
  59. attune/orchestration/_strategies/conditions.py +369 -0
  60. attune/orchestration/_strategies/core_strategies.py +479 -0
  61. attune/orchestration/_strategies/data_classes.py +64 -0
  62. attune/orchestration/_strategies/nesting.py +233 -0
  63. attune/orchestration/execution_strategies.py +58 -1567
  64. attune/orchestration/meta_orchestrator.py +1 -3
  65. attune/project_index/scanner.py +1 -3
  66. attune/project_index/scanner_parallel.py +7 -5
  67. attune/socratic/storage.py +2 -4
  68. attune/socratic_router.py +1 -3
  69. attune/telemetry/agent_coordination.py +9 -3
  70. attune/telemetry/agent_tracking.py +16 -3
  71. attune/telemetry/approval_gates.py +22 -5
  72. attune/telemetry/cli.py +1 -3
  73. attune/telemetry/commands/dashboard_commands.py +24 -8
  74. attune/telemetry/event_streaming.py +8 -2
  75. attune/telemetry/feedback_loop.py +10 -2
  76. attune/tools.py +2 -1
  77. attune/workflow_commands.py +1 -3
  78. attune/workflow_patterns/structural.py +4 -8
  79. attune/workflows/__init__.py +54 -10
  80. attune/workflows/autonomous_test_gen.py +158 -102
  81. attune/workflows/base.py +48 -672
  82. attune/workflows/batch_processing.py +1 -3
  83. attune/workflows/compat.py +156 -0
  84. attune/workflows/cost_mixin.py +141 -0
  85. attune/workflows/data_classes.py +92 -0
  86. attune/workflows/document_gen/workflow.py +11 -14
  87. attune/workflows/history.py +16 -9
  88. attune/workflows/llm_base.py +1 -3
  89. attune/workflows/migration.py +432 -0
  90. attune/workflows/output.py +2 -7
  91. attune/workflows/parsing_mixin.py +427 -0
  92. attune/workflows/perf_audit.py +3 -1
  93. attune/workflows/progress.py +9 -11
  94. attune/workflows/release_prep.py +5 -1
  95. attune/workflows/routing.py +0 -2
  96. attune/workflows/secure_release.py +4 -1
  97. attune/workflows/security_audit.py +20 -14
  98. attune/workflows/security_audit_phase3.py +28 -22
  99. attune/workflows/seo_optimization.py +27 -27
  100. attune/workflows/test_gen/test_templates.py +1 -4
  101. attune/workflows/test_gen/workflow.py +0 -2
  102. attune/workflows/test_gen_behavioral.py +6 -19
  103. attune/workflows/test_gen_parallel.py +8 -6
  104. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/METADATA +4 -3
  105. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/RECORD +121 -96
  106. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/entry_points.txt +0 -2
  107. attune_healthcare/monitors/monitoring/__init__.py +9 -9
  108. attune_llm/agent_factory/__init__.py +6 -6
  109. attune_llm/agent_factory/adapters/haystack_adapter.py +1 -4
  110. attune_llm/commands/__init__.py +10 -10
  111. attune_llm/commands/models.py +3 -3
  112. attune_llm/config/__init__.py +8 -8
  113. attune_llm/learning/__init__.py +3 -3
  114. attune_llm/learning/extractor.py +5 -3
  115. attune_llm/learning/storage.py +5 -3
  116. attune_llm/security/__init__.py +17 -17
  117. attune_llm/utils/tokens.py +3 -1
  118. attune/cli_legacy.py +0 -3978
  119. attune/memory/short_term.py +0 -2192
  120. attune/workflows/manage_docs.py +0 -87
  121. attune/workflows/test5.py +0 -125
  122. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/WHEEL +0 -0
  123. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/licenses/LICENSE +0 -0
  124. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +0 -0
  125. {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,479 @@
1
+ """Core execution strategies for agent composition.
2
+
3
+ This module contains the 6 foundational strategies for composing agents:
4
+ 1. Sequential (A → B → C)
5
+ 2. Parallel (A || B || C)
6
+ 3. Debate (A ⇄ B ⇄ C → Synthesis)
7
+ 4. Teaching (Junior → Expert validation)
8
+ 5. Refinement (Draft → Review → Polish)
9
+ 6. Adaptive (Classifier → Specialist)
10
+
11
+ Security:
12
+ - All agent outputs validated before passing to next agent
13
+ - No eval() or exec() usage
14
+ - Timeout enforcement at strategy level
15
+
16
+ Copyright 2025 Smart-AI-Memory
17
+ Licensed under Fair Source License 0.9
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import asyncio
23
+ import logging
24
+ from typing import TYPE_CHECKING, Any
25
+
26
+ from .base import ExecutionStrategy
27
+ from .data_classes import AgentResult, StrategyResult
28
+
29
+ if TYPE_CHECKING:
30
+ from ..agent_templates import AgentTemplate
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ class SequentialStrategy(ExecutionStrategy):
36
+ """Sequential composition (A → B → C).
37
+
38
+ Executes agents one after another, passing results forward.
39
+ Each agent receives output from previous agent in context.
40
+
41
+ Use when:
42
+ - Tasks must be done in order
43
+ - Each step depends on previous results
44
+ - Pipeline processing needed
45
+
46
+ Example:
47
+ Coverage Analyzer → Test Generator → Quality Validator
48
+ """
49
+
50
+ async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
51
+ """Execute agents sequentially.
52
+
53
+ Args:
54
+ agents: List of agents to execute in order
55
+ context: Initial context
56
+
57
+ Returns:
58
+ StrategyResult with sequential execution results
59
+ """
60
+ if not agents:
61
+ raise ValueError("agents list cannot be empty")
62
+
63
+ logger.info(f"Sequential execution of {len(agents)} agents")
64
+
65
+ results: list[AgentResult] = []
66
+ current_context = context.copy()
67
+ total_duration = 0.0
68
+
69
+ for agent in agents:
70
+ try:
71
+ result = await self._execute_agent(agent, current_context)
72
+ results.append(result)
73
+ total_duration += result.duration_seconds
74
+
75
+ # Pass output to next agent's context
76
+ if result.success:
77
+ current_context[f"{agent.id}_output"] = result.output
78
+ else:
79
+ logger.error(f"Agent {agent.id} failed: {result.error}")
80
+ # Continue or stop based on error handling policy
81
+ # For now: continue to next agent
82
+
83
+ except Exception as e:
84
+ logger.exception(f"Error executing agent {agent.id}: {e}")
85
+ results.append(
86
+ AgentResult(
87
+ agent_id=agent.id,
88
+ success=False,
89
+ output={},
90
+ error=str(e),
91
+ )
92
+ )
93
+
94
+ return StrategyResult(
95
+ success=all(r.success for r in results),
96
+ outputs=results,
97
+ aggregated_output=self._aggregate_results(results),
98
+ total_duration=total_duration,
99
+ errors=[r.error for r in results if not r.success],
100
+ )
101
+
102
+
103
+ class ParallelStrategy(ExecutionStrategy):
104
+ """Parallel composition (A || B || C).
105
+
106
+ Executes all agents simultaneously, aggregates results.
107
+ Each agent receives same initial context.
108
+
109
+ Use when:
110
+ - Independent validations needed
111
+ - Multi-perspective review desired
112
+ - Time optimization important
113
+
114
+ Example:
115
+ Security Audit || Performance Check || Code Quality || Docs Check
116
+ """
117
+
118
+ async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
119
+ """Execute agents in parallel.
120
+
121
+ Args:
122
+ agents: List of agents to execute concurrently
123
+ context: Initial context for all agents
124
+
125
+ Returns:
126
+ StrategyResult with parallel execution results
127
+ """
128
+ if not agents:
129
+ raise ValueError("agents list cannot be empty")
130
+
131
+ logger.info(f"Parallel execution of {len(agents)} agents")
132
+
133
+ # Execute all agents concurrently
134
+ tasks = [self._execute_agent(agent, context) for agent in agents]
135
+
136
+ try:
137
+ results = await asyncio.gather(*tasks, return_exceptions=True)
138
+ except Exception as e:
139
+ logger.exception(f"Error in parallel execution: {e}")
140
+ raise
141
+
142
+ # Process results (handle exceptions)
143
+ processed_results: list[AgentResult] = []
144
+ for i, result in enumerate(results):
145
+ if isinstance(result, Exception):
146
+ logger.error(f"Agent {agents[i].id} raised exception: {result}")
147
+ processed_results.append(
148
+ AgentResult(
149
+ agent_id=agents[i].id,
150
+ success=False,
151
+ output={},
152
+ error=str(result),
153
+ )
154
+ )
155
+ else:
156
+ # Type checker doesn't know we already filtered out exceptions
157
+ assert isinstance(result, AgentResult)
158
+ processed_results.append(result)
159
+
160
+ total_duration = max((r.duration_seconds for r in processed_results), default=0.0)
161
+
162
+ return StrategyResult(
163
+ success=all(r.success for r in processed_results),
164
+ outputs=processed_results,
165
+ aggregated_output=self._aggregate_results(processed_results),
166
+ total_duration=total_duration,
167
+ errors=[r.error for r in processed_results if not r.success],
168
+ )
169
+
170
+
171
+ class DebateStrategy(ExecutionStrategy):
172
+ """Debate/Consensus composition (A ⇄ B ⇄ C → Synthesis).
173
+
174
+ Agents provide independent opinions, then a synthesizer
175
+ aggregates and resolves conflicts.
176
+
177
+ Use when:
178
+ - Multiple expert opinions needed
179
+ - Architecture decisions require debate
180
+ - Tradeoff analysis needed
181
+
182
+ Example:
183
+ Architect(scale) || Architect(cost) || Architect(simplicity) → Synthesizer
184
+ """
185
+
186
+ async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
187
+ """Execute debate pattern.
188
+
189
+ Args:
190
+ agents: List of agents to debate (recommend 2-4)
191
+ context: Initial context
192
+
193
+ Returns:
194
+ StrategyResult with synthesized consensus
195
+ """
196
+ if not agents:
197
+ raise ValueError("agents list cannot be empty")
198
+
199
+ if len(agents) < 2:
200
+ logger.warning("Debate pattern works best with 2+ agents")
201
+
202
+ logger.info(f"Debate execution with {len(agents)} agents")
203
+
204
+ # Phase 1: Parallel execution for independent opinions
205
+ parallel_strategy = ParallelStrategy()
206
+ phase1_result = await parallel_strategy.execute(agents, context)
207
+
208
+ # Phase 2: Synthesis (simplified - no actual synthesizer agent)
209
+ # In production: would use dedicated synthesizer agent
210
+ synthesis = {
211
+ "debate_participants": [r.agent_id for r in phase1_result.outputs],
212
+ "opinions": [r.output for r in phase1_result.outputs],
213
+ "consensus": self._synthesize_opinions(phase1_result.outputs),
214
+ }
215
+
216
+ return StrategyResult(
217
+ success=phase1_result.success,
218
+ outputs=phase1_result.outputs,
219
+ aggregated_output=synthesis,
220
+ total_duration=phase1_result.total_duration,
221
+ errors=phase1_result.errors,
222
+ )
223
+
224
+ def _synthesize_opinions(self, results: list[AgentResult]) -> dict[str, Any]:
225
+ """Synthesize multiple agent opinions into consensus.
226
+
227
+ Args:
228
+ results: Agent results to synthesize
229
+
230
+ Returns:
231
+ Synthesized consensus
232
+ """
233
+ # Simplified synthesis: majority vote on success
234
+ success_votes = sum(1 for r in results if r.success)
235
+ consensus_reached = success_votes > len(results) / 2
236
+
237
+ return {
238
+ "consensus_reached": consensus_reached,
239
+ "success_votes": success_votes,
240
+ "total_votes": len(results),
241
+ "avg_confidence": (
242
+ sum(r.confidence for r in results) / len(results) if results else 0.0
243
+ ),
244
+ }
245
+
246
+
247
+ class TeachingStrategy(ExecutionStrategy):
248
+ """Teaching/Validation (Junior → Expert Review).
249
+
250
+ Junior agent attempts task (cheap tier), expert validates.
251
+ If validation fails, expert takes over.
252
+
253
+ Use when:
254
+ - Cost-effective generation desired
255
+ - Quality assurance critical
256
+ - Simple tasks with review needed
257
+
258
+ Example:
259
+ Junior Writer(CHEAP) → Quality Gate → (pass ? done : Expert Review(CAPABLE))
260
+ """
261
+
262
+ def __init__(self, quality_threshold: float = 0.7):
263
+ """Initialize teaching strategy.
264
+
265
+ Args:
266
+ quality_threshold: Minimum confidence for junior to pass (0-1)
267
+ """
268
+ self.quality_threshold = quality_threshold
269
+
270
+ async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
271
+ """Execute teaching pattern.
272
+
273
+ Args:
274
+ agents: [junior_agent, expert_agent] (exactly 2)
275
+ context: Initial context
276
+
277
+ Returns:
278
+ StrategyResult with teaching outcome
279
+ """
280
+ if len(agents) != 2:
281
+ raise ValueError("Teaching strategy requires exactly 2 agents")
282
+
283
+ junior, expert = agents
284
+ logger.info(f"Teaching: {junior.id} → {expert.id} validation")
285
+
286
+ results: list[AgentResult] = []
287
+ total_duration = 0.0
288
+
289
+ # Phase 1: Junior attempt
290
+ junior_result = await self._execute_agent(junior, context)
291
+ results.append(junior_result)
292
+ total_duration += junior_result.duration_seconds
293
+
294
+ # Phase 2: Quality gate
295
+ if junior_result.success and junior_result.confidence >= self.quality_threshold:
296
+ logger.info(f"Junior passed quality gate (confidence={junior_result.confidence:.2f})")
297
+ aggregated = {"outcome": "junior_success", "junior_output": junior_result.output}
298
+ else:
299
+ logger.info(
300
+ f"Junior failed quality gate, expert taking over "
301
+ f"(confidence={junior_result.confidence:.2f})"
302
+ )
303
+
304
+ # Phase 3: Expert takeover
305
+ expert_context = context.copy()
306
+ expert_context["junior_attempt"] = junior_result.output
307
+ expert_result = await self._execute_agent(expert, expert_context)
308
+ results.append(expert_result)
309
+ total_duration += expert_result.duration_seconds
310
+
311
+ aggregated = {
312
+ "outcome": "expert_takeover",
313
+ "junior_output": junior_result.output,
314
+ "expert_output": expert_result.output,
315
+ }
316
+
317
+ return StrategyResult(
318
+ success=all(r.success for r in results),
319
+ outputs=results,
320
+ aggregated_output=aggregated,
321
+ total_duration=total_duration,
322
+ errors=[r.error for r in results if not r.success],
323
+ )
324
+
325
+
326
+ class RefinementStrategy(ExecutionStrategy):
327
+ """Progressive Refinement (Draft → Review → Polish).
328
+
329
+ Iterative improvement through multiple quality levels.
330
+ Each agent refines output from previous stage.
331
+
332
+ Use when:
333
+ - Iterative improvement needed
334
+ - Quality ladder desired
335
+ - Multi-stage refinement beneficial
336
+
337
+ Example:
338
+ Drafter(CHEAP) → Reviewer(CAPABLE) → Polisher(PREMIUM)
339
+ """
340
+
341
+ async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
342
+ """Execute refinement pattern.
343
+
344
+ Args:
345
+ agents: [drafter, reviewer, polisher] (3+ agents)
346
+ context: Initial context
347
+
348
+ Returns:
349
+ StrategyResult with refined output
350
+ """
351
+ if len(agents) < 2:
352
+ raise ValueError("Refinement strategy requires at least 2 agents")
353
+
354
+ logger.info(f"Refinement with {len(agents)} stages")
355
+
356
+ results: list[AgentResult] = []
357
+ current_context = context.copy()
358
+ total_duration = 0.0
359
+
360
+ for i, agent in enumerate(agents):
361
+ stage_name = f"stage_{i + 1}"
362
+ logger.info(f"Refinement {stage_name}: {agent.id}")
363
+
364
+ result = await self._execute_agent(agent, current_context)
365
+ results.append(result)
366
+ total_duration += result.duration_seconds
367
+
368
+ if result.success:
369
+ # Pass refined output to next stage
370
+ current_context[f"{stage_name}_output"] = result.output
371
+ current_context["previous_output"] = result.output
372
+ else:
373
+ logger.error(f"Refinement stage {i + 1} failed: {result.error}")
374
+ break # Stop refinement on failure
375
+
376
+ # Final output is from last successful stage
377
+ final_output = results[-1].output if results[-1].success else {}
378
+
379
+ return StrategyResult(
380
+ success=all(r.success for r in results),
381
+ outputs=results,
382
+ aggregated_output={
383
+ "refinement_stages": len(results),
384
+ "final_output": final_output,
385
+ "stage_outputs": [r.output for r in results],
386
+ },
387
+ total_duration=total_duration,
388
+ errors=[r.error for r in results if not r.success],
389
+ )
390
+
391
+
392
+ class AdaptiveStrategy(ExecutionStrategy):
393
+ """Adaptive Routing (Classifier → Specialist).
394
+
395
+ Classifier assesses task complexity, routes to appropriate specialist.
396
+ Right-sizing: match agent tier to task needs.
397
+
398
+ Use when:
399
+ - Variable task complexity
400
+ - Cost optimization desired
401
+ - Right-sizing important
402
+
403
+ Example:
404
+ Classifier(CHEAP) → route(simple|moderate|complex) → Specialist(tier)
405
+ """
406
+
407
+ async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
408
+ """Execute adaptive routing pattern.
409
+
410
+ Args:
411
+ agents: [classifier, *specialists] (2+ agents)
412
+ context: Initial context
413
+
414
+ Returns:
415
+ StrategyResult with routed execution
416
+ """
417
+ if len(agents) < 2:
418
+ raise ValueError("Adaptive strategy requires at least 2 agents")
419
+
420
+ classifier = agents[0]
421
+ specialists = agents[1:]
422
+
423
+ logger.info(f"Adaptive: {classifier.id} → {len(specialists)} specialists")
424
+
425
+ results: list[AgentResult] = []
426
+ total_duration = 0.0
427
+
428
+ # Phase 1: Classification
429
+ classifier_result = await self._execute_agent(classifier, context)
430
+ results.append(classifier_result)
431
+ total_duration += classifier_result.duration_seconds
432
+
433
+ if not classifier_result.success:
434
+ logger.error("Classifier failed, defaulting to first specialist")
435
+ selected_specialist = specialists[0]
436
+ else:
437
+ # Phase 2: Route to specialist based on classification
438
+ # Simplified: select based on confidence score
439
+ if classifier_result.confidence > 0.8:
440
+ # High confidence → simple task → cheap specialist
441
+ selected_specialist = min(
442
+ specialists,
443
+ key=lambda s: {
444
+ "CHEAP": 0,
445
+ "CAPABLE": 1,
446
+ "PREMIUM": 2,
447
+ }.get(s.tier_preference, 1),
448
+ )
449
+ else:
450
+ # Low confidence → complex task → premium specialist
451
+ selected_specialist = max(
452
+ specialists,
453
+ key=lambda s: {
454
+ "CHEAP": 0,
455
+ "CAPABLE": 1,
456
+ "PREMIUM": 2,
457
+ }.get(s.tier_preference, 1),
458
+ )
459
+
460
+ logger.info(f"Routed to specialist: {selected_specialist.id}")
461
+
462
+ # Phase 3: Execute selected specialist
463
+ specialist_context = context.copy()
464
+ specialist_context["classification"] = classifier_result.output
465
+ specialist_result = await self._execute_agent(selected_specialist, specialist_context)
466
+ results.append(specialist_result)
467
+ total_duration += specialist_result.duration_seconds
468
+
469
+ return StrategyResult(
470
+ success=all(r.success for r in results),
471
+ outputs=results,
472
+ aggregated_output={
473
+ "classification": classifier_result.output,
474
+ "selected_specialist": selected_specialist.id,
475
+ "specialist_output": specialist_result.output,
476
+ },
477
+ total_duration=total_duration,
478
+ errors=[r.error for r in results if not r.success],
479
+ )
@@ -0,0 +1,64 @@
1
+ """Data classes for execution strategy results.
2
+
3
+ This module contains the core data structures used across all execution strategies:
4
+ - AgentResult: Result from individual agent execution
5
+ - StrategyResult: Aggregated result from strategy execution
6
+
7
+ Extracted from execution_strategies.py for improved maintainability.
8
+
9
+ Copyright 2025 Smart-AI-Memory
10
+ Licensed under Fair Source License 0.9
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass, field
16
+ from typing import Any
17
+
18
+
19
+ @dataclass
20
+ class AgentResult:
21
+ """Result from agent execution.
22
+
23
+ Attributes:
24
+ agent_id: ID of agent that produced result
25
+ success: Whether execution succeeded
26
+ output: Agent output data
27
+ confidence: Confidence score (0-1)
28
+ duration_seconds: Execution time
29
+ error: Error message if failed
30
+ """
31
+
32
+ agent_id: str
33
+ success: bool
34
+ output: dict[str, Any]
35
+ confidence: float = 0.0
36
+ duration_seconds: float = 0.0
37
+ error: str = ""
38
+
39
+
40
+ @dataclass
41
+ class StrategyResult:
42
+ """Aggregated result from strategy execution.
43
+
44
+ Attributes:
45
+ success: Whether overall execution succeeded
46
+ outputs: List of individual agent results
47
+ aggregated_output: Combined/synthesized output
48
+ total_duration: Total execution time
49
+ errors: List of errors encountered
50
+ """
51
+
52
+ success: bool
53
+ outputs: list[AgentResult]
54
+ aggregated_output: dict[str, Any]
55
+ total_duration: float = 0.0
56
+ errors: list[str] = field(default_factory=list)
57
+
58
+ def __post_init__(self):
59
+ """Initialize errors list if None."""
60
+ if not self.errors:
61
+ self.errors = []
62
+
63
+
64
+ __all__ = ["AgentResult", "StrategyResult"]