ADR 0015: EventBus abstraction over the message transport¶
Status: Accepted — superseded in part by ADR 0017,
which made "nats" the default backend. The EventBus ABC and the factory below
remain live; the "mqtt" default does not.
Date: 2026-06-21
Deciders: Scott, Development Team
Backlog: "[ARCH] Abstract paho-mqtt Behind a Generic EventBus Interface" (Epic 4)
Context¶
Active Inference and offline analysis want durable, replayable event logs, but
the transport today is ephemeral MQTT. The good news: agents already do not
import paho-mqtt directly — they all go through orpheus_common.mqtt.MQTTClient,
which already wraps paho (auto-reconnect, JSON, topic matching, LWT). What was
missing was a transport-agnostic contract and a factory so the backend
becomes a deployment decision (MQTT now; a durable stream later — see the Epic 4
"Durable Event Backend Evaluation" spike) without editing call sites.
Decision¶
Add a small, purely additive abstraction in orpheus-common:
EventBusABC (orpheus_common.event_bus) with the methods the existing client already exposes:publish,subscribe,unsubscribe,connect,disconnect, and anis_connectedproperty (it's a property on the current client; the ABC matches that so callers keep writingbus.is_connected).EventCallback = Callable[[str, dict], None].MQTTClientimplementsEventBus(subclass) and gains the one missing method,unsubscribe.MQTTBusis an alias ofMQTTClient(the EventBus-vocabulary name);MQTTClientstays the canonical name so every existingfrom orpheus_common.mqtt import MQTTClientkeeps working.create_event_bus(config, *, client_id, ...)factory with a backend registry, readingconfig.event_bus.backend(default"nats"). A new backend is one registry entry — no call-site changes.event_bus.backendconfig (EventBusConfig, default"nats") — additive and defaulted, so a config with noevent_bus:section still validates and behaves exactly as before.
Migration path¶
Adoption is incremental and non-breaking — create_event_bus() returns the same
MQTTClient object an agent would have built itself, so migrated and
un-migrated components interoperate. Components move from
MQTTClient(broker_host=cfg.mqtt.broker_host, ...) to
create_event_bus(cfg, client_id=..., will_topic=..., will_payload=...) one at a
time. (This change migrates orpheus-agent-event-correlator and orpheus-gps
as the first adopters; the rest follow in later batches.)
Consequences¶
- Fully reversible / stack-safe. Pure addition: a new module, a new
(defaulted) config section, one new method, an alias. No schema/MQTT-payload
change, no behavior change for code still using
MQTTClientdirectly. Reverts to baremaincleanly, alone or stacked with other flywheel commits. - Unblocks the durable-backend evaluation spike and historical event replay
(both
depends_onthis abstraction). - The
is_connectedproperty (vs. the issue's sketchedis_connected()method) was chosen to match the existing client so no caller changes. - A second backend still needs the spike's decision before it's added; this ADR only lands the seam.