30 KiB
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:
# 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
# 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_infodevuelve consistentemente 1 track
2. Z.ai Backoff/Retry/Cache - IMPLEMENTADO ✅
Archivo: AbletonMCP_AI/AbletonMCP_AI/MCP_Server/zai_judges.py
Configuración:
# 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:
# 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:
# 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:
# 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:
# 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:
# 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):
# 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):
# 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):
# 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:
# 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:
# 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:
# 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:
# 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:
- Job se encola correctamente (status: "queued")
- Job cambia a "running"
- Servidor deja de responder a cualquier comando MCP
get_generation_job_statustimeout- 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:
- Usar
multiprocessing.Processen vez deThreadPoolExecutor - Añadir
asyncioconrun_in_executory checkpoints - Separar el job runner en proceso independiente con queue
- Usar
fastapio similar para endpoint de status separado
6. Smoke Test - IMPLEMENTADO ⚠️ CON ISSUE
Archivo: temp\smoke_test_async.py (547 líneas)
Estructura:
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:
# 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
✅ 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
-
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
-
Smoke test module isolation
- Impacto: "Job not found" en primer poll
- Causa:
_generation_jobsno compartido entre instancias - Fix: Usar socket directo o singleton
- Prioridad: MEDIA
-
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
-
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
-
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)
# 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
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.pycomo proceso independiente - Comunicación via socket o archivo
- MCP server solo orquesta, no genera
Media Prioridad
-
Validar same-pack selection
- Generar track y inspeccionar logs
- Verificar fill_fx/snare_roll vienen de pack principal
-
Validar Z.ai retry
- Probar contra API real
- Forzar 429 si es posible (rate limiting)
-
Fix smoke test
- Usar socket directo de Live (127.0.0.1:9877)
- O mantener singleton del server module
Baja Prioridad
-
Mejorar BPM detection
- Usar tempo detection más robusto
- O parsear BPM del filename
-
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 agentesKIMI_K2_ACTIVE_HANDOFF.md- Estado actual verificadoCLAUDE.md- Reglas del proyectodocs/ROADMAP.md- Roadmap canonicaldocs/SPRINT_v0.1.2_NEXT.md- Sprint activodocs/KNOWN_ISSUES.md- Issues conocidos
Comandos Útiles
# 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
- No confíes ciegamente en docs históricos: Siempre verificar con código real primero
- Separar implementación de validación: El código puede estar listo pero sin probar en vivo
- Server blocking es el issue más crítico: Arreglar esto primero antes de más features
- Usar PowerShell en Windows: No bash, rutas absolutas Windows
- Validar con runtime:
get_session_info,get_tracks, logs de Ableton - 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
-
section_context needs to be set from server.py during generation
- Currently
SECTION_ROLE_PROFILESexists but never used - Generation flow doesn't know which section it's in
- Currently
-
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
- Method exists in
-
joint_scoring needs selections to be recorded first
JOINT_SCORING_GROUPSandFOLDER_COMPATIBILITY_BONUSdefined- Can't apply joint scoring without recorded selections
-
Section-aware filtering needs to be integrated into selection flow
SECTION_ROLE_PROFILESdefines 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.