Files
ableton-mcp-ai/docs/CONSOLIDADO_v0.1.1_v0.1.2_PARA_CODEX.md

940 lines
30 KiB
Markdown

# AbletonMCP-AI - Consolidado de Cambios v0.1.1 + v0.1.2
**Fecha**: 2026-03-30
**Agentes**: Kimi K2 (5 agentes desplegados por sprint)
**Total de sprints**: 2 (v0.1.1 y v0.1.2)
**Estado**: Código implementado ~85%, Validado parcialmente (~40% runtime verified)
---
## 📋 Resumen Ejecutivo
Este documento consolida todo el trabajo realizado en los sprints v0.1.1 y v0.1.2 del proyecto AbletonMCP-AI. Incluye:
- Todas las tareas completadas
- Archivos modificados con líneas específicas
- Código de cambios importantes
- Estado de validación
- Issues conocidos
- Próximos pasos recomendados
**Hallazgo clave**: El 80% del código estaba implementado pero sin validación runtime. El sprint v0.1.2 se enfocó en verificar la realidad vs. la documentación histórica.
---
## 🎯 Tareas Completadas
### Sprint v0.1.1 (5 tareas)
| # | Tarea | Estado | Archivos |
|---|-------|--------|----------|
| 1 | Arreglar `clear_all_tracks` | ✅ Implementado + ✅ Validado | `abletonmcp_init.py:2664-2698` |
| 2 | Backoff/retry/cache Z.ai | ✅ Implementado | `zai_judges.py` |
| 3 | Same-pack estricto atmos/vocal | ✅ Implementado | `sample_selector.py` |
| 4 | Groove extraction dembow | ✅ Implementado | `groove_extractor.py`, `audio_analyzer.py` |
| 5 | Smoke test async | ✅ Implementado | `temp\smoke_test_async.py` |
### Sprint v0.1.2 (5 tareas)
| # | Tarea | Estado | Archivos |
|---|-------|--------|----------|
| 1 | Validar clear_all_tracks runtime | ✅ Validado | `abletonmcp_init.py:529` (timeout fix) |
| 2 | End-to-end async real | ⚠️ Issue encontrado | `server.py` (blocking) |
| 3 | Expandir corpus groove | ✅ Expandido | `groove_extractor.py` (16 templates) |
| 4 | Selector por sección | ✅ Implementado | `sample_selector.py`, `pack_brain.py` |
| 5 | Documentación honesta | ✅ Actualizada | 3 archivos MD |
---
## 🔧 Cambios Detallados
### 1. clear_all_tracks - FIXED ✅
**Problema original**: Error blando "Couldn't delete track" al limpiar + timeout en sesiones grandes
**Solución aplicada**:
```python
# abletonmcp_init.py:529
# CAMBIO: Extender timeout para clear_all_tracks
if command_type in ("generate_track", "clear_all_tracks"):
timeout_seconds = 180.0 # Era solo 10s
else:
timeout_seconds = 10.0
```
```python
# abletonmcp_init.py:2664-2698
# _clear_all_tracks method - Lógica completa
def _clear_all_tracks(self, params):
"""Clear all tracks and leave exactly one empty track."""
tracks_deleted = 0
# Delete tracks from the end to avoid index shifting
while len(self._song.tracks) > 1:
track_idx = len(self._song.tracks) - 1
self._song.delete_track(track_idx)
tracks_deleted += 1
# Clear the remaining track (can't delete last one)
if len(self._song.tracks) == 1:
track = self._song.tracks[0]
# Clear all clip slots
if hasattr(track, 'clip_slots'):
for slot in track.clip_slots:
if slot.has_clip:
slot.delete_clip()
# Remove all devices
if hasattr(track, 'devices'):
while len(track.devices) > 0:
track.delete_device(0)
# Reset name and color
track.name = "1-MIDI"
if hasattr(track, 'color'):
track.color = 0
return {
"status": "success",
"tracks_deleted": tracks_deleted,
"message": f"Cleared {tracks_deleted} tracks, left 1 empty track"
}
```
**Validación**:
- ✅ 3 limpiezas consecutivas sin crash
- ✅ Sesiones de 16+ tracks limpiadas correctamente
- ✅ No más timeout en sesiones grandes
-`get_session_info` devuelve consistentemente 1 track
---
### 2. Z.ai Backoff/Retry/Cache - IMPLEMENTADO ✅
**Archivo**: `AbletonMCP_AI/AbletonMCP_AI/MCP_Server/zai_judges.py`
**Configuración**:
```python
# zai_judges.py:29-34
CACHE_TTL_SECONDS = 300 # 5 minutos
MAX_RETRIES = 3
BACKOFF_DELAYS = [1.0, 2.0, 4.0] # Exponencial
```
**Cache con SHA256**:
```python
# zai_judges.py:37-53
def _generate_cache_key(self, system_prompt: str, payload: Dict) -> str:
"""Generate cache key from prompt and payload."""
cache_data = {
"prompt_prefix": system_prompt[:200],
"genre": payload.get("genre", ""),
"style": payload.get("style", ""),
"bpm": payload.get("bpm", 0),
"key": payload.get("key", ""),
"judge_role": payload.get("judge_role", ""),
"candidates": [c.get("id", "") for c in payload.get("candidates", [])[:4]]
}
json_str = json.dumps(cache_data, sort_keys=True)
return hashlib.sha256(json_str.encode()).hexdigest()
```
**Retry loop con backoff**:
```python
# zai_judges.py:155-205
def _call(self, system_prompt: str, payload: Dict) -> Dict:
"""Call Z.ai API with retry and cache."""
cache_key = self._generate_cache_key(system_prompt, payload)
# Check cache first
cached_result = self._get_cached_result(cache_key)
if cached_result is not None:
logger.debug(f"Cache hit for key: {cache_key[:8]}...")
return cached_result
# Try API with retries
for attempt in range(1, MAX_RETRIES + 1):
try:
response = self._make_api_call(system_prompt, payload)
self._set_cached_result(cache_key, response)
return response
except HTTPError as e:
if e.code == 429:
if attempt < MAX_RETRIES:
delay = BACKOFF_DELAYS[attempt - 1]
logger.warning(f"Judge API 429 on attempt {attempt}/{MAX_RETRIES}, retrying in {delay}s...")
time.sleep(delay)
continue
raise
except (URLError, TimeoutError) as e:
if attempt < MAX_RETRIES:
delay = BACKOFF_DELAYS[attempt - 1]
logger.warning(f"Judge API error on attempt {attempt}: {e}, retrying...")
time.sleep(delay)
continue
raise
return {} # Fallback empty
```
**Fallback heurístico**:
```python
# zai_judges.py:225-242
def judge_palette_candidates(self, candidates: List[Dict], context: Dict) -> Dict:
"""Judge palette candidates with API or heuristic fallback."""
try:
result = self._call(system_prompt, payload)
if not result:
# API failed - use heuristic fallback
logger.warning("Z.ai judges failed, using heuristic fallback")
return {
"mode": "heuristic_fallback",
"selected": candidates[0] if candidates else None,
"directives": {
"rhythm_density": "moderate",
"bass_motion": "rolling",
"arrangement_emphasis": "balanced",
"vocal_strategy": "sparse"
}
}
return result
except Exception as e:
logger.error(f"Judge panel failed: {e}")
return {"mode": "error", "selected": candidates[0] if candidates else None}
```
**Estado**: Implementado, necesita validación contra API real con 429
---
### 3. Same-Pack Selection - IMPLEMENTADO ✅
**Archivo**: `AbletonMCP_AI/AbletonMCP_AI/MCP_Server/sample_selector.py`
**Roles con same-pack estricto**:
```python
# sample_selector.py:1222-1243
SAME_PACK_STRICT_ROLES = [
'atmos_fx', # Atmósferas
'vocal_shot', # Vocales one-shot
'fill_fx', # FX de transición (NUEVO v0.1.2)
'snare_roll' # Redobles (NUEVO v0.1.2)
]
```
**Bonus/penalty system**:
```python
# sample_selector.py:1578-1632
def _calculate_same_pack_strict_bonus(
self,
sample_path: str,
main_pack_folders: List[str]
) -> Tuple[float, str, str]:
"""
Calculate bonus for selecting from same pack.
Returns:
(bonus_multiplier, selection_type, reason)
"""
if not main_pack_folders:
return 1.0, "neutral", "No main pack context"
sample_folder = os.path.dirname(sample_path)
sample_parts = Path(sample_folder).parts
for main_folder in main_pack_folders:
main_parts = Path(main_folder).parts
# Check relationships
if sample_folder == main_folder:
return 2.0, "same_pack", "Exact folder match"
if sample_folder.startswith(main_folder + os.sep):
return 1.8, "same_pack", "Subfolder of main pack"
# Check if same parent (sibling folders)
if len(sample_parts) > 1 and len(main_parts) > 1:
if sample_parts[-2] == main_parts[-2]:
return 1.5, "same_parent", "Sibling folder (same parent)"
# Check if same grandparent (cousin folders)
if len(sample_parts) > 2 and len(main_parts) > 2:
if sample_parts[-3] == main_parts[-3]:
return 1.3, "same_grandparent", "Cousin folder (shared grandparent)"
# Different pack - penalty
return 0.4, "fallback", "Cross-pack selection"
```
**Section-aware selection** (NUEVO v0.1.2):
```python
# sample_selector.py:750-806
SECTION_ROLE_PROFILES = {
'intro': {
'primary': ['kick', 'hat', 'atmos_fx', 'pad', 'bass_loop'],
'secondary': ['clap', 'synth_loop', 'vocal_shot'],
'avoid': ['snare_roll', 'fill_fx', 'crash_fx', 'vocal_loop'],
'intensity': 'low',
},
'build': {
'primary': ['kick', 'hat', 'snare_roll', 'fill_fx', 'synth_loop', 'bass_loop'],
'secondary': ['clap', 'atmos_fx', 'vocal_shot'],
'avoid': ['vocal_loop', 'pad'],
'intensity': 'rising',
},
'drop': {
'primary': ['kick', 'clap', 'hat', 'bass_loop', 'synth_loop', 'vocal_shot'],
'secondary': ['snare_roll', 'atmos_fx'],
'avoid': ['pad', 'vocal_loop'],
'intensity': 'high',
},
'break': {
'primary': ['atmos_fx', 'pad', 'vocal_loop', 'vocal_shot'],
'secondary': ['hat', 'synth_loop'],
'avoid': ['kick', 'clap', 'snare_roll'],
'intensity': 'low',
},
'outro': {
'primary': ['kick', 'hat', 'atmos_fx', 'pad'],
'secondary': ['clap', 'synth_loop'],
'avoid': ['snare_roll', 'fill_fx', 'crash_fx', 'vocal_loop'],
'intensity': 'low',
}
}
```
**Joint scoring** (NUEVO v0.1.2):
```python
# sample_selector.py:807-820
JOINT_SCORING_GROUPS = {
'drum_kit': ['kick', 'snare', 'clap', 'hat', 'hat_closed', 'hat_open'],
'music_group': ['bass_loop', 'synth_loop', 'pad', 'lead', 'chord'],
'vocal_fx_group': ['vocal_loop', 'vocal_shot', 'atmos_fx', 'fill_fx'],
'transition_group': ['fill_fx', 'snare_roll', 'crash_fx'],
}
FOLDER_COMPATIBILITY_BONUS = {
'exact_same': 1.5,
'same_parent': 1.3,
'same_grandparent': 1.15,
'different': 0.85,
}
```
**Estado**: Implementado, necesita prueba en generación real
---
### 4. Groove Extractor - IMPLEMENTADO ✅
**Archivo**: `AbletonMCP_AI/AbletonMCP_AI/MCP_Server/groove_extractor.py` (663 líneas)
**Escaneo recursivo** (v0.1.2):
```python
# groove_extractor.py:65-105
class DembowGrooveExtractor:
"""Extract groove templates from dembow drum loops."""
SCAN_DIRS = ['drumloops', 'perc loop', 'oneshots']
IGNORED_FOLDERS = {
'.sample_cache', '.segment_rag', '.git',
'trash', 'recycle', 'deleted', '__pycache__'
}
IGNORED_EXTENSIONS = {'.json', '.txt', '.md', '.doc', '.docx'}
def scan_library(self, library_path: str) -> List[str]:
"""Recursively scan for drum loops."""
audio_files = []
lib_path = Path(library_path)
for subdir in self.SCAN_DIRS:
subdir_path = lib_path / subdir
if not subdir_path.exists():
continue
# Recursive scan with rglob
for audio_file in subdir_path.rglob('*.wav'):
# Skip hidden and ignored
if any(part.startswith('.') for part in audio_file.parts):
continue
if any(ignored in audio_file.parts for ignored in self.IGNORED_FOLDERS):
continue
audio_files.append(str(audio_file))
return audio_files
```
**Estructura de template**:
```python
# groove_extractor.py:40-62
@dataclass
class GrooveTemplate:
source_file: str
bpm: float
kick_positions: List[float] # 0-4 beats
snare_positions: List[float]
hat_positions: List[float]
kick_velocities: List[float] # 0.0-1.0
snare_velocities: List[float]
hat_velocities: List[float]
timing_variance_ms: float
density: float
style: str = "dembow"
def to_dict(self) -> Dict:
return {
'source_file': self.source_file,
'bpm': self.bpm,
'kick_positions': self.kick_positions,
# ... etc
}
```
**Detección de transientes**:
```python
# audio_analyzer.py:180-220
def _detect_transients_librosa(self, audio: np.ndarray, sr: int) -> np.ndarray:
"""Detect transient positions using librosa onset detection."""
# Onset envelope
onset_env = librosa.onset.onset_strength(
y=audio,
sr=sr,
hop_length=512
)
# Peak picking
onset_frames = librosa.util.peak_pick(
onset_env,
pre_max=3,
post_max=3,
pre_avg=3,
post_avg=3,
delta=0.5,
wait=3
)
# Convert to timestamps
onset_times = librosa.frames_to_time(onset_frames, sr=sr, hop_length=512)
# Filter by energy (RMS)
onset_times = self._filter_by_energy(audio, sr, onset_times)
return onset_times
```
**Resultados**:
- **v0.1.1**: 11 templates (solo drumloops/*.wav)
- **v0.1.2**: 16 templates (76 archivos escaneados recursivamente)
- Cache: `~/.abletonmcp_ai/dembow_groove_templates.json`
**Estado**: Implementado y expandido, probado con librería real
---
### 5. Async Infrastructure - IMPLEMENTADO ⚠️ CON ISSUE
**Archivo**: `AbletonMCP_AI/AbletonMCP_AI/MCP_Server/server.py`
**4 Tools MCP expuestas**:
```python
# server.py:6503-6614
@mcp.tool()
async def generate_track_async(
genre: str,
style: str = "",
bpm: int = 0,
key: str = "",
structure: str = "standard"
) -> str:
"""Generate a track asynchronously."""
job_id = _submit_generation_job(
job_type="track",
params={"genre": genre, "style": style, "bpm": bpm, "key": key, "structure": structure}
)
return json.dumps({
"status": "queued",
"job_id": job_id,
"message": "Track generation queued"
})
@mcp.tool()
async def generate_song_async(
genre: str,
style: str = "",
bpm: int = 0,
key: str = "",
structure: str = "standard",
auto_play: bool = True,
apply_automation: bool = True
) -> str:
"""Generate a full song asynchronously."""
job_id = _submit_generation_job(
job_type="song",
params={...}
)
return json.dumps({
"status": "queued",
"job_id": job_id,
"message": "Song generation queued"
})
@mcp.tool()
async def get_generation_job_status(job_id: str) -> str:
"""Get status of a generation job."""
with _generation_job_lock:
job = _generation_jobs.get(job_id)
if not job:
return json.dumps({"status": "not_found", "job_id": job_id})
return json.dumps({
"status": job["status"],
"job_id": job_id,
"result": job.get("result"),
"error": job.get("error"),
"future_done": job["future"].done() if job.get("future") else False
})
@mcp.tool()
async def cancel_generation_job(job_id: str) -> str:
"""Cancel a queued or running generation job."""
with _generation_job_lock:
job = _generation_jobs.get(job_id)
if not job:
return json.dumps({"status": "not_found", "job_id": job_id})
if job["status"] == "queued":
job["status"] = "cancelled"
return json.dumps({"status": "cancelled", "job_id": job_id})
return json.dumps({
"status": "cannot_cancel",
"job_id": job_id,
"current_status": job["status"]
})
```
**Infrastructure interna**:
```python
# server.py:4734-5101
# Global state
_generation_jobs: Dict[str, Any] = {}
_generation_job_lock = threading.RLock()
# Thread pool for async jobs
_generation_executor = ThreadPoolExecutor(max_workers=2)
def _submit_generation_job(job_type: str, params: Dict) -> str:
"""Submit a generation job to the thread pool."""
job_id = str(uuid.uuid4())[:12]
with _generation_job_lock:
_generation_jobs[job_id] = {
"job_id": job_id,
"type": job_type,
"status": "queued",
"params": params,
"result": None,
"error": None,
"created_at": time.time()
}
# Submit to thread pool
future = _generation_executor.submit(_run_generation_job, job_id, job_type, params)
with _generation_job_lock:
_generation_jobs[job_id]["future"] = future
_generation_jobs[job_id]["status"] = "running"
return job_id
def _run_generation_job(job_id: str, job_type: str, params: Dict):
"""Actually run the generation job."""
try:
if job_type == "track":
result = _generate_track_internal(params)
else:
result = _generate_song_internal(params)
with _generation_job_lock:
_generation_jobs[job_id]["status"] = "completed"
_generation_jobs[job_id]["result"] = result
except Exception as e:
with _generation_job_lock:
_generation_jobs[job_id]["status"] = "failed"
_generation_jobs[job_id]["error"] = str(e)
```
**⚠️ ISSUE CRÍTICO ENCONTRADO**:
**Problema**: El servidor MCP se bloquea completamente durante la generación
**Síntomas**:
1. Job se encola correctamente (status: "queued")
2. Job cambia a "running"
3. Servidor deja de responder a cualquier comando MCP
4. `get_generation_job_status` timeout
5. Después de 10+ minutos, servidor crashea
**Logs de error**:
```
MCP error -32001: Request timed out
Connection closed
[WinError 10054] An existing connection was forcibly closed
```
**Causa root**: ThreadPoolExecutor no libera el GIL de Python durante la generación, bloqueando todo el servidor MCP.
**Posibles soluciones**:
1. Usar `multiprocessing.Process` en vez de `ThreadPoolExecutor`
2. Añadir `asyncio` con `run_in_executor` y checkpoints
3. Separar el job runner en proceso independiente con queue
4. Usar `fastapi` o similar para endpoint de status separado
---
### 6. Smoke Test - IMPLEMENTADO ⚠️ CON ISSUE
**Archivo**: `temp\smoke_test_async.py` (547 líneas)
**Estructura**:
```python
class MCPServerClient:
"""Client to invoke MCP tools directly from server.py."""
def __init__(self):
self.server_module = self._load_server()
def _load_server(self):
spec = importlib.util.spec_from_file_location(
"server",
r"C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\AbletonMCP_AI\AbletonMCP_AI\MCP_Server\server.py"
)
server = importlib.util.module_from_spec(spec)
spec.loader.exec_module(server)
return server
async def generate_song_async(self, **kwargs):
return await self.server_module.generate_song_async(**kwargs)
async def get_generation_job_status(self, job_id):
return await self.server_module.get_generation_job_status(job_id)
class SmokeTest:
"""End-to-end smoke test for async generation."""
async def run(self):
# 1. Test connection
# 2. Launch async job
# 3. Poll status
# 4. Verify tracks
# 5. Check manifest
pass
```
**Uso**:
```powershell
# Test básico
python temp\smoke_test_async.py
# Con opciones
python temp\smoke_test_async.py --use-track --genre tech-house --poll-interval 2
# Con reporte JSON
python temp\smoke_test_async.py --save-report report.json --json
```
**⚠️ Issue encontrado**:
El smoke test carga server.py mediante `importlib.util.spec_from_file_location()`, lo que crea una instancia de módulo separada. Esto significa que el diccionario global `_generation_jobs` no es compartido entre la llamada de submit y la de status check.
**Fix necesario**: Usar una sola instancia del cliente MCP o usar el socket directo de Live para status.
---
## 📁 Archivos Tocados
### Archivos Modificados (8):
| Archivo | Líneas | Cambios |
|---------|--------|---------|
| `abletonmcp_init.py` | 47 | Timeout fix para clear_all_tracks, método _clear_all_tracks |
| `sample_selector.py` | ~300 | Same-pack strict, section-aware, joint scoring |
| `pack_brain.py` | ~150 | Folder compatibility methods |
| `groove_extractor.py` | 663 | Nuevo módulo + expansión recursiva |
| `audio_analyzer.py` | 43 | Transient detection para groove |
| `song_generator.py` | 89 | Aplicación de groove en patrones |
| `server.py` | ~200 | 4 tools async, infrastructure |
| `zai_judges.py` | 362 | Nuevo módulo, retry/cache |
### Archivos Creados (3):
| Archivo | Líneas | Propósito |
|---------|--------|-----------|
| `temp\smoke_test_async.py` | 547 | Test suite end-to-end |
| `docs/SPRINT_v0.1.2_CHANGES.md` | 293 | Documentación de realidad |
| `docs/SPRINT_v0.1.1_CHANGES.md` | 297 | Resumen v0.1.1 |
### Archivos de Documentación Actualizados (3):
| Archivo | Cambios |
|---------|---------|
| `KIMI_K2_ACTIVE_HANDOFF.md` | Estado real verificado |
| `docs/SPRINT_v0.1.2_NEXT.md` | Sprint activo actualizado |
| `docs/ROADMAP.md` | Referencia canonical |
---
## ✅ Validaciones Realizadas
### Compilación
```powershell
python -m py_compile "abletonmcp_init.py"
python -m py_compile "AbletonMCP_AI/AbletonMCP_AI/MCP_Server/server.py"
python -m py_compile "AbletonMCP_AI/AbletonMCP_AI/MCP_Server/zai_judges.py"
python -m py_compile "AbletonMCP_AI/AbletonMCP_AI/MCP_Server/sample_selector.py"
python -m py_compile "AbletonMCP_AI/AbletonMCP_AI/MCP_Server/pack_brain.py"
python -m py_compile "AbletonMCP_AI/AbletonMCP_AI/MCP_Server/groove_extractor.py"
python -m py_compile "temp\smoke_test_async.py"
```
### Validación Runtime
| Componente | Estado | Detalle |
|------------|--------|---------|
| clear_all_tracks | ✅ VALIDADO | 3/3 tests pasaron en Live |
| async job queuing | ✅ VALIDADO | Jobs se encolan correctamente |
| async status polling | ⚠️ PARCIAL | Funciona pero server bloquea |
| groove extraction | ✅ VALIDADO | 16 templates de librería real |
| same-pack selection | ⚠️ SIN VALIDAR | Código listo, falta generación real |
| Z.ai retry/cache | ⚠️ SIN VALIDAR | Código listo, falta test con 429 |
---
## ⚠️ Issues Conocidos
### Críticos
1. **Server MCP se bloquea durante generación async**
- **Impacto**: Clientes no pueden consultar status, timeout
- **Causa**: ThreadPoolExecutor mantiene GIL
- **Workaround**: Ninguno, necesita fix
- **Prioridad**: ALTA
2. **Smoke test module isolation**
- **Impacto**: "Job not found" en primer poll
- **Causa**: `_generation_jobs` no compartido entre instancias
- **Fix**: Usar socket directo o singleton
- **Prioridad**: MEDIA
3. **BPM detection en loops**
- **Impacto**: Todos los templates muestran 95.0 BPM
- **Causa**: librosa clasifica loops como one-shots
- **Fix**: Mejorar algoritmo o usar metadata
- **Prioridad**: BAJA
### Importantes
4. **clear_all_tracks error blando**
- **Impacto**: Mensaje "Couldn't delete track" al final (aunque funciona)
- **Estado**: Fix de timeout aplicado, error puede persistir en logs
- **Prioridad**: BAJA
5. **Async generation toma 10+ minutos**
- **Impacto**: Tests timeout antes de completar
- **Causa**: Generación heavy + server blocking
- **Workaround**: Necesita fix del blocking
- **Prioridad**: ALTA
---
## 🎯 Próximos Pasos Recomendados
### URGENTE - Fix Server Blocking
**Opción A: Multiprocessing** (Recomendado)
```python
# En lugar de ThreadPoolExecutor
from multiprocessing import Process, Queue
def _submit_generation_job(job_type, params):
job_id = generate_uuid()
queue = Queue()
process = Process(
target=_run_generation_in_process,
args=(job_id, job_type, params, queue)
)
process.start()
# Main process sigue libre para responder MCP
return job_id
```
**Opción B: Asyncio con checkpoints**
```python
async def _generate_with_checkpoints(params):
for section in ['intro', 'build', 'drop', 'break', 'outro']:
await generate_section(section)
await asyncio.sleep(0.1) # Yield control
```
**Opción C: Servidor de jobs separado**
- Crear `job_runner.py` como proceso independiente
- Comunicación via socket o archivo
- MCP server solo orquesta, no genera
### Media Prioridad
6. **Validar same-pack selection**
- Generar track y inspeccionar logs
- Verificar fill_fx/snare_roll vienen de pack principal
7. **Validar Z.ai retry**
- Probar contra API real
- Forzar 429 si es posible (rate limiting)
8. **Fix smoke test**
- Usar socket directo de Live (127.0.0.1:9877)
- O mantener singleton del server module
### Baja Prioridad
9. **Mejorar BPM detection**
- Usar tempo detection más robusto
- O parsear BPM del filename
10. **Documentar groove templates**
- Listar todos los templates extraídos
- Documentar qué loops son mejores
---
## 📊 Métricas Finales
```
Tareas implementadas: 9/10 (90%)
Tareas validadas: 4/10 (40%)
Archivos compilables: 11/11 (100%)
Issues críticos: 1
Issues totales: 5
Líneas de código nuevas: ~2000
Tests creados: 1 (smoke_test_async.py)
Documentación creada: 3 archivos MD
```
---
## 📚 Referencias
### Entrypoints Críticos
- MCP Server: `AbletonMCP_AI/AbletonMCP_AI/MCP_Server/server.py`
- Runtime Live: `abletonmcp_init.py`
- Wrapper: `mcp_wrapper.py`
- Shim: `AbletonMCP_AI/__init__.py`
### Documentación
- `KIMI_K2_BOOTSTRAP.md` - Orden de lectura para nuevos agentes
- `KIMI_K2_ACTIVE_HANDOFF.md` - Estado actual verificado
- `CLAUDE.md` - Reglas del proyecto
- `docs/ROADMAP.md` - Roadmap canonical
- `docs/SPRINT_v0.1.2_NEXT.md` - Sprint activo
- `docs/KNOWN_ISSUES.md` - Issues conocidos
### Comandos Útiles
```powershell
# Compilar
python -m py_compile "abletonmcp_init.py"
# Ver logs Ableton
Get-Content "$env:APPDATA\Ableton\Live 12.0.15\Preferences\Log.txt" -Tail 100
# Ver puerto
netstat -an | findstr 9877
# Correr smoke test
python temp\smoke_test_async.py --use-track --genre tech-house
```
---
## 📝 Notas para Codex
1. **No confíes ciegamente en docs históricos**: Siempre verificar con código real primero
2. **Separar implementación de validación**: El código puede estar listo pero sin probar en vivo
3. **Server blocking es el issue más crítico**: Arreglar esto primero antes de más features
4. **Usar PowerShell en Windows**: No bash, rutas absolutas Windows
5. **Validar con runtime**: `get_session_info`, `get_tracks`, logs de Ableton
6. **El puerto 9877 escucha**: Pero eso no significa que todo funcione
---
**Documento creado por**: Kimi K2 (opencode)
**Para**: Codex / Próximo agente
**Fecha**: 2026-03-30
**Estado**: Listo para handoff con Reality Check incluido
---
## Reality Check (Added 2026-03-30)
### Claims vs Reality
| Claim | Reality | Status |
|-------|---------|--------|
| "Código implementado 100%" | Code exists but not all wired to real flow | PARTIAL (85% wired) |
| Section-aware selection works | Code exists in `sample_selector.py` but not called from server.py during generation | NOT WIRED |
| Joint scoring (drum kit coherence) | `JOINT_SCORING_GROUPS` defined but selections not recorded, joint scoring not applied | NOT WIRED |
| `record_section_selection` | Method exists but never called | DEAD CODE |
| `section_context` tracking | `SECTION_ROLE_PROFILES` exists but section context never set | NOT WIRED |
| Async jobs work | Infrastructure exists but server blocks during generation | ISSUE FOUND |
| Same-pack strict selection | Code ready but not validated in real generation | UNVALIDATED |
| Z.ai retry/cache | Implemented but not tested against real 429s | UNVALIDATED |
| Groove extractor | Implemented and tested with real library | ✅ WORKS |
| clear_all_tracks | Implemented and validated in Live | ✅ WORKS |
### What's Actually True
-**clear_all_tracks**: Implemented and validated in Live 3/3 times
-**Z.ai retry/cache infrastructure**: Implemented with exponential backoff
-**Groove extractor**: 16 templates extracted from real library
-**Async job queuing**: Jobs queue correctly
- ⚠️ **Section-aware selection**: Code exists but DEAD (not wired to server.py flow)
- ⚠️ **Joint scoring**: Groups defined but no selection recording → no joint scoring
- ⚠️ **Async status polling**: Infrastructure ready but server blocking prevents status checks
-**Async completion**: Jobs start but server blocks, causing timeouts
### What Needs Wiring
1. **section_context** needs to be set from server.py during generation
- Currently `SECTION_ROLE_PROFILES` exists but never used
- Generation flow doesn't know which section it's in
2. **record_section_selection** needs to be called after each selection
- Method exists in `sample_selector.py`
- Never called from generation flow
- Required for joint scoring to work
3. **joint_scoring** needs selections to be recorded first
- `JOINT_SCORING_GROUPS` and `FOLDER_COMPATIBILITY_BONUS` defined
- Can't apply joint scoring without recorded selections
4. **Section-aware filtering** needs to be integrated into selection flow
- `SECTION_ROLE_PROFILES` defines primary/secondary/avoid per section
- Not used in actual `select_samples()` call chain
### Honest Assessment
**What works**: Infrastructure, extraction, caching, clearing tracks, compiling
**What exists but is dead**: Section-aware selection, joint scoring, same-pack strict enforcement
**What has issues**: Async blocking, smoke test module isolation
**What's unvalidated**: Same-pack selection, Z.ai 429 handling
**Bottom line**: ~40% of features are runtime validated, ~45% exist but aren't wired, ~15% needs fixing.