livepilot 1.12.2 → 1.14.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 +219 -0
- package/README.md +7 -7
- 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/branches/__init__.py +34 -0
- package/mcp_server/branches/types.py +286 -0
- package/mcp_server/composer/__init__.py +10 -1
- package/mcp_server/composer/branch_producer.py +349 -0
- package/mcp_server/composer/tools.py +58 -1
- package/mcp_server/evaluation/policy.py +227 -2
- package/mcp_server/experiment/engine.py +47 -11
- package/mcp_server/experiment/models.py +112 -8
- package/mcp_server/experiment/tools.py +502 -38
- package/mcp_server/memory/taste_graph.py +84 -11
- package/mcp_server/persistence/taste_store.py +21 -5
- package/mcp_server/runtime/session_kernel.py +46 -0
- package/mcp_server/runtime/tools.py +29 -3
- package/mcp_server/server.py +1 -0
- package/mcp_server/synthesis_brain/__init__.py +53 -0
- package/mcp_server/synthesis_brain/adapters/__init__.py +34 -0
- package/mcp_server/synthesis_brain/adapters/analog.py +273 -0
- package/mcp_server/synthesis_brain/adapters/base.py +86 -0
- package/mcp_server/synthesis_brain/adapters/drift.py +271 -0
- package/mcp_server/synthesis_brain/adapters/meld.py +261 -0
- package/mcp_server/synthesis_brain/adapters/operator.py +292 -0
- package/mcp_server/synthesis_brain/adapters/wavetable.py +364 -0
- package/mcp_server/synthesis_brain/engine.py +91 -0
- package/mcp_server/synthesis_brain/models.py +121 -0
- package/mcp_server/synthesis_brain/timbre.py +194 -0
- package/mcp_server/synthesis_brain/tools.py +231 -0
- package/mcp_server/tools/_conductor.py +144 -0
- package/mcp_server/wonder_mode/engine.py +324 -0
- package/mcp_server/wonder_mode/tools.py +153 -1
- package/package.json +2 -2
- package/remote_script/LivePilot/__init__.py +1 -1
- package/server.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,224 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.14.0 — Branch-native v2: producer context, synth intelligence, render verify (April 20 2026)
|
|
4
|
+
|
|
5
|
+
Five-PR follow-up to v1.13.0 that closes the loops the first pass left
|
|
6
|
+
open. Producer context flows through the branch lifecycle via a versioned
|
|
7
|
+
`producer_payload`; synthesis adapters decode algorithm topology instead
|
|
8
|
+
of always targeting the same operator; composer winners commit the full
|
|
9
|
+
resolved plan instead of the audition scaffold; render-verify captures
|
|
10
|
+
audio before/after each branch and feeds spectral movement into the
|
|
11
|
+
hard-rule classifier; and four dedicated MCP tools expose the new
|
|
12
|
+
producers to the LLM.
|
|
13
|
+
|
|
14
|
+
**Tool count**: 398 → **402** (added `analyze_synth_patch`,
|
|
15
|
+
`propose_synth_branches`, `extract_timbre_fingerprint`,
|
|
16
|
+
`propose_composer_branches`). **Domain count**: unchanged at 52.
|
|
17
|
+
**Tests**: 2409 → **2467 passing** (+58 new), 0 regressions.
|
|
18
|
+
|
|
19
|
+
### New substrate
|
|
20
|
+
|
|
21
|
+
- **`producer_payload: dict` on `BranchSeed`** (PR1) — versioned
|
|
22
|
+
opaque dict producers populate with regeneration / provenance /
|
|
23
|
+
winner-escalation context. Always carries `schema_version` (default 1)
|
|
24
|
+
so older payloads don't break newer readers. Lives on the seed, not
|
|
25
|
+
`compiled_plan`, so analytical-only branches can carry context too.
|
|
26
|
+
Canonical shapes documented per producer (synthesis / composer /
|
|
27
|
+
semantic_move / freeform / technique).
|
|
28
|
+
|
|
29
|
+
- **`BranchSnapshot` gains render-based fields** (PR4) —
|
|
30
|
+
`capture_path`, `loudness`, `spectral_shape`, `fingerprint`. Populated
|
|
31
|
+
only when `run_experiment(render_verify=True)` opts in. Pre-v2
|
|
32
|
+
consumers see no shape change when render-verify is off.
|
|
33
|
+
|
|
34
|
+
### Synthesis adapters get topology awareness (PR2)
|
|
35
|
+
|
|
36
|
+
- **Wavetable**: position→region classification (sub / mid / bright /
|
|
37
|
+
complex) drives shift direction. Target brightness biases the chosen
|
|
38
|
+
target region — not just freshness scaling.
|
|
39
|
+
|
|
40
|
+
- **Operator**: static `_ALGO_TOPOLOGY` table maps all 11 algorithms
|
|
41
|
+
to their carrier/modulator roles. Targeting picks the modulator with
|
|
42
|
+
the highest Level for the ratio shift; additive algorithms (5, 9)
|
|
43
|
+
fall back to the dominant carrier. No more "always Osc B".
|
|
44
|
+
|
|
45
|
+
- **Analog / Drift / Meld**: single fixed proposers become strategy
|
|
46
|
+
registries. Gates honor `role_hint` ("bass" skips `detune_warmth`,
|
|
47
|
+
"pad" skips `filter_pluck`, silent engines skip `engine_mix_shift`)
|
|
48
|
+
and target fingerprint dimensions (`target.brightness` picks
|
|
49
|
+
`filter_sweep_open` vs `filter_sweep_close`).
|
|
50
|
+
|
|
51
|
+
### Composer winner escalation (PR3)
|
|
52
|
+
|
|
53
|
+
- Composer seeds now carry their `CompositionIntent` in
|
|
54
|
+
`producer_payload` at emit time. On `commit_experiment`,
|
|
55
|
+
`escalate_composer_branch` rehydrates the intent and runs the full
|
|
56
|
+
`ComposerEngine.compose()` pipeline — Splice / filesystem / browser
|
|
57
|
+
sample resolution — then swaps the scaffold plan for the resolved one
|
|
58
|
+
BEFORE `commit_branch_async` runs it through the async router. The
|
|
59
|
+
scaffold is preserved on `branch.evaluation` for audit.
|
|
60
|
+
|
|
61
|
+
- Graceful fallback when compose yields zero executable layers:
|
|
62
|
+
commit runs the scaffold instead of erroring. User gets tracks +
|
|
63
|
+
scenes they can populate manually, with `composer_escalation.error`
|
|
64
|
+
explaining why escalation couldn't complete.
|
|
65
|
+
|
|
66
|
+
- **Latency note**: composer winner commit now takes 10-30s (Splice +
|
|
67
|
+
filesystem resolution) vs ~0.5s pre-v2. Documented; a progress
|
|
68
|
+
callback version is future work.
|
|
69
|
+
|
|
70
|
+
### Render-verify + classifier wiring (PR4)
|
|
71
|
+
|
|
72
|
+
- `run_experiment(render_verify=False, render_duration_seconds=2.0)` —
|
|
73
|
+
opt-in per-branch audio capture → offline loudness + spectrum
|
|
74
|
+
analysis → `TimbralFingerprint` extraction. ~2 * duration seconds +
|
|
75
|
+
~1-2s analysis overhead per branch; default off preserves speed.
|
|
76
|
+
|
|
77
|
+
- New `derive_goal_progress_from_fingerprint(diff, target?)` turns
|
|
78
|
+
TimbralFingerprint diffs into `(goal_progress, measurable_count)`.
|
|
79
|
+
Dimensions below 0.02 epsilon are dropped as noise. With a target:
|
|
80
|
+
sign(target) * diff gives signed progress. Without: magnitude-only
|
|
81
|
+
contribution (branch moved = measurable).
|
|
82
|
+
|
|
83
|
+
- `classify_branch_outcome` accepts `fingerprint_diff` + `timbral_target`
|
|
84
|
+
kwargs. When set and caller didn't supply their own measurable inputs,
|
|
85
|
+
the classifier derives them from the diff. Caller-supplied values
|
|
86
|
+
still take precedence (back-compat). Protection violations still
|
|
87
|
+
trump fingerprint evidence — safety invariant preserved.
|
|
88
|
+
|
|
89
|
+
- `compare_experiments` automatically surfaces `fingerprint_diff` +
|
|
90
|
+
`fingerprint_before` + `fingerprint_after` on each branch's
|
|
91
|
+
`evaluation` dict via the existing pass-through.
|
|
92
|
+
|
|
93
|
+
### Four new MCP tools (PR5, 398 → 402)
|
|
94
|
+
|
|
95
|
+
- **`analyze_synth_patch(track_index, device_index, role_hint="")`** —
|
|
96
|
+
SynthProfile for any supported native synth. Fetches parameter state
|
|
97
|
+
+ display values, hands to the adapter. Opaque fallback for
|
|
98
|
+
non-supported devices.
|
|
99
|
+
|
|
100
|
+
- **`propose_synth_branches(track_index, device_index, target?,
|
|
101
|
+
freshness?, role_hint?)`** — algorithm/topology-aware branch seeds
|
|
102
|
+
with pre-compiled plans. Feeds directly to
|
|
103
|
+
`create_experiment(seeds=..., compiled_plans=...)`.
|
|
104
|
+
|
|
105
|
+
- **`extract_timbre_fingerprint(spectrum?, loudness?, spectral_shape?)`**
|
|
106
|
+
— pure transform from analysis dicts to 9-dimensional
|
|
107
|
+
`TimbralFingerprint`. For callers that already have analysis data.
|
|
108
|
+
|
|
109
|
+
- **`propose_composer_branches(request_text, count=2, freshness=0.65)`**
|
|
110
|
+
— N compositional hypotheses (canonical / energy_shift /
|
|
111
|
+
layer_contrast) with producer_payload-captured intents for
|
|
112
|
+
winner-commit escalation.
|
|
113
|
+
|
|
114
|
+
### Migration notes for callers
|
|
115
|
+
|
|
116
|
+
- All additions are optional-param / new-function shaped. Pre-v2
|
|
117
|
+
callers see no behavior change unless they opt in.
|
|
118
|
+
- Pre-v2 serialized branches deserialize fine: `producer_payload`
|
|
119
|
+
defaults to `{"schema_version": 1}` when absent.
|
|
120
|
+
- `create_experiment(move_ids=...)` identical behavior.
|
|
121
|
+
- `enter_wonder_mode` response shape stable; `branch_seeds` /
|
|
122
|
+
`compiled_plans_by_seed_id` still additive.
|
|
123
|
+
- `run_experiment(render_verify=False)` default matches v1.13.0
|
|
124
|
+
behavior exactly.
|
|
125
|
+
|
|
126
|
+
### Known limitations
|
|
127
|
+
|
|
128
|
+
See [`docs/manual/branch-native-migration.md`](docs/manual/branch-native-migration.md#known-limitations)
|
|
129
|
+
for the full list. Headlines:
|
|
130
|
+
|
|
131
|
+
- Wavetable region classification is a coarse heuristic on raw
|
|
132
|
+
`Osc 1 Pos`. Future work: render-based per-wavetable mapping.
|
|
133
|
+
- Composer commit latency (10-30s) with no progress callback yet.
|
|
134
|
+
- Render-verify requires the M4L analyzer bridge + LivePilot_Analyzer
|
|
135
|
+
on master; silently degrades to fast-path when either is missing.
|
|
136
|
+
- End-to-end render-verify path (capture_audio + bridge) is hardware-
|
|
137
|
+
dependent and not unit-tested; wiring is covered via classifier +
|
|
138
|
+
fingerprint derivation tests.
|
|
139
|
+
|
|
140
|
+
## 1.13.0 — Branch-native architecture (April 20 2026)
|
|
141
|
+
|
|
142
|
+
Twelve-PR migration from "match request → pick move → compile move" to
|
|
143
|
+
"understand intent → generate branches → compile branches → compare".
|
|
144
|
+
The planning layer opens up — Wonder, Preview Studio, Experiment, and
|
|
145
|
+
the new synthesis_brain all share one BranchSeed + CompiledBranch
|
|
146
|
+
contract. Move-first is still available as a targeted flow; branch-native
|
|
147
|
+
is the canonical exploratory path.
|
|
148
|
+
|
|
149
|
+
**No tool count change** (still 398). **Domain count 51 → 52**
|
|
150
|
+
(added `synthesis_brain`). **+175 new tests** across 9 new files
|
|
151
|
+
(2206 → 2387 passing, 1 skipped, 0 failures).
|
|
152
|
+
|
|
153
|
+
### New substrate (additive, non-breaking)
|
|
154
|
+
|
|
155
|
+
- **`mcp_server/branches/`** (PR1) — shared `BranchSeed` and
|
|
156
|
+
`CompiledBranch` types with `seed_from_move_id` / `freeform_seed` /
|
|
157
|
+
`analytical_seed` factories. `BranchSeed` sources:
|
|
158
|
+
`semantic_move` / `freeform` / `synthesis` / `composer` / `technique`.
|
|
159
|
+
- **SessionKernel creative controls** (PR2) — `freshness`,
|
|
160
|
+
`creativity_profile`, `sacred_elements`, `synth_hints` added as
|
|
161
|
+
optional fields on `SessionKernel` and `get_session_kernel`. Legacy
|
|
162
|
+
callers see zero behavior change.
|
|
163
|
+
- **ExperimentBranch compat shim** (PR3) — `move_id` now optional;
|
|
164
|
+
new `ExperimentBranch.from_seed()` classmethod and
|
|
165
|
+
`create_experiment_from_seeds(seeds=[...], compiled_plans=[...])`
|
|
166
|
+
entry point. Legacy `create_experiment(move_ids=...)` keeps working
|
|
167
|
+
and internally delegates via `seed_from_move_id`.
|
|
168
|
+
- **Creative conductor fork** (PR4) — `classify_request_creative()`
|
|
169
|
+
alongside `classify_request()`. Adds producer selection
|
|
170
|
+
(`branch_sources`, `seed_hints`) based on request content + kernel state.
|
|
171
|
+
- **`interesting_but_failed` branch status** (PR7) — new
|
|
172
|
+
`classify_branch_outcome()` in `evaluation/policy.py`. Exploration
|
|
173
|
+
mode downgrades score / measurable-delta failures to
|
|
174
|
+
`interesting_but_failed`; protection violations still force undo
|
|
175
|
+
(safety invariant).
|
|
176
|
+
- **Per-goal-mode novelty bands** (PR8) — TasteGraph's single
|
|
177
|
+
`novelty_band` is now a view over `novelty_bands["improve"]`; the
|
|
178
|
+
`explore` band lets surprise-me branch generation disconnect from
|
|
179
|
+
conservative improve-mode history. `bypass_taste_in_generation` flag
|
|
180
|
+
makes `rank_moves` return uniform scores.
|
|
181
|
+
|
|
182
|
+
### Branch-native producers
|
|
183
|
+
|
|
184
|
+
- **Wonder branch assembler** (PR6) — `generate_branch_seeds()` emits
|
|
185
|
+
seeds from four sources: semantic_move, technique (session memory),
|
|
186
|
+
sacred-element inversion (freshness-gated), and corpus hints.
|
|
187
|
+
`enter_wonder_mode` now surfaces `branch_seeds` alongside variants.
|
|
188
|
+
- **`synthesis_brain/` subsystem** (PR9, PR10) — native-synth-aware
|
|
189
|
+
branch production with adapters for Wavetable, Operator, Analog,
|
|
190
|
+
Drift, Meld. `analyze_synth_patch()` / `propose_synth_branches()`
|
|
191
|
+
callable from Python; `extract_timbre_fingerprint()` builds a
|
|
192
|
+
TimbralFingerprint from 8-band spectrum + optional FluCoMa
|
|
193
|
+
descriptors. No MCP tools yet — next release will wire dedicated
|
|
194
|
+
tools and do the tool-count metadata sweep in one pass.
|
|
195
|
+
- **Composer branch producer** (PR11) — `propose_composer_branches()`
|
|
196
|
+
emits N distinct compositional hypotheses from one prompt via three
|
|
197
|
+
strategies (canonical / energy_shift / layer_contrast), gated on
|
|
198
|
+
freshness. Each branch ships a pre-compiled scaffolding plan.
|
|
199
|
+
|
|
200
|
+
### Docs refactor
|
|
201
|
+
|
|
202
|
+
- **Skills + command guides thinned** (PR5) — `livepilot-core/SKILL.md`
|
|
203
|
+
now presents two peer flows (Flow A targeted / Flow B exploratory)
|
|
204
|
+
instead of one recipe-first pipeline. `arrange` / `beat` / `mix` /
|
|
205
|
+
`sounddesign` commands each add a short Branch-Native section.
|
|
206
|
+
- **Branch status vocabulary** documented including
|
|
207
|
+
`interesting_but_failed` retention semantics.
|
|
208
|
+
|
|
209
|
+
### Migration notes for callers
|
|
210
|
+
|
|
211
|
+
- All additions are optional-param / new-function shaped. Any code
|
|
212
|
+
reading `branch.move_id` keeps working because `ExperimentBranch`
|
|
213
|
+
mirrors `seed.move_id` there. Any code calling
|
|
214
|
+
`create_experiment(move_ids=...)` keeps its exact behavior.
|
|
215
|
+
- If you have persistent state on disk (`~/.livepilot/taste.json`):
|
|
216
|
+
v1.13 migrates `novelty_band` (flat float) to `novelty_bands` (dict)
|
|
217
|
+
on first read. Old clients reading the file still see the flat field.
|
|
218
|
+
- Tests added across 9 new files — no existing test needed editing
|
|
219
|
+
beyond `test_experiment_engine.py` (which gains PR3 coverage but
|
|
220
|
+
keeps every pre-PR3 test passing).
|
|
221
|
+
|
|
3
222
|
## 1.12.2 — Post-release audit reliability fixes (April 18 2026)
|
|
4
223
|
|
|
5
224
|
Six issues surfaced by an immediate post-v1.12.0 deep audit (parallel
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
19
|
An agentic production system for Ableton Live 12.<br>
|
|
20
|
-
|
|
20
|
+
402 tools. 52 domains. Device atlas. Splice integration. Auto-composition. Spectral perception. Technique memory.
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
23
|
<br>
|
|
@@ -79,8 +79,8 @@ Most MCP servers are tool collections — they execute commands. LivePilot is an
|
|
|
79
79
|
│ └─────────────────┼──────────────────┘ │
|
|
80
80
|
│ ▼ │
|
|
81
81
|
│ ┌─────────────────┐ │
|
|
82
|
-
│ │
|
|
83
|
-
│ │
|
|
82
|
+
│ │ 402 MCP Tools │ │
|
|
83
|
+
│ │ 52 domains │ │
|
|
84
84
|
│ └────────┬────────┘ │
|
|
85
85
|
│ │ │
|
|
86
86
|
│ Remote Script ──┤── TCP 9878 │
|
|
@@ -120,7 +120,7 @@ Most MCP servers are tool collections — they execute commands. LivePilot is an
|
|
|
120
120
|
|
|
121
121
|
## The Intelligence Layer
|
|
122
122
|
|
|
123
|
-
12 engines sit on top of the
|
|
123
|
+
12 engines sit on top of the 402 tools. They give the AI musical judgment, not just musical execution.
|
|
124
124
|
|
|
125
125
|
### SongBrain — What the Song Is
|
|
126
126
|
|
|
@@ -172,7 +172,7 @@ Every engine follows: **measure before → act → measure after → compare**.
|
|
|
172
172
|
|
|
173
173
|
## Tools
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
402 tools across 52 domains. Highlights below — [full catalog here](docs/manual/tool-catalog.md).
|
|
176
176
|
|
|
177
177
|
<br>
|
|
178
178
|
|
|
@@ -360,7 +360,7 @@ The V2 intelligence layer. These tools analyze, diagnose, plan, evaluate, and le
|
|
|
360
360
|
| Creative Constraints | 5 | constraint activation, reference-inspired variants |
|
|
361
361
|
| Preview Studio | 5 | variant creation, preview rendering, comparison, commit |
|
|
362
362
|
|
|
363
|
-
> **[View all
|
|
363
|
+
> **[View all 402 tools →](docs/manual/tool-catalog.md)**
|
|
364
364
|
|
|
365
365
|
<br>
|
|
366
366
|
|
|
@@ -587,7 +587,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for architecture details, code guidelines
|
|
|
587
587
|
|
|
588
588
|
| Document | What's inside |
|
|
589
589
|
|----------|---------------|
|
|
590
|
-
| [Manual](docs/manual/index.md) | Complete reference: architecture, all
|
|
590
|
+
| [Manual](docs/manual/index.md) | Complete reference: architecture, all 402 tools, workflows |
|
|
591
591
|
| [Intelligence Layer](docs/manual/intelligence.md) | How the 12 engines connect — conductor, moves, preview, evaluation |
|
|
592
592
|
| [Device Atlas](docs/manual/device-atlas.md) | 1305 devices indexed — search, suggest, chain building |
|
|
593
593
|
| [Samples & Slicing](docs/manual/samples.md) | 3-source search, fitness critics, slice workflows |
|
|
Binary file
|
package/mcp_server/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""LivePilot MCP Server — bridges MCP protocol to Ableton Live."""
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.14.0"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Branch-native types — the shared substrate for Wonder, Preview Studio,
|
|
2
|
+
Experiment, and future producers (synthesis_brain, composer).
|
|
3
|
+
|
|
4
|
+
A BranchSeed is a producer-emitted creative intent (pre-compilation).
|
|
5
|
+
A CompiledBranch pairs a seed with a concrete plan ready for the execution
|
|
6
|
+
router. compiled_plan=None ⇒ analytical-only (directional suggestion).
|
|
7
|
+
|
|
8
|
+
This module is pure types + factory helpers. No I/O, no side effects, no
|
|
9
|
+
imports from other mcp_server subsystems.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .types import (
|
|
13
|
+
BranchSeed,
|
|
14
|
+
CompiledBranch,
|
|
15
|
+
BranchSource,
|
|
16
|
+
RiskLabel,
|
|
17
|
+
NoveltyLabel,
|
|
18
|
+
PRODUCER_PAYLOAD_SCHEMA_VERSION,
|
|
19
|
+
seed_from_move_id,
|
|
20
|
+
freeform_seed,
|
|
21
|
+
analytical_seed,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"BranchSeed",
|
|
26
|
+
"CompiledBranch",
|
|
27
|
+
"BranchSource",
|
|
28
|
+
"RiskLabel",
|
|
29
|
+
"NoveltyLabel",
|
|
30
|
+
"PRODUCER_PAYLOAD_SCHEMA_VERSION",
|
|
31
|
+
"seed_from_move_id",
|
|
32
|
+
"freeform_seed",
|
|
33
|
+
"analytical_seed",
|
|
34
|
+
]
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""Branch-native types.
|
|
2
|
+
|
|
3
|
+
Design (see docs/specs/2026-03-17-livepilot-design.md and the branch-native
|
|
4
|
+
migration plan):
|
|
5
|
+
|
|
6
|
+
- Producers (Wonder, synthesis_brain, composer, technique memory) emit
|
|
7
|
+
BranchSeed objects expressing creative intent. A seed carries a hypothesis,
|
|
8
|
+
a source label, a distinctness reason, and novelty/risk labels — but no
|
|
9
|
+
executable plan yet.
|
|
10
|
+
- A compiler turns a seed into a CompiledBranch by attaching a plan. For
|
|
11
|
+
source="semantic_move" seeds, the existing semantic_moves.compiler is used.
|
|
12
|
+
For freeform/synthesis/composer seeds, the producer supplies the plan.
|
|
13
|
+
- CompiledBranch is the canonical post-compilation shape. PreviewVariant
|
|
14
|
+
and ExperimentBranch will migrate to thin wrappers over CompiledBranch
|
|
15
|
+
in later PRs; this PR only introduces the types.
|
|
16
|
+
|
|
17
|
+
compiled_plan=None means analytical_only — the branch is a directional
|
|
18
|
+
suggestion with no executable path. This case already exists in Wonder
|
|
19
|
+
(build_analytical_variant); the branch-native schema promotes it to a
|
|
20
|
+
first-class concept across the whole system.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import hashlib
|
|
26
|
+
from dataclasses import asdict, dataclass, field
|
|
27
|
+
from typing import Literal, Optional
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
BranchSource = Literal[
|
|
31
|
+
"semantic_move", # compiled via semantic_moves.compiler
|
|
32
|
+
"freeform", # producer supplies the compiled plan directly
|
|
33
|
+
"synthesis", # from synthesis_brain (PR9+)
|
|
34
|
+
"composer", # from composer branch-native path (PR11)
|
|
35
|
+
"technique", # from technique memory replay
|
|
36
|
+
]
|
|
37
|
+
RiskLabel = Literal["low", "medium", "high"]
|
|
38
|
+
NoveltyLabel = Literal["safe", "strong", "unexpected"]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
PRODUCER_PAYLOAD_SCHEMA_VERSION = 1
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class BranchSeed:
|
|
46
|
+
"""Pre-compilation creative intent.
|
|
47
|
+
|
|
48
|
+
Distinctness between branches is checked at seed layer — two seeds with
|
|
49
|
+
the same (source, hypothesis_hash, affected_scope_hash) are not distinct.
|
|
50
|
+
|
|
51
|
+
Fields:
|
|
52
|
+
seed_id: stable identifier. For semantic_move seeds, derived from move_id.
|
|
53
|
+
source: producer category.
|
|
54
|
+
move_id: populated when source="semantic_move"; empty otherwise.
|
|
55
|
+
hypothesis: one-line human-readable prediction of what the branch does.
|
|
56
|
+
protected_qualities: dimension names the producer promises not to regress.
|
|
57
|
+
affected_scope: {track_indices, device_paths, section_ids, clip_slots}.
|
|
58
|
+
Session-scope information: which tracks/devices/sections the branch
|
|
59
|
+
affects. Distinct from ``producer_payload`` which carries regeneration
|
|
60
|
+
context.
|
|
61
|
+
distinctness_reason: why this seed is different from siblings in a set.
|
|
62
|
+
risk_label: execution safety tier.
|
|
63
|
+
novelty_label: creative novelty tier — maps to the safe/strong/unexpected UX triptych.
|
|
64
|
+
analytical_only: true ⇒ no plan will be compiled; branch is directional only.
|
|
65
|
+
producer_payload: free-form dict the emitting producer populates with
|
|
66
|
+
whatever it needs for regeneration / provenance / winner escalation.
|
|
67
|
+
Consumers must check ``schema_version`` (defaults to 1) before reading
|
|
68
|
+
specific keys so older serialized branches don't break newer readers.
|
|
69
|
+
|
|
70
|
+
Canonical shapes per producer:
|
|
71
|
+
semantic_move: {schema_version: 1} (empty — move_id is the key)
|
|
72
|
+
freeform / technique: {schema_version: 1} (optional)
|
|
73
|
+
synthesis:
|
|
74
|
+
{schema_version: 1,
|
|
75
|
+
device_name: "Wavetable",
|
|
76
|
+
track_index: int, device_index: int,
|
|
77
|
+
strategy: "osc_position_shift" | "voice_width_variant" | ...,
|
|
78
|
+
topology_hint: {...}} (used by PR4 render-verify to re-target)
|
|
79
|
+
composer:
|
|
80
|
+
{schema_version: 1,
|
|
81
|
+
strategy: "canonical" | "energy_shift" | "layer_contrast",
|
|
82
|
+
intent: {<CompositionIntent.to_dict()>}} (used by PR3 to
|
|
83
|
+
rehydrate for winner-commit escalation)
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
seed_id: str
|
|
87
|
+
source: BranchSource
|
|
88
|
+
move_id: str = ""
|
|
89
|
+
hypothesis: str = ""
|
|
90
|
+
protected_qualities: list[str] = field(default_factory=list)
|
|
91
|
+
affected_scope: dict = field(default_factory=dict)
|
|
92
|
+
distinctness_reason: str = ""
|
|
93
|
+
risk_label: RiskLabel = "low"
|
|
94
|
+
novelty_label: NoveltyLabel = "strong"
|
|
95
|
+
analytical_only: bool = False
|
|
96
|
+
producer_payload: dict = field(
|
|
97
|
+
default_factory=lambda: {"schema_version": PRODUCER_PAYLOAD_SCHEMA_VERSION}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def to_dict(self) -> dict:
|
|
101
|
+
d = asdict(self)
|
|
102
|
+
# Guarantee schema_version in the payload even if the dict was
|
|
103
|
+
# mutated to empty by a caller.
|
|
104
|
+
payload = d.get("producer_payload") or {}
|
|
105
|
+
if "schema_version" not in payload:
|
|
106
|
+
payload = dict(payload)
|
|
107
|
+
payload["schema_version"] = PRODUCER_PAYLOAD_SCHEMA_VERSION
|
|
108
|
+
d["producer_payload"] = payload
|
|
109
|
+
return d
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class CompiledBranch:
|
|
114
|
+
"""Post-compilation branch — supersedes the move_id-locked ExperimentBranch.
|
|
115
|
+
|
|
116
|
+
Fields:
|
|
117
|
+
branch_id: stable identifier scoped to a branch set.
|
|
118
|
+
seed: the originating BranchSeed (carries source, hypothesis, move_id).
|
|
119
|
+
compiled_plan: execution-router-ready plan dict, or None for analytical-only.
|
|
120
|
+
execution_log: per-step results after run/commit; [{tool, backend, ok, error, result}].
|
|
121
|
+
before_snapshot / after_snapshot: captured session state dicts.
|
|
122
|
+
evaluation: evaluator result dict; shape determined by the evaluator.
|
|
123
|
+
score: composite quality score (0-1).
|
|
124
|
+
status: pending | running | evaluated | committed | discarded
|
|
125
|
+
| interesting_but_failed ← PR7 will gate on this
|
|
126
|
+
created_at_ms / executed_at_ms: timestamps.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
branch_id: str
|
|
130
|
+
seed: BranchSeed
|
|
131
|
+
compiled_plan: Optional[dict] = None
|
|
132
|
+
execution_log: list = field(default_factory=list)
|
|
133
|
+
before_snapshot: Optional[dict] = None
|
|
134
|
+
after_snapshot: Optional[dict] = None
|
|
135
|
+
evaluation: Optional[dict] = None
|
|
136
|
+
score: float = 0.0
|
|
137
|
+
status: str = "pending"
|
|
138
|
+
created_at_ms: int = 0
|
|
139
|
+
executed_at_ms: int = 0
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def move_id(self) -> str:
|
|
143
|
+
"""Back-compat convenience — delegates to seed.move_id.
|
|
144
|
+
|
|
145
|
+
Lets callers that currently read branch.move_id keep working once
|
|
146
|
+
ExperimentBranch migrates to wrap CompiledBranch.
|
|
147
|
+
"""
|
|
148
|
+
return self.seed.move_id
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def analytical_only(self) -> bool:
|
|
152
|
+
"""A branch is analytical when the seed says so OR when no plan exists."""
|
|
153
|
+
return self.seed.analytical_only or self.compiled_plan is None
|
|
154
|
+
|
|
155
|
+
def to_dict(self) -> dict:
|
|
156
|
+
d = {
|
|
157
|
+
"branch_id": self.branch_id,
|
|
158
|
+
"seed": self.seed.to_dict(),
|
|
159
|
+
"move_id": self.move_id,
|
|
160
|
+
"score": self.score,
|
|
161
|
+
"status": self.status,
|
|
162
|
+
"analytical_only": self.analytical_only,
|
|
163
|
+
"created_at_ms": self.created_at_ms,
|
|
164
|
+
}
|
|
165
|
+
if self.compiled_plan:
|
|
166
|
+
d["step_count"] = self.compiled_plan.get("step_count", 0)
|
|
167
|
+
d["summary"] = self.compiled_plan.get("summary", "")
|
|
168
|
+
if self.before_snapshot is not None:
|
|
169
|
+
d["before_snapshot"] = self.before_snapshot
|
|
170
|
+
if self.after_snapshot is not None:
|
|
171
|
+
d["after_snapshot"] = self.after_snapshot
|
|
172
|
+
if self.evaluation is not None:
|
|
173
|
+
d["evaluation"] = self.evaluation
|
|
174
|
+
if self.execution_log:
|
|
175
|
+
d["execution_log"] = self.execution_log
|
|
176
|
+
d["steps_ok"] = sum(1 for e in self.execution_log if e.get("ok"))
|
|
177
|
+
d["steps_failed"] = sum(1 for e in self.execution_log if not e.get("ok"))
|
|
178
|
+
return d
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ── Factory helpers ──────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
def _stable_seed_id(prefix: str, *parts: str) -> str:
|
|
184
|
+
"""Deterministic seed_id from parts — no timestamps, stable across runs."""
|
|
185
|
+
seed = "|".join(str(p) for p in parts)
|
|
186
|
+
return f"{prefix}_" + hashlib.sha256(seed.encode()).hexdigest()[:10]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def seed_from_move_id(
|
|
190
|
+
move_id: str,
|
|
191
|
+
seed_id: str = "",
|
|
192
|
+
hypothesis: str = "",
|
|
193
|
+
novelty_label: NoveltyLabel = "strong",
|
|
194
|
+
risk_label: RiskLabel = "low",
|
|
195
|
+
protected_qualities: Optional[list[str]] = None,
|
|
196
|
+
distinctness_reason: str = "",
|
|
197
|
+
producer_payload: Optional[dict] = None,
|
|
198
|
+
) -> BranchSeed:
|
|
199
|
+
"""Build a semantic_move seed — the baseline producer path.
|
|
200
|
+
|
|
201
|
+
Mirrors how wonder_mode.engine.discover_moves already hands moves to
|
|
202
|
+
build_variant. In later PRs, the Wonder distinctness selector will emit
|
|
203
|
+
BranchSeed directly instead of move dicts.
|
|
204
|
+
"""
|
|
205
|
+
if not seed_id:
|
|
206
|
+
seed_id = _stable_seed_id("seed", "move", move_id, novelty_label)
|
|
207
|
+
return BranchSeed(
|
|
208
|
+
seed_id=seed_id,
|
|
209
|
+
source="semantic_move",
|
|
210
|
+
move_id=move_id,
|
|
211
|
+
hypothesis=hypothesis or f"Apply {move_id}",
|
|
212
|
+
novelty_label=novelty_label,
|
|
213
|
+
risk_label=risk_label,
|
|
214
|
+
protected_qualities=protected_qualities or [],
|
|
215
|
+
distinctness_reason=distinctness_reason,
|
|
216
|
+
producer_payload=_normalize_payload(producer_payload),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def freeform_seed(
|
|
221
|
+
seed_id: str,
|
|
222
|
+
hypothesis: str,
|
|
223
|
+
affected_scope: Optional[dict] = None,
|
|
224
|
+
protected_qualities: Optional[list[str]] = None,
|
|
225
|
+
distinctness_reason: str = "",
|
|
226
|
+
novelty_label: NoveltyLabel = "strong",
|
|
227
|
+
risk_label: RiskLabel = "medium",
|
|
228
|
+
source: BranchSource = "freeform",
|
|
229
|
+
producer_payload: Optional[dict] = None,
|
|
230
|
+
) -> BranchSeed:
|
|
231
|
+
"""Build a freeform seed — producer has a concrete hypothesis without a move.
|
|
232
|
+
|
|
233
|
+
The compiled plan is attached downstream by the producer; this seed
|
|
234
|
+
carries intent only. Used by synthesis_brain, composer, and any
|
|
235
|
+
producer that doesn't go through semantic_moves.compiler.
|
|
236
|
+
"""
|
|
237
|
+
return BranchSeed(
|
|
238
|
+
seed_id=seed_id,
|
|
239
|
+
source=source,
|
|
240
|
+
hypothesis=hypothesis,
|
|
241
|
+
affected_scope=affected_scope or {},
|
|
242
|
+
protected_qualities=protected_qualities or [],
|
|
243
|
+
distinctness_reason=distinctness_reason,
|
|
244
|
+
novelty_label=novelty_label,
|
|
245
|
+
risk_label=risk_label,
|
|
246
|
+
producer_payload=_normalize_payload(producer_payload),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def analytical_seed(
|
|
251
|
+
seed_id: str,
|
|
252
|
+
hypothesis: str,
|
|
253
|
+
source: BranchSource = "freeform",
|
|
254
|
+
protected_qualities: Optional[list[str]] = None,
|
|
255
|
+
producer_payload: Optional[dict] = None,
|
|
256
|
+
) -> BranchSeed:
|
|
257
|
+
"""Build an analytical-only seed — no plan will be compiled.
|
|
258
|
+
|
|
259
|
+
Used when a producer has a directional suggestion but no executable path:
|
|
260
|
+
- Wonder fallback when no move matches (already present as
|
|
261
|
+
build_analytical_variant in wonder_mode.engine).
|
|
262
|
+
- Synthesis brain on opaque devices where parameter state can be read
|
|
263
|
+
but safe mutations cannot be proposed yet.
|
|
264
|
+
"""
|
|
265
|
+
return BranchSeed(
|
|
266
|
+
seed_id=seed_id,
|
|
267
|
+
source=source,
|
|
268
|
+
hypothesis=hypothesis,
|
|
269
|
+
protected_qualities=protected_qualities or [],
|
|
270
|
+
analytical_only=True,
|
|
271
|
+
producer_payload=_normalize_payload(producer_payload),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _normalize_payload(payload: Optional[dict]) -> dict:
|
|
276
|
+
"""Ensure a producer_payload always has schema_version set.
|
|
277
|
+
|
|
278
|
+
Producers that don't care about versioning pass None / empty dict and
|
|
279
|
+
get the default schema_version=PRODUCER_PAYLOAD_SCHEMA_VERSION back.
|
|
280
|
+
Producers that carry their own version number have it preserved.
|
|
281
|
+
"""
|
|
282
|
+
if not payload:
|
|
283
|
+
return {"schema_version": PRODUCER_PAYLOAD_SCHEMA_VERSION}
|
|
284
|
+
out = dict(payload)
|
|
285
|
+
out.setdefault("schema_version", PRODUCER_PAYLOAD_SCHEMA_VERSION)
|
|
286
|
+
return out
|
|
@@ -1 +1,10 @@
|
|
|
1
|
-
"""Composer Engine — auto-composition from text prompts via Splice + Sample Engine.
|
|
1
|
+
"""Composer Engine — auto-composition from text prompts via Splice + Sample Engine.
|
|
2
|
+
|
|
3
|
+
PR11 adds branch_producer.propose_composer_branches() for emitting
|
|
4
|
+
multiple section-hypothesis BranchSeeds alongside the existing
|
|
5
|
+
single-plan compose() entry point.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .branch_producer import propose_composer_branches, escalate_composer_branch
|
|
9
|
+
|
|
10
|
+
__all__ = ["propose_composer_branches", "escalate_composer_branch"]
|