Skip to content

Latest commit

 

History

History
732 lines (670 loc) · 47.4 KB

divergences.md

File metadata and controls

732 lines (670 loc) · 47.4 KB

← APPENDIX.md · Non-normative

5. Where the specs differ from the reference implementation

These specifications are prescriptive. Some of what they prescribe matches the reference implementation verbatim; some is a deliberate cleanup from which the implementation diverges. This section catalogues the known divergences between the specifications and the reference implementation.

5.1 Already aligned

Formalizations of behaviour in the reference implementation that need no change:

  • The Message envelope (type / data / context) — matches ovos-bus-client.Message.
  • source and destination as the routing pair, and the Message.reply swap that owns it — matches ovos-bus-client/message.py. The one point of departure is the array-valued destination the shipped swap still accepts; see §5.3.
  • ovos.common_query.ping / ovos.common_query.pong — static broadcast poll topics with the responder's identity in data.skill_id. Match ovos-common-query-pipeline-plugin/opm.py and ovos-workshop/skills/ovos.py's __handle_common_query_ping verbatim; the shipped pair is the model the other poll families were reshaped toward (§5.4).
  • Player times counted in milliseconds (GUI-1 §3.4 SYSTEM_audio_player / SYSTEM_media_player) — matches ovos-media/media_backends/base.py, which already carries position and seek targets in ms.
  • context.session as a serialized Session object — matches ovos-bus-client/client/client.py's message.context["session"] = sess.serialize().
  • session.session_id == "default" for device-local origin — matches ovos-audio/utils.py's require_default_session decorator.
  • session.lang as the user's preferred language — matches the Session class's lang attribute.
  • forward / reply / response derivation semantics — matches ovos-bus-client.Message.{forward,reply,response}.
  • The .response suffix convention — pervasive across OVOS topics.
  • ovos.utterance.cancelled and ovos.utterance.handled (PIPELINE-1) — match current topic names verbatim.
  • Per-utterance first-match-wins iteration (PIPELINE-1) — matches ovos-core/intent_services/service.py's handle_utterance / get_pipeline.
  • Per-session pipeline configuration (PIPELINE-1) — matches Session.pipeline.
  • The <skill_id>:<intent_name> dispatch topic shape (PIPELINE-1) — matches OVOS practice; skills subscribe to these topics.

5.2 Prescriptive renames

Spec Current Prescribed Notes
INTENT-3 v1.1 "host" "orchestrator" Editorial; conformance unchanged.
PIPELINE-1 mycroft.skill.handler.start / .complete / .error ovos.intent.handler.start / .complete / .error Renamed into the ovos.intent.* namespace for uniformity. Breaks every existing handler-lifecycle observer; the migration cost is real.
PIPELINE-1 recognizer_loop:utterance ovos.utterance.handle See §5.4 entry. Migration touches ovos-dinkum-listener, ovos-simple-listener, ovos-audio, and ovos-core/intent_services/service.py.
PIPELINE-1 complete_intent_failure ovos.intent.unmatched Follows ovos.intent.* namespace; pairs with ovos.intent.matched.
COMMON-QUERY-1 question:query / question:query.response ovos.common_query.request / ovos.common_query.response The shipped plugin broadcasts question:query and collects question:query.response, correlating answers by the phrase string. Both names fail MSG-1 §2.1.1 — : outside the dispatch shape, and a root that names neither the ecosystem nor the domain. The v1 pair is two static topics; the skill being asked is named in data.skill_id (COMMON-QUERY-1 §7.1.1), never in the topic. The one colon topic kept is the dispatch <pipeline_id>:common_query.

5.2.1 Topics to remove from ovos-core

The following topics exist in current ovos-core but are not defined by any spec and should be removed or replaced:

  • ovos.session.update_default — emitted by SessionManager to broadcast the default session. SESSION-2 §7 acknowledges that a deployment MAY publish default-session state on a topic of its own as a diagnostic, but assigns no normative name and no consumer. This ad-hoc topic is retired: a component that needs the default-session state subscribes to ovos.utterance.handled (PIPELINE-1 §9.5) and reads the session it carries, or adopts the session of any other assistant-emitted Message on the default session (SESSION-2 §2.7). See §5.7 for the migration mapping and §5.5 for the removed-mechanism record.
  • add_context / remove_context (Adapt-specific) and mycroft.skill.set_cross_context — emitted by shipped skills and MycroftSkill helpers but not defined by any spec: the write path CONTEXT-1 defines is the session mutation of §5.0/§5.3 of that spec, not a bus topic. These are retired the same way as ovos.session.update_default above. See §5.5 for the removed-mechanism record.

5.3 Prescriptive shape changes

  • Keyword intent registration is atomic (INTENT-4 §5). Today a keyword intent is built up via multiple register_vocab messages followed by a register_intent with an Adapt IntentBuilder.__dict__ payload. INTENT-4 collapses this into a single message with structured {required, optional, one_of, excluded} arrays of vocabulary descriptors. Every skill's keyword-intent path needs to be rewritten in the workshop layer.
  • Template intent registration uses structured identity (INTENT-4 §6). Today padatious:register_intent carries {name, samples, file_name, lang, blacklisted_words}; the prescribed shape uses the structured (skill_id, intent_name, lang) triple plus samples|file and blacklist|blacklist_file.
  • Dispatch payload is minimal (PIPELINE-1 §7.1). Today dispatch carries skill_id and intent_name in the payload. PIPELINE-1 drops both from the payload — they are already in the topic (<skill_id>:<intent_name>); a consumer that needs them splits the topic. The prescribed payload is {lang, utterance, slots}. For plugin-bundled handlers (pipeline_id == skill_id), the same uniform dispatch applies.
  • Handler-lifecycle payload updated (PIPELINE-1 §8.2). Today the trio payload is {name: <handler_func_name>}. Prescribed: {skill_id, intent_name, optional exception}.
  • destination is a single string (MSG-1 §3.3). A Message addresses one consumer or all of them; there is no multi-address form, and a producer wanting several named consumers emits one Message each. Current ovos-bus-client/message.py accepts an array: the reply swap tests isinstance(dst, list) and takes dst[0] as the new source, silently dropping every other member. Any deployment that relies on the array form loses recipients on the first swap already; under v1 the shape is simply not available.
  • Poll correlation is context.utterance_id (PIPELINE-1 §9.1.1, COMMON-QUERY-1 §6.4, FALLBACK-1 §6.1, CONVERSE-1 §4.2). No poll family mints a correlation key of its own — the earlier COMMON-QUERY-1 query_id is removed. Nothing in shipped code carries the field: the common-query plugin matches answers by the phrase string, and ovos-bus-client's CollectionMessage carries a (handler_id, query_id) pair that the derivations do not propagate. See §5.4 for the field itself.
  • A wrong-typed session field is treated as omitted, loudly (SESSION-1 §2). A field whose value is of the wrong type is filled from the default as if absent, and the violation SHOULD be logged at WARN naming both the field and the type received. Current Session deserialization accepts whatever it is handed and produces no diagnostic, so a typo'd client field fails silently for the life of the session.
  • The deployment's enabled languages are provisioned, never read from the bus (SESSION-1 §3.2.7): "a fixed set a component is provisioned with, not a value it learns or negotiates over the bus". Current ovos-core resolves the utterance language against message.context.get("valid_langs") or get_valid_languages() (ovos_core/intent_services/service.py:331), so a valid_langs list carried in context replaces the configured set for that utterance. Any bus participant can therefore make an utterance resolve to a language that the deployment is not provisioned with. A reader should expect a shipped deployment to honour a context-supplied valid_langs until the read is removed. In our orgs, only the ovos-core end-to-end tests set the field, so removing the read changes behaviour only for clients outside our orgs.

5.4 Architectural divergences

  • The orchestrator maintains a passive registration index (INTENT-4 §10). Today there is no central index — each plugin knows what it consumed; nothing aggregates that view. INTENT-4 prescribes the orchestrator subscribe to all registration topics in parallel with plugins and serve ovos.intent.list / ovos.intent.describe from the passive view. This is a new orchestrator responsibility, not a change to existing behaviour.

  • The match contract is the single obligation (PIPELINE-1 §4.2). The plugin's match operation has one MUST: return a Match or null. Bus emissions during match are allowed — converse plugins, LLM-backed matchers, and agent-backed shapes are all conformant. Session mutation during match goes via Match.updated_session so declined matches' mutations never escape.

  • Match.updated_session as the match-phase session channel (PIPELINE-1 §4.1, §4.2). Promotes the existing ovos-core code pattern sess = match.updated_session or SessionManager.get(message) to a normative Match field. The plugin that produces a claiming match composes any session mutations it needs (clearing or setting session.response_mode, pre-promoting an active-handler to the head, setting intent_context alongside the match) into a fresh snapshot returned in Match.updated_session. The orchestrator uses that snapshot for the dispatch and every downstream stage; a declined-match (plugin returns null) drops the snapshot at the plugin boundary. This is what makes match-phase mutation safe under §6.2 first-match-wins iteration.

  • ovos.utterance.handled on every terminal path (PIPELINE-1 §9.5). Current ovos-workshop's _on_event_error does not emit it on the handler-error path (ovos.py:1478-1497). PIPELINE-1 §8 places trio emission on the orchestrator-wrapper around the handler, not on the handler itself — workshop is the wrapper in current OVOS, and the spec contract requires the wrapper to emit ovos.utterance.handled unconditionally.

  • Handler-trio is orchestrator-owned (PIPELINE-1 §8). The orchestrator that invokes the handler wraps the call and emits ovos.intent.handler.start / .complete / .error around it. Third-party handler code carries no normative obligation to participate in trio emission. Skill authors are not protocol authors; the wrapper observes start / return / exception around an opaque callable.

  • Per-pipeline_id intent introspection (PIPELINE-1 §10). Pull-query / scatter-response surface keyed on pipeline_id, giving consumers visibility into which intents a particular pipeline plugin's matcher has compiled, distinct from the orchestrator's manifest of declared intents (INTENT-4 §10). No current OVOS analogue.

  • CONTEXT-1 scope and ownership encoded in the key shape (CONTEXT-1 §2, §3). A bare key Person is shared; a prefixed key music.skill:Person is private to music.skill. The : is load-bearing — mirroring the <skill_id>:<intent_name> dispatch topic. Drops separate scope and origin fields on stored entries (both were redundant with the key shape). requires_context and excludes_context declarations take an OPTIONAL scope: private|shared discriminator (default private) to express which lookup the gate uses; bare-string declarations default to private to prevent shared-leak.

  • Skill self-identification on every emission (INTENT-4 §3.1). Current OVOS skills set context.skill_id on some emissions but not uniformly. Enforcement is structural on the dispatch path: the orchestrator stamps context.skill_id from the <skill_id>:<intent_name> dispatch topic prefix, and skill emissions via forward/reply inherit automatically. Loader-side interception covers off-dispatch emissions.

  • Entry-point topic renamed ovos.utterance.handle (PIPELINE-1 §9.1). recognizer_loop:utterance fails MSG-1 §2.1.1 naming conventions: : as a segment separator, an implementation-role prefix, and no pairing with the terminal ovos.utterance.handled. Migration cost is real — every audio-input service and intent-service handler is affected. A transitional deployment MAY subscribe to both names during migration.

  • Transformer priority is ascending (TRANSFORM-1 §4). The reference implementation sorts transformer chains descending (reverse=True), where the lowest number runs last and — because each transformer's output overwrites its predecessor's — effectively "wins". That is the exact inverse of the spec's ascending convention (lower = earlier, default 50); an inverse-convention priority assignment MUST be renumbered, since run unrenumbered the chain executes backwards. The TRANSFORM-1 notes in rationale.md record why ascending was chosen.

  • context.utterance_id is the universal correlation key (PIPELINE-1 §9.1.1). The orchestrator stamps it exactly once at lifecycle entry — the utterance arrival, the out-of-band request, the UI command — and it propagates across forward / reply / response under the same MUST that governs session. Everything derived from that lifecycle carries the same value: transformer passes, the pipeline contest, every poll and pong, the dispatch, the terminal events. Two Messages belong to the same lifecycle iff their utterance_id matches, and that is the whole correlation rule — the poll specs (FALLBACK-1, COMMON-QUERY-1, CONVERSE-1) define nothing of their own. Nothing in current OVOS has an equivalent: no component stamps a lifecycle identifier, so a plugin holding a poll window today separates this round's answers from the previous round's by the window's own timing alone. A component MUST NOT overwrite an utterance_id already present, which makes the field the one piece of context a bridge or a transformer must carry through untouched.

  • Identifiers appear in topics only in the dispatch shape (MSG-1 §2.1.1). <skill_id>:<intent_name> is the single identifier-bearing topic shape and the single one a consumer parses; every dotted topic is a static string fixed by the specification that defines it. Addressing beyond the topic is payload, and the routing pair belongs to the reply swap alone. The consequence is that a monitor, a bridge allowlist, or a conformance harness can enumerate a deployment's complete topic surface from the specifications. Current OVOS mints topics from identifiers freely — <skill_id>.converse.ping, <skill_id>.stop.ping, <skill_id>.fallback.ping (in flight), question:action.<skill_id>, <skill_id>.activate — so no such enumeration is possible today, and each poll family needed its own reshaping.

  • The poll families are broadcast contests on static topics (STOP-1 §4.2, CONVERSE-1 §4.2, FALLBACK-1 §6, COMMON-QUERY-1 §6). One ping per round on a static topic, parallel pongs correlated by utterance_id, explicit declines, a bounded window that closes early once nothing still unanswered can change the outcome, and selection in pool order rather than arrival order. ovos.stop.ping / .pong was the model; converse and fallback were reshaped to match it, and common query already had the shape at the ping stage. Current OVOS runs each family differently and none of them this way — §5.7 carries the topic mapping. Two consequences carry the weight:

    • Candidacy stops travelling in the topic. A converse candidate self-checks its own skill_id against context.session.converse_handlers (CONVERSE-1 §4.2) instead of being addressed by name; a common-query skill checks data.skill_id. Shipped ovos-core's converse_service.py and stop_service.py instead emit one addressed ping per candidate in a loop, so a round costs as many Messages as there are candidates and the candidate set is fixed at emit time rather than read from the session the ping carries.
    • The pong stops being a second, differently-shaped name. Shipped skills answer <skill_id>.converse.ping on the shared skill.converse.pong, and <skill_id>.stop.ping on skill.stop.pong — a per-skill request paired with a broadcast reply. Under v1 both halves are static and symmetric.
  • The in-flight fallback implementation diverges from FALLBACK-1 as well as V0 does. ovos-core#808 and ovos-workshop#465 replace the broadcast ovos.skills.fallback.ping with a skill-addressed <skill_id>.fallback.ping / <skill_id>.fallback.pong pair, keeping the broadcast topic bound alongside it. That is the opposite direction from FALLBACK-1 §6, which prescribes one broadcast ovos.fallback.ping per round, answered in parallel on ovos.fallback.pong. The divergence is catalogued here because that work is unmerged and reads as conformance work: it is not.

  • The persona pipeline is stoppable under its own pipeline_id (PERSONA-1 §6, §8.6). The persona pipeline answers the STOP-1 ping affirmatively for a session it holds a conversation on, and is stopped by the ordinary per-skill stop dispatch addressed to its pipeline_id — no drain-list entry and no stop-plugin configuration is involved. Current OVOS has no persona pipeline in the stop cascade at all, so there is no shipped behaviour to change; what the ruling forecloses is the alternative shape, in which the stop plugin would have had to know that persona exists.

  • Handler activity and response state are two session fields (SESSION-1 §3, PIPELINE-1 §7.1, CONVERSE-1 §2.2). active_handlers ({skill_id, activated_at} objects) and response_mode ({skill_id, expires_at}) are the registered fields. The pre-spec Session carried active_skills, a list of (skill_id, timestamp) pairs, and utterance_states, a map of skill_id to "intent" or "response"; both are legacy views of the registered fields and neither is a session field of this corpus. ovos-bus-client exposes utterance_states as a deprecated view of response_mode.

  • Every message of §§5–8 acts on the payload skill_id (INTENT-4 §3.2, §8.5). The payload names the target — the skill whose registration is created, removed, suppressed or re-armed — and context.skill_id names the source, which is provenance a consumer logs but never substitutes in and never rejects on. Registration, deregistration, enable and disable all read the same way, so an admin UI, a provisioning tool or a conflict-resolving skill acts on another skill's registrations without being that skill. Unguarded, ovos.skill.deregister is a remote uninstall, so an orchestrator MAY block cross-skill messages as deployment hardening; the policy's shape is deployment-defined. Cross-session control needs no field of its own — the message affects the scope of whichever session its context declares. Current mycroft.skill.enable_intent / disable_intent carry no identity check in either direction, and no notion of a target distinct from a source.

  • Legacy compatibility twins ride alongside the canonical emission, marked for suppression on the receiving end (MSG-1 §2.1.1, §2.3). ovos-bus-client emits every MIGRATION_MAP canonical topic a second time under its legacy spelling, and every canonical <skill_id>:<intent_name> dispatch a second time as <skill_id>:<intent_name>.intent. The twin frame carries a private context marker — _intent_compat_twin or _namespace_compat_twin — that a receiving client uses to suppress the duplicate before dispatch; MSG-1 §2.3 tolerates the unknown key. No spec claims these markers: they are migration tooling, not part of the normative context shape, and are removed at the next major of ovos-bus-client.

  • ovos.intent.matched.intent_name carries the full dispatch string, not the bare intent name (PIPELINE-1 §9.2). The shipped orchestrator puts <skill_id>:<intent_name> in data.intent_name; PIPELINE-1 §9.2 shows the bare intent name alone. A consumer that needs the bare name derives it by splitting on the first :. Aligned at the next major of ovos-core.

  • Orchestrator-side slot fill from context is an accepted extension, not a divergence to retire (CONTEXT-1 §7). CONTEXT-1 §7 places slot fill from intent_context on the engine, before matching. The shipped orchestrator additionally fills any slot the engine left unresolved from context after the match, merging it into the dispatch data. The two fills draw from distinct sources — engine slots and transformer/context slots — and both are retained. ovos.intent.matched.slots reflects only the engine's slots; the context-filled additions surface solely in the dispatch payload.

5.5 New topics with no direct precedent

  • ovos.intent.matched (PIPELINE-1 §9.2). The positive-match broadcast notification. No current equivalent.
  • ovos.intent.unmatched (PIPELINE-1 §9.3). Renamed from complete_intent_failure; follows the ovos.intent.* namespace for symmetry with ovos.intent.matched.
  • ovos.utterance.speak (PIPELINE-1 §9.6). The NL output exit point; symmetric to ovos.utterance.handle. No current equivalent — TTS trigger is currently implicit.
  • ovos.utterance.speak.b64 (AUDIO-1 §3.4). Variant of ovos.utterance.speak for remote-client delivery: the audio output service runs the same TTS pipeline but emits synthesised audio as base64 via ovos.audio.speech instead of queuing for local playback. Used by bridges serving satellites without TTS (BRIDGE-1 §4.2.4).
  • ovos.audio.speech (AUDIO-1 §4.3). Base64-encoded synthesised audio broadcast; emitted in response to ovos.utterance.speak.b64. Carries a listen flag. Remote clients (e.g. satellites relayed by a bridge) decode and play the audio themselves.
  • ovos.audio.queue / ovos.audio.play_sound (AUDIO-1 §4.1, §4.2). Sound-effect playback topics. Payloads accept either a uri or inline base64 audio field, enabling cross-host audio delivery without shared filesystem access.
  • ovos.stt.failed (AUDIO-IN-1 §5). Terminal event a MUST when a capture yields no usable transcription: the service emits it instead of ovos.utterance.handle, never both, and no handler lifecycle follows. ovos.utterance.handle with an empty or phantom utterances list is non-conformant, which is what makes the failure observable rather than a silence a client has to distinguish from "still transcribing". The payload MAY be empty; context.session rides along like every other emission from the service. Current ovos-dinkum-listener emits recognizer_loop:speech.recognition.unknown — a name that fails MSG-1 §2.1.1 on the : separator and the implementation-role root, and one no other listener is obliged to emit. See §5.7 for the mapping.
  • ovos.intent.list / ovos.intent.describe (INTENT-4 §10). Introspection topics served from the orchestrator's passive registration index.
  • ovos.transformer.{type}.list (TRANSFORM-1 §6). Per-type introspection of loaded transformers.
  • Materialize-default-session rule on forward / reply / response (MSG-1 §5). Formalizes a "MAY" convenience for in-process subsystems; compatible with existing behaviour.

Removed mechanisms — out-of-band state pushes

The specs define no topic on which any participant pushes a session at another. Three mechanisms in shipped code do exactly that, and none has a home in the spec family. This appendix is the one place that record lives; the specs themselves describe only what exists.

  • add_context / remove_context (Adapt-specific) and mycroft.skill.set_cross_context — the pre-CONTEXT-1 write path for intent context, a bus message distinct from the session it mutated. CONTEXT-1 §5.0 does not replace these with a new topic; it removes the class of mechanism. A skill writes session.intent_context directly on the session it carries or replies with (CONTEXT-1 §5.1–§5.3), and the write travels with the message per SESSION-1 §4 — there is nothing left to push separately. The reasoning is the same as for session.sync below: a mutation message that is not the session itself races the reply the session belongs to.

    v1 history. CONTEXT-1 v1 (commit 387125e) briefly normatively defined a topic-based equivalent — ovos.context.set / .unset / .clear — as the skill-facing write API. The consistency-audit commit a94382f deleted that topic table along with the rest of the message-based write path, but its CHANGELOG entry mislabelled the deleted topics "nonexistent" and pointed at ovos.session.sync as their replacement — itself an out-of-band push topic later retired by #160. This §5.0 addition completes that arc: the write path settled on is the session-carried one, not a replacement topic of either kind.

  • ovos.session.sync — a bare session broadcast. Shipped code implements handle_session_sync and a bare-sync bootstrap on bus connect. The spec has no equivalent: a named session is client-authoritative (SESSION-2 §2.5), so a server-side push fights the ownership model and is overwritten by the next inbound client Message. Every legitimate mutation has an in-lifecycle boundary instead (SESSION-2 §2.6), and a proactive interaction propagates by the adoption rule (SESSION-2 §3.2): the client adopts the session of any user-facing Message it renders.

  • ovos.session.update_default — a default-session broadcast (see §5.2.1), emitted from connect_to_bus via _broadcast_default_session. The spec converges the default session two other ways (SESSION-2 §2.7): co-located processes derive their initial view from the same deployment configuration, and thereafter adopt the session of the Messages they act on or render. That leaves one writer per session and removes the multi-writer clobber the broadcast makes possible.

V1 removal scope. handle_session_sync and its subscription; the bare-sync bootstrap on connect; _broadcast_default_session and its connect_to_bus call site. The only bridging behaviour code keeps during migration is the connect handshake: a bare sync on connect maps to ovos.session.update_default until both mechanisms are gone.

5.6 Things the specs do not change

  • The session object's internal shape is owned by OVOS-SESSION-1; the field set is the closed set defined there plus whatever future specs claim via SESSION-1 §2.2. The "extra" fields current OVOS Session carries (persona_id, system_unit, time_format, date_format, …) ride through as non-normative pass-through and may be claimed by future per-domain specs.
  • The mycroft.* topic prefix outside the intent layer (e.g. mycroft.audio.*) — these are not part of any spec here.
  • The <skill_id>:<intent_name> dispatch topic — kept verbatim from current OVOS so no skill needs to migrate its handler subscription. Under MSG-1 §2.1.1 it is now also the only topic shape built from identifiers; keeping it unchanged is what let every other identifier-bearing topic go static (§5.4).
  • Engine-specific introspection topics. The standard plugins expose their own debug / inspection topics — for example intent.service.adapt.reply, intent.service.adapt.manifest, intent.service.adapt.vocab.manifest, and intent.service.padatious.get. These are plugin-specific surface, parallel to the spec's generic ovos.intent.list / ovos.intent.describe (INTENT-4 §10). The specs do not claim authority over them — they remain plugin-defined and may continue to coexist with the orchestrator's generic index.

5.7 Predecessor-topic mapping

The bus topics formalized by INTENT-4 and PIPELINE-1 replace a number of predecessor names. The mapping:

Registration topics (INTENT-4)

Predecessor topic v1 replacement Notes
register_vocab folded into ovos.intent.register.keyword Vocabularies in v1 are inline samples or file-by-path inside the registration.
register_intent (Adapt parser) ovos.intent.register.keyword Adapt's IntentBuilder.__dict__ payload replaced by the structured shape.
padatious:register_intent ovos.intent.register.template Same content, structured payload.
padatious:register_entity ovos.entity.register Entities are not Padatious-specific.
detach_intent ovos.intent.deregister Identity now expressed as the structured triple, not the munged skill_id:intent_name string.
detach_skill ovos.skill.deregister Predecessor handlers resolve the skill from context. A client that mirrors ovos.skill.deregister onto detach_skill therefore turns two shapes into a self-deregistration on the mirrored copy: a cross-skill deregistration (payload names another skill), which removes the source together with the target, and a malformed one (payload omits skill_id), which the spec handlers reject and the predecessor handlers act on. The cross-skill form of INTENT-4 §8.4 needs the mirror off (emit_legacy=false in ovos-bus-client); the malformed form is unsafe on a mirroring client rather than merely ignored. The mirror runs both ways through two flags: emit_legacy copies ovos.skill.deregister onto detach_skill, and modernize copies detach_skill onto ovos.skill.deregister. So a producer emitting detach_skill directly reaches the same consumers, and the source is removed whichever wire it uses.
mycroft.skill.enable_intent / mycroft.skill.disable_intent ovos.intent.enable / ovos.intent.disable First-class topics under v1, with the prefix dropped. The legacy payload names only the intent and never a target skill_id, and the legacy payload has no field for the payload skill_id that INTENT-4 §3.2 requires. Current deployments act on a toggle in two ways. On the legacy topic, ovos-workshop sends the message to every skill, and each skill acts only when the named intent is one of its own; the target is the skill that receives the message, and nothing is logged. On the v1 topic, ovos-spec-tools copies only intent_name across the bridge, and ovos-core uses context.skill_id when the payload has no skill_id and logs the substitution at WARNING. The substitution only sets the manifest entry's enabled field (ovos-core ovos_core/intent_services/manifest.py:270-275). ovos-adapt-pipeline-plugin (ovos_adapt/opm.py:96-118) and ovos-padatious-pipeline-plugin (ovos_padatious/opm.py:1088-1091) act on the payload skill_id only, as §3.2 requires, so a bridged toggle changes the manifest's enabled field but not match candidacy. This substitution does not conform to §3.2 and is kept only while the legacy bridge exists.
mycroft.skills.loaded ovos.skill.loaded Session-keyed, carries capabilities instead of a filesystem path and display name. mycroft.skills.loading_failure has no successor: a skill that did not load announces nothing.
skillmanager.list / mycroft.skills.list ovos.skills.list / .list.response A response derivation with a skills array instead of a map keyed by skill id; entries carry session_id, capabilities and an intent count instead of active.

Utterance-lifecycle topics (PIPELINE-1)

Predecessor topic Status
recognizer_loop:utterance renamed to ovos.utterance.handle (see §5.4)
complete_intent_failure renamed to ovos.intent.unmatched — follows ovos.intent.* namespace.
ovos.utterance.cancelled unchanged — kept as the cancellation signal.
ovos.utterance.handled unchanged — kept as the universal end-marker.
<skill_id>:<intent_name> unchanged — dispatch topic; a plugin-bundled handler has skill_id == pipeline_id.
speak ovos.utterance.speak
mycroft.skill.handler.start / .complete / .error renamed to ovos.intent.handler.start / .complete / .error
ovos.session.update_default retire — subscribe to ovos.utterance.handled (PIPELINE-1 §9.5) to read updated default-session state; or adopt the session of any assistant-emitted Message on the default session (SESSION-2 §2.7). See §5.2.1 and §5.5.
ovos.session.sync (incl. the bare-sync connect bootstrap) retire — no spec defines a session push topic; session mutates at the SESSION-2 §2.6 boundaries and converges by adoption (SESSION-2 §2.7, §3.2). See §5.5.

Poll-family topics (STOP-1, CONVERSE-1, FALLBACK-1, COMMON-QUERY-1)

Every poll family resolves to a static ping / pong pair, with the responder's identity in data.skill_id and the round identified by context.utterance_id (§5.4).

Predecessor topic v1 replacement Notes
<skill_id>.stop.ping ovos.stop.ping One broadcast for the round instead of one addressed ping per active handler. Silence is not a decline: with no positive pong the plugin falls back to recency over session.active_handlers (STOP-1 §4.1), where shipped stop_service.py defaults a non-responder to False and can end the round with no target at all.
skill.stop.pong ovos.stop.pong Already a shared reply topic; renamed for symmetry with the ping.
<skill_id>.converse.ping ovos.converse.ping Candidacy moves from the topic to a session.converse_handlers membership self-check (CONVERSE-1 §4.2).
skill.converse.pong ovos.converse.pong Shared reply topic renamed; payload gains skill_id, result, and an optional error_code.
ovos.skills.fallback.register / .deregister ovos.fallback.register / .deregister Same payload (skill_id, priority); renamed into the ovos.fallback.* root. Shipped ovos-workshop emits and ovos-core consumes the predecessor spelling. From ovos-spec-tools 1.12.0a1 the pair is in MIGRATION_MAP, so a modern ovos-bus-client on either end bridges the two spellings (compat twins, above). A producer on one spelling and a consumer on the other meet only when one of them runs that client; two pre-spec-tools clients never meet.
ovos.skills.fallback.ping ovos.fallback.ping Already a broadcast; renamed into the ovos.fallback.* root. Declines become explicit so the window can close early.
ovos.skills.fallback.pong ovos.fallback.pong As above. Note the in-flight <skill_id>.fallback.ping / .pong work (§5.4) moves away from this shape.
ovos.common_query.ping / .pong unchanged Already static broadcast topics with identity in the payload.
question:query ovos.common_query.request Static topic; the skill being asked is named in data.skill_id. See §5.2.
question:query.response ovos.common_query.response Answer correlated by utterance_id, not by the phrase string.

Agent tools topics (TOOLS-1)

Predecessor topic v1 replacement Notes
ovos.persona.tools.<toolbox_id>.call ovos.persona.tools.call Identifier moves from the topic to data.toolbox_id (TOOLS-1 §4.2); payloads unchanged. Shipped ovos-plugin-manager ToolBox.bind subscribes to the predecessor; kept one stable cycle.
ovos.persona.tools.discover unchanged Already a static broadcast answered per toolbox with data.toolbox_id (TOOLS-1 §4.1).
ovos.tools.list / .get / .invoke / .reload unchanged Already static topics with the tool in the payload; TOOLS-1 §5 fixes their payloads.

Listening-failure topic (AUDIO-IN-1)

Predecessor topic v2 replacement Notes
recognizer_loop:speech.recognition.unknown ovos.stt.failed Terminal event when STT produced no usable transcription; a MUST rather than a listener-specific courtesy. See §5.5.

Out of scope

Predecessor topic Status
add_context / remove_context Removed, not replaced by a topic — CONTEXT-1 §5.0 writes session.intent_context in place on the carried/replied session. See §5.5 "Removed mechanisms".
mycroft.skill.set_cross_context / remove_cross_context Same removal; a shared-scope (bare-key) entry under CONTEXT-1 §3.
<skill_id>.activate Activity-tracking emit currently in ovos-core; not part of any spec here.

Listening-lifecycle topics (AUDIO-IN-1)

Predecessor topic v2 replacement Notes
recognizer_loop:record_begin ovos.listener.record.started Capture start. : segment separator and implementation-role prefix dropped; no payload.
recognizer_loop:record_end ovos.listener.record.ended Capture end; pairs with the start signal.
recognizer_loop:sleep ovos.listener.sleep Controller-to-listener sleep request.
recognizer_loop:wake_up ovos.listener.wake Leave sleep mode (AUDIO-IN-1 §6.4); no payload, device-scoped.
mycroft.awoken ovos.listener.awoken Sleep→awake transition; moved into the ovos.listener.* namespace.

Resting-display data (GUI-1)

The predecessor events are emitted by the homescreen manager inside the legacy render adapter (ovos-legacy-mycroft-gui-plugin homescreen.py, documented in its docs/homescreen.md) for the Qt shell. They reach no other adapter. The homescreen.data.apps, .examples, .connectivity and homescreen.widget.* events are not mapped here.

Predecessor topic Replacement Notes
homescreen.data.time none The backend reads the device clock (GUI-1 §6.9.1). The predecessor pushed preformatted strings every 10 seconds.
homescreen.data.weather gui.ambient.weather Carries the SYSTEM_weather keys instead of weather_code / weather_temp. The predecessor polled one weather application on its own topic (skill-ovos-weather.openvoiceos.weather.request) every 900 seconds; the replacement is emitted by whichever component holds the data. weather_api_enabled: false becomes the absence of the event.
homescreen.data.notifications gui.ambient.notifications notification_counter becomes count. The stored notification model has no successor.
homescreen.data.wallpaper none The wallpaper is part of the resting display the backend owns (GUI-1 §6.9).

GUI status events (GUI-1)

The status events a render backend sees today, as forwarded by ovos-gui 1.5.3a1 (namespace.py _define_messages_to_forward), mapped to the topics GUI-1 §5.3 requires the GUI service to deliver. That release already forwards ovos.listener.record.started / .ended, ovos.listener.sleep, ovos.listener.awoken and ovos.utterance.handled under their spec names; the rest reach clients only under the predecessor names.

Predecessor topic Replacement Notes
recognizer_loop:wakeword ovos.listener.wakeword AUDIO-IN-1 §6.5.
recognizer_loop:record_begin / recognizer_loop:record_end ovos.listener.record.started / .ended See listening-lifecycle topics, above.
recognizer_loop:utterance ovos.utterance.handle PIPELINE-1 §9.1.
mycroft.skill.handler.start / .complete ovos.intent.handler.start / .complete PIPELINE-1 §8. The predecessor forward list carries no .error; GUI-1 adds it, so an erroring handler ends the thinking state.
complete_intent_failure ovos.intent.unmatched PIPELINE-1 §9.3.
speak ovos.utterance.speak PIPELINE-1 §9.6.
recognizer_loop:audio_output_start / recognizer_loop:audio_output_end ovos.audio.output.started / .ended AUDIO-1 §5.1, §5.2.
enclosure.mouth.viseme_list ovos.audio.output.visemes AUDIO-1 §5.4. Same payload (start, visemes as [code, end] pairs with codes "0"–"6" from ovos-utils lang/visimes.py); emitted by ovos-audio playback.py at the start of each spoken item, from the TTS plugin or a G2P plugin. The predecessor name sits in the enclosure namespace of the Mark 1 hardware; the signal belongs to the audio output service.
recognizer_loop:recognition_unknown ovos.stt.failed The forward list names a spelling the shipped listener does not emit (it emits recognizer_loop:speech.recognition.unknown, see listening-failure topic, above), so no listening-failure event reaches a render backend today.
mycroft.events.triggered (namespace: "system", event_name: the topic) the status-event hook (GUI-1 §6.3) The envelope the legacy GUI service wraps each forwarded topic in on its client socket. Under GUI-1 the client framing is the adapter's concern (GUI-1 §1); the legacy render adapter keeps it.

Producing namespace (GUI-1)

The predecessor carries the producing namespace in data. ovos-bus-client GUIInterface (apis/gui.py) builds every GUI Message as Message(type, data) with no context and puts {'__from': skill_id} in the data; ovos-gui (namespace.py) reads data['__from']. With no context, these Messages also carry no session, so routing (GUI-1 §5.1) falls back to "default". GUIInterface.show_page (apis/gui.py) also puts __idle in the gui.page.show data, as null when the caller set none, and ovos-gui strips __-prefixed keys from session data and reads __idle.

Predecessor key Replacement Notes
data.__from context.skill_id GUI-1 §4.1, the attribution key of INTENT-4 §3.1. For one stable release cycle a producer MAY keep sending __from beside context.skill_id, and a GUI service MAY read __from when the context names no skill_id. GUI-1 reserves no data keys, so during that cycle a GUI service removes __from from data before it merges the rest as session data. After that cycle a GUI Message with no context.skill_id is ignored with a diagnostic.
data.__idle data.persist GUI-1 §4.2, §4.3; same values and meaning. For one stable release cycle a producer MAY keep sending __idle beside persist, and a GUI service MAY read __idle when persist is absent. A null __idle means omitted. gui.page.show data is not session data, so nothing is stripped.

Interaction events (GUI-1)

The legacy GUI service turns a client's mycroft.events.triggered into a bus Message whose type it builds from the client's namespace and event_name (ovos-gui bus.py on_message). GUIInterface.register_handler in ovos-bus-client subscribes to that type.

Predecessor topic Replacement Notes
<namespace>.<event> (data: the event's parameters) gui.interaction (namespace, event, data) GUI-1 §7.2. The predecessor puts a skill id in the message type, can collide with a skill's own messages, and cannot be filtered as one family. The adapter, not the GUI service, emits the replacement. An adapter MAY emit both for one stable release cycle (VERSIONING.md).

Page focus and touch (GUI-1)

The legacy GUI service reads two bus topics that a client emits about the page it shows (ovos-gui namespace.py handle_page_gained_focus, handle_page_interaction). Both carry namespace, page_number and skill_id; the handlers key on skill_id.

Predecessor topic Replacement Notes
gui.page_gained_focus gui.page.focus (namespace, page_number) GUI-1 §4.2. The predecessor is a report that the client already changed page, and acts on any active namespace; the replacement is a request, acts only on the namespace on top, and makes the service dispatch the new entry to every adapter. Keyed on namespace, not skill_id.
gui.page_interaction gui.page.focus naming the current entry GUI-1 §4.2. Restarts the namespace's removal timer, as the predecessor does.

A GUI service MAY read both predecessors for one stable release cycle (VERSIONING.md).

Notifications (GUI-1, draft)

GUIInterface.show_notification in ovos-bus-client emits the predecessor for the Qt shell's notification popups.

Predecessor topic Replacement Notes
ovos.notification.api.set (duration s, sender, text, action, type, style, callback_data) ovos.notification.set (id, text, sender, type, style, action, duration_ms) GUI-1 §5.4. duration in seconds becomes duration_ms; id is new and required; the success style and callback_data have no successor; a tap emits gui.interaction with action as event. A producer MAY emit both for one stable release cycle (VERSIONING.md).
ovos.notification.api.set.controlled / ovos.notification.api.remove.controlled ovos.notification.set with type: "sticky" / ovos.notification.clear The controlled notification becomes a sticky one with an id.

5.8 Bus bridge (BRIDGE-1)

  • BRIDGE-1 defines source-stamping and destination-based routing; current HiveMind bridges route primarily by session_id. HiveMind groups messages by session_id and delivers them to the peer that owns that session. BRIDGE-1 prescribes destination as the primary signal because two peers sharing the same session_id (including "default") cannot be distinguished by session_id alone. HiveMind deployments that use per-peer session_ids are conformant with either model; deployments that share the "default" session across multiple peers must migrate to destination-based routing for client isolation.
  • Poll-family broadcasts are filtered by what a participant hosts, not by addressing. BRIDGE-1 §3.2 enumerates the poll families — ovos.converse.ping / .pong, ovos.common_query.ping / .pong / .request / .response, ovos.fallback.ping / .pong, ovos.stop.ping / .pong — and forbids relaying any of them to a participant with no skill registered in the Message's session: a pure client has nothing a poll could address, and relaying to it both exposes internal handler enumeration and invites answers to polls it cannot serve. A participant that does host skills in the session receives the broadcasts unfiltered and its skills decide for themselves. The rule exists because the reply derivation gives a poll a destination, so destination-based routing alone would relay it. Current HiveMind bridges apply no such filter; a satellite that hosts no skills still sees the poll traffic of every session it shares.
  • No existing implementation fully conforms to OVOS-BRIDGE-1. The bridge spec formalizes a role that exists in deployments (the HiveMind gateway, the bus client, any inbound message fan-in) with a tighter normative core — source-stamping and session-preservation requirements — than current implementations provide.