livepilot 1.9.22 → 1.9.24

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 (118) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.mcpbignore +40 -0
  3. package/AGENTS.md +3 -3
  4. package/CHANGELOG.md +84 -0
  5. package/CONTRIBUTING.md +1 -1
  6. package/README.md +141 -72
  7. package/bin/livepilot.js +135 -0
  8. package/livepilot/.Codex-plugin/plugin.json +2 -2
  9. package/livepilot/.claude-plugin/plugin.json +2 -2
  10. package/livepilot/agents/livepilot-producer/AGENT.md +13 -0
  11. package/livepilot/commands/arrange.md +42 -23
  12. package/livepilot/commands/mix.md +34 -19
  13. package/livepilot/commands/perform.md +31 -19
  14. package/livepilot/commands/sounddesign.md +38 -25
  15. package/livepilot/skills/livepilot-arrangement/SKILL.md +2 -1
  16. package/livepilot/skills/livepilot-composition-engine/references/transition-archetypes.md +2 -2
  17. package/livepilot/skills/livepilot-core/SKILL.md +60 -4
  18. package/livepilot/skills/livepilot-core/references/device-atlas/distortion-and-character.md +11 -11
  19. package/livepilot/skills/livepilot-core/references/device-atlas/drums-and-percussion.md +25 -25
  20. package/livepilot/skills/livepilot-core/references/device-atlas/dynamics-and-punch.md +21 -21
  21. package/livepilot/skills/livepilot-core/references/device-atlas/eq-and-filtering.md +13 -13
  22. package/livepilot/skills/livepilot-core/references/device-atlas/midi-tools.md +13 -13
  23. package/livepilot/skills/livepilot-core/references/device-atlas/movement-and-modulation.md +5 -5
  24. package/livepilot/skills/livepilot-core/references/device-atlas/space-and-depth.md +16 -16
  25. package/livepilot/skills/livepilot-core/references/device-atlas/spectral-and-weird.md +40 -40
  26. package/livepilot/skills/livepilot-core/references/m4l-devices.md +3 -3
  27. package/livepilot/skills/livepilot-core/references/overview.md +4 -4
  28. package/livepilot/skills/livepilot-evaluation/SKILL.md +12 -8
  29. package/livepilot/skills/livepilot-evaluation/references/memory-promotion.md +2 -2
  30. package/livepilot/skills/livepilot-mix-engine/SKILL.md +1 -1
  31. package/livepilot/skills/livepilot-mix-engine/references/mix-moves.md +2 -2
  32. package/livepilot/skills/livepilot-mixing/SKILL.md +3 -1
  33. package/livepilot/skills/livepilot-notes/SKILL.md +2 -1
  34. package/livepilot/skills/livepilot-release/SKILL.md +29 -15
  35. package/livepilot/skills/livepilot-sound-design-engine/SKILL.md +2 -2
  36. package/livepilot/skills/livepilot-wonder/SKILL.md +62 -0
  37. package/livepilot.mcpb +0 -0
  38. package/manifest.json +91 -0
  39. package/mcp_server/__init__.py +1 -1
  40. package/mcp_server/creative_constraints/__init__.py +6 -0
  41. package/mcp_server/creative_constraints/engine.py +277 -0
  42. package/mcp_server/creative_constraints/models.py +75 -0
  43. package/mcp_server/creative_constraints/tools.py +341 -0
  44. package/mcp_server/experiment/__init__.py +6 -0
  45. package/mcp_server/experiment/engine.py +213 -0
  46. package/mcp_server/experiment/models.py +120 -0
  47. package/mcp_server/experiment/tools.py +263 -0
  48. package/mcp_server/hook_hunter/__init__.py +5 -0
  49. package/mcp_server/hook_hunter/analyzer.py +365 -0
  50. package/mcp_server/hook_hunter/models.py +58 -0
  51. package/mcp_server/hook_hunter/tools.py +588 -0
  52. package/mcp_server/memory/taste_graph.py +328 -0
  53. package/mcp_server/memory/tools.py +99 -0
  54. package/mcp_server/mix_engine/critics.py +2 -2
  55. package/mcp_server/mix_engine/models.py +1 -1
  56. package/mcp_server/mix_engine/state_builder.py +2 -2
  57. package/mcp_server/musical_intelligence/__init__.py +8 -0
  58. package/mcp_server/musical_intelligence/detectors.py +434 -0
  59. package/mcp_server/musical_intelligence/phrase_critic.py +163 -0
  60. package/mcp_server/musical_intelligence/tools.py +224 -0
  61. package/mcp_server/persistence/__init__.py +1 -0
  62. package/mcp_server/persistence/base_store.py +82 -0
  63. package/mcp_server/persistence/project_store.py +106 -0
  64. package/mcp_server/persistence/taste_store.py +122 -0
  65. package/mcp_server/preview_studio/__init__.py +5 -0
  66. package/mcp_server/preview_studio/engine.py +280 -0
  67. package/mcp_server/preview_studio/models.py +74 -0
  68. package/mcp_server/preview_studio/tools.py +466 -0
  69. package/mcp_server/runtime/capability.py +66 -0
  70. package/mcp_server/runtime/capability_probe.py +118 -0
  71. package/mcp_server/runtime/execution_router.py +139 -0
  72. package/mcp_server/runtime/remote_commands.py +82 -0
  73. package/mcp_server/runtime/session_kernel.py +96 -0
  74. package/mcp_server/runtime/tools.py +90 -1
  75. package/mcp_server/semantic_moves/__init__.py +13 -0
  76. package/mcp_server/semantic_moves/compiler.py +116 -0
  77. package/mcp_server/semantic_moves/mix_compilers.py +291 -0
  78. package/mcp_server/semantic_moves/mix_moves.py +157 -0
  79. package/mcp_server/semantic_moves/models.py +46 -0
  80. package/mcp_server/semantic_moves/performance_compilers.py +208 -0
  81. package/mcp_server/semantic_moves/performance_moves.py +81 -0
  82. package/mcp_server/semantic_moves/registry.py +32 -0
  83. package/mcp_server/semantic_moves/resolvers.py +126 -0
  84. package/mcp_server/semantic_moves/sound_design_compilers.py +266 -0
  85. package/mcp_server/semantic_moves/sound_design_moves.py +78 -0
  86. package/mcp_server/semantic_moves/tools.py +205 -0
  87. package/mcp_server/semantic_moves/transition_compilers.py +222 -0
  88. package/mcp_server/semantic_moves/transition_moves.py +76 -0
  89. package/mcp_server/server.py +10 -0
  90. package/mcp_server/services/__init__.py +1 -0
  91. package/mcp_server/services/motif_service.py +67 -0
  92. package/mcp_server/session_continuity/__init__.py +6 -0
  93. package/mcp_server/session_continuity/models.py +86 -0
  94. package/mcp_server/session_continuity/tools.py +230 -0
  95. package/mcp_server/session_continuity/tracker.py +263 -0
  96. package/mcp_server/song_brain/__init__.py +6 -0
  97. package/mcp_server/song_brain/builder.py +504 -0
  98. package/mcp_server/song_brain/models.py +136 -0
  99. package/mcp_server/song_brain/tools.py +312 -0
  100. package/mcp_server/stuckness_detector/__init__.py +5 -0
  101. package/mcp_server/stuckness_detector/detector.py +400 -0
  102. package/mcp_server/stuckness_detector/models.py +66 -0
  103. package/mcp_server/stuckness_detector/tools.py +195 -0
  104. package/mcp_server/tools/_conductor.py +104 -6
  105. package/mcp_server/tools/analyzer.py +1 -1
  106. package/mcp_server/tools/devices.py +34 -0
  107. package/mcp_server/wonder_mode/__init__.py +6 -0
  108. package/mcp_server/wonder_mode/diagnosis.py +84 -0
  109. package/mcp_server/wonder_mode/engine.py +493 -0
  110. package/mcp_server/wonder_mode/session.py +114 -0
  111. package/mcp_server/wonder_mode/tools.py +290 -0
  112. package/package.json +2 -2
  113. package/remote_script/LivePilot/__init__.py +1 -1
  114. package/remote_script/LivePilot/browser.py +4 -1
  115. package/remote_script/LivePilot/devices.py +29 -0
  116. package/remote_script/LivePilot/tracks.py +11 -4
  117. package/scripts/generate_tool_catalog.py +131 -0
  118. package/scripts/sync_metadata.py +132 -0
@@ -39,6 +39,12 @@ class ConductorPlan:
39
39
  notes: list[str] = field(default_factory=list)
40
40
  budget: Optional[dict] = None
41
41
 
42
+ # V2 additions
43
+ semantic_moves: list[dict] = field(default_factory=list)
44
+ workflow_mode: str = "guided_workflow" # quick_fix | guided_workflow | agentic_loop | creative_search | performance_safe
45
+ use_session_kernel: bool = True
46
+ experiment_recommended: bool = False
47
+
42
48
  def to_dict(self) -> dict:
43
49
  result = {
44
50
  "request": self.request,
@@ -48,6 +54,10 @@ class ConductorPlan:
48
54
  "primary_engine": self.routes[0].engine if self.routes else None,
49
55
  "capability_requirements": self.capability_requirements,
50
56
  "notes": self.notes,
57
+ "semantic_moves": self.semantic_moves,
58
+ "workflow_mode": self.workflow_mode,
59
+ "use_session_kernel": self.use_session_kernel,
60
+ "experiment_recommended": self.experiment_recommended,
51
61
  }
52
62
  if self.budget is not None:
53
63
  result["budget"] = self.budget
@@ -103,6 +113,65 @@ _ROUTING_PATTERNS: list[tuple[str, str, str, str, list[str]]] = [
103
113
  ]
104
114
 
105
115
 
116
+ def _find_matching_semantic_moves(request_lower: str) -> list[dict]:
117
+ """Search the semantic move registry for moves matching the request."""
118
+ try:
119
+ from ..semantic_moves.registry import _REGISTRY
120
+ except ImportError:
121
+ return []
122
+
123
+ matches = []
124
+ request_words = set(request_lower.split())
125
+
126
+ for move in _REGISTRY.values():
127
+ score = 0.0
128
+ move_words = set(move.move_id.replace("_", " ").split())
129
+ intent_words = set(move.intent.lower().split())
130
+
131
+ # Word overlap
132
+ overlap = request_words & (move_words | intent_words)
133
+ score += len(overlap) * 0.3
134
+
135
+ # Dimension keyword matching
136
+ for dim in move.targets:
137
+ if dim in request_lower:
138
+ score += 0.2
139
+
140
+ # Direct ID match
141
+ if move.move_id.replace("_", " ") in request_lower:
142
+ score += 1.0
143
+
144
+ if score > 0.1:
145
+ d = move.to_dict()
146
+ d["match_score"] = round(score, 3)
147
+ matches.append(d)
148
+
149
+ matches.sort(key=lambda x: -x["match_score"])
150
+ return matches[:3]
151
+
152
+
153
+ def _infer_workflow_mode(request_lower: str) -> str:
154
+ """Infer the appropriate workflow mode from request language."""
155
+ # Performance-safe keywords
156
+ if re.search(r"live|perform|safe|set\b|show\b|gig", request_lower):
157
+ return "performance_safe"
158
+
159
+ # Creative search keywords
160
+ if re.search(r"try|experiment|explore|surprise|option|variant|idea|branch", request_lower):
161
+ return "creative_search"
162
+
163
+ # Quick fix keywords
164
+ if re.search(r"fix|quick|just|only|undo|revert|simple", request_lower):
165
+ return "quick_fix"
166
+
167
+ # Agentic loop keywords (full autonomous)
168
+ if re.search(r"autonomous|auto|full|everything|deep|polish|finish", request_lower):
169
+ return "agentic_loop"
170
+
171
+ # Default
172
+ return "guided_workflow"
173
+
174
+
106
175
  def classify_request(request: str) -> ConductorPlan:
107
176
  """Analyze a production request and route to the right engines.
108
177
 
@@ -126,7 +195,15 @@ def classify_request(request: str) -> ConductorPlan:
126
195
  engine_scores[engine]["score"] += 1
127
196
 
128
197
  if not engine_scores:
129
- # Default: try Agent OS core loop (general "make it better")
198
+ # No engine matched but semantic moves might still apply
199
+ semantic_moves = _find_matching_semantic_moves(lower)
200
+ workflow_mode = _infer_workflow_mode(lower)
201
+ notes = ["General request — Agent OS core loop with goal vector"]
202
+ if semantic_moves:
203
+ notes.append(
204
+ f"Semantic moves available: {', '.join(m['move_id'] for m in semantic_moves[:3])}. "
205
+ "Use apply_semantic_move for intent-level execution."
206
+ )
130
207
  return ConductorPlan(
131
208
  request=request,
132
209
  request_type="general",
@@ -134,11 +211,14 @@ def classify_request(request: str) -> ConductorPlan:
134
211
  engine="agent_os",
135
212
  priority=1,
136
213
  reason="No specific engine matched — using core Agent OS loop",
137
- entry_tool="build_world_model",
138
- follow_up_tools=["evaluate_move"],
214
+ entry_tool="get_session_kernel",
215
+ follow_up_tools=["propose_next_best_move", "evaluate_move"],
139
216
  )],
140
217
  capability_requirements=["session_access"],
141
- notes=["General request — Agent OS core loop with goal vector"],
218
+ notes=notes,
219
+ semantic_moves=semantic_moves,
220
+ workflow_mode=workflow_mode,
221
+ experiment_recommended=(workflow_mode == "creative_search"),
142
222
  )
143
223
 
144
224
  # Sort engines by score (most matches = primary)
@@ -165,19 +245,37 @@ def classify_request(request: str) -> ConductorPlan:
165
245
  if any(r.engine == "performance_engine" for r in routes):
166
246
  caps.append("live_performance_safe")
167
247
 
168
- # Always suggest starting with Project Brain for complex multi-engine tasks
248
+ # Notes and guidance
169
249
  notes = []
170
250
  if len(routes) > 1:
171
- notes.append("Multi-engine task — call build_project_brain first for shared state")
251
+ notes.append("Multi-engine task — start with get_session_kernel for shared state")
172
252
  if any(r.engine == "mix_engine" for r in routes):
173
253
  notes.append("Mix engine works best with analyzer data — check get_capability_state")
174
254
 
255
+ # V2: Search semantic moves for matching intents
256
+ semantic_moves = _find_matching_semantic_moves(lower)
257
+
258
+ # V2: Infer workflow mode from request language
259
+ workflow_mode = _infer_workflow_mode(lower)
260
+
261
+ # V2: Recommend experiments for exploratory/creative requests
262
+ experiment_recommended = workflow_mode == "creative_search"
263
+
264
+ if semantic_moves:
265
+ notes.append(
266
+ f"Semantic moves available: {', '.join(m['move_id'] for m in semantic_moves[:3])}. "
267
+ "Use apply_semantic_move for intent-level execution."
268
+ )
269
+
175
270
  return ConductorPlan(
176
271
  request=request,
177
272
  request_type=primary_type,
178
273
  routes=routes,
179
274
  capability_requirements=caps,
180
275
  notes=notes,
276
+ semantic_moves=semantic_moves,
277
+ workflow_mode=workflow_mode,
278
+ experiment_recommended=experiment_recommended,
181
279
  )
182
280
 
183
281
 
@@ -1,6 +1,6 @@
1
1
  """Analyzer MCP tools — real-time spectral analysis and deep LOM access.
2
2
 
3
- 29 tools requiring the LivePilot Analyzer M4L device on the master track.
3
+ 30 tools requiring the LivePilot Analyzer M4L device on the master track.
4
4
  These tools are optional — all core tools work without the device.
5
5
  """
6
6
 
@@ -328,6 +328,29 @@ def load_device_by_uri(ctx: Context, track_index: int, uri: str) -> dict:
328
328
  return _postflight_loaded_device(ctx, result)
329
329
 
330
330
 
331
+ @mcp.tool()
332
+ def move_device(
333
+ ctx: Context,
334
+ track_index: int,
335
+ device_index: int,
336
+ target_index: int,
337
+ target_track_index: Optional[int] = None,
338
+ ) -> dict:
339
+ """Move a device to a new position on the same or different track.
340
+ track_index: 0+ for regular tracks, -1/-2/... for return tracks, -1000 for master."""
341
+ _validate_track_index(track_index)
342
+ _validate_device_index(device_index)
343
+ params: dict = {
344
+ "track_index": track_index,
345
+ "device_index": device_index,
346
+ "target_index": target_index,
347
+ }
348
+ if target_track_index is not None:
349
+ _validate_track_index(target_track_index)
350
+ params["target_track_index"] = target_track_index
351
+ return _get_ableton(ctx).send_command("move_device", params)
352
+
353
+
331
354
  @mcp.tool()
332
355
  def find_and_load_device(ctx: Context, track_index: int, device_name: str) -> dict:
333
356
  """Search the browser for a device by name and load it onto a track.
@@ -335,6 +358,17 @@ def find_and_load_device(ctx: Context, track_index: int, device_name: str) -> di
335
358
  _validate_track_index(track_index)
336
359
  if not device_name.strip():
337
360
  raise ValueError("device_name cannot be empty")
361
+
362
+ # Guardrail: bare Drum Rack produces silence (no samples loaded)
363
+ if device_name.strip().lower() == "drum rack":
364
+ raise ValueError(
365
+ "Loading a bare 'Drum Rack' creates an empty rack that produces silence. "
366
+ "Instead, use search_browser(path='drums') to find a kit preset "
367
+ "(e.g., '808 Core Kit'), then load it with load_browser_item(). "
368
+ "Or use DS drum synths (DS Kick, DS Snare, DS HH, DS Tom, DS Clap, "
369
+ "DS Cymbal) which are self-contained."
370
+ )
371
+
338
372
  result = _get_ableton(ctx).send_command("find_and_load_device", {
339
373
  "track_index": track_index,
340
374
  "device_name": device_name,
@@ -0,0 +1,6 @@
1
+ """Wonder Mode — controlled surprise for creative exploration.
2
+
3
+ Discovers relevant semantic moves, generates safe / strong / unexpected
4
+ variants with real move differentiation, and ranks by taste + identity +
5
+ novelty + coherence.
6
+ """
@@ -0,0 +1,84 @@
1
+ """Wonder Mode diagnosis builder — pure computation, zero I/O.
2
+
3
+ Builds a WonderDiagnosis from stuckness report, SongBrain, action
4
+ ledger, and open creative threads. Each input is optional — the
5
+ builder degrades gracefully.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Optional
11
+
12
+ from .session import WonderDiagnosis
13
+
14
+
15
+ # ── Problem class -> candidate domains mapping ────────────────────
16
+
17
+ _DOMAIN_MAP: dict[str, list[str]] = {
18
+ "overpolished_loop": ["arrangement", "transition"],
19
+ "identity_unclear": ["sound_design", "mix"],
20
+ "contrast_needed": ["transition", "arrangement", "sound_design"],
21
+ "hook_underdeveloped": ["sound_design", "mix"],
22
+ "too_dense_to_progress": ["mix", "arrangement"],
23
+ "too_safe_to_progress": ["sound_design", "transition"],
24
+ "section_missing": ["arrangement", "transition"],
25
+ "transition_not_earned": ["transition", "arrangement"],
26
+ }
27
+
28
+ _STUCKNESS_THRESHOLD = 0.2 # Below this, treat as user_request
29
+
30
+
31
+ def build_diagnosis(
32
+ stuckness_report: Optional[dict] = None,
33
+ song_brain: Optional[dict] = None,
34
+ action_ledger: Optional[list[dict]] = None,
35
+ ) -> WonderDiagnosis:
36
+ """Build a WonderDiagnosis from available session state.
37
+
38
+ Note: open_threads domain prioritization is deferred — not yet implemented.
39
+ """
40
+ degraded: list[str] = []
41
+
42
+ # 1. Determine trigger reason and problem class from stuckness
43
+ trigger_reason = "user_request"
44
+ problem_class = "exploration"
45
+ confidence = 0.0
46
+
47
+ # Check action ledger for repeated undos first
48
+ if action_ledger:
49
+ undo_count = sum(1 for a in action_ledger if a.get("kept") is False)
50
+ if undo_count >= 3:
51
+ trigger_reason = "repeated_undos"
52
+
53
+ if stuckness_report and stuckness_report.get("confidence", 0) >= _STUCKNESS_THRESHOLD:
54
+ trigger_reason = "stuckness_detected"
55
+ problem_class = stuckness_report.get("primary_rescue_type", "exploration") or "exploration"
56
+ confidence = stuckness_report.get("confidence", 0.0)
57
+
58
+ # If trigger is repeated_undos but no stuckness, keep problem_class as exploration
59
+ if trigger_reason == "repeated_undos" and problem_class == "exploration":
60
+ confidence = max(confidence, 0.3)
61
+
62
+ # 2. Read SongBrain
63
+ current_identity = ""
64
+ sacred_elements: list[dict] = []
65
+
66
+ if song_brain:
67
+ current_identity = song_brain.get("identity_core", "")
68
+ sacred_elements = song_brain.get("sacred_elements", [])
69
+ else:
70
+ degraded.append("song_brain")
71
+
72
+ # 3. Map problem_class to candidate domains
73
+ candidate_domains = _DOMAIN_MAP.get(problem_class, [])
74
+
75
+ return WonderDiagnosis(
76
+ trigger_reason=trigger_reason,
77
+ problem_class=problem_class,
78
+ current_identity=current_identity,
79
+ sacred_elements=sacred_elements,
80
+ blocked_dimensions=[],
81
+ candidate_domains=list(candidate_domains), # copy
82
+ confidence=confidence,
83
+ degraded_capabilities=degraded,
84
+ )