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
|
@@ -22,6 +22,7 @@ import time
|
|
|
22
22
|
from typing import Optional
|
|
23
23
|
|
|
24
24
|
from .models import ExperimentSet, ExperimentBranch, BranchSnapshot
|
|
25
|
+
from ..branches import BranchSeed, seed_from_move_id
|
|
25
26
|
import logging
|
|
26
27
|
|
|
27
28
|
logger = logging.getLogger(__name__)
|
|
@@ -39,27 +40,43 @@ def _gen_id(prefix: str, seed: str) -> str:
|
|
|
39
40
|
# ── Create experiments ───────────────────────────────────────────────────────
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
def
|
|
43
|
+
def create_experiment_from_seeds(
|
|
43
44
|
request_text: str,
|
|
44
|
-
|
|
45
|
+
seeds: list[BranchSeed],
|
|
45
46
|
kernel_id: str = "",
|
|
47
|
+
compiled_plans: Optional[list] = None,
|
|
46
48
|
) -> ExperimentSet:
|
|
47
|
-
"""Create an experiment set
|
|
49
|
+
"""Create an experiment set from BranchSeeds (PR3 canonical path).
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
seeds: one BranchSeed per desired branch. Can be any source — semantic_move,
|
|
52
|
+
freeform, synthesis, composer, technique.
|
|
53
|
+
compiled_plans: optional parallel list; when entry ``i`` is a dict, that
|
|
54
|
+
plan is attached to branch ``i`` (used by freeform / synthesis / composer
|
|
55
|
+
producers that do their own compilation). When None or entry is None,
|
|
56
|
+
run_experiment compiles from the seed at run time — which only succeeds
|
|
57
|
+
for source="semantic_move" seeds.
|
|
58
|
+
|
|
59
|
+
Does NOT execute anything — call run_experiment() to trial each branch.
|
|
51
60
|
"""
|
|
61
|
+
if compiled_plans is not None and len(compiled_plans) != len(seeds):
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"compiled_plans length ({len(compiled_plans)}) must match "
|
|
64
|
+
f"seeds length ({len(seeds)})"
|
|
65
|
+
)
|
|
66
|
+
|
|
52
67
|
exp_id = _gen_id("exp", request_text)
|
|
53
68
|
now = int(time.time() * 1000)
|
|
54
69
|
|
|
55
70
|
branches = []
|
|
56
|
-
for i,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
for i, seed in enumerate(seeds):
|
|
72
|
+
plan = compiled_plans[i] if compiled_plans else None
|
|
73
|
+
display = seed.move_id or (seed.hypothesis[:32] if seed.hypothesis else seed.seed_id)
|
|
74
|
+
branch = ExperimentBranch.from_seed(
|
|
75
|
+
seed=seed,
|
|
76
|
+
branch_id=_gen_id("br", f"{seed.seed_id}_{i}"),
|
|
77
|
+
name=f"Branch {i+1}: {display}",
|
|
61
78
|
source_kernel_id=kernel_id,
|
|
62
|
-
|
|
79
|
+
compiled_plan=plan,
|
|
63
80
|
created_at_ms=now,
|
|
64
81
|
)
|
|
65
82
|
branches.append(branch)
|
|
@@ -76,6 +93,25 @@ def create_experiment(
|
|
|
76
93
|
return experiment
|
|
77
94
|
|
|
78
95
|
|
|
96
|
+
def create_experiment(
|
|
97
|
+
request_text: str,
|
|
98
|
+
move_ids: list[str],
|
|
99
|
+
kernel_id: str = "",
|
|
100
|
+
) -> ExperimentSet:
|
|
101
|
+
"""Create an experiment set with one semantic_move branch per move_id.
|
|
102
|
+
|
|
103
|
+
Legacy API — kept for back-compat. Internally builds one BranchSeed per
|
|
104
|
+
move_id via seed_from_move_id and delegates to create_experiment_from_seeds.
|
|
105
|
+
Branch naming, ids, and lifecycle are unchanged for existing callers.
|
|
106
|
+
"""
|
|
107
|
+
seeds = [seed_from_move_id(mid) for mid in move_ids]
|
|
108
|
+
return create_experiment_from_seeds(
|
|
109
|
+
request_text=request_text,
|
|
110
|
+
seeds=seeds,
|
|
111
|
+
kernel_id=kernel_id,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
79
115
|
def get_experiment(experiment_id: str) -> Optional[ExperimentSet]:
|
|
80
116
|
"""Get an experiment by ID."""
|
|
81
117
|
return _EXPERIMENTS.get(experiment_id)
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
"""Experiment branch data models.
|
|
2
2
|
|
|
3
|
-
An ExperimentBranch represents one trial
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
An ExperimentBranch represents one trial against the current session state.
|
|
4
|
+
|
|
5
|
+
Pre-PR3 every branch was tied to a semantic_move (positional required
|
|
6
|
+
``move_id``). PR3 widens this: a branch may now be built from any
|
|
7
|
+
:class:`mcp_server.branches.BranchSeed` — semantic_move, freeform,
|
|
8
|
+
synthesis, composer, or technique. The ``move_id`` field is retained as
|
|
9
|
+
an optional convenience that mirrors ``seed.move_id`` for back-compat with
|
|
10
|
+
callers that read ``branch.move_id`` directly.
|
|
11
|
+
|
|
12
|
+
Multiple branches form an experiment set that can be compared and ranked.
|
|
6
13
|
"""
|
|
7
14
|
|
|
8
15
|
from __future__ import annotations
|
|
@@ -11,15 +18,42 @@ import time
|
|
|
11
18
|
from dataclasses import dataclass, field
|
|
12
19
|
from typing import Any, Optional
|
|
13
20
|
|
|
21
|
+
from ..branches import BranchSeed
|
|
22
|
+
|
|
14
23
|
|
|
15
24
|
@dataclass
|
|
16
25
|
class BranchSnapshot:
|
|
17
|
-
"""Captured state before or after a branch experiment.
|
|
26
|
+
"""Captured state before or after a branch experiment.
|
|
27
|
+
|
|
28
|
+
Pre-PR4 fields (spectrum / rms / peak / track_meters) stay the same —
|
|
29
|
+
they remain the fast-path evidence when render-verify isn't available
|
|
30
|
+
or wasn't opted in.
|
|
31
|
+
|
|
32
|
+
PR4 adds render-based fields that are populated only when the
|
|
33
|
+
experiment runs with render_verify=True:
|
|
34
|
+
|
|
35
|
+
capture_path: path to the captured audio file (useful for re-analysis
|
|
36
|
+
or user audition of the branch output).
|
|
37
|
+
loudness: {lufs, lra, rms, peak, crest} from analyze_loudness.
|
|
38
|
+
spectral_shape: {centroid, flatness, rolloff, crest} from FluCoMa or
|
|
39
|
+
the offline analyzer.
|
|
40
|
+
fingerprint: TimbralFingerprint.to_dict() extracted from the
|
|
41
|
+
captured audio.
|
|
42
|
+
|
|
43
|
+
The fingerprint is what classify_branch_outcome reads to derive a
|
|
44
|
+
real goal_progress + measurable_count instead of relying on the
|
|
45
|
+
inline meter-based heuristic alone.
|
|
46
|
+
"""
|
|
18
47
|
spectrum: Optional[dict] = None
|
|
19
48
|
rms: Optional[float] = None
|
|
20
49
|
peak: Optional[float] = None
|
|
21
50
|
track_meters: Optional[list] = None
|
|
22
51
|
timestamp_ms: int = 0
|
|
52
|
+
# PR4 — render-based evidence (opt-in via render_verify flag)
|
|
53
|
+
capture_path: Optional[str] = None
|
|
54
|
+
loudness: Optional[dict] = None
|
|
55
|
+
spectral_shape: Optional[dict] = None
|
|
56
|
+
fingerprint: Optional[dict] = None # TimbralFingerprint.to_dict()
|
|
23
57
|
|
|
24
58
|
def to_dict(self) -> dict:
|
|
25
59
|
d = {}
|
|
@@ -31,20 +65,40 @@ class BranchSnapshot:
|
|
|
31
65
|
d["peak"] = self.peak
|
|
32
66
|
if self.track_meters is not None:
|
|
33
67
|
d["track_meters"] = self.track_meters
|
|
68
|
+
if self.capture_path is not None:
|
|
69
|
+
d["capture_path"] = self.capture_path
|
|
70
|
+
if self.loudness is not None:
|
|
71
|
+
d["loudness"] = self.loudness
|
|
72
|
+
if self.spectral_shape is not None:
|
|
73
|
+
d["spectral_shape"] = self.spectral_shape
|
|
74
|
+
if self.fingerprint is not None:
|
|
75
|
+
d["fingerprint"] = self.fingerprint
|
|
34
76
|
d["timestamp_ms"] = self.timestamp_ms
|
|
35
77
|
return d
|
|
36
78
|
|
|
37
79
|
|
|
38
80
|
@dataclass
|
|
39
81
|
class ExperimentBranch:
|
|
40
|
-
"""One trial branch in an experiment set.
|
|
82
|
+
"""One trial branch in an experiment set.
|
|
83
|
+
|
|
84
|
+
move_id is retained as an optional convenience (empty for freeform /
|
|
85
|
+
synthesis / composer seeds) so pre-PR3 callers that read
|
|
86
|
+
``branch.move_id`` directly keep working. The authoritative source of
|
|
87
|
+
branch intent is ``seed`` when present.
|
|
88
|
+
"""
|
|
41
89
|
branch_id: str
|
|
42
90
|
name: str
|
|
43
|
-
|
|
91
|
+
# PR3 — was a required positional; now defaults to "" for seeds whose
|
|
92
|
+
# source is not "semantic_move". When seed is present, move_id mirrors
|
|
93
|
+
# seed.move_id (populated by ExperimentBranch.from_seed).
|
|
94
|
+
move_id: str = ""
|
|
44
95
|
source_kernel_id: str = ""
|
|
45
|
-
status: str = "pending" # pending | running | evaluated | committed | discarded
|
|
96
|
+
status: str = "pending" # pending | running | evaluated | committed | discarded | interesting_but_failed
|
|
46
97
|
|
|
47
|
-
# Compiled plan for this branch
|
|
98
|
+
# Compiled plan for this branch. Pre-PR3 this was always filled in at
|
|
99
|
+
# run_experiment time. Post-PR3, freeform / synthesis / composer producers
|
|
100
|
+
# MAY pre-populate it on the seed path; run_experiment respects a
|
|
101
|
+
# pre-existing plan and only compiles when it's None.
|
|
48
102
|
compiled_plan: Optional[dict] = None
|
|
49
103
|
|
|
50
104
|
# Captured snapshots
|
|
@@ -65,6 +119,44 @@ class ExperimentBranch:
|
|
|
65
119
|
created_at_ms: int = 0
|
|
66
120
|
executed_at_ms: int = 0
|
|
67
121
|
|
|
122
|
+
# PR3 — branch-native seed. None for legacy move-only branches built via
|
|
123
|
+
# the bare constructor; populated when built through from_seed() or via
|
|
124
|
+
# create_experiment_from_seeds.
|
|
125
|
+
seed: Optional[BranchSeed] = None
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def from_seed(
|
|
129
|
+
cls,
|
|
130
|
+
seed: BranchSeed,
|
|
131
|
+
branch_id: str,
|
|
132
|
+
name: str = "",
|
|
133
|
+
source_kernel_id: str = "",
|
|
134
|
+
compiled_plan: Optional[dict] = None,
|
|
135
|
+
created_at_ms: int = 0,
|
|
136
|
+
) -> "ExperimentBranch":
|
|
137
|
+
"""Construct an ExperimentBranch from a BranchSeed.
|
|
138
|
+
|
|
139
|
+
``move_id`` is mirrored from ``seed.move_id`` (empty for freeform /
|
|
140
|
+
synthesis / composer / technique seeds). When ``compiled_plan`` is
|
|
141
|
+
provided, the producer has already compiled — run_experiment will
|
|
142
|
+
skip compilation for this branch. When None, compilation defers to
|
|
143
|
+
the semantic_moves.compiler at run time and only succeeds for
|
|
144
|
+
source="semantic_move" seeds.
|
|
145
|
+
"""
|
|
146
|
+
default_name = (
|
|
147
|
+
f"Branch ({seed.source}:{seed.move_id or seed.seed_id[:8]})"
|
|
148
|
+
)
|
|
149
|
+
return cls(
|
|
150
|
+
branch_id=branch_id,
|
|
151
|
+
name=name or default_name,
|
|
152
|
+
move_id=seed.move_id,
|
|
153
|
+
source_kernel_id=source_kernel_id,
|
|
154
|
+
status="pending",
|
|
155
|
+
compiled_plan=compiled_plan,
|
|
156
|
+
created_at_ms=created_at_ms,
|
|
157
|
+
seed=seed,
|
|
158
|
+
)
|
|
159
|
+
|
|
68
160
|
def to_dict(self) -> dict:
|
|
69
161
|
d = {
|
|
70
162
|
"branch_id": self.branch_id,
|
|
@@ -87,6 +179,18 @@ class ExperimentBranch:
|
|
|
87
179
|
d["execution_log"] = self.execution_log
|
|
88
180
|
d["steps_ok"] = sum(1 for e in self.execution_log if e.get("ok"))
|
|
89
181
|
d["steps_failed"] = sum(1 for e in self.execution_log if not e.get("ok"))
|
|
182
|
+
if self.seed is not None:
|
|
183
|
+
d["seed"] = self.seed.to_dict()
|
|
184
|
+
d["branch_source"] = self.seed.source
|
|
185
|
+
d["analytical_only"] = (
|
|
186
|
+
self.seed.analytical_only or self.compiled_plan is None
|
|
187
|
+
)
|
|
188
|
+
# Shortcut to the seed's producer_payload so downstream callers
|
|
189
|
+
# (composer winner-commit, synthesis re-target, provenance logs)
|
|
190
|
+
# don't have to reach into d["seed"]["producer_payload"] every
|
|
191
|
+
# time. The full seed dict is still available for producers
|
|
192
|
+
# that need other fields.
|
|
193
|
+
d["producer_payload"] = dict(self.seed.producer_payload or {})
|
|
90
194
|
return d
|
|
91
195
|
|
|
92
196
|
|