Skip to main content

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-topology artifact to binding-topology; adds discovery-overview artifact 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:

  1. Terminology conflation: The term "device" means three different things (physical unit, OS port, config object). This confuses users and LLMs alike.
  2. No Discovery layer in the UI: The GUI shows connected devices but not all available ports. Unbound ports are invisible.
  3. 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 (limited conductor_create_device_identity), manual TOML editing, or CLI.
  4. MIDI-only binding model: The DeviceIdentityConfig struct 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.
  5. LLM tooling gaps: No update/delete for bindings, no discovery awareness, no channel/protocol support in create, skills teach wrong terminology.
  6. 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)
  • DeviceStatusPillsBindingPills (component rename)
  • DeviceListBindingList (component rename)
  • DeviceGroup (Signal Flow) → still DeviceGroup (too embedded in ADR-020; rename deferred)
  • DeviceIdentityConfig struct → retain struct name for backward compat; add [[bindings]] as config alias for [[devices]]
  • Agent skill conductor-device-setupconductor-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:

  1. Discovered Ports — all ports grouped by protocol, showing bound/unbound status
  2. 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)

DeviceStatusPillsBindingPills. 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 status
  • conductor_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, description
  • conductor_update_device_identity — modify alias, matchers, description, enabled, channel scope
  • conductor_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-setupconductor-binding-setup: Rewritten with three-layer model, binding terminology, channel scope guidance, protocol-specific examples
  • conductor-midi-learnconductor-learn: Extended beyond MIDI, adds source identification phase, connects Learn output to conductor_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-topologybinding-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 parser
  • conductor_list_discovered_ports MCP tool implementation
  • Rewritten conductor-binding-setup skill
  • New conductor-troubleshooting skill
  • Skill rename conductor-midi-learnconductor-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_ports Tauri command
  • discoveredPorts Svelte 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_binding MCP tool (protocol, channel, I/O matchers)
  • conductor_update_device_identity MCP tool
  • conductor_delete_device_identity MCP tool
  • conductor_get_binding_health MCP 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:

  • Protocol enum and protocol field on DeviceIdentityConfig
  • 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)
  • SysExIdentity matcher variant
  • UsbIdentifier matcher implementation (currently placeholder)
  • MIDI Learn source annotation UI step
  • Event fingerprinting engine
  • conductor_suggest_binding MCP tool
  • conductor_probe_device_identity MCP 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-topology Canvas artifact (ADR-013 extension)
  • discovery-overview Canvas 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

FilePurpose
conductor-core/src/config/protocol.rsProtocol enum (Midi, Hid, Osc, ArtNet)
conductor-daemon/src/mcp/tools/list_discovered_ports.rsMCP tool: list all discovered ports
conductor-daemon/src/mcp/tools/create_binding.rsMCP tool: create binding with full schema
conductor-daemon/src/mcp/tools/update_device_identity.rsMCP tool: update binding
conductor-daemon/src/mcp/tools/delete_device_identity.rsMCP tool: delete binding
conductor-daemon/src/mcp/tools/get_binding_health.rsMCP tool: binding health report
conductor-daemon/src/mcp/tools/suggest_binding.rsMCP tool: event fingerprint suggestion
conductor-daemon/src/mcp/tools/probe_device_identity.rsMCP tool: SysEx Identity Request
conductor-daemon/src/fingerprint.rsEvent fingerprinting engine
conductor-gui/ui/src/lib/components/BindingPills.svelteRenamed DeviceStatusPills
conductor-gui/ui/src/lib/components/BindingList.svelteRenamed DeviceList
conductor-gui/ui/src/lib/workspace/ConnectionsView.svelteDiscovered Ports + Configured Bindings
conductor-gui/ui/src/lib/stores/discoveredPorts.jsReactive discovered ports store
skills/conductor-binding-setup/Rewritten device setup skill
skills/conductor-troubleshooting/New troubleshooting skill

Specification: Modified Files

FileChange
conductor-core/src/config/types.rsAdd protocol: Option<Protocol>, channels: Option<Vec<u8>> to DeviceIdentityConfig
conductor-core/src/config/loader.rsAccept [[bindings]] as alias for [[devices]]
conductor-core/src/identity.rsAdd SysExIdentity matcher variant, implement UsbIdentifier matching
conductor-daemon/src/mcp/mod.rsRegister new tools
conductor-daemon/src/midi_device.rsOptional SysEx Identity probing on connection
conductor-gui/src-tauri/src/commands.rsAdd get_discovered_ports command
conductor-gui/ui/src/lib/components/DeviceStatusPills.svelteRename to BindingPills, add +N unbound, protocol icons
conductor-gui/ui/src/lib/components/signal-flow/DeviceGroup.svelteAdd Unbound Sources rendering
conductor-gui/ui/src/lib/workspace/EventStreamPanel.svelteUnbound event styling, "Create Binding" button
conductor-gui/ui/src/lib/stores/workspace.jsAdd discoveredPorts, Connections view registration
skills/conductor-midi-learn/skills/conductor-learn/Rename + extend for multi-protocol

Backward Compatibility

  • Config: [[devices]] continues to work. protocol defaults to Midi. channels defaults 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