| 1 | # SplitFire AI Agent |
| 2 | |
| 3 | Play like you mean it. |
| 4 | |
| 5 | A music assistant that speaks the [Agent Client Protocol](https://agentclientprotocol.com) and |
| 6 | ships its own terminal UI. It covers music theory, production and DAW workflow, the music |
| 7 | industry, culture and history, and live shows, and it does the arithmetic with tools instead of |
| 8 | guessing: note spelling, chords, keys, tempo maths and audio measurements are computed, not |
| 9 | recalled by the model. |
| 10 | |
| 11 | It runs on [Onde Inference](https://ondeinference.com) (Onde Cloud). Built on the official |
| 12 | [Rust SDK](https://github.com/agentclientprotocol/rust-sdk). |
| 13 | |
| 14 | ## What it can do |
| 15 | |
| 16 | **Music theory tools** (exact, read-only, no permission prompts) |
| 17 | |
| 18 | | Tool | Does | |
| 19 | |------|------| |
| 20 | | `theory_scale` | Spell a scale or mode from any root, with intervals, key signature and the diatonic chords (C# major really has E# and B#) | |
| 21 | | `theory_chord` | Spell a chord symbol (`F#m7b5`, `Bb13#11/D`, `C6/9`) or identify chords from notes | |
| 22 | | `theory_transpose` | Transpose chords or notes by semitones or between keys, with correct enharmonic spelling | |
| 23 | | `theory_progression` | Roman numerals, functions, secondary dominants, borrowed chords and ranked key candidates; or realize numerals (`ii7 V7 Imaj7`) in a key | |
| 24 | | `theory_tempo` | Note values (straight, dotted, triplet) in ms and Hz, bar length, sample counts, bpm from a loop length | |
| 25 | | `theory_pitch` | Note name, MIDI number and frequency conversion, with cents offset and adjustable A4 | |
| 26 | |
| 27 | **Audio analysis**: `analyze_audio` reads WAV, FLAC, MP3, OGG, M4A/AAC and AIFF and reports |
| 28 | duration, sample rate, channels, peak, RMS, integrated loudness (LUFS), and estimated tempo |
| 29 | (including half and double time) and key with a confidence. Tempo and key are signal-analysis |
| 30 | estimates, and the agent says so. Audio attached to a prompt is saved to a scratch file and the |
| 31 | model is given its path. |
| 32 | |
| 33 | **Project files**: `read_file`, `write_file`, `edit_file`, `list_directory`, `run_command`, with |
| 34 | permission prompts for writes and commands, multi-root workspaces, and the client's own |
| 35 | `fs/*` and `terminal/*` methods when the editor offers them. |
| 36 | |
| 37 | **Slash commands**: `/analyze <file>`, `/chords <progression>`, `/practice <goal>`, and |
| 38 | `/stems <file>` when stem separation is installed. |
| 39 | |
| 40 | **Sessions** persist across restarts: `session/list`, `load` (with tool calls and their output |
| 41 | replayed), `resume`, `close` and `delete`. |
| 42 | |
| 43 | ### Stem separation (optional) |
| 44 | |
| 45 | Stem separation is not built in. Install [`demucs`](https://github.com/splitfireai/demucs-rs) and |
| 46 | put it on your `PATH` (or set `SPLITFIRE_DEMUCS_BIN`). SplitFire starts `demucs --mcp` the first |
| 47 | time a session begins, and offers `separate_stems` and `list_models` as tools. Separation runs |
| 48 | locally and can take minutes; the first use of a model downloads its weights, and the permission |
| 49 | prompt tells you how large the download is. If `demucs` is missing or fails to start, SplitFire |
| 50 | logs one line and carries on without it. An MCP server named `demucs` supplied by your editor |
| 51 | takes precedence. `SPLITFIRE_MCP_DEMUCS=off` disables it. |
| 52 | |
| 53 | Apps that embed SplitFire decide what stems mean the same way. They pass a `demucs` MCP server of |
| 54 | their own in `session/new`, or return no built-in servers from their `Profile` and write their own |
| 55 | stems guidance. The SplitFire AI Audio Separator supplies its own executable in `--mcp` mode on |
| 56 | its on-device engine; KaroKowe Jam and KaroKowe Karaoke supply none and point users at their in-app flows. The stem |
| 57 | hooks only check the tool name `mcp__demucs__separate_stems` and the `list_models` and |
| 58 | `separate_stems` result shapes (`structuredContent.models[]{id,size_mb,cached}` and |
| 59 | `structuredContent.stems[].path`). |
| 60 | |
| 61 | Separate only music you have the right to work with. |
| 62 | |
| 63 | ### Other MCP servers |
| 64 | |
| 65 | Stdio MCP servers configured in your editor are connected for each session. Their tools show up |
| 66 | as `mcp__<server>__<tool>`, ask for permission unless the server marks them read-only, stream |
| 67 | progress into the tool call, and are cancelled when you cancel the turn. A server that fails to |
| 68 | start is skipped. |
| 69 | |
| 70 | ## Embedding |
| 71 | |
| 72 | A sandboxed app (an App Store build) can't launch the agent as a subprocess, so it runs it |
| 73 | in-process: it serves [`SplitFire`] with `ed_acp::serve` over an in-memory channel and talks to |
| 74 | it as a regular ACP client. The app-neutral half of that — the client, sessions, permissions, |
| 75 | the key store — lives in the published [`ed-acp-tauri`](https://crates.io/crates/ed-acp-tauri) |
| 76 | crate. The SplitFire-only pieces live here, behind the off-by-default `embed` feature: |
| 77 | |
| 78 | ```toml |
| 79 | splitfire-agent = { version = "1.2", default-features = false, features = ["embed"] } |
| 80 | ed-acp-tauri = "1.3" |
| 81 | ed-acp = "1.3" |
| 82 | ``` |
| 83 | |
| 84 | `splitfire_agent::embed` has no `tauri` dependency; anything that needs the app's identifier |
| 85 | takes it as a `&str`: |
| 86 | |
| 87 | - `embed::fetch_key(url, access_token, app_identifier)` asks the SplitFire API for the signed-in |
| 88 | user's Onde key, rejecting with `SIGN_IN_REQUIRED` or `UNAVAILABLE`. |
| 89 | - `embed::splitfire_mcp(url, access_token, app_identifier)` builds the SplitFire API's MCP server |
| 90 | for a session (nothing without a token). |
| 91 | - `embed::trim` drops `run_command` from the toolsets (`without_commands`) and swaps the stems |
| 92 | paragraph (`replace_no_stems`) for a profile that runs where it can't start processes, e.g. |
| 93 | iOS. |
| 94 | |
| 95 | All embedding apps must resolve a single `ed-acp` 1.3.x, so an `Arc<dyn Profile>` from this crate |
| 96 | matches the one `ed-acp-tauri` expects. |
| 97 | |
| 98 | ## Install and set up |
| 99 | |
| 100 | ```sh |
| 101 | cargo build --release # -> target/release/splitfire-agent |
| 102 | splitfire-agent --setup |
| 103 | ``` |
| 104 | |
| 105 | `--setup` asks for your Onde credentials (`app-id:app-secret`, from |
| 106 | [ondeinference.com](https://ondeinference.com/root/login): register an app and assign a model), |
| 107 | checks them against Onde Cloud, and stores them in the platform config directory |
| 108 | (`~/Library/Application Support/splitfire-agent/env` on macOS, `~/.config/splitfire-agent/env` on |
| 109 | Linux, `%APPDATA%\splitfire-agent\env` on Windows). Alternatively set `ONDE_API_KEY`. |
| 110 | |
| 111 | Editors that can run terminal auth (`clientCapabilities.auth.terminal`) are offered `--setup` as |
| 112 | the authentication method. Until a key exists the agent answers `session/new`, `session/load`, |
| 113 | `session/resume` and `session/prompt` with `AUTH_REQUIRED`. A key stored by `--setup` is picked up |
| 114 | without a restart, whether or not the editor calls `authenticate` afterwards. `logout` removes the |
| 115 | stored key (a key exported as `ONDE_API_KEY` stays in effect). |
| 116 | |
| 117 | ## Use from the terminal |
| 118 | |
| 119 | ```sh |
| 120 | cd my-project |
| 121 | splitfire-agent # asks before edits and commands |
| 122 | splitfire-agent --yolo # approves everything |
| 123 | ``` |
| 124 | |
| 125 | Enter sends, Esc cancels a running turn, Up/Down and PgUp/PgDn scroll, Ctrl-C or `/quit` exits. |
| 126 | Approval prompts take `y` (allow), `a` (always allow this tool), `n` (reject) or `r` (always reject this tool). The TUI is itself |
| 127 | an ACP client: it starts `splitfire-agent --acp` as a subprocess. |
| 128 | |
| 129 | ## Use with Zed |
| 130 | |
| 131 | Editors launch the agent with piped stdio, which selects ACP mode (or pass `--acp`). |
| 132 | |
| 133 | ```json |
| 134 | { |
| 135 | "agent_servers": { |
| 136 | "splitfire-agent": { |
| 137 | "command": "/path/to/splitfire-agent", |
| 138 | "args": ["--acp"] |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | ``` |
| 143 | |
| 144 | The model picker lists the models Onde Cloud serves for your app. Editors that support boolean |
| 145 | config options also get an "Auto-approve actions" toggle per thread. Threads are saved to disk, so |
| 146 | Zed can reopen and import them after a restart; a thread whose history is gone reopens empty with |
| 147 | a short notice instead of failing. Linked files (`@`-mentions and selections) are inlined, only |
| 148 | the selected lines for a selection; links to audio files are handed to `analyze_audio` instead. |
| 149 | Token usage is reported against `SPLITFIRE_CONTEXT_WINDOW`. |
| 150 | |
| 151 | ## Configuration |
| 152 | |
| 153 | | Env var | Default | | |
| 154 | |---------|---------|---| |
| 155 | | `ONDE_API_KEY` | from the config `env` file | `app-id:app-secret`; required | |
| 156 | | `SPLITFIRE_BASE_URL` | `https://cloud.ondeinference.com/v1` | override the OpenAI-compatible endpoint | |
| 157 | | `SPLITFIRE_MODEL` | `onde-kkk` | default model | |
| 158 | | `SPLITFIRE_MODELS` | listed from `/models` | comma-separated list for the model picker | |
| 159 | | `SPLITFIRE_YOLO` | unset | `1` skips permission prompts (the default for each thread's auto-approve toggle) | |
| 160 | | `SPLITFIRE_CONTEXT_WINDOW` | `128000` | context size reported in `usage_update` | |
| 161 | | `SPLITFIRE_STEMS_DIR` | the app's iCloud and local folders | where `list_separations` looks for separated songs | |
| 162 | | `SPLITFIRE_DATA_DIR` | platform config dir | where `env`, sessions and scratch audio live | |
| 163 | | `SPLITFIRE_SURFACE` | `acp` | `tui` when launched by the terminal UI; used in the commit trailer | |
| 164 | | `SPLITFIRE_DEMUCS_BIN` | `demucs` on `PATH` | stem separation binary | |
| 165 | | `SPLITFIRE_MCP_DEMUCS` | on | `off` disables stem separation | |
| 166 | | `RUST_LOG` | unset | logs go to stderr | |
| 167 | |
| 168 | `splitfire-agent --list-models` prints the models your endpoint serves. |
| 169 | |
| 170 | ## Development |
| 171 | |
| 172 | ```sh |
| 173 | cargo fmt -- --check |
| 174 | cargo clippy --all-targets -- -D warnings |
| 175 | cargo test --locked |
| 176 | ``` |
| 177 | |
| 178 | These are exactly what CI runs. To catch the formatting check before you push, enable the |
| 179 | tracked pre-commit hook once per clone: |
| 180 | |
| 181 | ```sh |
| 182 | git config core.hooksPath .githooks |
| 183 | ``` |
| 184 | |
| 185 | SplitFire is built on the Onde Agent Platform crates in the |
| 186 | [`onde-ed`](https://github.com/ondeinference/ed) workspace, checked out next to this repository: |
| 187 | `ed-acp` (the ACP server: sessions, auth, the turn loop, approval, workspace tools), |
| 188 | `ed-acp-tui` (the terminal UI) and `ed-mcp` (MCP over `rmcp`). This crate holds the music. |
| 189 | |
| 190 | The integration tests run the real binary against a scripted mock model and a test MCP server |
| 191 | (`src/bin/mcp_stdio_stub.rs`, from `ed-mcp`). `tests/acp_compliance.rs` runs the platform's ACP |
| 192 | conformance suite from `ed-acp-testkit` plus SplitFire's own tests. `tests/live.rs` talks to Onde |
| 193 | Cloud and is skipped unless `ONDE_API_KEY` is set. |
| 194 | |
| 195 | ``` |
| 196 | src/main.rs flags and mode dispatch (ACP, TUI, --setup, --list-models) |
| 197 | src/profile.rs the ed-acp Profile: identity, tools, commands, built-in demucs |
| 198 | src/prompt.rs system prompt and slash commands |
| 199 | src/tools/ theory, audio analysis and separation-library tools |
| 200 | src/theory/ pitch, interval, scale, chord, key, tempo |
| 201 | src/audio/ decoding and analysis |
| 202 | registry/ ACP registry submission |
| 203 | ``` |
| 204 | |
| 205 | ## Copyright |
| 206 | |
| 207 | © 2026 [Splitfire AB](https://5mb.app) ([SplitFire AI](https://splitfire.ai)). Licensed under the |
| 208 | Apache License, Version 2.0; see [LICENSE](LICENSE). |