Debug Your Assistant from a URL: the OVOS Messagebus Monitor
JarbasAl
OVOS Contributor
Debug Your Assistant from a URL
This post is for people who run or build OVOS and want to watch what it's doing internally. If that's not you, the short version: OVOS has a browser-based debugger for its internal event stream.
The OVOS messagebus carries every event the platform processes: utterances, intent matches, skill activations, TTS requests, session state. By default that stream goes to a terminal as scrolling text. Finding one message in a running system means writing a throwaway listener or grepping log files after the fact.
ovos-busmon puts the bus in your browser instead: filters, JSON inspection, capture export, and message injection when you need to force a bug to reproduce. Try it at openvoiceos.github.io/ovos-busmon, with no install and no server.
Two Ways to Connect
The monitor is a single static page. It detects which transport to use automatically, so you never configure that by hand.
Zero-server: open the page
Open openvoiceos.github.io/ovos-busmon and it connects to ws://localhost:8181/core by default. Set host, port, and path in the connection panel, or as query parameters, to point it at any reachable OVOS device:
https://openvoiceos.github.io/ovos-busmon/?host=192.168.1.10&port=8181&path=/core
No backend, nothing to install. It is a static page, so it's also on GitHub Pages: anyone on a laptop that can reach an OVOS device opens that URL and watches the device's bus within seconds.
One browser caveat: Chromium-based browsers allow a ws://localhost connection from an https:// page, because localhost counts as a trustworthy origin. Safari and some Firefox builds block it. If the connection is refused, use the Download standalone HTML button. Save the file and open it as file://; it works with no origin restrictions.
Always-on: run the FastAPI service
For a monitor that stays up, install the package and run it:
pip install ovos-busmon
ovos-busmon
# Listens on http://127.0.0.1:8005 by default
With a messagebus running on the same machine, version 0.1.0 answers like this (HTTP Basic auth with the defaults; without credentials the same call returns 401):
$ curl -u ovos:ovos http://127.0.0.1:8005/api/status
{"version": "0.1.0", "buffered": 2, "buffer_capacity": 2000, "bus_host": "localhost", "bus_port": 8181}
$ curl -u ovos:ovos -X POST http://127.0.0.1:8005/api/send \
-H 'Content-Type: application/json' -d '{"type":"speak","data":{"utterance":"hello from busmon"}}'
{"ok":true}
Here the connection to the bus happens server-side, through ovos-bus-client, and the same UI is served over HTTP. The page pulls events over Server-Sent Events and REST instead of a browser WebSocket. The API is small:
| Endpoint | What it does |
|---|---|
GET / |
The same static UI (auto-switches to the SSE transport) |
GET /api/status |
Health, buffer stats, and which bus it is attached to |
GET /api/messages |
Ring-buffer contents, with ?since_id=N&limit=M paging |
GET /api/stream |
SSE live tail |
POST /api/send |
Inject a message onto the bus |
GET /api/export |
JSONL download of the whole capture buffer |
The service keeps a ring buffer (a fixed-size queue that overwrites its oldest entries once full) of recent messages, 2000 by default. A client that reconnects, or one you open a minute late, pages back through what it missed with since_id instead of starting blind.
Configuration is environment variables, or a .env file. There are no flags to memorize, and it's easy to drop into a container or systemd unit:
| Variable | Default | Meaning |
|---|---|---|
OVOS_BUS_HOST |
localhost |
Messagebus host to attach to |
OVOS_BUS_PORT |
8181 |
Messagebus port |
BUSMON_HOST |
127.0.0.1 |
Address the HTTP service binds to |
BUSMON_PORT |
8005 |
Port the HTTP service binds to |
BUSMON_USERNAME / BUSMON_PASSWORD |
ovos / ovos |
HTTP Basic auth |
BUFFER_SIZE |
2000 |
Ring-buffer capacity |
A Docker Compose file ships with the repo. It binds the service to 127.0.0.1:8005 only and sets OVOS_BUS_HOST=host.docker.internal so the container can still reach a bus running on the host.
BUSMON_USERNAME and BUSMON_PASSWORD default to ovos / ovos. Set both to something else before you run the service unattended. Otherwise, anyone who can reach the port logs in with a password taken straight from the docs.
Filtering the Firehose
Both modes share the same interface, built for cutting a busy bus down to what matters:
- Message type, with glob patterns (wildcard matching, e.g.
recognizer_loop:*for the input side,ovos.*for namespaced events, or an exact type likespeak). - Full-text search across type, data, context, and session.
- Session ID, source and destination, to isolate one conversation or one skill.
- Sort newest-first or oldest-first, and pause/resume capture to freeze the view and read.
Each message expands into syntax-highlighted JSON, so you can inspect the full payload without leaving the page. Export what you find as JSONL or JSON, client-side from the buffer or via /api/export in service mode, to attach to a bug report or replay later.
Injecting Messages
The Inject panel, backed by POST /api/send, puts an arbitrary message onto the bus: pick a type, write a JSON payload, send. You can reproduce a skill's behavior by emitting the exact message that triggers it, instead of staging the whole voice pipeline to trigger one intent handler.
That power is why injection is meant for local, personal administration only, even when you run it as an always-on service. Keep the default 127.0.0.1 binding, change BUSMON_USERNAME and BUSMON_PASSWORD from their ovos / ovos defaults, add TLS if you move it off localhost, and never expose the injection endpoint to an untrusted network. Anyone who can reach it can log in and emit any message on your bus.
ovos-busmon logs nothing to disk beyond what you explicitly export. Zero-server for a quick look, the FastAPI service for a permanent one, and an inject panel for when watching is not enough.
Limits
The hosted page reaches a device only when your browser can open a WebSocket to it, so a bus bound to localhost on another machine needs the service mode or an SSH tunnel. The ring buffer is memory only; a restart empties it. Bugs and questions go to the issue tracker.
This work is part of the OpenVoiceOS From Beta to Breakthrough milestone, funded through the NGI0 Commons Fund, a fund established by NLnet with financial support from the European Commission's Next Generation Internet programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 101135429. Additional funding is made available by the Swiss State Secretariat for Education, Research and Innovation (SERI).
Help Us Build Voice for Everyone
OpenVoiceOS is more than software, it's a mission. If you believe voice assistants should be open, inclusive, and user-controlled, here's how you can help:
- 💸 Donate: Help us fund development, infrastructure, and legal protection.
- 📣 Contribute Open Data: Share voice samples and transcriptions under open licenses.
- 🌍 Translate: Help make OVOS accessible in every language.
We're not building this for profit. We're building it for people. With your support, we can keep voice tech transparent, private, and community-owned.