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.
Files changed (120) hide show
  1. attune/cli/__init__.py +3 -59
  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 +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 +467 -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 +655 -0
  31. attune/memory/short_term/pagination.py +215 -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 +249 -0
  38. attune/memory/short_term/timelines.py +234 -0
  39. attune/memory/short_term/transactions.py +186 -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/models/adaptive_routing.py +4 -8
  49. attune/models/auth_cli.py +3 -9
  50. attune/models/auth_strategy.py +2 -4
  51. attune/models/telemetry/analytics.py +0 -2
  52. attune/models/telemetry/backend.py +0 -3
  53. attune/models/telemetry/storage.py +0 -2
  54. attune/orchestration/_strategies/__init__.py +156 -0
  55. attune/orchestration/_strategies/base.py +231 -0
  56. attune/orchestration/_strategies/conditional_strategies.py +373 -0
  57. attune/orchestration/_strategies/conditions.py +369 -0
  58. attune/orchestration/_strategies/core_strategies.py +491 -0
  59. attune/orchestration/_strategies/data_classes.py +64 -0
  60. attune/orchestration/_strategies/nesting.py +233 -0
  61. attune/orchestration/execution_strategies.py +58 -1567
  62. attune/orchestration/meta_orchestrator.py +1 -3
  63. attune/project_index/scanner.py +1 -3
  64. attune/project_index/scanner_parallel.py +7 -5
  65. attune/socratic_router.py +1 -3
  66. attune/telemetry/agent_coordination.py +9 -3
  67. attune/telemetry/agent_tracking.py +16 -3
  68. attune/telemetry/approval_gates.py +22 -5
  69. attune/telemetry/cli.py +1 -3
  70. attune/telemetry/commands/dashboard_commands.py +24 -8
  71. attune/telemetry/event_streaming.py +8 -2
  72. attune/telemetry/feedback_loop.py +10 -2
  73. attune/tools.py +1 -0
  74. attune/workflow_commands.py +1 -3
  75. attune/workflows/__init__.py +53 -10
  76. attune/workflows/autonomous_test_gen.py +158 -102
  77. attune/workflows/base.py +48 -672
  78. attune/workflows/batch_processing.py +1 -3
  79. attune/workflows/compat.py +156 -0
  80. attune/workflows/cost_mixin.py +141 -0
  81. attune/workflows/data_classes.py +92 -0
  82. attune/workflows/document_gen/workflow.py +11 -14
  83. attune/workflows/history.py +62 -37
  84. attune/workflows/llm_base.py +1 -3
  85. attune/workflows/migration.py +422 -0
  86. attune/workflows/output.py +2 -7
  87. attune/workflows/parsing_mixin.py +427 -0
  88. attune/workflows/perf_audit.py +3 -1
  89. attune/workflows/progress.py +9 -11
  90. attune/workflows/release_prep.py +5 -1
  91. attune/workflows/routing.py +0 -2
  92. attune/workflows/secure_release.py +2 -1
  93. attune/workflows/security_audit.py +19 -14
  94. attune/workflows/security_audit_phase3.py +28 -22
  95. attune/workflows/seo_optimization.py +27 -27
  96. attune/workflows/test_gen/test_templates.py +1 -4
  97. attune/workflows/test_gen/workflow.py +0 -2
  98. attune/workflows/test_gen_behavioral.py +6 -19
  99. attune/workflows/test_gen_parallel.py +6 -4
  100. {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/METADATA +4 -3
  101. {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/RECORD +116 -91
  102. {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/entry_points.txt +0 -2
  103. attune_healthcare/monitors/monitoring/__init__.py +9 -9
  104. attune_llm/agent_factory/__init__.py +6 -6
  105. attune_llm/commands/__init__.py +10 -10
  106. attune_llm/commands/models.py +3 -3
  107. attune_llm/config/__init__.py +8 -8
  108. attune_llm/learning/__init__.py +3 -3
  109. attune_llm/learning/extractor.py +5 -3
  110. attune_llm/learning/storage.py +5 -3
  111. attune_llm/security/__init__.py +17 -17
  112. attune_llm/utils/tokens.py +3 -1
  113. attune/cli_legacy.py +0 -3978
  114. attune/memory/short_term.py +0 -2192
  115. attune/workflows/manage_docs.py +0 -87
  116. attune/workflows/test5.py +0 -125
  117. {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/WHEEL +0 -0
  118. {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/licenses/LICENSE +0 -0
  119. {attune_ai-2.1.5.dist-info → attune_ai-2.2.0.dist-info}/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +0 -0
  120. {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,6 +6,9 @@ Copyright 2025 Smart-AI-Memory
6
6
  Licensed under Fair Source License 0.9
7
7
  """
8
8
 
9
+ # ruff: noqa: E402
10
+ # Typer app must be created before importing commands that use it
11
+
9
12
  import typer
10
13
 
11
14
  # Create Typer app for meta-workflow commands
@@ -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
  )
@@ -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 mode_key, mode_data in comparison.items():
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
@@ -590,5 +590,3 @@ class TelemetryAnalytics:
590
590
 
591
591
  # Singleton for global telemetry
592
592
  _telemetry_store: TelemetryStore | None = None
593
-
594
-
@@ -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
-
@@ -485,5 +485,3 @@ class TelemetryStore:
485
485
  results.append(record)
486
486
 
487
487
  return results
488
-
489
-
@@ -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
+ ]