livepilot 1.10.4 → 1.10.6

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 (74) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/AGENTS.md +3 -3
  3. package/CHANGELOG.md +148 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +6 -6
  6. package/livepilot/.Codex-plugin/plugin.json +2 -2
  7. package/livepilot/.claude-plugin/plugin.json +2 -2
  8. package/livepilot/skills/livepilot-core/SKILL.md +4 -4
  9. package/livepilot/skills/livepilot-core/references/overview.md +3 -3
  10. package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
  11. package/livepilot/skills/livepilot-release/SKILL.md +5 -5
  12. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  13. package/m4l_device/livepilot_bridge.js +12 -1
  14. package/manifest.json +3 -3
  15. package/mcp_server/__init__.py +1 -1
  16. package/mcp_server/composer/sample_resolver.py +10 -6
  17. package/mcp_server/composer/tools.py +10 -6
  18. package/mcp_server/connection.py +6 -1
  19. package/mcp_server/creative_constraints/tools.py +9 -8
  20. package/mcp_server/experiment/engine.py +9 -5
  21. package/mcp_server/experiment/tools.py +9 -9
  22. package/mcp_server/hook_hunter/tools.py +14 -9
  23. package/mcp_server/m4l_bridge.py +11 -0
  24. package/mcp_server/memory/taste_graph.py +7 -2
  25. package/mcp_server/mix_engine/tools.py +8 -3
  26. package/mcp_server/musical_intelligence/tools.py +15 -10
  27. package/mcp_server/performance_engine/tools.py +6 -2
  28. package/mcp_server/preview_studio/tools.py +21 -15
  29. package/mcp_server/project_brain/tools.py +18 -10
  30. package/mcp_server/reference_engine/tools.py +7 -5
  31. package/mcp_server/runtime/capability_probe.py +10 -4
  32. package/mcp_server/runtime/tools.py +8 -2
  33. package/mcp_server/sample_engine/tools.py +394 -33
  34. package/mcp_server/semantic_moves/tools.py +5 -1
  35. package/mcp_server/server.py +10 -9
  36. package/mcp_server/services/motif_service.py +9 -3
  37. package/mcp_server/session_continuity/tools.py +7 -3
  38. package/mcp_server/session_continuity/tracker.py +9 -8
  39. package/mcp_server/song_brain/tools.py +17 -12
  40. package/mcp_server/splice_client/client.py +19 -6
  41. package/mcp_server/stuckness_detector/tools.py +8 -5
  42. package/mcp_server/tools/_agent_os_engine/__init__.py +52 -0
  43. package/mcp_server/tools/_agent_os_engine/critics.py +134 -0
  44. package/mcp_server/tools/_agent_os_engine/evaluation.py +206 -0
  45. package/mcp_server/tools/_agent_os_engine/models.py +132 -0
  46. package/mcp_server/tools/_agent_os_engine/taste.py +192 -0
  47. package/mcp_server/tools/_agent_os_engine/techniques.py +161 -0
  48. package/mcp_server/tools/_agent_os_engine/world_model.py +170 -0
  49. package/mcp_server/tools/_composition_engine/__init__.py +67 -0
  50. package/mcp_server/tools/_composition_engine/analysis.py +174 -0
  51. package/mcp_server/tools/_composition_engine/critics.py +522 -0
  52. package/mcp_server/tools/_composition_engine/gestures.py +230 -0
  53. package/mcp_server/tools/_composition_engine/harmony.py +70 -0
  54. package/mcp_server/tools/_composition_engine/models.py +193 -0
  55. package/mcp_server/tools/_composition_engine/sections.py +371 -0
  56. package/mcp_server/tools/_perception_engine.py +18 -11
  57. package/mcp_server/tools/agent_os.py +23 -15
  58. package/mcp_server/tools/analyzer.py +166 -7
  59. package/mcp_server/tools/automation.py +6 -1
  60. package/mcp_server/tools/composition.py +25 -16
  61. package/mcp_server/tools/devices.py +10 -6
  62. package/mcp_server/tools/motif.py +7 -2
  63. package/mcp_server/tools/planner.py +6 -2
  64. package/mcp_server/tools/research.py +13 -10
  65. package/mcp_server/transition_engine/tools.py +6 -1
  66. package/mcp_server/translation_engine/tools.py +8 -6
  67. package/mcp_server/wonder_mode/engine.py +8 -3
  68. package/mcp_server/wonder_mode/tools.py +29 -21
  69. package/package.json +2 -2
  70. package/remote_script/LivePilot/__init__.py +1 -1
  71. package/requirements.txt +6 -0
  72. package/livepilot.mcpb +0 -0
  73. package/mcp_server/tools/_agent_os_engine.py +0 -947
  74. package/mcp_server/tools/_composition_engine.py +0 -1530
@@ -22,7 +22,9 @@ import time
22
22
  from typing import Optional
23
23
 
24
24
  from .models import ExperimentSet, ExperimentBranch, BranchSnapshot
25
+ import logging
25
26
 
27
+ logger = logging.getLogger(__name__)
26
28
 
27
29
  # ── In-memory experiment store ───────────────────────────────────────────────
28
30
 
@@ -34,9 +36,9 @@ def _gen_id(prefix: str, seed: str) -> str:
34
36
  h = hashlib.sha256(f"{prefix}:{seed}:{time.time()}".encode()).hexdigest()[:8]
35
37
  return f"{prefix}_{h}"
36
38
 
37
-
38
39
  # ── Create experiments ───────────────────────────────────────────────────────
39
40
 
41
+
40
42
  def create_experiment(
41
43
  request_text: str,
42
44
  move_ids: list[str],
@@ -83,9 +85,9 @@ def list_experiments() -> list[dict]:
83
85
  """List all experiment sets."""
84
86
  return [exp.to_dict() for exp in _EXPERIMENTS.values()]
85
87
 
86
-
87
88
  # ── Run experiments (requires Ableton connection) ────────────────────────────
88
89
 
90
+
89
91
  def run_branch(
90
92
  branch: ExperimentBranch,
91
93
  ableton, # AbletonConnection
@@ -143,7 +145,8 @@ def _run_branch_sync(branch, ableton, compiled_plan, capture_fn):
143
145
  for _ in range(steps_executed):
144
146
  try:
145
147
  ableton.send_command("undo", {})
146
- except Exception:
148
+ except Exception as exc:
149
+ logger.debug("_run_branch_sync failed: %s", exc)
147
150
  break
148
151
 
149
152
  branch.status = "evaluated" if steps_executed > 0 else "failed"
@@ -215,7 +218,8 @@ async def run_branch_async(
215
218
  for _ in range(steps_executed):
216
219
  try:
217
220
  ableton.send_command("undo", {})
218
- except Exception:
221
+ except Exception as exc:
222
+ logger.debug("run_branch_async failed: %s", exc)
219
223
  break
220
224
 
221
225
  # A branch is "evaluated" only if it actually applied at least one step.
@@ -244,9 +248,9 @@ def evaluate_branch(
244
248
  branch.score = result.get("score", 0.0)
245
249
  return branch
246
250
 
247
-
248
251
  # ── Commit / discard ─────────────────────────────────────────────────────────
249
252
 
253
+
250
254
  async def commit_branch_async(
251
255
  experiment: ExperimentSet,
252
256
  branch_id: str,
@@ -18,6 +18,9 @@ from fastmcp import Context
18
18
  from ..server import mcp
19
19
  from . import engine
20
20
  from .models import BranchSnapshot
21
+ import logging
22
+
23
+ logger = logging.getLogger(__name__)
21
24
 
22
25
 
23
26
  def _get_ableton(ctx: Context):
@@ -35,25 +38,22 @@ def _capture_snapshot(ctx: Context) -> BranchSnapshot:
35
38
  try:
36
39
  meters = ableton.send_command("get_track_meters", {"include_stereo": True})
37
40
  snapshot.track_meters = meters.get("tracks", [])
38
- except Exception:
39
- pass
40
-
41
+ except Exception as exc:
42
+ logger.debug("_capture_snapshot failed: %s", exc)
41
43
  # Spectral data (requires M4L analyzer)
42
44
  if spectral and spectral.is_connected:
43
45
  try:
44
46
  spec = spectral.get("spectrum")
45
47
  if spec:
46
48
  snapshot.spectrum = spec.get("value", {})
47
- except Exception:
48
- pass
49
-
49
+ except Exception as exc:
50
+ logger.debug("_capture_snapshot failed: %s", exc)
50
51
  try:
51
52
  rms_data = spectral.get("rms")
52
53
  if rms_data:
53
54
  snapshot.rms = rms_data.get("value")
54
- except Exception:
55
- pass
56
-
55
+ except Exception as exc:
56
+ logger.debug("_capture_snapshot failed: %s", exc)
57
57
  return snapshot
58
58
 
59
59
 
@@ -17,6 +17,9 @@ from fastmcp import Context
17
17
 
18
18
  from ..server import mcp
19
19
  from . import analyzer
20
+ import logging
21
+
22
+ logger = logging.getLogger(__name__)
20
23
 
21
24
 
22
25
  def _get_ableton(ctx: Context):
@@ -39,8 +42,8 @@ def _fetch_tracks_and_scenes(ctx: Context) -> tuple[list[dict], list[dict], dict
39
42
  try:
40
43
  session = ableton.send_command("get_session_info", {})
41
44
  tracks = session.get("tracks", [])
42
- except Exception:
43
- pass
45
+ except Exception as exc:
46
+ logger.debug("_fetch_tracks_and_scenes failed: %s", exc)
44
47
 
45
48
  try:
46
49
  matrix = ableton.send_command("get_scene_matrix")
@@ -50,15 +53,16 @@ def _fetch_tracks_and_scenes(ctx: Context) -> tuple[list[dict], list[dict], dict
50
53
  zip(matrix.get("scenes", []), matrix.get("matrix", []))
51
54
  )
52
55
  ]
53
- except Exception:
54
- pass
56
+ except Exception as exc:
57
+ logger.debug("_fetch_tracks_and_scenes failed: %s", exc)
55
58
 
56
59
  # Fetch motif data — via shared motif service
57
60
  try:
58
61
  from ..services.motif_service import get_motif_data, fetch_notes_from_ableton
59
62
  notes_by_track = fetch_notes_from_ableton(ableton, tracks)
60
63
  motif_data = get_motif_data(notes_by_track)
61
- except Exception:
64
+ except Exception as exc:
65
+ logger.debug("_fetch_tracks_and_scenes failed: %s", exc)
62
66
  pass # Motif graph requires notes in clips; empty dict is valid fallback
63
67
 
64
68
  return tracks, scenes, motif_data
@@ -325,7 +329,6 @@ def suggest_payoff_repair(ctx: Context) -> dict:
325
329
  "repair_count": len(repairs),
326
330
  }
327
331
 
328
-
329
332
  # ── Helpers ───────────────────────────────────────────────────────
330
333
 
331
334
 
@@ -376,8 +379,8 @@ def _get_section_data(ableton) -> list[dict]:
376
379
  "density": round(density, 3),
377
380
  "has_drums": has_drums,
378
381
  })
379
- except Exception:
380
- pass
382
+ except Exception as exc:
383
+ logger.debug("_get_section_data failed: %s", exc)
381
384
 
382
385
  return sections
383
386
 
@@ -391,6 +394,7 @@ def _get_song_brain_dict() -> dict:
391
394
  except Exception as _e:
392
395
  if __debug__:
393
396
  import sys
397
+
394
398
  print(f"LivePilot: SongBrain unavailable in hook_hunter: {_e}", file=sys.stderr)
395
399
  return {}
396
400
 
@@ -421,7 +425,8 @@ def detect_hook_neglect(ctx: Context) -> dict:
421
425
 
422
426
  try:
423
427
  matrix = ableton.send_command("get_scene_matrix")
424
- except Exception:
428
+ except Exception as exc:
429
+ logger.debug("detect_hook_neglect failed: %s", exc)
425
430
  return {
426
431
  "neglected": False,
427
432
  "hook": hook.to_dict(),
@@ -7,6 +7,17 @@ the M4L device on the master track. Sends commands back for deep LOM access.
7
7
  Architecture:
8
8
  M4L → UDP:9880 → SpectralReceiver → SpectralCache → MCP tools
9
9
  MCP tools → M4LBridge → UDP:9881 → M4L device
10
+
11
+ OSC address convention:
12
+ - OUTGOING (this side → M4L): address string is sent WITHOUT a leading
13
+ slash because Max's `udpreceive` treats a literal '/' as part of the
14
+ selector. The JS side (livepilot_bridge.js) routes on bare selectors
15
+ like "cmd" / "ping".
16
+ - INCOMING (M4L → this side): the M4L side uses Max's `udpsend`, whose
17
+ outlet messages include the leading slash (e.g. "/response"). The
18
+ `_parse_osc` helper normalizes with `rest = "/" + rest.lstrip("/\\")`
19
+ so both forms are tolerated — keep that normalization; both sides
20
+ bend toward leniency but the outgoing convention here is slash-less.
10
21
  """
11
22
 
12
23
  from __future__ import annotations
@@ -17,6 +17,9 @@ from __future__ import annotations
17
17
  import time
18
18
  from dataclasses import dataclass, field
19
19
  from typing import Optional
20
+ import logging
21
+
22
+ logger = logging.getLogger(__name__)
20
23
 
21
24
 
22
25
  @dataclass
@@ -127,7 +130,8 @@ class TasteGraph:
127
130
  if self._persistent_store is not None:
128
131
  try:
129
132
  self._persistent_store.record_move_outcome(move_id, family, kept, score)
130
- except Exception:
133
+ except Exception as exc:
134
+ logger.debug("record_move_outcome failed: %s", exc)
131
135
  pass # persistence is best-effort
132
136
 
133
137
  def record_device_use(self, device_name: str, positive: bool = True) -> None:
@@ -249,9 +253,9 @@ class TasteGraph:
249
253
  "explanations": explanations,
250
254
  }
251
255
 
252
-
253
256
  # ── Builder ──────────────────────────────────────────────────────────────────
254
257
 
258
+
255
259
  def build_taste_graph(
256
260
  taste_store=None, # TasteMemoryStore
257
261
  anti_store=None, # AntiMemoryStore
@@ -299,6 +303,7 @@ def build_taste_graph(
299
303
  # Device affinities
300
304
  for dev_name, dev_data in persisted.get("device_affinities", {}).items():
301
305
  from .taste_graph import DeviceAffinity
306
+
302
307
  graph.device_affinities[dev_name] = DeviceAffinity(
303
308
  device_name=dev_name,
304
309
  affinity=dev_data.get("affinity", 0.0),
@@ -15,6 +15,10 @@ from ..evaluation.fabric import evaluate_sonic_move
15
15
  from .state_builder import build_mix_state
16
16
  from .critics import run_all_mix_critics
17
17
  from .planner import plan_mix_moves
18
+ import logging
19
+
20
+ logger = logging.getLogger(__name__)
21
+
18
22
 
19
23
 
20
24
  # ── Helpers ─────────────────────────────────────────────────────────
@@ -32,7 +36,8 @@ def _fetch_mix_data(ctx: Context) -> dict:
32
36
  try:
33
37
  info = ableton.send_command("get_track_info", {"track_index": i})
34
38
  track_infos.append(info)
35
- except Exception:
39
+ except Exception as exc:
40
+ logger.debug("_fetch_mix_data failed: %s", exc)
36
41
  continue
37
42
 
38
43
  # Get spectrum and RMS data directly from SpectralCache (not TCP)
@@ -51,8 +56,8 @@ def _fetch_mix_data(ctx: Context) -> dict:
51
56
  rms_snap = spectral.get("rms")
52
57
  if rms_snap:
53
58
  rms_data = rms_snap["value"] if isinstance(rms_snap["value"], dict) else rms_snap["value"]
54
- except Exception:
55
- pass
59
+ except Exception as exc:
60
+ logger.debug("_fetch_mix_data failed: %s", exc)
56
61
 
57
62
  return {
58
63
  "session_info": session_info,
@@ -13,6 +13,9 @@ from fastmcp import Context
13
13
 
14
14
  from ..server import mcp
15
15
  from . import detectors
16
+ import logging
17
+
18
+ logger = logging.getLogger(__name__)
16
19
 
17
20
 
18
21
  def _get_ableton(ctx: Context):
@@ -34,7 +37,8 @@ def detect_repetition_fatigue(ctx: Context) -> dict:
34
37
  # Get scene matrix for clip reuse analysis
35
38
  try:
36
39
  matrix = ableton.send_command("get_scene_matrix")
37
- except Exception:
40
+ except Exception as exc:
41
+ logger.debug("detect_repetition_fatigue failed: %s", exc)
38
42
  matrix = {}
39
43
 
40
44
  scenes = []
@@ -53,8 +57,8 @@ def detect_repetition_fatigue(ctx: Context) -> dict:
53
57
  track_list = session_info.get("tracks", [])
54
58
  notes_by_track = fetch_notes_from_ableton(ableton, track_list)
55
59
  motif_graph = get_motif_data(notes_by_track)
56
- except Exception:
57
- pass
60
+ except Exception as exc:
61
+ logger.debug("detect_repetition_fatigue failed: %s", exc)
58
62
 
59
63
  report = detectors.detect_repetition_fatigue(scenes, motif_graph)
60
64
  return report.to_dict()
@@ -97,7 +101,8 @@ def infer_section_purposes(ctx: Context) -> dict:
97
101
  # Get scene matrix for density analysis
98
102
  try:
99
103
  matrix = ableton.send_command("get_scene_matrix")
100
- except Exception:
104
+ except Exception as exc:
105
+ logger.debug("infer_section_purposes failed: %s", exc)
101
106
  matrix = {}
102
107
 
103
108
  scenes = []
@@ -132,7 +137,8 @@ def score_emotional_arc(ctx: Context) -> dict:
132
137
 
133
138
  try:
134
139
  matrix = ableton.send_command("get_scene_matrix")
135
- except Exception:
140
+ except Exception as exc:
141
+ logger.debug("score_emotional_arc failed: %s", exc)
136
142
  matrix = {}
137
143
 
138
144
  scenes = []
@@ -147,7 +153,6 @@ def score_emotional_arc(ctx: Context) -> dict:
147
153
  arc = detectors.score_emotional_arc(purposes)
148
154
  return arc.to_dict()
149
155
 
150
-
151
156
  # ── Phrase Evaluation ────────────────────────────────────────────────
152
157
 
153
158
 
@@ -179,14 +184,14 @@ def analyze_phrase_arc(
179
184
  try:
180
185
  from ..tools._perception_engine import compute_loudness
181
186
  loudness_data = compute_loudness(file_path, detail="full")
182
- except Exception:
183
- pass
187
+ except Exception as exc:
188
+ logger.debug("analyze_phrase_arc failed: %s", exc)
184
189
 
185
190
  try:
186
191
  from ..tools._perception_engine import compute_spectral
187
192
  spectrum_data = compute_spectral(file_path)
188
- except Exception:
189
- pass
193
+ except Exception as exc:
194
+ logger.debug("analyze_phrase_arc failed: %s", exc)
190
195
 
191
196
  critique = phrase_critic.analyze_phrase(loudness_data, spectrum_data, target)
192
197
  critique.render_id = file_path.split("/")[-1] if "/" in file_path else file_path
@@ -12,6 +12,10 @@ from ..server import mcp
12
12
  from .models import EnergyWindow, SceneRole
13
13
  from .planner import build_performance_state, plan_scene_transition, suggest_energy_moves
14
14
  from .safety import classify_move_safety, get_blocked_moves, get_safe_moves
15
+ import logging
16
+
17
+ logger = logging.getLogger(__name__)
18
+
15
19
 
16
20
 
17
21
  # ── Helpers ─────────────────────────────────────────────────────────
@@ -87,8 +91,8 @@ def _fetch_scene_data(ctx: Context) -> tuple[list[SceneRole], int]:
87
91
  if s.get("is_triggered", False):
88
92
  current_scene = i
89
93
  break
90
- except Exception:
91
- pass
94
+ except Exception as exc:
95
+ logger.debug("_fetch_scene_data failed: %s", exc)
92
96
 
93
97
  return scene_roles, current_scene
94
98
 
@@ -15,6 +15,9 @@ from fastmcp import Context
15
15
 
16
16
  from ..server import mcp
17
17
  from . import engine
18
+ import logging
19
+
20
+ logger = logging.getLogger(__name__)
18
21
 
19
22
 
20
23
  def _get_ableton(ctx: Context):
@@ -39,10 +42,9 @@ def _find_wonder_session_by_preview(set_id: str):
39
42
  try:
40
43
  from ..wonder_mode.session import find_session_by_preview_set
41
44
  return find_session_by_preview_set(set_id)
42
- except Exception:
45
+ except Exception as exc:
46
+ logger.debug("_find_wonder_session_by_preview failed: %s", exc)
43
47
  return None
44
-
45
-
46
48
  @mcp.tool()
47
49
  def create_preview_set(
48
50
  ctx: Context,
@@ -148,8 +150,8 @@ def create_preview_set(
148
150
  # Fallback: if no keyword match, take top 3 from full registry
149
151
  if not available_moves:
150
152
  available_moves = registry.list_moves()[:3]
151
- except Exception:
152
- pass
153
+ except Exception as exc:
154
+ logger.debug("create_preview_set failed: %s", exc)
153
155
 
154
156
  # Get song brain if available
155
157
  song_brain: dict = {}
@@ -177,8 +179,8 @@ def create_preview_set(
177
179
  persistent_store=persistent,
178
180
  )
179
181
  taste_graph = graph.to_dict()
180
- except Exception:
181
- pass
182
+ except Exception as exc:
183
+ logger.debug("create_preview_set failed: %s", exc)
182
184
 
183
185
  ps = engine.create_preview_set(
184
186
  request_text=request_text,
@@ -345,8 +347,8 @@ async def commit_preview_variant(
345
347
  )
346
348
  if ws.creative_thread_id:
347
349
  resolve_thread(ws.creative_thread_id)
348
- except Exception:
349
- pass
350
+ except Exception as exc:
351
+ logger.debug("commit_preview_variant failed: %s", exc)
350
352
 
351
353
  # Update taste graph (with persistent backing)
352
354
  try:
@@ -373,8 +375,8 @@ async def commit_preview_variant(
373
375
  family=family,
374
376
  kept=True,
375
377
  )
376
- except Exception:
377
- pass
378
+ except Exception as exc:
379
+ logger.debug("commit_preview_variant failed: %s", exc)
378
380
 
379
381
  result["wonder_session_id"] = ws.session_id
380
382
 
@@ -488,6 +490,7 @@ async def render_preview_variant(
488
490
  playback_started = True
489
491
 
490
492
  import time as _time
493
+
491
494
  _time.sleep(play_seconds)
492
495
 
493
496
  spectral_after = cache.get_all()
@@ -496,7 +499,8 @@ async def render_preview_variant(
496
499
  playback_started = False
497
500
 
498
501
  preview_mode = "audible_preview"
499
- except Exception:
502
+ except Exception as exc:
503
+ logger.debug("render_preview_variant failed: %s", exc)
500
504
  # Spectral capture is best-effort; keep preview_mode as metadata_only
501
505
  pass
502
506
 
@@ -507,12 +511,14 @@ async def render_preview_variant(
507
511
  if playback_started:
508
512
  try:
509
513
  ableton.send_command("stop_playback", {})
510
- except Exception:
511
- pass
514
+ except Exception as exc:
515
+ logger.debug("render_preview_variant failed: %s", exc)
516
+
512
517
  for _ in range(applied_count):
513
518
  try:
514
519
  ableton.send_command("undo")
515
- except Exception:
520
+ except Exception as exc:
521
+ logger.debug("render_preview_variant failed: %s", exc)
516
522
  break
517
523
 
518
524
  variant.status = "rendered"
@@ -11,6 +11,10 @@ from fastmcp import Context
11
11
 
12
12
  from ..server import mcp
13
13
  from .builder import build_project_state_from_data
14
+ import logging
15
+
16
+ logger = logging.getLogger(__name__)
17
+
14
18
 
15
19
 
16
20
  def _get_ableton(ctx: Context):
@@ -39,7 +43,8 @@ def build_project_brain(ctx: Context) -> dict:
39
43
  try:
40
44
  scenes_resp = ableton.send_command("get_scenes_info")
41
45
  scenes = scenes_resp.get("scenes", [])
42
- except Exception:
46
+ except Exception as exc:
47
+ logger.debug("build_project_brain failed: %s", exc)
43
48
  scenes = session_info.get("scenes", [])
44
49
 
45
50
  # 3. Get clip matrix (scene_matrix)
@@ -47,8 +52,8 @@ def build_project_brain(ctx: Context) -> dict:
47
52
  try:
48
53
  matrix_resp = ableton.send_command("get_scene_matrix")
49
54
  clip_matrix = matrix_resp.get("matrix", [])
50
- except Exception:
51
- pass
55
+ except Exception as exc:
56
+ logger.debug("build_project_brain failed: %s", exc)
52
57
 
53
58
  # 4. Gather per-track info with devices
54
59
  track_infos = []
@@ -58,7 +63,8 @@ def build_project_brain(ctx: Context) -> dict:
58
63
  "track_index": track["index"],
59
64
  })
60
65
  track_infos.append(info)
61
- except Exception:
66
+ except Exception as exc:
67
+ logger.debug("build_project_brain failed: %s", exc)
62
68
  track_infos.append({
63
69
  "index": track.get("index", 0),
64
70
  "name": track.get("name", ""),
@@ -75,8 +81,8 @@ def build_project_brain(ctx: Context) -> dict:
75
81
  clips = arr.get("clips", [])
76
82
  if clips:
77
83
  arrangement_clips[track["index"]] = clips
78
- except Exception:
79
- pass
84
+ except Exception as exc:
85
+ logger.debug("build_project_brain failed: %s", exc)
80
86
 
81
87
  # 5b. Build notes_map for role inference.
82
88
  # Shape: {section_id: {track_index: [notes]}}. Without this, role_graph
@@ -102,12 +108,14 @@ def build_project_brain(ctx: Context) -> dict:
102
108
  notes = notes_resp.get("notes", [])
103
109
  if notes:
104
110
  per_track[t_idx] = notes
105
- except Exception:
111
+ except Exception as exc:
112
+ logger.debug("build_project_brain failed: %s", exc)
106
113
  # Individual note fetch failing is fine — continue with others
107
114
  continue
108
115
  if per_track:
109
116
  notes_map[section_id] = per_track
110
- except Exception:
117
+ except Exception as exc:
118
+ logger.debug("build_project_brain failed: %s", exc)
111
119
  # Overall failure: empty map, degrade to "all tracks active" fallback
112
120
  notes_map = {}
113
121
 
@@ -127,8 +135,8 @@ def build_project_brain(ctx: Context) -> dict:
127
135
  if spectral.get(key) is not None:
128
136
  flucoma_ok = True
129
137
  break
130
- except Exception:
131
- pass
138
+ except Exception as exc:
139
+ logger.debug("build_project_brain failed: %s", exc)
132
140
 
133
141
  # 7. Build state
134
142
  state = build_project_state_from_data(
@@ -13,7 +13,9 @@ from ..tools._research_engine import get_style_tactics
13
13
  from .profile_builder import build_audio_reference_profile, build_style_reference_profile
14
14
  from .gap_analyzer import analyze_gaps, classify_gap_relevance, detect_identity_warnings
15
15
  from .tactic_router import build_reference_plan
16
+ import logging
16
17
 
18
+ logger = logging.getLogger(__name__)
17
19
 
18
20
  # ── Helpers ────────────────────────────────────────────────────────
19
21
 
@@ -50,6 +52,7 @@ def _fetch_project_snapshot(ctx: Context) -> dict:
50
52
  rms = rms_snap["value"] if isinstance(rms_snap["value"], (int, float)) else 0.0
51
53
  if rms > 0:
52
54
  import math
55
+
53
56
  snapshot["loudness"] = round(20 * math.log10(max(rms, 1e-10)), 2)
54
57
 
55
58
  spec_data = spectral.get("spectrum")
@@ -58,8 +61,8 @@ def _fetch_project_snapshot(ctx: Context) -> dict:
58
61
  key_data = spectral.get("key")
59
62
  if key_data:
60
63
  snapshot["spectral"]["detected_key"] = key_data["value"]
61
- except Exception:
62
- pass
64
+ except Exception as exc:
65
+ logger.debug("_fetch_project_snapshot failed: %s", exc)
63
66
 
64
67
  # Try to get session info for pacing / density
65
68
  try:
@@ -69,12 +72,11 @@ def _fetch_project_snapshot(ctx: Context) -> dict:
69
72
  # Rough density estimate
70
73
  snapshot["density"] = min(1.0, track_count / 20.0)
71
74
  snapshot["pacing"] = [{"label": f"scene_{i}", "bars": 8} for i in range(scene_count)]
72
- except Exception:
73
- pass
75
+ except Exception as exc:
76
+ logger.debug("_fetch_project_snapshot failed: %s", exc)
74
77
 
75
78
  return snapshot
76
79
 
77
-
78
80
  # ── MCP Tools ──────────────────────────────────────────────────────
79
81
 
80
82
 
@@ -9,6 +9,9 @@ from __future__ import annotations
9
9
  import os
10
10
  from pathlib import Path
11
11
  from typing import Any
12
+ import logging
13
+
14
+ logger = logging.getLogger(__name__)
12
15
 
13
16
 
14
17
  def probe_capabilities(
@@ -27,8 +30,9 @@ def probe_capabilities(
27
30
  try:
28
31
  info = ableton.send_command("ping")
29
32
  ableton_ok = info is not None
30
- except Exception:
31
- pass
33
+ except Exception as exc:
34
+ logger.debug("probe_capabilities failed: %s", exc)
35
+
32
36
  report["ableton"] = {
33
37
  "status": "ok" if ableton_ok else "unavailable",
34
38
  "detail": "TCP 9878 connection active" if ableton_ok else "Not connected",
@@ -40,8 +44,9 @@ def probe_capabilities(
40
44
  try:
41
45
  info = ableton.send_command("get_session_info")
42
46
  live_version_str = info.get("live_version", "12.0.0")
43
- except Exception:
44
- pass
47
+ except Exception as exc:
48
+ logger.debug("probe_capabilities failed: %s", exc)
49
+
45
50
  from .live_version import LiveVersionCapabilities
46
51
  version_caps = LiveVersionCapabilities.from_version_string(live_version_str)
47
52
  report["live_version"] = {
@@ -75,6 +80,7 @@ def probe_capabilities(
75
80
  numpy_ok = False
76
81
  try:
77
82
  import numpy # noqa: F401
83
+
78
84
  numpy_ok = True
79
85
  except ImportError:
80
86
  pass
@@ -12,6 +12,9 @@ from fastmcp import Context
12
12
  from ..server import mcp
13
13
  from ..memory.technique_store import TechniqueStore
14
14
  from .capability_state import build_capability_state
15
+ import logging
16
+
17
+ logger = logging.getLogger(__name__)
15
18
 
16
19
  _memory_store = TechniqueStore()
17
20
 
@@ -31,7 +34,8 @@ def get_capability_state(ctx: Context) -> dict:
31
34
  try:
32
35
  result = ableton.send_command("get_session_info")
33
36
  session_ok = isinstance(result, dict) and "error" not in result
34
- except Exception:
37
+ except Exception as exc:
38
+ logger.debug("get_capability_state failed: %s", exc)
35
39
  session_ok = False
36
40
 
37
41
  # ── Probe analyzer (M4L bridge) ─────────────────────────────────
@@ -49,7 +53,8 @@ def get_capability_state(ctx: Context) -> dict:
49
53
  try:
50
54
  _memory_store.list_techniques(limit=1)
51
55
  memory_ok = True
52
- except Exception:
56
+ except Exception as exc:
57
+ logger.debug("get_capability_state failed: %s", exc)
53
58
  memory_ok = False
54
59
 
55
60
  # ── Web / FluCoMa — not probed live, default to False ───────────
@@ -180,6 +185,7 @@ def get_session_kernel(
180
185
  if request_text.strip():
181
186
  try:
182
187
  from ..tools._conductor import classify_request
188
+
183
189
  plan = classify_request(request_text)
184
190
  kernel.recommended_engines = [r.engine for r in plan.routes[:3]]
185
191
  kernel.recommended_workflow = plan.workflow_mode