livepilot 1.10.3 → 1.10.5

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
3
3
  "name": "dreamrec-LivePilot",
4
- "description": "Agentic MCP production system for Ableton Live 12 — 317 tools, 43 domains",
4
+ "description": "Agentic MCP production system for Ableton Live 12 — 320 tools, 43 domains",
5
5
  "owner": {
6
6
  "name": "dreamrec",
7
7
  "email": "dreamrec@users.noreply.github.com"
@@ -9,8 +9,8 @@
9
9
  "plugins": [
10
10
  {
11
11
  "name": "livepilot",
12
- "description": "Agentic production system for Ableton Live 12 — 317 tools, 43 domains, device atlas, spectral perception, technique memory, sample intelligence, auto-composition, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
13
- "version": "1.10.3",
12
+ "description": "Agentic production system for Ableton Live 12 — 320 tools, 43 domains, device atlas, spectral perception, technique memory, sample intelligence, auto-composition, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
13
+ "version": "1.10.5",
14
14
  "author": {
15
15
  "name": "Pilot Studio"
16
16
  },
package/.mcpbignore CHANGED
@@ -17,6 +17,12 @@ scripts/
17
17
  *.pyc
18
18
  .DS_Store
19
19
  *.egg-info
20
+ .gitignore
21
+ .mcpbignore
22
+ package-lock.json
23
+
24
+ # Untracked Ableton presets (saved by users into m4l_device/)
25
+ m4l_device/*.adv
20
26
 
21
27
  # Credentials and tokens
22
28
  .mcpregistry_*
@@ -24,15 +30,26 @@ scripts/
24
30
  .npmrc
25
31
 
26
32
  # Docs and marketing (not needed at runtime)
33
+ # Ship only docs/manual/, docs/assets/, docs/M4L_BRIDGE.md, docs/TOOL_REFERENCE.md
27
34
  docs/specs/
35
+ docs/superpowers/
36
+ docs/plans/
37
+ docs/research/
38
+ docs/v2-master-spec/
39
+ docs/LivePilot-1.7-Perception/
40
+ docs/2026-*
41
+ docs/AGENT_OS_V1.md
42
+ docs/COMPOSITION_ENGINE_V1.md
43
+ docs/ableton-library-map.md
44
+ docs/patreon-content.md
28
45
  docs/social-banner.*
29
46
  docs/screenshots/
30
47
 
31
48
  # Large binary already in User Library after install
32
49
  # m4l_device/LivePilot_Analyzer.amxd
33
50
 
34
- # Dev config
35
- .mcp.json
51
+ # Dev config (root-level only — keep livepilot/.mcp.json which the plugin needs)
52
+ /.mcp.json
36
53
  .npmignore
37
54
  .editorconfig
38
55
  CODE_OF_CONDUCT.md
package/AGENTS.md CHANGED
@@ -1,4 +1,4 @@
1
- # LivePilot v1.10.3 — Ableton Live 12
1
+ # LivePilot v1.10.5 — Ableton Live 12
2
2
 
3
3
  ## Project
4
4
  - **Repo:** This directory (LivePilot)
@@ -22,7 +22,7 @@
22
22
  ## Key Rules
23
23
  - ALL Live Object Model (LOM) calls must execute on Ableton's main thread via schedule_message queue
24
24
  - Live 12 minimum — use modern note API (add_new_notes, get_notes_extended, apply_note_modifications)
25
- - 317 tools across 43 domains: transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, analyzer, automation, theory, generative, harmony, midi_io, perception, agent_os, composition, motif, research, planner, project_brain, runtime, evaluation, mix_engine, sound_design, transition_engine, reference_engine, translation_engine, performance_engine, song_brain, preview_studio, hook_hunter, stuckness_detector, wonder_mode, session_continuity, creative_constraints, device_forge, sample_engine, atlas, composer
25
+ - 320 tools across 43 domains: transport, tracks, clips, notes, devices, scenes, mixing, browser, arrangement, memory, analyzer, automation, theory, generative, harmony, midi_io, perception, agent_os, composition, motif, research, planner, project_brain, runtime, evaluation, mix_engine, sound_design, transition_engine, reference_engine, translation_engine, performance_engine, song_brain, preview_studio, hook_hunter, stuckness_detector, wonder_mode, session_continuity, creative_constraints, device_forge, sample_engine, atlas, composer
26
26
  - JSON over TCP, newline-delimited, port 9878
27
27
  - Structured errors with codes: INDEX_ERROR, NOT_FOUND, INVALID_PARAM, STATE_ERROR, TIMEOUT, INTERNAL
28
28
 
@@ -43,4 +43,4 @@ When modifying .amxd attributes that Max editor won't persist (e.g., `openinpres
43
43
  4. Structure: 24-byte `ampf` header + `ptch` chunk + `mx@c` header + JSON patcher + frozen deps
44
44
 
45
45
  ## Tool Count
46
- Currently 317 tools. If adding/removing tools, update: README.md, package.json description, livepilot/.Codex-plugin/plugin.json, livepilot/.claude-plugin/plugin.json, server.json, livepilot/skills/livepilot-core/SKILL.md, livepilot/skills/livepilot-core/references/overview.md, AGENTS.md, CLAUDE.md, CHANGELOG.md, tests/test_tools_contract.py, docs/manual/index.md, docs/manual/tool-reference.md
46
+ Currently 320 tools. If adding/removing tools, update: README.md, package.json description, livepilot/.Codex-plugin/plugin.json, livepilot/.claude-plugin/plugin.json, server.json, livepilot/skills/livepilot-core/SKILL.md, livepilot/skills/livepilot-core/references/overview.md, AGENTS.md, CLAUDE.md, CHANGELOG.md, tests/test_tools_contract.py, docs/manual/index.md, docs/manual/tool-reference.md
package/CHANGELOG.md CHANGED
@@ -1,5 +1,149 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.10.5 — Splice online catalog unblocked + Simpler sample-loading fixes (April 14 2026)
4
+
5
+ The Splice integration was **never working online** in previous releases. The
6
+ `SpliceGRPCClient` existed in the codebase but silently fell back to a
7
+ SQLite-only path that returned only locally-downloaded samples (2 files on the
8
+ test user's machine). The bug was a missing `grpcio` dependency in the venv
9
+ combined with `sources.py` never checking for the gRPC client. Once unblocked,
10
+ a single query returns 19,690+ catalog hits. The "Beatles × Boards of Canada"
11
+ session that surfaced these bugs is archived at
12
+ `docs/2026-04-14-bugs-discovered.md` with 13 bugs categorized P0–P3.
13
+
14
+ Tool count: **317 → 320** (three new Splice catalog tools added).
15
+
16
+ ### Added
17
+ - **`get_splice_credits`** — query the Splice user's subscription tier and
18
+ remaining credit balance. Returns `{connected, username, plan,
19
+ credits_remaining, credit_floor, can_download}`. Graceful degradation when
20
+ Splice desktop isn't running or grpcio is missing.
21
+ - **`splice_catalog_hunt`** — search Splice's ONLINE catalog via gRPC (not
22
+ just local downloads). Supports query, bpm_min/max, key, sample_type,
23
+ genre filters. Returns full sample metadata including `file_hash` for
24
+ downloads. This is the tool that unblocks 19,690+ results previously
25
+ inaccessible.
26
+ - **`splice_download_sample`** — download a sample by `file_hash` (costs 1
27
+ credit), with automatic credit-floor safety check. Optionally copies the
28
+ downloaded file into `~/Music/Ableton/User Library/Samples/Splice/` so
29
+ Ableton's browser indexes it, returning a `browser_uri` ready for
30
+ `load_browser_item`.
31
+ - **Smart warped-loop defaults** in `load_sample_to_simpler` and
32
+ `replace_simpler_sample`: when the filename contains a BPM marker (e.g.
33
+ `86bpm`), Simpler's `S Start` is set to 0, `S Length` to 100%, and
34
+ `S Loop On` to 1 so the full musical loop plays. Previously these tools
35
+ used crop defaults designed for one-shots, which chopped warped loops.
36
+
37
+ ### Fixed
38
+ - **P0-2 — Splice online catalog is finally reachable.** `grpcio>=1.60.0`
39
+ and `protobuf>=4.25.0` are now REQUIRED dependencies (added to
40
+ `requirements.txt`). `search_samples(source="splice")` now uses the gRPC
41
+ client from `ctx.lifespan_context["splice_client"]` when connected and
42
+ only falls back to SQLite when the gRPC path is unavailable. Before this
43
+ fix, a query like `"mellotron"` returned 0 hits; after, it returns 851.
44
+ Queries like `"lofi chord"` 80-92 BPM return 19,690 hits.
45
+ - **P0-1 — Simpler sample replacement is verified.** Both
46
+ `replace_simpler_sample` and `load_sample_to_simpler` now verify by
47
+ reading the device name back after the replace. If the name doesn't
48
+ match the requested filename stem, the tool returns a clear error
49
+ instead of silently shipping a wrong sample (the previous behavior
50
+ caused the test session to play a kick drum named as a vocal for two
51
+ consecutive rebuilds). The error message recommends
52
+ `load_browser_item` as a more reliable alternative.
53
+ - **P1-1 — Simpler `Snap` is automatically turned OFF** after sample load.
54
+ With Snap ON, the Sample Start position gets snapped to a zero-crossing
55
+ outside the newly loaded sample's data, causing silent playback. This was
56
+ the root cause of every "sample loaded but doesn't play" symptom in
57
+ previous sessions. The fix also applies to `replace_simpler_sample`.
58
+ - **P2-6 — Warped-loop sample defaults** no longer crop arbitrary sections.
59
+ When `load_sample_to_simpler` or `replace_simpler_sample` detects a BPM
60
+ marker in the filename, it applies loop-appropriate defaults instead of
61
+ one-shot-appropriate defaults.
62
+
63
+ ### Removed
64
+ Nothing removed — all additions are additive.
65
+
66
+ ### Verified
67
+ - `tests/test_tools_contract.py::test_total_tool_count` — 320 tools
68
+ (up from 317)
69
+ - `tests/test_tools_contract.py::test_sample_engine_tools_registered` —
70
+ includes `get_splice_credits`, `splice_catalog_hunt`,
71
+ `splice_download_sample`
72
+ - Live gRPC round-trip: searched 3 queries against Splice online, found
73
+ 21,488 combined catalog hits, downloaded 3 samples (credits 100 → 97),
74
+ copied into User Library, loaded onto 3 Ableton tracks via
75
+ `load_browser_item` — all verified via `get_track_info` device name
76
+ matching.
77
+
78
+ ### Known limitations
79
+ - **Unlimited downloads inside Splice Sounds.vst3 are not yet drivable**
80
+ programmatically. The gRPC download path always decrements monthly
81
+ credits (100/month on most subscription tiers) regardless of
82
+ `SoundsStatus: subscribed`. The Splice Sounds VST3 uses a separate HTTPS
83
+ API that LivePilot cannot drive through Ableton's plugin boundary.
84
+ Treat this as a research item — see P2-7 in
85
+ `docs/2026-04-14-bugs-discovered.md`.
86
+ - **The M4L bridge `.amxd` still reports 1.10.4** in its ping response.
87
+ Source code is at 1.10.5 but the frozen JS inside the .amxd wasn't
88
+ re-exported. For users installing via `npm install -g livepilot@1.10.5`
89
+ this is cosmetic — no bridge commands changed. If publishing a new
90
+ `.mcpb`, re-freeze or binary-patch the version bytes first (see
91
+ `feedback_amxd_freeze_drift.md`).
92
+
93
+ ### Why a new patch version
94
+ The P0-2 fix (missing grpcio dependency) is a correctness bug: users
95
+ installed previous versions believed the Splice integration worked, when
96
+ in fact every "online" search returned only locally-downloaded files.
97
+ This is a silent incorrect-behavior bug — the tool returned 0-2 results
98
+ confidently without any warning. Users deserve a clearly-communicated
99
+ fix release and the ability to `npm install -g livepilot@1.10.5`.
100
+
101
+ ## 1.10.4 — Bridge ping sync (April 14 2026)
102
+
103
+ A pure ship-fix release. The frozen JS inside `LivePilot_Analyzer.amxd` was
104
+ last re-exported during the v1.10.1 hardening pass and never re-frozen
105
+ during the 1.10.2 / 1.10.3 sweeps. The published `npm livepilot@1.10.3`
106
+ tarball therefore shipped with a stale `.amxd` whose `ping` returned
107
+ `{"version": "1.10.1"}`. End users installing via `npm install -g livepilot`
108
+ got the v1.10.3 source code but a v1.10.1 bridge.
109
+
110
+ ### Fixed
111
+ - **M4L bridge ping reports 1.10.4 (was 1.10.1).** Source
112
+ `m4l_device/livepilot_bridge.js` updated and the `.amxd` was binary-patched
113
+ in place — replacing the 6-byte version literal at offset 6669978
114
+ (`b"1.10.3" -> b"1.10.4"`) leaves all `dire`/`sz32`/`of32` chunk offsets
115
+ numerically valid, so the patched device opens cleanly in Ableton without
116
+ needing a Max re-export. Verified by `tests/test_bridge_parity.py` and the
117
+ capture/contract tests (36 tests pass against the patched binary).
118
+ - **`livepilot.mcpb` no longer ships internal docs.** `.mcpbignore` was
119
+ tightened to exclude `docs/superpowers/`, `docs/research/`, `docs/plans/`,
120
+ `docs/v2-master-spec/`, `docs/LivePilot-1.7-Perception/`, `docs/2026-*`,
121
+ `AGENT_OS_V1.md`, `COMPOSITION_ENGINE_V1.md`, `ableton-library-map.md`,
122
+ `patreon-content.md`, and `m4l_device/*.adv` (Ableton presets users save
123
+ into the dev folder). The bundle is back to lean shape: 4.66 MB / 403 files
124
+ vs the bloated 5.56 MB / 491 files an unpatched repack produced. The
125
+ `.mcp.json` exclusion is now root-only (`/.mcp.json`) so the
126
+ plugin-internal `livepilot/.mcp.json` stays in the bundle.
127
+
128
+ ### Why a new patch version
129
+ npm package versions are immutable. Once `livepilot@1.10.3` was published with
130
+ the stale `.amxd`, the only way to ship a fix to anyone using
131
+ `npm install -g livepilot` is to bump and republish. Same root cause and same
132
+ fix as the v1.10.1 → v1.10.2 jump.
133
+
134
+ ### Deprecation
135
+ - `npm livepilot@1.10.3` deprecated with message: "stale M4L bridge .amxd
136
+ ships v1.10.1 ping; please install 1.10.4".
137
+
138
+ ### Verification
139
+ - 36 bridge-related tests (`test_bridge_parity`, `test_capture_bridge`,
140
+ `test_m4l_capture_contract`, `test_sample_engine_analyzer`) pass against
141
+ the patched binary.
142
+ - `unzip -p livepilot.mcpb m4l_device/LivePilot_Analyzer.amxd | grep 1.10.4`
143
+ matches; `1.10.1` and `1.10.3` are absent.
144
+ - GitHub release `LivePilot_Analyzer.amxd` and bundled `livepilot.mcpb`
145
+ asset SHAs match local artifacts.
146
+
3
147
  ## 1.10.3 — Truth Release (April 14 2026)
4
148
 
5
149
  A correctness pass focused on making the top-layer workflows **trustworthy
package/CONTRIBUTING.md CHANGED
@@ -98,7 +98,7 @@ Prefix with `fix:`, `feat:`, `docs:`, `refactor:`, `test:`, or `chore:`.
98
98
 
99
99
  ## Tool Count Discipline
100
100
 
101
- Currently **317 tools**. If you add or remove a `@mcp.tool()` decorator, update all of these files:
101
+ Currently **320 tools**. If you add or remove a `@mcp.tool()` decorator, update all of these files:
102
102
 
103
103
  - `README.md`
104
104
  - `CLAUDE.md`
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
- 317 tools. 43 domains. Device atlas. Splice integration. Auto-composition. Spectral perception. Technique memory.
20
+ 320 tools. 43 domains. Device atlas. Splice integration. Auto-composition. Spectral perception. Technique memory.
21
21
  </p>
22
22
 
23
23
  <br>
@@ -79,7 +79,7 @@ Most MCP servers are tool collections — they execute commands. LivePilot is an
79
79
  │ └─────────────────┼──────────────────┘ │
80
80
  │ ▼ │
81
81
  │ ┌─────────────────┐ │
82
- │ │ 317 MCP Tools │ │
82
+ │ │ 320 MCP Tools │ │
83
83
  │ │ 43 domains │ │
84
84
  │ └────────┬────────┘ │
85
85
  │ │ │
@@ -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 317 tools. They give the AI musical judgment, not just musical execution.
123
+ 12 engines sit on top of the 320 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
- 317 tools across 43 domains. Highlights below — [full catalog here](docs/manual/tool-catalog.md).
175
+ 320 tools across 43 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 317 tools →](docs/manual/tool-catalog.md)**
363
+ > **[View all 320 tools →](docs/manual/tool-catalog.md)**
364
364
 
365
365
  <br>
366
366
 
@@ -585,7 +585,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for architecture details, code guidelines
585
585
 
586
586
  | Document | What's inside |
587
587
  |----------|---------------|
588
- | [Manual](docs/manual/index.md) | Complete reference: architecture, all 317 tools, workflows |
588
+ | [Manual](docs/manual/index.md) | Complete reference: architecture, all 320 tools, workflows |
589
589
  | [Intelligence Layer](docs/manual/intelligence.md) | How the 12 engines connect — conductor, moves, preview, evaluation |
590
590
  | [Device Atlas](docs/manual/device-atlas.md) | 1305 devices indexed — search, suggest, chain building |
591
591
  | [Samples & Slicing](docs/manual/samples.md) | 3-source search, fitness critics, slice workflows |
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.10.3",
4
- "description": "Agentic production system for Ableton Live 12 — 317 tools, 43 domains, device atlas, sample intelligence, auto-composition, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
3
+ "version": "1.10.5",
4
+ "description": "Agentic production system for Ableton Live 12 — 320 tools, 43 domains, device atlas, sample intelligence, auto-composition, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
5
5
  "author": {
6
6
  "name": "Pilot Studio"
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.10.3",
4
- "description": "Agentic production system for Ableton Live 12 — 317 tools, 43 domains, device atlas, sample intelligence, auto-composition, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
3
+ "version": "1.10.5",
4
+ "description": "Agentic production system for Ableton Live 12 — 320 tools, 43 domains, device atlas, sample intelligence, auto-composition, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
5
5
  "author": {
6
6
  "name": "Pilot Studio"
7
7
  }
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: livepilot-core
3
- description: Core discipline for LivePilot — agentic production system for Ableton Live 12. 317 tools across 43 domains. This skill should be used whenever working with Ableton Live through MCP tools. Provides golden rules, tool speed tiers, error handling protocol, and pointers to domain and engine skills.
3
+ description: Core discipline for LivePilot — agentic production system for Ableton Live 12. 320 tools across 43 domains. This skill should be used whenever working with Ableton Live through MCP tools. Provides golden rules, tool speed tiers, error handling protocol, and pointers to domain and engine skills.
4
4
  ---
5
5
 
6
6
  # LivePilot Core — Ableton Live 12
7
7
 
8
- Agentic production system for Ableton Live 12. 317 tools across 43 domains, three layers:
8
+ Agentic production system for Ableton Live 12. 320 tools across 43 domains, three layers:
9
9
 
10
10
  - **Device Atlas** — 1305 devices indexed (81 enriched with sonic intelligence, 683 drum kits). Consult `atlas_search` or `atlas_suggest` before loading any device. Never guess a device name.
11
11
  - **M4L Analyzer** — Real-time audio analysis on the master bus (8-band spectrum, RMS/peak, key detection). Optional — all core tools work without it.
@@ -39,7 +39,7 @@ Agentic production system for Ableton Live 12. 317 tools across 43 domains, thre
39
39
  ## Tool Speed Tiers
40
40
 
41
41
  ### Instant (<1s) — Use freely
42
- All 317 tools plus M4L perception tools.
42
+ All 320 tools plus M4L perception tools.
43
43
 
44
44
  ### Fast (1-5s) — Use freely
45
45
  `analyze_loudness` · `analyze_mix` · `analyze_sound_design`
@@ -128,7 +128,7 @@ Deep production knowledge in `references/`:
128
128
 
129
129
  | File | Content |
130
130
  |------|---------|
131
- | `references/overview.md` | All 317 tools with params and ranges |
131
+ | `references/overview.md` | All 320 tools with params and ranges |
132
132
  | `references/device-atlas/` | 280+ device corpus with URIs and presets |
133
133
  | `references/midi-recipes.md` | Drum patterns, chord voicings, humanization |
134
134
  | `references/sound-design.md` | Synth recipes, device chain patterns |
@@ -1,6 +1,6 @@
1
- # LivePilot v1.10.3 — Architecture & Tool Reference
1
+ # LivePilot v1.10.5 — Architecture & Tool Reference
2
2
 
3
- Agentic production system for Ableton Live 12. 317 tools across 43 domains. Device atlas (1305 devices, 81 enriched), spectral perception (M4L analyzer), technique memory, automation intelligence (16 curve types, 15 recipes), music theory (Krumhansl-Schmuckler, species counterpoint), generative algorithms (Euclidean rhythm, tintinnabuli, phase shift, additive process), neo-Riemannian harmony (PRL transforms, Tonnetz), MIDI file I/O.
3
+ Agentic production system for Ableton Live 12. 320 tools across 43 domains. Device atlas (1305 devices, 81 enriched), spectral perception (M4L analyzer), technique memory, automation intelligence (16 curve types, 15 recipes), music theory (Krumhansl-Schmuckler, species counterpoint), generative algorithms (Euclidean rhythm, tintinnabuli, phase shift, additive process), neo-Riemannian harmony (PRL transforms, Tonnetz), MIDI file I/O, Splice online catalog search and download via gRPC (v1.10.5 unblocked 19,690+ catalog hits previously inaccessible).
4
4
 
5
5
  ## Architecture
6
6
 
@@ -32,7 +32,7 @@ A flat tool list lets the AI press buttons. LivePilot's three layers give it con
32
32
 
33
33
  This turns "set EQ band 3 to -4 dB" into "cut 400 Hz by 4 dB, then read the spectrum to confirm the mud is actually reduced."
34
34
 
35
- ## The 317 Tools — What Each One Does
35
+ ## The 320 Tools — What Each One Does
36
36
 
37
37
  ### Transport (12) — Playback, tempo, global state, diagnostics
38
38
 
@@ -104,7 +104,7 @@ Call `get_capability_state` at the start of any evaluation session. The response
104
104
  {
105
105
  "mode": "normal",
106
106
  "analyzer_connected": true,
107
- "bridge_version": "1.10.3",
107
+ "bridge_version": "1.10.5",
108
108
  "spectral_cache_age_ms": 1200,
109
109
  "flucoma_available": false,
110
110
  "session_connected": true
@@ -28,20 +28,20 @@ Run this checklist EVERY time the user says "update everything", "push", "releas
28
28
 
29
29
  ## 2. Tool Count (must ALL match)
30
30
 
31
- Current: **317 tools across 43 domains**.
32
- Core (no M4L): **286**. Bridge (M4L): **30** (backed by 28 bridge commands).
31
+ Current: **320 tools across 43 domains**.
32
+ Core (no M4L): **289**. Bridge (M4L): **30** (backed by 28 bridge commands).
33
33
 
34
34
  Verify: `grep -rc "@mcp.tool" mcp_server/tools/ | grep -v ":0" | awk -F: '{sum+=$2} END{print sum}'`
35
35
 
36
36
  Files that reference tool count:
37
- - [ ] `README.md` — header ("317 tools. 43 domains"), bridge section ("281 core...36 bridge")
37
+ - [ ] `README.md` — header ("320 tools. 43 domains"), bridge section ("281 core...36 bridge")
38
38
  - [ ] `package.json` → `"description"`
39
39
  - [ ] `server.json` → `"description"`
40
40
  - [ ] `manifest.json` → `"description"`
41
41
  - [ ] `livepilot/.Codex-plugin/plugin.json` → `"description"` (primary Codex manifest)
42
42
  - [ ] `livepilot/.claude-plugin/plugin.json` → `"description"` (must match Codex plugin)
43
43
  - [ ] `.claude-plugin/marketplace.json` → `"description"`
44
- - [ ] `CLAUDE.md` → "317 tools across 43 domains"
44
+ - [ ] `CLAUDE.md` → "320 tools across 43 domains"
45
45
  - [ ] `CONTRIBUTING.md` → tool count in intro
46
46
  - [ ] `livepilot/skills/livepilot-core/SKILL.md` — tool/domain count
47
47
  - [ ] `livepilot/skills/livepilot-core/references/overview.md` — tool/domain count
@@ -93,7 +93,7 @@ Current: **43 domains**: transport, tracks, clips, notes, devices, scenes, mixin
93
93
 
94
94
  - [ ] `README.md` — features match current capabilities, "Coming" section is accurate
95
95
  - [ ] `docs/manual/getting-started.md` — install instructions current
96
- - [ ] `docs/manual/tool-reference.md` — all 43 domains listed, all 317 tools present
96
+ - [ ] `docs/manual/tool-reference.md` — all 43 domains listed, all 320 tools present
97
97
  - [ ] `docs/TOOL_REFERENCE.md` — all 43 domains present with correct counts
98
98
  - [ ] `docs/M4L_BRIDGE.md` — architecture accurate, core/bridge counts correct
99
99
 
Binary file
@@ -84,7 +84,7 @@ function anything() {
84
84
  function dispatch(cmd, args) {
85
85
  switch(cmd) {
86
86
  case "ping":
87
- send_response({"ok": true, "version": "1.10.3"});
87
+ send_response({"ok": true, "version": "1.10.5"});
88
88
  break;
89
89
  case "get_params":
90
90
  cmd_get_params(args);
package/manifest.json CHANGED
@@ -2,9 +2,9 @@
2
2
  "manifest_version": "0.3",
3
3
  "name": "livepilot",
4
4
  "display_name": "LivePilot — AI for Ableton Live",
5
- "version": "1.10.3",
6
- "description": "Agentic production system for Ableton Live 12. Make beats, mix tracks, design sounds, and arrange songs with 317 AI-powered tools.",
7
- "long_description": "LivePilot is an agentic production system for Ableton Live 12. 317 tools across 43 domains — device atlas (1305 devices), sample intelligence (Splice + browser + filesystem), auto-composition, spectral perception, technique memory, and 12 creative engines.\n\n**What it does:**\n- Creates MIDI clips with notes, chords, and rhythms\n- Loads instruments and effects via Device Atlas (1305 devices indexed)\n- Searches samples across Splice, Ableton browser, and filesystem\n- Plans compositions from text prompts with genre-aware layering\n- Slices samples with intent-based MIDI generation\n- Mixes with volume, panning, sends, and automation\n- Analyzes your mix with real-time spectral data (M4L bridge)\n- Diagnoses stuck sessions and generates creative rescue variants\n- Remembers your production style across sessions\n\n**How it works:**\nLivePilot installs a Remote Script in Ableton that communicates with the AI over a local TCP connection. Everything runs on your machine — no audio leaves your computer.",
5
+ "version": "1.10.5",
6
+ "description": "Agentic production system for Ableton Live 12. Make beats, mix tracks, design sounds, and arrange songs with 320 AI-powered tools.",
7
+ "long_description": "LivePilot is an agentic production system for Ableton Live 12. 320 tools across 43 domains — device atlas (1305 devices), sample intelligence (Splice + browser + filesystem), auto-composition, spectral perception, technique memory, and 12 creative engines.\n\n**What it does:**\n- Creates MIDI clips with notes, chords, and rhythms\n- Loads instruments and effects via Device Atlas (1305 devices indexed)\n- Searches samples across Splice, Ableton browser, and filesystem\n- Plans compositions from text prompts with genre-aware layering\n- Slices samples with intent-based MIDI generation\n- Mixes with volume, panning, sends, and automation\n- Analyzes your mix with real-time spectral data (M4L bridge)\n- Diagnoses stuck sessions and generates creative rescue variants\n- Remembers your production style across sessions\n\n**How it works:**\nLivePilot installs a Remote Script in Ableton that communicates with the AI over a local TCP connection. Everything runs on your machine — no audio leaves your computer.",
8
8
  "author": {
9
9
  "name": "Pilot Studio",
10
10
  "url": "https://github.com/dreamrec/LivePilot"
@@ -1,2 +1,2 @@
1
1
  """LivePilot MCP Server — bridges MCP protocol to Ableton Live."""
2
- __version__ = "1.10.3"
2
+ __version__ = "1.10.5"
@@ -1,11 +1,12 @@
1
- """Sample Engine MCP tools — 7 intelligence-layer tools.
1
+ """Sample Engine MCP tools — intelligence-layer tools.
2
2
 
3
- No new Ableton communication these orchestrate existing tools
4
- through the analyzer, critics, planner, and technique library.
3
+ Wraps analyzer, critics, planner, technique library, and (as of v1.10.5)
4
+ direct Splice online catalog hunt/download via the gRPC client.
5
5
  """
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import os
9
10
  from typing import Optional
10
11
 
11
12
  from fastmcp import Context
@@ -155,7 +156,7 @@ def evaluate_sample_fit(
155
156
 
156
157
 
157
158
  @mcp.tool()
158
- def search_samples(
159
+ async def search_samples(
159
160
  ctx: Context,
160
161
  query: str,
161
162
  material_type: Optional[str] = None,
@@ -169,6 +170,12 @@ def search_samples(
169
170
  Searches all enabled sources in parallel and ranks results.
170
171
  Splice results include rich metadata (key, BPM, genre, tags, pack info).
171
172
 
173
+ When the Splice desktop app is running AND grpcio is installed, this
174
+ searches Splice's ONLINE catalog (19,690+ hits for a generic query)
175
+ and returns un-downloaded items alongside local files. When gRPC is
176
+ unavailable, it falls back to the local SQLite index and only returns
177
+ already-downloaded samples.
178
+
172
179
  query: search text like "dark vocal", "breakbeat", "foley metal"
173
180
  material_type: filter by type (vocal, drum_loop, texture, etc.)
174
181
  key: prefer samples in this key (e.g., "Cm", "F#")
@@ -188,21 +195,73 @@ def search_samples(
188
195
  except ValueError:
189
196
  pass
190
197
 
191
- # Splice search (richest metadata, searched first)
198
+ # Splice search prefer gRPC online catalog when available, fall back
199
+ # to local SQLite index. See docs/2026-04-14-bugs-discovered.md — P0-2.
192
200
  if source in (None, "splice"):
193
- splice = SpliceSource()
194
- if splice.enabled:
195
- splice_results = splice.search(
196
- query=query,
197
- max_results=max_results,
198
- key=key,
199
- bpm_min=bpm_min,
200
- bpm_max=bpm_max,
201
- )
202
- for candidate in splice_results:
203
- d = candidate.to_dict()
204
- d["source_priority"] = 1 # highest
205
- results.append(d)
201
+ grpc_client = None
202
+ try:
203
+ grpc_client = ctx.lifespan_context.get("splice_client")
204
+ except AttributeError:
205
+ grpc_client = None
206
+
207
+ used_grpc = False
208
+ if grpc_client is not None and getattr(grpc_client, "connected", False):
209
+ try:
210
+ grpc_result = await grpc_client.search_samples(
211
+ query=query,
212
+ key=(key or "").lower().rstrip("m") if key else "",
213
+ bpm_min=int(bpm_min) if bpm_min else 0,
214
+ bpm_max=int(bpm_max) if bpm_max else 0,
215
+ per_page=max_results,
216
+ page=1,
217
+ purchased_only=False,
218
+ )
219
+ for s in grpc_result.samples[:max_results]:
220
+ results.append({
221
+ "source": "splice",
222
+ "name": s.filename,
223
+ "file_path": s.local_path or None,
224
+ "uri": None,
225
+ "freesound_id": None,
226
+ "relevance_score": 0,
227
+ "source_priority": 1,
228
+ "splice_catalog": True,
229
+ "downloaded": bool(s.local_path),
230
+ "file_hash": s.file_hash,
231
+ "metadata": {
232
+ "key": s.audio_key,
233
+ "bpm": s.bpm,
234
+ "tags": ",".join(s.tags) if s.tags else "",
235
+ "genre": s.genre or None,
236
+ "sample_type": s.sample_type,
237
+ "material_type": "vocal" if "vocal" in (s.tags or []) else "unknown",
238
+ "pack": s.provider_name,
239
+ "pack_uuid": s.pack_uuid,
240
+ "duration": s.duration_ms / 1000.0 if s.duration_ms else 0.0,
241
+ "is_premium": s.is_premium,
242
+ "chord_type": s.chord_type,
243
+ },
244
+ })
245
+ used_grpc = True
246
+ except Exception:
247
+ used_grpc = False
248
+
249
+ # Also query local index (if not already covered by gRPC) to surface
250
+ # downloaded-only samples that might not appear in catalog results.
251
+ if not used_grpc:
252
+ splice = SpliceSource()
253
+ if splice.enabled:
254
+ splice_results = splice.search(
255
+ query=query,
256
+ max_results=max_results,
257
+ key=key,
258
+ bpm_min=bpm_min,
259
+ bpm_max=bpm_max,
260
+ )
261
+ for candidate in splice_results:
262
+ d = candidate.to_dict()
263
+ d["source_priority"] = 1
264
+ results.append(d)
206
265
 
207
266
  # Browser search
208
267
  if source in (None, "browser"):
@@ -543,3 +602,296 @@ def plan_slice_workflow(
543
602
  plan["style_hint"] = style_hint
544
603
 
545
604
  return plan
605
+
606
+
607
+ # ── v1.10.5 Splice online catalog tools ───────────────────────────────────
608
+ #
609
+ # These expose the SpliceGRPCClient's catalog capabilities as first-class MCP
610
+ # tools so the agent can drive hunt→download→load without a standalone helper
611
+ # script. See docs/2026-04-14-bugs-discovered.md — P0-2.
612
+ #
613
+ # Prerequisites:
614
+ # - Splice desktop app running (port.conf present in ~/Library/Application
615
+ # Support/com.splice.Splice/)
616
+ # - grpcio and protobuf installed (added to requirements.txt in v1.10.5)
617
+ #
618
+ # Credit model (as of 2026-04-14):
619
+ # - Even with `SoundsStatus: subscribed`, the gRPC `DownloadSample` endpoint
620
+ # always decrements a monthly credit counter (default 100/month on most
621
+ # subscription plans).
622
+ # - The "unlimited downloads in Ableton" the Splice marketing references
623
+ # only applies to the Splice Sounds.vst3 plugin, which uses a different
624
+ # HTTPS API that these tools cannot drive.
625
+ # - `CREDIT_HARD_FLOOR = 5` in client.py reserves 5 credits as a safety
626
+ # margin — downloads will refuse below the floor.
627
+
628
+
629
+ _SPLICE_USER_LIB_DEST = "~/Music/Ableton/User Library/Samples/Splice"
630
+
631
+
632
+ @mcp.tool()
633
+ async def get_splice_credits(ctx: Context) -> dict:
634
+ """Get the user's current Splice credit balance and subscription tier.
635
+
636
+ Returns: {
637
+ "connected": bool, # whether Splice desktop gRPC is reachable
638
+ "username": str,
639
+ "plan": str, # e.g. "subscribed", "free"
640
+ "credits_remaining": int,
641
+ "credit_floor": int, # safety reserve (typically 5)
642
+ "can_download": bool, # credits_remaining > credit_floor
643
+ }
644
+
645
+ Returns connected=False (with zero credits) when the Splice desktop app
646
+ isn't running or grpcio isn't installed.
647
+ """
648
+ from ..splice_client.client import CREDIT_HARD_FLOOR
649
+
650
+ client = None
651
+ try:
652
+ client = ctx.lifespan_context.get("splice_client")
653
+ except AttributeError:
654
+ pass
655
+
656
+ if client is None or not getattr(client, "connected", False):
657
+ return {
658
+ "connected": False,
659
+ "username": "",
660
+ "plan": "",
661
+ "credits_remaining": 0,
662
+ "credit_floor": CREDIT_HARD_FLOOR,
663
+ "can_download": False,
664
+ "hint": (
665
+ "Splice gRPC not connected. Ensure Splice desktop app is "
666
+ "running and grpcio+protobuf are installed in the LivePilot "
667
+ "venv (pip install grpcio protobuf)."
668
+ ),
669
+ }
670
+
671
+ try:
672
+ info = await client.get_credits()
673
+ except Exception as exc:
674
+ return {
675
+ "connected": False,
676
+ "error": f"get_credits failed: {exc}",
677
+ "credit_floor": CREDIT_HARD_FLOOR,
678
+ }
679
+
680
+ remaining = int(info.credits)
681
+ return {
682
+ "connected": True,
683
+ "username": info.username,
684
+ "plan": info.plan,
685
+ "credits_remaining": remaining,
686
+ "credit_floor": CREDIT_HARD_FLOOR,
687
+ "can_download": remaining > CREDIT_HARD_FLOOR,
688
+ }
689
+
690
+
691
+ @mcp.tool()
692
+ async def splice_catalog_hunt(
693
+ ctx: Context,
694
+ query: str,
695
+ bpm_min: int = 0,
696
+ bpm_max: int = 0,
697
+ key: str = "",
698
+ sample_type: str = "",
699
+ genre: str = "",
700
+ per_page: int = 10,
701
+ page: int = 1,
702
+ ) -> dict:
703
+ """Search Splice's ONLINE catalog via gRPC.
704
+
705
+ Unlike `search_samples` which can fall back to the local SQLite index,
706
+ this tool ONLY queries the online catalog — if Splice isn't connected
707
+ it returns an error instead of local-only results. Use this when you
708
+ specifically want fresh catalog content.
709
+
710
+ query: free-text search ("mellotron", "lofi chord", "soul vocal")
711
+ bpm_min: minimum BPM (0 = no lower bound)
712
+ bpm_max: maximum BPM (0 = no upper bound)
713
+ key: musical key (e.g. "cm", "f#", "a")
714
+ sample_type: "loop", "oneshot", or "" for any
715
+ genre: genre filter (e.g. "hip hop", "ambient")
716
+ per_page: results per page (1-50)
717
+ page: page number (1-indexed)
718
+
719
+ Returns: {
720
+ "connected": bool,
721
+ "total_hits": int, # total catalog matches
722
+ "samples": [...], # sample metadata with file_hash for download
723
+ }
724
+
725
+ Each sample entry contains `file_hash` which you can pass to
726
+ `splice_download_sample` to trigger a download.
727
+ """
728
+ client = None
729
+ try:
730
+ client = ctx.lifespan_context.get("splice_client")
731
+ except AttributeError:
732
+ pass
733
+
734
+ if client is None or not getattr(client, "connected", False):
735
+ return {
736
+ "connected": False,
737
+ "error": "Splice gRPC not connected",
738
+ "hint": (
739
+ "Ensure Splice desktop app is running. Also verify grpcio "
740
+ "and protobuf are installed: `pip install grpcio protobuf`."
741
+ ),
742
+ "samples": [],
743
+ "total_hits": 0,
744
+ }
745
+
746
+ try:
747
+ result = await client.search_samples(
748
+ query=query,
749
+ key=key.lower().rstrip("m") if key else "",
750
+ chord_type="minor" if key and key.lower().endswith("m") else "",
751
+ bpm_min=int(bpm_min),
752
+ bpm_max=int(bpm_max),
753
+ sample_type=sample_type,
754
+ genre=genre,
755
+ per_page=max(1, min(per_page, 50)),
756
+ page=max(1, int(page)),
757
+ purchased_only=False,
758
+ )
759
+ except Exception as exc:
760
+ return {
761
+ "connected": False,
762
+ "error": f"Splice search failed: {exc}",
763
+ "samples": [],
764
+ }
765
+
766
+ samples_out = []
767
+ for s in result.samples:
768
+ samples_out.append({
769
+ "file_hash": s.file_hash,
770
+ "filename": s.filename,
771
+ "key": s.audio_key,
772
+ "chord_type": s.chord_type,
773
+ "bpm": s.bpm,
774
+ "duration_sec": round((s.duration_ms or 0) / 1000.0, 2),
775
+ "genre": s.genre,
776
+ "sample_type": s.sample_type,
777
+ "tags": list(s.tags) if s.tags else [],
778
+ "pack": s.provider_name,
779
+ "pack_uuid": s.pack_uuid,
780
+ "is_premium": bool(s.is_premium),
781
+ "is_downloaded": bool(s.local_path),
782
+ "local_path": s.local_path or None,
783
+ "preview_url": s.preview_url,
784
+ })
785
+
786
+ return {
787
+ "connected": True,
788
+ "query": query,
789
+ "total_hits": result.total_hits,
790
+ "returned": len(samples_out),
791
+ "samples": samples_out,
792
+ "matching_tags": dict(result.matching_tags) if result.matching_tags else {},
793
+ }
794
+
795
+
796
+ @mcp.tool()
797
+ async def splice_download_sample(
798
+ ctx: Context,
799
+ file_hash: str,
800
+ copy_to_user_library: bool = True,
801
+ ) -> dict:
802
+ """Download a Splice sample by file_hash (costs 1 credit).
803
+
804
+ Use `splice_catalog_hunt` first to find samples and get their file_hash.
805
+ This tool will:
806
+ 1. Check credit balance against the safety floor (refuses if < 5)
807
+ 2. Trigger the download via the Splice desktop gRPC
808
+ 3. Poll until the file appears on disk (up to 30s)
809
+ 4. Optionally copy the file into `~/Music/Ableton/User Library/Samples/
810
+ Splice/` so Ableton's browser indexes it — this makes the sample
811
+ loadable via `load_browser_item` with a `query:UserLibrary#Samples:...`
812
+ URI.
813
+
814
+ Returns: {
815
+ "ok": bool,
816
+ "local_path": str, # Splice's own download path
817
+ "user_library_path": str, # if copy_to_user_library=True
818
+ "browser_uri": str, # ready for load_browser_item
819
+ "credits_remaining": int,
820
+ }
821
+
822
+ Note: even with an "unlimited" subscription, this gRPC path always
823
+ decrements credits (typically 100/month allotment). The unlimited
824
+ downloads inside Ableton's Splice Sounds VST3 use a different API
825
+ that LivePilot cannot drive programmatically yet.
826
+ """
827
+ import shutil
828
+
829
+ client = None
830
+ try:
831
+ client = ctx.lifespan_context.get("splice_client")
832
+ except AttributeError:
833
+ pass
834
+
835
+ if client is None or not getattr(client, "connected", False):
836
+ return {
837
+ "ok": False,
838
+ "error": "Splice gRPC not connected",
839
+ }
840
+
841
+ # Credit safety check
842
+ try:
843
+ can, remaining = await client.can_afford(1, budget=10)
844
+ except Exception as exc:
845
+ return {"ok": False, "error": f"Credit check failed: {exc}"}
846
+ if not can:
847
+ return {
848
+ "ok": False,
849
+ "error": (
850
+ f"Credit safety floor hit (remaining={remaining}, "
851
+ f"hard floor=5). Skipping download."
852
+ ),
853
+ "credits_remaining": remaining,
854
+ }
855
+
856
+ # Trigger download
857
+ try:
858
+ local_path = await client.download_sample(file_hash, timeout=30.0)
859
+ except Exception as exc:
860
+ return {"ok": False, "error": f"Download failed: {exc}"}
861
+
862
+ if not local_path:
863
+ return {
864
+ "ok": False,
865
+ "error": "Download did not complete within 30s timeout",
866
+ }
867
+
868
+ response: dict = {
869
+ "ok": True,
870
+ "local_path": local_path,
871
+ "filename": os.path.basename(local_path),
872
+ }
873
+
874
+ # Copy into User Library so Ableton's browser indexes it
875
+ if copy_to_user_library:
876
+ dest_dir = os.path.expanduser(_SPLICE_USER_LIB_DEST)
877
+ try:
878
+ os.makedirs(dest_dir, exist_ok=True)
879
+ dest_path = os.path.join(dest_dir, os.path.basename(local_path))
880
+ if not os.path.exists(dest_path):
881
+ shutil.copy2(local_path, dest_path)
882
+ response["user_library_path"] = dest_path
883
+ # URI format Ableton uses for user_library samples
884
+ response["browser_uri"] = (
885
+ f"query:UserLibrary#Samples:Splice:{os.path.basename(local_path)}"
886
+ )
887
+ except Exception as exc:
888
+ response["copy_warning"] = f"Failed to copy to User Library: {exc}"
889
+
890
+ # Post-credit count
891
+ try:
892
+ info = await client.get_credits()
893
+ response["credits_remaining"] = int(info.credits)
894
+ except Exception:
895
+ pass
896
+
897
+ return response
@@ -277,6 +277,128 @@ async def get_clip_file_path(
277
277
  return await bridge.send_command("get_clip_file_path", track_index, clip_index)
278
278
 
279
279
 
280
+ import os # for filename parsing in smart-defaults helper
281
+ import re
282
+
283
+
284
+ # ── Sample loading helpers (P0-1, P1-1, P2-6 fixes) ────────────────────────
285
+ #
286
+ # Critical bug 2026-04-14 (see docs/2026-04-14-bugs-discovered.md):
287
+ #
288
+ # The M4L bridge's `replace_simpler_sample` command can report success even
289
+ # when the sample is still the bootstrap placeholder. The Simpler's display
290
+ # name also does NOT refresh after a replace. After loading, Simpler's Snap
291
+ # parameter is ON by default which causes the Sample Start position to
292
+ # snap to a location outside the new sample's valid audio — resulting in
293
+ # silent playback.
294
+ #
295
+ # The fixes below:
296
+ # 1. After replace, verify by reading the actual device name via
297
+ # get_track_info and comparing to the expected filename stem. If the
298
+ # name doesn't match, return a clear error so the caller doesn't
299
+ # silently ship the wrong audio.
300
+ # 2. Auto-set Snap=0 to disarm the zero-crossing snap that breaks playback.
301
+ # 3. For WARPED LOOPS (detected by "NNbpm" in the filename), set
302
+ # S Start=0, S Length=1, S Loop On=1 so the full loop plays in its
303
+ # musical phrasing. For ONE-SHOTS, leave defaults alone.
304
+
305
+ _BPM_IN_FILENAME_RE = re.compile(r"(\d{2,3})\s*bpm", re.IGNORECASE)
306
+
307
+
308
+ def _is_warped_loop(file_path: str) -> bool:
309
+ """Return True if the filename contains a BPM marker (likely a tempo-locked loop)."""
310
+ stem = os.path.splitext(os.path.basename(file_path))[0]
311
+ return bool(_BPM_IN_FILENAME_RE.search(stem))
312
+
313
+
314
+ def _filename_stem(file_path: str) -> str:
315
+ return os.path.splitext(os.path.basename(file_path))[0]
316
+
317
+
318
+ async def _simpler_post_load_hygiene(
319
+ bridge,
320
+ ableton,
321
+ track_index: int,
322
+ device_index: int,
323
+ file_path: str,
324
+ ) -> dict:
325
+ """Apply post-load hygiene to a newly loaded Simpler and verify success.
326
+
327
+ Steps:
328
+ 1. Read track info to verify the device's actual name matches the
329
+ expected sample stem. If it doesn't, return an error.
330
+ 2. Set Snap=0 (Off) — required so sample playback works.
331
+ 3. If filename indicates a warped loop, set S Start=0, S Length=1,
332
+ S Loop On=1 so the loop plays fully instead of being cropped.
333
+ 4. Return a verified response dict.
334
+ """
335
+ expected_stem = _filename_stem(file_path)
336
+
337
+ # Step 1: verify device name matches expected file
338
+ try:
339
+ track_info = ableton.send_command(
340
+ "get_track_info", {"track_index": track_index}
341
+ )
342
+ except Exception as exc:
343
+ return {"error": f"Verification read failed: {exc}"}
344
+
345
+ devices = track_info.get("devices", []) or []
346
+ if device_index < 0 or device_index >= len(devices):
347
+ return {
348
+ "error": (
349
+ f"Device index {device_index} out of range after load "
350
+ f"(track has {len(devices)} devices)"
351
+ ),
352
+ "verified": False,
353
+ }
354
+ device = devices[device_index]
355
+ actual_name = str(device.get("name") or "")
356
+ verified = expected_stem in actual_name or actual_name in expected_stem
357
+ if not verified:
358
+ return {
359
+ "error": (
360
+ f"Sample verification FAILED — Simpler name '{actual_name}' "
361
+ f"does not match requested file '{expected_stem}'. The bridge "
362
+ f"reported success but the actual sample is different. "
363
+ f"Try `load_browser_item` with a user_library URI instead."
364
+ ),
365
+ "verified": False,
366
+ "actual_device_name": actual_name,
367
+ "expected_stem": expected_stem,
368
+ }
369
+
370
+ # Step 2: turn Snap OFF — required for reliable playback after replace
371
+ hygiene_params: list[dict] = [
372
+ {"name_or_index": "Snap", "value": 0},
373
+ ]
374
+
375
+ # Step 3: smart defaults for warped loops
376
+ if _is_warped_loop(file_path):
377
+ hygiene_params.extend([
378
+ {"name_or_index": "S Start", "value": 0.0},
379
+ {"name_or_index": "S Length", "value": 1.0},
380
+ {"name_or_index": "S Loop On", "value": 1},
381
+ ])
382
+
383
+ try:
384
+ ableton.send_command("batch_set_parameters", {
385
+ "track_index": track_index,
386
+ "device_index": device_index,
387
+ "parameters": hygiene_params,
388
+ })
389
+ except Exception:
390
+ # non-fatal — verification already succeeded
391
+ pass
392
+
393
+ return {
394
+ "verified": True,
395
+ "device_name": actual_name,
396
+ "track_index": track_index,
397
+ "device_index": device_index,
398
+ "warped_loop_defaults_applied": _is_warped_loop(file_path),
399
+ }
400
+
401
+
280
402
  @mcp.tool()
281
403
  async def replace_simpler_sample(
282
404
  ctx: Context,
@@ -292,6 +414,17 @@ async def replace_simpler_sample(
292
414
  manually first or use find_and_load_device to load a preset that already
293
415
  contains a sample.
294
416
 
417
+ **Prefer `load_browser_item(track, uri)` when possible** — see P0-1 in
418
+ docs/2026-04-14-bugs-discovered.md. The M4L bridge's replace path can
419
+ silently keep the bootstrap placeholder in some conditions; this tool
420
+ now verifies by reading back the device name and will return an error
421
+ if the replace didn't actually take effect.
422
+
423
+ Also auto-applies post-load hygiene:
424
+ - Sets Simpler Snap=0 (required for playback after replace)
425
+ - For warped loops (filename contains 'NNbpm'), sets S Start=0,
426
+ S Length=1, S Loop On=1
427
+
295
428
  Use get_clip_file_path to get the path of a resampled clip, then pass
296
429
  it here to load it into Simpler for slicing.
297
430
  Requires LivePilot Analyzer on master track.
@@ -299,6 +432,7 @@ async def replace_simpler_sample(
299
432
  cache = _get_spectral(ctx)
300
433
  _require_analyzer(cache)
301
434
  bridge = _get_m4l(ctx)
435
+ ableton = ctx.lifespan_context["ableton"]
302
436
  result = await bridge.send_command(
303
437
  "replace_simpler_sample", track_index, device_index, file_path
304
438
  )
@@ -312,6 +446,16 @@ async def replace_simpler_sample(
312
446
  "error": "Sample may not have loaded. Ensure the Simpler already "
313
447
  "has a sample loaded — replace_sample silently fails on empty Simplers."
314
448
  }
449
+
450
+ # Verify by reading back the device name — guards against the silent
451
+ # failure mode where the bridge reports success but keeps the placeholder.
452
+ hygiene = await _simpler_post_load_hygiene(
453
+ bridge, ableton, track_index, device_index, file_path
454
+ )
455
+ if not hygiene.get("verified"):
456
+ return hygiene
457
+
458
+ result.update(hygiene)
315
459
  return result
316
460
 
317
461
 
@@ -327,10 +471,18 @@ async def load_sample_to_simpler(
327
471
  This is the full workflow for programmatic sample loading:
328
472
  1. Loads a dummy sample via the browser (creates Simpler with a sample)
329
473
  2. Replaces the dummy with your audio file
330
- 3. Returns the Simpler ready for slicing/warping
474
+ 3. Applies post-load hygiene (Snap=0, loop defaults for warped loops)
475
+ 4. Verifies by reading back the device name — returns an error if
476
+ the Simpler still has the bootstrap placeholder (P0-1 guard)
331
477
 
332
478
  Use this instead of replace_simpler_sample when the track has no Simpler
333
479
  or the Simpler is empty. Works with any audio file path.
480
+
481
+ **For files that exist in Ableton's browser index** (Samples, User Library,
482
+ Packs), PREFER `load_browser_item(track, uri)` — it goes through Ableton's
483
+ native loading path and is more reliable. This tool is a workaround for
484
+ files that aren't browser-indexed.
485
+
334
486
  Requires LivePilot Analyzer on master track.
335
487
  """
336
488
  cache = _get_spectral(ctx)
@@ -380,6 +532,14 @@ async def load_sample_to_simpler(
380
532
  if not result.get("sample_loaded"):
381
533
  return {"error": "Sample replacement failed after bootstrap"}
382
534
 
535
+ # Step 4: Verify by reading back the device name (P0-1 guard)
536
+ hygiene = await _simpler_post_load_hygiene(
537
+ bridge, ableton, track_index, actual_device_index, file_path
538
+ )
539
+ if not hygiene.get("verified"):
540
+ return hygiene
541
+
542
+ result.update(hygiene)
383
543
  result["method"] = "bootstrap_and_replace"
384
544
  result["device_index"] = actual_device_index # additive — for step-result binding
385
545
  result["track_index"] = track_index
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.10.3",
3
+ "version": "1.10.5",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
- "description": "Agentic production system for Ableton Live 12 — 317 tools, 43 domains. Device atlas (1305 devices), sample engine (Splice + browser + filesystem), auto-composition, spectral perception, technique memory, creative intelligence (12 engines)",
5
+ "description": "Agentic production system for Ableton Live 12 — 320 tools, 43 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",
7
7
  "license": "BSL-1.1",
8
8
  "type": "commonjs",
@@ -5,7 +5,7 @@ Entry point for the ControlSurface. Ableton calls create_instance(c_instance)
5
5
  when this script is selected in Preferences > Link, Tempo & MIDI.
6
6
  """
7
7
 
8
- __version__ = "1.10.3"
8
+ __version__ = "1.10.5"
9
9
 
10
10
  from _Framework.ControlSurface import ControlSurface
11
11
  from .server import LivePilotServer
package/requirements.txt CHANGED
@@ -8,6 +8,12 @@ pyloudnorm>=0.1.0
8
8
  soundfile>=0.12.0
9
9
  scipy>=1.11.0
10
10
  mutagen>=1.47.0
11
+ # v1.10.5 Splice online catalog integration — required, not optional.
12
+ # Without these, SpliceGRPCClient silently disables itself and search_samples
13
+ # falls back to the SQLite sounds.db which only returns locally downloaded
14
+ # samples (see docs/2026-04-14-bugs-discovered.md — P0-2).
15
+ grpcio>=1.60.0
16
+ protobuf>=4.25.0
11
17
 
12
18
  # Development / testing (not required for runtime)
13
19
  # pip install pytest pytest-asyncio
package/livepilot.mcpb DELETED
Binary file