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