Skip to content

Sonos Audio Vertical Architecture

The Sonos audio integration in homectl is the pilot implementation of our Agent Plugin & Go Module Vertical strategy. It bridges high-level natural language agent commands with low-level UPnP SOAP actions and asynchronous GENA push notifications.


Sonos Vertical Architecture


1. Agent Skill & Token Economics (skills/sonos-soundscape)

Section titled “1. Agent Skill & Token Economics (skills/sonos-soundscape)”

Interacting with Sonos devices via raw SOAP produces verbose XML payloads (DIDL-Lite blocks) that consume 1,200–2,000 prompt tokens per query. The sonos-soundscape skill solves this via the Skill with Code pattern:

  • Compact JSON Transformation: Delegates XML parsing to summarize_metadata.py, transforming a multi-kilobyte DIDL-Lite fragment into a 40-byte JSON summary (title, artist, album). This yields a ~94% token reduction per status check.
  • Acoustic Safety Boundaries: Clamps standard listening levels between 15% and 40%. Mutating calls exceeding 60% require explicit user confirmation; volumes over 80% are blocked.
  • Error 701 Queue Recovery: Codifies the recovery workflow: if playback fails with UPnP Error 701 (Transition Not Available), the agent restores the speaker’s local queue (x-rincon-queue:<RINCON>#0) or falls back to ambient Sonos Radio before retrying.

The MCP server communicates with AI agents over standard I/O using the official @modelcontextprotocol/go-sdk:

Tool Mode Schema Wrapper Description
sonos_list_speakers 🔒 Read-Only ListSpeakersResult{Count, Speakers} Discovers or lists cached speakers on LAN with generation (S1/S2) and renderer status.
sonos_get_now_playing 🔒 Read-Only NowPlayingResult Compact playback state, progress, play mode, shuffle, repeat, crossfade, and track metadata.
sonos_get_topology 🔒 Read-Only TopologyResult{Count, Groups} Exposes zone groups and stereo-pair coordinator/follower relationships.
sonos_list_favorites 🔒 Read-Only ListFavoritesResult{Count, Favorites} Lists pinned cloud tracks/playlists from Spotify, Apple Music, and Sonos Radio.
sonos_control ⚡ Mutating {"status": "ok", "action": ...} Sends playback and grouping actions: play, pause, stop, next, previous, seek_track, seek_time, join, leave, unjoin.
sonos_set_volume ⚡ Mutating {"status": "ok", "volume": ...} Adjusts absolute volume (0–100) or applies relative step deltas (+5, -10).
sonos_play_favorite ⚡ Mutating {"status": "ok", "favorite_id": ...} Initiates playback of a pinned cloud favorite.
sonos_play_stream ⚡ Mutating {"status": "ok", "url": ...} Streams an arbitrary HTTP/HTTPS audio URL (radio, podcast, or TTS).
sonos_add_to_queue ⚡ Mutating {"status": "ok", "track_position": ...} Enqueues track or container URI (with optional metadata and as-next flag).
sonos_get_queue 🔒 Read-Only QueueResult{Items, TotalMatches} Inspects playback queue tracks with pagination support.
sonos_queue_edit ⚡ Mutating {"status": "ok", "action": ...} Edits queue: removes tracks, clears queue, reorders tracks, or configures shuffle/repeat/crossfade.
sonos_list_services 🔒 Read-Only ListServicesResult{Count, Services} Lists registered music services and configured default provider.

All output schemas conform to MCP SEP-2106 and OpenCode validation rules by returning Go records (JSON objects), never bare arrays.

The core domain logic is isolated in modules/sonos:

  • S1/S2 Hardware Classification & Bridge Detection: Legacy devices running S1 firmware (swGen: "1" or hardware ZP80-120, S5, ZB100, BR100, CR100-200, WD100) are classified into Generation: "S1", while modern devices report Generation: "S2". Bridges and docks without AVTransport endpoints are flagged with IsRenderer: false. The engine and MCP layer reject playback/volume mutations on non-renderers and validate grouping boundaries to prevent joining S1 and S2 speakers into incompatible mixed households.
  • Coordinator / Follower Resolution: In Sonos stereo pairs or groups, secondary speakers report their transport as PLAYING with an x-rincon:<coord> track URI but empty metadata. modules/sonos automatically detects follower nodes, resolves the group coordinator via GetCoordinatorIP(), and routes playback commands to the authoritative master speaker.
  • Container vs. Item Favorite Playback Protocol: Sonos handles single tracks/radio streams differently from multi-track containers (Spotify/Apple Music playlists, albums, YouTube Music Liked Music). modules/sonos detects container favorites via isContainerFavorite():
    1. Clears current queue (RemoveAllTracksFromQueue).
    2. Enqueues the container with stored DIDL-Lite metadata (AddURIToQueue).
    3. Points transport to local queue (x-rincon-queue:<rincon>#0).
    4. Begins playback via the group coordinator. Single-item streams bypass queueing and use direct SetAVTransportURI.
  • Zero Global State: Does not rely on global init() loggers or hardcoded file paths. All dependencies (core.Logger, core.Storage, core.Settings) are injected via functional options:
    client := sonos.NewClient(ip,
    sonos.WithLogger(logger),
    sonos.WithStorage(storage),
    sonos.WithHTTPClient(customHTTP),
    )
  • Comprehensive Mock Testing: Tested via modules/sonos/client_test.go using httptest.Server, covering DIDL-Lite parsing, SOAP error handling, and group topology without requiring physical hardware.
  • Outbound SOAP (Port 1400): Executes commands against standard UPnP services:
    • /MediaRenderer/AVTransport/Control
    • /MediaRenderer/RenderingControl/Control
    • /MediaServer/ContentDirectory/Control
    • /ZoneGroupTopology/Control
  • SSDP M-SEARCH (UDP 1900): Sends multicast probes to 239.255.255.250:1900 (ST: urn:schemas-upnp-org:device:ZonePlayer:1). Bypasses macOS port 5353 multicast conflicts to deliver sub-500ms discovery across all network nodes.
  • Inbound GENA Push Events: GENAListener (events.go) launches a background HTTP listener to receive real-time UPnP NOTIFY requests containing LastChange XML event fragments for instant volume and track synchronization.

5. Jibo Social Robot Voice Capability (jiboplugins/sonos)

Section titled “5. Jibo Social Robot Voice Capability (jiboplugins/sonos)”

The Sonos module also powers conversational smart home audio on the Jibo Social Robot:

  • Shared Discovery Cache: Jibo Cloud reads ~/.config/homectl/sonos_cache.json on the LAN to resolve room names with zero runtime mDNS latency during speech turns.
  • Auto-Resolution of Active Zone: When an owner asks “What song is this?” without specifying a room, ResolveActiveSpeaker() polls zones to find which speaker is actively PLAYING.
  • Queue Seeking by Song/Artist: Allows owners to jump to any track up to 100 positions ahead in the queue by speaking song titles or artist names (“Play Such Great Heights in the office”).
  • Embodied Performance: Jibo smiles, sways rhythmically (cat="dance" filter="music, slowdance"), and renders circular album cover art on its face while announcing tracks.

  • S1/S2 Detection Heuristics: The model identification rules, <swGen> UPnP descriptor inspection, and non-renderer bridge filtering build upon prior art and research from frankensonos by @dickelstoneworth.