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.
- attune/cli/__init__.py +3 -59
- attune/cli/commands/batch.py +4 -12
- attune/cli/commands/cache.py +8 -16
- attune/cli/commands/provider.py +17 -0
- attune/cli/commands/routing.py +3 -1
- attune/cli/commands/setup.py +122 -0
- attune/cli/commands/tier.py +1 -3
- attune/cli/commands/workflow.py +31 -0
- attune/cli/parsers/cache.py +1 -0
- attune/cli/parsers/help.py +1 -3
- attune/cli/parsers/provider.py +7 -0
- attune/cli/parsers/routing.py +1 -3
- attune/cli/parsers/setup.py +7 -0
- attune/cli/parsers/status.py +1 -3
- attune/cli/parsers/tier.py +1 -3
- attune/cli_minimal.py +9 -3
- attune/cli_router.py +9 -7
- attune/cli_unified.py +3 -0
- attune/dashboard/app.py +3 -1
- attune/dashboard/simple_server.py +3 -1
- attune/dashboard/standalone_server.py +7 -3
- attune/mcp/server.py +54 -102
- attune/memory/long_term.py +0 -2
- attune/memory/short_term/__init__.py +84 -0
- attune/memory/short_term/base.py +465 -0
- attune/memory/short_term/batch.py +219 -0
- attune/memory/short_term/caching.py +227 -0
- attune/memory/short_term/conflicts.py +265 -0
- attune/memory/short_term/cross_session.py +122 -0
- attune/memory/short_term/facade.py +653 -0
- attune/memory/short_term/pagination.py +207 -0
- attune/memory/short_term/patterns.py +271 -0
- attune/memory/short_term/pubsub.py +286 -0
- attune/memory/short_term/queues.py +244 -0
- attune/memory/short_term/security.py +300 -0
- attune/memory/short_term/sessions.py +250 -0
- attune/memory/short_term/streams.py +242 -0
- attune/memory/short_term/timelines.py +234 -0
- attune/memory/short_term/transactions.py +184 -0
- attune/memory/short_term/working.py +252 -0
- attune/meta_workflows/cli_commands/__init__.py +3 -0
- attune/meta_workflows/cli_commands/agent_commands.py +0 -4
- attune/meta_workflows/cli_commands/analytics_commands.py +0 -6
- attune/meta_workflows/cli_commands/config_commands.py +0 -5
- attune/meta_workflows/cli_commands/memory_commands.py +0 -5
- attune/meta_workflows/cli_commands/template_commands.py +0 -5
- attune/meta_workflows/cli_commands/workflow_commands.py +0 -6
- attune/meta_workflows/plan_generator.py +2 -4
- attune/models/adaptive_routing.py +4 -8
- attune/models/auth_cli.py +3 -9
- attune/models/auth_strategy.py +2 -4
- attune/models/telemetry/analytics.py +0 -2
- attune/models/telemetry/backend.py +0 -3
- attune/models/telemetry/storage.py +0 -2
- attune/monitoring/alerts.py +6 -10
- attune/orchestration/_strategies/__init__.py +156 -0
- attune/orchestration/_strategies/base.py +227 -0
- attune/orchestration/_strategies/conditional_strategies.py +365 -0
- attune/orchestration/_strategies/conditions.py +369 -0
- attune/orchestration/_strategies/core_strategies.py +479 -0
- attune/orchestration/_strategies/data_classes.py +64 -0
- attune/orchestration/_strategies/nesting.py +233 -0
- attune/orchestration/execution_strategies.py +58 -1567
- attune/orchestration/meta_orchestrator.py +1 -3
- attune/project_index/scanner.py +1 -3
- attune/project_index/scanner_parallel.py +7 -5
- attune/socratic/storage.py +2 -4
- attune/socratic_router.py +1 -3
- attune/telemetry/agent_coordination.py +9 -3
- attune/telemetry/agent_tracking.py +16 -3
- attune/telemetry/approval_gates.py +22 -5
- attune/telemetry/cli.py +1 -3
- attune/telemetry/commands/dashboard_commands.py +24 -8
- attune/telemetry/event_streaming.py +8 -2
- attune/telemetry/feedback_loop.py +10 -2
- attune/tools.py +2 -1
- attune/workflow_commands.py +1 -3
- attune/workflow_patterns/structural.py +4 -8
- attune/workflows/__init__.py +54 -10
- attune/workflows/autonomous_test_gen.py +158 -102
- attune/workflows/base.py +48 -672
- attune/workflows/batch_processing.py +1 -3
- attune/workflows/compat.py +156 -0
- attune/workflows/cost_mixin.py +141 -0
- attune/workflows/data_classes.py +92 -0
- attune/workflows/document_gen/workflow.py +11 -14
- attune/workflows/history.py +16 -9
- attune/workflows/llm_base.py +1 -3
- attune/workflows/migration.py +432 -0
- attune/workflows/output.py +2 -7
- attune/workflows/parsing_mixin.py +427 -0
- attune/workflows/perf_audit.py +3 -1
- attune/workflows/progress.py +9 -11
- attune/workflows/release_prep.py +5 -1
- attune/workflows/routing.py +0 -2
- attune/workflows/secure_release.py +4 -1
- attune/workflows/security_audit.py +20 -14
- attune/workflows/security_audit_phase3.py +28 -22
- attune/workflows/seo_optimization.py +27 -27
- attune/workflows/test_gen/test_templates.py +1 -4
- attune/workflows/test_gen/workflow.py +0 -2
- attune/workflows/test_gen_behavioral.py +6 -19
- attune/workflows/test_gen_parallel.py +8 -6
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/METADATA +4 -3
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/RECORD +121 -96
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/entry_points.txt +0 -2
- attune_healthcare/monitors/monitoring/__init__.py +9 -9
- attune_llm/agent_factory/__init__.py +6 -6
- attune_llm/agent_factory/adapters/haystack_adapter.py +1 -4
- attune_llm/commands/__init__.py +10 -10
- attune_llm/commands/models.py +3 -3
- attune_llm/config/__init__.py +8 -8
- attune_llm/learning/__init__.py +3 -3
- attune_llm/learning/extractor.py +5 -3
- attune_llm/learning/storage.py +5 -3
- attune_llm/security/__init__.py +17 -17
- attune_llm/utils/tokens.py +3 -1
- attune/cli_legacy.py +0 -3978
- attune/memory/short_term.py +0 -2192
- attune/workflows/manage_docs.py +0 -87
- attune/workflows/test5.py +0 -125
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/WHEEL +0 -0
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/licenses/LICENSE +0 -0
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +0 -0
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"""Conditional and nested execution strategies.
|
|
2
|
+
|
|
3
|
+
This module contains strategies that implement conditional branching and
|
|
4
|
+
nested workflow composition:
|
|
5
|
+
|
|
6
|
+
1. ConditionalStrategy - if/then/else branching based on gates
|
|
7
|
+
2. MultiConditionalStrategy - switch/case pattern
|
|
8
|
+
3. NestedStrategy - recursive workflow execution
|
|
9
|
+
4. NestedSequentialStrategy - sequential steps with nested workflow support
|
|
10
|
+
|
|
11
|
+
Security:
|
|
12
|
+
- Condition predicates validated (no code execution)
|
|
13
|
+
- Cycle detection prevents infinite recursion
|
|
14
|
+
- Max depth limits enforced
|
|
15
|
+
- No eval() or exec() usage
|
|
16
|
+
|
|
17
|
+
Copyright 2025 Smart-AI-Memory
|
|
18
|
+
Licensed under Fair Source License 0.9
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from typing import TYPE_CHECKING, Any
|
|
26
|
+
|
|
27
|
+
from .base import ExecutionStrategy
|
|
28
|
+
from .conditions import Condition, ConditionEvaluator
|
|
29
|
+
from .data_classes import AgentResult, StrategyResult
|
|
30
|
+
from .nesting import (
|
|
31
|
+
NestingContext,
|
|
32
|
+
WorkflowReference,
|
|
33
|
+
get_workflow,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from ..agent_templates import AgentTemplate
|
|
38
|
+
from .conditions import Branch
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ConditionalStrategy(ExecutionStrategy):
|
|
44
|
+
"""Conditional branching (if X then A else B).
|
|
45
|
+
|
|
46
|
+
The 7th grammar rule enabling dynamic workflow decisions based on gates.
|
|
47
|
+
|
|
48
|
+
Use when:
|
|
49
|
+
- Quality gates determine next steps
|
|
50
|
+
- Error handling requires different paths
|
|
51
|
+
- Agent consensus affects workflow
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
condition: Condition,
|
|
57
|
+
then_branch: Branch,
|
|
58
|
+
else_branch: Branch | None = None,
|
|
59
|
+
):
|
|
60
|
+
"""Initialize conditional strategy."""
|
|
61
|
+
self.condition = condition
|
|
62
|
+
self.then_branch = then_branch
|
|
63
|
+
self.else_branch = else_branch
|
|
64
|
+
self.evaluator = ConditionEvaluator()
|
|
65
|
+
|
|
66
|
+
async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
|
|
67
|
+
"""Execute conditional branching."""
|
|
68
|
+
# Import here to avoid circular import
|
|
69
|
+
from . import get_strategy
|
|
70
|
+
|
|
71
|
+
logger.info(f"Conditional: Evaluating '{self.condition.description or 'condition'}'")
|
|
72
|
+
|
|
73
|
+
condition_met = self.evaluator.evaluate(self.condition, context)
|
|
74
|
+
logger.info(f"Conditional: Condition evaluated to {condition_met}")
|
|
75
|
+
|
|
76
|
+
if condition_met:
|
|
77
|
+
selected_branch = self.then_branch
|
|
78
|
+
branch_label = "then"
|
|
79
|
+
else:
|
|
80
|
+
if self.else_branch is None:
|
|
81
|
+
return StrategyResult(
|
|
82
|
+
success=True,
|
|
83
|
+
outputs=[],
|
|
84
|
+
aggregated_output={"branch_taken": None},
|
|
85
|
+
total_duration=0.0,
|
|
86
|
+
)
|
|
87
|
+
selected_branch = self.else_branch
|
|
88
|
+
branch_label = "else"
|
|
89
|
+
|
|
90
|
+
logger.info(f"Conditional: Taking '{branch_label}' branch")
|
|
91
|
+
|
|
92
|
+
branch_strategy = get_strategy(selected_branch.strategy)
|
|
93
|
+
branch_context = context.copy()
|
|
94
|
+
branch_context["_conditional"] = {"condition_met": condition_met, "branch": branch_label}
|
|
95
|
+
|
|
96
|
+
result = await branch_strategy.execute(selected_branch.agents, branch_context)
|
|
97
|
+
result.aggregated_output["_conditional"] = {
|
|
98
|
+
"condition_met": condition_met,
|
|
99
|
+
"branch_taken": branch_label,
|
|
100
|
+
}
|
|
101
|
+
return result
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class MultiConditionalStrategy(ExecutionStrategy):
|
|
105
|
+
"""Multiple conditional branches (switch/case pattern)."""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
conditions: list[tuple[Condition, Branch]],
|
|
110
|
+
default_branch: Branch | None = None,
|
|
111
|
+
):
|
|
112
|
+
"""Initialize multi-conditional strategy."""
|
|
113
|
+
self.conditions = conditions
|
|
114
|
+
self.default_branch = default_branch
|
|
115
|
+
self.evaluator = ConditionEvaluator()
|
|
116
|
+
|
|
117
|
+
async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
|
|
118
|
+
"""Execute multi-conditional branching."""
|
|
119
|
+
# Import here to avoid circular import
|
|
120
|
+
from . import get_strategy
|
|
121
|
+
|
|
122
|
+
for i, (condition, branch) in enumerate(self.conditions):
|
|
123
|
+
if self.evaluator.evaluate(condition, context):
|
|
124
|
+
logger.info(f"MultiConditional: Condition {i + 1} matched")
|
|
125
|
+
branch_strategy = get_strategy(branch.strategy)
|
|
126
|
+
result = await branch_strategy.execute(branch.agents, context)
|
|
127
|
+
result.aggregated_output["_matched_index"] = i
|
|
128
|
+
return result
|
|
129
|
+
|
|
130
|
+
if self.default_branch:
|
|
131
|
+
branch_strategy = get_strategy(self.default_branch.strategy)
|
|
132
|
+
return await branch_strategy.execute(self.default_branch.agents, context)
|
|
133
|
+
|
|
134
|
+
return StrategyResult(
|
|
135
|
+
success=True,
|
|
136
|
+
outputs=[],
|
|
137
|
+
aggregated_output={"reason": "No conditions matched"},
|
|
138
|
+
total_duration=0.0,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class NestedStrategy(ExecutionStrategy):
|
|
143
|
+
"""Nested workflow execution (sentences within sentences).
|
|
144
|
+
|
|
145
|
+
Enables recursive composition where workflows invoke other workflows.
|
|
146
|
+
Implements the "subordinate clause" pattern in the grammar metaphor.
|
|
147
|
+
|
|
148
|
+
Features:
|
|
149
|
+
- Reference workflows by ID or define inline
|
|
150
|
+
- Configurable max depth (default: 3)
|
|
151
|
+
- Cycle detection prevents infinite recursion
|
|
152
|
+
- Full context inheritance from parent to child
|
|
153
|
+
|
|
154
|
+
Use when:
|
|
155
|
+
- Complex multi-stage pipelines need modular sub-workflows
|
|
156
|
+
- Reusable workflow components should be shared
|
|
157
|
+
- Hierarchical team structures (teams containing sub-teams)
|
|
158
|
+
|
|
159
|
+
Example:
|
|
160
|
+
>>> # Parent workflow with nested sub-workflow
|
|
161
|
+
>>> strategy = NestedStrategy(
|
|
162
|
+
... workflow_ref=WorkflowReference(workflow_id="security-audit"),
|
|
163
|
+
... max_depth=3
|
|
164
|
+
... )
|
|
165
|
+
>>> result = await strategy.execute([], context)
|
|
166
|
+
|
|
167
|
+
Example (inline):
|
|
168
|
+
>>> strategy = NestedStrategy(
|
|
169
|
+
... workflow_ref=WorkflowReference(
|
|
170
|
+
... inline=InlineWorkflow(
|
|
171
|
+
... agents=[analyzer, reviewer],
|
|
172
|
+
... strategy="parallel"
|
|
173
|
+
... )
|
|
174
|
+
... )
|
|
175
|
+
... )
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
def __init__(
|
|
179
|
+
self,
|
|
180
|
+
workflow_ref: WorkflowReference,
|
|
181
|
+
max_depth: int = NestingContext.DEFAULT_MAX_DEPTH,
|
|
182
|
+
):
|
|
183
|
+
"""Initialize nested strategy.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
workflow_ref: Reference to workflow (by ID or inline)
|
|
187
|
+
max_depth: Maximum nesting depth allowed
|
|
188
|
+
"""
|
|
189
|
+
self.workflow_ref = workflow_ref
|
|
190
|
+
self.max_depth = max_depth
|
|
191
|
+
|
|
192
|
+
async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
|
|
193
|
+
"""Execute nested workflow.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
agents: Ignored (workflow_ref defines agents)
|
|
197
|
+
context: Parent execution context (inherited by child)
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
StrategyResult from nested workflow execution
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
RecursionError: If max depth exceeded or cycle detected
|
|
204
|
+
"""
|
|
205
|
+
# Import here to avoid circular import
|
|
206
|
+
from . import get_strategy
|
|
207
|
+
|
|
208
|
+
# Get or create nesting context
|
|
209
|
+
nesting = NestingContext.from_context(context)
|
|
210
|
+
|
|
211
|
+
# Resolve workflow
|
|
212
|
+
if self.workflow_ref.workflow_id:
|
|
213
|
+
workflow_id = self.workflow_ref.workflow_id
|
|
214
|
+
workflow = get_workflow(workflow_id)
|
|
215
|
+
workflow_agents = workflow.agents
|
|
216
|
+
strategy_name = workflow.strategy
|
|
217
|
+
else:
|
|
218
|
+
workflow_id = f"inline_{id(self.workflow_ref.inline)}"
|
|
219
|
+
workflow_agents = self.workflow_ref.inline.agents
|
|
220
|
+
strategy_name = self.workflow_ref.inline.strategy
|
|
221
|
+
|
|
222
|
+
# Check nesting limits
|
|
223
|
+
if not nesting.can_nest(workflow_id):
|
|
224
|
+
if nesting.current_depth >= nesting.max_depth:
|
|
225
|
+
error_msg = (
|
|
226
|
+
f"Maximum nesting depth ({nesting.max_depth}) exceeded. "
|
|
227
|
+
f"Current stack: {' → '.join(nesting.workflow_stack)}"
|
|
228
|
+
)
|
|
229
|
+
else:
|
|
230
|
+
error_msg = (
|
|
231
|
+
f"Cycle detected: workflow '{workflow_id}' already in stack. "
|
|
232
|
+
f"Stack: {' → '.join(nesting.workflow_stack)}"
|
|
233
|
+
)
|
|
234
|
+
logger.error(error_msg)
|
|
235
|
+
raise RecursionError(error_msg)
|
|
236
|
+
|
|
237
|
+
logger.info(f"Nested: Entering '{workflow_id}' at depth {nesting.current_depth + 1}")
|
|
238
|
+
|
|
239
|
+
# Create child context with updated nesting
|
|
240
|
+
child_nesting = nesting.enter(workflow_id)
|
|
241
|
+
child_context = child_nesting.to_context(context.copy())
|
|
242
|
+
|
|
243
|
+
# Execute nested workflow
|
|
244
|
+
strategy = get_strategy(strategy_name)
|
|
245
|
+
result = await strategy.execute(workflow_agents, child_context)
|
|
246
|
+
|
|
247
|
+
# Augment result with nesting metadata
|
|
248
|
+
result.aggregated_output["_nested"] = {
|
|
249
|
+
"workflow_id": workflow_id,
|
|
250
|
+
"depth": child_nesting.current_depth,
|
|
251
|
+
"parent_stack": nesting.workflow_stack,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# Store result under specified key if provided
|
|
255
|
+
if self.workflow_ref.result_key:
|
|
256
|
+
result.aggregated_output[self.workflow_ref.result_key] = result.aggregated_output.copy()
|
|
257
|
+
|
|
258
|
+
logger.info(f"Nested: Exiting '{workflow_id}'")
|
|
259
|
+
|
|
260
|
+
return result
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@dataclass
|
|
264
|
+
class StepDefinition:
|
|
265
|
+
"""Definition of a step in NestedSequentialStrategy.
|
|
266
|
+
|
|
267
|
+
Either agent OR workflow_ref must be provided (mutually exclusive).
|
|
268
|
+
|
|
269
|
+
Attributes:
|
|
270
|
+
agent: Agent to execute directly
|
|
271
|
+
workflow_ref: Nested workflow to execute
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
agent: AgentTemplate | None = None
|
|
275
|
+
workflow_ref: WorkflowReference | None = None
|
|
276
|
+
|
|
277
|
+
def __post_init__(self):
|
|
278
|
+
"""Validate that exactly one step type is provided."""
|
|
279
|
+
if bool(self.agent) == bool(self.workflow_ref):
|
|
280
|
+
raise ValueError("StepDefinition must have exactly one of: agent or workflow_ref")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class NestedSequentialStrategy(ExecutionStrategy):
|
|
284
|
+
"""Sequential execution with nested workflow support.
|
|
285
|
+
|
|
286
|
+
Like SequentialStrategy but steps can be either agents OR workflow references.
|
|
287
|
+
Enables mixing direct agent execution with nested sub-workflows.
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
>>> strategy = NestedSequentialStrategy(
|
|
291
|
+
... steps=[
|
|
292
|
+
... StepDefinition(agent=analyzer),
|
|
293
|
+
... StepDefinition(workflow_ref=WorkflowReference(workflow_id="review-team")),
|
|
294
|
+
... StepDefinition(agent=reporter),
|
|
295
|
+
... ]
|
|
296
|
+
... )
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def __init__(
|
|
300
|
+
self,
|
|
301
|
+
steps: list[StepDefinition],
|
|
302
|
+
max_depth: int = NestingContext.DEFAULT_MAX_DEPTH,
|
|
303
|
+
):
|
|
304
|
+
"""Initialize nested sequential strategy.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
steps: List of step definitions (agents or workflow refs)
|
|
308
|
+
max_depth: Maximum nesting depth
|
|
309
|
+
"""
|
|
310
|
+
self.steps = steps
|
|
311
|
+
self.max_depth = max_depth
|
|
312
|
+
|
|
313
|
+
async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
|
|
314
|
+
"""Execute steps sequentially, handling both agents and nested workflows."""
|
|
315
|
+
if not self.steps:
|
|
316
|
+
raise ValueError("steps list cannot be empty")
|
|
317
|
+
|
|
318
|
+
logger.info(f"NestedSequential: Executing {len(self.steps)} steps")
|
|
319
|
+
|
|
320
|
+
results: list[AgentResult] = []
|
|
321
|
+
current_context = context.copy()
|
|
322
|
+
total_duration = 0.0
|
|
323
|
+
|
|
324
|
+
for i, step in enumerate(self.steps):
|
|
325
|
+
logger.info(f"NestedSequential: Step {i + 1}/{len(self.steps)}")
|
|
326
|
+
|
|
327
|
+
if step.agent:
|
|
328
|
+
# Direct agent execution
|
|
329
|
+
result = await self._execute_agent(step.agent, current_context)
|
|
330
|
+
results.append(result)
|
|
331
|
+
total_duration += result.duration_seconds
|
|
332
|
+
|
|
333
|
+
if result.success:
|
|
334
|
+
current_context[f"{step.agent.id}_output"] = result.output
|
|
335
|
+
else:
|
|
336
|
+
# Nested workflow execution
|
|
337
|
+
nested_strategy = NestedStrategy(
|
|
338
|
+
workflow_ref=step.workflow_ref,
|
|
339
|
+
max_depth=self.max_depth,
|
|
340
|
+
)
|
|
341
|
+
nested_result = await nested_strategy.execute([], current_context)
|
|
342
|
+
total_duration += nested_result.total_duration
|
|
343
|
+
|
|
344
|
+
# Convert to AgentResult for consistency
|
|
345
|
+
results.append(
|
|
346
|
+
AgentResult(
|
|
347
|
+
agent_id=f"nested_{step.workflow_ref.workflow_id or 'inline'}",
|
|
348
|
+
success=nested_result.success,
|
|
349
|
+
output=nested_result.aggregated_output,
|
|
350
|
+
confidence=nested_result.aggregated_output.get("avg_confidence", 0.0),
|
|
351
|
+
duration_seconds=nested_result.total_duration,
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
if nested_result.success:
|
|
356
|
+
key = step.workflow_ref.result_key or f"step_{i}_output"
|
|
357
|
+
current_context[key] = nested_result.aggregated_output
|
|
358
|
+
|
|
359
|
+
return StrategyResult(
|
|
360
|
+
success=all(r.success for r in results),
|
|
361
|
+
outputs=results,
|
|
362
|
+
aggregated_output=self._aggregate_results(results),
|
|
363
|
+
total_duration=total_duration,
|
|
364
|
+
errors=[r.error for r in results if not r.success],
|
|
365
|
+
)
|