livepilot 1.10.0 → 1.10.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/.claude-plugin/marketplace.json +3 -3
- package/AGENTS.md +3 -3
- package/CHANGELOG.md +214 -0
- package/CONTRIBUTING.md +2 -2
- package/LICENSE +62 -21
- package/README.md +264 -286
- package/livepilot/.Codex-plugin/plugin.json +2 -2
- package/livepilot/.claude-plugin/plugin.json +2 -2
- package/livepilot/skills/livepilot-arrangement/SKILL.md +18 -1
- package/livepilot/skills/livepilot-core/SKILL.md +5 -5
- package/livepilot/skills/livepilot-core/references/overview.md +3 -3
- package/livepilot/skills/livepilot-devices/SKILL.md +23 -2
- package/livepilot/skills/livepilot-evaluation/references/capability-modes.md +1 -1
- package/livepilot/skills/livepilot-release/SKILL.md +21 -17
- package/livepilot/skills/livepilot-sample-engine/SKILL.md +2 -1
- package/livepilot/skills/livepilot-wonder/SKILL.md +8 -6
- package/livepilot.mcpb +0 -0
- package/m4l_device/LivePilot_Analyzer.adv +0 -0
- package/m4l_device/LivePilot_Analyzer.amxd +0 -0
- package/m4l_device/livepilot_bridge.js +1 -1
- package/manifest.json +4 -4
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/composer/engine.py +249 -169
- package/mcp_server/composer/sample_resolver.py +153 -0
- package/mcp_server/composer/tools.py +97 -87
- package/mcp_server/memory/taste_accessors.py +47 -0
- package/mcp_server/preview_studio/engine.py +9 -2
- package/mcp_server/preview_studio/tools.py +78 -35
- package/mcp_server/project_brain/tools.py +34 -0
- package/mcp_server/runtime/execution_router.py +180 -38
- package/mcp_server/runtime/mcp_dispatch.py +46 -0
- package/mcp_server/runtime/remote_commands.py +4 -1
- package/mcp_server/runtime/tools.py +55 -32
- package/mcp_server/sample_engine/moves.py +12 -12
- package/mcp_server/sample_engine/slice_workflow.py +190 -0
- package/mcp_server/sample_engine/tools.py +104 -1
- package/mcp_server/semantic_moves/device_creation_moves.py +7 -7
- package/mcp_server/semantic_moves/mix_moves.py +8 -8
- package/mcp_server/semantic_moves/models.py +7 -7
- package/mcp_server/semantic_moves/performance_moves.py +4 -4
- package/mcp_server/semantic_moves/sample_compilers.py +14 -9
- package/mcp_server/semantic_moves/sound_design_moves.py +4 -4
- package/mcp_server/semantic_moves/tools.py +63 -10
- package/mcp_server/semantic_moves/transition_moves.py +4 -4
- package/mcp_server/server.py +20 -1
- package/mcp_server/session_continuity/tracker.py +4 -1
- package/mcp_server/tools/_conductor.py +16 -0
- package/mcp_server/tools/_planner_engine.py +24 -0
- package/mcp_server/tools/analyzer.py +2 -0
- package/mcp_server/tools/planner.py +3 -0
- package/mcp_server/wonder_mode/engine.py +59 -13
- package/mcp_server/wonder_mode/tools.py +33 -1
- package/package.json +8 -8
- package/remote_script/LivePilot/__init__.py +1 -1
- package/remote_script/LivePilot/devices.py +10 -0
|
@@ -1,34 +1,48 @@
|
|
|
1
|
-
"""Unified execution router for compiled plan steps.
|
|
1
|
+
"""Unified async execution router for compiled plan steps.
|
|
2
2
|
|
|
3
3
|
Classifies each step by backend (remote_command, mcp_tool, bridge_command)
|
|
4
|
-
and dispatches
|
|
5
|
-
|
|
4
|
+
and dispatches through the correct transport. Async-only — there is no
|
|
5
|
+
sync path. Callers that need to execute plans live inside an async tool
|
|
6
|
+
and await execute_plan_steps_async.
|
|
6
7
|
|
|
7
8
|
Step backends:
|
|
8
|
-
remote_command — valid Remote Script handler, goes through TCP
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
remote_command — valid Remote Script handler, goes through the sync TCP
|
|
10
|
+
client (ableton.send_command)
|
|
11
|
+
bridge_command — M4L bridge handler, goes through the async UDP M4L bridge
|
|
12
|
+
client (bridge.send_command), NOT through ableton
|
|
13
|
+
mcp_tool — in-process Python function, dispatched via an mcp_registry
|
|
14
|
+
dict supplied by the server lifespan
|
|
15
|
+
unknown — not a valid target anywhere; returns a clear error
|
|
16
|
+
|
|
17
|
+
Step-result binding:
|
|
18
|
+
Any step may carry an optional step_id. Later steps may reference an
|
|
19
|
+
earlier result by setting a param to {"$from_step": "<id>", "path": "a.b"}.
|
|
20
|
+
Resolved recursively BEFORE dispatch.
|
|
12
21
|
"""
|
|
13
22
|
|
|
14
23
|
from __future__ import annotations
|
|
15
24
|
|
|
25
|
+
import inspect
|
|
16
26
|
from dataclasses import dataclass
|
|
17
|
-
from typing import Any, Optional
|
|
27
|
+
from typing import Any, Awaitable, Callable, Optional
|
|
18
28
|
|
|
19
29
|
from .remote_commands import BRIDGE_COMMANDS, REMOTE_COMMANDS
|
|
20
30
|
|
|
21
31
|
|
|
22
32
|
# MCP-only tools that exist as Python functions but NOT as TCP handlers.
|
|
23
33
|
# These must be called through direct import, not ableton.send_command().
|
|
34
|
+
# NOTE: capture_audio is a BRIDGE command (livepilot_bridge.js:146), not MCP.
|
|
35
|
+
# It used to be duplicated here; removed to keep classification unambiguous.
|
|
24
36
|
MCP_TOOLS: frozenset[str] = frozenset({
|
|
25
37
|
"apply_automation_shape",
|
|
26
38
|
"apply_gesture_template",
|
|
27
39
|
"analyze_mix",
|
|
28
40
|
"get_master_spectrum",
|
|
29
41
|
"get_emotional_arc",
|
|
30
|
-
"capture_audio",
|
|
31
42
|
"get_motif_graph",
|
|
43
|
+
# Sample-engine workflow tools — async Python that orchestrates multiple
|
|
44
|
+
# sub-commands (search_browser + load_browser_item + bridge.replace_simpler_sample).
|
|
45
|
+
"load_sample_to_simpler",
|
|
32
46
|
# Device Forge tools (MCP-only, no TCP handler)
|
|
33
47
|
"generate_m4l_effect",
|
|
34
48
|
"install_m4l_device",
|
|
@@ -66,17 +80,75 @@ def classify_step(tool: str) -> str:
|
|
|
66
80
|
return "unknown"
|
|
67
81
|
|
|
68
82
|
|
|
69
|
-
|
|
83
|
+
# ── Step-result binding ─────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
def _resolve_binding(binding: dict, step_results: dict) -> Any:
|
|
86
|
+
"""Resolve a {"$from_step": step_id, "path": "a.b.c"} binding.
|
|
87
|
+
|
|
88
|
+
Raises ValueError with a clear message on missing step_id or missing key.
|
|
89
|
+
"""
|
|
90
|
+
step_id = binding["$from_step"]
|
|
91
|
+
path = binding.get("path", "")
|
|
92
|
+
|
|
93
|
+
if step_id not in step_results:
|
|
94
|
+
available = sorted(step_results.keys())
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"Step binding failed: step_id '{step_id}' not found. "
|
|
97
|
+
f"Available: {available or '(no earlier results)'}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
current = step_results[step_id]
|
|
101
|
+
if not isinstance(current, dict):
|
|
102
|
+
raise ValueError(
|
|
103
|
+
f"Step binding failed: result of '{step_id}' is "
|
|
104
|
+
f"{type(current).__name__}, not a dict"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if not path:
|
|
108
|
+
return current
|
|
109
|
+
|
|
110
|
+
for segment in path.split("."):
|
|
111
|
+
if not isinstance(current, dict) or segment not in current:
|
|
112
|
+
keys = list(current.keys()) if isinstance(current, dict) else type(current).__name__
|
|
113
|
+
raise ValueError(
|
|
114
|
+
f"Step binding failed: path '{path}' not found in result of "
|
|
115
|
+
f"'{step_id}'. Available at this level: {keys}"
|
|
116
|
+
)
|
|
117
|
+
current = current[segment]
|
|
118
|
+
|
|
119
|
+
return current
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _resolve_params(params: Any, step_results: dict) -> Any:
|
|
123
|
+
"""Recursively walk params and resolve any $from_step bindings."""
|
|
124
|
+
if isinstance(params, dict):
|
|
125
|
+
if "$from_step" in params:
|
|
126
|
+
return _resolve_binding(params, step_results)
|
|
127
|
+
return {k: _resolve_params(v, step_results) for k, v in params.items()}
|
|
128
|
+
if isinstance(params, list):
|
|
129
|
+
return [_resolve_params(v, step_results) for v in params]
|
|
130
|
+
return params
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ── Async execution path ────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
async def _execute_step_async(
|
|
70
136
|
tool: str,
|
|
71
137
|
params: dict,
|
|
72
|
-
ableton: Any
|
|
73
|
-
|
|
74
|
-
|
|
138
|
+
ableton: Any,
|
|
139
|
+
bridge: Any,
|
|
140
|
+
mcp_registry: dict[str, Callable],
|
|
141
|
+
ctx: Any,
|
|
142
|
+
declared_backend: Optional[str] = None,
|
|
75
143
|
) -> ExecutionResult:
|
|
76
|
-
"""
|
|
77
|
-
backend =
|
|
144
|
+
"""Dispatch a single step through the correct transport, async-aware."""
|
|
145
|
+
backend = (
|
|
146
|
+
declared_backend
|
|
147
|
+
if declared_backend in ("remote_command", "bridge_command", "mcp_tool")
|
|
148
|
+
else classify_step(tool)
|
|
149
|
+
)
|
|
78
150
|
|
|
79
|
-
if backend
|
|
151
|
+
if backend == "remote_command":
|
|
80
152
|
if ableton is None:
|
|
81
153
|
return ExecutionResult(
|
|
82
154
|
ok=False, backend=backend, tool=tool,
|
|
@@ -84,45 +156,95 @@ def execute_step(
|
|
|
84
156
|
)
|
|
85
157
|
try:
|
|
86
158
|
result = ableton.send_command(tool, params)
|
|
159
|
+
if isinstance(result, dict) and "error" in result:
|
|
160
|
+
return ExecutionResult(ok=False, backend=backend, tool=tool, error=result["error"])
|
|
87
161
|
return ExecutionResult(ok=True, backend=backend, tool=tool, result=result)
|
|
88
162
|
except Exception as e:
|
|
89
163
|
return ExecutionResult(ok=False, backend=backend, tool=tool, error=str(e))
|
|
90
164
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
165
|
+
if backend == "bridge_command":
|
|
166
|
+
if bridge is None:
|
|
167
|
+
return ExecutionResult(
|
|
168
|
+
ok=False, backend=backend, tool=tool,
|
|
169
|
+
error="M4L bridge unavailable — cannot dispatch bridge command",
|
|
170
|
+
)
|
|
171
|
+
try:
|
|
172
|
+
# M4LBridge.send_command accepts (command, *args) and OSC-encodes
|
|
173
|
+
# each arg positionally. Plan authors construct params dicts in
|
|
174
|
+
# the order the bridge command expects; we unpack by insertion
|
|
175
|
+
# order (Python 3.7+ guarantees this). This keeps plans readable
|
|
176
|
+
# while matching the real bridge's positional wire format.
|
|
177
|
+
positional = list(params.values()) if params else []
|
|
178
|
+
call = bridge.send_command(tool, *positional)
|
|
179
|
+
result = await call if inspect.isawaitable(call) else call
|
|
180
|
+
if isinstance(result, dict) and "error" in result:
|
|
181
|
+
return ExecutionResult(ok=False, backend=backend, tool=tool, error=result["error"])
|
|
182
|
+
return ExecutionResult(ok=True, backend=backend, tool=tool, result=result)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
return ExecutionResult(ok=False, backend=backend, tool=tool, error=str(e))
|
|
100
185
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
186
|
+
if backend == "mcp_tool":
|
|
187
|
+
fn = mcp_registry.get(tool) if mcp_registry else None
|
|
188
|
+
if fn is None:
|
|
189
|
+
return ExecutionResult(
|
|
190
|
+
ok=False, backend=backend, tool=tool,
|
|
191
|
+
error=(
|
|
192
|
+
f"MCP tool '{tool}' not registered in async router dispatch map. "
|
|
193
|
+
f"Add it to mcp_server.runtime.mcp_dispatch.build_mcp_dispatch_registry()."
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
try:
|
|
197
|
+
sig = inspect.signature(fn)
|
|
198
|
+
kwargs = {"ctx": ctx} if "ctx" in sig.parameters else {}
|
|
199
|
+
call = fn(params, **kwargs)
|
|
200
|
+
result = await call if inspect.isawaitable(call) else call
|
|
201
|
+
if isinstance(result, dict) and "error" in result:
|
|
202
|
+
return ExecutionResult(ok=False, backend=backend, tool=tool, error=result["error"])
|
|
203
|
+
return ExecutionResult(ok=True, backend=backend, tool=tool, result=result)
|
|
204
|
+
except Exception as e:
|
|
205
|
+
return ExecutionResult(ok=False, backend=backend, tool=tool, error=str(e))
|
|
206
|
+
|
|
207
|
+
return ExecutionResult(
|
|
208
|
+
ok=False, backend="unknown", tool=tool,
|
|
209
|
+
error=(
|
|
210
|
+
f"Unknown tool '{tool}' — not a Remote Script command, "
|
|
211
|
+
f"bridge command, or registered MCP tool"
|
|
212
|
+
),
|
|
213
|
+
)
|
|
107
214
|
|
|
108
215
|
|
|
109
|
-
def
|
|
216
|
+
async def execute_plan_steps_async(
|
|
110
217
|
steps: list[dict],
|
|
111
218
|
ableton: Any = None,
|
|
219
|
+
bridge: Any = None,
|
|
220
|
+
mcp_registry: Optional[dict[str, Callable]] = None,
|
|
112
221
|
ctx: Any = None,
|
|
113
222
|
stop_on_failure: bool = True,
|
|
114
223
|
) -> list[ExecutionResult]:
|
|
115
|
-
"""
|
|
224
|
+
"""Async plan executor with step-result binding and correct bridge transport.
|
|
116
225
|
|
|
117
|
-
|
|
118
|
-
|
|
226
|
+
Supports three backends:
|
|
227
|
+
- remote_command via ableton.send_command (sync TCP client)
|
|
228
|
+
- bridge_command via bridge.send_command (async UDP M4L bridge client)
|
|
229
|
+
- mcp_tool via mcp_registry[tool](params, ctx=ctx)
|
|
230
|
+
|
|
231
|
+
Step-result binding:
|
|
232
|
+
Any step may carry an optional "step_id". Later steps may reference an
|
|
233
|
+
earlier result by setting a param to {"$from_step": "<id>", "path": "a.b"}.
|
|
234
|
+
The router walks params recursively and resolves bindings before dispatch.
|
|
235
|
+
Missing ids or missing paths fail that step with a clear error.
|
|
236
|
+
|
|
237
|
+
stop_on_failure: Stop the plan on the first failing step (default). Set to
|
|
238
|
+
False for best-effort execution (each result still recorded).
|
|
119
239
|
"""
|
|
120
240
|
results: list[ExecutionResult] = []
|
|
241
|
+
step_results: dict[str, Any] = {}
|
|
242
|
+
mcp_registry = mcp_registry or {}
|
|
121
243
|
|
|
122
244
|
for step in steps:
|
|
123
245
|
tool = step.get("tool") or step.get("command", "")
|
|
124
|
-
|
|
125
|
-
|
|
246
|
+
raw_params = step.get("params") or step.get("args", {}) or {}
|
|
247
|
+
step_id = step.get("step_id")
|
|
126
248
|
declared_backend = step.get("backend")
|
|
127
249
|
|
|
128
250
|
if not tool:
|
|
@@ -134,9 +256,29 @@ def execute_plan_steps(
|
|
|
134
256
|
break
|
|
135
257
|
continue
|
|
136
258
|
|
|
137
|
-
|
|
259
|
+
# Resolve any $from_step bindings in params BEFORE dispatch.
|
|
260
|
+
try:
|
|
261
|
+
params = _resolve_params(raw_params, step_results)
|
|
262
|
+
except ValueError as e:
|
|
263
|
+
results.append(ExecutionResult(
|
|
264
|
+
ok=False, backend="binding", tool=tool, error=str(e),
|
|
265
|
+
))
|
|
266
|
+
if stop_on_failure:
|
|
267
|
+
break
|
|
268
|
+
continue
|
|
269
|
+
|
|
270
|
+
result = await _execute_step_async(
|
|
271
|
+
tool, params,
|
|
272
|
+
ableton=ableton, bridge=bridge,
|
|
273
|
+
mcp_registry=mcp_registry, ctx=ctx,
|
|
274
|
+
declared_backend=declared_backend,
|
|
275
|
+
)
|
|
138
276
|
results.append(result)
|
|
139
277
|
|
|
278
|
+
# Record successful step result for future bindings
|
|
279
|
+
if result.ok and step_id and isinstance(result.result, dict):
|
|
280
|
+
step_results[step_id] = result.result
|
|
281
|
+
|
|
140
282
|
if not result.ok and stop_on_failure:
|
|
141
283
|
break
|
|
142
284
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Registry of in-process MCP tools callable from the async execution router.
|
|
2
|
+
|
|
3
|
+
These tools live as Python async functions in the MCP server — not TCP Remote
|
|
4
|
+
Script handlers and not M4L bridge commands. Plans that want to invoke them
|
|
5
|
+
go through this registry so the async router can dispatch them in-process.
|
|
6
|
+
|
|
7
|
+
Each entry is a thin wrapper around the real MCP tool import, keeping the
|
|
8
|
+
module cheap to import (no heavy server wiring until a caller actually
|
|
9
|
+
dispatches an MCP step).
|
|
10
|
+
|
|
11
|
+
To add a new in-process tool to plans:
|
|
12
|
+
1. Add the tool name to MCP_TOOLS in execution_router.py so classify_step
|
|
13
|
+
returns "mcp_tool" for it.
|
|
14
|
+
2. Add an _adapter function here that imports the real implementation and
|
|
15
|
+
adapts its kwargs from a plan-style params dict.
|
|
16
|
+
3. Register the adapter in build_mcp_dispatch_registry.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import Any, Callable
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def _load_sample_to_simpler(params: dict, ctx: Any = None) -> dict:
|
|
25
|
+
"""Adapter for mcp_server.tools.analyzer.load_sample_to_simpler.
|
|
26
|
+
|
|
27
|
+
Accepts the plan-step params dict and unpacks into the real tool's kwargs.
|
|
28
|
+
"""
|
|
29
|
+
from ..tools.analyzer import load_sample_to_simpler
|
|
30
|
+
return await load_sample_to_simpler(
|
|
31
|
+
ctx,
|
|
32
|
+
track_index=int(params["track_index"]),
|
|
33
|
+
file_path=str(params["file_path"]),
|
|
34
|
+
device_index=int(params.get("device_index", 0)),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def build_mcp_dispatch_registry() -> dict[str, Callable]:
|
|
39
|
+
"""Return the canonical registry of MCP-only tools for plan execution.
|
|
40
|
+
|
|
41
|
+
Callers (typically the server lifespan init) should call this once and
|
|
42
|
+
pass the registry to execute_plan_steps_async via the mcp_registry kwarg.
|
|
43
|
+
"""
|
|
44
|
+
return {
|
|
45
|
+
"load_sample_to_simpler": _load_sample_to_simpler,
|
|
46
|
+
}
|
|
@@ -80,7 +80,10 @@ BRIDGE_COMMANDS: frozenset[str] = frozenset({
|
|
|
80
80
|
"remove_warp_marker", "capture_audio", "capture_stop",
|
|
81
81
|
"check_flucoma", "scrub_clip", "stop_scrub", "get_display_values",
|
|
82
82
|
"get_plugin_params", "map_plugin_param", "get_plugin_presets",
|
|
83
|
-
|
|
83
|
+
# NOTE: load_sample_to_simpler used to live here, but it's actually an
|
|
84
|
+
# async Python MCP tool in mcp_server/tools/analyzer.py, not a bridge
|
|
85
|
+
# command. It has no case in livepilot_bridge.js and no @register handler
|
|
86
|
+
# in remote_script. See mcp_server/runtime/execution_router.MCP_TOOLS.
|
|
84
87
|
})
|
|
85
88
|
|
|
86
89
|
# Combined: all valid send_command targets
|
|
@@ -110,11 +110,16 @@ def get_session_kernel(
|
|
|
110
110
|
memory_ok=True,
|
|
111
111
|
)
|
|
112
112
|
|
|
113
|
-
# Optional subcomponents — degrade gracefully
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
# Optional subcomponents — degrade gracefully, but reach into the SAME
|
|
114
|
+
# session-scoped stores the public memory tools read/write via
|
|
115
|
+
# ctx.lifespan_context.setdefault(...). Creating fresh stores here meant
|
|
116
|
+
# users who recorded anti-preferences, session memory, or taste signals
|
|
117
|
+
# through the MCP tools always saw an empty kernel.
|
|
118
|
+
ledger_summary: dict = {}
|
|
119
|
+
taste_graph: dict = {}
|
|
120
|
+
anti_prefs: list = []
|
|
121
|
+
session_mem: list = []
|
|
122
|
+
kernel_warnings: list[str] = []
|
|
118
123
|
|
|
119
124
|
try:
|
|
120
125
|
from .action_ledger import SessionLedger
|
|
@@ -122,38 +127,42 @@ def get_session_kernel(
|
|
|
122
127
|
if ledger is None:
|
|
123
128
|
ledger = SessionLedger()
|
|
124
129
|
ctx.lifespan_context["action_ledger"] = ledger
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
130
|
+
recent = ledger.get_recent_moves(limit=10)
|
|
131
|
+
ledger_summary = {
|
|
132
|
+
"total_moves": len(ledger._entries),
|
|
133
|
+
"memory_candidate_count": len(ledger.get_memory_candidates()),
|
|
134
|
+
"last_move": ledger.get_last_move().to_dict() if ledger.get_last_move() else None,
|
|
135
|
+
"recent_moves": [entry.to_dict() for entry in recent],
|
|
136
|
+
}
|
|
137
|
+
except Exception as e:
|
|
138
|
+
kernel_warnings.append(f"ledger_unavailable: {e}")
|
|
139
|
+
|
|
140
|
+
# Taste graph + anti-prefs — share stores via lifespan_context, use the
|
|
141
|
+
# canonical build_taste_graph() so consumers see dimension_weights shape.
|
|
136
142
|
try:
|
|
143
|
+
from ..memory.taste_graph import build_taste_graph
|
|
137
144
|
from ..memory.taste_memory import TasteMemoryStore
|
|
138
|
-
taste_store = TasteMemoryStore()
|
|
139
|
-
taste_graph = {d.name: d.to_dict() for d in taste_store._dims.values()
|
|
140
|
-
if d.evidence_count > 0}
|
|
141
|
-
except Exception:
|
|
142
|
-
pass
|
|
143
|
-
|
|
144
|
-
try:
|
|
145
145
|
from ..memory.anti_memory import AntiMemoryStore
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
from ..persistence.taste_store import PersistentTasteStore
|
|
147
|
+
taste_store = ctx.lifespan_context.setdefault("taste_memory", TasteMemoryStore())
|
|
148
|
+
anti_store = ctx.lifespan_context.setdefault("anti_memory", AntiMemoryStore())
|
|
149
|
+
persistent = ctx.lifespan_context.setdefault("persistent_taste", PersistentTasteStore())
|
|
150
|
+
graph = build_taste_graph(
|
|
151
|
+
taste_store=taste_store,
|
|
152
|
+
anti_store=anti_store,
|
|
153
|
+
persistent_store=persistent,
|
|
154
|
+
)
|
|
155
|
+
taste_graph = graph.to_dict()
|
|
156
|
+
anti_prefs = [p.to_dict() for p in anti_store.get_anti_preferences()]
|
|
157
|
+
except Exception as e:
|
|
158
|
+
kernel_warnings.append(f"taste_graph_unavailable: {e}")
|
|
150
159
|
|
|
151
160
|
try:
|
|
152
161
|
from ..memory.session_memory import SessionMemoryStore
|
|
153
|
-
mem_store = SessionMemoryStore()
|
|
154
|
-
session_mem = mem_store.
|
|
155
|
-
except Exception:
|
|
156
|
-
|
|
162
|
+
mem_store = ctx.lifespan_context.setdefault("session_memory", SessionMemoryStore())
|
|
163
|
+
session_mem = [entry.to_dict() for entry in mem_store.get_recent(limit=10)]
|
|
164
|
+
except Exception as e:
|
|
165
|
+
kernel_warnings.append(f"session_memory_unavailable: {e}")
|
|
157
166
|
|
|
158
167
|
kernel = build_session_kernel(
|
|
159
168
|
session_info=session_info,
|
|
@@ -167,4 +176,18 @@ def get_session_kernel(
|
|
|
167
176
|
anti_preferences=anti_prefs,
|
|
168
177
|
)
|
|
169
178
|
|
|
170
|
-
|
|
179
|
+
# Populate routing hints from conductor when request context is available
|
|
180
|
+
if request_text.strip():
|
|
181
|
+
try:
|
|
182
|
+
from ..tools._conductor import classify_request
|
|
183
|
+
plan = classify_request(request_text)
|
|
184
|
+
kernel.recommended_engines = [r.engine for r in plan.routes[:3]]
|
|
185
|
+
kernel.recommended_workflow = plan.workflow_mode
|
|
186
|
+
except Exception as e:
|
|
187
|
+
kernel_warnings.append(f"conductor_routing_unavailable: {e}")
|
|
188
|
+
|
|
189
|
+
result_dict = kernel.to_dict()
|
|
190
|
+
if kernel_warnings:
|
|
191
|
+
# Additive — callers can ignore; debug-mode introspection benefits.
|
|
192
|
+
result_dict["warnings"] = kernel_warnings
|
|
193
|
+
return result_dict
|
|
@@ -14,8 +14,8 @@ SAMPLE_CHOP_RHYTHM = SemanticMove(
|
|
|
14
14
|
targets={"groove": 0.5, "novelty": 0.3, "punch": 0.2},
|
|
15
15
|
protect={"clarity": 0.6, "coherence": 0.5},
|
|
16
16
|
risk_level="medium",
|
|
17
|
-
|
|
18
|
-
{"tool": "load_sample_to_simpler", "params": {"description": "Load sample into Simpler for slicing"}, "description": "Load into Simpler", "backend": "
|
|
17
|
+
plan_template=[
|
|
18
|
+
{"tool": "load_sample_to_simpler", "params": {"description": "Load sample into Simpler for slicing"}, "description": "Load into Simpler", "backend": "mcp_tool"},
|
|
19
19
|
{"tool": "set_simpler_playback_mode", "params": {"mode": "slice", "description": "Switch to slice mode for rhythmic chopping"}, "description": "Enable slice mode", "backend": "remote_command"},
|
|
20
20
|
{"tool": "crop_simpler", "params": {"description": "Crop to rhythmically relevant region"}, "description": "Crop to useful region", "backend": "bridge_command"},
|
|
21
21
|
],
|
|
@@ -32,8 +32,8 @@ SAMPLE_TEXTURE_LAYER = SemanticMove(
|
|
|
32
32
|
targets={"depth": 0.4, "motion": 0.3, "warmth": 0.3},
|
|
33
33
|
protect={"clarity": 0.7, "punch": 0.5},
|
|
34
34
|
risk_level="low",
|
|
35
|
-
|
|
36
|
-
{"tool": "load_sample_to_simpler", "params": {"description": "Load textural sample into Simpler"}, "description": "Load texture sample", "backend": "
|
|
35
|
+
plan_template=[
|
|
36
|
+
{"tool": "load_sample_to_simpler", "params": {"description": "Load textural sample into Simpler"}, "description": "Load texture sample", "backend": "mcp_tool"},
|
|
37
37
|
{"tool": "set_simpler_playback_mode", "params": {"mode": "classic", "description": "Classic mode for sustained texture playback"}, "description": "Classic playback", "backend": "remote_command"},
|
|
38
38
|
{"tool": "set_device_parameter", "params": {"description": "Lower filter cutoff to sit beneath main elements"}, "description": "Filter for background placement", "backend": "remote_command"},
|
|
39
39
|
{"tool": "set_track_send", "params": {"description": "Add reverb send for spatial depth"}, "description": "Reverb for depth", "backend": "remote_command"},
|
|
@@ -50,8 +50,8 @@ SAMPLE_VOCAL_GHOST = SemanticMove(
|
|
|
50
50
|
targets={"novelty": 0.4, "depth": 0.3, "motion": 0.3},
|
|
51
51
|
protect={"clarity": 0.5},
|
|
52
52
|
risk_level="medium",
|
|
53
|
-
|
|
54
|
-
{"tool": "load_sample_to_simpler", "params": {"description": "Load vocal sample into Simpler"}, "description": "Load vocal", "backend": "
|
|
53
|
+
plan_template=[
|
|
54
|
+
{"tool": "load_sample_to_simpler", "params": {"description": "Load vocal sample into Simpler"}, "description": "Load vocal", "backend": "mcp_tool"},
|
|
55
55
|
{"tool": "reverse_simpler", "params": {"description": "Reverse for ghostly character"}, "description": "Reverse vocal", "backend": "bridge_command"},
|
|
56
56
|
{"tool": "set_device_parameter", "params": {"description": "Detune -5 to -12 semitones for haunting pitch"}, "description": "Pitch down for ghost effect", "backend": "remote_command"},
|
|
57
57
|
{"tool": "set_track_send", "params": {"description": "Heavy reverb send 40-60% for wash"}, "description": "Reverb wash", "backend": "remote_command"},
|
|
@@ -68,9 +68,9 @@ SAMPLE_BREAK_LAYER = SemanticMove(
|
|
|
68
68
|
targets={"groove": 0.4, "punch": 0.3, "novelty": 0.3},
|
|
69
69
|
protect={"coherence": 0.6, "clarity": 0.5},
|
|
70
70
|
risk_level="medium",
|
|
71
|
-
|
|
71
|
+
plan_template=[
|
|
72
72
|
{"tool": "create_midi_track", "params": {"description": "New track for break layer"}, "description": "Create break track", "backend": "remote_command"},
|
|
73
|
-
{"tool": "load_sample_to_simpler", "params": {"description": "Load breakbeat into Simpler"}, "description": "Load break", "backend": "
|
|
73
|
+
{"tool": "load_sample_to_simpler", "params": {"description": "Load breakbeat into Simpler"}, "description": "Load break", "backend": "mcp_tool"},
|
|
74
74
|
{"tool": "set_simpler_playback_mode", "params": {"mode": "slice", "slice_by": "transient", "description": "Slice by transients for individual hits"}, "description": "Slice break by transients", "backend": "remote_command"},
|
|
75
75
|
{"tool": "set_track_volume", "params": {"description": "Set break layer volume below main drums"}, "description": "Balance break level", "backend": "remote_command"},
|
|
76
76
|
],
|
|
@@ -87,8 +87,8 @@ SAMPLE_RESAMPLE_DESTROY = SemanticMove(
|
|
|
87
87
|
targets={"novelty": 0.5, "motion": 0.3, "groove": 0.2},
|
|
88
88
|
protect={"coherence": 0.4},
|
|
89
89
|
risk_level="high",
|
|
90
|
-
|
|
91
|
-
{"tool": "load_sample_to_simpler", "params": {"description": "Load sample for destruction"}, "description": "Load source material", "backend": "
|
|
90
|
+
plan_template=[
|
|
91
|
+
{"tool": "load_sample_to_simpler", "params": {"description": "Load sample for destruction"}, "description": "Load source material", "backend": "mcp_tool"},
|
|
92
92
|
{"tool": "warp_simpler", "params": {"description": "Extreme warp settings for time-stretch artifacts"}, "description": "Warp for artifacts", "backend": "bridge_command"},
|
|
93
93
|
{"tool": "set_device_parameter", "params": {"description": "Add Redux or bitcrusher for lo-fi destruction"}, "description": "Bitcrush/reduce", "backend": "remote_command"},
|
|
94
94
|
{"tool": "set_device_parameter", "params": {"description": "Saturator drive to maximum for harmonic distortion"}, "description": "Saturate heavily", "backend": "remote_command"},
|
|
@@ -105,8 +105,8 @@ SAMPLE_ONE_SHOT_ACCENT = SemanticMove(
|
|
|
105
105
|
targets={"punch": 0.4, "groove": 0.3, "novelty": 0.3},
|
|
106
106
|
protect={"clarity": 0.6, "coherence": 0.5},
|
|
107
107
|
risk_level="low",
|
|
108
|
-
|
|
109
|
-
{"tool": "load_sample_to_simpler", "params": {"description": "Load one-shot into Simpler"}, "description": "Load one-shot", "backend": "
|
|
108
|
+
plan_template=[
|
|
109
|
+
{"tool": "load_sample_to_simpler", "params": {"description": "Load one-shot into Simpler"}, "description": "Load one-shot", "backend": "mcp_tool"},
|
|
110
110
|
{"tool": "set_simpler_playback_mode", "params": {"mode": "one_shot", "description": "One-shot mode for trigger playback"}, "description": "One-shot mode", "backend": "remote_command"},
|
|
111
111
|
{"tool": "crop_simpler", "params": {"description": "Tight crop around the transient"}, "description": "Crop to transient", "backend": "bridge_command"},
|
|
112
112
|
],
|