ADR-022: Device Discovery, Bindings & Multi-Protocol Routing
Status
Draft (For Discussion)
Relationship to Other ADRs
- Extends ADR-021 (I/O Device Qualification): ADR-021 established input/output port bindings, auto-pairing, alias-based output resolution, and direction classification for MIDI devices. ADR-022 builds on this foundation by: generalising the model across protocols, introducing the three-layer conceptual framework, adding discovery as a first-class UI concern, and integrating LLM tooling for binding management. ADR-021's decisions (D1–D11) remain in force; ADR-022 does not modify them.
- Amends ADR-013 (LLM Canvas): Extends Phase 3A
device-topologyartifact tobinding-topology; addsdiscovery-overviewartifact type. - Amends ADR-017 (Unified Mapping Experience): Extends D1 (Learn mode) with source annotation; extends D6 (Events-to-Mapping bridge) to Events-to-Binding bridge; ratifies D7 rename.
- Amends ADR-018 (Knowledge Layer): Specifies binding schema content for L1; device profile retrieval scope for L2; community profile scope for L3.
- Amends ADR-019 (Unified Workspace Filtering): Extends D4 (DeviceStatusPills→BindingPills); extends D10 (Unmapped→Unbound Sources).
- Amends ADR-020 (V3 Signal Flow): Extends DeviceGroup to support binding-scoped rendering and Unbound Sources group.
- Supersedes terminology in ADR-009 where "device" referred to what is now called a "binding."
Context
Problem Statement
Conductor's device model was built around MIDI and doesn't generalise cleanly to OSC, HID, or ArtNet. ADR-021 solved the immediate I/O qualification gap for MIDI, but the whitepaper "Device Discovery, Bindings & Multi-Protocol Routing" (March 2026) identified broader architectural gaps:
- Terminology conflation: The term "device" means three different things (physical unit, OS port, config object). This confuses users and LLMs alike.
- No Discovery layer in the UI: The GUI shows connected devices but not all available ports. Unbound ports are invisible.
- No Binding CRUD in the GUI: There is no GUI path to create, edit, or delete a binding (
DeviceIdentityConfig). The only paths are LLM chat (limitedconductor_create_device_identity), manual TOML editing, or CLI. - MIDI-only binding model: The
DeviceIdentityConfigstruct and all its matchers are MIDI-centric. HID gamepads use a separate discovery path. OSC endpoints are configured as action parameters, not bindings. ArtNet isn't supported at all. - LLM tooling gaps: No update/delete for bindings, no discovery awareness, no channel/protocol support in create, skills teach wrong terminology.
- Bridge transparency: Devices behind MIDI bridges (DIN cables to audio interfaces) are invisible. No UI affordances guide users through identification.
Three-Layer Mental Model
The whitepaper proposes a three-layer model that clarifies these concerns:
Layer 1 — Discovery: Conductor continuously scans for available endpoints across all supported protocols. Each discovered endpoint is a port — a raw communication channel the OS or network makes available. Discovery is observational: Conductor doesn't configure or claim ports.
Layer 2 — Bindings: A binding is a user-authored configuration object (DeviceIdentityConfig, proposed alias [[bindings]]) that gives a stable alias to one or more discovered ports via matcher rules. The binding's interaction pattern (receive, send, both) is derived from which matchers are present — it is not an intrinsic property of the hardware. ADR-021 D1–D3 established this for MIDI; ADR-022 generalises it.
Layer 3 — Mapping Rules: Rules that match events to actions. Rules can be scoped to a specific binding (device = "Mikro") or generic (no device filter = fires for any source). The CompiledRuleSet architecture (ADR-009 Phase 3) already supports this with device_rules: HashMap<String, Vec<CompiledRule>> + any_device_rules.
Decision
D1: Adopt Three-Layer Terminology
Terminology changes:
- "Device" in config context → "Binding" (the config object that gives an alias to ports)
- "Device" in runtime context → "Port" (what the OS discovers) or "Physical device" (the hardware)
DeviceStatusPills→BindingPills(component rename)DeviceList→BindingList(component rename)DeviceGroup(Signal Flow) → stillDeviceGroup(too embedded in ADR-020; rename deferred)DeviceIdentityConfigstruct → retain struct name for backward compat; add[[bindings]]as config alias for[[devices]]- Agent skill
conductor-device-setup→conductor-binding-setup
Config alias: The TOML parser accepts [[bindings]] as a synonym for [[devices]]. Both parse to Vec<DeviceIdentityConfig>. New documentation and LLM skills use [[bindings]] exclusively. [[devices]] is never removed from the parser.
Migration: Documentation-first. All user-facing text (UI labels, skill descriptions, error messages, docs) migrates to binding terminology. Code-level struct names change only where the cost is low and the benefit is high (components, not core types).
D2: Discovery as First-Class UI Concept
New Tauri command: get_discovered_ports returns all ports visible to the daemon across all protocols:
#[derive(Serialize)]
pub struct DiscoveredPort {
pub name: String,
pub protocol: Protocol, // Midi, Hid, Osc, ArtNet
pub direction: PortDirection, // Input, Output
pub binding: Option<String>, // alias if bound, None if unbound
pub connected: bool,
pub metadata: Option<PortMetadata>, // SysEx identity, USB VID/PID, etc.
}
New Svelte store: discoveredPorts in workspace.js — reactive list from get_discovered_ports, refreshed on port change events.
Connections view: New workspace view with two sections:
- Discovered Ports — all ports grouped by protocol, showing bound/unbound status
- Configured Bindings — all
[[bindings]]entries with health indicators (green=all ports matched, amber=partial, red=no ports matched)
D3: Multi-Protocol Binding Schema
New field on DeviceIdentityConfig: protocol: Option<Protocol> — explicitly tags the binding's protocol. When absent, inferred as MIDI (backward compat).
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
pub enum Protocol {
#[default]
Midi,
Hid,
Osc,
ArtNet,
}
Channel scope (future, low priority): channels: Option<Vec<u8>> — restricts a MIDI binding to specific channels on the matched port. Enables the bridge transparency use case (foot pedal on channel 1, keyboard on channel 2, same port).
OSC binding: For OSC, matchers are replaced by endpoint configuration:
[[bindings]]
alias = "TouchOSC"
protocol = "osc"
input = { host = "0.0.0.0", port = 9000 }
output = { host = "192.168.1.50", port = 8000 }
HID binding: For HID, matchers use the existing UsbIdentifier (VID/PID) or name matching:
[[bindings]]
alias = "Xbox"
protocol = "hid"
input = { matchers = [{ type = "NameContains", value = "Xbox" }] }
D4: Binding Pills (UI Rename)
DeviceStatusPills → BindingPills. Changes:
- Each pill represents a configured binding (not a discovered port)
- "+N unbound" badge at the end shows count of discovered ports with no binding
- Protocol icon prefix: 🎹 MIDI, 🎮 HID, 📡 OSC, 💡 ArtNet
- Click "+N unbound" opens Connections view
- Existing multi-select and linked/unlinked behavior (ADR-019) preserved
D5: Unbound Source Visibility
Event stream: Events from unbound ports get a dashed border and dimmed styling. A "Binding: none" column value replaces the device alias. An inline "Create Binding" button appears on hover.
Signal Flow: A new "Unbound Sources" group (dashed border, 50% opacity) appears below bound device groups. Shows event rate and type distribution for unbound ports. "+ Map" CTA on UnmappedRow triggers binding creation first, then mapping creation.
D6: Protocol-Level Identity Mechanisms
SysEx Identity probing: On port connection, daemon optionally sends F0 7E 7F 06 01 F7 (Identity Request) to output ports and captures replies. Results stored as PortMetadata on the DiscoveredPort. Gated behind a setting (default: off for safety).
New matcher variant: SysExIdentity { manufacturer: [u8; 3], family: u16, model: u16 } — high-specificity matcher (score 65-70) for deterministic binding resolution.
USB Identity implementation: Existing UsbIdentifier matcher (currently returns false) implemented for HID devices using gilrs VID/PID data.
D7: Bridge Detection and Event Fingerprinting
MIDI Learn source annotation: During Learn capture, an optional "What physical device produced this?" step. The text field value is stored as source_annotation on the Learn result and carried into binding suggestion.
Event fingerprinting engine: Analyzes event stream from a port over a configurable window (default 30s). Classifies sources by pattern:
- CC-only, narrow range → foot controller
- Wide note range, graded velocity → keyboard
- Clustered notes, high velocity variance → drum pads
- Sequential CC, relative encoding → encoders/knobs
Results power UI suggestions: "Conductor detected what appears to be a foot controller and a keyboard sharing this port. Create separate bindings?"
D8: MCP Binding CRUD Tools
New ReadOnly tools:
conductor_list_discovered_ports— all ports across protocols with binding statusconductor_get_binding_health— detailed health for a specific binding
New ConfigChange tools (Plan/Apply):
conductor_create_binding— full schema: protocol, input/output matchers, channel scope, descriptionconductor_update_device_identity— modify alias, matchers, description, enabled, channel scopeconductor_delete_device_identity— remove binding, report cascade impact on mapping rules
New Stateful tools:
conductor_suggest_binding— given port name or Learn events, suggests binding config using event fingerprinting
New HardwareIO tools:
conductor_probe_device_identity— send SysEx Identity Request, return structured response
D9: Agent Skill Updates
conductor-device-setup→conductor-binding-setup: Rewritten with three-layer model, binding terminology, channel scope guidance, protocol-specific examplesconductor-midi-learn→conductor-learn: Extended beyond MIDI, adds source identification phase, connects Learn output toconductor_suggest_binding- New:
conductor-troubleshooting: Teaches diagnosis of common problems (device not found, events not triggering, wrong device, LEDs not working)
D10: Canvas Binding Artifacts (ADR-013 Extension)
device-topology → binding-topology: Data model changes to ports→bindings→mappings. Three-column interactive graph. Data sources: conductor_list_discovered_ports + binding health + mapping counts.
New: discovery-overview: Lightweight artifact for new device plug-in. Shows discovered ports with bound/unbound status, binding health, "Create Binding" action buttons.
D11: Knowledge Layer Binding Content (ADR-018 Extension)
L1 Core Reference: Binding schema documentation (config format, matcher types, interaction patterns, channel scope, three-layer model) added to docs/llm-reference.md.
L2 Device Profiles: 60-90 curated device profile chunks covering common manufacturers. Enables smart binding suggestions: "set up my Mikro MK3" → retrieves pad layout, note ranges, LED protocol → generates informed binding.
L3 Community Profiles: Users contribute binding configurations for rare devices. Privacy-preserved via embedding vectors.
Implementation Phases
Phase 1 — Foundation (No dependencies; can start immediately)
Scope: D1 (terminology), D8 partial (list_discovered_ports), D9 (skills) Proposals: P2, P10, P14, P17, P20 Effort: 8-12h Deliverables:
- UI label changes throughout GUI (Device→Binding in user-facing text)
[[bindings]]config alias in TOML parserconductor_list_discovered_portsMCP tool implementation- Rewritten
conductor-binding-setupskill - New
conductor-troubleshootingskill - Skill rename
conductor-midi-learn→conductor-learn
Phase 2 — Discovery UI (Depends on ADR-019 Phase 1 for filter stores)
Scope: D2, D4, D5 partial Proposals: P1, P3, P4, P6 Effort: 10-14h Deliverables:
get_discovered_portsTauri commanddiscoveredPortsSvelte store- Connections view (workspace view with Discovered Ports + Configured Bindings sections)
- BindingPills component (rename + "+N unbound" badge + protocol icons)
- Unbound Sources group in Signal Flow
- ADR-019 amendment: BindingPills rename + protocol icons on DeviceStatusPills
Phase 3 — Binding CRUD (Depends on Phase 1 tools)
Scope: D8 (remaining CRUD tools), D5 partial Proposals: P15, P16, P21 Effort: 8-12h Deliverables:
conductor_create_bindingMCP tool (protocol, channel, I/O matchers)conductor_update_device_identityMCP toolconductor_delete_device_identityMCP toolconductor_get_binding_healthMCP tool- Events-to-Binding bridge (ADR-017 amendment)
- "Create Binding" inline button on unbound events
Phase 4 — Multi-Protocol (Depends on Phase 2 discovery UI)
Scope: D3 Proposals: P5, P7, P8, P9 Effort: 6-10h Deliverables:
Protocolenum andprotocolfield onDeviceIdentityConfig- Protocol badges on event rows
- Channel column promotion in event stream
- Optional channel scope on bindings
- OSC binding config schema
- HID binding config schema
Phase 5 — Device Intelligence (Depends on Phase 3 CRUD)
Scope: D6, D7 Proposals: P11, P12, P13, P19 Effort: 10-14h Deliverables:
- SysEx Identity probing (optional, setting-gated)
SysExIdentitymatcher variantUsbIdentifiermatcher implementation (currently placeholder)- MIDI Learn source annotation UI step
- Event fingerprinting engine
conductor_suggest_bindingMCP toolconductor_probe_device_identityMCP tool
Phase 6 — LLM Integration (Depends on ADR-013 Phase 3 + ADR-018)
Scope: D10, D11 Proposals: P22, P23, P24, P25 Effort: 8-12h Deliverables:
binding-topologyCanvas artifact (ADR-013 extension)discovery-overviewCanvas artifact- Binding schema in L1 core reference document
- Device profile retrieval via L2
- Community profile sharing via L3 (stub)
Total estimated effort: 50-74h across 6 phases
Specification: New Files
| File | Purpose |
|---|---|
conductor-core/src/config/protocol.rs | Protocol enum (Midi, Hid, Osc, ArtNet) |
conductor-daemon/src/mcp/tools/list_discovered_ports.rs | MCP tool: list all discovered ports |
conductor-daemon/src/mcp/tools/create_binding.rs | MCP tool: create binding with full schema |
conductor-daemon/src/mcp/tools/update_device_identity.rs | MCP tool: update binding |
conductor-daemon/src/mcp/tools/delete_device_identity.rs | MCP tool: delete binding |
conductor-daemon/src/mcp/tools/get_binding_health.rs | MCP tool: binding health report |
conductor-daemon/src/mcp/tools/suggest_binding.rs | MCP tool: event fingerprint suggestion |
conductor-daemon/src/mcp/tools/probe_device_identity.rs | MCP tool: SysEx Identity Request |
conductor-daemon/src/fingerprint.rs | Event fingerprinting engine |
conductor-gui/ui/src/lib/components/BindingPills.svelte | Renamed DeviceStatusPills |
conductor-gui/ui/src/lib/components/BindingList.svelte | Renamed DeviceList |
conductor-gui/ui/src/lib/workspace/ConnectionsView.svelte | Discovered Ports + Configured Bindings |
conductor-gui/ui/src/lib/stores/discoveredPorts.js | Reactive discovered ports store |
skills/conductor-binding-setup/ | Rewritten device setup skill |
skills/conductor-troubleshooting/ | New troubleshooting skill |
Specification: Modified Files
| File | Change |
|---|---|
conductor-core/src/config/types.rs | Add protocol: Option<Protocol>, channels: Option<Vec<u8>> to DeviceIdentityConfig |
conductor-core/src/config/loader.rs | Accept [[bindings]] as alias for [[devices]] |
conductor-core/src/identity.rs | Add SysExIdentity matcher variant, implement UsbIdentifier matching |
conductor-daemon/src/mcp/mod.rs | Register new tools |
conductor-daemon/src/midi_device.rs | Optional SysEx Identity probing on connection |
conductor-gui/src-tauri/src/commands.rs | Add get_discovered_ports command |
conductor-gui/ui/src/lib/components/DeviceStatusPills.svelte | Rename to BindingPills, add +N unbound, protocol icons |
conductor-gui/ui/src/lib/components/signal-flow/DeviceGroup.svelte | Add Unbound Sources rendering |
conductor-gui/ui/src/lib/workspace/EventStreamPanel.svelte | Unbound event styling, "Create Binding" button |
conductor-gui/ui/src/lib/stores/workspace.js | Add discoveredPorts, Connections view registration |
skills/conductor-midi-learn/ → skills/conductor-learn/ | Rename + extend for multi-protocol |
Backward Compatibility
- Config:
[[devices]]continues to work.protocoldefaults to Midi.channelsdefaults to None. All existing configs load without modification. - API: Existing MCP tools (
conductor_create_device_identity,conductor_list_device_bindings, etc.) continue to work. New tools are additive. - GUI: Component renames are internal. User-facing label changes ("Device" → "Binding") are the primary visible impact.
- Skills: Old skill names are deprecated but remain functional for one major version.
Consequences
Positive:
- Clear conceptual model that scales across protocols
- GUI gains discovery visibility and binding CRUD (currently absent)
- LLM becomes first-class binding management interface
- Bridge transparency addressed with progressive approaches (manual → assisted → automatic)
- Foundation for OSC, HID, and ArtNet as first-class citizens
Negative:
- Terminology migration effort across docs, UI, skills, and community materials
- 50-74h of implementation work across 6 phases
- Increased config schema complexity (protocol field, channel scope, OSC endpoint config)
Neutral:
- ADR-021's decisions remain in force — this is additive, not a rewrite
- The binding struct name (
DeviceIdentityConfig) stays for backward compat even though the concept is now "binding"
References
- Whitepaper:
conductor-bindings-whitepaper.md(March 2026) - Mockups:
whitepaper-binding-mockups.html(6-tab interactive mockup) - ADR-021:
ADR-021-input-output-device-qualification.md - ADR-013:
ADR-013-llm-canvas-artifact-projection.md - ADR-017:
ADR-017-unified-mapping-experience.md - ADR-018:
ADR-018-knowledge-layer-retrieval-architecture.md - ADR-019:
ADR-019-unified-workspace-filtering.md - ADR-020:
ADR-020-v3-structural-rendering-migration.md