livepilot 1.10.5 → 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 (68) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +50 -0
  4. package/livepilot/.Codex-plugin/plugin.json +1 -1
  5. package/livepilot/.claude-plugin/plugin.json +1 -1
  6. package/livepilot/skills/livepilot-core/references/overview.md +2 -2
  7. package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
  8. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  9. package/m4l_device/livepilot_bridge.js +12 -1
  10. package/manifest.json +1 -1
  11. package/mcp_server/__init__.py +1 -1
  12. package/mcp_server/composer/sample_resolver.py +10 -6
  13. package/mcp_server/composer/tools.py +10 -6
  14. package/mcp_server/connection.py +6 -1
  15. package/mcp_server/creative_constraints/tools.py +9 -8
  16. package/mcp_server/experiment/engine.py +9 -5
  17. package/mcp_server/experiment/tools.py +9 -9
  18. package/mcp_server/hook_hunter/tools.py +14 -9
  19. package/mcp_server/m4l_bridge.py +11 -0
  20. package/mcp_server/memory/taste_graph.py +7 -2
  21. package/mcp_server/mix_engine/tools.py +8 -3
  22. package/mcp_server/musical_intelligence/tools.py +15 -10
  23. package/mcp_server/performance_engine/tools.py +6 -2
  24. package/mcp_server/preview_studio/tools.py +21 -15
  25. package/mcp_server/project_brain/tools.py +18 -10
  26. package/mcp_server/reference_engine/tools.py +7 -5
  27. package/mcp_server/runtime/capability_probe.py +10 -4
  28. package/mcp_server/runtime/tools.py +8 -2
  29. package/mcp_server/sample_engine/tools.py +27 -18
  30. package/mcp_server/semantic_moves/tools.py +5 -1
  31. package/mcp_server/server.py +10 -9
  32. package/mcp_server/services/motif_service.py +9 -3
  33. package/mcp_server/session_continuity/tools.py +7 -3
  34. package/mcp_server/session_continuity/tracker.py +9 -8
  35. package/mcp_server/song_brain/tools.py +17 -12
  36. package/mcp_server/splice_client/client.py +19 -6
  37. package/mcp_server/stuckness_detector/tools.py +8 -5
  38. package/mcp_server/tools/_agent_os_engine/__init__.py +52 -0
  39. package/mcp_server/tools/_agent_os_engine/critics.py +134 -0
  40. package/mcp_server/tools/_agent_os_engine/evaluation.py +206 -0
  41. package/mcp_server/tools/_agent_os_engine/models.py +132 -0
  42. package/mcp_server/tools/_agent_os_engine/taste.py +192 -0
  43. package/mcp_server/tools/_agent_os_engine/techniques.py +161 -0
  44. package/mcp_server/tools/_agent_os_engine/world_model.py +170 -0
  45. package/mcp_server/tools/_composition_engine/__init__.py +67 -0
  46. package/mcp_server/tools/_composition_engine/analysis.py +174 -0
  47. package/mcp_server/tools/_composition_engine/critics.py +522 -0
  48. package/mcp_server/tools/_composition_engine/gestures.py +230 -0
  49. package/mcp_server/tools/_composition_engine/harmony.py +70 -0
  50. package/mcp_server/tools/_composition_engine/models.py +193 -0
  51. package/mcp_server/tools/_composition_engine/sections.py +371 -0
  52. package/mcp_server/tools/_perception_engine.py +18 -11
  53. package/mcp_server/tools/agent_os.py +23 -15
  54. package/mcp_server/tools/analyzer.py +7 -8
  55. package/mcp_server/tools/automation.py +6 -1
  56. package/mcp_server/tools/composition.py +25 -16
  57. package/mcp_server/tools/devices.py +10 -6
  58. package/mcp_server/tools/motif.py +7 -2
  59. package/mcp_server/tools/planner.py +6 -2
  60. package/mcp_server/tools/research.py +13 -10
  61. package/mcp_server/transition_engine/tools.py +6 -1
  62. package/mcp_server/translation_engine/tools.py +8 -6
  63. package/mcp_server/wonder_mode/engine.py +8 -3
  64. package/mcp_server/wonder_mode/tools.py +29 -21
  65. package/package.json +1 -1
  66. package/remote_script/LivePilot/__init__.py +1 -1
  67. package/mcp_server/tools/_agent_os_engine.py +0 -947
  68. package/mcp_server/tools/_composition_engine.py +0 -1530
@@ -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
@@ -6,12 +6,15 @@ direct Splice online catalog hunt/download via the gRPC client.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import logging
9
10
  import os
10
11
  from typing import Optional
11
12
 
12
13
  from fastmcp import Context
13
14
 
14
15
  from ..server import mcp
16
+
17
+ logger = logging.getLogger(__name__)
15
18
  from .models import SampleProfile, SampleIntent, SampleFitReport
16
19
  from .analyzer import build_profile_from_filename
17
20
  from .critics import run_all_sample_critics
@@ -47,8 +50,8 @@ async def analyze_sample(
47
50
  )
48
51
  if not result.get("error"):
49
52
  file_path = result.get("file_path")
50
- except Exception:
51
- pass
53
+ except Exception as exc:
54
+ logger.warning("m4l get_clip_file_path failed: %s", exc)
52
55
 
53
56
  if file_path is None:
54
57
  return {"error": "Could not determine file path — provide file_path directly"}
@@ -97,7 +100,8 @@ def evaluate_sample_fit(
97
100
  name = track_info.get("name", "").lower()
98
101
  if name:
99
102
  existing_roles.append(name)
100
- except Exception:
103
+ except Exception as exc:
104
+ logger.debug("get_track_info(%d) skipped: %s", i, exc)
101
105
  continue
102
106
 
103
107
  # Detect key from MIDI tracks
@@ -119,12 +123,13 @@ def evaluate_sample_fit(
119
123
  mode_suffix = "m" if "minor" in mode else ""
120
124
  song_key = f"{key_result['tonic_name']}{mode_suffix}"
121
125
  break
122
- except Exception:
126
+ except Exception as exc:
127
+ logger.debug("key detection on track %d skipped: %s", i, exc)
123
128
  continue
124
129
  except ImportError:
125
130
  pass
126
- except Exception:
127
- pass
131
+ except Exception as exc:
132
+ logger.warning("session context for evaluate_sample_fit failed: %s", exc)
128
133
 
129
134
  critics = run_all_sample_critics(
130
135
  profile=profile,
@@ -243,7 +248,8 @@ async def search_samples(
243
248
  },
244
249
  })
245
250
  used_grpc = True
246
- except Exception:
251
+ except Exception as exc:
252
+ logger.warning("Splice gRPC search failed, falling back to SQL: %s", exc)
247
253
  used_grpc = False
248
254
 
249
255
  # Also query local index (if not already covered by gRPC) to surface
@@ -282,10 +288,11 @@ async def search_samples(
282
288
  d = candidate.to_dict()
283
289
  d["source_priority"] = 2
284
290
  results.append(d)
285
- except Exception:
291
+ except Exception as exc:
292
+ logger.debug("browser search %s skipped: %s", category, exc)
286
293
  continue
287
- except Exception:
288
- pass
294
+ except Exception as exc:
295
+ logger.warning("browser search unavailable: %s", exc)
289
296
 
290
297
  # Filesystem search
291
298
  if source in (None, "filesystem"):
@@ -442,7 +449,8 @@ def get_sample_opportunities(ctx: Context) -> dict:
442
449
  try:
443
450
  ableton = ctx.lifespan_context["ableton"]
444
451
  info = ableton.send_command("get_session_info", {})
445
- except Exception:
452
+ except Exception as exc:
453
+ logger.warning("get_sample_opportunities: Ableton not reachable: %s", exc)
446
454
  return {"opportunities": [], "note": "Cannot read session — Ableton not connected"}
447
455
 
448
456
  track_count = info.get("track_count", 0)
@@ -458,7 +466,8 @@ def get_sample_opportunities(ctx: Context) -> dict:
458
466
  for d in devices:
459
467
  if d.get("class_name") in ("OriginalSimpler", "MultiSampler"):
460
468
  has_sampler = True
461
- except Exception:
469
+ except Exception as exc:
470
+ logger.debug("track scan idx=%d skipped: %s", i, exc)
462
471
  continue
463
472
 
464
473
  # No organic texture
@@ -542,8 +551,8 @@ def plan_slice_workflow(
542
551
  if ableton:
543
552
  info = ableton.send_command("get_session_info", {})
544
553
  tempo = float(info.get("tempo", 120.0))
545
- except Exception:
546
- pass
554
+ except Exception as exc:
555
+ logger.debug("plan_slice_workflow tempo fetch failed (using 120): %s", exc)
547
556
 
548
557
  # Read slice count from existing Simpler if track provided
549
558
  slice_count = 8 # Default transient slice count
@@ -556,8 +565,8 @@ def plan_slice_workflow(
556
565
  })
557
566
  if isinstance(slices, dict) and slices.get("slice_count"):
558
567
  slice_count = slices["slice_count"]
559
- except Exception:
560
- pass # Fall back to default
568
+ except Exception as exc:
569
+ logger.debug("get_simpler_slices failed (using default 8): %s", exc)
561
570
 
562
571
  # Build the plan
563
572
  plan = plan_slice_steps(
@@ -891,7 +900,7 @@ async def splice_download_sample(
891
900
  try:
892
901
  info = await client.get_credits()
893
902
  response["credits_remaining"] = int(info.credits)
894
- except Exception:
895
- pass
903
+ except Exception as exc:
904
+ logger.warning("post-download credit check failed: %s", exc)
896
905
 
897
906
  return response
@@ -14,6 +14,9 @@ from fastmcp import Context
14
14
 
15
15
  from ..server import mcp
16
16
  from . import registry
17
+ import logging
18
+
19
+ logger = logging.getLogger(__name__)
17
20
 
18
21
 
19
22
  @mcp.tool()
@@ -80,7 +83,8 @@ def preview_semantic_move(
80
83
  info = ableton.send_command("get_session_info")
81
84
  if isinstance(info, dict):
82
85
  session_info = info
83
- except Exception:
86
+ except Exception as exc:
87
+ logger.debug("preview_semantic_move failed: %s", exc)
84
88
  session_info = {}
85
89
 
86
90
  state = build_capability_state(
@@ -44,9 +44,9 @@ def _master_has_livepilot_analyzer(ableton: AbletonConnection) -> bool:
44
44
  """Check whether the analyzer device is currently on the master track."""
45
45
  try:
46
46
  track = ableton.send_command("get_master_track")
47
- except Exception:
47
+ except Exception as exc:
48
+ logger.debug("_master_has_livepilot_analyzer failed: %s", exc)
48
49
  return False
49
-
50
50
  devices = track.get("devices", []) if isinstance(track, dict) else []
51
51
  for device in devices:
52
52
  normalized = " ".join(
@@ -92,7 +92,8 @@ async def lifespan(server):
92
92
  splice_client = SpliceGRPCClient()
93
93
  try:
94
94
  await splice_client.connect()
95
- except Exception:
95
+ except Exception as exc:
96
+ logger.debug("lifespan failed: %s", exc)
96
97
  pass # client remains in disconnected state
97
98
 
98
99
  # Start UDP listener for incoming M4L spectral data (port 9880)
@@ -144,10 +145,8 @@ async def lifespan(server):
144
145
  ableton.disconnect()
145
146
  try:
146
147
  await splice_client.disconnect()
147
- except Exception:
148
- pass
149
-
150
-
148
+ except Exception as exc:
149
+ logger.debug("lifespan failed: %s", exc)
151
150
  mcp = FastMCP("LivePilot", lifespan=lifespan)
152
151
 
153
152
  # Import tool modules so they register with `mcp`
@@ -199,7 +198,9 @@ from .device_forge import tools as device_forge_tools # noqa: F401, E40
199
198
  from .sample_engine import tools as sample_engine_tools # noqa: F401, E402
200
199
  from .atlas import tools as atlas_tools # noqa: F401, E402
201
200
  from .composer import tools as composer_tools # noqa: F401, E402
201
+ import logging
202
202
 
203
+ logger = logging.getLogger(__name__)
203
204
 
204
205
  # ---------------------------------------------------------------------------
205
206
  # Schema coercion patch — accept strings for numeric parameters
@@ -213,6 +214,7 @@ from .composer import tools as composer_tools # noqa: F401, E40
213
214
  # "5" → 5 and "0.75" → 0.75 automatically, so no tool code changes needed.
214
215
  # ---------------------------------------------------------------------------
215
216
 
217
+
216
218
  def _coerce_schema_property(prop: dict) -> None:
217
219
  """Widen a single JSON Schema property to also accept strings."""
218
220
  if prop.get("type") in ("integer", "number") and "anyOf" not in prop:
@@ -253,6 +255,7 @@ def _get_all_tools():
253
255
  if hasattr(mcp, "_local_provider") and hasattr(mcp._local_provider, "_components"):
254
256
  return list(mcp._local_provider._components.values())
255
257
  import sys
258
+
256
259
  print(
257
260
  "LivePilot: WARNING — could not access FastMCP tool registry, "
258
261
  "string-to-number schema coercion will not work",
@@ -273,7 +276,6 @@ def _patch_tool_schemas() -> None:
273
276
  if isinstance(definition, dict):
274
277
  _coerce_schema_property(definition)
275
278
 
276
-
277
279
  _patch_tool_schemas()
278
280
 
279
281
 
@@ -281,6 +283,5 @@ def main():
281
283
  """Run the MCP server over stdio."""
282
284
  mcp.run(transport="stdio")
283
285
 
284
-
285
286
  if __name__ == "__main__":
286
287
  main()
@@ -9,6 +9,9 @@ Pure computation — no I/O. Callers provide pre-fetched data.
9
9
  from __future__ import annotations
10
10
 
11
11
  from typing import Optional
12
+ import logging
13
+
14
+ logger = logging.getLogger(__name__)
12
15
 
13
16
 
14
17
  def get_motif_data(
@@ -28,13 +31,15 @@ def get_motif_data(
28
31
 
29
32
  try:
30
33
  from ..tools import _motif_engine as motif_engine
34
+
31
35
  motifs = motif_engine.detect_motifs(notes_by_track)
32
36
  return {
33
37
  "motifs": [m.to_dict() for m in motifs],
34
38
  "motif_count": len(motifs),
35
39
  "tracks_analyzed": len(notes_by_track),
36
40
  }
37
- except Exception:
41
+ except Exception as exc:
42
+ logger.debug("get_motif_data failed: %s", exc)
38
43
  return {"motifs": [], "motif_count": 0, "tracks_analyzed": 0}
39
44
 
40
45
 
@@ -60,8 +65,9 @@ def fetch_notes_from_ableton(ableton, tracks: list[dict], max_clips: int = 8) ->
60
65
  "clip_index": clip_idx,
61
66
  })
62
67
  track_notes.extend(result.get("notes", []))
63
- except Exception:
64
- pass
68
+ except Exception as exc:
69
+ logger.debug("fetch_notes_from_ableton failed: %s", exc)
70
+
65
71
  if track_notes:
66
72
  notes_by_track[t_idx] = track_notes
67
73
  return notes_by_track