livepilot 1.18.0 → 1.18.2
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 +194 -0
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/composer/branch_producer.py +12 -1
- package/mcp_server/composer/layer_planner.py +13 -0
- package/mcp_server/composer/prompt_parser.py +12 -1
- package/mcp_server/experiment/models.py +72 -2
- package/mcp_server/experiment/tools.py +10 -6
- package/mcp_server/wonder_mode/engine.py +114 -11
- package/package.json +1 -1
- package/remote_script/LivePilot/__init__.py +1 -1
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,199 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.18.2 — Wonder cold-start + tie-break + genre catalog closure (April 24 2026)
|
|
4
|
+
|
|
5
|
+
Second patch in the v1.18.x series. Three items from the v1.18.0/v1.18.1
|
|
6
|
+
Known Issues list resolved. Test suite grew to 2785 pass, xfail marker
|
|
7
|
+
removed (formerly 1, now 0).
|
|
8
|
+
|
|
9
|
+
### Fixes
|
|
10
|
+
|
|
11
|
+
- **#10 Wonder Mode zero-variant degradation on empty session context.**
|
|
12
|
+
`enter_wonder_mode` on an empty/sparse session was returning 3
|
|
13
|
+
IDENTICAL `analytical_only` variants all with intent "Analytical
|
|
14
|
+
suggestion for: <request>". Live-verified during v1.18.0 Test 4
|
|
15
|
+
("I'm stuck" on a 4-track empty session). Fix: introduced
|
|
16
|
+
`_COLD_START_SEEDS` in `mcp_server/wonder_mode/engine.py` — three
|
|
17
|
+
distinct starting-point suggestions covering different families
|
|
18
|
+
(`device_creation × rhythmic` + `sound_design × harmonic` +
|
|
19
|
+
`mix × architecture-first`). When `executable_count == 0`, the
|
|
20
|
+
padding loop uses `build_cold_start_variant()` which pulls from
|
|
21
|
+
the seed set by index, producing genuinely distinct variants with
|
|
22
|
+
specific actionable `what_changed` / `why_it_matters` text.
|
|
23
|
+
Partial-match case (1-2 executable) still uses the generic
|
|
24
|
+
fallback to avoid mixing real moves with architecture-first seeds.
|
|
25
|
+
|
|
26
|
+
- **#11 Experiment ranking tie-break coarseness.**
|
|
27
|
+
`ExperimentSet.ranked_branches()` was a single-key sort by score,
|
|
28
|
+
producing unstable rankings at score ties. Live-verified in v1.18.0
|
|
29
|
+
Test 8 — 3-branch experiment with `add_space` + `add_warmth` +
|
|
30
|
+
`widen_stereo` all scored 0.6 with no clear winner. Fix: composite
|
|
31
|
+
sort key via new `_branch_rank_key()` helper, in priority order:
|
|
32
|
+
(1) `-score` (primary, higher wins), (2) `-novelty_rank` (higher
|
|
33
|
+
novelty wins score ties — creative asks reward variation),
|
|
34
|
+
(3) `risk_rank` (lower risk wins secondary ties — safety default),
|
|
35
|
+
(4) `step_count` (simpler plans win tertiary ties),
|
|
36
|
+
(5) `branch_id` (deterministic final tiebreak for reproducibility).
|
|
37
|
+
|
|
38
|
+
- **Concept packet catalog closure.** 13 new genre YAMLs
|
|
39
|
+
(drone, downtempo, lo_fi, boom_bap, footwork, techno,
|
|
40
|
+
detroit_techno, synthwave, deep_house, disco, soul, dub, hyperpop)
|
|
41
|
+
+ 15 too-generic/narrow refs removed from 12 artist packets
|
|
42
|
+
(electronic ×5, electronica, bass_music, cinematic, acid_techno,
|
|
43
|
+
french_house, nu_disco, soulful_house, vaporwave, juke, jungle).
|
|
44
|
+
The xfailing `test_all_artist_genre_refs_resolve_strictly` test
|
|
45
|
+
is now a required green pass. The concept surface has full graph
|
|
46
|
+
closure — every artist→genre cross-reference resolves to an actual
|
|
47
|
+
genre YAML's `id` field.
|
|
48
|
+
|
|
49
|
+
### Tests added / changed
|
|
50
|
+
|
|
51
|
+
- `test_wonder_cold_start_has_distinct_variants` (new — guards
|
|
52
|
+
against regression to the 3-identical-generics degradation)
|
|
53
|
+
- `test_experiment_tie_break_prefers_higher_novelty` (new — unexpected
|
|
54
|
+
> strong > safe at equal scores)
|
|
55
|
+
- `test_experiment_tie_break_is_deterministic` (new — ranking stable
|
|
56
|
+
across input order)
|
|
57
|
+
- `test_all_artist_genre_refs_resolve_strictly` (was xfailing, now
|
|
58
|
+
passing — xfail marker removed)
|
|
59
|
+
- `test_concept_packets_count` (floor updated 14 → 27 genres)
|
|
60
|
+
|
|
61
|
+
### Still open for v1.18.3 / v1.19
|
|
62
|
+
|
|
63
|
+
5 items remain from the original v1.18.0 Known Issues list:
|
|
64
|
+
|
|
65
|
+
- **#7 Packet `avoid` list runtime enforcement** (still advisory —
|
|
66
|
+
pre-flight check against tool args needed)
|
|
67
|
+
- **#8 `locked_dimensions` runtime enforcement** (same pattern as #7)
|
|
68
|
+
- **Experiment state continuity between branches** (before-snapshot
|
|
69
|
+
drift)
|
|
70
|
+
- **Hybrid-packet compilation algorithm** (union/intersection logic
|
|
71
|
+
for "Basic Channel meets Dilla")
|
|
72
|
+
- **Full architectural fix for #3** (route director Phase 6 through
|
|
73
|
+
semantic_move commits — big redesign, v1.19 scope)
|
|
74
|
+
|
|
75
|
+
These all need new infrastructure or architectural decisions
|
|
76
|
+
unsuitable for a patch release.
|
|
77
|
+
|
|
78
|
+
## 1.18.1 — Director HIGH-severity patches (April 23 2026)
|
|
79
|
+
|
|
80
|
+
Patch release addressing 4 of the 12 known issues documented in v1.18.0.
|
|
81
|
+
All three HIGH-severity bugs are fixed, plus 6 medium-severity items
|
|
82
|
+
(data cleanups and doc clarifications). All fixes landed with
|
|
83
|
+
regression-guard tests. 2779 project tests pass + 1 xfail (pre-existing).
|
|
84
|
+
|
|
85
|
+
### High-severity fixes
|
|
86
|
+
|
|
87
|
+
- **#1 `create_experiment` auto-proposal returned single-char move_ids.**
|
|
88
|
+
Python unpacking bug at `mcp_server/experiment/tools.py:267` —
|
|
89
|
+
`[m[0] for m, _ in scored]` indexed the first character of each
|
|
90
|
+
move_id string. Fix: `[move_id for move_id, _ in scored[:limit]]`.
|
|
91
|
+
Pre-fix, calling the director's Flow B with no explicit seeds/move_ids
|
|
92
|
+
would always fail at run_experiment with `"Move t not found"`.
|
|
93
|
+
|
|
94
|
+
- **#2 `propose_composer_branches` ignored concept-packet arrangement
|
|
95
|
+
idioms.** Dub-techno prompts (referencing Basic Channel, Gas, etc.)
|
|
96
|
+
were collapsed to the generic techno template (Intro→Build→Drop→
|
|
97
|
+
Breakdown→Drop 2→Outro with 6 standard layers). Fix: "dub techno"
|
|
98
|
+
is now its own canonical genre in `GENRE_DEFAULTS` and
|
|
99
|
+
`SECTION_TEMPLATES`, with a continuous-evolution scaffold
|
|
100
|
+
(Dawn→Pulse→Chord→Depth→Withdraw→Return, 3-5 layers, energy 0.4).
|
|
101
|
+
Removed the `dub techno → techno` alias that was causing the
|
|
102
|
+
collapse; added `dub-techno` (hyphenated) as an alias to the new
|
|
103
|
+
canonical.
|
|
104
|
+
|
|
105
|
+
- **#3 Director raw-tool-call path bypassed the action ledger.**
|
|
106
|
+
Min-effective doc fix: Phase 6 of `livepilot-creative-director`
|
|
107
|
+
now mandates `add_session_memory(category="move_executed", ...)`
|
|
108
|
+
after raw-tool execution batches, so anti-repetition detection on
|
|
109
|
+
subsequent creative turns isn't blind. Added a state-inference
|
|
110
|
+
fallback table in `anti-repetition-rules.md` for when the ledger
|
|
111
|
+
is still empty (infer recent family from loaded devices, non-default
|
|
112
|
+
mixer state, clip slot contents). Full architectural fix (route all
|
|
113
|
+
Phase 6 execution through `apply_semantic_move`/`commit_experiment`)
|
|
114
|
+
is deferred to v1.19.
|
|
115
|
+
|
|
116
|
+
### Medium-severity fixes
|
|
117
|
+
|
|
118
|
+
- **#4 Ping Pong Delay ghost packet.** `affordances/devices/ping-pong-delay.yaml`
|
|
119
|
+
previously described a standalone device that doesn't exist in
|
|
120
|
+
Live 12 (`search_browser` returned empty). Rewritten as an explicit
|
|
121
|
+
mode-alias for Echo with `Channel Mode = 1`. `echo.yaml` now prominently
|
|
122
|
+
documents the Channel Mode enum (0=Stereo, 1=Ping Pong, 2=Mid/Side).
|
|
123
|
+
|
|
124
|
+
- **#5 Auto Filter affordance used legacy 20-135 Hz ranges.** Modern
|
|
125
|
+
`AutoFilter2` class (Live 12 default) uses 0-1 normalized for
|
|
126
|
+
Frequency, Resonance, and LFO Amount. Live-verified mapping (raw
|
|
127
|
+
0.45 → display 448 Hz) is now documented in the YAML. Legacy/modern
|
|
128
|
+
distinction clarified in the notes.
|
|
129
|
+
|
|
130
|
+
- **#9 `propose_composer_branches` silent count degradation.** Explicit
|
|
131
|
+
`count=3` at `freshness<0.7` was silently returning 2 seeds because
|
|
132
|
+
`layer_contrast` was gated behind `freshness>=0.7`. Fix: explicit
|
|
133
|
+
`count>=3` now raises freshness internally to 0.7 to admit all
|
|
134
|
+
strategies. Default `count=2` (no override) still respects the
|
|
135
|
+
freshness gate — preserves the "freshness shapes default strategy
|
|
136
|
+
count" contract and the existing tests that assert it.
|
|
137
|
+
|
|
138
|
+
- **#12 Low-novelty-budget escape hatch for 3-plan diversity rule.**
|
|
139
|
+
`move-family-diversity-rule.md` gained a dedicated section: when
|
|
140
|
+
`novelty_budget < 0.35` (user prompts like "keep the vibe, just
|
|
141
|
+
cleaner"), 1-2 family plans is acceptable and honest. Prevents the
|
|
142
|
+
rule from fighting cleanup requests.
|
|
143
|
+
|
|
144
|
+
### Bonus fixes
|
|
145
|
+
|
|
146
|
+
- **`batch_set_parameters` schema gotcha documented.** Core SKILL.md
|
|
147
|
+
Rule 15 now shows the correct dict-of-dicts shape:
|
|
148
|
+
`{"ParamName": {"value": v}}`. Live verification surfaced this during
|
|
149
|
+
v1.18.0 pressure testing.
|
|
150
|
+
|
|
151
|
+
- **Convolution Reverb phantom `ir_length` already fixed in v1.18.0
|
|
152
|
+
commit 9** — regression test now guards against reintroduction.
|
|
153
|
+
|
|
154
|
+
### Tests added
|
|
155
|
+
|
|
156
|
+
- `test_create_experiment_auto_proposal_no_m0_bug` (source-pattern
|
|
157
|
+
regression guard)
|
|
158
|
+
- `test_create_experiment_auto_proposal_functional` (mirror-logic
|
|
159
|
+
integration check)
|
|
160
|
+
- `test_composer_dub_techno_prompt_avoids_drop_scaffold` (no Drop/Drop 2
|
|
161
|
+
on Basic Channel prompts)
|
|
162
|
+
- `test_propose_composer_branches_honors_explicit_count` (count=3
|
|
163
|
+
returns 3 regardless of freshness)
|
|
164
|
+
- `test_medium_freshness_count_3_unlocks_all_strategies` (renamed from
|
|
165
|
+
test encoding the pre-fix bug as desired behavior)
|
|
166
|
+
- `test_medium_freshness_default_count_gives_two` (preserves
|
|
167
|
+
default-count-respects-freshness contract)
|
|
168
|
+
- `test_ping_pong_delay_is_documented_as_echo_mode`
|
|
169
|
+
- `test_auto_filter_ranges_are_normalized_for_modern_class`
|
|
170
|
+
- `test_low_novelty_escape_hatch_documented`
|
|
171
|
+
- `test_batch_set_parameters_schema_documented`
|
|
172
|
+
- `test_director_phase6_records_ledger_marker`
|
|
173
|
+
- `test_anti_repetition_has_state_inference_fallback`
|
|
174
|
+
|
|
175
|
+
### Still open for v1.18.2 / v1.19
|
|
176
|
+
|
|
177
|
+
8 items remain from the v1.18.0 Known Issues list:
|
|
178
|
+
|
|
179
|
+
- **#7 Packet `avoid` list runtime enforcement** (currently advisory —
|
|
180
|
+
pre-flight check against tool args needed)
|
|
181
|
+
- **#8 `locked_dimensions` runtime enforcement** (same pattern as #7)
|
|
182
|
+
- **#10 Wonder Mode zero-variant degradation on empty session context**
|
|
183
|
+
- **#11 Evaluation tie-break coarseness** (3-way ties at score 0.6)
|
|
184
|
+
- **Experiment state continuity between branches** (before-snapshot
|
|
185
|
+
drift)
|
|
186
|
+
- **Hybrid-packet compilation algorithm** (union/intersection logic
|
|
187
|
+
for "Basic Channel meets Dilla")
|
|
188
|
+
- **~20 missing genre YAMLs** (downtempo, boom_bap, lo_fi, synthwave,
|
|
189
|
+
techno, etc.) — xfail test tracks this
|
|
190
|
+
- **Full architectural fix for #3** (route director Phase 6 through
|
|
191
|
+
semantic_move commits, replacing the doc-level fix shipped here)
|
|
192
|
+
|
|
193
|
+
These are each scoped for focused follow-up sessions — they need new
|
|
194
|
+
infrastructure or architectural decisions not suitable for a patch
|
|
195
|
+
release.
|
|
196
|
+
|
|
3
197
|
## 1.18.0 — Creative Director + concept packets + device affordances (April 23 2026)
|
|
4
198
|
|
|
5
199
|
A structural feature release. Addresses the "agent doesn't variate
|
package/mcp_server/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""LivePilot MCP Server — bridges MCP protocol to Ableton Live."""
|
|
2
|
-
__version__ = "1.18.
|
|
2
|
+
__version__ = "1.18.2"
|
|
@@ -124,7 +124,18 @@ def propose_composer_branches(
|
|
|
124
124
|
|
|
125
125
|
intent = parse_prompt(request_text)
|
|
126
126
|
|
|
127
|
-
#
|
|
127
|
+
# v1.18.1 #9 fix: explicit count=3 overrides the freshness default.
|
|
128
|
+
# Pre-fix, count=3 at freshness=0.6 silently returned 2 (canonical +
|
|
129
|
+
# energy_shift only; layer_contrast was gated behind freshness>=0.7).
|
|
130
|
+
# Now: caller asking for all 3 strategies gets them by internally
|
|
131
|
+
# raising freshness to 0.7. Count=2 (the default) does NOT raise
|
|
132
|
+
# freshness — the freshness gate still caps at 1 on low-freshness
|
|
133
|
+
# runs, which is the documented "freshness cautiously shapes default
|
|
134
|
+
# strategy count" contract.
|
|
135
|
+
if count >= 3:
|
|
136
|
+
freshness = max(freshness, 0.7)
|
|
137
|
+
|
|
138
|
+
# Gate high-novelty strategies on (possibly-raised) freshness.
|
|
128
139
|
if freshness < 0.4:
|
|
129
140
|
strategies = [_STRATEGIES[0]] # canonical only
|
|
130
141
|
elif freshness < 0.7:
|
|
@@ -172,6 +172,19 @@ SECTION_TEMPLATES: dict[str, list[dict]] = {
|
|
|
172
172
|
{"name": "Drop 2", "bars": 16, "layers": ["drums", "bass", "lead", "percussion", "vocal", "texture"]},
|
|
173
173
|
{"name": "Outro", "bars": 8, "layers": ["drums:-6dB", "texture", "pad"]},
|
|
174
174
|
],
|
|
175
|
+
# Dub techno — continuous-evolution aesthetic. No Drop structure, no
|
|
176
|
+
# Build/Break cycle. Section names reflect dub-techno arrangement idioms:
|
|
177
|
+
# slow reveal, subtraction before addition, return deeper not louder.
|
|
178
|
+
# Source: concepts/artists/basic-channel.yaml arrangement_idioms +
|
|
179
|
+
# live-verification finding from v1.18.0 CHANGELOG #2. v1.18.1 #2 fix.
|
|
180
|
+
"dub techno": [
|
|
181
|
+
{"name": "Dawn", "bars": 16, "layers": ["texture:-12dB"]},
|
|
182
|
+
{"name": "Pulse", "bars": 16, "layers": ["drums:-6dB", "texture"]},
|
|
183
|
+
{"name": "Chord", "bars": 32, "layers": ["drums", "bass", "pad:-6dB", "texture"]},
|
|
184
|
+
{"name": "Depth", "bars": 32, "layers": ["drums", "bass", "pad", "texture"]},
|
|
185
|
+
{"name": "Withdraw", "bars": 16, "layers": ["pad:-6dB", "texture:-6dB"]},
|
|
186
|
+
{"name": "Return", "bars": 16, "layers": ["texture:-12dB"]},
|
|
187
|
+
],
|
|
175
188
|
"house": [
|
|
176
189
|
{"name": "Intro", "bars": 8, "layers": ["drums:-6dB", "pad"]},
|
|
177
190
|
{"name": "Verse", "bars": 16, "layers": ["drums", "bass", "pad"]},
|
|
@@ -51,6 +51,13 @@ GENRE_DEFAULTS: dict[str, dict] = {
|
|
|
51
51
|
"tempo": 128, "keys": ["Am", "Cm"], "energy": 0.7,
|
|
52
52
|
"layers_min": 5, "layers_max": 7,
|
|
53
53
|
},
|
|
54
|
+
# Dub techno is its own canonical genre — continuous-evolution aesthetic
|
|
55
|
+
# referencing Basic Channel / Rhythm & Sound / Gas. Previously aliased to
|
|
56
|
+
# "techno" which defaulted to Drop-based scaffolds (v1.18.1 #2 fix).
|
|
57
|
+
"dub techno": {
|
|
58
|
+
"tempo": 125, "keys": ["Am", "Em"], "energy": 0.4,
|
|
59
|
+
"layers_min": 3, "layers_max": 5,
|
|
60
|
+
},
|
|
54
61
|
"house": {
|
|
55
62
|
"tempo": 124, "keys": ["Cm", "Fm"], "energy": 0.6,
|
|
56
63
|
"layers_min": 5, "layers_max": 6,
|
|
@@ -93,7 +100,11 @@ _GENRE_ALIASES: dict[str, str] = {
|
|
|
93
100
|
"industrial techno": "techno",
|
|
94
101
|
"minimal techno": "techno",
|
|
95
102
|
"detroit techno": "techno",
|
|
96
|
-
"dub techno"
|
|
103
|
+
# NOTE: "dub techno" is intentionally NOT aliased to "techno" —
|
|
104
|
+
# it's now its own canonical genre with dub-appropriate defaults
|
|
105
|
+
# (see GENRE_DEFAULTS above) and a non-Drop section template (see
|
|
106
|
+
# layer_planner.SECTION_TEMPLATES). v1.18.1 #2 fix.
|
|
107
|
+
"dub-techno": "dub techno",
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
|
|
@@ -194,6 +194,52 @@ class ExperimentBranch:
|
|
|
194
194
|
return d
|
|
195
195
|
|
|
196
196
|
|
|
197
|
+
# v1.18.2 #11: composite tie-break ranking for experiment branches.
|
|
198
|
+
# Maps novelty_label / risk_label strings to integer ranks.
|
|
199
|
+
_NOVELTY_RANK: dict[str, int] = {
|
|
200
|
+
"safe": 0,
|
|
201
|
+
"medium": 1, # rarely used, but accept it for robustness
|
|
202
|
+
"strong": 1,
|
|
203
|
+
"unexpected": 2,
|
|
204
|
+
"bold": 2, # alias in some producer outputs
|
|
205
|
+
}
|
|
206
|
+
_RISK_RANK: dict[str, int] = {
|
|
207
|
+
"low": 0,
|
|
208
|
+
"medium": 1,
|
|
209
|
+
"high": 2,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _branch_rank_key(branch: "ExperimentBranch") -> tuple:
|
|
214
|
+
"""Composite sort key for ExperimentSet.ranked_branches().
|
|
215
|
+
|
|
216
|
+
Returns a tuple (-score, -novelty, risk, step_count, branch_id) such
|
|
217
|
+
that Python's default ascending sort produces the desired ranking:
|
|
218
|
+
higher scores first, then higher novelty at score ties, then lower
|
|
219
|
+
risk under equal novelty, then simpler plans, then branch_id as a
|
|
220
|
+
deterministic final tiebreak.
|
|
221
|
+
"""
|
|
222
|
+
score = float(getattr(branch, "score", 0.0) or 0.0)
|
|
223
|
+
seed = getattr(branch, "seed", None)
|
|
224
|
+
|
|
225
|
+
if seed is not None:
|
|
226
|
+
novelty_label = (seed.novelty_label or "").lower()
|
|
227
|
+
risk_label = (seed.risk_label or "").lower()
|
|
228
|
+
else:
|
|
229
|
+
novelty_label = ""
|
|
230
|
+
risk_label = ""
|
|
231
|
+
|
|
232
|
+
novelty_rank = _NOVELTY_RANK.get(novelty_label, 1) # middle if unknown
|
|
233
|
+
risk_rank = _RISK_RANK.get(risk_label, 1)
|
|
234
|
+
|
|
235
|
+
plan = getattr(branch, "compiled_plan", None) or {}
|
|
236
|
+
step_count = int(plan.get("step_count", 0) or 0)
|
|
237
|
+
|
|
238
|
+
branch_id = getattr(branch, "branch_id", "") or ""
|
|
239
|
+
|
|
240
|
+
return (-score, -novelty_rank, risk_rank, step_count, branch_id)
|
|
241
|
+
|
|
242
|
+
|
|
197
243
|
@dataclass
|
|
198
244
|
class ExperimentSet:
|
|
199
245
|
"""A collection of branches being compared for one request."""
|
|
@@ -215,9 +261,33 @@ class ExperimentSet:
|
|
|
215
261
|
return None
|
|
216
262
|
|
|
217
263
|
def ranked_branches(self) -> list[ExperimentBranch]:
|
|
218
|
-
"""Return branches sorted by
|
|
264
|
+
"""Return evaluated branches sorted by composite rank.
|
|
265
|
+
|
|
266
|
+
v1.18.2 #11 fix: pre-fix this was a single-key sort by score,
|
|
267
|
+
which produced unstable rankings at score ties (live-verified in
|
|
268
|
+
v1.18.0 Test 8 — three branches at 0.6 with no winner).
|
|
269
|
+
|
|
270
|
+
Sort keys, in priority order:
|
|
271
|
+
1. -score — higher score wins
|
|
272
|
+
2. -novelty_rank — higher novelty wins at score ties
|
|
273
|
+
(creative asks reward variation)
|
|
274
|
+
3. risk_rank — lower risk wins secondary ties
|
|
275
|
+
(safety default under equal novelty)
|
|
276
|
+
4. step_count — simpler plans win tertiary ties
|
|
277
|
+
5. branch_id — deterministic final tiebreak
|
|
278
|
+
(stable ranking across equal branches)
|
|
279
|
+
|
|
280
|
+
Novelty labels rank: "safe"=0, "strong"=1, "unexpected"=2, "bold"=2.
|
|
281
|
+
Risk labels rank: "low"=0, "medium"=1, "high"=2.
|
|
282
|
+
Unknown labels default to the middle (1).
|
|
283
|
+
"""
|
|
219
284
|
evaluated = [b for b in self.branches if b.status == "evaluated"]
|
|
220
|
-
return sorted(evaluated, key=
|
|
285
|
+
return sorted(evaluated, key=_branch_rank_key)
|
|
286
|
+
|
|
287
|
+
# expose the key function for testing + custom rankers
|
|
288
|
+
def rank_key_for(self, branch: "ExperimentBranch") -> tuple:
|
|
289
|
+
"""Return the composite rank key for a branch (for tie-break debugging)."""
|
|
290
|
+
return _branch_rank_key(branch)
|
|
221
291
|
|
|
222
292
|
def to_dict(self) -> dict:
|
|
223
293
|
return {
|
|
@@ -244,18 +244,22 @@ def create_experiment(
|
|
|
244
244
|
|
|
245
245
|
# ── Mode 2/3: legacy move_ids path ──────────────────────────────────
|
|
246
246
|
if not move_ids:
|
|
247
|
-
# Auto-propose moves from the registry
|
|
247
|
+
# Auto-propose moves from the registry by keyword overlap.
|
|
248
|
+
# v1.18.1 #1 fix: the previous selector indexed the first character
|
|
249
|
+
# of each move_id (a Python unpacking trap — the variable was
|
|
250
|
+
# already the full string, the [0] subscript sliced into it).
|
|
251
|
+
# Result pre-fix: single-char move_ids like 't', 'w', 'm' that
|
|
252
|
+
# failed at run_experiment with "Move t not found". Now the whole
|
|
253
|
+
# move_id string is kept.
|
|
248
254
|
from ..semantic_moves import registry
|
|
249
|
-
from ..semantic_moves.tools import propose_next_best_move
|
|
250
|
-
# Use the propose function's logic directly
|
|
251
255
|
all_moves = list(registry._REGISTRY.values())
|
|
252
256
|
request_lower = request_text.lower()
|
|
253
|
-
|
|
257
|
+
request_words = set(request_lower.split())
|
|
258
|
+
scored: list[tuple[str, float]] = []
|
|
254
259
|
for move in all_moves:
|
|
255
260
|
score = 0.0
|
|
256
261
|
move_words = set(move.move_id.replace("_", " ").split())
|
|
257
262
|
intent_words = set(move.intent.lower().split())
|
|
258
|
-
request_words = set(request_lower.split())
|
|
259
263
|
overlap = request_words & (move_words | intent_words)
|
|
260
264
|
score += len(overlap) * 0.3
|
|
261
265
|
for dim in move.targets:
|
|
@@ -264,7 +268,7 @@ def create_experiment(
|
|
|
264
268
|
if score > 0.1:
|
|
265
269
|
scored.append((move.move_id, score))
|
|
266
270
|
scored.sort(key=lambda x: -x[1])
|
|
267
|
-
move_ids = [
|
|
271
|
+
move_ids = [move_id for move_id, _ in scored[:limit]] if scored else []
|
|
268
272
|
|
|
269
273
|
if not move_ids:
|
|
270
274
|
return {"error": "No matching semantic moves found for this request"}
|
|
@@ -321,6 +321,82 @@ def build_analytical_variant(label: str, request_text: str, novelty_level: float
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
|
|
324
|
+
# v1.18.2 #10 fix: distinct cold-start variant seeds for empty/sparse
|
|
325
|
+
# sessions. Used when no semantic moves match the request. Each seed has
|
|
326
|
+
# a specific `what_changed` + `why_it_matters` covering a different
|
|
327
|
+
# starting-point family (device_creation × rhythm + device_creation ×
|
|
328
|
+
# harmony + mix-architecture-first). Replaces the 3-identical-generics
|
|
329
|
+
# degradation that v1.18.0 Test 4 surfaced.
|
|
330
|
+
_COLD_START_SEEDS: list[dict] = [
|
|
331
|
+
{
|
|
332
|
+
"label": "safe",
|
|
333
|
+
"family": "device_creation",
|
|
334
|
+
"intent": "Begin with a rhythmic foundation",
|
|
335
|
+
"what_changed": "Load a drum kit (Drum Rack or Core Kit) on a fresh MIDI track, program a 4-bar kick-and-hat pattern",
|
|
336
|
+
"what_preserved": "blank slate — first move sets the tempo and grid foundation",
|
|
337
|
+
"why_it_matters": "Every track needs a rhythmic anchor before timbral or structural work. Safe starting point — drums-first is the most common composition entry.",
|
|
338
|
+
"novelty_level": 0.3,
|
|
339
|
+
"identity_effect": "establishes",
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"label": "strong",
|
|
343
|
+
"family": "sound_design",
|
|
344
|
+
"intent": "Begin with a harmonic source",
|
|
345
|
+
"what_changed": "Load Drift or Meld on a MIDI track with a chord-stab patch (short attack, moderate release, slight detune), sketch a 2-bar chord pattern",
|
|
346
|
+
"what_preserved": "tempo and key are still open to discovery — lets the harmony suggest the rhythm",
|
|
347
|
+
"why_it_matters": "A harmonic source opens a different emotional palette than drums-first. Chord-first composition (Isolée / Luomo style) is less common but produces distinctive results.",
|
|
348
|
+
"novelty_level": 0.55,
|
|
349
|
+
"identity_effect": "establishes",
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"label": "unexpected",
|
|
353
|
+
"family": "mix",
|
|
354
|
+
"intent": "Begin with the space, not the source",
|
|
355
|
+
"what_changed": "Configure return tracks BEFORE any instrument work — set up Return A with Convolution Reverb (cathedral IR) and Return B with Echo in ping-pong mode",
|
|
356
|
+
"what_preserved": "the blank slate IS the canvas; the sends are the frame you'll paint into",
|
|
357
|
+
"why_it_matters": "Dub techno and ambient producers (Basic Channel, Gas, Henke) build sound AROUND pre-configured sends. Unusual but genre-appropriate starting point.",
|
|
358
|
+
"novelty_level": 0.85,
|
|
359
|
+
"identity_effect": "establishes",
|
|
360
|
+
},
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def build_cold_start_variant(seed: dict, request_text: str, variant_id: str = "") -> dict:
|
|
365
|
+
"""Build a cold-start variant seed for an empty/sparse session.
|
|
366
|
+
|
|
367
|
+
Used when no semantic moves match the request. Returns a variant with
|
|
368
|
+
distinct, actionable `what_changed` / `why_it_matters` text — NOT the
|
|
369
|
+
generic 'No matching moves found' fallback. Each seed covers a
|
|
370
|
+
different starting-point family; together they give the user three
|
|
371
|
+
genuinely distinct first-moves to choose from.
|
|
372
|
+
|
|
373
|
+
See `_COLD_START_SEEDS` for the seed set. The variant is
|
|
374
|
+
`analytical_only=True` (no compiled_plan) — turning these into
|
|
375
|
+
one-click executable plans is a v1.19 enhancement.
|
|
376
|
+
"""
|
|
377
|
+
return {
|
|
378
|
+
"variant_id": variant_id,
|
|
379
|
+
"label": seed["label"],
|
|
380
|
+
"move_id": "",
|
|
381
|
+
"family": seed["family"],
|
|
382
|
+
"intent": seed["intent"],
|
|
383
|
+
"what_changed": seed["what_changed"],
|
|
384
|
+
"what_preserved": seed["what_preserved"],
|
|
385
|
+
"why_it_matters": seed["why_it_matters"],
|
|
386
|
+
"identity_effect": seed["identity_effect"],
|
|
387
|
+
"novelty_level": seed["novelty_level"],
|
|
388
|
+
"taste_fit": 0.5,
|
|
389
|
+
"targets_snapshot": {},
|
|
390
|
+
"compiled_plan": None,
|
|
391
|
+
"score": 0.0,
|
|
392
|
+
"rank": 0,
|
|
393
|
+
"score_breakdown": {},
|
|
394
|
+
"analytical_only": True,
|
|
395
|
+
"distinctness_reason": f"Cold-start seed ({seed['family']}) — empty session, no moves matched",
|
|
396
|
+
"cold_start": True,
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
324
400
|
# ── Taste fit scoring ────────────────────────────────────────────
|
|
325
401
|
|
|
326
402
|
|
|
@@ -577,16 +653,37 @@ def generate_wonder_variants(
|
|
|
577
653
|
|
|
578
654
|
executable_count = len(variants)
|
|
579
655
|
|
|
580
|
-
#
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
656
|
+
# v1.18.2 #10 fix: when NO executable moves matched, seed from the
|
|
657
|
+
# cold-start distinct-starting-points set instead of padding with
|
|
658
|
+
# identical generic analytical variants. Pre-fix, cold-start on an
|
|
659
|
+
# empty session returned 3 variants all with the same generic
|
|
660
|
+
# "No matching moves found" text — unhelpful to the user.
|
|
661
|
+
#
|
|
662
|
+
# The partial-match case (1 or 2 executable variants) still pads with
|
|
663
|
+
# the generic analytical fallback because we don't want to mix real
|
|
664
|
+
# move-based variants with architecture-first seeds — that would
|
|
665
|
+
# confuse the presentation.
|
|
666
|
+
if executable_count == 0:
|
|
667
|
+
while len(variants) < 3:
|
|
668
|
+
idx = len(variants)
|
|
669
|
+
seed = _COLD_START_SEEDS[idx]
|
|
670
|
+
v = build_cold_start_variant(
|
|
671
|
+
seed=seed,
|
|
672
|
+
request_text=request_text,
|
|
673
|
+
variant_id=f"{set_prefix}_{seed['label']}",
|
|
674
|
+
)
|
|
675
|
+
variants.append(v)
|
|
676
|
+
else:
|
|
677
|
+
# Partial-match: pad to 3 with generic analytical variants
|
|
678
|
+
while len(variants) < 3:
|
|
679
|
+
idx = len(variants)
|
|
680
|
+
v = build_analytical_variant(
|
|
681
|
+
label=labels[idx],
|
|
682
|
+
request_text=request_text,
|
|
683
|
+
novelty_level=_NOVELTY_LEVELS.get(labels[idx], 0.5),
|
|
684
|
+
variant_id=f"{set_prefix}_{labels[idx]}",
|
|
685
|
+
)
|
|
686
|
+
variants.append(v)
|
|
590
687
|
|
|
591
688
|
novelty_band = 0.5
|
|
592
689
|
taste_evidence = 0
|
|
@@ -603,7 +700,13 @@ def generate_wonder_variants(
|
|
|
603
700
|
|
|
604
701
|
degraded_reason = ""
|
|
605
702
|
if executable_count == 0:
|
|
606
|
-
|
|
703
|
+
# v1.18.2 #10: cold-start path — distinct starting-point seeds
|
|
704
|
+
# rather than identical-generic padding.
|
|
705
|
+
degraded_reason = (
|
|
706
|
+
"No matching executable moves — cold-start variants seeded "
|
|
707
|
+
"from distinct starting-point families (device_creation × 2 "
|
|
708
|
+
"+ mix-architecture-first)"
|
|
709
|
+
)
|
|
607
710
|
elif executable_count == 1:
|
|
608
711
|
degraded_reason = "Only 1 distinct executable move found"
|
|
609
712
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livepilot",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.2",
|
|
4
4
|
"mcpName": "io.github.dreamrec/livepilot",
|
|
5
5
|
"description": "Agentic production system for Ableton Live 12 — 427 tools, 52 domains. Device atlas (1305 devices), sample engine (Splice + browser + filesystem), auto-composition, spectral perception, technique memory, creative intelligence (12 engines)",
|
|
6
6
|
"author": "Pilot Studio",
|
|
@@ -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.18.
|
|
8
|
+
__version__ = "1.18.2"
|
|
9
9
|
|
|
10
10
|
from _Framework.ControlSurface import ControlSurface
|
|
11
11
|
from . import router
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/dreamrec/LivePilot",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.18.
|
|
9
|
+
"version": "1.18.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "livepilot",
|
|
14
|
-
"version": "1.18.
|
|
14
|
+
"version": "1.18.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|