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.
- attune/cli/__init__.py +3 -55
- attune/cli/commands/batch.py +4 -12
- attune/cli/commands/cache.py +7 -15
- 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 +34 -28
- attune/cli_router.py +9 -7
- attune/cli_unified.py +3 -0
- attune/core.py +190 -0
- attune/dashboard/app.py +4 -2
- 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 +467 -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 +655 -0
- attune/memory/short_term/pagination.py +215 -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 +249 -0
- attune/memory/short_term/timelines.py +234 -0
- attune/memory/short_term/transactions.py +186 -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/workflow.py +1 -1
- attune/models/adaptive_routing.py +4 -8
- attune/models/auth_cli.py +3 -9
- attune/models/auth_strategy.py +2 -4
- attune/models/provider_config.py +20 -1
- attune/models/telemetry/analytics.py +0 -2
- attune/models/telemetry/backend.py +0 -3
- attune/models/telemetry/storage.py +0 -2
- attune/orchestration/_strategies/__init__.py +156 -0
- attune/orchestration/_strategies/base.py +231 -0
- attune/orchestration/_strategies/conditional_strategies.py +373 -0
- attune/orchestration/_strategies/conditions.py +369 -0
- attune/orchestration/_strategies/core_strategies.py +491 -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_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 +3 -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 +1 -0
- attune/workflow_commands.py +1 -3
- attune/workflows/__init__.py +53 -10
- attune/workflows/autonomous_test_gen.py +160 -104
- attune/workflows/base.py +48 -664
- attune/workflows/batch_processing.py +2 -4
- 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 +62 -37
- attune/workflows/llm_base.py +2 -4
- attune/workflows/migration.py +422 -0
- attune/workflows/output.py +3 -9
- attune/workflows/parsing_mixin.py +427 -0
- attune/workflows/perf_audit.py +3 -1
- attune/workflows/progress.py +10 -13
- attune/workflows/release_prep.py +5 -1
- attune/workflows/routing.py +0 -2
- attune/workflows/secure_release.py +2 -1
- attune/workflows/security_audit.py +19 -14
- attune/workflows/security_audit_phase3.py +28 -22
- attune/workflows/seo_optimization.py +29 -29
- attune/workflows/test_gen/test_templates.py +1 -4
- attune/workflows/test_gen/workflow.py +0 -2
- attune/workflows/test_gen_behavioral.py +7 -20
- attune/workflows/test_gen_parallel.py +6 -4
- {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/METADATA +4 -3
- {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/RECORD +119 -94
- {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.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/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 -3957
- attune/memory/short_term.py +0 -2192
- attune/workflows/manage_docs.py +0 -87
- attune/workflows/test5.py +0 -125
- {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/WHEEL +0 -0
- {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +0 -0
- {attune_ai-2.1.4.dist-info → attune_ai-2.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Workflow nesting support for recursive composition.
|
|
2
|
+
|
|
3
|
+
This module enables "sentences within sentences" - workflows that invoke
|
|
4
|
+
other workflows. Supports both registered workflow IDs and inline definitions.
|
|
5
|
+
|
|
6
|
+
Key concepts:
|
|
7
|
+
- WorkflowReference: Points to a nested workflow (by ID or inline)
|
|
8
|
+
- InlineWorkflow: Defines a sub-workflow directly within parent
|
|
9
|
+
- NestingContext: Tracks depth and prevents infinite recursion
|
|
10
|
+
- WorkflowDefinition: A registered workflow that can be referenced by ID
|
|
11
|
+
|
|
12
|
+
Security:
|
|
13
|
+
- Cycle detection prevents infinite loops
|
|
14
|
+
- Max depth limit prevents stack overflow
|
|
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, field
|
|
25
|
+
from typing import TYPE_CHECKING, Any
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from ..agent_templates import AgentTemplate
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# =============================================================================
|
|
34
|
+
# Nested Sentence Types (Phase 2 - Recursive Composition)
|
|
35
|
+
# =============================================================================
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class WorkflowReference:
|
|
40
|
+
"""Reference to a workflow for nested composition.
|
|
41
|
+
|
|
42
|
+
Enables "sentences within sentences" - workflows that invoke other workflows.
|
|
43
|
+
Supports both registered workflow IDs and inline definitions.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
workflow_id: ID of registered workflow (mutually exclusive with inline)
|
|
47
|
+
inline: Inline workflow definition (mutually exclusive with workflow_id)
|
|
48
|
+
context_mapping: Optional mapping of parent context fields to child
|
|
49
|
+
result_key: Key to store nested workflow result in parent context
|
|
50
|
+
|
|
51
|
+
Example (by ID):
|
|
52
|
+
>>> ref = WorkflowReference(
|
|
53
|
+
... workflow_id="security-audit-team",
|
|
54
|
+
... result_key="security_result"
|
|
55
|
+
... )
|
|
56
|
+
|
|
57
|
+
Example (inline):
|
|
58
|
+
>>> ref = WorkflowReference(
|
|
59
|
+
... inline=InlineWorkflow(
|
|
60
|
+
... agents=[agent1, agent2],
|
|
61
|
+
... strategy="parallel"
|
|
62
|
+
... ),
|
|
63
|
+
... result_key="analysis_result"
|
|
64
|
+
... )
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
workflow_id: str = ""
|
|
68
|
+
inline: InlineWorkflow | None = None
|
|
69
|
+
context_mapping: dict[str, str] = field(default_factory=dict)
|
|
70
|
+
result_key: str = "nested_result"
|
|
71
|
+
|
|
72
|
+
def __post_init__(self):
|
|
73
|
+
"""Validate that exactly one reference type is provided."""
|
|
74
|
+
if bool(self.workflow_id) == bool(self.inline):
|
|
75
|
+
raise ValueError("WorkflowReference must have exactly one of: workflow_id or inline")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class InlineWorkflow:
|
|
80
|
+
"""Inline workflow definition for nested composition.
|
|
81
|
+
|
|
82
|
+
Allows defining a sub-workflow directly within a parent workflow,
|
|
83
|
+
without requiring registration.
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
agents: Agents to execute
|
|
87
|
+
strategy: Strategy name (from STRATEGY_REGISTRY)
|
|
88
|
+
description: Human-readable description
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
>>> inline = InlineWorkflow(
|
|
92
|
+
... agents=[analyzer, reviewer],
|
|
93
|
+
... strategy="sequential",
|
|
94
|
+
... description="Code review sub-workflow"
|
|
95
|
+
... )
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
agents: list[AgentTemplate]
|
|
99
|
+
strategy: str = "sequential"
|
|
100
|
+
description: str = ""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class NestingContext:
|
|
104
|
+
"""Tracks nesting depth and prevents infinite recursion.
|
|
105
|
+
|
|
106
|
+
Attributes:
|
|
107
|
+
current_depth: Current nesting level (0 = root)
|
|
108
|
+
max_depth: Maximum allowed nesting depth
|
|
109
|
+
workflow_stack: Stack of workflow IDs for cycle detection
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
CONTEXT_KEY = "_nesting"
|
|
113
|
+
DEFAULT_MAX_DEPTH = 3
|
|
114
|
+
|
|
115
|
+
def __init__(self, max_depth: int = DEFAULT_MAX_DEPTH):
|
|
116
|
+
"""Initialize nesting context.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
max_depth: Maximum allowed nesting depth
|
|
120
|
+
"""
|
|
121
|
+
self.current_depth = 0
|
|
122
|
+
self.max_depth = max_depth
|
|
123
|
+
self.workflow_stack: list[str] = []
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def from_context(cls, context: dict[str, Any]) -> NestingContext:
|
|
127
|
+
"""Extract or create NestingContext from execution context.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
context: Execution context dict
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
NestingContext instance
|
|
134
|
+
"""
|
|
135
|
+
if cls.CONTEXT_KEY in context:
|
|
136
|
+
return context[cls.CONTEXT_KEY]
|
|
137
|
+
return cls()
|
|
138
|
+
|
|
139
|
+
def can_nest(self, workflow_id: str = "") -> bool:
|
|
140
|
+
"""Check if another nesting level is allowed.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
workflow_id: ID of workflow to nest (for cycle detection)
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
True if nesting is allowed
|
|
147
|
+
"""
|
|
148
|
+
if self.current_depth >= self.max_depth:
|
|
149
|
+
return False
|
|
150
|
+
if workflow_id and workflow_id in self.workflow_stack:
|
|
151
|
+
return False # Cycle detected
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
def enter(self, workflow_id: str = "") -> NestingContext:
|
|
155
|
+
"""Create a child context for nested execution.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
workflow_id: ID of workflow being entered
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
New NestingContext with incremented depth
|
|
162
|
+
"""
|
|
163
|
+
child = NestingContext(self.max_depth)
|
|
164
|
+
child.current_depth = self.current_depth + 1
|
|
165
|
+
child.workflow_stack = self.workflow_stack.copy()
|
|
166
|
+
if workflow_id:
|
|
167
|
+
child.workflow_stack.append(workflow_id)
|
|
168
|
+
return child
|
|
169
|
+
|
|
170
|
+
def to_context(self, context: dict[str, Any]) -> dict[str, Any]:
|
|
171
|
+
"""Add nesting context to execution context.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
context: Execution context dict
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Updated context with nesting info
|
|
178
|
+
"""
|
|
179
|
+
context = context.copy()
|
|
180
|
+
context[self.CONTEXT_KEY] = self
|
|
181
|
+
return context
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Registry for named workflows (populated at runtime)
|
|
185
|
+
WORKFLOW_REGISTRY: dict[str, WorkflowDefinition] = {}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@dataclass
|
|
189
|
+
class WorkflowDefinition:
|
|
190
|
+
"""A registered workflow definition.
|
|
191
|
+
|
|
192
|
+
Workflows can be registered and referenced by ID in nested compositions.
|
|
193
|
+
|
|
194
|
+
Attributes:
|
|
195
|
+
id: Unique workflow identifier
|
|
196
|
+
agents: Agents in the workflow
|
|
197
|
+
strategy: Composition strategy name
|
|
198
|
+
description: Human-readable description
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
id: str
|
|
202
|
+
agents: list[AgentTemplate]
|
|
203
|
+
strategy: str = "sequential"
|
|
204
|
+
description: str = ""
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def register_workflow(workflow: WorkflowDefinition) -> None:
|
|
208
|
+
"""Register a workflow for nested references.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
workflow: Workflow definition to register
|
|
212
|
+
"""
|
|
213
|
+
WORKFLOW_REGISTRY[workflow.id] = workflow
|
|
214
|
+
logger.info(f"Registered workflow: {workflow.id}")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def get_workflow(workflow_id: str) -> WorkflowDefinition:
|
|
218
|
+
"""Get a registered workflow by ID.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
workflow_id: Workflow identifier
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
WorkflowDefinition
|
|
225
|
+
|
|
226
|
+
Raises:
|
|
227
|
+
ValueError: If workflow is not registered
|
|
228
|
+
"""
|
|
229
|
+
if workflow_id not in WORKFLOW_REGISTRY:
|
|
230
|
+
raise ValueError(
|
|
231
|
+
f"Unknown workflow: {workflow_id}. Available: {list(WORKFLOW_REGISTRY.keys())}"
|
|
232
|
+
)
|
|
233
|
+
return WORKFLOW_REGISTRY[workflow_id]
|