attune-ai 2.1.5__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 -59
- 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 +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 +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/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/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 +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 +1 -0
- attune/workflow_commands.py +1 -3
- attune/workflows/__init__.py +53 -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 +62 -37
- attune/workflows/llm_base.py +1 -3
- attune/workflows/migration.py +422 -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 +2 -1
- attune/workflows/security_audit.py +19 -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 +6 -4
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/METADATA +4 -3
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/RECORD +116 -91
- {attune_ai-2.1.5.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 -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.0.dist-info}/WHEEL +0 -0
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +0 -0
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""Working memory operations - stash and retrieve.
|
|
2
|
+
|
|
3
|
+
This module provides the primary interface for storing and retrieving
|
|
4
|
+
agent working memory:
|
|
5
|
+
- Stash: Store data with optional TTL and metadata
|
|
6
|
+
- Retrieve: Get data by key
|
|
7
|
+
- Clear: Remove all working memory for an agent
|
|
8
|
+
|
|
9
|
+
Key Prefix: PREFIX_WORKING = "empathy:working:"
|
|
10
|
+
|
|
11
|
+
Classes:
|
|
12
|
+
WorkingMemory: Core stash/retrieve operations
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
>>> from attune.memory.short_term.working import WorkingMemory
|
|
16
|
+
>>> from attune.memory.types import AgentCredentials, AccessTier
|
|
17
|
+
>>> # Typically composed into RedisShortTermMemory facade
|
|
18
|
+
>>> creds = AgentCredentials("agent_1", AccessTier.CONTRIBUTOR)
|
|
19
|
+
>>> working = WorkingMemory(base_ops, security_sanitizer)
|
|
20
|
+
>>> working.stash("key", {"data": 123}, creds)
|
|
21
|
+
>>> result = working.retrieve("key", creds)
|
|
22
|
+
|
|
23
|
+
Copyright 2025 Smart-AI-Memory
|
|
24
|
+
Licensed under Fair Source License 0.9
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
from datetime import datetime
|
|
31
|
+
from typing import TYPE_CHECKING, Any
|
|
32
|
+
|
|
33
|
+
import structlog
|
|
34
|
+
|
|
35
|
+
from attune.memory.types import (
|
|
36
|
+
AgentCredentials,
|
|
37
|
+
TTLStrategy,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from attune.memory.short_term.base import BaseOperations
|
|
42
|
+
from attune.memory.short_term.security import DataSanitizer
|
|
43
|
+
|
|
44
|
+
logger = structlog.get_logger(__name__)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class WorkingMemory:
|
|
48
|
+
"""Working memory operations for agent data storage.
|
|
49
|
+
|
|
50
|
+
Provides stash (store) and retrieve operations with:
|
|
51
|
+
- Access control based on agent credentials
|
|
52
|
+
- Optional PII scrubbing and secrets detection
|
|
53
|
+
- Configurable TTL strategies
|
|
54
|
+
- Agent-scoped key namespacing
|
|
55
|
+
|
|
56
|
+
The class is designed to be composed with BaseOperations and
|
|
57
|
+
DataSanitizer for dependency injection.
|
|
58
|
+
|
|
59
|
+
Attributes:
|
|
60
|
+
PREFIX_WORKING: Key prefix for working memory namespace
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
>>> working = WorkingMemory(base_ops, sanitizer)
|
|
64
|
+
>>> creds = AgentCredentials("agent_1", AccessTier.CONTRIBUTOR)
|
|
65
|
+
>>> working.stash("analysis", {"score": 95}, creds)
|
|
66
|
+
True
|
|
67
|
+
>>> working.retrieve("analysis", creds)
|
|
68
|
+
{'score': 95}
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
PREFIX_WORKING = "empathy:working:"
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
base: BaseOperations,
|
|
76
|
+
sanitizer: DataSanitizer | None = None,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Initialize working memory operations.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
base: BaseOperations instance for storage access
|
|
82
|
+
sanitizer: Optional DataSanitizer for PII/secrets handling
|
|
83
|
+
"""
|
|
84
|
+
self._base = base
|
|
85
|
+
self._sanitizer = sanitizer
|
|
86
|
+
|
|
87
|
+
def stash(
|
|
88
|
+
self,
|
|
89
|
+
key: str,
|
|
90
|
+
data: Any,
|
|
91
|
+
credentials: AgentCredentials,
|
|
92
|
+
ttl: TTLStrategy = TTLStrategy.WORKING_RESULTS,
|
|
93
|
+
skip_sanitization: bool = False,
|
|
94
|
+
) -> bool:
|
|
95
|
+
"""Stash data in short-term memory.
|
|
96
|
+
|
|
97
|
+
Stores data with automatic TTL expiration and optional
|
|
98
|
+
security sanitization (PII scrubbing, secrets detection).
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
key: Unique key for the data
|
|
102
|
+
data: Data to store (will be JSON serialized)
|
|
103
|
+
credentials: Agent credentials for access control
|
|
104
|
+
ttl: Time-to-live strategy (default: WORKING_RESULTS)
|
|
105
|
+
skip_sanitization: Skip PII scrubbing and secrets detection
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
True if successful
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ValueError: If key is empty or invalid
|
|
112
|
+
PermissionError: If credentials lack write access
|
|
113
|
+
SecurityError: If secrets are detected (when enabled)
|
|
114
|
+
|
|
115
|
+
Note:
|
|
116
|
+
PII (emails, SSNs, etc.) is automatically scrubbed unless
|
|
117
|
+
skip_sanitization=True. Secrets block storage by default.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
>>> creds = AgentCredentials("agent_1", AccessTier.CONTRIBUTOR)
|
|
121
|
+
>>> working.stash("analysis_v1", {"findings": [...]}, creds)
|
|
122
|
+
True
|
|
123
|
+
"""
|
|
124
|
+
# Pattern 1: String ID validation
|
|
125
|
+
if not key or not key.strip():
|
|
126
|
+
raise ValueError(f"key cannot be empty. Got: {key!r}")
|
|
127
|
+
|
|
128
|
+
if not credentials.can_stage():
|
|
129
|
+
raise PermissionError(
|
|
130
|
+
f"Agent {credentials.agent_id} (Tier {credentials.tier.name}) "
|
|
131
|
+
"cannot write to memory. Requires CONTRIBUTOR or higher.",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Sanitize data (PII scrubbing + secrets detection)
|
|
135
|
+
pii_count = 0
|
|
136
|
+
if not skip_sanitization and self._sanitizer is not None:
|
|
137
|
+
data, pii_count = self._sanitizer.sanitize(data)
|
|
138
|
+
if pii_count > 0:
|
|
139
|
+
logger.info(
|
|
140
|
+
"stash_pii_scrubbed",
|
|
141
|
+
key=key,
|
|
142
|
+
agent_id=credentials.agent_id,
|
|
143
|
+
pii_count=pii_count,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
full_key = f"{self.PREFIX_WORKING}{credentials.agent_id}:{key}"
|
|
147
|
+
payload = {
|
|
148
|
+
"data": data,
|
|
149
|
+
"agent_id": credentials.agent_id,
|
|
150
|
+
"stashed_at": datetime.now().isoformat(),
|
|
151
|
+
}
|
|
152
|
+
return self._base._set(full_key, json.dumps(payload), ttl.value)
|
|
153
|
+
|
|
154
|
+
def retrieve(
|
|
155
|
+
self,
|
|
156
|
+
key: str,
|
|
157
|
+
credentials: AgentCredentials,
|
|
158
|
+
agent_id: str | None = None,
|
|
159
|
+
) -> Any | None:
|
|
160
|
+
"""Retrieve data from short-term memory.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
key: Key to retrieve
|
|
164
|
+
credentials: Agent credentials
|
|
165
|
+
agent_id: Owner agent ID (defaults to credentials agent)
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Retrieved data or None if not found
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
ValueError: If key is empty or invalid
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
>>> data = working.retrieve("analysis_v1", creds)
|
|
175
|
+
>>> if data:
|
|
176
|
+
... print(f"Found: {data}")
|
|
177
|
+
"""
|
|
178
|
+
# Pattern 1: String ID validation
|
|
179
|
+
if not key or not key.strip():
|
|
180
|
+
raise ValueError(f"key cannot be empty. Got: {key!r}")
|
|
181
|
+
|
|
182
|
+
owner = agent_id or credentials.agent_id
|
|
183
|
+
full_key = f"{self.PREFIX_WORKING}{owner}:{key}"
|
|
184
|
+
raw = self._base._get(full_key)
|
|
185
|
+
|
|
186
|
+
if raw is None:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
payload = json.loads(raw)
|
|
190
|
+
return payload.get("data")
|
|
191
|
+
|
|
192
|
+
def clear(self, credentials: AgentCredentials) -> int:
|
|
193
|
+
"""Clear all working memory for an agent.
|
|
194
|
+
|
|
195
|
+
Removes all keys in the working memory namespace for
|
|
196
|
+
the given agent.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
credentials: Agent credentials (must own the memory or be Steward)
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Number of keys deleted
|
|
203
|
+
|
|
204
|
+
Example:
|
|
205
|
+
>>> creds = AgentCredentials("agent_1", AccessTier.CONTRIBUTOR)
|
|
206
|
+
>>> deleted = working.clear(creds)
|
|
207
|
+
>>> print(f"Deleted {deleted} keys")
|
|
208
|
+
"""
|
|
209
|
+
pattern = f"{self.PREFIX_WORKING}{credentials.agent_id}:*"
|
|
210
|
+
keys = self._base._keys(pattern)
|
|
211
|
+
count = 0
|
|
212
|
+
for key in keys:
|
|
213
|
+
if self._base._delete(key):
|
|
214
|
+
count += 1
|
|
215
|
+
return count
|
|
216
|
+
|
|
217
|
+
def exists(
|
|
218
|
+
self,
|
|
219
|
+
key: str,
|
|
220
|
+
credentials: AgentCredentials,
|
|
221
|
+
agent_id: str | None = None,
|
|
222
|
+
) -> bool:
|
|
223
|
+
"""Check if a key exists in working memory.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
key: Key to check
|
|
227
|
+
credentials: Agent credentials
|
|
228
|
+
agent_id: Owner agent ID (defaults to credentials agent)
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
True if key exists
|
|
232
|
+
"""
|
|
233
|
+
if not key or not key.strip():
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
owner = agent_id or credentials.agent_id
|
|
237
|
+
full_key = f"{self.PREFIX_WORKING}{owner}:{key}"
|
|
238
|
+
return self._base._get(full_key) is not None
|
|
239
|
+
|
|
240
|
+
def list_keys(self, credentials: AgentCredentials) -> list[str]:
|
|
241
|
+
"""List all working memory keys for an agent.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
credentials: Agent credentials
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
List of key names (without prefix)
|
|
248
|
+
"""
|
|
249
|
+
pattern = f"{self.PREFIX_WORKING}{credentials.agent_id}:*"
|
|
250
|
+
keys = self._base._keys(pattern)
|
|
251
|
+
prefix_len = len(f"{self.PREFIX_WORKING}{credentials.agent_id}:")
|
|
252
|
+
return [k[prefix_len:] for k in keys]
|
|
@@ -6,7 +6,6 @@ Copyright 2025 Smart-AI-Memory
|
|
|
6
6
|
Licensed under Fair Source License 0.9
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
import typer
|
|
11
10
|
from rich.console import Console
|
|
12
11
|
from rich.panel import Panel
|
|
@@ -151,7 +150,6 @@ def create_agent(
|
|
|
151
150
|
console.print(f" {costs.get(tier, costs['capable'])} per execution[/dim]\n")
|
|
152
151
|
|
|
153
152
|
|
|
154
|
-
|
|
155
153
|
@meta_workflow_app.command("create-team")
|
|
156
154
|
def create_team(
|
|
157
155
|
interactive: bool = typer.Option(
|
|
@@ -317,5 +315,3 @@ def create_team(
|
|
|
317
315
|
|
|
318
316
|
if __name__ == "__main__":
|
|
319
317
|
meta_workflow_app()
|
|
320
|
-
|
|
321
|
-
|
|
@@ -139,7 +139,6 @@ def show_analytics(
|
|
|
139
139
|
# =============================================================================
|
|
140
140
|
|
|
141
141
|
|
|
142
|
-
|
|
143
142
|
@meta_workflow_app.command("list-runs")
|
|
144
143
|
def list_runs(
|
|
145
144
|
template_id: str | None = typer.Option(
|
|
@@ -233,7 +232,6 @@ def list_runs(
|
|
|
233
232
|
raise typer.Exit(code=1)
|
|
234
233
|
|
|
235
234
|
|
|
236
|
-
|
|
237
235
|
@meta_workflow_app.command("show")
|
|
238
236
|
def show_execution(
|
|
239
237
|
run_id: str = typer.Argument(..., help="Run ID to display"),
|
|
@@ -322,7 +320,6 @@ def show_execution(
|
|
|
322
320
|
# =============================================================================
|
|
323
321
|
|
|
324
322
|
|
|
325
|
-
|
|
326
323
|
@meta_workflow_app.command("cleanup")
|
|
327
324
|
def cleanup_executions(
|
|
328
325
|
older_than_days: int = typer.Option(
|
|
@@ -437,6 +434,3 @@ def cleanup_executions(
|
|
|
437
434
|
# =============================================================================
|
|
438
435
|
# Memory Search Commands
|
|
439
436
|
# =============================================================================
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
@@ -6,7 +6,6 @@ Copyright 2025 Smart-AI-Memory
|
|
|
6
6
|
Licensed under Fair Source License 0.9
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
import typer
|
|
11
10
|
from rich.console import Console
|
|
12
11
|
from rich.table import Table
|
|
@@ -115,7 +114,6 @@ def suggest_defaults_cmd(
|
|
|
115
114
|
# =============================================================================
|
|
116
115
|
|
|
117
116
|
|
|
118
|
-
|
|
119
117
|
@meta_workflow_app.command("migrate")
|
|
120
118
|
def show_migration_guide(
|
|
121
119
|
crew_name: str | None = typer.Argument(
|
|
@@ -227,6 +225,3 @@ def show_migration_guide(
|
|
|
227
225
|
# =============================================================================
|
|
228
226
|
# Dynamic Agent/Team Creation Commands (v4.4)
|
|
229
227
|
# =============================================================================
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
@@ -6,7 +6,6 @@ Copyright 2025 Smart-AI-Memory
|
|
|
6
6
|
Licensed under Fair Source License 0.9
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
import typer
|
|
11
10
|
from rich.console import Console
|
|
12
11
|
from rich.panel import Panel
|
|
@@ -103,7 +102,6 @@ def search_memory(
|
|
|
103
102
|
# =============================================================================
|
|
104
103
|
|
|
105
104
|
|
|
106
|
-
|
|
107
105
|
@meta_workflow_app.command("session-stats")
|
|
108
106
|
def show_session_stats(
|
|
109
107
|
session_id: str | None = typer.Option(
|
|
@@ -177,6 +175,3 @@ def show_session_stats(
|
|
|
177
175
|
except Exception as e:
|
|
178
176
|
console.print(f"[red]Error:[/red] {e}")
|
|
179
177
|
raise typer.Exit(code=1)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
@@ -108,7 +108,6 @@ def list_templates(
|
|
|
108
108
|
raise typer.Exit(code=1)
|
|
109
109
|
|
|
110
110
|
|
|
111
|
-
|
|
112
111
|
@meta_workflow_app.command("inspect")
|
|
113
112
|
def inspect_template(
|
|
114
113
|
template_id: str = typer.Argument(..., help="Template ID to inspect"),
|
|
@@ -207,7 +206,6 @@ def inspect_template(
|
|
|
207
206
|
# =============================================================================
|
|
208
207
|
|
|
209
208
|
|
|
210
|
-
|
|
211
209
|
@meta_workflow_app.command("plan")
|
|
212
210
|
def generate_plan_cmd(
|
|
213
211
|
template_id: str = typer.Argument(..., help="Template ID to generate plan for"),
|
|
@@ -349,6 +347,3 @@ def generate_plan_cmd(
|
|
|
349
347
|
# =============================================================================
|
|
350
348
|
# Execution Commands
|
|
351
349
|
# =============================================================================
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
@@ -6,7 +6,6 @@ Copyright 2025 Smart-AI-Memory
|
|
|
6
6
|
Licensed under Fair Source License 0.9
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
import typer
|
|
11
10
|
from rich.console import Console
|
|
12
11
|
from rich.panel import Panel
|
|
@@ -208,7 +207,6 @@ def run_workflow(
|
|
|
208
207
|
raise typer.Exit(code=1)
|
|
209
208
|
|
|
210
209
|
|
|
211
|
-
|
|
212
210
|
@meta_workflow_app.command("ask")
|
|
213
211
|
def natural_language_run(
|
|
214
212
|
request: str = typer.Argument(..., help="Natural language description of what you need"),
|
|
@@ -317,7 +315,6 @@ def natural_language_run(
|
|
|
317
315
|
raise typer.Exit(code=1)
|
|
318
316
|
|
|
319
317
|
|
|
320
|
-
|
|
321
318
|
@meta_workflow_app.command("detect")
|
|
322
319
|
def detect_intent(
|
|
323
320
|
request: str = typer.Argument(..., help="Natural language request to analyze"),
|
|
@@ -377,6 +374,3 @@ def detect_intent(
|
|
|
377
374
|
# =============================================================================
|
|
378
375
|
# Analytics Commands
|
|
379
376
|
# =============================================================================
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
@@ -30,6 +30,7 @@ def _get_registry():
|
|
|
30
30
|
global _model_registry
|
|
31
31
|
if _model_registry is None:
|
|
32
32
|
from .registry import MODEL_REGISTRY
|
|
33
|
+
|
|
33
34
|
_model_registry = MODEL_REGISTRY
|
|
34
35
|
return _model_registry
|
|
35
36
|
|
|
@@ -241,9 +242,7 @@ class AdaptiveModelRouter:
|
|
|
241
242
|
|
|
242
243
|
return best.model_id
|
|
243
244
|
|
|
244
|
-
def recommend_tier_upgrade(
|
|
245
|
-
self, workflow: str, stage: str
|
|
246
|
-
) -> tuple[bool, str]:
|
|
245
|
+
def recommend_tier_upgrade(self, workflow: str, stage: str) -> tuple[bool, str]:
|
|
247
246
|
"""Check if tier should be upgraded based on failure rate.
|
|
248
247
|
|
|
249
248
|
Analyzes recent telemetry (last 20 calls) for this workflow/stage.
|
|
@@ -335,8 +334,7 @@ class AdaptiveModelRouter:
|
|
|
335
334
|
performance_by_model[model] = {
|
|
336
335
|
"calls": len(model_entries),
|
|
337
336
|
"success_rate": model_successes / len(model_entries),
|
|
338
|
-
"avg_cost": sum(e.get("cost", 0.0) for e in model_entries)
|
|
339
|
-
/ len(model_entries),
|
|
337
|
+
"avg_cost": sum(e.get("cost", 0.0) for e in model_entries) / len(model_entries),
|
|
340
338
|
"avg_latency_ms": sum(e.get("duration_ms", 0) for e in model_entries)
|
|
341
339
|
/ len(model_entries),
|
|
342
340
|
}
|
|
@@ -385,9 +383,7 @@ class AdaptiveModelRouter:
|
|
|
385
383
|
successes = sum(1 for e in model_entries if e.get("success", True))
|
|
386
384
|
success_rate = successes / total
|
|
387
385
|
|
|
388
|
-
avg_latency = (
|
|
389
|
-
sum(e.get("duration_ms", 0) for e in model_entries) / total
|
|
390
|
-
)
|
|
386
|
+
avg_latency = sum(e.get("duration_ms", 0) for e in model_entries) / total
|
|
391
387
|
avg_cost = sum(e.get("cost", 0.0) for e in model_entries) / total
|
|
392
388
|
|
|
393
389
|
# Analyze recent failures (last 20 calls)
|
attune/models/auth_cli.py
CHANGED
|
@@ -123,9 +123,7 @@ def cmd_auth_status(args: Any) -> int:
|
|
|
123
123
|
"✅ Yes\n" if strategy.setup_completed else "❌ No (run 'empathy auth setup')\n"
|
|
124
124
|
)
|
|
125
125
|
|
|
126
|
-
console.print(
|
|
127
|
-
Panel(config_text, title="Authentication Strategy", border_style="blue")
|
|
128
|
-
)
|
|
126
|
+
console.print(Panel(config_text, title="Authentication Strategy", border_style="blue"))
|
|
129
127
|
|
|
130
128
|
# Module size thresholds
|
|
131
129
|
threshold_table = Table(title="Module Size Thresholds", show_header=True)
|
|
@@ -353,9 +351,7 @@ def cmd_auth_recommend(args: Any) -> int:
|
|
|
353
351
|
if cost_estimate["mode"] == "subscription":
|
|
354
352
|
print("Monetary Cost: $0.00")
|
|
355
353
|
print(f"Quota Cost: {cost_estimate['quota_cost']}")
|
|
356
|
-
print(
|
|
357
|
-
f"Fits in 200K: {'Yes' if cost_estimate['fits_in_context'] else 'No'}"
|
|
358
|
-
)
|
|
354
|
+
print(f"Fits in 200K: {'Yes' if cost_estimate['fits_in_context'] else 'No'}")
|
|
359
355
|
else:
|
|
360
356
|
print(f"Monetary Cost: ${cost_estimate['monetary_cost']:.4f}")
|
|
361
357
|
print("Quota Cost: None")
|
|
@@ -396,9 +392,7 @@ Examples:
|
|
|
396
392
|
subparsers.add_parser("setup", help="Run interactive authentication strategy setup")
|
|
397
393
|
|
|
398
394
|
# Status command
|
|
399
|
-
status_parser = subparsers.add_parser(
|
|
400
|
-
"status", help="Show current authentication strategy"
|
|
401
|
-
)
|
|
395
|
+
status_parser = subparsers.add_parser("status", help="Show current authentication strategy")
|
|
402
396
|
status_parser.add_argument(
|
|
403
397
|
"--json", action="store_true", help="Output as JSON instead of formatted table"
|
|
404
398
|
)
|
attune/models/auth_strategy.py
CHANGED
|
@@ -335,7 +335,7 @@ def configure_auth_interactive(module_lines: int = 1000) -> AuthStrategy:
|
|
|
335
335
|
|
|
336
336
|
comparison = strategy.get_pros_cons(module_lines)
|
|
337
337
|
|
|
338
|
-
for
|
|
338
|
+
for _mode_key, mode_data in comparison.items():
|
|
339
339
|
print(f"\n### {mode_data['name']}")
|
|
340
340
|
print(f"Cost: {mode_data['cost']}")
|
|
341
341
|
print("\nPros:")
|
|
@@ -374,9 +374,7 @@ def configure_auth_interactive(module_lines: int = 1000) -> AuthStrategy:
|
|
|
374
374
|
# Show recommendation
|
|
375
375
|
print(f"\n✓ Using {default_mode.value} mode")
|
|
376
376
|
if default_mode == AuthMode.AUTO:
|
|
377
|
-
print(
|
|
378
|
-
f" Small/medium modules (< {strategy.medium_module_threshold} LOC) → Subscription"
|
|
379
|
-
)
|
|
377
|
+
print(f" Small/medium modules (< {strategy.medium_module_threshold} LOC) → Subscription")
|
|
380
378
|
print(f" Large modules (> {strategy.medium_module_threshold} LOC) → API")
|
|
381
379
|
|
|
382
380
|
return strategy
|
|
@@ -42,7 +42,6 @@ def _parse_timestamp(timestamp_str: str) -> datetime:
|
|
|
42
42
|
return dt
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
|
|
46
45
|
class TelemetryBackend(Protocol):
|
|
47
46
|
"""Protocol for telemetry storage backends.
|
|
48
47
|
|
|
@@ -192,5 +191,3 @@ def _parse_timestamp(timestamp_str: str) -> datetime:
|
|
|
192
191
|
dt = dt.replace(tzinfo=None)
|
|
193
192
|
|
|
194
193
|
return dt
|
|
195
|
-
|
|
196
|
-
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Execution strategies for agent composition patterns.
|
|
2
|
+
|
|
3
|
+
This submodule implements the grammar rules 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
|
+
7. Conditional (if X then A else B) - branching based on gates
|
|
11
|
+
|
|
12
|
+
This package provides modular organization of execution strategies:
|
|
13
|
+
- data_classes: AgentResult, StrategyResult
|
|
14
|
+
- conditions: ConditionType, Condition, Branch, ConditionEvaluator
|
|
15
|
+
- nesting: WorkflowReference, InlineWorkflow, NestingContext, WorkflowDefinition
|
|
16
|
+
- base: ExecutionStrategy ABC
|
|
17
|
+
- core_strategies: Sequential, Parallel, Debate, Teaching, Refinement, Adaptive
|
|
18
|
+
- conditional_strategies: Conditional, MultiConditional, Nested, NestedSequential
|
|
19
|
+
- advanced: ToolEnhanced, PromptCached, etc. (TODO)
|
|
20
|
+
|
|
21
|
+
Copyright 2025 Smart-AI-Memory
|
|
22
|
+
Licensed under Fair Source License 0.9
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
# Base strategy class
|
|
28
|
+
from .base import ExecutionStrategy
|
|
29
|
+
|
|
30
|
+
# Conditional and nested strategies
|
|
31
|
+
from .conditional_strategies import (
|
|
32
|
+
ConditionalStrategy,
|
|
33
|
+
MultiConditionalStrategy,
|
|
34
|
+
NestedSequentialStrategy,
|
|
35
|
+
NestedStrategy,
|
|
36
|
+
StepDefinition,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Condition types and evaluator
|
|
40
|
+
from .conditions import Branch, Condition, ConditionEvaluator, ConditionType
|
|
41
|
+
|
|
42
|
+
# Core strategies
|
|
43
|
+
from .core_strategies import (
|
|
44
|
+
AdaptiveStrategy,
|
|
45
|
+
DebateStrategy,
|
|
46
|
+
ParallelStrategy,
|
|
47
|
+
RefinementStrategy,
|
|
48
|
+
SequentialStrategy,
|
|
49
|
+
TeachingStrategy,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Data classes
|
|
53
|
+
from .data_classes import AgentResult, StrategyResult
|
|
54
|
+
|
|
55
|
+
# Nesting support
|
|
56
|
+
from .nesting import (
|
|
57
|
+
WORKFLOW_REGISTRY,
|
|
58
|
+
InlineWorkflow,
|
|
59
|
+
NestingContext,
|
|
60
|
+
WorkflowDefinition,
|
|
61
|
+
WorkflowReference,
|
|
62
|
+
get_workflow,
|
|
63
|
+
register_workflow,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Strategy registry for lookup by name (partial - advanced strategies added in main module)
|
|
67
|
+
_STRATEGY_REGISTRY: dict[str, type[ExecutionStrategy]] = {
|
|
68
|
+
# Core patterns (1-6)
|
|
69
|
+
"sequential": SequentialStrategy,
|
|
70
|
+
"parallel": ParallelStrategy,
|
|
71
|
+
"debate": DebateStrategy,
|
|
72
|
+
"teaching": TeachingStrategy,
|
|
73
|
+
"refinement": RefinementStrategy,
|
|
74
|
+
"adaptive": AdaptiveStrategy,
|
|
75
|
+
# Conditional patterns (7+)
|
|
76
|
+
"conditional": ConditionalStrategy,
|
|
77
|
+
"multi_conditional": MultiConditionalStrategy,
|
|
78
|
+
"nested": NestedStrategy,
|
|
79
|
+
"nested_sequential": NestedSequentialStrategy,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_strategy(strategy_name: str) -> ExecutionStrategy:
|
|
84
|
+
"""Get strategy instance by name.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
strategy_name: Strategy name (e.g., "sequential", "parallel")
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
ExecutionStrategy instance
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If strategy name is invalid
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
>>> strategy = get_strategy("sequential")
|
|
97
|
+
>>> isinstance(strategy, SequentialStrategy)
|
|
98
|
+
True
|
|
99
|
+
"""
|
|
100
|
+
if strategy_name not in _STRATEGY_REGISTRY:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Unknown strategy: {strategy_name}. Available: {list(_STRATEGY_REGISTRY.keys())}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
strategy_class = _STRATEGY_REGISTRY[strategy_name]
|
|
106
|
+
return strategy_class()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def register_strategy(name: str, strategy_class: type[ExecutionStrategy]) -> None:
|
|
110
|
+
"""Register a strategy class by name.
|
|
111
|
+
|
|
112
|
+
Used by main module to add advanced strategies.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
name: Strategy name
|
|
116
|
+
strategy_class: Strategy class
|
|
117
|
+
"""
|
|
118
|
+
_STRATEGY_REGISTRY[name] = strategy_class
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
__all__ = [
|
|
122
|
+
# Data classes
|
|
123
|
+
"AgentResult",
|
|
124
|
+
"StrategyResult",
|
|
125
|
+
# Conditions
|
|
126
|
+
"Branch",
|
|
127
|
+
"Condition",
|
|
128
|
+
"ConditionEvaluator",
|
|
129
|
+
"ConditionType",
|
|
130
|
+
# Nesting
|
|
131
|
+
"InlineWorkflow",
|
|
132
|
+
"NestingContext",
|
|
133
|
+
"WORKFLOW_REGISTRY",
|
|
134
|
+
"WorkflowDefinition",
|
|
135
|
+
"WorkflowReference",
|
|
136
|
+
"get_workflow",
|
|
137
|
+
"register_workflow",
|
|
138
|
+
# Base strategy
|
|
139
|
+
"ExecutionStrategy",
|
|
140
|
+
# Core strategies
|
|
141
|
+
"AdaptiveStrategy",
|
|
142
|
+
"DebateStrategy",
|
|
143
|
+
"ParallelStrategy",
|
|
144
|
+
"RefinementStrategy",
|
|
145
|
+
"SequentialStrategy",
|
|
146
|
+
"TeachingStrategy",
|
|
147
|
+
# Conditional strategies
|
|
148
|
+
"ConditionalStrategy",
|
|
149
|
+
"MultiConditionalStrategy",
|
|
150
|
+
"NestedSequentialStrategy",
|
|
151
|
+
"NestedStrategy",
|
|
152
|
+
"StepDefinition",
|
|
153
|
+
# Functions
|
|
154
|
+
"get_strategy",
|
|
155
|
+
"register_strategy",
|
|
156
|
+
]
|