livepilot 1.20.3 → 1.21.1
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 +290 -0
- package/README.md +62 -33
- 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 +149 -12
- 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/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
|
@@ -27,14 +27,30 @@ def _empty_plan(move: SemanticMove, warnings: list[str]) -> CompiledPlan:
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def _compile_configure_device(move: SemanticMove, kernel: dict) -> CompiledPlan:
|
|
30
|
+
"""Compile configure_device.
|
|
31
|
+
|
|
32
|
+
v1.20 contract: seed_args.param_overrides (explicit dict of
|
|
33
|
+
{param_name: value}).
|
|
34
|
+
|
|
35
|
+
v1.21 additions (additive — v1.20 callers unaffected):
|
|
36
|
+
- seed_args.preset: str — named preset in the affordance library
|
|
37
|
+
- seed_args.device_slug: str — required when preset is used; v1.21
|
|
38
|
+
does not auto-infer from class_name
|
|
39
|
+
|
|
40
|
+
Merge contract: preset resolves first, then explicit param_overrides
|
|
41
|
+
entries merge on top (last-write-wins at dict-key granularity).
|
|
42
|
+
"""
|
|
30
43
|
args = kernel.get("seed_args") or {}
|
|
31
44
|
track_index = args.get("track_index")
|
|
32
45
|
device_index = args.get("device_index")
|
|
33
|
-
|
|
46
|
+
explicit_overrides = args.get("param_overrides")
|
|
47
|
+
preset_name = args.get("preset")
|
|
48
|
+
device_slug = args.get("device_slug")
|
|
34
49
|
|
|
35
|
-
if track_index is None or device_index is None
|
|
50
|
+
if track_index is None or device_index is None:
|
|
36
51
|
return _empty_plan(move, [
|
|
37
|
-
"configure_device requires seed_args.track_index + device_index
|
|
52
|
+
"configure_device requires seed_args.track_index + device_index "
|
|
53
|
+
"(plus either param_overrides or a preset+device_slug pair)"
|
|
38
54
|
])
|
|
39
55
|
if not isinstance(track_index, int) or not isinstance(device_index, int):
|
|
40
56
|
return _empty_plan(move, [
|
|
@@ -43,14 +59,49 @@ def _compile_configure_device(move: SemanticMove, kernel: dict) -> CompiledPlan:
|
|
|
43
59
|
])
|
|
44
60
|
if device_index < 0:
|
|
45
61
|
return _empty_plan(move, [f"device_index must be non-negative, got {device_index}"])
|
|
46
|
-
|
|
62
|
+
|
|
63
|
+
# explicit param_overrides is now optional (may be None if preset provides
|
|
64
|
+
# everything), but when present must be a dict.
|
|
65
|
+
if explicit_overrides is not None and not isinstance(explicit_overrides, dict):
|
|
47
66
|
return _empty_plan(move, [
|
|
48
|
-
f"param_overrides must be a dict[str, Any], got
|
|
67
|
+
f"param_overrides must be a dict[str, Any], got "
|
|
68
|
+
f"{type(explicit_overrides).__name__}"
|
|
49
69
|
])
|
|
50
|
-
|
|
70
|
+
|
|
71
|
+
# v1.21: resolve preset if requested
|
|
72
|
+
preset_overrides: dict = {}
|
|
73
|
+
if preset_name is not None:
|
|
74
|
+
if not device_slug:
|
|
75
|
+
return _empty_plan(move, [
|
|
76
|
+
"preset seed_arg requires device_slug (v1.21 contract — "
|
|
77
|
+
"auto-inference from class_name is v1.22 scope). Example: "
|
|
78
|
+
"args={\"track_index\": -1, \"device_index\": 0, "
|
|
79
|
+
"\"device_slug\": \"reverb\", \"preset\": \"dub-cathedral\"}"
|
|
80
|
+
])
|
|
81
|
+
# Late import so mcp_server.semantic_moves doesn't hard-depend on
|
|
82
|
+
# mcp_server.affordances at import time — branch is only taken
|
|
83
|
+
# when a caller explicitly asks for a preset.
|
|
84
|
+
from ..affordances import resolve_preset
|
|
85
|
+
resolved = resolve_preset(device_slug, preset_name)
|
|
86
|
+
if resolved is None:
|
|
87
|
+
return _empty_plan(move, [
|
|
88
|
+
f"No preset {preset_name!r} for device slug {device_slug!r}. "
|
|
89
|
+
f"Check mcp_server/affordances/devices/{device_slug}.yaml "
|
|
90
|
+
f"exists and contains the named preset."
|
|
91
|
+
])
|
|
92
|
+
preset_overrides = resolved
|
|
93
|
+
|
|
94
|
+
# Merge: preset resolves first, explicit param_overrides merge on top
|
|
95
|
+
# (last-write-wins at dict-key granularity).
|
|
96
|
+
merged: dict = dict(preset_overrides)
|
|
97
|
+
if explicit_overrides:
|
|
98
|
+
merged.update(explicit_overrides)
|
|
99
|
+
|
|
100
|
+
if not merged:
|
|
51
101
|
return _empty_plan(move, [
|
|
52
|
-
"
|
|
53
|
-
"
|
|
102
|
+
"configure_device requires either a non-empty param_overrides "
|
|
103
|
+
"dict OR a preset+device_slug combination that resolves to "
|
|
104
|
+
"params. Neither was provided."
|
|
54
105
|
])
|
|
55
106
|
|
|
56
107
|
# WIRE-FORMAT NOTE: compiled steps use the remote_command backend,
|
|
@@ -61,9 +112,12 @@ def _compile_configure_device(move: SemanticMove, kernel: dict) -> CompiledPlan:
|
|
|
61
112
|
# exclusively. Emit that key directly.
|
|
62
113
|
parameters = [
|
|
63
114
|
{"name_or_index": str(name), "value": value}
|
|
64
|
-
for name, value in
|
|
115
|
+
for name, value in merged.items()
|
|
65
116
|
]
|
|
66
117
|
|
|
118
|
+
preset_suffix = (
|
|
119
|
+
f" from preset {device_slug}/{preset_name}" if preset_name else ""
|
|
120
|
+
)
|
|
67
121
|
step = CompiledStep(
|
|
68
122
|
tool="batch_set_parameters",
|
|
69
123
|
params={
|
|
@@ -72,9 +126,9 @@ def _compile_configure_device(move: SemanticMove, kernel: dict) -> CompiledPlan:
|
|
|
72
126
|
"parameters": parameters,
|
|
73
127
|
},
|
|
74
128
|
description=(
|
|
75
|
-
f"Configure device at track {track_index}, device_index
|
|
76
|
-
f"set {len(parameters)}
|
|
77
|
-
f"{', '.join(p['name_or_index'] for p in parameters)}"
|
|
129
|
+
f"Configure device at track {track_index}, device_index "
|
|
130
|
+
f"{device_index}{preset_suffix} — set {len(parameters)} "
|
|
131
|
+
f"parameter(s): {', '.join(p['name_or_index'] for p in parameters)}"
|
|
78
132
|
),
|
|
79
133
|
verify_after=True,
|
|
80
134
|
backend="remote_command",
|
|
@@ -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.
|
|
@@ -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.1",
|
|
4
4
|
"mcpName": "io.github.dreamrec/livepilot",
|
|
5
|
-
"description": "Agentic production system for Ableton Live 12 — 430 tools, 53 domains. Device atlas (1305 devices),
|
|
5
|
+
"description": "Agentic production system for Ableton Live 12 — 430 tools, 53 domains, 44 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.1"
|
|
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": "430-tool agentic MCP production system for Ableton Live 12 — device atlas,
|
|
4
|
+
"description": "430-tool agentic MCP production system for Ableton Live 12 — 53 domains, 44 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.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "livepilot",
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.21.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|