Spec ID: OVOS-STOP-1 · Version: 2 · Status: Draft
This specification defines the stop pipeline plugin — a pipeline plugin that matches utterances expressing the user's intention to interrupt the assistant's current activity — and the bus surface by which it cascades a stop request across the recency-ordered list of active handlers or broadcasts a global stop signal.
The intent_name stop is reserved at OVOS-PIPELINE-1 §7.3.
Dependencies: OVOS-MSG-1 (envelope and derivations), OVOS-PIPELINE-1
(pipeline-plugin contract, dispatch shape, reserved-name registry,
active_handlers stamping), OVOS-SESSION-1 (session field registry),
OVOS-SESSION-2 (mutation boundaries), OVOS-CONVERSE-1 (response_mode
and converse_handlers field definitions).
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, MAY are used as in RFC 2119.
This specification defines: the stop plugin role, the reserved
intent_name stop, the stoppability discovery and cascade algorithm,
the global broadcast, and the session-scoping obligations of stop
subscribers.
It does not define: vocabulary file formats, matching algorithms, confidence thresholds, audio capture control, handler-side framework APIs, wake-word and barge-in policies, or post-stop in-flight interaction teardown (a skill-side or orchestrator-side concern).
| Reserved intent_name | Meaning |
|---|---|
stop |
Cease activity for the inbound session_id. Dispatched on <target_skill_id>:stop where the target is the most recently activated (highest activated_at) positive pong responder (§4). |
Skills and other pipelines MUST NOT register stop under
OVOS-INTENT-4. A registration naming this intent_name is malformed per
OVOS-INTENT-4 §5.3 — consumers log at WARN and do not index.
The intent_name global_stop is not reserved. The stop plugin
uses it for its own self-dispatch (<stop_plugin_id>:global_stop, §5),
namespaced under its own pipeline_id.
A stop plugin is an ordinary pipeline plugin (PIPELINE-1 §3) that matches stop-command utterances and returns Matches under §2. It is subject to the same denylist filtering, first-match-wins iteration, and circuit-breaker rules as every other pipeline plugin.
A stop plugin MAY be loaded as multiple pipeline_id entries in
session.pipeline (e.g. separate confidence tiers). When multiple
entries are deployed, the implementation MUST ensure exactly one
ovos.stop broadcast is emitted per global stop event per session.
Match.skill_id MUST equal:
- for
intent_name: "stop"— the most recently activated positive pong responder selected per §4; - for
intent_name: "global_stop"— thepipeline_idwhose handler emitsovos.stop.
A stop plugin MUST:
- return
Nonefor any language for which it cannot resolve stop vocabulary; - read
session.active_handlersto drive the §4 cascade; - perform the ping-pong exchange (§4.2) inside
match.
A stop plugin SHOULD provide explicit "stop everything" vocabulary that
maps directly to intent_name: "global_stop" without running the §4
cascade. Generic stop utterances run the cascade per §4.
Inside match:
- Read
session.active_handlers. If empty, return aglobal_stopMatch per §5. - Emit
ovos.stop.pingand collectovos.stop.pongresponses within a deployer-configured timeout (default: 0.5 s; SHOULD NOT exceed 1 s). - Identify positive responders: pongs where
can_handle: trueandskill_idappears insession.active_handlers. Pongs from skills not inactive_handlersMUST be ignored; late pongs MAY be ignored. - If at least one positive responder exists, select the entry with the
highest
activated_atinsession.active_handlers. If two entries share the sameactivated_at, select the entry appearing latest in the list (most recently stamped). Constructupdated_sessionremoving thatskill_idfromactive_handlersand clearing anyresponse_modeentry it owns. ReturnMatch(skill_id=<that_skill_id>, intent_name="stop", updated_session=...). - If no positive responder exists, return a
global_stopMatch per §5.
ovos.stop.ping — broadcast. Payload MAY be empty. session_id
is carried in Message context per OVOS-MSG-1.
ovos.stop.pong — shared reply topic. A handler MUST emit a
Message of type ovos.stop.pong derived via reply (OVOS-MSG-1 §5),
so that routing metadata is preserved and the pong reaches the stop
plugin regardless of where the skill is running (local or remote).
source and destination are layer-2 metadata and do not affect the
topic name.
{ "skill_id": "example.skill", "can_handle": true }| Field | Type | Required | Meaning |
|---|---|---|---|
skill_id |
string | yes | The skill_id of the responding handler. |
can_handle |
boolean | yes | Whether the handler has stoppable activity for the inbound session_id. |
can_handle: true asserts that the handler has user-visible or
session-affecting activity in progress for the inbound session_id
and is prepared to cease it on receipt of <skill_id>:stop.
A handler with no current activity MUST respond can_handle: false
or remain silent. A handler that does not subscribe to
ovos.stop.ping, or does not respond within the timeout, is treated
as can_handle: false and is ineligible as a stop target for that
ping round. If no handler declares stoppability, the cascade escalates
to global_stop per §4.1 step 5.
The orchestrator dispatches <target_skill_id>:stop per PIPELINE-1 §7,
firing the handler-lifecycle trio (ovos.intent.handler.start,
.complete, .error).
The stop handler MUST:
- cease the activity it declared stoppable, scoped to the inbound
session_id; - not initiate a second stop sequence if a stop dispatch arrives while already stopping — the duplicate MUST be treated as a no-op.
The stop handler MUST NOT interrupt activity belonging to a different
session_id.
Match.updated_session is committed before dispatch (PIPELINE-1 §4.2)
and is not rolled back if the stop handler emits the .error
lifecycle event.
A handler that cannot be stopped SHOULD remove itself from
session.active_handlers by emitting any session-carrying Message
with the updated list, so that future ping rounds bypass it.
A global_stop Match is returned in three cases:
- explicit "stop everything" vocabulary (§3.2);
- generic stop with empty
active_handlers(§4.1 step 1); - generic stop with no positive pong responders (§4.1 step 5).
The global_stop Match MUST carry a fully-cleaned updated_session:
Match(
skill_id = <shared_pipeline_id>,
intent_name = "global_stop",
updated_session = <session with:
active_handlers → []
converse_handlers → []
response_mode → absent>
)
All three fields are cleared atomically at match time via
Match.updated_session (PIPELINE-1 §4.2), before dispatch.
PIPELINE-1 §7.1 stamps <shared_pipeline_id> onto active_handlers
at dispatch time (the name global_stop is not reserved, so stamping
suppression does not apply). This is intentional: the stop plugin MAY
participate in converse after a global stop — for example, to handle a
follow-up clarification — provided it registers a converse handler.
The handler dispatched by <shared_pipeline_id>:global_stop MUST emit
ovos.stop. Every component performing user-visible activity MUST
subscribe to ovos.stop and cease activity for the session_id
carried in Message context per OVOS-MSG-1.
ovos.stop is not a dispatch topic — it does not follow the
<skill_id>:<intent_name> shape and does not fire the handler-lifecycle
trio. The namespace ovos.stop.* is reserved by this specification.
For intent_name: "stop", a stop plugin MUST clear the
session.response_mode entry whose skill_id matches the dispatch
target, via Match.updated_session. If no such entry exists, the
field is left unchanged. For intent_name: "global_stop",
response_mode is removed entirely as part of the §5.2 Match
construction.
session.active_handlers (OVOS-PIPELINE-1 §7.1) is the stop
cascade's recency input. It is distinct from session.converse_handlers
(OVOS-CONVERSE-1 §2.1), the converse plugin's eligibility list.
A stop plugin MUST drain active_handlers via Match.updated_session
(committed pre-dispatch per PIPELINE-1 §4.2):
stopMatch — remove the dispatch target entry only;global_stopMatch — emptyactive_handlersentirely and emptyconverse_handlers(OVOS-CONVERSE-1 §2.1) entirely.
The stamping push (PIPELINE-1 §7.1) is suppressed for the reserved
intent_name stop, so the removal in updated_session is the final
state. It is not suppressed for global_stop.
A stop plugin MUST honour session.blacklisted_skills and
session.blacklisted_intents (PIPELINE-1 §5):
blacklisted_skills: a handler whoseskill_idappears in this list MUST NOT be selected as a stop target;blacklisted_intents: applies to the dispatched intent_name ("stop"or"global_stop"). A stop plugin MUST not resolve a intent_name that appears inblacklisted_intents. Astoputterance that would resolve toglobal_stop(§4.1 steps 1 or 5) is subject to theglobal_stopentry, not thestopentry. This list does not affect the ping broadcast.
A deployment that includes the stop plugin SHOULD place the
highest-confidence stop stage first in session.pipeline, ahead
of the converse plugin and every intent-matching stage. Lower-confidence
stop stages MAY be interleaved with intent-matching stages.
Typical ordering:
session.pipeline: [
"stop_high",
"converse",
"intent_high",
"stop_medium",
"intent_medium",
"stop_low",
"intent_low"
]
| Topic | Direction | Purpose |
|---|---|---|
ovos.stop.ping |
stop plugin → all | Stoppability query (broadcast) |
ovos.stop.pong |
skill → stop plugin | Stoppability response |
<target_skill_id>:stop |
orchestrator → skill | Skill-directed stop dispatch |
<stop_plugin_id>:global_stop |
orchestrator → stop handler | Global stop dispatch |
ovos.stop |
stop handler → all | Universal stop broadcast |
Dispatch topics (<…>:stop, <…>:global_stop) fire the
handler-lifecycle trio. No other topic in this table does.
- return
intent_nameof exactly"stop"or"global_stop"(§2, §3.1); - set
Match.skill_idper §3.1; - return
Nonewhen no stop vocabulary matches orlangis unsupported (§3.1); - collect pong responses within a deployer-configured timeout (§4.1);
- ignore pongs from skills absent from
session.active_handlers(§4.1); - treat non-responding handlers as
can_handle: false(§4.2); - clear
session.response_modefor the dispatch target viaMatch.updated_session(§6.1); - drain
active_handlersviaMatch.updated_session(§6.2); - on
global_stop, also emptyconverse_handlersviaMatch.updated_session(§6.2); - return
global_stopwhenactive_handlersis empty or no positive pong responder exists (§4.1 steps 1, 5); - honour
session.blacklisted_skillsandsession.blacklisted_intentsper §6.3; - subscribe to
<own_pipeline_id>:global_stopand emitovos.stop(§5.3); - emit exactly one
ovos.stopbroadcast per global stop event per session (§3.1).
- configure the ping-pong timeout to not exceed 1 s (§4.1);
- provide explicit "stop everything" vocabulary mapping to
global_stopwithout cascade (§3.2).
- place the highest-confidence stop stage first in
session.pipeline(§7); - configure stop vocabulary for every supported language.
- subscribe to both
<own_skill_id>:stopandovos.stop; - on
<own_skill_id>:stop, cease stoppable activity for the inboundsession_id(§4.3); - on
ovos.stop, cease all activity for the inboundsession_id; - treat a duplicate
<own_skill_id>:stoporovos.stopas a no-op (§4.3).
- subscribe to
ovos.stop.pingand respond with areply-derivedovos.stop.pongcarryingcan_handlefor the inboundsession_id(§4.2); - remove itself from
session.active_handlerswhen it cannot be stopped (§4.4).
- subscribe to
ovos.stopand cease activity for the inboundsession_id.
- treat OVOS-INTENT-4 registrations naming
stopas malformed — log at WARN and decline to index (§2).
- OVOS-PIPELINE-1 — pipeline contract, dispatch, active_handlers
- OVOS-SESSION-1 — session field registry
- OVOS-SESSION-2 — mutation boundaries
- OVOS-MSG-1 — Message envelope and derivations