Migration Guide
This page is for scripts: anything calling Producer Pal through MCP, the REST API, or an agent skill. If you only chat with Producer Pal, there is nothing here for you: the assistant reads the current tool descriptions on every conversation and writes calls in the current spelling.
There are two migrations here and they are not equally urgent:
| What | When | Urgency |
|---|---|---|
| Response fields moved | already shipped, in 2.3 | Do this now. No field kept a back-compat key. |
| Input params removed | 2.4 | Forward notice. Everything still works, and warns, until then. |
Most upgrade guides lead with the deprecations. This one leads with the responses, because that is the half that breaks the moment you install 2.3.
Responses changed in 2.3
Fields were renamed, removed, and reshaped across most tools, and not one kept a back-compat key. A script reading a removed field sees undefined rather than an error, so these fail quietly.
The theme is that a result now says where its object is, once, in a path you can pass straight back into the next call, instead of scattering trackIndex, sceneIndex, deviceIndex and arrangementStart across the response.
| Tool | Change | What to do |
|---|---|---|
ppal-read-live-set | masterTrack → mainTrack | rename the key you read |
| read-live-set, read-track, select | type is gone from return tracks and the main track | read path (rt0, mt); type is now only midi/audio, on regular tracks |
| create-track, read-track, read-live-set, select | trackIndex, returnTrackIndex removed | parse path (t3, rt0) |
| create-scene, read-scene, read-live-set | sceneIndex removed | parse path (s2) |
| create-device | deviceIndex removed | parse path (t1/d2) |
| every clip result | arrangementStart removed | the clip's path carries it: t0[5|1] |
ppal-delete | a successful delete reports deletedPath, not path | branch on deleted: read deletedPath when true, path when false |
ppal-playback | currentTime removed | it was never the playhead; read startTime for where the next play begins |
ppal-playback | arrangementLoop: {start, end} → loop/loopStart/loopEnd | read the three flat fields |
ppal-playback | sceneIndex, sceneName → scene: {id, path, name} | read scene.name; scene.path is an address you can spend |
ppal-duplicate | a buried copy has no id | {path, overwritten: true} marks a copy this call destroyed |
ppal-duplicate | name removed from scene→arrangement clip entries | it only echoed your own argument; entries are {id, path} |
| update-device, create-device | a device inside a drum pad reports p<pitch>/c<n>, not c<n> | don't rebuild rack-relative chain paths from a write result |
type is the subtle one. It used to answer two questions (which signal a track carries, and what role it plays) and now answers only the first. A script branching on type === "return" or type === "master" gets undefined.
Fields that became conditional
ppal-playback's loop, loopStart and loopEnd come back only when your call didn't name them. Don't read them unconditionally.
The device tools' params can be longer than the number of params you wrote: a name that reached nothing comes back as {name, reason} with no value. Key off value, not the entry's presence.
ppal-delete can return an array where it used to return a single object, since failures are now included instead of dropped. Its results are also in request order now, not internal deletion order, so you can pair results to targets by index.
Three values read differently without the field changing
- An all-digit name is a string. A track named
5678used to serialize as the JSON number5678. Strict type checks will notice. - Gain is rounded to 0.01 dB. A gain of -6.333333 used to read back as -6.333000183105469. Exact comparisons need the same rounding.
update-live-set'sscaleis the spelling Live stores, not yours:"F# Dorian"in,"Gb Dorian"out. Every read already said this; the write result was the one that disagreed.
Error and warning text
Error executing tool 'ppal-update-clip': <reason> is now Error: <reason>, and no warning carries a tool-name prefix any more. Anything matching on that text needs updating.
Params being removed in 2.4
Every param below still works today and emits a deprecation warning saying what to use instead. They are removed in 2.4.
A param that warns is not always one of these. ppal-read-clip takes trackIndex and sceneIndex as aliases: names a model reaches for on its own, folded onto path. They warn too, and they are staying.
They all say the same thing: an object is named by one path, counting from 0, instead of by a scattering of index params. t2 is the third track, rt0 the first return, mt the main track, s1 the second scene, t2/s1 a clip slot, t2[5|1] a spot on an arrangement, t2/l0 a take lane.
| Old | New |
|---|---|
trackIndex + trackType | path: t2, rt0, mt |
trackIndex: -1 on create-track | path: "t+" (append) |
sceneIndex | path: "s2" |
slot: "1/0", slots, toSlot | path / toPath: t1/s0 |
arrangementStart: "5|1" | fused onto the path: t1[5|1] |
takeLane: "1" | /l0 on the path |
locator: "Chorus" on duplicate | toPath: "[loc:Chorus]" |
startLocator, loopStartLocator, loopEndLocator | startTime / loopStart / loopEnd: loc:Chorus |
devicePath on select | path |
inputRoutingTypeId and the other three *Id | drop the Id suffix |
The last row is a plain rename: the surviving param already accepts a name or an id. Most of the rest are mechanical. Two are not, and a find-and-replace on them writes a call that quietly does the wrong thing.
takeLane counts from 1; l<n> counts from 0
| Old | New |
|---|---|
takeLane: 1 | t0/l0, the first take lane |
takeLane: 2 | t0/l1 |
takeLane: 0 | t0, the main lane (no take lane) |
So it is off by one everywhere, and at zero it isn't a take lane at all. A take lane in a path also always needs its track: t1/l0[5|1] works, l0[5|1] is refused.
takeLane: "new" is gone: name the lane by index instead, and lanes up to it are created as needed. Read a track's takeLanes first and use the next free index, since a track holds 8 lanes and none can be deleted. Appending a lane without knowing how many a track has isn't possible for now; it will come back as a track-tool feature.
// before: two clips, one new lane
{ trackIndex: 1, arrangementStart: "21|1,25|1", takeLane: "new" }
// after
{ path: "t1/l0[21|1],t1/l0[25|1]" }takeLaneName is deprecated
It still works on ppal-create-clip and ppal-duplicate, with a warning, and will be removed. Name take lanes in Live.
arrangementStart becomes a coordinate, not a param
It stops being its own param and becomes a […] coordinate on the destination path, so it has to be paired with a track rather than renamed in place. The bar|beat value itself doesn't change.
// before
{ trackIndex: 1, arrangementStart: "33|1,37|1" }
// after: one destination track broadcasts across every position
{ path: "t1[33|1],t1[37|1]" }On ppal-update-clip and ppal-duplicate there was never a destination-track param: the clip stayed on its own track. A path spells that as a bare coordinate.
// before
{ path: "t1[41|1],t1[45|1]", arrangementStart: "49|1,53|1" }
// after
{ path: "t1[41|1],t1[45|1]", toPath: "[49|1],[53|1]" }split is not a rename either
ppal-update-clip has both split (deprecated) and arrangementSplit (the survivor), and they read positions in different coordinate systems:
splitpositions are offsets from each clip's own start.arrangementSplitpositions are on the song timeline.
The same value cuts somewhere else. On a clip starting at bar 13, split: "2|1" cuts at song bar 14; arrangementSplit: "2|1" on that clip matches nothing at all and warns that it cut nothing.
Converting means adding each clip's arrangement start to each offset, which means reading the clips first. There is no offline rewrite, which is why the adapter script below leaves split alone and tells you so.
Three values, not params, are also retiring
These don't show up as a param rename because it's the value that retires:
| Old | New | Deprecated since |
|---|---|---|
ppal-create-track type: "return" | path: "rt+" | 2.2 |
ppal-library action: "searchBatch" | action: "search" with a searches list | 2.3 |
a device path in front of params[].name | address the device by path, send the bare name | 2.3 |
The params[].name prefix
On ppal-create-device and ppal-update-device, a param name could carry a device path in front of it. The device gets addressed by path instead:
// before
{ path: "t5/d0", params: [{ name: "pC1/c0/d0/Volume", value: "-6" }] }
// after
{ path: "t5/d0/pC1/c0/d0", params: [{ name: "Volume", value: "-6" }] }Two things to know before you rewrite these:
- A real param name containing a slash needs no change.
Dry/Weton a Reverb is matched as a name first, and only falls back to path-routing when the device has no such param. paramsapplies to every path in a call. So where the prefixed form let one call set a different value on each pad, the replacement needs one call per distinct value. Setting the same value across pads still works in one call:path: "t5/d0/pD1/d0,t5/d0/pE1/d0".
The drum-pad sample shortcut ({name: "pC1/sample"}) is not deprecated: it creates the pad's Simpler as well as addressing it, so it isn't just a spelling for a path.
The adapter script
examples/migration/ has a zero-dependency adapter in both Node and Python that rewrites the mechanical cases for you:
node ppal-migrate.mjs ppal-read-track '{"trackIndex": 2}'
# {"path": "t2"}
python ppal_migrate.py ppal-create-clip '{"trackIndex":1,"arrangementStart":"33|1,37|1"}'
# {"path": "t1[33|1],t1[37|1]"}As a library it returns the rewritten args plus notes, anything it could not translate on its own:
import { migrateArgs } from "./ppal-migrate.mjs";
const { args, notes } = migrateArgs("ppal-update-clip", {
path: "t1[9|1]",
split: "2|1",
});
// args -> unchanged
// notes -> ['split "2|1" left as-is: its positions are offsets from each
// clip's start, and arrangementSplit reads the song timeline…']Always check notes. An empty list means the call migrated cleanly. Anything in it names a param the adapter left exactly as it was, because answering needed a Live read or a judgement call: split, params[].name prefixes, searchBatch, takeLane on a duplicate whose source is addressed by id, a track and a scene selected on a return track, and any value the tool itself refuses. A half-migrated call is worse than an untouched one.
It also exports buildPath and parsePath if you'd rather assemble paths from parts than build strings by hand.
Finding what you're still sending
Every deprecated param warns when you send it, and the warning says what to use instead:
WARNING: param "takeLane" is deprecated and will be removed; use "path" instead.
Lanes count from 0 in a path: takeLane 1 is "l0", and takeLane 0 is the main
lane.Over the REST API these arrive in a warnings array beside the result; over MCP they're appended to the tool result. Run your existing scripts against 2.3, log every warning, and you have the list of calls to fix, with no auditing by hand.