Runtime YAML
Configuration
Volt loads selected plugin settings from YAML files under user/.
Edit workspace discovery, ACP clients, UI defaults, and oil behavior without
recompiling the user library. The SDL shell watches these files and reloads
them while the editor is running.
Filter guides, hooks, and config keys.
Overview
The compiled user library still owns package metadata, commands,
hooks, and keybindings. For settings that change often, Volt also supports a
runtime config layer implemented in user/config.rs.
user/config.yaml and the referenced child files under
user/config/ are picked up while Volt is running. Rust modules read
the latest values through crate::config::load().
Theme colors and shared editor options still live in
user/themes/*.toml and user/themes/global.toml.
See the user packages guide
for theme and statusline customization.
File layout
The master file is a reference table, not a YAML include. Volt
reads user/config.yaml, then loads each child path listed there.
# user/config.yaml
workspace: config/workspace.yaml
acp: config/acp.yaml
ui: config/ui.yaml
oil: config/oil.yaml
At startup the host resolves the config root by walking upward from the
executable directory and preferring the workspace user/ directory
when a Cargo.toml is found nearby.
Config sections
Click a card above or use the tabs below to inspect each child file. Every
example matches the repository defaults in user/config/.
Workspace discovery
Consumed by user/workspace.rs when building the project picker.
# user/config/workspace.yaml
project_search_roots:
- path: P:\
max_depth: 4
- path: W:\
max_depth: 4
- path: C:\Users\sam\
max_depth: 4
Paths that do not exist on the current machine are filtered out automatically.
ACP clients
Consumed by user/acp.rs when shaping the agent picker and launching sessions.
# user/config/acp.yaml
clients:
- id: agent
label: Cursor Agent(ACP)
command: agent
args:
- acp
- --yolo
- id: codex
label: Codex (ACP)
command: codex-acp
Optional fields: env as key/value pairs and cwd for the child process.
UI, terminal, and pane defaults
Read by user/picker.rs, user/ligatures.rs, user/pane.rs, and user/terminal.rs.
# user/config/ui.yaml
picker_truncate_strategy: auto
ligatures_enabled: true
pane:
golden_ratio: true
terminal:
program: nu
args: []
Picker truncation values
end-ellipsis, start-ellipsis, middle-ellipsis, shrink-directories, shrink-all, file-name, file-name-with-parent, parent-initial-file-name, shrink-leading-keep-tail, full, auto
Oil directory browser
Consumed by user/oil.rs through feature_spec().
# user/config/oil.yaml
defaults:
show_hidden: false
sort_mode: type-then-name
trash_enabled: false
keybindings:
open_entry: Enter
open_vertical_split: Ctrl+\\
prefix: g
toggle_hidden: "."
Sort modes: type-then-name or type-then-name-desc. All oil chords use the same canonical Ctrl+ prefix format as package keybindings.
Plugins backed by YAML today
| Plugin module | Config section | What changes at runtime |
|---|---|---|
workspace.rs | workspace | Project search roots and depth |
acp.rs | acp | ACP client list and launch commands |
picker.rs | ui | Picker label truncation strategy |
ligatures.rs | ui | Font ligature enablement |
pane.rs | ui.pane | Golden-ratio split sizing |
terminal.rs | ui.terminal | Default shell program and args |
oil.rs | oil | Hidden files, sort mode, trash, chords |
Hot reload
The SDL shell fingerprints user/config.yaml and every referenced
child file on a short polling interval. When a file changes on disk, the next
frame marks the config stale and plugin modules see the updated values the
next time they call crate::config::load().
Packaged builds copy the user/ tree next to the executable, so
edit the staged user/config/ directory beside volt
when you are not running from a development checkout.
How plugins read config
First-party plugins do not parse YAML directly. They call the shared loader and map typed sections into SDK exports:
// user/oil.rs
pub fn feature_spec() -> OilFeatureSpec {
let config = crate::config::load().oil;
OilFeatureSpec {
defaults: config.defaults.oil_defaults(),
keybindings: config.keybindings.oil_keybindings(),
// ...
}
}
// user/terminal.rs
pub fn default_shell_program() -> String {
let terminal = crate::config::load().ui.terminal;
if terminal.program.trim().is_empty() {
default_shell_program_fallback()
} else {
terminal.program
}
}
Add config to your plugin
To expose YAML-backed settings for a new or existing plugin, extend the shared
loader in user/config.rs and read it from your module.
Create user/config/myplugin.yaml and add a pointer in the master file:
# user/config.yaml
myplugin: config/myplugin.yaml
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct MyPluginSection {
pub greeting: String,
}
// In MasterConfig:
myplugin: Option<String>,
// In load_from_root:
if let Some(path) = master.myplugin.as_deref() {
config.myplugin = read_section::<MyPluginSection>(root_dir, path)
.unwrap_or_default();
}
pub fn greeting_message() -> String {
crate::config::load().myplugin.greeting
}
Use the value when building commands, feature specs, or exported SDK types. Keep parsing in config.rs so every plugin shares one schema and reload path.
Commands, hooks, and keybindings still require a rebuild because they are compiled into the user shared library. YAML is for data that should change quickly: launch commands, search roots, truncation modes, and feature defaults.