livepilot 1.20.2 → 1.21.0
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.
- package/CHANGELOG.md +222 -0
- package/README.md +64 -37
- package/m4l_device/LivePilot_Analyzer.amxd +0 -0
- package/m4l_device/livepilot_bridge.js +1 -1
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/affordances/__init__.py +31 -0
- package/mcp_server/affordances/_schema.py +143 -0
- package/mcp_server/affordances/devices/auto-filter.yaml +14 -0
- package/mcp_server/affordances/devices/delay.yaml +18 -0
- package/mcp_server/affordances/devices/reverb.yaml +16 -0
- package/mcp_server/affordances/presets.py +74 -0
- package/mcp_server/experiment/tools.py +113 -0
- package/mcp_server/memory/tools.py +10 -0
- package/mcp_server/runtime/tools.py +7 -0
- package/mcp_server/semantic_moves/device_creation_compilers.py +37 -3
- package/mcp_server/semantic_moves/device_creation_moves.py +7 -7
- package/mcp_server/semantic_moves/device_mutation_compilers.py +66 -12
- package/mcp_server/semantic_moves/performance_compilers.py +157 -0
- package/mcp_server/semantic_moves/performance_moves.py +46 -1
- package/mcp_server/semantic_moves/tools.py +8 -5
- package/mcp_server/song_brain/tools.py +6 -0
- package/mcp_server/stuckness_detector/tools.py +4 -0
- package/mcp_server/tools/_agent_os_engine/taste.py +6 -0
- package/mcp_server/tools/analyzer.py +125 -0
- package/mcp_server/tools/memory.py +7 -0
- package/mcp_server/wonder_mode/tools.py +4 -0
- package/package.json +2 -2
- package/remote_script/LivePilot/__init__.py +1 -1
- package/server.json +3 -3
|
@@ -200,9 +200,166 @@ def _compile_emergency_simplify(move: SemanticMove, kernel: dict) -> CompiledPla
|
|
|
200
200
|
)
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
def _compile_configure_record_readiness(move: SemanticMove, kernel: dict) -> CompiledPlan:
|
|
204
|
+
"""Compile configure_record_readiness.
|
|
205
|
+
|
|
206
|
+
seed_args:
|
|
207
|
+
track_index: int — required; must be >= 0 (return tracks can't be armed)
|
|
208
|
+
armed: bool — required
|
|
209
|
+
exclusive: bool — optional, default False
|
|
210
|
+
|
|
211
|
+
Steps:
|
|
212
|
+
exclusive=True + armed=True
|
|
213
|
+
→ N+1 steps: set_track_arm(other_idx, arm=False) for every
|
|
214
|
+
regular track ≠ target, then set_track_arm(target, arm=True).
|
|
215
|
+
— Emulates Ableton's exclusive-arm mode manually. Cannot use
|
|
216
|
+
``set_exclusive_arm`` directly: ``song.exclusive_arm`` has
|
|
217
|
+
no Python setter in Live 12.4 (property getter only — a
|
|
218
|
+
pre-existing v1.20.3 Remote Script bug surfaced during v1.21's
|
|
219
|
+
live-test pre-flight). The manual disarm loop produces the
|
|
220
|
+
same user-facing outcome (target is the single armed track)
|
|
221
|
+
without depending on the broken toggle.
|
|
222
|
+
else
|
|
223
|
+
→ [set_track_arm(track_index, arm=armed)]
|
|
224
|
+
|
|
225
|
+
Wire-format discipline: emit `arm` (not `armed`). The remote_command
|
|
226
|
+
backend bypasses the MCP tool rename layer (``tools/tracks.py:317``
|
|
227
|
+
renames ``armed → arm`` before send_command), so the compiler must
|
|
228
|
+
emit ``arm`` directly. See remote_script/LivePilot/tracks.py:263
|
|
229
|
+
for the Remote Script handler.
|
|
230
|
+
"""
|
|
231
|
+
args = kernel.get("seed_args") or {}
|
|
232
|
+
track_index = args.get("track_index")
|
|
233
|
+
armed = args.get("armed")
|
|
234
|
+
exclusive = args.get("exclusive", False)
|
|
235
|
+
|
|
236
|
+
# Required-seed-args
|
|
237
|
+
if track_index is None or armed is None:
|
|
238
|
+
return CompiledPlan(
|
|
239
|
+
move_id=move.move_id,
|
|
240
|
+
intent=move.intent,
|
|
241
|
+
summary="missing required seed_args",
|
|
242
|
+
warnings=[
|
|
243
|
+
"configure_record_readiness requires seed_args.track_index "
|
|
244
|
+
"(int) and seed_args.armed (bool). Example: "
|
|
245
|
+
"apply_semantic_move(\"configure_record_readiness\", "
|
|
246
|
+
"mode=\"explore\", args={\"track_index\": 0, \"armed\": True})"
|
|
247
|
+
],
|
|
248
|
+
)
|
|
249
|
+
if not isinstance(track_index, int):
|
|
250
|
+
return CompiledPlan(
|
|
251
|
+
move_id=move.move_id, intent=move.intent,
|
|
252
|
+
summary="invalid track_index type",
|
|
253
|
+
warnings=[f"track_index must be int, got {type(track_index).__name__}"],
|
|
254
|
+
)
|
|
255
|
+
if not isinstance(armed, bool):
|
|
256
|
+
return CompiledPlan(
|
|
257
|
+
move_id=move.move_id, intent=move.intent,
|
|
258
|
+
summary="invalid armed type",
|
|
259
|
+
warnings=[f"armed must be bool, got {type(armed).__name__}"],
|
|
260
|
+
)
|
|
261
|
+
if not isinstance(exclusive, bool):
|
|
262
|
+
return CompiledPlan(
|
|
263
|
+
move_id=move.move_id, intent=move.intent,
|
|
264
|
+
summary="invalid exclusive type",
|
|
265
|
+
warnings=[f"exclusive must be bool, got {type(exclusive).__name__}"],
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Contradiction: exclusive requires armed
|
|
269
|
+
if exclusive and not armed:
|
|
270
|
+
return CompiledPlan(
|
|
271
|
+
move_id=move.move_id, intent=move.intent,
|
|
272
|
+
summary="contradictory exclusive+armed",
|
|
273
|
+
warnings=[
|
|
274
|
+
"exclusive=True requires armed=True (the point of exclusive "
|
|
275
|
+
"is to become the single armed track); to disarm individually "
|
|
276
|
+
"call configure_record_readiness with exclusive=False"
|
|
277
|
+
],
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Return-track constraint (Ableton's handler rejects negative indices)
|
|
281
|
+
if track_index < 0:
|
|
282
|
+
return CompiledPlan(
|
|
283
|
+
move_id=move.move_id, intent=move.intent,
|
|
284
|
+
summary="return tracks cannot be armed",
|
|
285
|
+
warnings=[
|
|
286
|
+
f"Cannot arm a return track (track_index={track_index}). "
|
|
287
|
+
"Ableton's set_track_arm handler rejects negative indices "
|
|
288
|
+
"(remote_script/LivePilot/tracks.py:261). Provide a regular "
|
|
289
|
+
"track index (>= 0)."
|
|
290
|
+
],
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
steps: list[CompiledStep] = []
|
|
294
|
+
if exclusive and armed:
|
|
295
|
+
# Manual emulation of Ableton's exclusive-arm mode (set_exclusive_arm
|
|
296
|
+
# handler is broken in Live 12.4 per above docstring). Emit N+1
|
|
297
|
+
# steps: disarm every other regular track, then arm target.
|
|
298
|
+
all_tracks = kernel.get("session_info", {}).get("tracks", []) or []
|
|
299
|
+
if not all_tracks:
|
|
300
|
+
return CompiledPlan(
|
|
301
|
+
move_id=move.move_id, intent=move.intent,
|
|
302
|
+
summary="exclusive mode requires session_info.tracks",
|
|
303
|
+
warnings=[
|
|
304
|
+
"configure_record_readiness exclusive=True requires "
|
|
305
|
+
"session_info.tracks to know which other tracks to disarm. "
|
|
306
|
+
"apply_semantic_move builds session_info automatically; "
|
|
307
|
+
"direct compiler callers must supply it explicitly."
|
|
308
|
+
],
|
|
309
|
+
)
|
|
310
|
+
for track in all_tracks:
|
|
311
|
+
idx = track.get("index")
|
|
312
|
+
if idx is None or idx == track_index:
|
|
313
|
+
continue
|
|
314
|
+
# Skip return / master — can't be armed anyway, and Ableton's
|
|
315
|
+
# set_track_arm rejects negative indices (tracks.py:261).
|
|
316
|
+
if track.get("type") in ("return", "master"):
|
|
317
|
+
continue
|
|
318
|
+
if isinstance(idx, int) and idx < 0:
|
|
319
|
+
continue
|
|
320
|
+
name = track.get("name", f"track {idx}")
|
|
321
|
+
steps.append(CompiledStep(
|
|
322
|
+
tool="set_track_arm",
|
|
323
|
+
params={"track_index": idx, "arm": False},
|
|
324
|
+
description=f"Disarm {name} (exclusive-arm emulation)",
|
|
325
|
+
backend="remote_command",
|
|
326
|
+
))
|
|
327
|
+
steps.append(CompiledStep(
|
|
328
|
+
tool="set_track_arm",
|
|
329
|
+
params={"track_index": track_index, "arm": True},
|
|
330
|
+
description=(
|
|
331
|
+
f"Arm track {track_index} "
|
|
332
|
+
f"(exclusive — single-armed, {len(steps)} other(s) disarmed)"
|
|
333
|
+
),
|
|
334
|
+
backend="remote_command",
|
|
335
|
+
))
|
|
336
|
+
summary = (
|
|
337
|
+
f"Exclusive-arm track {track_index} — "
|
|
338
|
+
f"{len(steps)-1} other regular track(s) disarmed first"
|
|
339
|
+
)
|
|
340
|
+
else:
|
|
341
|
+
steps.append(CompiledStep(
|
|
342
|
+
tool="set_track_arm",
|
|
343
|
+
params={"track_index": track_index, "arm": armed},
|
|
344
|
+
description=f"{'Arm' if armed else 'Disarm'} track {track_index}",
|
|
345
|
+
backend="remote_command",
|
|
346
|
+
))
|
|
347
|
+
summary = f"{'Arm' if armed else 'Disarm'} track {track_index}"
|
|
348
|
+
|
|
349
|
+
return CompiledPlan(
|
|
350
|
+
move_id=move.move_id,
|
|
351
|
+
intent=move.intent,
|
|
352
|
+
steps=steps,
|
|
353
|
+
risk_level=move.risk_level,
|
|
354
|
+
summary=summary,
|
|
355
|
+
requires_approval=False, # Performance moves execute immediately
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
203
359
|
# ── Register ────────────────────────────────────────────────────────────────
|
|
204
360
|
|
|
205
361
|
register_compiler("recover_energy", _compile_recover_energy)
|
|
206
362
|
register_compiler("decompress_tension", _compile_decompress_tension)
|
|
207
363
|
register_compiler("safe_spotlight", _compile_safe_spotlight)
|
|
208
364
|
register_compiler("emergency_simplify", _compile_emergency_simplify)
|
|
365
|
+
register_compiler("configure_record_readiness", _compile_configure_record_readiness)
|
|
@@ -76,6 +76,51 @@ EMERGENCY_SIMPLIFY = SemanticMove(
|
|
|
76
76
|
],
|
|
77
77
|
)
|
|
78
78
|
|
|
79
|
+
# v1.21: configure_record_readiness — closes the tech_debt entry from
|
|
80
|
+
# v1.20 live test 6 (raw set_track_arm without a semantic-move wrapper).
|
|
81
|
+
# seed_args: {track_index: int, armed: bool, exclusive?: bool = False}.
|
|
82
|
+
# Note: `armed` here is the *ergonomic* seed_arg name — the compiler
|
|
83
|
+
# translates it to the wire-format key `arm` per remote_script/LivePilot/
|
|
84
|
+
# tracks.py:263. See _compile_configure_record_readiness.
|
|
85
|
+
CONFIGURE_RECORD_READINESS = SemanticMove(
|
|
86
|
+
move_id="configure_record_readiness",
|
|
87
|
+
family="performance",
|
|
88
|
+
intent=(
|
|
89
|
+
"Arm or disarm a track for recording. When exclusive=True, disarms "
|
|
90
|
+
"all other regular tracks then arms the target — the standard "
|
|
91
|
+
"one-take recording setup (Live 12.4's `song.exclusive_arm` toggle "
|
|
92
|
+
"is read-only from Python, so the compiler emulates the mode via "
|
|
93
|
+
"a manual disarm loop)."
|
|
94
|
+
),
|
|
95
|
+
targets={},
|
|
96
|
+
protect={"signal_integrity": 0.7},
|
|
97
|
+
risk_level="low",
|
|
98
|
+
required_capabilities=["session"],
|
|
99
|
+
plan_template=[
|
|
100
|
+
# Informational — compiler builds concrete steps from seed_args.
|
|
101
|
+
{
|
|
102
|
+
"tool": "set_track_arm",
|
|
103
|
+
"params": {"description": "Arm or disarm the target track"},
|
|
104
|
+
"description": "Toggle track arm",
|
|
105
|
+
"backend": "remote_command",
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
verification_plan=[
|
|
109
|
+
{
|
|
110
|
+
"tool": "get_track_info",
|
|
111
|
+
"check": "track's arm field matches requested value",
|
|
112
|
+
"backend": "remote_command",
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
79
118
|
# Register all performance moves
|
|
80
|
-
for _move in [
|
|
119
|
+
for _move in [
|
|
120
|
+
RECOVER_ENERGY,
|
|
121
|
+
DECOMPRESS_TENSION,
|
|
122
|
+
SAFE_SPOTLIGHT,
|
|
123
|
+
EMERGENCY_SIMPLIFY,
|
|
124
|
+
CONFIGURE_RECORD_READINESS, # v1.21
|
|
125
|
+
]:
|
|
81
126
|
register(_move)
|
|
@@ -397,11 +397,14 @@ async def apply_semantic_move(
|
|
|
397
397
|
success_count = sum(1 for s in executed_steps if s["ok"])
|
|
398
398
|
failure_count = sum(1 for s in executed_steps if not s["ok"])
|
|
399
399
|
|
|
400
|
-
#
|
|
401
|
-
#
|
|
402
|
-
#
|
|
403
|
-
#
|
|
404
|
-
#
|
|
400
|
+
# store_purpose: writer
|
|
401
|
+
# v1.20: apply_semantic_move is the canonical semantic-moves writer
|
|
402
|
+
# to the SessionLedger. Downstream anti-repetition / stuckness /
|
|
403
|
+
# song-brain readers (annotated store_purpose: anti_repetition) consume
|
|
404
|
+
# entries this block writes. commit_experiment (v1.21) mirrors this
|
|
405
|
+
# pattern with a "composer|experiment" engine tag instead of
|
|
406
|
+
# "semantic_moves". Best-effort — a ledger write failure must not
|
|
407
|
+
# fail the overall move.
|
|
405
408
|
ledger_entry_id: Optional[str] = None
|
|
406
409
|
try:
|
|
407
410
|
from ..runtime.action_ledger import SessionLedger
|
|
@@ -139,6 +139,12 @@ def _fetch_session_data(ctx: Context) -> dict:
|
|
|
139
139
|
except Exception as exc:
|
|
140
140
|
logger.debug("_fetch_session_data failed: %s", exc)
|
|
141
141
|
|
|
142
|
+
# store_purpose: anti_repetition
|
|
143
|
+
# song_brain's _fetch_session_data surfaces recent moves into the
|
|
144
|
+
# brain's context so section analysis can detect repeated work
|
|
145
|
+
# patterns. Recency signal — NOT the persistent technique library.
|
|
146
|
+
# Correct store: SessionLedger.get_recent_moves (v1.20 director SKILL
|
|
147
|
+
# previously pointed at memory_list for this, which was wrong).
|
|
142
148
|
# Recent moves — from session-scoped action ledger
|
|
143
149
|
try:
|
|
144
150
|
from ..runtime.action_ledger import SessionLedger
|
|
@@ -27,6 +27,10 @@ def _get_action_history(ctx: Context) -> list[dict]:
|
|
|
27
27
|
repeated undos, local-tweaking, loop-without-structure detection.
|
|
28
28
|
Falls back to empty list when no ledger data exists (graceful degradation).
|
|
29
29
|
"""
|
|
30
|
+
# store_purpose: anti_repetition
|
|
31
|
+
# Stuckness detection reads recent_moves to spot repeated undos,
|
|
32
|
+
# local-tweaking loops, and loop-without-structure patterns.
|
|
33
|
+
# Recency signal — NOT the persistent technique library.
|
|
30
34
|
try:
|
|
31
35
|
from ..runtime.action_ledger import SessionLedger
|
|
32
36
|
ledger = ctx.lifespan_context.get("action_ledger")
|
|
@@ -14,6 +14,12 @@ from typing import Any, Optional
|
|
|
14
14
|
from .models import QUALITY_DIMENSIONS, _clamp
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
# store_purpose: technique_library
|
|
18
|
+
# analyze_outcome_history consumes payloads from the persistent
|
|
19
|
+
# technique library (memory_list(type="outcome")) — NOT recency data.
|
|
20
|
+
# Taste-inference work reads accumulated outcome records, unlike
|
|
21
|
+
# anti-repetition which reads SessionLedger.get_recent_moves.
|
|
22
|
+
|
|
17
23
|
# ── Outcome Memory Analysis (Round 1) ────────────────────────────────
|
|
18
24
|
def analyze_outcome_history(outcomes: list[dict]) -> dict:
|
|
19
25
|
"""Analyze accumulated outcome memories to identify user taste patterns.
|
|
@@ -1913,3 +1913,128 @@ async def compressor_set_sidechain(
|
|
|
1913
1913
|
params["source_channel"] = str(source_channel)
|
|
1914
1914
|
ableton = ctx.lifespan_context["ableton"]
|
|
1915
1915
|
return ableton.send_command("set_compressor_sidechain", params)
|
|
1916
|
+
|
|
1917
|
+
|
|
1918
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
1919
|
+
# v1.20.3 — ensure_analyzer_on_master
|
|
1920
|
+
#
|
|
1921
|
+
# Motivated by the v1.20.1 live-test campaign operator-error (see
|
|
1922
|
+
# ~/Desktop/DREAM AI/demo Project/REPORT.md). The LLM operator had a
|
|
1923
|
+
# clear global-memory instruction to load LivePilot_Analyzer on master
|
|
1924
|
+
# proactively on a fresh session, and missed it — leaving analyzer-
|
|
1925
|
+
# gated moves brittle. This tool closes that class of error by making
|
|
1926
|
+
# the load idempotent + automatable.
|
|
1927
|
+
|
|
1928
|
+
_ANALYZER_DEVICE_NAME = "LivePilot_Analyzer"
|
|
1929
|
+
|
|
1930
|
+
|
|
1931
|
+
def _load_analyzer_impl(ctx, track_index: int, device_name: str,
|
|
1932
|
+
allow_duplicate: bool = False) -> dict:
|
|
1933
|
+
"""Indirection so tests can monkeypatch the load call without having
|
|
1934
|
+
to fake the full find_and_load_device MCP-tool machinery. Production
|
|
1935
|
+
calls straight through to the existing tool."""
|
|
1936
|
+
from .devices import find_and_load_device
|
|
1937
|
+
return find_and_load_device(
|
|
1938
|
+
ctx,
|
|
1939
|
+
track_index=track_index,
|
|
1940
|
+
device_name=device_name,
|
|
1941
|
+
allow_duplicate=allow_duplicate,
|
|
1942
|
+
)
|
|
1943
|
+
|
|
1944
|
+
|
|
1945
|
+
@mcp.tool()
|
|
1946
|
+
def ensure_analyzer_on_master(ctx: Context) -> dict:
|
|
1947
|
+
"""Idempotent pre-flight: load LivePilot_Analyzer on master if missing.
|
|
1948
|
+
|
|
1949
|
+
Safe to call at the start of any session or before any move that
|
|
1950
|
+
declares analyzer dependency. Calling it repeatedly is cheap —
|
|
1951
|
+
subsequent calls short-circuit via a single get_master_track read.
|
|
1952
|
+
|
|
1953
|
+
CLAUDE.md invariant: "LivePilot_Analyzer must be LAST on master."
|
|
1954
|
+
This tool reports whether the invariant holds via ``is_last_on_master``;
|
|
1955
|
+
it does NOT move the device (that's a user action in Ableton's GUI).
|
|
1956
|
+
|
|
1957
|
+
Return shape:
|
|
1958
|
+
- status: one of {"already_loaded", "loaded", "install_required", "failed"}
|
|
1959
|
+
- device_index: int — position of the analyzer on master (when present)
|
|
1960
|
+
- is_last_on_master: bool — True when analyzer is the last device
|
|
1961
|
+
- duplicate_count: int — 2+ when multiple analyzers exist (shouldn't)
|
|
1962
|
+
- warning: str | None — surfaces last-on-master violations
|
|
1963
|
+
- hint: str — actionable next step when status != "already_loaded"/"loaded"
|
|
1964
|
+
- error: str | None — present on status="failed"
|
|
1965
|
+
"""
|
|
1966
|
+
ableton = ctx.lifespan_context["ableton"]
|
|
1967
|
+
|
|
1968
|
+
# 1. Inspect the master chain for an existing analyzer.
|
|
1969
|
+
try:
|
|
1970
|
+
master = ableton.send_command("get_master_track")
|
|
1971
|
+
except Exception as exc:
|
|
1972
|
+
return {
|
|
1973
|
+
"status": "failed",
|
|
1974
|
+
"error": f"Could not read master track: {exc}",
|
|
1975
|
+
"hint": "Verify MCP connection to Ableton; retry with get_session_info first.",
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
devices = (master or {}).get("devices") or []
|
|
1979
|
+
matches = [d for d in devices if d.get("name") == _ANALYZER_DEVICE_NAME]
|
|
1980
|
+
|
|
1981
|
+
if matches:
|
|
1982
|
+
# 2. Already loaded — build a status report without side effects.
|
|
1983
|
+
first = matches[0]
|
|
1984
|
+
device_index = first.get("index")
|
|
1985
|
+
is_last = False
|
|
1986
|
+
if devices:
|
|
1987
|
+
last_name = devices[-1].get("name")
|
|
1988
|
+
is_last = (last_name == _ANALYZER_DEVICE_NAME)
|
|
1989
|
+
|
|
1990
|
+
result: dict = {
|
|
1991
|
+
"status": "already_loaded",
|
|
1992
|
+
"device_index": device_index,
|
|
1993
|
+
"is_last_on_master": is_last,
|
|
1994
|
+
"duplicate_count": len(matches),
|
|
1995
|
+
}
|
|
1996
|
+
if len(matches) > 1:
|
|
1997
|
+
result["warning"] = (
|
|
1998
|
+
f"{len(matches)} instances of {_ANALYZER_DEVICE_NAME} on master — "
|
|
1999
|
+
"only one is needed. Remove extras in Ableton's GUI."
|
|
2000
|
+
)
|
|
2001
|
+
elif not is_last:
|
|
2002
|
+
result["warning"] = (
|
|
2003
|
+
f"{_ANALYZER_DEVICE_NAME} is not the LAST device on master. "
|
|
2004
|
+
"CLAUDE.md invariant requires it to come after ALL effects so "
|
|
2005
|
+
"it reads the final output, not pre-effect signal. "
|
|
2006
|
+
"Move it to the end of the master chain in Ableton's GUI."
|
|
2007
|
+
)
|
|
2008
|
+
return result
|
|
2009
|
+
|
|
2010
|
+
# 3. Not on master — try loading from the Ableton browser.
|
|
2011
|
+
try:
|
|
2012
|
+
loaded = _load_analyzer_impl(
|
|
2013
|
+
ctx,
|
|
2014
|
+
track_index=-1000, # master convention
|
|
2015
|
+
device_name=_ANALYZER_DEVICE_NAME,
|
|
2016
|
+
allow_duplicate=False,
|
|
2017
|
+
)
|
|
2018
|
+
except Exception as exc:
|
|
2019
|
+
# Typical path: device not in browser (user hasn't installed via
|
|
2020
|
+
# install_m4l_device yet).
|
|
2021
|
+
return {
|
|
2022
|
+
"status": "install_required",
|
|
2023
|
+
"error": str(exc),
|
|
2024
|
+
"hint": (
|
|
2025
|
+
"LivePilot_Analyzer not found in Ableton's browser. Install "
|
|
2026
|
+
"first with install_m4l_device(source_path="
|
|
2027
|
+
"\"<repo>/m4l_device/LivePilot_Analyzer.amxd\") — that copies "
|
|
2028
|
+
"the .amxd into ~/Music/Ableton/User Library/Presets/Audio "
|
|
2029
|
+
"Effects/Max Audio Effect/. Then call ensure_analyzer_on_master "
|
|
2030
|
+
"again to complete the load."
|
|
2031
|
+
),
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
device_index = (loaded or {}).get("device_index")
|
|
2035
|
+
return {
|
|
2036
|
+
"status": "loaded",
|
|
2037
|
+
"device_index": device_index,
|
|
2038
|
+
"is_last_on_master": True, # fresh load always lands at the end
|
|
2039
|
+
"duplicate_count": 1,
|
|
2040
|
+
}
|
|
@@ -150,6 +150,13 @@ def _generate_replay_steps(technique: dict) -> list[str]:
|
|
|
150
150
|
return ["Replay the technique from the stored payload"]
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
# store_purpose: mcp_tool_definition
|
|
154
|
+
# memory_list is the MCP tool for browsing the persistent technique
|
|
155
|
+
# library (memory_learn-populated). Callers that use its output for
|
|
156
|
+
# anti-repetition recency have the v1.20 store-confusion BUG: correct
|
|
157
|
+
# pattern is SessionLedger.get_recent_moves or get_action_ledger_summary.
|
|
158
|
+
# The test tests/test_ledger_readers.py::TestAntiRepetitionUsesLedgerNotMemoryList
|
|
159
|
+
# enforces this invariant across the codebase.
|
|
153
160
|
@mcp.tool()
|
|
154
161
|
def memory_list(
|
|
155
162
|
ctx: Context,
|
|
@@ -148,6 +148,10 @@ def _get_active_constraints():
|
|
|
148
148
|
|
|
149
149
|
def _get_ledger_entries(ctx: Context) -> list[dict]:
|
|
150
150
|
"""Get recent action ledger entries as dicts."""
|
|
151
|
+
# store_purpose: anti_repetition
|
|
152
|
+
# Wonder Mode's rescue trigger reads recent_moves to feed the
|
|
153
|
+
# stuckness detector — classic recency signal, NOT the persistent
|
|
154
|
+
# technique library. Correct store: SessionLedger.get_recent_moves.
|
|
151
155
|
try:
|
|
152
156
|
from ..runtime.action_ledger import SessionLedger
|
|
153
157
|
ledger: SessionLedger = ctx.lifespan_context.setdefault(
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"mcpName": "io.github.dreamrec/livepilot",
|
|
5
|
-
"description": "Agentic production system for Ableton Live 12 —
|
|
5
|
+
"description": "Agentic production system for Ableton Live 12 — 430 tools, 53 domains, 43 semantic moves. Device atlas (1305 devices, 120 enriched, 7 indexes), Splice intelligence (gRPC + GraphQL describe-a-sound + preview + collections + presets), 9-band spectral perception auto-loaded via ensure_analyzer_on_master, Creative Director skill, technique memory, 12 creative intelligence engines",
|
|
6
6
|
"author": "Pilot Studio",
|
|
7
7
|
"license": "BSL-1.1",
|
|
8
8
|
"type": "commonjs",
|
|
@@ -5,7 +5,7 @@ Entry point for the ControlSurface. Ableton calls create_instance(c_instance)
|
|
|
5
5
|
when this script is selected in Preferences > Link, Tempo & MIDI.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "1.
|
|
8
|
+
__version__ = "1.21.0"
|
|
9
9
|
|
|
10
10
|
from _Framework.ControlSurface import ControlSurface
|
|
11
11
|
from . import router
|
package/server.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.dreamrec/livepilot",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "430-tool agentic MCP production system for Ableton Live 12 — 53 domains, 43 semantic moves, device atlas (1305 devices), Splice intelligence (gRPC + GraphQL), 9-band spectral perception auto-loaded, Creative Director skill, technique memory, 12 creative engines",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "https://github.com/dreamrec/LivePilot",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.21.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "livepilot",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.21.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|