ismail
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| ISMAIL_FFMPEG | No | Path to the ffmpeg binary used for MP3 previews. Defaults to looking for ffmpeg on PATH. | |
| ISMAIL_VOICES | No | Path list of folders containing your personal voice modules. Each folder is searched after the song's own `voices/` folder and before the built-in voices. |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| logging | {} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| project_newC | Create a new project directory. offset_sec = time of bar 1 (match a reference's grid with analyze_grid). objective: what this piece is for, in the person's words ("keep a listener asleep for 3 hours"); see project_set. derived_from: the project this one is a version of; its objectives and lineage are carried over, so a derivative keeps the intent it came from (intent provenance). |
| project_infoB | Summary of the whole project: grid, tracks (instrument, note count, bars used, fx), buses, master, sounds. |
| project_setB | Change project settings. Changing bpm keeps notes on the same beats (the song gets faster/slower). reference_stems = a folder of already-separated stems (drums.wav, bass.wav, ...) for the reference. objective: what the piece is for, in the words of whoever set it (objective_by, default 'user'): a sleep set's might be "stay asleep", a cover's "sound like the 1970 record". Earlier objectives are kept as history; project_info shows the current one, and every version made from this one carries them (derived_from). |
| undoA | Restore the project to its state |
| track_addB | Add a track. instrument: a dict (see instrument_help) or 'preset:' (see presets_list). None = audio-only track. |
| track_setC | Mixer settings for a track. sends = {bus: level_db} (replaces all sends; {} removes). offset_ms moves every sound of the track (notes and audio clips) off its beat, negative = earlier, e.g. -35 for a part whose attacks come late; 0 removes it. Notes keep their beats (notes_read shows them on the grid); automation stays on the song's time. |
| track_removeC | Delete a track (undo restores it). |
| bus_addB | Add an effect-return bus (e.g. reverb). Route with track_set(sends={name: -12}) or track_set(output=name). |
| guideA | Read this first: how to use this DAW as an agent (workflow, conventions, which tool for which question). |
| lexicon_noteA | Record what the person called something, and what it means in ismail. said: their words, verbatim ("too clean", "the snare is boxy"). means: the system terms it maps to, a list or ';'-separated ("fx eq peak 400 Hz -3 dB on snare; analyze_timbre centroid"). craft: the role the word belongs to (composer, arranger, performer, sound designer, recording/mixing/mastering engineer, producer, dj, director, cinematographer, colourist, editor, choreographer, listener). outcome: open | worked | partly | missed, with why. Update an entry with id=... (means, craft, outcome, why); the words themselves never change. Words about the work only: never record emotion, mood or health. The file is local and shared by every session (lexicon_view shows where). Returns the entry and what the same words meant before. |
| lexicon_findB | Look up the person's words in both directions: from their word to the system ("what did 'crisp' mean last time?") and from a system term to their word (text='high shelf' finds what they call it, so you can say it their way). Best matches first, with whether the change worked. |
| lexicon_viewB | The person's vocabulary as a readout: entries by craft and outcome, the share of trade words in what they say by month (description moving toward technique), new entries per week, entries not mapped yet, the newest ten. since='2026-10-01' limits it. Read it at the start of a session to speak the person's language. |
| presets_listC | List instrument presets with their type (voice presets name their voice module). |
| voices_listA | List voice modules (code instruments kept as Python files) and mimic profiles (instruments measured from recordings): the song's own (/voices/), those on $ISMAIL_VOICES, and the built-ins. Use one as {"type": "code", "voice": ""} or its preset; voice_help(name) shows its velocity mapping, functions and parameters. |
| voice_helpB | Everything about one voice module: where it comes from, what velocity does, its functions and parameters. |
| instrument_helpA | Full parameter reference (with defaults) for an instrument type: synth (= sprite), sampler, kit, code, mimic, kick, snare, hat, clap, tom, noise_hit. |
| instrument_setB | Set a track's instrument. merge=True deep-merges the given keys into the current instrument (oscs list is replaced whole); merge=False replaces it. instrument may be 'preset:'. |
| track_modelA | Record what a track's sound is modeled on, shown by project_info and render. on: the example it was measured from ('ref', 'ref:', 'sound:', a path, or a note like 'kit comp_2 of the ref drums'), 'designed' for a sound made on purpose (most electronic music), or '' to clear. by: how (an op, a song script, 'ear exam'). Fits record it themselves (instrument_fit apply_to_track, track_fit apply=True); mimic profiles, measured library voices and imported samples need nothing. |
| instrument_showC | Show a track's instrument JSON (full=True includes all defaults). |
| notes_writeA | Write notes starting at |
| pattern_writeC | Step-sequence: lanes = {pitch: 'x...x...X...x..o'} ('.' rest, X accent 127, x 100, o 70, - 45, '_' tie). step = beats per character (0.25 = 16ths). Pattern length (chars*step) sets the span; repeat tiles it. |
| notes_readA | Read a track's notes in bars [a, b] (inclusive). view='list' (bar, beat in the bar, pitch, dur, vel, and an @offset when a note is nudged), 'roll' (ASCII piano roll, max 8 bars, on the beats), or 'rel' (notes_write format relative to bar a - copy/edit/write back, offsets included). A track offset is named on the first line. |
| notes_clearC | Delete notes starting in bars [a, b]; optionally only the given pitches. |
| notes_copyC | Copy notes from bars [a, b] of |
| notes_transformB | Edit notes in bars [a, b]: transpose (semitones), velocity (set) / vel_scale, shift_beats, quantize (grid in beats; a note's @offset is kept, so a nudge survives quantizing), dur_scale / dur_set, legato (extend each note to the next onset), offset_ms (set each note's nudge in ms, negative = earlier; 0 removes it). pitches limits to those pitches. |
| fx_addB | Add an effect to target ('', 'bus:' or 'master'). fx = {'type': ..., params}; see fx_help. index = position in chain (default end). |
| fx_setC | Update params of effect #index on target (merge). |
| fx_removeA | Remove effect #index from target. Automation on later fx indices is re-pointed automatically. |
| fx_helpB | Effect types with default params; type= for one. Automatable params listed per type. |
| automation_setA | Automate a parameter over time (track='master' for a master volume fade, track='bus:' for a bus's volume_db or fx.., e.g. a filter sweep on a drum bus). points = [[bar, value], ...] with fractional bars allowed (17.5 = beat 3 of bar 17); linear between points (log for Hz params), held before/after. param: 'volume_db' (dB OFFSET added to the track fader: 0 = unchanged, -30 = fade out), 'pan', 'inst.' (inst.filter.cutoff, inst.oscs.0.level ... see instrument_help), 'inst.lane.' (an expression lane of a performer voice, e.g. a guitar's bend in semitones: voice_help lists its lanes), 'fx..'. mode='merge' keeps existing points outside the new points' span. |
| automation_clearA | Remove automation for one param (or all when param is None). |
| automation_readB | List automation lanes on a track ('master' and 'bus:' too) as [bar, value] points. |
| sound_makeA | Synthesize a sound into the bank: play |
| sound_importC | Import audio into the bank from a file or any analysis source (ref, ref:vocals, track:x, render...), optionally a time slice (seconds) or bars [a, b] on the project grid. |
| sound_listC | List the sound bank. |
| audio_placeC | Place a bank sound on a track's timeline at |
| renderA | Render to renders/latest.wav (and renders/.wav). bars=[a,b] renders a window (fast iteration); tracks limits to those tracks (+ their sidechain sources, which stay silent); stems=True writes per-track files for analysis as 'track:'. Analysis of a windowed render and its stems still uses song bar numbers (bars outside the window raise). mp3='also' writes renders/.mp3 next to the wav, mp3='only' writes the named render as mp3 only (latest.wav is always written: analysis reads it); needs ffmpeg on PATH or $ISMAIL_FFMPEG. Returns levels, clipping and timing. |
| analyze_gridB | Estimate tempo, the time of bar 1 (kick, harmony, section changes and snare on 2 and 4 vote), the tuning offset from A440 and, with a reference drum stem, the swing. Use the result in project_new/project_set. |
| analyze_overviewC | Loudness, key and a section map (bars, level, dominant bands, chroma) of an audio source on the project grid. |
| analyze_barsC | Per-bar table (max 32 bars): level dB, 6 band energies, spectral centroid, onset count, chroma chord. |
| analyze_chordsC | Chord + bass note per 1/per_bar of a bar (max 32 bars). |
| analyze_melodyB | Monophonic pitch -> note list in notes_write format (max 16 bars). Best on a stem or a soloed line. |
| analyze_notesC | Polyphonic note estimate -> notes_write format (max 16 bars). Raise threshold to drop weak/ghost notes. |
| analyze_pitchesB | Pitches sounding in each 1/per_bar of a bar, loudest first with dB (max 16 bars). Best view for pads, drones, sustained basses and chords; use analyze_melody for fast monophonic lines. |
| analyze_drumsA | Drum hits as step strings per bar in 3 lanes (low/snare/hat bands), max 16 bars. Paste into pattern_write. Best on an isolated drum source: 'track:' after render(stems=True), or 'ref:drums'. On a full mix ('render', 'ref') the lanes are band activity, so bass, pads and leads also register as hits. |
| analyze_envelopeC | Level per step as digits 0-9 (max 8 bars); band = sub|bass|lowmid|mid|himid|air to isolate a range. Reveals sidechain pumping, gating, note rhythm. |
| analyze_spectrumC | 1/3-octave levels + strongest peaks (with note names) over a window. Window: span=[a, b] in fractional bars, end exclusive (span=[5, 6] = all of bar 5, [5, 5.25] = its first beat), or bars=[a, b] inclusive like the other analysis tools, or t0/t1 in seconds. |
| analyze_timbreA | Describe the sound in a window: envelope, pitch, harmonic profile -> waveform guess, brightness/filter, noisiness, stereo width. Use on isolated sounds (stems, sound bank, soloed tracks) for sound design. Window: span=[a, b] in fractional bars, end exclusive (span=[5, 6] = all of bar 5, [5, 5.25] = its first beat), or bars=[a, b] inclusive like the other analysis tools, or t0/t1 in seconds. |
| analyze_formantsB | Vowel view of a voice: LPC formants F1/F2/F3 per step and the nearest vowel (max 8 bars). Use it to see what a vocal part is saying/singing and to match a vocoder / formant sound to it. |
| analyze_keyB | Key estimate (Krumhansl) over the whole source or bars [a, b]. |
| compareB | Compare two sources bar by bar on the project grid (default: your render vs the reference). Scores: chroma_sim, rhythm_corr, band dB deltas, log-mel L1, plus the worst bars and advice. Compare stems too: a='track:bass', b='ref:bass'. |
| alignB | Timing lag between two sources (onset cross-correlation, optionally in one band e.g. band='sub' for kicks). Use it to line a render up with the reference: project_set(offset_sec = offset_sec + lag). |
| spectrogramB | Write a mel spectrogram PNG with bar lines (the one non-text view). Returns the PNG path. |
| separateB | Split a source into drums/bass/other/vocals stems with demucs (GPU if available). For 'ref' the stems become 'ref:drums' etc. |
| machine_statusA | The shared machine before anything heavy (a render over a minute, separate, mimic_measure, a fit, Blender,
whisper): GPU heat and throttling, CPU, free memory, every heavy job running now in any session, and whether a new
GPU or CPU job may start. Heavy ops check it themselves and refuse with the reason; run commands outside ismail
through |
| batchB | Run many ops in order: ops = [{"op": "notes_write", "track": ..., ...}, ...] (project is implied). Mutations are atomic: if one fails the project is restored to its state before the batch. Returns each op's output. |
| stem_map_setB | Declare which reference stem each of your tracks belongs to, e.g. {"kick": "drums", "riff": "other"}. cmp_run(stems='tracks') builds your stems from these groups (no separation artefacts on your side). |
| analyze_structureA | Macro view of a whole song: arrangement map (bands + stems, one char per bar), sections lettered by similar material, repeats, loop length. source='ref' uses reference stems; 'render' uses your stem groups (default: the reference if the project has one, else your render). |
| analyze_rollB | Piano roll of any source from the same step features the comparisons score: one row per pitch, one char per 16th ('#' note starts, '=' sustains). low/high limit the pitch range (split bass / chords / lead that share a stem); rel_db = how far under the step's loudest note still counts; floor_db = absolute floor. as_notes=True returns a notes_write list (beats relative to the first bar) instead - write it, then cmp to verify. |
| notes_from_audio_loopA | Consensus transcription: average the step-note grid of |
| notes_from_audioA | Transcribe a pitch range of an audio source into a track over bars [a, b] (any length), using the same step features the comparisons score (so writing them raises note_f1 by construction). mode='replace' clears that range of the track first. Use low/high to route registers of one stem to different tracks (bass / stabs / lead). Check the result with notes_read(view='roll') and cmp_run. |
| cmp_runA | Build and store a full comparison of your latest render (A) vs the reference (B): every stem (mix, drums, bass, other, vocals), every bar, every 16th step. stems: 'tracks' (your stems from stem_map; needs a full render(stems=True)), 'demucs' (separate your render with the reference's model: apples to apples, slower), 'none' (mix only). Returns the summary vs ceiling/floor baselines. Drill in with cmp_sections, cmp_bars, cmp_worst, cmp_zoom, cmp_arrangement; cmp_list shows progress across runs. |
| cmp_summaryC | Summary of a stored comparison (default latest): per-stem metrics vs ceiling/floor baselines. |
| cmp_sectionsC | Metrics per reference section for one stem. |
| cmp_barsC | Per-bar metrics for one stem, 32 bars per page (bars=[a, b]). |
| cmp_worstB | The n worst bars of a stem, overall or by one metric (note_f1 pc_f1 attack_f1 hit_low hit_snare hit_hat shape dlevel). |
| cmp_zoomC | Step-level A vs B for bar..bar+bars-1: level digits, drum-lane hits, one row per pitch (b both, r reference only = you miss it, y yours only = extra, UPPERCASE = the note starts). layers: level bands hits notes. |
| cmp_arrangementB | Arrangement maps, yours vs reference, per stem (one char per bar): the macro structure at a glance. |
| cmp_listB | All stored comparisons with closeness per stem, oldest first: progress over time. |
| sound_compareA | A/B two sounds: 1/3-octave bands side by side (with << >> where A is >6 dB off), envelope from onset, centroid, stereo width, and one distance number. a/b are sources ('sound:', 'ref:drums', 'track:x', ...); window=[t0, t1] seconds or span=[start_bar, end_bar] (fractional) selects a slice. |
| instrument_fitA | Search instrument parameters so its sound matches a target (spectrum + envelope + width). target: a source like 'sound:refsnare' (cut one first with sound_import); instrument: dict, 'preset:x' or 'track:' (start from that track's instrument); params: {"path": [lo, hi]} e.g. {"tone_hz": [100, 250], "filter.cutoff": [300, 8000], "oscs.0.detune": [0, 40]}; notes: what to play (beats at project tempo; use the target's pitch); fmin/fmax restrict the spectral match to a range (e.g. ignore kick bleed below 70 Hz). Saves the result as sound save_as and/or sets it on apply_to_track. |
| sound_extractA | Build a clean one-shot of a repeated event by averaging every occurrence at grid position |
| sound_speakB | Text-to-speech into the sound bank with the OS's own engine: Windows SAPI (voices like David, Zira; the
default is David), macOS |
| track_fitA | Tune a track IN CONTEXT: render the track's whole stem group (stem_map) over bars [a, b] and score it against the same window of the reference stem with the perceptual (CLAP) metric plus band levels. params: {path: [lo, hi]} with 'inst.' (inst.filter.cutoff, inst.oscs.0.detune ...), 'fx..', 'volume_db' or 'pan'. 8-16 bars and 20-40 iterations is a good budget (each iteration renders the window). apply=True writes the best values into the track. |
| eq_matchA | Match a track's long-term spectrum to the reference stem over bars [a, b]: renders the track's stem group (stem_map), compares 1/3-octave levels with the same window of the reference stem, and writes a peaking EQ at octave centres (63 Hz .. 16 kHz, gains clipped to +-max_db) as the track's fx 0 (replacing an earlier eq_match EQ), plus the level difference on the fader when level=True. Best where that track dominates its stem in the window (e.g. a pad in an intro). |
| mimic_measureA | Measure an instrument from recorded notes into a mimic profile (/voices/.mimic.json), then
use it as {"type": "mimic", "profile": ""}. notes: [[source, pitch], ...] or [[source, pitch, vel,
[t0, t1]]] where source is 'sound:', 'ref', 'ref:' or a path, pitch like 'A4', vel 0..1 (how hard
it was played; default |
| ref_retuneA | Retune the reference (and its stems) onto A440 for analysis: measures the offset (or takes |
| analyze_swingA | How late the swung hi-hats land, in beats and ms (16ths: the 'a' against the 'and'; 8ths: the 'and' against the beat). Default source: the reference drum stem if there is one, else the reference/render. Use it on a reference before writing swung notes, and on 'track:' to check yours. Straight = 0. |
| analyze_kitA | Discover the pieces of a drum kit: NMF splits the drum source into k components (each a spectrum + when it
hits), prints each one's pitch region, share of energy and hit pattern per 16th folded over a |
| analyze_sectionsB | Loudness per section: rms, peak, loudest and quietest 400 ms, and the song's dynamic range. sections =
{"intro": [1, 4], "climax": [25, 28], ...} (bars inclusive); omitted = blocks of |
| levels_from_refA | Set faders from the reference's stem balance instead of by feel: for every stem in the stem map, compares its level against the sum of the mapped stems in the reference (demucs stems) and in your render (track stems), and proposes a fader change per track (the same change for every track of one stem). apply=True writes them. Needs stem_map_set, separate(source='ref') and render(stems=True) over the same bars. Re-render and re-check after applying: compressors and the limiter move levels again. |
| live_startA | Start the live engine for |
| live_stopA | Fade out and stop the live engine (closes any recording). Waits until the process has gone; an engine that does not stop (a dead audio device, a hung process) is killed with its render workers. |
| live_statusA | Where the live set is: bar heard, each deck (on air or cued, fader, eq, filter, level, song), each track not on a deck (instrument, fader, level now and the loudest over the last 10 s, playing/next clip, render cost, errors), the safety chain's gain reduction since the last status, runway (the last scheduled change and what loops after it) and render health (backlog, late events, underruns). A STALLED line means the audio device stopped asking for audio. deck='B' lists that deck's tracks and buses instead. Problems that arrive between calls (a dropped note, a stall) are added to the next reply of any live op. |
| live_trackA | Create or change a live track. instrument: a dict (instrument_help), 'preset:' (presets_list),
{'type': 'code', 'voice': ''} (voices_list) or 'track:' (copy from the folder's project.json).
fx: the track's whole effect chain, a list of effect dicts (fx_help), or 'track:' to copy a project
track's chain; [] removes all. Sidechain/duck/vocoder sources must be live tracks. sends: {bus: dB} replaces
the track's sends (post-fader; live_bus creates buses). A new instrument or chain on a playing track takes
over at |
| live_deckA | A deck's channel strip (a deck = a group of tracks, e.g. a loaded song). volume_db: fader (-60 or less =
off). low_db / mid_db / high_db: 3-band isolator, bands split at 250 Hz and 2.5 kHz; -40 or less kills the
band. filter: one knob, -1 (low-pass closed) .. 0 (off) .. +1 (high-pass closed). These glide over ramp_beats
from |
| live_loadA | Load an ismail song (its project folder: tracks, instruments, effects, buses, notes) onto a deck; every track starts on the same bar. bars=[a, b] takes a section. It plays at the house tempo (re-rendered, not time-stretched) and loops unless loop=False. Cued (off air) by default while another deck is on air: listen with live_listen(deck=...), then bring it in with live_transition. The song's mix comes over as rendered: its automation (effect params and volume as ramps, instrument params rendered with the notes, the master fade; repeated every pass on a looping deck) and its group buses (a reverb on a drum bus keeps the dry drums). Placed audio clips and the master effect chain do not (the reply lists what was left out). Loading replaces what a cued deck held. |
| live_transitionA | Queue a mix from one deck to another as ramps on their strips, starting at |
| live_busA | Create or change a send bus (a shared effect such as one hall for many tracks; tracks feed it with live_track(sends={bus: dB})). fx: its chain (list, or 'bus:' / 'track:' to copy from project.json); reverbs and delays on a bus output only the wet signal unless given 'dry'. A replaced chain's tail rings out. remove=True deletes it and the sends into it. |
| live_fxA | Change params of one effect in a live chain. target: a track, or 'bus:'; index: 0-based position in
its chain (live_status lists chains), or the effect's type ('filter'; 'filter:2' = the second filter), which
survives edits to the chain. Automatable params (fx_help lists them: cutoff, mix, gain_db, depth ...)
glide from their current value to the new one over ramp_beats, starting at |
| live_queueA | Queue clips (a batch; all or nothing). Each clip: {track, notes and/or lanes, bars, loop, at}. notes: ' <dur_beats> [vel]; ...' with beats from the clip start (same as notes_write). lanes: {pitch: 'x...x...'} step strings (same as pattern_write), step = beats per char (0.25). bars (or beats, for odd lengths): clip length; default = whole bars covering the notes. It repeats every length. loop: repeats before it ends (default 'forever': it plays until something replaces it). expr: {lane: [[beat, value], ...]} expression for a performer voice (bend in semitones, vib in cents ...), beats from the clip start; the voice's INFO lists its lanes. at: next_bar (default) | next_beat | next_2 | next_4 | next_8 | next_16 (phrase boundaries counted from bar 1) | asap | bar: | after: (when that clip ends; chain clips to pre-program an arc) | after:# (when item k of this same batch ends: a whole arc in one call, no ids needed). A clip on a track replaces whatever that track would play from its start (a playing clip is cut there; its last notes ring out). {track, stop: true, at} silences a track. The reply gives each clip's id and the exact bar it lands on (moved later when its first notes cannot render in time) and the runway: how long until the queue stops changing. Before a long job (sound design, fitting), queue enough that the runway covers it. |
| live_cancelA | Remove clips that have not started yet (ids from live_queue / live_view). A playing clip cannot be cancelled: queue {track, stop: true} or a replacement instead. |
| live_viewB | The queue ahead: which clip each track plays at each bar for the next |
| live_listenA | Hear the live output: analyse the last |
| live_recordB | Record the live output (after the safety chain) to /live/rec_.wav; on=False stops it. |
| live_parityA | Does this song play on a deck as it renders in the studio? Renders bars=[a, b] in the studio and plays them on a silent engine of its own (no live_start needed, nothing is late), then compares each track, each bus and the mix: level, envelope, octave bands, and the residual (how close to sample for sample; -100 dB and below is identical; under -80 dB nobody can tell). Judged from the second bar: in the first, the studio rings in what came before the window and a deck starts clean (its own line, 'edge'). tracks limits both sides to those tracks. Takes about as long as rendering the bars twice. |
| live_deviceA | Move a running set to another audio output without stopping it: the timeline, the queue and the audio mixed ahead carry on, with a gap of about a second. device: 'default' (the system's default output now: use it after connecting a Bluetooth speaker), a name or part of one ('JBL'), an index, or 'none'. With device 'default' the engine also follows the system default by itself (checked every few seconds; follow=False stops that), and a device that stops taking audio (a speaker switched off) falls back to the default. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 97 tools
Many tools share prefixes and overlapping purposes across analysis, comparison, live, and sound families (e.g., compare vs cmp_run, analyze_notes vs notes_from_audio, track_fit vs eq_match vs instrument_fit). Despite detailed descriptions, the sheer number of similar tools makes misselection likely.
All tool names use consistent snake_case with domain-prefix patterns (analyze_*, cmp_*, live_*, notes_*, track_*, sound_*). The convention is predictable and readable throughout, even if not strictly verb_noun.
With 97 tools, the surface far exceeds the typical 3-15 range and even the 25+ threshold for 'too many'. While the domain is complex, this count is extreme and likely overwhelming for an agent.
The surface covers project lifecycle, tracks, notes, instruments, effects, automation, deep analysis, comparison, live performance, sound design, machine status, undo, batch, and lexicon. No obvious gaps or dead ends for a DAW agent.