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,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
+ )