empathy-framework 3.9.3__py3-none-any.whl → 3.10.2__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.
- {empathy_framework-3.9.3.dist-info → empathy_framework-3.10.2.dist-info}/METADATA +37 -5
- {empathy_framework-3.9.3.dist-info → empathy_framework-3.10.2.dist-info}/RECORD +12 -12
- empathy_os/cache/hybrid.py +69 -9
- empathy_os/cli.py +183 -18
- empathy_os/cli_unified.py +28 -3
- empathy_os/models/telemetry.py +900 -2
- empathy_os/workflows/health_check.py +37 -0
- empathy_os/workflows/new_sample_workflow1.py +3 -3
- {empathy_framework-3.9.3.dist-info → empathy_framework-3.10.2.dist-info}/WHEEL +0 -0
- {empathy_framework-3.9.3.dist-info → empathy_framework-3.10.2.dist-info}/entry_points.txt +0 -0
- {empathy_framework-3.9.3.dist-info → empathy_framework-3.10.2.dist-info}/licenses/LICENSE +0 -0
- {empathy_framework-3.9.3.dist-info → empathy_framework-3.10.2.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: empathy-framework
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.10.2
|
|
4
4
|
Summary: AI collaboration framework with intelligent caching (up to 57% cache hit rate), tier routing (34-86% cost savings depending on task complexity), XML-enhanced prompts, persistent memory, CrewAI integration, and multi-agent orchestration. Includes HIPAA-compliant healthcare wizards.
|
|
5
5
|
Author-email: Patrick Roebuck <admin@smartaimemory.com>
|
|
6
6
|
Maintainer-email: Smart-AI-Memory <admin@smartaimemory.com>
|
|
@@ -178,7 +178,7 @@ Requires-Dist: pyyaml<7.0,>=6.0
|
|
|
178
178
|
Requires-Dist: anthropic<1.0.0,>=0.25.0
|
|
179
179
|
Requires-Dist: crewai<1.0.0,>=0.1.0
|
|
180
180
|
Requires-Dist: langchain<2.0.0,>=0.1.0
|
|
181
|
-
Requires-Dist: langchain-core<2.0.0,>=
|
|
181
|
+
Requires-Dist: langchain-core<2.0.0,>=1.2.6
|
|
182
182
|
Provides-Extra: anthropic
|
|
183
183
|
Requires-Dist: anthropic<1.0.0,>=0.25.0; extra == "anthropic"
|
|
184
184
|
Provides-Extra: openai
|
|
@@ -368,7 +368,41 @@ Dynamic: license-file
|
|
|
368
368
|
pip install empathy-framework[developer] # Lightweight for individual developers
|
|
369
369
|
```
|
|
370
370
|
|
|
371
|
-
## What's New in v3.
|
|
371
|
+
## What's New in v3.10.0 (Current Release)
|
|
372
|
+
|
|
373
|
+
### 🎯 **Intelligent Tier Fallback: Start CHEAP, Upgrade Only When Needed**
|
|
374
|
+
|
|
375
|
+
**Automatic cost optimization with quality-based tier escalation.**
|
|
376
|
+
|
|
377
|
+
- ✅ **30-50% cost savings** on average workflow execution
|
|
378
|
+
- ✅ **CHEAP → CAPABLE → PREMIUM** automatic fallback chain
|
|
379
|
+
- ✅ **Quality gates** validate each tier before upgrading
|
|
380
|
+
- ✅ **Opt-in design** - backward compatible, enabled via `--use-recommended-tier`
|
|
381
|
+
- ✅ **Full telemetry** tracks tier progression and savings
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
# Enable intelligent tier fallback
|
|
385
|
+
empathy workflow run health-check --use-recommended-tier
|
|
386
|
+
|
|
387
|
+
# Result: Both stages succeeded at CHEAP tier
|
|
388
|
+
# 💰 Cost Savings: $0.0300 (66.7% vs. all-PREMIUM)
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**How it works:**
|
|
392
|
+
1. Try CHEAP tier first (Haiku)
|
|
393
|
+
2. If quality gates fail → upgrade to CAPABLE (Sonnet 4.5)
|
|
394
|
+
3. If still failing → upgrade to PREMIUM (Opus 4.5)
|
|
395
|
+
4. Track savings and learn from patterns
|
|
396
|
+
|
|
397
|
+
**When to use:** Cost-sensitive workflows where quality can be validated (health-check, test-gen, doc-gen)
|
|
398
|
+
|
|
399
|
+
See [CHANGELOG.md](https://github.com/Smart-AI-Memory/empathy-framework/blob/main/CHANGELOG.md#3100---2026-01-09) for full details.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
### Previous Releases
|
|
404
|
+
|
|
405
|
+
#### v3.9.0
|
|
372
406
|
|
|
373
407
|
### 🔒 **Security Hardening: 174 Security Tests (Up from 14)**
|
|
374
408
|
|
|
@@ -407,8 +441,6 @@ See [SECURITY.md](https://github.com/Smart-AI-Memory/empathy-framework/blob/main
|
|
|
407
441
|
|
|
408
442
|
---
|
|
409
443
|
|
|
410
|
-
### Previous Releases
|
|
411
|
-
|
|
412
444
|
#### v3.8.3
|
|
413
445
|
|
|
414
446
|
### 🎯 **Transparent Cost Claims: Honest Role-Based Savings (34-86%)**
|
|
@@ -20,7 +20,7 @@ coach_wizards/refactoring_wizard.py,sha256=X0MTx3BHpOlOMAYDow-3HX5GyryY70JGAF5vA
|
|
|
20
20
|
coach_wizards/scaling_wizard.py,sha256=n1RLtpWmj1RSEGSWssMiUPwCdpskO3z2Z3yhLlTdXro,2598
|
|
21
21
|
coach_wizards/security_wizard.py,sha256=19SOClSxo6N-QqUc_QsFXOE7yEquiZF4kLi7jRomA7g,2605
|
|
22
22
|
coach_wizards/testing_wizard.py,sha256=vKFgFG4uJfAVFmCIQbkrWNvZhIfLC6ve_XbvWZKrPg4,2563
|
|
23
|
-
empathy_framework-3.
|
|
23
|
+
empathy_framework-3.10.2.dist-info/licenses/LICENSE,sha256=IJ9eeI5KSrD5P7alsn7sI_6_1bDihxBA5S4Sen4jf2k,4937
|
|
24
24
|
empathy_healthcare_plugin/__init__.py,sha256=4NioL1_86UXzkd-QNkQZUSZ8rKTQGSP0TC9VXP32kQs,295
|
|
25
25
|
empathy_healthcare_plugin/monitors/__init__.py,sha256=Udp8qfZR504QAq5_eQjvtIaE7v06Yguc7nuF40KllQc,196
|
|
26
26
|
empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py,sha256=GkNh2Yuw9cvuKuPh3mriWtKJZFq_sTxBD7Ci8lFV9gQ,11620
|
|
@@ -95,8 +95,8 @@ empathy_llm_toolkit/wizards/patient_assessment_wizard.py,sha256=dsvoOq0AYCBigmn6
|
|
|
95
95
|
empathy_llm_toolkit/wizards/technology_wizard.py,sha256=8hQirzzGQp7UVtj1hFCoaoLLtqAtx9HFf4mdUWV1xH0,7533
|
|
96
96
|
empathy_os/__init__.py,sha256=zAvie0wQFlSpv7thmNNtdrPy0YY1NUaQfb1oD_hKmns,5896
|
|
97
97
|
empathy_os/agent_monitoring.py,sha256=s4seLC_J4AtQ3PYWrRPO8YHM-Fbm0Q36kPEdlTHf2HI,13375
|
|
98
|
-
empathy_os/cli.py,sha256=
|
|
99
|
-
empathy_os/cli_unified.py,sha256=
|
|
98
|
+
empathy_os/cli.py,sha256=2xeJgb3EZ9PT9Uc-4V6X_8WIcPRlGFklVTf3QkE5yk0,116971
|
|
99
|
+
empathy_os/cli_unified.py,sha256=o6l1nokC5Ku8nvEDAEBJoP_p63g5xbVWz_JKTSFNsgE,27181
|
|
100
100
|
empathy_os/config.py,sha256=jsFQuXpZP_jt6UrZtfLlkBZwPUSNbnW5VVtMS5JIhbA,16276
|
|
101
101
|
empathy_os/coordination.py,sha256=E2HvHxKk1xbYswtgxhnVKB6DRxfXUV5pCt-XWHOvNKM,28509
|
|
102
102
|
empathy_os/core.py,sha256=PvrPs8JQPwQjPZZQj6zy6xQTFGHKziaeRxCIlIs3iHA,52951
|
|
@@ -124,7 +124,7 @@ empathy_os/cache/__init__.py,sha256=nKHyWnQmyBU6f24UOq5iZ4ULUFys5Dhl6dFZlU0jlHU,
|
|
|
124
124
|
empathy_os/cache/base.py,sha256=1waj2OZ_5gRhwYhSfsSfgQs1N_ftZcAq6UAV9RctSek,3968
|
|
125
125
|
empathy_os/cache/dependency_manager.py,sha256=Khwpi4LiT5xFp1Pka4XQHO0bLzWUG1ElpRQ_Zmup_iM,7614
|
|
126
126
|
empathy_os/cache/hash_only.py,sha256=CdyZvl81lbargRyVhWVBJB4VDUNay4KIf3knUmqvZCo,7427
|
|
127
|
-
empathy_os/cache/hybrid.py,sha256=
|
|
127
|
+
empathy_os/cache/hybrid.py,sha256=HSqStILYY6YhfyKGnYkrjYnGpOjHemMEF7YaKivtbQU,15264
|
|
128
128
|
empathy_os/cache/storage.py,sha256=VrvvpAeQF29T-d6Ly6L-bqvu-XHFJRWICb-gfiC0Hqw,7659
|
|
129
129
|
empathy_os/config/__init__.py,sha256=jkdGF6eWEqOayld3Xcl1tsk92YIXIBXI-QcecMUBbXE,1577
|
|
130
130
|
empathy_os/config/xml_config.py,sha256=QOnFV0rshX-7xoUSadLNDCltz5DZwCi8UvC0y2Pqt-w,8672
|
|
@@ -165,7 +165,7 @@ empathy_os/models/fallback.py,sha256=6xerVS4VHaFB9JC0XnPEGHMrYo9CLACYnCx1QPu3zC8
|
|
|
165
165
|
empathy_os/models/provider_config.py,sha256=-r-q-Yiy1gjBp3ovP7VXz3bEJ6hjVaoPCw5tzBFpKHQ,19463
|
|
166
166
|
empathy_os/models/registry.py,sha256=tBLCzjZeb_wuzAPL5aMXI2VIl3jhXWvprc_whfn0ah8,12987
|
|
167
167
|
empathy_os/models/tasks.py,sha256=XyS0IN6kadLpyC26sMiX9FEXgfP2j57AYPJO2oH4sKU,8691
|
|
168
|
-
empathy_os/models/telemetry.py,sha256=
|
|
168
|
+
empathy_os/models/telemetry.py,sha256=bsiUAtI-6zHpTtnWpfHLYgsRuQeF8KmJFJyIlUJfyoM,45558
|
|
169
169
|
empathy_os/models/token_estimator.py,sha256=hUeWH0oJvp2UbYRLMsgDBGQRzICfLQxzPi_rsqP6fkE,12576
|
|
170
170
|
empathy_os/models/validation.py,sha256=K7Pki8jKmQ3zKG2CSJtx4Bv7cfn9l5g6aIxXWdMA4cs,8925
|
|
171
171
|
empathy_os/monitoring/__init__.py,sha256=efAzUX0DYeUTGEES-MV6jI4iUYmhH-RkQYnVLnjSl-M,1507
|
|
@@ -237,9 +237,9 @@ empathy_os/workflows/config.py,sha256=qahi2OOs-5ahaZd4LWkH2uX0N8YV9KxY-o6popeS4I
|
|
|
237
237
|
empathy_os/workflows/dependency_check.py,sha256=zWwSCAWThnAUkOqEUx2GTl5Vgf2fW9P5HRqwWhYZXlc,23452
|
|
238
238
|
empathy_os/workflows/document_gen.py,sha256=z9jVtGYCvl01oNk2JfelQs4CfFMw35t1vo9IIeC6sWQ,39135
|
|
239
239
|
empathy_os/workflows/documentation_orchestrator.py,sha256=sSzTsLJPkSlHcE_o-pfM-27DVAVpwPcmxPZT71GYfeo,43013
|
|
240
|
-
empathy_os/workflows/health_check.py,sha256=
|
|
240
|
+
empathy_os/workflows/health_check.py,sha256=0OOfL7OyTfQYL1uspwAdTqMdWZkM6zQauE5Gn2v9qNg,26067
|
|
241
241
|
empathy_os/workflows/manage_documentation.py,sha256=gknIse4MzTLxRowIAS07WSXNqWAoWCfxmoIJSbTYBNM,29419
|
|
242
|
-
empathy_os/workflows/new_sample_workflow1.py,sha256=
|
|
242
|
+
empathy_os/workflows/new_sample_workflow1.py,sha256=vG4s_Dnn61y7qe1PEwjIBkKpXh6afpEVXp8orCI_EOo,3977
|
|
243
243
|
empathy_os/workflows/new_sample_workflow1_README.md,sha256=bzLyqukgaKilG1OnCdLsc5GNWsYEagI7mn3n80BPMHY,2366
|
|
244
244
|
empathy_os/workflows/perf_audit.py,sha256=JzJjuU5WQAa7Eisdwmwz4CQTB5W5v-mphVJ_yhORM5A,25275
|
|
245
245
|
empathy_os/workflows/pr_review.py,sha256=H6YukqPrHYA8uDmddISYy0PVp8axmaoeBeB6dZ0GpLE,26193
|
|
@@ -334,8 +334,8 @@ workflow_scaffolding/__init__.py,sha256=UpX5vjjjPjIaAKyIV1D4GxJzLUZy5DzdzgSkePYM
|
|
|
334
334
|
workflow_scaffolding/__main__.py,sha256=0qspuNoadTDqyskXTlT8Sahqau-XIxN35NHTSGVW6z4,236
|
|
335
335
|
workflow_scaffolding/cli.py,sha256=R4rCTDENRMil2c3v32MnisqteFRDfilS6RHBNlYV39Q,6752
|
|
336
336
|
workflow_scaffolding/generator.py,sha256=whWbBmWEA0rN3M3X9EzTjfbwBxHcF40Jin8-nbj0S0E,8858
|
|
337
|
-
empathy_framework-3.
|
|
338
|
-
empathy_framework-3.
|
|
339
|
-
empathy_framework-3.
|
|
340
|
-
empathy_framework-3.
|
|
341
|
-
empathy_framework-3.
|
|
337
|
+
empathy_framework-3.10.2.dist-info/METADATA,sha256=pQcro0TjRgxx_66gSv4KDQfX61zzBhlNlHsxR9LKu88,51251
|
|
338
|
+
empathy_framework-3.10.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
339
|
+
empathy_framework-3.10.2.dist-info/entry_points.txt,sha256=zMu7sKCiLndbEEXjTecltS-1P_JZoEUKrifuRBBbroc,1268
|
|
340
|
+
empathy_framework-3.10.2.dist-info/top_level.txt,sha256=wrNU1aVMutVDACer58H-udv0P_171Dv6z_42sZtZ-xM,124
|
|
341
|
+
empathy_framework-3.10.2.dist-info/RECORD,,
|
empathy_os/cache/hybrid.py
CHANGED
|
@@ -15,11 +15,13 @@ Licensed under Fair Source License 0.9
|
|
|
15
15
|
import hashlib
|
|
16
16
|
import logging
|
|
17
17
|
import time
|
|
18
|
+
from pathlib import Path
|
|
18
19
|
from typing import TYPE_CHECKING, Any
|
|
19
20
|
|
|
20
21
|
import numpy as np
|
|
21
22
|
|
|
22
23
|
from .base import BaseCache, CacheEntry, CacheStats
|
|
24
|
+
from .storage import CacheStorage
|
|
23
25
|
|
|
24
26
|
if TYPE_CHECKING:
|
|
25
27
|
from sentence_transformers import SentenceTransformer
|
|
@@ -77,6 +79,7 @@ class HybridCache(BaseCache):
|
|
|
77
79
|
similarity_threshold: float = 0.95,
|
|
78
80
|
model_name: str = "all-MiniLM-L6-v2",
|
|
79
81
|
device: str = "cpu",
|
|
82
|
+
cache_dir: Path | None = None,
|
|
80
83
|
):
|
|
81
84
|
"""Initialize hybrid cache.
|
|
82
85
|
|
|
@@ -87,6 +90,7 @@ class HybridCache(BaseCache):
|
|
|
87
90
|
similarity_threshold: Semantic similarity threshold (0.0-1.0, default: 0.95).
|
|
88
91
|
model_name: Sentence transformer model (default: all-MiniLM-L6-v2).
|
|
89
92
|
device: Device for embeddings ("cpu" or "cuda").
|
|
93
|
+
cache_dir: Directory for persistent cache storage (default: ~/.empathy/cache/).
|
|
90
94
|
|
|
91
95
|
"""
|
|
92
96
|
super().__init__(max_size_mb, default_ttl)
|
|
@@ -106,9 +110,16 @@ class HybridCache(BaseCache):
|
|
|
106
110
|
self._model: SentenceTransformer | None = None
|
|
107
111
|
self._load_model()
|
|
108
112
|
|
|
113
|
+
# Initialize persistent storage
|
|
114
|
+
self._storage = CacheStorage(cache_dir=cache_dir, max_disk_mb=max_size_mb)
|
|
115
|
+
|
|
116
|
+
# Load existing entries from storage into memory caches
|
|
117
|
+
self._load_from_storage()
|
|
118
|
+
|
|
109
119
|
logger.info(
|
|
110
120
|
f"HybridCache initialized (model: {model_name}, threshold: {similarity_threshold}, "
|
|
111
|
-
f"device: {device}, max_memory: {max_memory_mb}MB
|
|
121
|
+
f"device: {device}, max_memory: {max_memory_mb}MB, "
|
|
122
|
+
f"loaded: {len(self._hash_cache)} entries from disk)"
|
|
112
123
|
)
|
|
113
124
|
|
|
114
125
|
def _load_model(self) -> None:
|
|
@@ -131,6 +142,34 @@ class HybridCache(BaseCache):
|
|
|
131
142
|
logger.warning("Falling back to hash-only mode")
|
|
132
143
|
self._model = None
|
|
133
144
|
|
|
145
|
+
def _load_from_storage(self) -> None:
|
|
146
|
+
"""Load cached entries from persistent storage into memory caches."""
|
|
147
|
+
try:
|
|
148
|
+
# Get all non-expired entries from storage
|
|
149
|
+
entries = self._storage.get_all()
|
|
150
|
+
|
|
151
|
+
if not entries:
|
|
152
|
+
logger.debug("No cached entries found in storage")
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
# Populate hash cache
|
|
156
|
+
for entry in entries:
|
|
157
|
+
self._hash_cache[entry.key] = entry
|
|
158
|
+
self._access_times[entry.key] = entry.timestamp
|
|
159
|
+
|
|
160
|
+
logger.info(f"Loaded {len(entries)} entries from persistent storage into hash cache")
|
|
161
|
+
|
|
162
|
+
# Populate semantic cache if model available
|
|
163
|
+
if self._model is not None:
|
|
164
|
+
logger.debug("Generating embeddings for cached prompts...")
|
|
165
|
+
# Note: We don't have the original prompts, so semantic cache
|
|
166
|
+
# will be populated on-demand as cache hits occur
|
|
167
|
+
# This is acceptable since semantic matching is secondary to hash matching
|
|
168
|
+
logger.debug("Semantic cache will be populated on-demand from hash hits")
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.warning(f"Failed to load cache from storage: {e}, starting with empty cache")
|
|
172
|
+
|
|
134
173
|
def get(
|
|
135
174
|
self,
|
|
136
175
|
workflow: str,
|
|
@@ -257,7 +296,7 @@ class HybridCache(BaseCache):
|
|
|
257
296
|
response: Any,
|
|
258
297
|
ttl: int | None = None,
|
|
259
298
|
) -> None:
|
|
260
|
-
"""Store response in both hash and semantic caches.
|
|
299
|
+
"""Store response in both hash and semantic caches, and persist to disk.
|
|
261
300
|
|
|
262
301
|
Args:
|
|
263
302
|
workflow: Workflow name.
|
|
@@ -295,14 +334,26 @@ class HybridCache(BaseCache):
|
|
|
295
334
|
prompt_embedding = self._model.encode(prompt, convert_to_numpy=True)
|
|
296
335
|
self._semantic_cache.append((prompt_embedding, entry))
|
|
297
336
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
337
|
+
# Persist to disk storage
|
|
338
|
+
try:
|
|
339
|
+
self._storage.put(entry)
|
|
340
|
+
logger.debug(
|
|
341
|
+
f"Cache PUT (hybrid): {workflow}/{stage} "
|
|
342
|
+
f"(hash_entries: {len(self._hash_cache)}, "
|
|
343
|
+
f"semantic_entries: {len(self._semantic_cache)}, "
|
|
344
|
+
f"persisted: True)"
|
|
345
|
+
)
|
|
346
|
+
except Exception as e:
|
|
347
|
+
logger.warning(f"Failed to persist cache entry to disk: {e}")
|
|
348
|
+
logger.debug(
|
|
349
|
+
f"Cache PUT (hybrid): {workflow}/{stage} "
|
|
350
|
+
f"(hash_entries: {len(self._hash_cache)}, "
|
|
351
|
+
f"semantic_entries: {len(self._semantic_cache)}, "
|
|
352
|
+
f"persisted: False)"
|
|
353
|
+
)
|
|
303
354
|
|
|
304
355
|
def clear(self) -> None:
|
|
305
|
-
"""Clear all cached entries."""
|
|
356
|
+
"""Clear all cached entries from memory and disk."""
|
|
306
357
|
hash_count = len(self._hash_cache)
|
|
307
358
|
semantic_count = len(self._semantic_cache)
|
|
308
359
|
|
|
@@ -310,7 +361,16 @@ class HybridCache(BaseCache):
|
|
|
310
361
|
self._access_times.clear()
|
|
311
362
|
self._semantic_cache.clear()
|
|
312
363
|
|
|
313
|
-
|
|
364
|
+
# Clear persistent storage
|
|
365
|
+
try:
|
|
366
|
+
storage_count = self._storage.clear()
|
|
367
|
+
logger.info(
|
|
368
|
+
f"Cache cleared (hash: {hash_count}, semantic: {semantic_count}, "
|
|
369
|
+
f"storage: {storage_count} entries)"
|
|
370
|
+
)
|
|
371
|
+
except Exception as e:
|
|
372
|
+
logger.warning(f"Failed to clear persistent storage: {e}")
|
|
373
|
+
logger.info(f"Cache cleared (hash: {hash_count}, semantic: {semantic_count} entries)")
|
|
314
374
|
|
|
315
375
|
def get_stats(self) -> CacheStats:
|
|
316
376
|
"""Get cache statistics."""
|
empathy_os/cli.py
CHANGED
|
@@ -41,11 +41,15 @@ from empathy_os.workflows import list_workflows as get_workflow_list
|
|
|
41
41
|
# Import telemetry CLI commands
|
|
42
42
|
try:
|
|
43
43
|
from empathy_os.telemetry.cli import (
|
|
44
|
+
cmd_agent_performance,
|
|
45
|
+
cmd_task_routing_report,
|
|
44
46
|
cmd_telemetry_compare,
|
|
45
47
|
cmd_telemetry_export,
|
|
46
48
|
cmd_telemetry_reset,
|
|
47
49
|
cmd_telemetry_savings,
|
|
48
50
|
cmd_telemetry_show,
|
|
51
|
+
cmd_test_status,
|
|
52
|
+
cmd_tier1_status,
|
|
49
53
|
)
|
|
50
54
|
|
|
51
55
|
TELEMETRY_CLI_AVAILABLE = True
|
|
@@ -2148,7 +2152,20 @@ def cmd_workflow(args):
|
|
|
2148
2152
|
|
|
2149
2153
|
wf_config = WorkflowConfig.load()
|
|
2150
2154
|
provider = wf_config.default_provider
|
|
2151
|
-
|
|
2155
|
+
|
|
2156
|
+
# Initialize workflow with tier fallback if requested
|
|
2157
|
+
use_tier_fallback = getattr(args, "use_recommended_tier", False)
|
|
2158
|
+
workflow_kwargs = {
|
|
2159
|
+
"provider": provider,
|
|
2160
|
+
"enable_tier_fallback": use_tier_fallback,
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
# Add health-check specific parameters
|
|
2164
|
+
if name == "health-check":
|
|
2165
|
+
health_score_threshold = getattr(args, "health_score_threshold", 100)
|
|
2166
|
+
workflow_kwargs["health_score_threshold"] = health_score_threshold
|
|
2167
|
+
|
|
2168
|
+
workflow = workflow_cls(**workflow_kwargs)
|
|
2152
2169
|
|
|
2153
2170
|
# Parse input
|
|
2154
2171
|
input_data = {}
|
|
@@ -2244,25 +2261,76 @@ def cmd_workflow(args):
|
|
|
2244
2261
|
}
|
|
2245
2262
|
print(json_mod.dumps(output, indent=2))
|
|
2246
2263
|
# Display the actual results - this is what users want to see
|
|
2247
|
-
elif result.success:
|
|
2248
|
-
if output_content:
|
|
2249
|
-
print(f"\n{output_content}\n")
|
|
2250
|
-
else:
|
|
2251
|
-
print("\n✓ Workflow completed successfully.\n")
|
|
2252
2264
|
else:
|
|
2253
|
-
#
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2265
|
+
# Show tier progression if tier fallback was used
|
|
2266
|
+
if use_tier_fallback and hasattr(workflow, "_tier_progression"):
|
|
2267
|
+
tier_progression = workflow._tier_progression
|
|
2268
|
+
if tier_progression:
|
|
2269
|
+
print("\n" + "=" * 60)
|
|
2270
|
+
print(" TIER PROGRESSION (Intelligent Fallback)")
|
|
2271
|
+
print("=" * 60)
|
|
2272
|
+
|
|
2273
|
+
# Group by stage
|
|
2274
|
+
stage_tiers: dict[str, list[tuple[str, bool]]] = {}
|
|
2275
|
+
for stage, tier, success in tier_progression:
|
|
2276
|
+
if stage not in stage_tiers:
|
|
2277
|
+
stage_tiers[stage] = []
|
|
2278
|
+
stage_tiers[stage].append((tier, success))
|
|
2279
|
+
|
|
2280
|
+
# Display progression for each stage
|
|
2281
|
+
for stage, attempts in stage_tiers.items():
|
|
2282
|
+
status = "✓" if any(success for _, success in attempts) else "✗"
|
|
2283
|
+
print(f"\n{status} Stage: {stage}")
|
|
2284
|
+
|
|
2285
|
+
for idx, (tier, success) in enumerate(attempts, 1):
|
|
2286
|
+
attempt_status = "✓ SUCCESS" if success else "✗ FAILED"
|
|
2287
|
+
if idx == 1:
|
|
2288
|
+
print(f" Attempt {idx}: {tier.upper():8} → {attempt_status}")
|
|
2289
|
+
else:
|
|
2290
|
+
prev_tier = attempts[idx - 2][0]
|
|
2291
|
+
print(
|
|
2292
|
+
f" Attempt {idx}: {tier.upper():8} → {attempt_status} "
|
|
2293
|
+
f"(upgraded from {prev_tier.upper()})"
|
|
2294
|
+
)
|
|
2295
|
+
|
|
2296
|
+
# Calculate cost savings (only if result has stages attribute)
|
|
2297
|
+
if hasattr(result, "stages") and result.stages:
|
|
2298
|
+
actual_cost = sum(stage.cost for stage in result.stages if stage.cost)
|
|
2299
|
+
# Estimate what cost would be if all stages used PREMIUM
|
|
2300
|
+
premium_cost = actual_cost * 3 # Conservative estimate
|
|
2301
|
+
|
|
2302
|
+
savings = premium_cost - actual_cost
|
|
2303
|
+
savings_pct = (savings / premium_cost * 100) if premium_cost > 0 else 0
|
|
2304
|
+
|
|
2305
|
+
print("\n" + "-" * 60)
|
|
2306
|
+
print("💰 Cost Savings:")
|
|
2307
|
+
print(f" Actual cost: ${actual_cost:.4f}")
|
|
2308
|
+
print(f" Premium cost: ${premium_cost:.4f} (if all PREMIUM)")
|
|
2309
|
+
print(f" Savings: ${savings:.4f} ({savings_pct:.1f}%)")
|
|
2310
|
+
print("=" * 60 + "\n")
|
|
2311
|
+
|
|
2312
|
+
# Display workflow result
|
|
2313
|
+
if result.success:
|
|
2314
|
+
if output_content:
|
|
2315
|
+
print(f"\n{output_content}\n")
|
|
2260
2316
|
else:
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2317
|
+
print("\n✓ Workflow completed successfully.\n")
|
|
2318
|
+
else:
|
|
2319
|
+
# Extract error from various result types
|
|
2320
|
+
error_msg = getattr(result, "error", None)
|
|
2321
|
+
if not error_msg:
|
|
2322
|
+
# Check for blockers (CodeReviewPipelineResult)
|
|
2323
|
+
blockers = getattr(result, "blockers", [])
|
|
2324
|
+
if blockers:
|
|
2325
|
+
error_msg = "; ".join(blockers)
|
|
2326
|
+
else:
|
|
2327
|
+
# Check metadata for error
|
|
2328
|
+
metadata = getattr(result, "metadata", {})
|
|
2329
|
+
error_msg = (
|
|
2330
|
+
metadata.get("error") if isinstance(metadata, dict) else None
|
|
2331
|
+
)
|
|
2332
|
+
error_msg = error_msg or "Unknown error"
|
|
2333
|
+
print(f"\n✗ Workflow failed: {error_msg}\n")
|
|
2266
2334
|
|
|
2267
2335
|
except KeyError as e:
|
|
2268
2336
|
print(f"Error: {e}")
|
|
@@ -2436,6 +2504,38 @@ def _cmd_telemetry_export(args):
|
|
|
2436
2504
|
return cmd_telemetry_export(args)
|
|
2437
2505
|
|
|
2438
2506
|
|
|
2507
|
+
def _cmd_tier1_status(args):
|
|
2508
|
+
"""Wrapper for tier1 status command."""
|
|
2509
|
+
if not TELEMETRY_CLI_AVAILABLE:
|
|
2510
|
+
print("Tier 1 monitoring commands not available. Install telemetry dependencies.")
|
|
2511
|
+
return 1
|
|
2512
|
+
return cmd_tier1_status(args)
|
|
2513
|
+
|
|
2514
|
+
|
|
2515
|
+
def _cmd_task_routing_report(args):
|
|
2516
|
+
"""Wrapper for task routing report command."""
|
|
2517
|
+
if not TELEMETRY_CLI_AVAILABLE:
|
|
2518
|
+
print("Tier 1 monitoring commands not available. Install telemetry dependencies.")
|
|
2519
|
+
return 1
|
|
2520
|
+
return cmd_task_routing_report(args)
|
|
2521
|
+
|
|
2522
|
+
|
|
2523
|
+
def _cmd_test_status(args):
|
|
2524
|
+
"""Wrapper for test status command."""
|
|
2525
|
+
if not TELEMETRY_CLI_AVAILABLE:
|
|
2526
|
+
print("Tier 1 monitoring commands not available. Install telemetry dependencies.")
|
|
2527
|
+
return 1
|
|
2528
|
+
return cmd_test_status(args)
|
|
2529
|
+
|
|
2530
|
+
|
|
2531
|
+
def _cmd_agent_performance(args):
|
|
2532
|
+
"""Wrapper for agent performance command."""
|
|
2533
|
+
if not TELEMETRY_CLI_AVAILABLE:
|
|
2534
|
+
print("Tier 1 monitoring commands not available. Install telemetry dependencies.")
|
|
2535
|
+
return 1
|
|
2536
|
+
return cmd_agent_performance(args)
|
|
2537
|
+
|
|
2538
|
+
|
|
2439
2539
|
def main():
|
|
2440
2540
|
"""Main CLI entry point"""
|
|
2441
2541
|
# Configure Windows-compatible asyncio event loop policy
|
|
@@ -2929,6 +3029,60 @@ def main():
|
|
|
2929
3029
|
)
|
|
2930
3030
|
parser_telemetry_export.set_defaults(func=lambda args: _cmd_telemetry_export(args))
|
|
2931
3031
|
|
|
3032
|
+
# Tier 1 automation monitoring commands
|
|
3033
|
+
|
|
3034
|
+
# tier1 command - comprehensive status
|
|
3035
|
+
parser_tier1 = subparsers.add_parser(
|
|
3036
|
+
"tier1",
|
|
3037
|
+
help="Show Tier 1 automation status (tasks, tests, coverage, agents)",
|
|
3038
|
+
)
|
|
3039
|
+
parser_tier1.add_argument(
|
|
3040
|
+
"--hours",
|
|
3041
|
+
type=int,
|
|
3042
|
+
default=24,
|
|
3043
|
+
help="Hours to analyze (default: 24)",
|
|
3044
|
+
)
|
|
3045
|
+
parser_tier1.set_defaults(func=lambda args: _cmd_tier1_status(args))
|
|
3046
|
+
|
|
3047
|
+
# tasks command - task routing report
|
|
3048
|
+
parser_tasks = subparsers.add_parser(
|
|
3049
|
+
"tasks",
|
|
3050
|
+
help="Show task routing report",
|
|
3051
|
+
)
|
|
3052
|
+
parser_tasks.add_argument(
|
|
3053
|
+
"--hours",
|
|
3054
|
+
type=int,
|
|
3055
|
+
default=24,
|
|
3056
|
+
help="Hours to analyze (default: 24)",
|
|
3057
|
+
)
|
|
3058
|
+
parser_tasks.set_defaults(func=lambda args: _cmd_task_routing_report(args))
|
|
3059
|
+
|
|
3060
|
+
# tests command - test execution status
|
|
3061
|
+
parser_tests = subparsers.add_parser(
|
|
3062
|
+
"tests",
|
|
3063
|
+
help="Show test execution status",
|
|
3064
|
+
)
|
|
3065
|
+
parser_tests.add_argument(
|
|
3066
|
+
"--hours",
|
|
3067
|
+
type=int,
|
|
3068
|
+
default=24,
|
|
3069
|
+
help="Hours to analyze (default: 24)",
|
|
3070
|
+
)
|
|
3071
|
+
parser_tests.set_defaults(func=lambda args: _cmd_test_status(args))
|
|
3072
|
+
|
|
3073
|
+
# agents command - agent performance
|
|
3074
|
+
parser_agents = subparsers.add_parser(
|
|
3075
|
+
"agents",
|
|
3076
|
+
help="Show agent performance metrics",
|
|
3077
|
+
)
|
|
3078
|
+
parser_agents.add_argument(
|
|
3079
|
+
"--hours",
|
|
3080
|
+
type=int,
|
|
3081
|
+
default=168,
|
|
3082
|
+
help="Hours to analyze (default: 168 / 7 days)",
|
|
3083
|
+
)
|
|
3084
|
+
parser_agents.set_defaults(func=lambda args: _cmd_agent_performance(args))
|
|
3085
|
+
|
|
2932
3086
|
# New command (project scaffolding)
|
|
2933
3087
|
parser_new = subparsers.add_parser("new", help="Create a new project from a template")
|
|
2934
3088
|
parser_new.add_argument(
|
|
@@ -3018,6 +3172,11 @@ def main():
|
|
|
3018
3172
|
help="Force overwrite existing config file",
|
|
3019
3173
|
)
|
|
3020
3174
|
parser_workflow.add_argument("--json", action="store_true", help="Output as JSON")
|
|
3175
|
+
parser_workflow.add_argument(
|
|
3176
|
+
"--use-recommended-tier",
|
|
3177
|
+
action="store_true",
|
|
3178
|
+
help="Enable intelligent tier fallback: start with CHEAP tier and automatically upgrade if quality gates fail",
|
|
3179
|
+
)
|
|
3021
3180
|
parser_workflow.add_argument(
|
|
3022
3181
|
"--write-tests",
|
|
3023
3182
|
action="store_true",
|
|
@@ -3028,6 +3187,12 @@ def main():
|
|
|
3028
3187
|
default="tests/generated",
|
|
3029
3188
|
help="(test-gen workflow) Output directory for generated tests",
|
|
3030
3189
|
)
|
|
3190
|
+
parser_workflow.add_argument(
|
|
3191
|
+
"--health-score-threshold",
|
|
3192
|
+
type=int,
|
|
3193
|
+
default=95,
|
|
3194
|
+
help="(health-check workflow) Minimum health score required (0-100, default: 95 for very strict quality)",
|
|
3195
|
+
)
|
|
3031
3196
|
parser_workflow.set_defaults(func=cmd_workflow)
|
|
3032
3197
|
|
|
3033
3198
|
# Sync-claude command (sync patterns to Claude Code)
|
empathy_os/cli_unified.py
CHANGED
|
@@ -462,11 +462,36 @@ def workflow_list():
|
|
|
462
462
|
def workflow_run(
|
|
463
463
|
name: str = typer.Argument(..., help="Workflow name"),
|
|
464
464
|
path: Path = typer.Option(Path(), "--path", "-p", help="Path to run on"),
|
|
465
|
+
use_recommended_tier: bool = typer.Option(
|
|
466
|
+
False,
|
|
467
|
+
"--use-recommended-tier",
|
|
468
|
+
help="Enable intelligent tier fallback: start with CHEAP tier and automatically upgrade if quality gates fail",
|
|
469
|
+
),
|
|
470
|
+
health_score_threshold: int = typer.Option(
|
|
471
|
+
95,
|
|
472
|
+
"--health-score-threshold",
|
|
473
|
+
help="(health-check workflow) Minimum health score required (0-100, default: 95 for very strict quality)",
|
|
474
|
+
),
|
|
465
475
|
):
|
|
466
476
|
"""Run a multi-model workflow."""
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
477
|
+
cmd = [
|
|
478
|
+
sys.executable,
|
|
479
|
+
"-m",
|
|
480
|
+
"empathy_os.cli",
|
|
481
|
+
"workflow",
|
|
482
|
+
"run",
|
|
483
|
+
name,
|
|
484
|
+
"--input",
|
|
485
|
+
f'{{"path": "{path}"}}',
|
|
486
|
+
]
|
|
487
|
+
|
|
488
|
+
if use_recommended_tier:
|
|
489
|
+
cmd.append("--use-recommended-tier")
|
|
490
|
+
|
|
491
|
+
if health_score_threshold != 95:
|
|
492
|
+
cmd.extend(["--health-score-threshold", str(health_score_threshold)])
|
|
493
|
+
|
|
494
|
+
subprocess.run(cmd, check=False)
|
|
470
495
|
|
|
471
496
|
|
|
472
497
|
@workflow_app.command("create")
|