← APPENDIX.md · Non-normative
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.
Formalizations of behaviour in the reference implementation that need no change:
- The Message envelope (
type/data/context) — matchesovos-bus-client.Message. sourceanddestinationas the routing pair, and theMessage.replyswap that owns it — matchesovos-bus-client/message.py. The one point of departure is the array-valueddestinationthe shipped swap still accepts; see §5.3.ovos.common_query.ping/ovos.common_query.pong— static broadcast poll topics with the responder's identity indata.skill_id. Matchovos-common-query-pipeline-plugin/opm.pyandovos-workshop/skills/ovos.py's__handle_common_query_pingverbatim; 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) — matchesovos-media/media_backends/base.py, which already carriespositionand seek targets in ms. context.sessionas a serialized Session object — matchesovos-bus-client/client/client.py'smessage.context["session"] = sess.serialize().session.session_id == "default"for device-local origin — matchesovos-audio/utils.py'srequire_default_sessiondecorator.session.langas the user's preferred language — matches the Session class'slangattribute.forward/reply/responsederivation semantics — matchesovos-bus-client.Message.{forward,reply,response}.- The
.responsesuffix convention — pervasive across OVOS topics. ovos.utterance.cancelledandovos.utterance.handled(PIPELINE-1) — match current topic names verbatim.- Per-utterance first-match-wins iteration (PIPELINE-1) —
matches
ovos-core/intent_services/service.py'shandle_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.
| 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. |
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 bySessionManagerto 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 toovos.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) andmycroft.skill.set_cross_context— emitted by shipped skills andMycroftSkillhelpers 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 asovos.session.update_defaultabove. See §5.5 for the removed-mechanism record.
- Keyword intent registration is atomic (INTENT-4 §5).
Today a keyword intent is built up via multiple
register_vocabmessages followed by aregister_intentwith an AdaptIntentBuilder.__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_intentcarries{name, samples, file_name, lang, blacklisted_words}; the prescribed shape uses the structured(skill_id, intent_name, lang)triple plussamples|fileandblacklist|blacklist_file. - Dispatch payload is minimal (PIPELINE-1 §7.1). Today
dispatch carries
skill_idandintent_namein 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}. destinationis 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. Currentovos-bus-client/message.pyaccepts an array: thereplyswap testsisinstance(dst, list)and takesdst[0]as the newsource, 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-1query_idis removed. Nothing in shipped code carries the field: the common-query plugin matches answers by thephrasestring, andovos-bus-client'sCollectionMessagecarries 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
Sessiondeserialization 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-coreresolves the utterance language againstmessage.context.get("valid_langs") or get_valid_languages()(ovos_core/intent_services/service.py:331), so avalid_langslist carried incontextreplaces 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-suppliedvalid_langsuntil the read is removed. In our orgs, only theovos-coreend-to-end tests set the field, so removing the read changes behaviour only for clients outside our orgs.
-
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.describefrom 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
matchoperation has one MUST: return aMatchornull. Bus emissions duringmatchare allowed — converse plugins, LLM-backed matchers, and agent-backed shapes are all conformant. Session mutation duringmatchgoes viaMatch.updated_sessionso declined matches' mutations never escape. -
Match.updated_sessionas the match-phase session channel (PIPELINE-1 §4.1, §4.2). Promotes the existing ovos-core code patternsess = 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 settingsession.response_mode, pre-promoting an active-handler to the head, setting intent_context alongside the match) into a fresh snapshot returned inMatch.updated_session. The orchestrator uses that snapshot for the dispatch and every downstream stage; a declined-match (plugin returnsnull) drops the snapshot at the plugin boundary. This is what makes match-phase mutation safe under §6.2 first-match-wins iteration. -
ovos.utterance.handledon every terminal path (PIPELINE-1 §9.5). Currentovos-workshop's_on_event_errordoes 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 emitovos.utterance.handledunconditionally. -
Handler-trio is orchestrator-owned (PIPELINE-1 §8). The orchestrator that invokes the handler wraps the call and emits
ovos.intent.handler.start/.complete/.erroraround 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
Personis shared; a prefixed keymusic.skill:Personis private tomusic.skill. The:is load-bearing — mirroring the<skill_id>:<intent_name>dispatch topic. Drops separatescopeandoriginfields on stored entries (both were redundant with the key shape).requires_contextandexcludes_contextdeclarations take an OPTIONALscope: private|shareddiscriminator (defaultprivate) 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_idon some emissions but not uniformly. Enforcement is structural on the dispatch path: the orchestrator stampscontext.skill_idfrom the<skill_id>:<intent_name>dispatch topic prefix, and skill emissions viaforward/replyinherit automatically. Loader-side interception covers off-dispatch emissions. -
Entry-point topic renamed
ovos.utterance.handle(PIPELINE-1 §9.1).recognizer_loop:utterancefails MSG-1 §2.1.1 naming conventions::as a segment separator, an implementation-role prefix, and no pairing with the terminalovos.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_idis 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 acrossforward/reply/responseunder the same MUST that governssession. 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 theirutterance_idmatches, 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 anutterance_idalready present, which makes the field the one piece ofcontexta 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 thereplyswap 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/.pongwas 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_idagainstcontext.session.converse_handlers(CONVERSE-1 §4.2) instead of being addressed by name; a common-query skill checksdata.skill_id. Shippedovos-core'sconverse_service.pyandstop_service.pyinstead 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.pingon the sharedskill.converse.pong, and<skill_id>.stop.pingonskill.stop.pong— a per-skill request paired with a broadcast reply. Under v1 both halves are static and symmetric.
- Candidacy stops travelling in the topic. A converse
candidate self-checks its own
-
The in-flight fallback implementation diverges from FALLBACK-1 as well as V0 does.
ovos-core#808andovos-workshop#465replace the broadcastovos.skills.fallback.pingwith a skill-addressed<skill_id>.fallback.ping/<skill_id>.fallback.pongpair, keeping the broadcast topic bound alongside it. That is the opposite direction from FALLBACK-1 §6, which prescribes one broadcastovos.fallback.pingper round, answered in parallel onovos.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 itspipeline_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) andresponse_mode({skill_id, expires_at}) are the registered fields. The pre-specSessioncarriedactive_skills, a list of(skill_id, timestamp)pairs, andutterance_states, a map ofskill_idto"intent"or"response"; both are legacy views of the registered fields and neither is a session field of this corpus.ovos-bus-clientexposesutterance_statesas a deprecated view ofresponse_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 — andcontext.skill_idnames 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.deregisteris 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 itscontextdeclares. Currentmycroft.skill.enable_intent/disable_intentcarry 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-clientemits everyMIGRATION_MAPcanonical 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_twinor_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 ofovos-bus-client. -
ovos.intent.matched.intent_namecarries the full dispatch string, not the bare intent name (PIPELINE-1 §9.2). The shipped orchestrator puts<skill_id>:<intent_name>indata.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 ofovos-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_contexton the engine, before matching. The shipped orchestrator additionally fills any slot the engine left unresolved from context after the match, merging it into the dispatchdata. The two fills draw from distinct sources — engine slots and transformer/context slots — and both are retained.ovos.intent.matched.slotsreflects only the engine's slots; the context-filled additions surface solely in the dispatch payload.
ovos.intent.matched(PIPELINE-1 §9.2). The positive-match broadcast notification. No current equivalent.ovos.intent.unmatched(PIPELINE-1 §9.3). Renamed fromcomplete_intent_failure; follows theovos.intent.*namespace for symmetry withovos.intent.matched.ovos.utterance.speak(PIPELINE-1 §9.6). The NL output exit point; symmetric toovos.utterance.handle. No current equivalent — TTS trigger is currently implicit.ovos.utterance.speak.b64(AUDIO-1 §3.4). Variant ofovos.utterance.speakfor remote-client delivery: the audio output service runs the same TTS pipeline but emits synthesised audio as base64 viaovos.audio.speechinstead 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 toovos.utterance.speak.b64. Carries alistenflag. 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 aurior inline base64audiofield, 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 ofovos.utterance.handle, never both, and no handler lifecycle follows.ovos.utterance.handlewith an empty or phantomutteranceslist 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.sessionrides along like every other emission from the service. Currentovos-dinkum-listeneremitsrecognizer_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.
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) andmycroft.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 writessession.intent_contextdirectly 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 forsession.syncbelow: 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 commita94382fdeleted that topic table along with the rest of the message-based write path, but its CHANGELOG entry mislabelled the deleted topics "nonexistent" and pointed atovos.session.syncas 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 implementshandle_session_syncand 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 fromconnect_to_busvia_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.
- 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, andintent.service.padatious.get. These are plugin-specific surface, parallel to the spec's genericovos.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.
The bus topics formalized by INTENT-4 and PIPELINE-1 replace a number of predecessor names. The mapping:
| 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. |
| 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. |
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. |
| 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. |
| 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. |
| 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. |
| 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. |
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). |
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. |
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. |
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). |
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).
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. |
- BRIDGE-1 defines source-stamping and destination-based routing;
current HiveMind bridges route primarily by session_id.
HiveMind groups messages by
session_idand delivers them to the peer that owns that session. BRIDGE-1 prescribesdestinationas the primary signal because two peers sharing the samesession_id(including"default") cannot be distinguished by session_id alone. HiveMind deployments that use per-peersession_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 thereplyderivation gives a poll adestination, 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.