Daemon & Hot-Reload Guide
Conductor v2.0.0 introduces a production-ready background daemon service with lightning-fast configuration hot-reloading.
Architecture Overview
The Conductor daemon runs as a background service, providing:
- Hot-Reload: Configuration changes applied in 0-10ms without restart
- IPC Control: Control via CLI (
conductorctl) or GUI - State Persistence: Automatic state saving with atomic writes
- Crash Recovery: Auto-restart with KeepAlive (when using LaunchAgent)
- Menu Bar Integration: macOS menu bar for quick actions
Starting the Daemon
Via GUI (Recommended)
Open the Conductor GUI app - the daemon starts automatically in the background.
Via CLI
# Start daemon in foreground
conductor
# Start daemon in background
conductor &
# Check if daemon is running
ps aux | grep conductor | grep -v grep
# Check daemon status via IPC
conductorctl status
Via LaunchAgent (Auto-Start)
See macOS Installation Guide for LaunchAgent setup.
Controlling the Daemon
conductorctl CLI
The conductorctl command provides full control over the daemon:
Status
# Human-readable status
conductorctl status
# Example output:
# Conductor Daemon Status:
# State: Running
# Uptime: 1h 23m 45s
# Config: /Users/you/.config/conductor/config.toml
# Last Reload: 2m 15s ago
# IPC Socket: /tmp/conductor.sock
# PID: 12345
JSON output (for scripting):
conductorctl status --json
# Example output:
# {
# "state": "Running",
# "uptime_seconds": 5025,
# "config_path": "/Users/you/.config/conductor/config.toml",
# "last_reload_seconds": 135,
# "ipc_socket": "/tmp/conductor.sock",
# "pid": 12345
# }
Reload Configuration
# Reload config (hot-reload in 0-10ms)
conductorctl reload
# Example output:
# Config reloaded successfully in 3ms
# Loaded 15 mappings across 3 modes
This applies configuration changes without restarting the daemon or interrupting active MIDI connections.
Performance: Config reload completes in 0-10ms on average (vs. ~500ms for full restart).
Validate Configuration
# Validate config without reloading
conductorctl validate
# Example output (success):
# ✓ Config is valid
# Found 3 modes with 15 total mappings
# Found 2 global mappings
# No errors detected
# Example output (error):
# ✗ Config validation failed:
# Error in mode 'Default', mapping 3:
# Invalid note number: 128 (must be 0-127)
This is useful for:
- Testing config changes before applying
- CI/CD validation
- Pre-commit hooks
Stop Daemon
# Graceful shutdown
conductorctl stop
# Example output:
# Daemon stopped gracefully
The daemon saves its state before shutting down.
Ping (Latency Check)
# Check IPC round-trip latency
conductorctl ping
# Example output:
# Pong! Round-trip: 0.43ms
This verifies the daemon is responsive and measures IPC performance.
Configuration Hot-Reload
How It Works
- File Watcher: Monitors
~/.config/conductor/config.tomlfor changes - Debouncing: 500ms debounce window to avoid redundant reloads
- Parsing: Validates TOML syntax and config structure
- Atomic Swap: Replaces active config with new one in a single operation
- Reload Time: 0-10ms typical (measured)
Workflow
Edit your config file:
# Open in your editor
code ~/.config/conductor/config.toml
# Make changes and save
Automatic reload happens within 500-510ms of saving:
[2025-11-14 10:30:15] Config file changed
[2025-11-14 10:30:15] Waiting 500ms for debounce...
[2025-11-14 10:30:15] Reloading config...
[2025-11-14 10:30:15] Config reloaded successfully in 3ms
Or manually trigger reload:
conductorctl reload
What Gets Reloaded
- ✅ All mappings (modes + global)
- ✅ Device settings
- ✅ LED schemes
- ✅ Advanced settings (timeouts, thresholds)
- ❌ IPC socket path (requires daemon restart)
- ❌ Auto-start settings (requires LaunchAgent reload)
Error Handling
If config reload fails:
# Example: Invalid TOML syntax
conductorctl reload
# Output:
# ✗ Config reload failed: TOML parse error
# Error at line 42: expected '=' after key 'trigger'
# Previous config still active (no changes applied)
The daemon keeps the previous valid config and continues running.
State Persistence
The daemon automatically saves state to ~/.config/conductor/state.json:
What Gets Saved
- Current mode
- Active LED scheme
- Per-app profile assignments
- Last known device connection
Atomic Writes
State is saved using atomic writes to prevent corruption:
- Write to temporary file:
state.json.tmp - Verify write succeeded
- Rename to
state.json(atomic operation) - SHA256 checksum for integrity
Emergency Save
The daemon registers signal handlers to save state on:
SIGTERM(graceful shutdown)SIGINT(Ctrl+C)SIGHUP(hangup)
IPC Protocol
The daemon uses Unix domain sockets for inter-process communication.
Socket Location
Default: /tmp/conductor.sock
Protocol
Format: JSON messages over Unix socket
Request:
{
"command": "reload",
"args": {}
}
Response:
{
"status": "success",
"message": "Config reloaded successfully",
"duration_ms": 3
}
Available Commands
status- Get daemon statusreload- Reload configurationvalidate- Validate config without reloadingstop- Graceful shutdownping- Latency check
Security Limits
The IPC protocol enforces security limits to prevent abuse:
Request Size Limit: 1MB (1,048,576 bytes)
Requests exceeding this limit will be rejected with error code 1004 (InvalidRequest):
{
"status": "error",
"error": {
"code": 1004,
"message": "Request too large: 1500000 bytes exceeds maximum of 1048576 bytes (1MB)",
"details": {
"request_size": 1500000,
"max_size": 1048576,
"security": "Request rejected to prevent memory exhaustion"
}
}
}
Why this limit exists: Prevents memory exhaustion denial-of-service attacks where an attacker sends arbitrarily large requests to consume daemon memory.
Is 1MB enough?: Yes. Typical IPC requests are:
status: ~200 bytesreload: ~100 bytesvalidate: ~150 bytesping: ~50 bytes
Even large configuration payloads are well under 100KB. The 1MB limit provides 10x safety margin.
Performance Metrics
The daemon tracks and reports performance metrics:
Config Reload Latency
conductorctl status --json | jq '.metrics.reload_latency_ms'
# Output: 3.2
Targets:
- ✅ <10ms: Excellent (target met in v2.0.0)
- ⚠️ 10-50ms: Acceptable
- ❌ >50ms: Investigate
IPC Round-Trip
conductorctl ping
# Output: Pong! Round-trip: 0.43ms
Targets:
- ✅ <1ms: Excellent (target met in v2.0.0)
- ⚠️ 1-5ms: Acceptable
- ❌ >5ms: Investigate
Memory Usage
ps aux | grep conductor | awk '{print $6/1024 " MB"}'
# Output: 8.2 MB
Targets:
- ✅ 5-10MB: Normal (daemon only)
- ⚠️ 10-20MB: Acceptable
- ❌ >20MB: Investigate memory leak
CPU Usage
top -pid $(pgrep conductor) -stats cpu -l 1 | tail -1
# Output: 0.3%
Targets:
- ✅ <1%: Idle (no MIDI activity)
- ✅ <5%: Active (processing MIDI events)
- ⚠️ 5-10%: Heavy load
- ❌ >10%: Investigate
Menu Bar Integration (macOS)
The daemon includes an optional menu bar icon:
Features
- Status indicator: Running (green), Stopped (gray), Error (red)
- Quick actions:
- Pause/Resume
- Reload Config
- Open GUI
- Quit
Enable Menu Bar
In GUI Settings:
- Go to Settings → General
- Enable “Show menu bar icon”
- Click Save
Or edit config:
[settings]
show_menu_bar = true
Troubleshooting
Daemon Won’t Start
Check if already running:
ps aux | grep conductor
If already running, stop it first:
conductorctl stop
Check logs:
# If using LaunchAgent
tail -f /tmp/conductor.err
# If running manually
conductor # Run in foreground to see errors
Config Won’t Reload
Validate config syntax:
conductorctl validate
Check file watcher:
# Manually trigger reload
conductorctl reload
Check file permissions:
ls -l ~/.config/conductor/config.toml
# Should be readable by your user
High Latency
Check IPC performance:
conductorctl ping
Check system load:
top -l 1 | head -10
Restart daemon:
conductorctl stop
conductor &
State Not Persisting
Check state file:
ls -l ~/.config/conductor/state.json
cat ~/.config/conductor/state.json | jq
Check file permissions:
# State directory should be writable
ls -ld ~/.config/conductor
Advanced Configuration
Custom IPC Socket Path
Edit daemon config (future feature):
[daemon]
ipc_socket = "/tmp/my-custom-conductor.sock"
Then tell conductorctl to use it:
export MIDIMON_SOCKET=/tmp/my-custom-conductor.sock
conductorctl status
Custom Config Path
Run daemon with custom config:
conductor --config /path/to/config.toml
Or set environment variable:
export MIDIMON_CONFIG=/path/to/config.toml
conductor
Logging
Enable debug logging:
DEBUG=1 conductor
Output includes:
- Config reload events
- IPC requests/responses
- State persistence operations
- MIDI event processing (verbose)
Next Steps
- Per-App Profiles - Automatic profile switching
- CLI Reference - Complete CLI documentation
- Configuration Overview - Config file reference
- Performance Tuning - Optimization guide
Last Updated: November 14, 2025 (v2.0.0)