Ask a producer, sound designer and language guide sitting inside the workbench. Have it explain a patch you inherited, say why the bass is muddy, critique the song you just finished, or describe a song and hear it a few seconds later. It has the language, every builtin, the sound library and your song's computed facts in front of it before you ask, it measures before it talks about how something sounds, and anything it wants to change in your source has to compile before you can accept it.
Open it#
Header copilot toggle → a right-column pane with one mode row, one instruction box, and send ⌘↵. On the desktop the first visit is a setup state: what Copilot does, four example asks (each says it needs a key; a picked one waits in the composer and focuses the key field), a provider choice (anthropic / deepseek) and that provider's API key field with Save key as its one primary action. In the browser there is nothing to set up.
The reply streams in as a rendered document — prose, Volta code syntax-coloured, tables with their cells' bold, italics and code styled, measurements drawn as cards, and any proposed change as an inline diff with verify & apply on it. clear forgets the conversation (and an Instant Song); stop copilot cancels what is in flight.
Sending jumps the transcript to the bottom, wherever you had scrolled, and it follows the reply while it streams; scroll up during a reply and it stays where you put it. On a song with no tracks yet (a blank tempo = 120), the empty state leads with compose examples ("Describe a song…") instead of asks about a patch the song does not have; picking one switches to compose.
Who answers#
Two chips on the mode row: the provider (anthropic or deepseek) and the model within it (ui/src/copilot/provider.rs is the one table every shell and the gateway use):
| provider | models (chip) | tiers |
|---|---|---|
| Anthropic | claude-haiku-5-5 (haiku), claude-sonnet-5-5 (sonnet), claude-opus-5-5 (opus) | fast · work · judge |
| DeepSeek | deepseek-flash (flash), deepseek-v4-pro (v4-pro) | flash is fast and work · v4-pro judges |
The chosen model answers questions and edits. Instant Song routes by tier inside the chosen provider: the plan goes to the fast model, the part writers to the work model, the producer pass to the judge.
DeepSeek is reached through its Anthropic-compatible endpoint (api.deepseek.com/anthropic), so both providers share one request assembly, one stream parser and one tool loop; the body is fitted to DeepSeek on the way out (shape_request: thinking enabled or disabled rather than adaptive, only effort kept in output_config, no redacted_thinking, no strict). DeepSeek caches prompt prefixes on its own and ignores cache_control.
Thinking is adaptive, with an effort per mode (Mode::effort: high for critique, continue, write and skin; medium elsewhere). Effort is not part of the provider's cache key — a high-effort critique reads the prefix a medium-effort pre-warm wrote. At medium a typo fix usually answers without thinking at all, so no mode runs lower. The Instant Song plan runs with thinking off — it fills in a form, and thinking was most of its latency.
What it knows before you ask#
Three layers, cheapest-to-reread first.
L0 — the knowledge pack (ui/src/copilot/pack.rs)#
One generated, byte-identical block behind a short preamble (the teaching and editing contracts, the card vocabulary, the data-not-instructions rule):
- the language primer;
- the idioms and traps (each checkable one re-proved by
ui/tests/idiom_probes.rs); - the reference index with every guide's
##sections as citablereference/<id>#<anchor>; - every builtin with its signature and its example;
- the sound library — every Foundations instrument and machine drum with its role and four macros (
ui/src/copilot/library.rs); - a harmony and groove atlas of the corpus (common progressions; a groove table by genre), checked in as
ui/src/copilot/atlas_snapshot.md; - five complete example songs.
It is budgeted in tokens (PACK_TOKEN_BUDGET, a deliberately over-counting estimate; Anthropic reports the real prefix as about 114k tokens) and carries a cache breakpoint, so after the first request of a session it is a cache read — about a tenth of the price of fresh input. The panel pre-warms it when it opens (a max_tokens: 0 request, not streamed) and keeps it warm just inside the provider's five-minute cache life while someone asked something in the last half hour. The MCP server serves the same text as volta://grounding/pack (Use Volta from your own AI agent (MCP)).
L1 — the song dossier (ui/src/copilot/dossier.rs)#
While the copilot is open, a few seconds after the buffer stops changing, the desktop builds a dossier of your song off the UI thread (app-native/src/copilot_services.rs), in two deliveries:
- at once (a fraction of a second): the form (sections, bars, scenes, tracks that never play), the harmony per section in roman numerals with its findings, the notes of each track's first bars and of each section's first bar, the doctor's findings, compile warnings, and each patch's module and cable count;
- then loudness, rendered offline: master and per-section LUFS and true peak, and — for songs of 16 tracks or fewer — each track's loudness and eight bands. It covers up to 64 bars and says so when it stops short.
The dossier also carries the song's history digest (history-journal.md §10): the last eight versions, one line each — v12 · Copilot · darker bass · 3m ago, with ★ for a kept idea and editing / sounding on the versions your draft started from and the engine plays — and the kept ideas. It comes from the workbench's own journal, not a build, so it is current at every request.
The first request of a conversation carries the dossier; later ones carry only what changed (sections, keys, chords, loudness moves over 1 LU, new findings, new versions). Questions about the song are answered from it without a tool call. Compose and skin modes never see it.
Instant facts. When a question names something the dossier answers directly — the key, the tempo, the length, how loud a section or track is, which tracks play in a section — a workbench card with those numbers appears before the model's reply.
L2 — your question#
Assembled per turn: the learner line, the selection, the module, the compiled module map, the Timeline selection block (in the Arrange workspace, below), the diagnostics, retrieved snippets (BM25 over reference sections, Learn lessons and per-binding example code, ui/src/corpus.rs), and your prompt.
Tools#
Every song mode declares the same tool list, so the cached prefix survives a mode switch:
compile_volta,measure_audio,inspect_ir— the copilot's own schemas (ui/src/aitools.rs).compile_voltaandmeasure_audio(render)take binding-leveledits/adds, spliced into the draft (below), so checking or hearing a change never means writing the module out again.measure_audio(render)speaks the MCP tool's argument names (section,bars,whole_song,per_track, plusvariants) and the desktop runs it with the MCP server's own measurement (below).- the read tools the desktop runs in-process with the MCP server's own code and descriptions:
song_structure,pattern_events,harmony_report,reharmonise,doctor,eval_expr,lookup_builtin,search_docs,read_doc,list_sounds,case_view,harmony_atlas. They run off the UI thread, each on its own worker, and the turn waits for them and for its renders together. Nothing that writes a file is offered. submit_plan, used by Instant Song's plan (any other mode is told it is not for it).history— the song's journal (ui/src/copilot/journal.rs, the same formatter the MCP server's history tools use). With no arguments, the newest versions (title, author, intent, age, via, touched parts, ideas, markers);filter/part/limitnarrow it;versionreads one (source, what changed against its parent, what it captured);aandbcompare two with a unified diff. In the editing modes,revertputs back only the parts a version changed (from its parent, where the song still holds them) andrestoreputs back a version's whole song or onepart— into the draft, not the buffer. The workbench then posts the draft as the run's candidate (Draft — revert v12 (Copilot · darker bass) · placed from the song's history…), and you verify & apply it like any edit, so "go back to the darker bass" keeps the apply's protection, its receipt and Copilot's name on the version it makes. Controls a version only moved (knob rides) are named in the result, not reverted — they are not source. Compose refuses it (it never reads your song).
Skin mode keeps its one tool, validate_skin.
The modes#
| mode | what it does |
|---|---|
explain | reads back the selection, or the whole module — what it sounds like, how the signal flows, the one concept that unlocks it |
ask | the language, sound design, music theory, and this song — from the dossier first |
critique | a producer's review: measured, the three weakest things ranked, one verified fix each |
edit | change the module as asked, as small a change as possible |
fix | repair toward the diagnostics, or a goal you state |
continue | develop the existing material in Volta's pattern and arrangement algebra |
write | one new part for the region selected on the Timeline, placed on exactly that region (below) |
compose | Instant Song — a song from a brief, playing within seconds (below) |
skin | a workbench skin from a brief (Skins § In the copilot) |
The teaching contract is unchanged: lead with what the listener hears, then the signal path, then the one concept that unlocks it; cards rather than recited numbers; one experiment you can hear in a minute; terse for a producer, one idea at a time for someone new; and never claim it listened — it measured.
Changing your source: the draft workspace#
The model never writes the same thing twice. In edit, fix, continue and critique the panel keeps a draft: a copy of your buffer the run works on.
- A reply proposes binding-level fences:
```volta-edit <name>with the complete new binding,```volta-addwith new bindings, or — for a sweeping change — one whole-module```voltafence. A field fence changes one field of a large block instead of resending it:```volta-edit mixer.master(one mixer strip),```volta-edit <song>.edgesor<song>.scenes(one field of asong { … }),```volta-edit <patch>.<field>, and deeper paths through nested record literals (mixer.master.inserts). Its body is the field (master = { … }) or just its value; the field is replaced when the block has it and added — in the block's own separator style — when it does not. - When the reply ends, the workbench splices those fences into the draft and compiles it. Nothing goes back to the model when it is clean.
- When it is not, the model gets one repair message with the errors only, and answers with fences for the broken bindings alone; the draft keeps everything else it already wrote. A draft that took repair rounds is posted whole as the run's candidate.
- A draft that compiles but adds a doctor finding the buffer did not have (a track no scene plays, an empty scene — what the editor's status bar shows as
doctor: …) goes back once per run: fix it, or say in one line why it is intended. Findings the buffer already had are never sent, and the host never loops on them. The draft is then posted whole. - The candidate renders as an inline diff against your buffer with
+n −nstats and a semantic annotation strip (what the change means: patches, tracks, scenes, transitions named by their endpoints —transition into floor (1 bar) added— the master's level and inserts, mixer strips); any doctor finding the candidate still adds is listed under it in the alert colour, so "compiles" never reads as "nothing to fix".measure Δrenders both sides; andverify & applycompiles it once more through the editor's analyzer and replaces the buffer only with text that came back clean.discardtouches nothing. - An applied card turns into the record of the apply: APPLIED EDIT, the diff it applied (frozen — a save that reformats the buffer, or any later edit, does not re-diff it),
applied as v4and ahistorychip once the version is journaled, and noverify & apply,measure Δordiscard. Only the newest reply's candidate is ever a card; an older reply's fences read as plain code. Undo from the receipt makes the card offerable again.
The editing contract every drafting mode shares (EDIT_CONTRACT) says every change a reply recommends must be in a fence — never an edit described for the user to make by hand — and names the field-fence form for the long mixer and song blocks a model would otherwise leave as prose.
The card's semantic strip and the receipt read the compiled candidate through the shell's learn_compile, which gathers any import the candidate adds that the song did not have (an Instant Song draft's creator_foundations onto a blank buffer), as analyze does — so the strip analyzes whatever verify & apply verified.
A fence naming a binding the draft does not have is added as a new binding. A field fence needs its block: one naming a block the module lacks is refused (except mixer.<strip>, which opens a mixer { … }), and so is a shape the splice cannot read safely — a computed block, or a song that writes its edges as bare a -> b : … rules — with the reason, so the model sends the whole binding instead. Field fences are located on the compiler's own tokens (ui/src/copilot/field.rs), and the same splice serves a reply's fence and a compile_volta / measure_audio edit whose binding is a field address, so what a check compiled is what the card applies. compile_volta reports the doctor's findings as a doctor list beside errors and warnings (only when there are any).
Instant Song's writers share the draft code: a writer whose clean edits add a doctor finding has them merged, then is asked once inside its turn budget; a reply with no fences ("it is a hand-started stab") ends the job with its edits kept. × 3 (on edit and continue) still asks for three whole-module alternatives, outside the draft.
In the Arrange workspace: write a part#
When the copilot is docked as the Agent pane beside the Timeline (Arrange — write and arrange with the agent), the region selected on the Timeline is the subject of the turn. The panel shows it under the mode row (selection: lead · bars 9–12 · plays leadIdea (2 placements)), and every song mode's request carries a selection block: the track, the bars, the section (the marker the region starts in), the meter and tempo, the pattern the region plays and how many placements share it, the region's own notes now, and the notes every other track plays in those bars — pitch@startBeat:durBeats:vel, beats counted from the region start, from the same projection the roll's context ghosts use. Nothing is rendered for it.
write asks for one part: "a counter-melody over the chorus chords".
- Without a selection the panel says to select a region and sends nothing.
- The host names the binding to write (
leadPart, orleadPart2when that is taken) and the model answers with onevolta-addfence for it. - The host points only the selected region at that binding (
workflow_edit::repoint_region_content), so every other placement of a shared phrase keeps playing the original. A reply that edits the region's own pattern instead is re-read as the new binding — a shared phrase is never edited behind your back. - The placed draft is compiled like any other; the diff card reads
write: leadPart → lead bars 9–12.
A region that is one pass of a repeat or group, and curve or audio regions, are refused with the reason. Scene songs have no region selection yet, so write asks for one there.
Step trace. Under your prompt, a collapsible list of what the run did — Thought, Read song structure, Compiled leadPart — clean, Measured the mix render 4c — sent, Workbench compiled the draft — clean — one line per step with a short argument, built from the tool loop's own events. Once the apply is journaled, the trace ends with Applied v12 — open in History, a link that selects the version the write minted in History.
Receipts. Every verify & apply leaves a receipt card computed from the compiled module before and after, never from the reply's prose (ui/src/copilot/receipt.rs). Each track's notes are what the scheduler plays over one pass of the song, in song bars: a written timeline region by region, a scene song through the Map's own schedule (the projection the roll's context ghosts use) — so an edges fill, a track dropped from a section, or a track that only plays in the last section all read where they sound. A flat song (and a scene song's live-choice or externally timed track) is one pass of its pattern from bar 1. The notes are compared as a multiset: Added 16 notes to lead, removed 16 · bars 9–12; separate changed stretches are named as runs (Added 32 notes to fillTrack · bars 16, 64) and lit separately on the clip strip. Whole-apply cases get a headline: a buffer with no notes before reads New song — 11 tracks, 88 bars; a repair of a buffer that did not compile reads Repaired — the song compiles again and is compared against the last version that compiled (or describes the result when there is none); a result the shell cannot read says so and names no change; and No notes changed means exactly that (a mix or sound edit). open in Pattern opens that track's notes, undo takes that apply back — the editor's own undo while the buffer is still what the apply left, and after any later edit a revert of the version it minted (history-journal.md §5: only the parts it changed go back; your later edits stay) — and dismiss closes it.
Arrange verbs. In edit, continue and write the request also carries an Arrangement block: each track's region ids, bars, content and pass length, and the op grammar. The model may answer with a ```volta-arrange fence of JSON ops — loop, extend, move, duplicate a region, rename_track — instead of rewriting a region list (ui/src/copilot/arrange.rs). The fence is not source: the host applies the ops after the reply's binding fences, each through the Timeline's or the Patterns pane's own guarded rewrite and against the compiled song; in the host check they wait until the draft's fences compile. The tool list does not change, so the cached prefix survives. An op that cannot apply (an unknown id, a pass of a repeat or group, a song without a written timeline) refuses the whole candidate with a one-line reason, and the receipt gains one line per applied op (Looped t1 · bars 1–8, Renamed t1 → drums). Details: Arrange — write and arrange with the agent § Arrange verbs.
Instant Song#
Open copilot → compose and describe the song: "a three-minute melodic deep house song with a warm bass, a pluck hook and a quiet breakdown."
- Plan. The fast model calls
submit_planonce (ui/src/copilot/plan.rs): title, tempo, key, sections with bars, energy, a groove and a roman-numeral progression, the form, a palette of library sounds per role, a hook — and the name table, every binding name each part will use. The plan is validated locally (key, numerals, bars, sounds that exist in the library with the right role, names that are valid and free); a refused plan gets one retry with the reasons. - Sketch. Local code builds a complete module from the plan (
ui/src/copilot/sketch.rs): tempo, key, the library import, chords per section, drums from the groove table, a bass on the roots, pads and comping on the progression, a placeholder hook, tracks, a send, ducks, a mixer with a limiter, markers, and one scene per section. It compiles before anything else happens, and its loudest section renders and starts looping — the first sound. - Parts. One writer per part — drums, bass, harmony, hook, transitions — runs in parallel (six streams on the desktop and through the gateway), each owning a disjoint set of bindings. A writer's fences are applied to a copy of the current draft, compiled, and merged only when the whole song still compiles; a writer that reaches into another's bindings, or does not compile, is sent back alone; one that cannot get clean in three turns leaves the sketch's version in place. Each merge re-renders the loop, which switches at its next wrap with a short crossfade, so the song fills in while you listen.
- Polish (the
polishchip, off by default — it roughly triples the time to a draft). The dossier of the finished draft is built — loudness measured offline — and the judge model makes at most six small edits to balance, section contrast, transitions and the ending, checked like a writer's. - Hand-off. The draft is formatted as the editor's save formats it (
volta fmtat the width the editor will have once it holds the draft — its gutter grows a digit past 99 lines — whitespace-only) and posted as one candidate: diff,measure Δ,verify & apply, as for any edit. The apply lands the posted text byte for byte, so applying and then saving changes nothing.
The transcript shows each step as a workbench row: the plan line, playing the sketch — first sound N s, each part's bindings and time, any part sent back and why, and verified draft N s.
Steer it while it plays. While a song is on screen the input box steers it: "darker bass", "busier hats". A sentence that names one part (drums, bass, harmony, hook, transitions) goes to a writer that owns only that part; anything else goes to one that may touch the whole song. Each steer ends in a new candidate. clear starts a new song.
Steering after apply works on your buffer. Once this run's newest candidate was applied, a steer works on the song as it is in the editor — formatted by a save, or edited by hand — so its candidate differs from the buffer only in what it steered (AiUi::song_steer_base, a_steer_after_apply_keeps_the_users_edits). The rule, exactly: the buffer is adopted when this run's candidate was applied to this buffer (a song switch opens a new one), the run has no newer unapplied candidate, and the buffer has changed since the apply but was not undone back to what the apply replaced. Then it must still hold the song's spine (progression and instantSong) and compile clean; otherwise the steer is refused with one line ("not steered — …") and the input is kept. In every other case the steer works on the run's own draft and the buffer is not read.
Privacy. The brief never carries your buffer, its map or its dossier: the brief, the cached grounding and the run's own draft go out, nothing else (instant_song_never_sends_the_editor_buffer). The one exception is the rule above: a buffer this run's candidate was applied to is the song, and a steer sends it.
Compiling and measuring a draft is not a judgement of musical quality; the loop and the dossier are measurements, not listening.
Hearing the song#
measure_audio is what stops the copilot guessing. live reads the playing transport's meters (master LUFS-M/S/I, true peak, clips; per-track RMS / peak and, while the panel is open, eight labeled bands). render is a deterministic offline render, run by the MCP server's measure_audio code (app-native/src/mcp/mod.rs measure_render, reached through app-native/src/copilot_services.rs measure), of the draft — or a full source, or the draft with edits / adds spliced in — resolved like the Studio's compile (the song's folder, its unsaved edited imports, its pinned prints):
section(a marker or scene name),bars[from, to] orwhole_songrender through the arrangement — scenes, transitions, automation — and reply with master loudness (LUFS-I, true peak, RMS, eight bands) andsections: each arrangement section's LUFS / true peak / PLR, named by scene and marker. "How loud is the airbreak against the return?" is onewhole_songcall, or asectioncall each.per_trackadds each track's own numbers (default for spans of 32 bars or fewer).- With no span and no
cycles, an arranged song is measured whole, and the reply'sspan_notesays so; a song with no arrangement renders a flat 4-cycle loop.cycles(max 8) asks for that flat loop explicitly — every track at once with per-track solo numbers — and on an arranged song itsscope_notesays it is not the song as it plays. variants(up to 4{label, edits?, adds?}) renders each candidate beside the draft over the same span, in parallel, master only, and returns one comparison: each variant's LUFS-I, true peak and per-section loudness with its deltas against the base (loudness, peak, the eight master bands). A level sweep is one call, not one render per model turn. A candidate that does not splice or analyze is its row's error.
Renders run on their own worker threads (EngineBridge::ai_measure_start; the result lands on ai_tool_poll like a read tool's), any number per turn and in parallel; the turn waits for all of them without freezing the panel. Arranged renders share the MCP server's render cache, so a span the dossier or an earlier call already rendered comes back sliced from it (cached). The measure card shows the master tiles, then a row per section and per variant (Δ LUFS), then the tracks. The panel's own candidate A/B (measure Δ) keeps the flat render seam (measure_render_start). Bands are producer vocabulary — sub, low, low-mid, mid, high-mid, presence, brilliance, air (ui/src/audiodiff.rs).
Desktop only. In the browser the tool says audio measurement is available in the desktop app only, and the copilot reasons from the source and says the numbers were not measured. The browser builds no dossier and serves no read tools.
Who it is teaching#
The you: producer chip opens the learner row: level (new, producer, expert) and about you (one line). Both persist; the copilot also tracks the builtins and pages it has shown you this session and is told not to re-explain them. A Learn lesson you have open goes in too.
What a run costs#
Every run ends with a ⏱ line: time to first token, first sound and verified draft where they apply, total time, requests, output tokens, and the share of input served from the provider's cache (ui/src/copilot/metrics.rs). The desktop appends each run as one JSON line to ~/.volta/copilot-metrics.jsonl (VOLTA_COPILOT_METRICS=<file> sends it elsewhere): per request its send time, time to first event and first text, wall, tokens, the tools its reply called, and the gap before the next request (local work — renders, read tools, the host compile); per run what the dossier held at the send (none, early, full).
Output is capped at 16k tokens per turn; a run of the turn loop has six turns. Input is where the money is, and it is why the pack is one byte-identical block with a cache marker and a pre-warm. The pre-warm keeps the cache alive for half an hour after the last ask, or after the panel's first warm when nobody has asked anything.
Time is round trips and renders. Each extra turn costs a time-to-first-token plus its thinking, and a measure_audio(render) is an offline render of the song — the slowest thing a run can wait on. So:
- Critique and Ask answer from the dossier — its loudness is already a measured render of the song. They call
measure_audioonly when that is missing or still measuring, or (Ask) for a change the dossier cannot show; critique leaves its fixes to the host check and the candidate'smeasure Δand labels the change it expects as an estimate. A critique is one request; sent while the dossier is still measuring, it waits for it (at most 90 s,workbenchsays so) rather than render the same song a second time. - The dossier starts when the panel first opens (only a later edit waits for the buffer to be still), so its loudness is usually measured before the first question is sent.
- The retrieval index is built on a worker once the panel is usable, not on the paint thread at the first send.
- A run keeps going while the panel is hidden: the shell ticks the turn loop every frame the panel is not drawn (
AiUi::tick_hidden).
Measured timings live in dated notes; the latest is docs/notes/copilot-speed-2026-11-05.md. volta-shot measures in real time with VOLTA_SHOT_REALTIME=1 (its module doc).
Desktop and browser#
The panel is the same Rust code in both, and both shells stream through one parser (ui/src/aistream.rs), which also reports input, cache-read and cache-write tokens. Only the transport and the keys differ.
Desktop — your own keys, no middleman (app-native/src/ai.rs). Keys live at ~/.volta/anthropic-api-key and ~/.volta/deepseek-api-key, mode 0600, written by the panel's key row and sent only to that provider. Besides the panel's lead stream (shared with the bench and the plugin explorer) the desktop runs six parallel streams for Instant Song. This is where measurement, the dossier, the read tools and the looped preview live.
Browser — Volta's hosted copilot (ui-wasm/src/web.rs → the gateway). Nothing to configure: requests go to /api/ai/complete with your session token; providers and models come from /api/ai/usage; pre-warms go to /api/ai/prewarm.
Running it locally is the dev split: the Worker under npm run dev in web/ (wrangler dev, :8787; keys in web/.dev.vars — ANTHROPIC_API_KEY and/or DEEPSEEK_API_KEY for /api/ai/*, JEV_API_KEY for the command bar's /api/say), and the page under VITE_PASTE_API=http://localhost:8787/api npm run dev in runtime/ on :5173.
The gateway — web/src/ai.ts#
POST /api/ai/complete— one streamed completion. Validates the model against the shared table, routesdeepseek-*to DeepSeek and the rest to Anthropic (a model whose provider has no key is a 503), forwards only known Messages-API fields, capsmax_tokensand the body, forces streaming, and pipes the SSE back unmodified.POST /api/ai/prewarm— one non-streamedmax_tokens: 0request (1 for DeepSeek) that writes the cache and returns{model, usage}only. It has its own small per-caller counter and never spends your allowance.GET /api/ai/usage—{configured, providers, signedIn, used, limit, models, allModels, run}.POST /api/ai/repair— the service-side boundedcompile_voltaloop, verified by the real compiler bundled as wasm.
Quota is in units: one request, or one Instant Song — requests that share an X-Volta-Run id within ten minutes count once (at most twelve per run). No key configured ⇒ 503, and the workbench degrades gracefully.
Structured diagnostics#
The machine-readable error signal the host check, the editor squiggles and the eval harness share. compiler/src/diagnostics.rs is the single serializer: StructuredDiagnostic { level, message, span:{start,end}, kind?, file? }, with diagnose(src, sources) → { ok, diagnostics[] }, exposed over wasm (compile_checked), the CLI (volta --diagnostics-json), libvolta, the LSP and the editor.
Is it any good? — the eval harness and the bench#
web/evals/ runs the product's own request (printed by ui/examples/copilot_request.rs, --provider anthropic|deepseek) across five suites, graded by compile checks and a judge. Each case records latency as a first-class result — first token, first text, wall time, round trips, output and cache-read tokens, cost — reported per suite as median and p90.
make eval-copilot ARGS="--suite language --limit 5" # a smoke run
make eval-copilot ARGS="--provider deepseek --dry-run" # no key, no spend
make eval-copilot-check # the harness's own tests
Instant Song is measured end to end by a headless bench over the native transport, the real compiler, the MCP renders and the dossier builder (app-native/src/copilot_bench.rs, ignored; it spends real money):
VOLTA_BENCH_WARM=1 cargo test -p volta-studio --profile fast --bin volta-studio \
copilot_bench -- --ignored --nocapture
Nothing calls a provider without a key. A model bump is gated on a fresh harness pass at the new pin.
Notes and limits#
- The copilot never touches the transport, the seed or a file. Its only route into your song is a candidate you accept. Instant Song's loop plays through the preview output, not the transport.
- Retrieved snippets, your source, the dossier and tool results are data. A directive hidden inside a comment or a corpus file is ignored by contract.
- Instant Song writers cannot add track effects: the tracks belong to the sketch, so a writer works in its patterns and instruments.
- The dossier's loudness covers at most 64 bars, and per-track numbers only for songs of 16 tracks or fewer; unsaved edits to an imported module are not seen by the dossier or the read tools.
Not built yet#
- Browser parity for measurement, the dossier, the read tools and the looped preview.
- Cross-session conversation memory — the learner profile persists; the conversation does not.
- Annotate write-back, and the refinement-obligation repair goal.
Next steps#
- The language it is grounded in: Language reference.
- What "muddy", "no headroom" and "it pumps" mean in numbers: Mixing tracks and buses, The production handbook.
- The same tools from your own agent: Use Volta from your own AI agent (MCP).
- Instrument exploration with real plugins: Explore plugin sounds with Copilot.