Skip to content

GUI Adapter Plugins

In a nutshell

This page is for developers. It describes the new, not-yet-finished way OVOS will draw things on a screen: the planned replacement for the old, deprecated GUI. The idea: instead of OVOS talking to one kind of screen directly, it sends a generic "show this weather card" message. Small plugins called adapters translate that into whatever the actual display is (a touchscreen, a web browser, even a terminal). Several adapters can run at once, so the same content shows on multiple screens. This is upcoming work, not the everyday path today. See the GUI Protocol for the current legacy screen and the Glossary for terms.

Nothing on this page is built or released yet

This page documents the in-progress replacement for the deprecated legacy GUI. None of it is usable on a stable install today. Until it ships, there is no generally usable OVOS GUI. Mark 2 devices keep a screen via the legacy stack that the ovos-installer sets up. The pieces are at different stages:

  • ovos-gui-api-client: a template-based GUIInterface already exists and works today.
  • ovos-legacy-mycroft-gui-plugin and ovos-gui-plugin-ag-ui already implement the adapter-side contract described below (an AbstractGUIPlugin subclass registered under opm.gui_adapter). But that base class does not exist yet in any released ovos-plugin-manager, and ovos-gui does not yet contain the router that would dispatch events to these adapters. These plugins are therefore built ahead of their own dependency. The router mentioned elsewhere on this page (connection status, handle_show_* dispatch) is not yet built either.
  • The formal contract is specified by OVOS-GUI-1, an architecture spec. The spec deliberately leaves the exact entry-point group name and method signatures non-normative. It gives them only as an illustrative example. The names below (AbstractGUIPlugin, handle_show_*, opm.gui_adapter) are what the real adapter plugins linked above have already standardized on. That is why this page uses them.

Per OVOS-GUI-1, the sole per-event routing key for the adapter contract is session_id. There is no separate site/room/location dimension. A shared/multi-room screen is expressed by its clients sharing one session_id. The ovos-gui-plugin-ag-ui adapter already follows this convention (every handle_show_* / lifecycle signature takes session_id: str = "default").

The legacy adapter still routes on site_id (in flux)

The shipped ovos-legacy-mycroft-gui-plugin has not yet adopted the session_id contract. Every handle_show_* and lifecycle method takes site_id: str = "default" instead (ovos_legacy_mycroft_gui/__init__.py), and its websocket layer routes on site_id (send_to_clients_for_site(site_id)). The Qt6 client rework branch (mycroft-gui-qt6) likewise still ships a separate site_id dimension (--site-id flag, MYCROFT_SITE_ID env var) for multi-site setups. So at both the legacy adapter and client layers, site_id is not yet gone, even though the OVOS-GUI-1 adapter contract collapses routing to session_id. The two layers may still be reconciled.

In the rework, ovos-gui no longer renders or talks to Qt clients directly. It becomes a router that dispatches each display event to every installed GUI adapter plugin. Each adapter translates template events into whatever protocol it needs (Qt WebSocket, HTTP+SSE, curses, and more). Multiple adapters can run at once, so output can reach several display types at the same time.

Adapters can add their own extra functionality (optional)

Beyond rendering the standard display templates, a GUI adapter plugin may define its own extra bus event listeners. This exposes additional, optional capabilities that skills can choose to use. Examples include Mark 1 events / faceplate control (the Mark 1 faceplate becomes such an adapter), home screens, and custom QML. These are opt-in. A skill works without them, but can adopt the extra functionality offered by whichever adapter(s) are installed.

Entry point

Adapters are discovered via the opm.gui_adapter entry-point group:

[project.entry-points."opm.gui_adapter"]
"my-adapter" = "my_package:MyGUIPlugin"

The class extends AbstractGUIPlugin from ovos_plugin_manager.templates.gui.

AbstractGUIPlugin

# NOTE: this import fails today — AbstractGUIPlugin does not exist in any released
# ovos-plugin-manager yet (see the warning above). This is a forward-looking example.
from ovos_plugin_manager.templates.gui import AbstractGUIPlugin

class MyGUIPlugin(AbstractGUIPlugin):
    def __init__(self, config: dict, bus=None):
        super().__init__(config, bus)
        # start your server / rendering pipeline here

Template handlers

Override any of the following methods to render a template. Each receives skill_id: str (the namespace), data: dict (current session data), and session_id: str (the routing id, default "default"). All default to no-ops, so partial implementations are valid.

Method Template Key data keys
handle_show_idle SYSTEM_idle none
handle_show_loading SYSTEM_loading label
handle_show_status SYSTEM_status label, success
handle_show_error SYSTEM_error label, detail
handle_show_text SYSTEM_text text, title
handle_show_image SYSTEM_image image, title, caption, fill
handle_show_animated_image SYSTEM_animated_image same as image
handle_show_list SYSTEM_list title, items
handle_show_grid SYSTEM_grid title, items
handle_show_table SYSTEM_table title, columns, rows
handle_show_html SYSTEM_html html
handle_show_url SYSTEM_url url
handle_show_audio_player SYSTEM_audio_player title, artist, album, image, playing, position, duration
handle_show_video_player SYSTEM_video_player uri, title, playing
handle_show_media_player SYSTEM_media_player media fields
handle_show_clock SYSTEM_clock none
handle_show_timer SYSTEM_timer end_time, label, count_up
handle_show_weather SYSTEM_weather current_temp, min_temp, max_temp, condition, icon, location
handle_show_map SYSTEM_map latitude, longitude, zoom, label
handle_show_confirm SYSTEM_confirm question
handle_show_select SYSTEM_select prompt, items
handle_show_face SYSTEM_face sleeping
handle_show_ocp_now_playing SYSTEM_ocp_now_playing media fields
handle_show_ocp_search SYSTEM_ocp_search search-result fields
handle_show_ocp_playlist SYSTEM_ocp_playlist playlist fields

The three SYSTEM_ocp_* rows are a legacy-adapter extension, not part of OVOS-GUI-1's closed vocabulary of 22 templates (see GUI Service): the legacy mycroft-gui adapter implements them, the ag-ui adapter implements only the spec's 22, and a new spec-conformant adapter is not required to handle them.

Lifecycle hooks

def on_namespace_activated(self, skill_id: str, session_id: str = "default") -> None:
    """Called when a namespace moves to the top of the display stack."""

def on_namespace_deactivated(self, skill_id: str, session_id: str = "default") -> None:
    """Called when a namespace is removed from the display stack."""

def on_idle(self) -> None:
    """Called when the display returns to the idle/resting state."""

def on_session_update(self, skill_id: str, data: dict, session_id: str = "default") -> None:
    """Called whenever skill session data changes (e.g. gui['key'] = value)."""

def on_status_event(self, event_name: str, data: dict, session_id: str = "default") -> None:
    """Called for system status events (wakeword, utterance handled, etc.)."""

Dispatch

The (not-yet-built) router is expected to map each SYSTEM_* template name to the matching handle_show_* method. The naming convention handle_show_<template suffix> is already fixed by the adapter plugins that implement this contract today. An adapter overrides only the individual handle_show_* methods it cares about. Unimplemented ones default to no-ops.

Connection status

The legacy gui.status.request bus message already exists today and answers whether any display is connected. An adapter is expected to optionally provide an any_client_connected() -> bool method, so a future router can fold it into that response. The legacy adapter already ships a concrete module-level any_client_connected() (ovos_legacy_mycroft_gui/websocket.py) as a reference implementation. Since the base class and router are not yet built, treat the exact hook-up (duck-typing or otherwise) as illustrative:

def any_client_connected(self) -> bool:
    return len(self._my_connected_clients) > 0

Built-in adapters

Repo PyPI package Entry-point name Class Description
ovos-legacy-mycroft-gui-plugin ovos-legacy-mycroft-gui-plugin ovos-legacy-mycroft-gui LegacyMycoftGuiPlugin Tornado WebSocket to Qt / mycroft-gui clients, also runs HomescreenManager
ovos-gui-plugin-ag-ui ovos-gui-plugin-ag-ui ovos-gui-plugin-ag-ui AgUiGuiPlugin ag-ui protocol adapter: renders GUI state/template events as ag-ui SSE events for ag-ui / CopilotKit frontends

pyhtmx-gui-client is a client, not an adapter

The pyhtmx-gui-client repo (PyPI ovos-pyhtmx-gui-client, console script pyhtmx-gui) is not a GUI adapter plugin. It ships no AbstractGUIPlugin subclass and no opm.gui_adapter entry point. It is a standalone FastAPI/uvicorn GUI client whose GUIClient connects as a WebSocket client to the legacy ovos-gui protocol (ws://localhost:18181/gui, config keys ovos-server-url / client-id), similar to a mycroft-gui Qt client. It belongs to the legacy websocket-client path, not the adapter architecture described here.

Writing a custom adapter

from ovos_plugin_manager.templates.gui import AbstractGUIPlugin

class TerminalGUIPlugin(AbstractGUIPlugin):
    """Render OVOS GUI templates in the terminal."""

    def __init__(self, config, bus=None):
        super().__init__(config, bus)

    def handle_show_text(self, skill_id, data, session_id="default"):
        print(data.get("title", ""))
        print(data.get("text", ""))

    def handle_show_weather(self, skill_id, data, session_id="default"):
        print(data.get("location", ""),
              f"{data['current_temp']}° — {data['condition']}")

Register in pyproject.toml:

[project.entry-points."opm.gui_adapter"]
"my-terminal-gui" = "my_package:TerminalGUIPlugin"

Configuration

Adapter configuration is expected to live under a gui.adapters.<entry-point-name> key in mycroft.conf, following the existing get_plugin_config() convention used by other OPM plugin types. It is expected to be passed as the config dict to the adapter's __init__. This exact key path is not yet fixed by any released code. Treat it as illustrative:

{
  "gui": {
    "adapters": {
      "ovos-gui-plugin-ag-ui": {
        "host": "0.0.0.0",
        "port": 8080
      }
    }
  }
}

The shipped legacy adapter, however, reads concrete config today rather than the gui.adapters.* path. It consumes the top-level [gui_websocket] section via ovos_config (host default 127.0.0.1, base_port default 18181, route default /gui) and gui.default_qt_version (default 5):

{
  "gui_websocket": {
    "host": "127.0.0.1",
    "base_port": 18181,
    "route": "/gui"
  },
  "gui": {
    "default_qt_version": 5
  }
}

Multi-modal rendering

With two adapters installed (e.g. legacy Qt and ag-ui), every display event is dispatched to both:

self.gui.show_weather(…)
ovos-gui router, fanned out to every installed opm.gui_adapter plugin
       ├──→ LegacyMycoftGuiPlugin.handle_show_weather(…)   → Qt client
       └──→ AgUiGuiPlugin.handle_show_weather(…)            → ag-ui / CopilotKit frontend

Both displays update simultaneously and independently.


Read next: Agents Overview Related: GUI Protocol · Home Screen · Plugin Manager