From 48fd997d2d864626dd45d2ca985b368157a04b67 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 01:52:56 +0100 Subject: [PATCH 01/21] =?UTF-8?q?OVOS-STOP-1=20v1=20=E2=80=94=20stop=20pip?= =?UTF-8?q?eline=20plugin=20specification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New normative spec defining the stop pipeline plugin role, the two reserved intent_names `stop` and `global_stop`, the broadcast ping-pong stoppability discovery, the LIFO cascade across `session.active_handlers`, and the `ovos.stop` universal broadcast namespace. Five-topic bus surface: - `ovos.stop.ping` / `ovos.stop.pong` — single-broadcast feasibility query with shared shared response topic - `:stop` — standard PIPELINE-1 dispatch to the LIFO head of positive responders - `:global_stop` — fallback / explicit "stop everything" dispatch; handler emits `ovos.stop` - `ovos.stop` — universal broadcast every component subscribes to, session-scoped cleanup obligation Skills SHOULD self-prune from `active_handlers` when unstoppable; stop subscribers MUST cease only activity keyed to the inbound `session_id`. Spec body is implementation-agnostic; no real-project names appear outside the spec-ID namespace. Depends on: OVOS-MSG-1, OVOS-PIPELINE-1, OVOS-SESSION-1, OVOS-SESSION-2, OVOS-CONVERSE-1. Co-Authored-By: Claude Opus 4.7 (1M context) --- stop.md | 381 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 381 insertions(+) create mode 100644 stop.md diff --git a/stop.md b/stop.md new file mode 100644 index 00000000..f794af11 --- /dev/null +++ b/stop.md @@ -0,0 +1,381 @@ +# Stop Pipeline Plugin Specification + +**Spec ID:** OVOS-STOP-1 · **Version:** 1 · **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 and broadcasts a global +stop signal when no handler can absorb the request. The two +intent_names `stop` and `global_stop` are reserved at the +OVOS-PIPELINE-1 §7.3 registry; no other plugin or skill may +register them. + +It builds on OVOS-MSG-1 (envelope, `forward` / `reply` / +`response`), OVOS-PIPELINE-1 (pipeline-plugin contract, dispatch +shape, lifecycle trio, reserved-name registry), +OVOS-SESSION-1 (session field registry), OVOS-SESSION-2 +(mutation boundaries and session-keyed-state projection), and +OVOS-CONVERSE-1 (`session.active_handlers` recency list, +`session.response_mode`). + +The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, +**MAY**, and **RECOMMENDED** are used as in RFC 2119. + +--- + +## 1. Scope + +This specification defines the stop plugin role, the two reserved +intent_names, the stoppability discovery and cascade algorithm, +the global broadcast namespace, and the session-scoping +obligations of stop subscribers. + +It does **not** define vocabulary file formats, matching +algorithms, confidence thresholds, voice-activity detection, +microphone or audio capture control, handler-side framework +APIs, or wake-word and barge-in policies. + +--- + +## 2. Reserved intent_names + +| Reserved intent_name | Meaning of a Match bearing this name | +|----------------------|--------------------------------------| +| `stop` | A specific active handler should cease activity. Dispatched on `:stop` where the target is the LIFO head of positive pong responders (§4). | +| `global_stop` | All assistant activity should cease. Dispatched on `:global_stop`; the handler emits `ovos.stop` (§5). | + +Skills and other pipelines **MUST NOT** register either name +under OVOS-INTENT-4. A registration naming a reserved +intent_name is malformed per OVOS-INTENT-4 §5.3 — consumers log +at WARN and do not index. + +A stop plugin **MUST** use one of these two intent_names in +every Match it returns. + +--- + +## 3. The stop plugin role + +The **stop plugin role** is a behavioural contract a pipeline +plugin (PIPELINE-1 §3) MAY adopt. A stop plugin is an ordinary +pipeline plugin — subject to the same denylist filtering, +first-match-wins iteration, and circuit-breaker rules — that +matches stop-command utterances and emits Matches under §2. + +### 3.1 Pipeline identity + +A stop plugin is loaded as one or more `pipeline_id` entries in +`session.pipeline`. The conventional three confidence tiers +`stop_high`, `stop_medium`, `stop_low` MAY be registered as +separate `pipeline_id`s or merged into one multi-tier plugin; +the choice is a deployment concern. + +`Match.skill_id` MUST equal the target of the dispatch: + +- for `intent_name: "stop"`, the LIFO head of positive pong + responders (§4.2); +- for `intent_name: "global_stop"`, the stop plugin's own + `pipeline_id`. + +### 3.2 Match obligations + +In addition to the general PIPELINE-1 §4 contract: + +- A stop plugin MUST return `None` for any language for which it + cannot resolve stop vocabulary. +- A stop plugin MUST read `session.active_handlers` to drive the + cascade (§4). +- A stop plugin emitting the stoppability ping-pong (§4.2) + performs that exchange **inside `match`**. This is a documented + exception to the PIPELINE-1 §4.4 low-latency guidance, + justified by the stop plugin's escape-hatch position at the + head of the pipeline (§8). No other plugin is iterating during + this exchange. + +### 3.3 Vocabulary + +A stop plugin SHOULD distinguish utterances expressing a generic +stop intention from utterances expressing an explicit +"stop everything" intention. Vocabulary file organisation is not +normative; only the resulting Match's `intent_name` is. + +The "stop everything" vocabulary maps directly to +`intent_name: "global_stop"` without consulting +`active_handlers`. The generic vocabulary triggers the §4 +cascade. + +--- + +## 4. Generic stop — `intent_name: "stop"` + +When the utterance matches generic stop vocabulary, the stop +plugin performs stoppability discovery against +`session.active_handlers` and either dispatches a directed stop +to a single handler or escalates to `global_stop`. + +### 4.1 Algorithm + +Inside `match`: + +1. Read `session.active_handlers`. If empty, return + `Match(skill_id=, intent_name="global_stop")` + (§5). +2. Emit a single `ovos.stop.ping` broadcast and collect responses + on `ovos.stop.pong` up to a deployer-defined timeout + (RECOMMENDED default: 0.5 seconds). +3. Identify positive responders — those whose `skill_id` + appears in `session.active_handlers` and whose pong returned + `can_handle: true` within the window. Pongs from skills not + in `active_handlers` MUST be ignored; late pongs MAY be + ignored. +4. If at least one positive responder exists, select the one + whose position in `session.active_handlers` is most recent + (LIFO head among positives) and return + `Match(skill_id=, intent_name="stop")`. +5. If no positive responder exists, return + `Match(skill_id=, intent_name="global_stop")`. + +### 4.2 Ping and pong + +`ovos.stop.ping` is a single broadcast addressing every active +handler at once; `ovos.stop.pong` is a single shared response +topic carrying the responder's `skill_id` in the payload. This +shape avoids any need for the stop plugin to enumerate per-skill +topics. + +**Ping** — `ovos.stop.ping`: + +Payload MAY be empty. The inbound `session_id` is carried by the +Message's session context (OVOS-MSG-1) and identifies the +session for which feasibility is being queried. + +**Pong** — `ovos.stop.pong`: + +```json +{ "skill_id": "music.skill", "can_handle": true } +``` + +| Field | Type | Required | Meaning | +|-------|------|----------|---------| +| `skill_id` | string | yes | The `owner_id` of the responding handler. | +| `can_handle` | boolean | yes | Whether the handler can process a stop request *for the inbound `session_id`*. | + +The ping-pong is a request/response pair under OVOS-MSG-1 §5.3. +A handler that does not respond within the timeout window is +treated as `can_handle: false`. Silence indicates only that the +handler did not opt in to the discovery protocol. + +A handler MUST respond only when it has stoppable activity for +the inbound `session_id`. A handler that is active for one +`session_id` but cannot stop its activity for the inbound +`session_id` MUST either respond `can_handle: false` or remain +silent. + +### 4.3 Dispatch and skill stop handler + +The Match's dispatch follows the standard PIPELINE-1 §7 contract. +The orchestrator emits `:stop`; the target +skill's stop handler runs as an ordinary intent handler, firing +the standard handler-lifecycle trio +(`ovos.intent.handler.start`, `.complete`, `.error`). + +The skill's stop handler MUST cease only the activity keyed to +the inbound `session_id`. A skill serving multiple sessions in +parallel MUST NOT interrupt activity belonging to a different +session. + +### 4.4 Self-pruning of `active_handlers` + +A handler that cannot be stopped — by design, by current state, +or for the relevant session — SHOULD remove itself from +`session.active_handlers` (via the mutation pathway defined in +CONVERSE-1) so that future ping rounds bypass it entirely. +Self-pruning is the static complement to the runtime ping-pong; +together they keep the discovery cost proportional to the number +of genuinely stoppable handlers. + +--- + +## 5. Global stop — `intent_name: "global_stop"` + +A `global_stop` Match is returned in three cases (§3.3, §4.1): + +- explicit "stop everything" vocabulary match; +- generic stop with empty `active_handlers`; +- generic stop with no positive pong responders. + +### 5.1 Dispatch and broadcast + +The orchestrator dispatches `:global_stop`. The +stop handler emits the universal broadcast: + +| Topic | Direction | Summary | +|-------|-----------|---------| +| `ovos.stop` | stop handler → all | Cease all activity. | + +Payload MAY be empty. Every component performing user-visible +activity (audio playback, TTS, media, timers, animation, the +converse plugin's polling loop) MUST subscribe to `ovos.stop` +and cease its activity on receipt. + +`ovos.stop` is not a dispatch — it does not follow the +`:` shape and does not fire the +handler-lifecycle trio. The trio fires for the `global_stop` +dispatch itself. + +The namespace `ovos.stop.*` is reserved by this specification +for future stop-related signals. + +### 5.2 Session scoping + +A subscriber to `ovos.stop` MUST cease only the activity it has +running for the broadcast's inbound `session_id`. A TTS engine +speaking concurrently for two sessions stops only the session +that issued the stop; a media player playing for one session +does not interrupt another session's playback. + +--- + +## 6. Session interaction + +### 6.1 `response_mode` + +A stop plugin SHOULD clear `session.response_mode` via +`Match.updated_session` whenever it matches: + +- for `intent_name: "stop"`, clear the entry whose `owner_id` + matches the dispatch target; +- for `intent_name: "global_stop"`, remove `response_mode` + entirely. + +Per PIPELINE-1 §6.3, session mutations carried by +`Match.updated_session` are committed at dispatch time; the +clear is durable even if the handler crashes mid-execution. + +### 6.2 `active_handlers` + +A stop plugin MUST NOT modify `session.active_handlers` directly. +The list is mutated by CONVERSE-1 (TTL pruning, size cap, +activation events) and by handler self-pruning under §4.4. + +### 6.3 Denylists + +A stop plugin MUST honour `session.blacklisted_skills` and +`session.blacklisted_intents` (PIPELINE-1 §5.3–§5.4). A handler +whose `owner_id` appears in `blacklisted_skills` MUST NOT be +pinged or selected as a stop target. + +--- + +## 7. Pipeline positioning + +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. +This positioning is the user-facing escape hatch from any +in-flight conversational state and is also what makes the +in-match ping-pong of §3.2 latency-safe: no other plugin is +iterating during the discovery window. + +Lower-confidence stop stages MAY be interleaved with +intent-matching stages so that ambiguous stop-like utterances do +not pre-empt ordinary intents. A typical ordering: + +``` +session.pipeline: [ + "stop_high", # escape hatch + "converse", + "intent_matcher_high", + "stop_medium", + "intent_matcher_medium", + "stop_low", + "intent_matcher_low" +] +``` + +--- + +## 8. Bus surface summary + +| Topic | Direction | Purpose | Defined in | +|-------|-----------|---------|------------| +| `ovos.stop.ping` | stop plugin → all | Stoppability ping (broadcast) | §4.2 | +| `ovos.stop.pong` | skill → stop plugin | Stoppability response (shared) | §4.2 | +| `:stop` | orchestrator → target skill | Skill-directed stop dispatch | §4.3 | +| `:global_stop` | orchestrator → stop handler | Global stop dispatch | §5.1 | +| `ovos.stop` | stop handler → all | Universal stop broadcast | §5.1 | + +Dispatch topics fire the handler-lifecycle trio per PIPELINE-1 +§7; no other topic in this table does. + +--- + +## 9. Conformance + +### A stop pipeline plugin **MUST**: + +- match per PIPELINE-1 §4, returning either `intent_name: "stop"` + or `intent_name: "global_stop"` and never any other value; +- set `Match.skill_id` per §3.1 — the dispatch target for `stop`, + the plugin's own `pipeline_id` for `global_stop`; +- return `None` when no stop vocabulary matches or when no + vocabulary is available for the requested `lang`; +- read `session.active_handlers` to drive the cascade (§4.1); +- communicate session mutations exclusively through + `Match.updated_session`; +- honour `session.blacklisted_skills` and + `session.blacklisted_intents` (§6.3); +- subscribe to `:global_stop` and emit `ovos.stop` + from that handler. + +### A stop pipeline plugin **SHOULD**: + +- clear `session.response_mode` via `Match.updated_session` (§6.1); +- skip the ping-pong and return `global_stop` immediately when + `active_handlers` is empty. + +### A deployment that includes a stop plugin **SHOULD**: + +- place the highest-confidence stop stage first in + `session.pipeline` (§7); +- configure stop vocabulary for every supported language. + +### A skill that participates in stop **SHOULD**: + +- subscribe to `ovos.stop.ping` and reply on `ovos.stop.pong` + with its own `skill_id` and `can_handle` reflecting feasibility + *for the inbound `session_id`* — or remain silent if it has no + stoppable activity for that session; +- implement a stop intent handler subscribed to + `:stop` that ceases only the activity keyed to + the inbound `session_id`; +- subscribe to `ovos.stop` and cease all activity for the + inbound `session_id`; +- remove itself from `session.active_handlers` (via CONVERSE-1's + mutation pathway) when it cannot be stopped, so that future + ping rounds bypass it (§4.4); +- treat duplicate stop dispatches and `ovos.stop` broadcasts as + idempotent. + +### Every non-skill component performing user-visible activity **MUST**: + +- subscribe to `ovos.stop` and cease activity keyed to the + inbound `session_id` on receipt. + +### The orchestrator **MUST**: + +- treat OVOS-INTENT-4 registrations naming `stop` or + `global_stop` as malformed per INTENT-4 §5.3 — log at WARN + and decline to index. + +--- + +## See also + +- *Utterance Lifecycle and Pipeline Specification* (OVOS-PIPELINE-1) +- *Active Handlers and Interactive Response Specification* (OVOS-CONVERSE-1) +- *Bus Message Specification* (OVOS-MSG-1) +- *Session Carrier Wire Shape Specification* (OVOS-SESSION-1) +- *Session Lifecycle and State Ownership Specification* (OVOS-SESSION-2) From 43d9c3d0d17968eec6ae3cc947b2211f9dfe31ce Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 01:57:20 +0100 Subject: [PATCH 02/21] STOP-1: narrow reservation to `stop` only; `global_stop` is plugin-internal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only the intent_name `stop` is reserved at PIPELINE-1 §7.3, because skills are dispatched on `:stop` and a competing skill-level match would bypass the §4 cascade. `global_stop` is the stop plugin's escalation self-dispatch and remains namespaced under `:global_stop` — collision-free regardless of any other skill registering the same name. Drop its reservation from §2 and from the orchestrator conformance MUST. Co-Authored-By: Claude Opus 4.7 (1M context) --- stop.md | 64 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/stop.md b/stop.md index f794af11..5373173b 100644 --- a/stop.md +++ b/stop.md @@ -7,10 +7,9 @@ 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 and broadcasts a global -stop signal when no handler can absorb the request. The two -intent_names `stop` and `global_stop` are reserved at the -OVOS-PIPELINE-1 §7.3 registry; no other plugin or skill may -register them. +stop signal when no handler can absorb the request. The +intent_name `stop` is reserved at the OVOS-PIPELINE-1 §7.3 +registry; no other plugin or skill may register it. It builds on OVOS-MSG-1 (envelope, `forward` / `reply` / `response`), OVOS-PIPELINE-1 (pipeline-plugin contract, dispatch @@ -39,20 +38,28 @@ APIs, or wake-word and barge-in policies. --- -## 2. Reserved intent_names +## 2. Reserved intent_name + +This specification reserves a single intent_name at the +OVOS-PIPELINE-1 §7.3 registry: | Reserved intent_name | Meaning of a Match bearing this name | |----------------------|--------------------------------------| -| `stop` | A specific active handler should cease activity. Dispatched on `:stop` where the target is the LIFO head of positive pong responders (§4). | -| `global_stop` | All assistant activity should cease. Dispatched on `:global_stop`; the handler emits `ovos.stop` (§5). | - -Skills and other pipelines **MUST NOT** register either name -under OVOS-INTENT-4. A registration naming a reserved -intent_name is malformed per OVOS-INTENT-4 §5.3 — consumers log -at WARN and do not index. - -A stop plugin **MUST** use one of these two intent_names in -every Match it returns. +| `stop` | A specific active handler should cease activity for the inbound `session_id`. Dispatched on `:stop` where the target is the LIFO head of positive pong responders (§4). | + +Skills and other pipelines **MUST NOT** register `stop` under +OVOS-INTENT-4. A registration naming the reserved intent_name +is malformed per OVOS-INTENT-4 §5.3 — consumers log at WARN and +do not index. The reservation prevents competing skill-level +matches from bypassing the §4 cascade. + +The stop plugin's escalation path uses the intent_name +`global_stop` for its own self-dispatch +(`:global_stop`, §5). This name is plugin-internal +and not reserved: a skill MAY register `global_stop` for its own +purposes, as the dispatch shape namespaces the topic under +`` and cannot collide with another skill's +handler. --- @@ -198,17 +205,25 @@ of genuinely stoppable handlers. --- -## 5. Global stop — `intent_name: "global_stop"` +## 5. Global stop escalation + +When a stop utterance cannot be cascaded to a specific handler, +the stop plugin escalates to a global broadcast. The escalation +path uses the intent_name `global_stop` as a plugin-internal +self-dispatch (not a reserved name; see §2). -A `global_stop` Match is returned in three cases (§3.3, §4.1): +A `global_stop` self-dispatch is emitted in three cases: -- explicit "stop everything" vocabulary match; -- generic stop with empty `active_handlers`; -- generic stop with no positive pong responders. +- explicit "stop everything" vocabulary match (§3.3); +- generic stop with empty `active_handlers` (§4.1 step 1); +- generic stop with no positive pong responders (§4.1 step 5). ### 5.1 Dispatch and broadcast -The orchestrator dispatches `:global_stop`. The +The orchestrator dispatches `:global_stop`. +Because `skill_id` equals the stop plugin's own `pipeline_id`, +the dispatch is uniquely routed to the stop handler regardless +of any skill that happens to use the same intent_name. The stop handler emits the universal broadcast: | Topic | Direction | Summary | @@ -366,9 +381,10 @@ Dispatch topics fire the handler-lifecycle trio per PIPELINE-1 ### The orchestrator **MUST**: -- treat OVOS-INTENT-4 registrations naming `stop` or - `global_stop` as malformed per INTENT-4 §5.3 — log at WARN - and decline to index. +- treat OVOS-INTENT-4 registrations naming `stop` as malformed + per INTENT-4 §5.3 — log at WARN and decline to index. + Registrations naming `global_stop` are accepted normally; + `global_stop` is not reserved. --- From 9a16232a0775787026eff74f0bafdb0e767677cf Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 02:00:11 +0100 Subject: [PATCH 03/21] =?UTF-8?q?STOP-1=20=C2=A74.1/=C2=A76.2:=20stop=20Ma?= =?UTF-8?q?tch=20MUST=20remove=20target=20from=20active=5Fhandlers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stop plugin's match-phase mutation of session.active_handlers is part of the cascade's correctness story: removing the dispatched target via Match.updated_session propagates the post-stop state through the rest of the utterance lifecycle without waiting for TTL pruning or size-cap eviction, keeping downstream reads of the list accurate. Reverses the prior §6.2 prohibition for the dispatch-target case only; CONVERSE-1's other mutation pathways (TTL, size cap, activation, self-pruning §4.4) continue to own the remaining lifecycle. A global_stop Match MUST NOT touch active_handlers. Co-Authored-By: Claude Opus 4.7 (1M context) --- stop.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/stop.md b/stop.md index 5373173b..d0587a97 100644 --- a/stop.md +++ b/stop.md @@ -139,8 +139,11 @@ Inside `match`: ignored. 4. If at least one positive responder exists, select the one whose position in `session.active_handlers` is most recent - (LIFO head among positives) and return - `Match(skill_id=, intent_name="stop")`. + (LIFO head among positives). Construct `updated_session` by + removing that `skill_id` from `active_handlers` and clearing + any `response_mode` entry it owns. Return + `Match(skill_id=, intent_name="stop", + updated_session=...)`. 5. If no positive responder exists, return `Match(skill_id=, intent_name="global_stop")`. @@ -271,9 +274,22 @@ clear is durable even if the handler crashes mid-execution. ### 6.2 `active_handlers` -A stop plugin MUST NOT modify `session.active_handlers` directly. -The list is mutated by CONVERSE-1 (TTL pruning, size cap, -activation events) and by handler self-pruning under §4.4. +A stop plugin MUST remove the dispatch target from +`session.active_handlers` via `Match.updated_session` whenever it +emits a `stop` Match. This propagates the post-stop state through +the rest of the utterance lifecycle and keeps subsequent reads of +`active_handlers` (by downstream plugins, the handler trio, or +the next utterance) accurate without waiting for TTL pruning or +size-cap eviction. + +The plugin MUST NOT modify entries other than the dispatch +target. Other CONVERSE-1 mutation pathways (TTL pruning, size +cap, activation events) and skill self-pruning (§4.4) continue +to own the remaining lifecycle of the list. + +A `global_stop` Match MUST NOT modify `active_handlers` — the +cascade affects no single handler in particular, and the +`ovos.stop` broadcast subscribers handle their own state. ### 6.3 Denylists @@ -340,6 +356,8 @@ Dispatch topics fire the handler-lifecycle trio per PIPELINE-1 - read `session.active_handlers` to drive the cascade (§4.1); - communicate session mutations exclusively through `Match.updated_session`; +- on a `stop` Match, remove the dispatch target from + `session.active_handlers` via `Match.updated_session` (§6.2); - honour `session.blacklisted_skills` and `session.blacklisted_intents` (§6.3); - subscribe to `:global_stop` and emit `ovos.stop` From e2418de0480392430d95a06b750cc3acc25ee26e Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 02:03:05 +0100 Subject: [PATCH 04/21] =?UTF-8?q?STOP-1=20=C2=A75/=C2=A76.2/=C2=A79:=20glo?= =?UTF-8?q?bal=5Fstop=20wipes=20active=5Fhandlers;=20skills=20MUST=20liste?= =?UTF-8?q?n=20to=20both=20topics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two correctness clarifications: 1. A global_stop Match MUST empty session.active_handlers entirely via Match.updated_session (§5.1, §6.2). Previously global_stop was specified as not touching the list; on reflection, global stop terminates every handler the session is tracking, so the cleared list is the accurate post-stop state and must propagate through the rest of the utterance lifecycle for the same reason §4.1 propagates the single- target removal. 2. Skills MUST subscribe to BOTH :stop and ovos.stop. The two are not alternatives — depending on the cascade outcome a skill receives one or the other, and must be ready for either. Lifted the subscription requirements from SHOULD to MUST and split the skill conformance into MUST (subscriptions, session-scoped cleanup, idempotency) and SHOULD (ping-pong participation, self-pruning). Co-Authored-By: Claude Opus 4.7 (1M context) --- stop.md | 84 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 30 deletions(-) diff --git a/stop.md b/stop.md index d0587a97..4f026e8c 100644 --- a/stop.md +++ b/stop.md @@ -126,9 +126,8 @@ to a single handler or escalates to `global_stop`. Inside `match`: -1. Read `session.active_handlers`. If empty, return - `Match(skill_id=, intent_name="global_stop")` - (§5). +1. Read `session.active_handlers`. If empty, return a + `global_stop` Match constructed per §5. 2. Emit a single `ovos.stop.ping` broadcast and collect responses on `ovos.stop.pong` up to a deployer-defined timeout (RECOMMENDED default: 0.5 seconds). @@ -144,8 +143,8 @@ Inside `match`: any `response_mode` entry it owns. Return `Match(skill_id=, intent_name="stop", updated_session=...)`. -5. If no positive responder exists, return - `Match(skill_id=, intent_name="global_stop")`. +5. If no positive responder exists, return a `global_stop` Match + constructed per §5. ### 4.2 Ping and pong @@ -221,7 +220,22 @@ A `global_stop` self-dispatch is emitted in three cases: - generic stop with empty `active_handlers` (§4.1 step 1); - generic stop with no positive pong responders (§4.1 step 5). -### 5.1 Dispatch and broadcast +### 5.1 Match construction + +A `global_stop` Match MUST be: + +`Match(skill_id=, intent_name="global_stop", +updated_session=...)` + +where `updated_session` is the inbound session with: + +- `active_handlers` emptied — global stop terminates every + handler the session is tracking, and the cleared list + propagates through the rest of the utterance lifecycle and + subsequent utterances; +- `response_mode` removed entirely (§6.1). + +### 5.2 Dispatch and broadcast The orchestrator dispatches `:global_stop`. Because `skill_id` equals the stop plugin's own `pipeline_id`, @@ -246,7 +260,7 @@ dispatch itself. The namespace `ovos.stop.*` is reserved by this specification for future stop-related signals. -### 5.2 Session scoping +### 5.3 Session scoping A subscriber to `ovos.stop` MUST cease only the activity it has running for the broadcast's inbound `session_id`. A TTS engine @@ -274,22 +288,23 @@ clear is durable even if the handler crashes mid-execution. ### 6.2 `active_handlers` -A stop plugin MUST remove the dispatch target from -`session.active_handlers` via `Match.updated_session` whenever it -emits a `stop` Match. This propagates the post-stop state through -the rest of the utterance lifecycle and keeps subsequent reads of +A stop plugin MUST update `session.active_handlers` via +`Match.updated_session` according to the Match it emits: + +- a `stop` Match MUST remove the dispatch target entry only, + leaving the rest of the list intact; +- a `global_stop` Match MUST empty `active_handlers` entirely + (§5.1). + +This propagates the post-stop state through the rest of the +utterance lifecycle and keeps subsequent reads of `active_handlers` (by downstream plugins, the handler trio, or the next utterance) accurate without waiting for TTL pruning or size-cap eviction. -The plugin MUST NOT modify entries other than the dispatch -target. Other CONVERSE-1 mutation pathways (TTL pruning, size -cap, activation events) and skill self-pruning (§4.4) continue -to own the remaining lifecycle of the list. - -A `global_stop` Match MUST NOT modify `active_handlers` — the -cascade affects no single handler in particular, and the -`ovos.stop` broadcast subscribers handle their own state. +Other CONVERSE-1 mutation pathways (TTL pruning, size cap, +activation events) and skill self-pruning (§4.4) continue to own +the remaining lifecycle of the list outside of stop matches. ### 6.3 Denylists @@ -335,8 +350,8 @@ session.pipeline: [ | `ovos.stop.ping` | stop plugin → all | Stoppability ping (broadcast) | §4.2 | | `ovos.stop.pong` | skill → stop plugin | Stoppability response (shared) | §4.2 | | `:stop` | orchestrator → target skill | Skill-directed stop dispatch | §4.3 | -| `:global_stop` | orchestrator → stop handler | Global stop dispatch | §5.1 | -| `ovos.stop` | stop handler → all | Universal stop broadcast | §5.1 | +| `:global_stop` | orchestrator → stop handler | Global stop dispatch | §5.2 | +| `ovos.stop` | stop handler → all | Universal stop broadcast | §5.2 | Dispatch topics fire the handler-lifecycle trio per PIPELINE-1 §7; no other topic in this table does. @@ -357,7 +372,8 @@ Dispatch topics fire the handler-lifecycle trio per PIPELINE-1 - communicate session mutations exclusively through `Match.updated_session`; - on a `stop` Match, remove the dispatch target from - `session.active_handlers` via `Match.updated_session` (§6.2); + `session.active_handlers` via `Match.updated_session`; on a + `global_stop` Match, empty `active_handlers` entirely (§6.2); - honour `session.blacklisted_skills` and `session.blacklisted_intents` (§6.3); - subscribe to `:global_stop` and emit `ovos.stop` @@ -375,22 +391,30 @@ Dispatch topics fire the handler-lifecycle trio per PIPELINE-1 `session.pipeline` (§7); - configure stop vocabulary for every supported language. +### A skill that participates in stop **MUST**: + +- subscribe to **both** `:stop` and `ovos.stop`. + `:stop` carries the skill-directed cascade + dispatch (§4.3); `ovos.stop` carries the global broadcast + (§5.2). The two subscriptions are not alternatives — a skill + receives one or the other depending on the cascade outcome, + and must be ready for either; +- on receiving `:stop`, cease only the activity + keyed to the inbound `session_id`; +- on receiving `ovos.stop`, cease all activity keyed to the + inbound `session_id`; +- treat duplicate stop dispatches and `ovos.stop` broadcasts as + idempotent. + ### A skill that participates in stop **SHOULD**: - subscribe to `ovos.stop.ping` and reply on `ovos.stop.pong` with its own `skill_id` and `can_handle` reflecting feasibility *for the inbound `session_id`* — or remain silent if it has no stoppable activity for that session; -- implement a stop intent handler subscribed to - `:stop` that ceases only the activity keyed to - the inbound `session_id`; -- subscribe to `ovos.stop` and cease all activity for the - inbound `session_id`; - remove itself from `session.active_handlers` (via CONVERSE-1's mutation pathway) when it cannot be stopped, so that future - ping rounds bypass it (§4.4); -- treat duplicate stop dispatches and `ovos.stop` broadcasts as - idempotent. + ping rounds bypass it (§4.4). ### Every non-skill component performing user-visible activity **MUST**: From 3e1251daa280d95c603ff786ac00d7f7c589673f Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 02:06:47 +0100 Subject: [PATCH 05/21] STOP-1: incorporate lint tweaks Co-Authored-By: Claude Opus 4.7 (1M context) --- stop.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stop.md b/stop.md index 4f026e8c..75e9b89c 100644 --- a/stop.md +++ b/stop.md @@ -26,9 +26,9 @@ The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, ## 1. Scope -This specification defines the stop plugin role, the two reserved -intent_names, the stoppability discovery and cascade algorithm, -the global broadcast namespace, and the session-scoping +This specification defines the stop plugin role, the reserved +intent_name `stop`, the stoppability discovery and cascade +algorithm, the global broadcast namespace, and the session-scoping obligations of stop subscribers. It does **not** define vocabulary file formats, matching From 476283db0194c6424feac59229954713c85a5c84 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 02:07:25 +0100 Subject: [PATCH 06/21] =?UTF-8?q?STOP-1:=20rename=20stop.md=20=E2=86=92=20?= =?UTF-8?q?ovos-stop-1.md;=20add=20to=20README=20spec=20index?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligns with the project-wide filename convention (filename matches the lowercase spec identifier), matching the recent rename of the other spec files. Adds a new entry to the README's "Utterance lifecycle and matching" spec table under OVOS-CONVERSE-1 with a link to the in-review PR #33. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 1 + stop.md => ovos-stop-1.md | 0 2 files changed, 1 insertion(+) rename stop.md => ovos-stop-1.md (100%) diff --git a/README.md b/README.md index bb40eb1c..fe834f39 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ below). Adoption is voluntary; conformance, once adopted, is not. | OVOS-TRANSFORM-1 | [Transformer Plugins](transformer.md) | 1 | [Draft — in review (PR #20)](https://github.com/OpenVoiceOS/architecture/pull/20) | | OVOS-CONTEXT-1 | [Intent Context](intent-context.md) | 1 | [Draft — in review (PR #18)](https://github.com/OpenVoiceOS/architecture/pull/18) | | OVOS-CONVERSE-1 | [Active Handlers and Interactive Response](converse.md) | 1 | [Draft — in review (PR #25)](https://github.com/OpenVoiceOS/architecture/pull/25) | +| OVOS-STOP-1 | [Stop Pipeline Plugin](ovos-stop-1.md) | 1 | [Draft — in review (PR #33)](https://github.com/OpenVoiceOS/architecture/pull/33) | Each spec carries its own scope statement, design rationale, and conformance section in its header. Open the document for the full diff --git a/stop.md b/ovos-stop-1.md similarity index 100% rename from stop.md rename to ovos-stop-1.md From 4c3de3677d7050913849d4c824bfcc0c533d6145 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 02:22:17 +0100 Subject: [PATCH 07/21] README: include STOP-1 in pipeline-plugin / orchestrator reading orders STOP-1 was added to the spec index table but missing from the role-based reading-order lists. Adds it alongside CONVERSE-1 / CONTEXT-1 / TRANSFORM-1 in the "Building a pipeline plugin?" and "Building an orchestrator?" lines. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe834f39..47416b6b 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ below). Adoption is voluntary; conformance, once adopted, is not. | OVOS-CONTEXT-1 | [Intent Context](intent-context.md) | 1 | [Draft — in review (PR #18)](https://github.com/OpenVoiceOS/architecture/pull/18) | | OVOS-CONVERSE-1 | [Active Handlers and Interactive Response](converse.md) | 1 | [Draft — in review (PR #25)](https://github.com/OpenVoiceOS/architecture/pull/25) | | OVOS-STOP-1 | [Stop Pipeline Plugin](ovos-stop-1.md) | 1 | [Draft — in review (PR #33)](https://github.com/OpenVoiceOS/architecture/pull/33) | +| OVOS-COMMON-QUERY-1 | [Common Query Pipeline Plugin](common-query.md) | 1 | Draft | Each spec carries its own scope statement, design rationale, and conformance section in its header. Open the document for the full @@ -120,8 +121,8 @@ picture — the tables above are an index. **Reading order by role:** - *Writing a skill?* INTENT-1 → INTENT-2 → INTENT-3. INTENT-4 only if you need the registration wire format. -- *Building a pipeline plugin?* PIPELINE-1, then SESSION-1 + SESSION-2, then the role spec (CONVERSE-1, CONTEXT-1, or TRANSFORM-1). -- *Building an orchestrator?* MSG-1 → SESSION-1 → SESSION-2 → PIPELINE-1, then INTENT-4, CONTEXT-1, CONVERSE-1, TRANSFORM-1. +- *Building a pipeline plugin?* PIPELINE-1, then SESSION-1 + SESSION-2, then the role spec (CONVERSE-1, CONTEXT-1, TRANSFORM-1, or STOP-1). +- *Building an orchestrator?* MSG-1 → SESSION-1 → SESSION-2 → PIPELINE-1, then INTENT-4, CONTEXT-1, CONVERSE-1, TRANSFORM-1, STOP-1. - *Surveying the architecture?* [appendix/overview.md §1](appendix/overview.md) for the three-stack narrative. For background — design rationale, comparisons with other systems, From 72386606d0d1ce23c748a67149ba1456f7a98f48 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 14:05:26 +0100 Subject: [PATCH 08/21] =?UTF-8?q?STOP-1=20+=20PIPELINE-1=20=C2=A77.1/?= =?UTF-8?q?=C2=A77.3:=20active=5Fhandlers=20ownership=20at=20the=20orchest?= =?UTF-8?q?rator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves STOP-1's CONVERSE-1 dependency by relocating active_handlers to PIPELINE-1, with reserved-name suppression of the stamping push. PIPELINE-1 §7.1 — new dispatch-time stamping bullet: the orchestrator MUST push {skill_id} onto the head of session.active_handlers, evicting prior entries with the same skill_id. The push is suppressed in two cases: 1. pipeline-plugin self-dispatches (skill_id == pipeline_id), which record no skill activity; 2. dispatches on reserved intent_names listed in §7.3 — converse, response, stop — which represent continuation or termination of an already-active skill's participation, not fresh activation. The push is applied AFTER Match.updated_session is committed, so a plugin that needs to remove the target ahead of the dispatch (the stop cascade) does so via updated_session and the suppressed push leaves the removal in effect. This makes pre-dispatch the single, well-defined mutation boundary — no consumer-side removal protocol, no race between optimistic pre-removal and a failed handler. PIPELINE-1 §7.3 — amend the "stamping fires identically" claim to except the active_handlers push for reserved-name dispatches. SESSION-1 field registry — active_handlers owner moved from CONVERSE-1 §2.1 to PIPELINE-1 §7.1; entry shape generalised to {skill_id, ...} so future specs can extend without re-registering. STOP-1 §4.1 / §5.1 / §6.2 — restore the Match.updated_session removal of the dispatch target on stop matches; global_stop wipes active_handlers entirely. §6.2 explains the pre-dispatch boundary: orchestrator commits updated_session, suppresses the push (reserved-name dispatch), downstream lifecycle sees the post-stop list. The earlier consumer-removal rule is dropped. STOP-1 §6.1 — response_mode clearing made conditional ("if present"); semantics owned elsewhere (CONVERSE-1 when it lands). STOP-1 §3.4 self-pruning — no longer references CONVERSE-1's mutation pathway; communicated via any session-carrying Message. STOP-1 preamble — drop CONVERSE-1 from the dependency list; add the PIPELINE-1 §7.1 source-of-truth pointer. APPENDIX preamble + §1.2 narrative updated by user to include CONVERSE-1, STOP-1, COMMON-QUERY-1. Co-Authored-By: Claude Opus 4.7 (1M context) --- ovos-pipeline-1.md | 29 ++++++++++++++---- ovos-session-1.md | 2 +- ovos-stop-1.md | 75 +++++++++++++++++++++++++++------------------- 3 files changed, 70 insertions(+), 36 deletions(-) diff --git a/ovos-pipeline-1.md b/ovos-pipeline-1.md index 1a26c679..d8bc5365 100644 --- a/ovos-pipeline-1.md +++ b/ovos-pipeline-1.md @@ -807,6 +807,22 @@ The dispatch Message's `context` (OVOS-MSG-1 §4): `pipeline_id` of the plugin that produced the match (§3.1). When the match is self-addressed (`skill_id == pipeline_id`, §7.0), both context keys carry the same identifier. +- **`session.active_handlers` push.** The orchestrator **MUST** + push `{skill_id: }` onto the head of + `session.active_handlers`, evicting any prior entry with the same + `skill_id`. The list is a recency-ordered (MRU) record of which + skills are currently active for the session. The push is + **suppressed** in two cases: (1) pipeline-plugin self-dispatches + (`skill_id == pipeline_id`, §7.0) — the list records skill + activity, not pipeline-plugin internals; (2) dispatches on any + reserved intent_name listed in §7.3 — a reserved-name dispatch + represents a continuation of an already-active skill's + participation (converse, response) or its termination (stop), + not a fresh activation. The push is applied after + `Match.updated_session` is committed, so a plugin that wants to + remove the target ahead of the dispatch (e.g., STOP-1's stop + cascade) does so via `updated_session` and the suppressed push + leaves the removal in effect. The dispatch Message's `data`: @@ -859,11 +875,14 @@ pipeline plugin role. A reserved intent_name is one that: reserving specification defines. A reservation is a **namespace lease**, not a dispatch -modification. Every dispatch in this specification — including -dispatches on reserved intent_names — fires §7.1 stamping, -§7.2 routing, and §8 handler-trio identically. The reserving -specification gets exclusive use of the name across the -deployment's skill set; it gets no other privilege. +modification. Dispatches on reserved intent_names fire §7.1 +routing and §8 handler-trio identically to ordinary dispatches. +The one exception is the §7.1 `session.active_handlers` push, +which is suppressed on reserved-name dispatches — a reserved +name represents a continuation or termination of an already- +active skill's participation, not a fresh activation. The +reserving specification gets exclusive use of the name across +the deployment's skill set; it gets no other privilege. Reservations currently in force: diff --git a/ovos-session-1.md b/ovos-session-1.md index 32c96987..2486de74 100644 --- a/ovos-session-1.md +++ b/ovos-session-1.md @@ -192,7 +192,7 @@ session and persist across utterances. | `detected_lang` | string (BCP-47) | §3.2 (this spec) | | `pipeline` | array of string | OVOS-PIPELINE-1 §5 | | `intent_context` | object | OVOS-CONTEXT-1 §2 | -| `active_handlers` | array of object `{id, activated_at}` | OVOS-CONVERSE-1 §2.1 | +| `active_handlers` | array of object `{skill_id, ...}` | OVOS-PIPELINE-1 §7.1 | | `response_mode` | object `{owner_id, expires_at}` | OVOS-CONVERSE-1 §2.2 | | `audio_transformers` | array of string | OVOS-TRANSFORM-1 §5 | | `utterance_transformers` | array of string | OVOS-TRANSFORM-1 §5 | diff --git a/ovos-stop-1.md b/ovos-stop-1.md index 75e9b89c..fc301928 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -14,10 +14,11 @@ registry; no other plugin or skill may register it. It builds on OVOS-MSG-1 (envelope, `forward` / `reply` / `response`), OVOS-PIPELINE-1 (pipeline-plugin contract, dispatch shape, lifecycle trio, reserved-name registry), -OVOS-SESSION-1 (session field registry), OVOS-SESSION-2 -(mutation boundaries and session-keyed-state projection), and -OVOS-CONVERSE-1 (`session.active_handlers` recency list, -`session.response_mode`). +OVOS-SESSION-1 (session field registry — `active_handlers`, +`response_mode`), and OVOS-SESSION-2 (mutation boundaries and +session-keyed-state projection). `session.active_handlers` is +populated by OVOS-PIPELINE-1 §7.1's dispatch-time stamping rule +and drained by stop consumption defined here. The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, **MAY**, and **RECOMMENDED** are used as in RFC 2119. @@ -142,7 +143,10 @@ Inside `match`: removing that `skill_id` from `active_handlers` and clearing any `response_mode` entry it owns. Return `Match(skill_id=, intent_name="stop", - updated_session=...)`. + updated_session=...)`. PIPELINE-1 §7.1's dispatch-time push + is suppressed for reserved-name dispatches (§7.3), so the + removal carried in `updated_session` is the final state at + dispatch. 5. If no positive responder exists, return a `global_stop` Match constructed per §5. @@ -199,11 +203,13 @@ session. A handler that cannot be stopped — by design, by current state, or for the relevant session — SHOULD remove itself from -`session.active_handlers` (via the mutation pathway defined in -CONVERSE-1) so that future ping rounds bypass it entirely. -Self-pruning is the static complement to the runtime ping-pong; -together they keep the discovery cost proportional to the number -of genuinely stoppable handlers. +`session.active_handlers` so that future ping rounds bypass it +entirely. The removal MAY be communicated by emitting any +session-carrying Message with the updated list; the orchestrator +and downstream consumers observe the next inbound session +without the entry. Self-pruning is the static complement to the +runtime ping-pong; together they keep the discovery cost +proportional to the number of genuinely stoppable handlers. --- @@ -230,9 +236,11 @@ updated_session=...)` where `updated_session` is the inbound session with: - `active_handlers` emptied — global stop terminates every - handler the session is tracking, and the cleared list - propagates through the rest of the utterance lifecycle and - subsequent utterances; + active handler, and the cleared list is the accurate + post-stop state. Because the `global_stop` dispatch is a + pipeline-plugin self-dispatch, PIPELINE-1 §7.1's stamping + push is suppressed (§7.0) — `updated_session` is the final + state at dispatch; - `response_mode` removed entirely (§6.1). ### 5.2 Dispatch and broadcast @@ -274,8 +282,8 @@ does not interrupt another session's playback. ### 6.1 `response_mode` -A stop plugin SHOULD clear `session.response_mode` via -`Match.updated_session` whenever it matches: +If `session.response_mode` is present, a stop plugin SHOULD clear +it via `Match.updated_session` whenever it matches: - for `intent_name: "stop"`, clear the entry whose `owner_id` matches the dispatch target; @@ -284,27 +292,35 @@ A stop plugin SHOULD clear `session.response_mode` via Per PIPELINE-1 §6.3, session mutations carried by `Match.updated_session` are committed at dispatch time; the -clear is durable even if the handler crashes mid-execution. +clear is durable even if the handler crashes mid-execution. The +semantics and ownership of the field are defined elsewhere; this +spec only requires that a stop operation cancels any wait the +stopped target was holding. ### 6.2 `active_handlers` -A stop plugin MUST update `session.active_handlers` via -`Match.updated_session` according to the Match it emits: +`session.active_handlers` is populated by PIPELINE-1 §7.1's +dispatch-time stamping rule on every ordinary dispatch. The +stamping push is suppressed for reserved intent_names (§7.3) — +`stop`, `converse`, `response` — so reserved-name dispatches +never add to the list. + +A stop plugin MUST drain the list via `Match.updated_session`, +which is committed pre-dispatch (PIPELINE-1 §4.2): - a `stop` Match MUST remove the dispatch target entry only, leaving the rest of the list intact; - a `global_stop` Match MUST empty `active_handlers` entirely (§5.1). -This propagates the post-stop state through the rest of the -utterance lifecycle and keeps subsequent reads of -`active_handlers` (by downstream plugins, the handler trio, or -the next utterance) accurate without waiting for TTL pruning or -size-cap eviction. - -Other CONVERSE-1 mutation pathways (TTL pruning, size cap, -activation events) and skill self-pruning (§4.4) continue to own -the remaining lifecycle of the list outside of stop matches. +Pre-dispatch mutation is the cleanest defined boundary for +session state changes: the orchestrator commits +`updated_session`, suppresses the push (because the dispatch +is on a reserved name or self-addressed to the stop plugin), +and the downstream lifecycle sees the post-stop list. No +consumer-side removal protocol is needed; no race between an +optimistic pre-removal and a failed handler exists because +removal and dispatch happen atomically at the same boundary. ### 6.3 Denylists @@ -412,9 +428,8 @@ Dispatch topics fire the handler-lifecycle trio per PIPELINE-1 with its own `skill_id` and `can_handle` reflecting feasibility *for the inbound `session_id`* — or remain silent if it has no stoppable activity for that session; -- remove itself from `session.active_handlers` (via CONVERSE-1's - mutation pathway) when it cannot be stopped, so that future - ping rounds bypass it (§4.4). +- remove itself from `session.active_handlers` when it cannot + be stopped, so that future ping rounds bypass it (§4.4). ### Every non-skill component performing user-visible activity **MUST**: From ad51204e418330c8b92c19ecb4c8449f69805264 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 14:07:26 +0100 Subject: [PATCH 09/21] PIPELINE-1/STOP-1: drop self-dispatch suppression; key push purely off reserved names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The §7.1 active_handlers stamping rule no longer special-cases pipeline-plugin self-dispatches. The orchestrator applies the §7.0 polymorphism rule uniformly — it does not distinguish skill from pipeline-plugin dispatches — and the push is suppressed strictly on the reserved-name registry. To preserve the architectural intent (the stop plugin's pipeline_id must not land in active_handlers on global stop), global_stop is now a reserved intent_name alongside stop, converse, and response: PIPELINE-1 §7.3 — global_stop added to the reservations table with the stop-plugin escalation semantics. stop also added (was previously implicit from STOP-1's reservation claim). STOP-1 §2 — restore both reservations, with an explicit note that reserving global_stop is what gates the active_handlers suppression. Drops the prior "global_stop is plugin-internal and not reserved" framing; the architectural cost of the new suppression rule is one reserved name, paid back by a cleaner orchestrator contract. STOP-1 §5 / §5.1 / §9 — rewording for the restored reservation. Co-Authored-By: Claude Opus 4.7 (1M context) --- ovos-pipeline-1.md | 24 ++++++++++---------- ovos-stop-1.md | 55 +++++++++++++++++++++++----------------------- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/ovos-pipeline-1.md b/ovos-pipeline-1.md index d8bc5365..2453671d 100644 --- a/ovos-pipeline-1.md +++ b/ovos-pipeline-1.md @@ -812,17 +812,17 @@ The dispatch Message's `context` (OVOS-MSG-1 §4): `session.active_handlers`, evicting any prior entry with the same `skill_id`. The list is a recency-ordered (MRU) record of which skills are currently active for the session. The push is - **suppressed** in two cases: (1) pipeline-plugin self-dispatches - (`skill_id == pipeline_id`, §7.0) — the list records skill - activity, not pipeline-plugin internals; (2) dispatches on any - reserved intent_name listed in §7.3 — a reserved-name dispatch - represents a continuation of an already-active skill's - participation (converse, response) or its termination (stop), - not a fresh activation. The push is applied after - `Match.updated_session` is committed, so a plugin that wants to - remove the target ahead of the dispatch (e.g., STOP-1's stop - cascade) does so via `updated_session` and the suppressed push - leaves the removal in effect. + **suppressed** only for dispatches on reserved intent_names + listed in §7.3 — a reserved-name dispatch represents a + continuation of an already-active skill's participation or its + termination, not a fresh activation. The orchestrator applies + the polymorphism rule (§7.0) uniformly and does not otherwise + distinguish skill from pipeline-plugin dispatches; suppression + is keyed strictly off the reserved-name registry. The push is + applied after `Match.updated_session` is committed, so a plugin + that wants to remove the target ahead of the dispatch (e.g., + STOP-1's stop cascade) does so via `updated_session` and the + suppressed push leaves the removal in effect. The dispatch Message's `data`: @@ -890,6 +890,8 @@ Reservations currently in force: |----------------------|----------------|--------------------------------------| | `converse` | OVOS-CONVERSE-1 §4 | a converse plugin's claim that `` (an active handler) wants this utterance — the orchestrator dispatches `:converse` and the owner's converse handler runs | | `response` | OVOS-CONVERSE-1 §5 | a converse plugin's signal that `` (the response-mode holder) is to receive the awaited utterance — the orchestrator dispatches `:response` and the owner's response handler runs | +| `stop` | OVOS-STOP-1 §4 | a stop plugin's claim that `` (an active handler) should cease activity — the orchestrator dispatches `:stop` and the owner's stop handler runs | +| `global_stop` | OVOS-STOP-1 §5 | a stop plugin's escalation that no specific handler can absorb the stop — the orchestrator dispatches `:global_stop` and the stop handler emits the `ovos.stop` broadcast | This specification fixes only the registry mechanism (reservation listing); the per-name semantics are owned by the reserving diff --git a/ovos-stop-1.md b/ovos-stop-1.md index fc301928..7d9419ac 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -8,8 +8,9 @@ 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 and broadcasts a global stop signal when no handler can absorb the request. The -intent_name `stop` is reserved at the OVOS-PIPELINE-1 §7.3 -registry; no other plugin or skill may register it. +intent_names `stop` and `global_stop` are reserved at the +OVOS-PIPELINE-1 §7.3 registry; no other plugin or skill may +register them. It builds on OVOS-MSG-1 (envelope, `forward` / `reply` / `response`), OVOS-PIPELINE-1 (pipeline-plugin contract, dispatch @@ -28,7 +29,8 @@ The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, ## 1. Scope This specification defines the stop plugin role, the reserved -intent_name `stop`, the stoppability discovery and cascade +intent_names `stop` and `global_stop`, the stoppability +discovery and cascade algorithm, the global broadcast namespace, and the session-scoping obligations of stop subscribers. @@ -39,28 +41,28 @@ APIs, or wake-word and barge-in policies. --- -## 2. Reserved intent_name +## 2. Reserved intent_names -This specification reserves a single intent_name at the +This specification reserves two intent_names at the OVOS-PIPELINE-1 §7.3 registry: | Reserved intent_name | Meaning of a Match bearing this name | |----------------------|--------------------------------------| | `stop` | A specific active handler should cease activity for the inbound `session_id`. Dispatched on `:stop` where the target is the LIFO head of positive pong responders (§4). | +| `global_stop` | The stop plugin's escalation when no specific handler can absorb the stop. Dispatched on `:global_stop`; the handler emits the `ovos.stop` broadcast (§5). | -Skills and other pipelines **MUST NOT** register `stop` under -OVOS-INTENT-4. A registration naming the reserved intent_name -is malformed per OVOS-INTENT-4 §5.3 — consumers log at WARN and -do not index. The reservation prevents competing skill-level -matches from bypassing the §4 cascade. +Skills and other pipelines **MUST NOT** register either name +under OVOS-INTENT-4. A registration naming a reserved +intent_name is malformed per OVOS-INTENT-4 §5.3 — consumers log +at WARN and do not index. -The stop plugin's escalation path uses the intent_name -`global_stop` for its own self-dispatch -(`:global_stop`, §5). This name is plugin-internal -and not reserved: a skill MAY register `global_stop` for its own -purposes, as the dispatch shape namespaces the topic under -`` and cannot collide with another skill's -handler. +Reserving `global_stop` is what gates PIPELINE-1 §7.1's +suppression of the `active_handlers` push for the stop plugin's +own self-dispatch: the orchestrator does not distinguish +skill from pipeline-plugin dispatches, so suppression is keyed +strictly off the reserved-name registry. Without the +reservation, the stop plugin's `pipeline_id` would be pushed +into `active_handlers` on every global stop. --- @@ -217,8 +219,8 @@ proportional to the number of genuinely stoppable handlers. When a stop utterance cannot be cascaded to a specific handler, the stop plugin escalates to a global broadcast. The escalation -path uses the intent_name `global_stop` as a plugin-internal -self-dispatch (not a reserved name; see §2). +path uses the reserved intent_name `global_stop` as a +self-dispatch (§2). A `global_stop` self-dispatch is emitted in three cases: @@ -237,10 +239,10 @@ where `updated_session` is the inbound session with: - `active_handlers` emptied — global stop terminates every active handler, and the cleared list is the accurate - post-stop state. Because the `global_stop` dispatch is a - pipeline-plugin self-dispatch, PIPELINE-1 §7.1's stamping - push is suppressed (§7.0) — `updated_session` is the final - state at dispatch; + post-stop state. Because `global_stop` is a reserved + intent_name (§2), PIPELINE-1 §7.1's stamping push is + suppressed — `updated_session` is the final state at + dispatch; - `response_mode` removed entirely (§6.1). ### 5.2 Dispatch and broadcast @@ -438,10 +440,9 @@ Dispatch topics fire the handler-lifecycle trio per PIPELINE-1 ### The orchestrator **MUST**: -- treat OVOS-INTENT-4 registrations naming `stop` as malformed - per INTENT-4 §5.3 — log at WARN and decline to index. - Registrations naming `global_stop` are accepted normally; - `global_stop` is not reserved. +- treat OVOS-INTENT-4 registrations naming `stop` or + `global_stop` as malformed per INTENT-4 §5.3 — log at WARN + and decline to index. --- From b491a576e6c9e56b458a9eedee8c1e4e20a5cbbd Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 14:14:09 +0100 Subject: [PATCH 10/21] PIPELINE-1/STOP-1: stop plugin in active_handlers is ordinary stamp outcome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the prior ordering flip and the global_stop reservation. The stop plugin landing in active_handlers on its own global_stop dispatch is not a problem to engineer around — it is the ordinary behaviour of the recency-ordered dispatch record, and it composes correctly with subsequent ping rounds (the stop plugin doesn't subscribe to ovos.stop.ping, so it silently times out as can_handle:false on the next stop). PIPELINE-1 §7.1 — restore "stamp applied after updated_session is committed". The dispatched skill_id always lands at the head of active_handlers unless the intent_name is reserved. PIPELINE-1 §7.3 — global_stop is not in the reservation table (only stop, converse, response). STOP-1 §2 — single reservation (stop). global_stop framed as "plugin-internal and not reserved" with the note that the stop plugin's pipeline_id landing in active_handlers via the normal stamp is the ordinary behaviour, not a special case. STOP-1 §5.1 — global_stop's updated_session wipes the inbound active_handlers (so prior active skills are correctly cleared); the post-dispatch state is [{skill_id: stop_plugin_id}] via the normal stamp on top. This is the expected outcome. Co-Authored-By: Claude Opus 4.7 (1M context) --- ovos-pipeline-1.md | 10 +++---- ovos-stop-1.md | 66 ++++++++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/ovos-pipeline-1.md b/ovos-pipeline-1.md index 2453671d..ae44b490 100644 --- a/ovos-pipeline-1.md +++ b/ovos-pipeline-1.md @@ -819,10 +819,11 @@ The dispatch Message's `context` (OVOS-MSG-1 §4): the polymorphism rule (§7.0) uniformly and does not otherwise distinguish skill from pipeline-plugin dispatches; suppression is keyed strictly off the reserved-name registry. The push is - applied after `Match.updated_session` is committed, so a plugin - that wants to remove the target ahead of the dispatch (e.g., - STOP-1's stop cascade) does so via `updated_session` and the - suppressed push leaves the removal in effect. + applied after `Match.updated_session` is committed: a plugin + that mutates `active_handlers` via `updated_session` (e.g., + STOP-1's global stop wiping the list) sees the stamp applied + on top, so the dispatched skill_id always lands at the head + unless the intent_name is reserved. The dispatch Message's `data`: @@ -891,7 +892,6 @@ Reservations currently in force: | `converse` | OVOS-CONVERSE-1 §4 | a converse plugin's claim that `` (an active handler) wants this utterance — the orchestrator dispatches `:converse` and the owner's converse handler runs | | `response` | OVOS-CONVERSE-1 §5 | a converse plugin's signal that `` (the response-mode holder) is to receive the awaited utterance — the orchestrator dispatches `:response` and the owner's response handler runs | | `stop` | OVOS-STOP-1 §4 | a stop plugin's claim that `` (an active handler) should cease activity — the orchestrator dispatches `:stop` and the owner's stop handler runs | -| `global_stop` | OVOS-STOP-1 §5 | a stop plugin's escalation that no specific handler can absorb the stop — the orchestrator dispatches `:global_stop` and the stop handler emits the `ovos.stop` broadcast | This specification fixes only the registry mechanism (reservation listing); the per-name semantics are owned by the reserving diff --git a/ovos-stop-1.md b/ovos-stop-1.md index 7d9419ac..42f671a3 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -8,9 +8,8 @@ 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 and broadcasts a global stop signal when no handler can absorb the request. The -intent_names `stop` and `global_stop` are reserved at the -OVOS-PIPELINE-1 §7.3 registry; no other plugin or skill may -register them. +intent_name `stop` is reserved at the OVOS-PIPELINE-1 §7.3 +registry; no other plugin or skill may register it. It builds on OVOS-MSG-1 (envelope, `forward` / `reply` / `response`), OVOS-PIPELINE-1 (pipeline-plugin contract, dispatch @@ -29,8 +28,7 @@ The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, ## 1. Scope This specification defines the stop plugin role, the reserved -intent_names `stop` and `global_stop`, the stoppability -discovery and cascade +intent_name `stop`, the stoppability discovery and cascade algorithm, the global broadcast namespace, and the session-scoping obligations of stop subscribers. @@ -41,28 +39,30 @@ APIs, or wake-word and barge-in policies. --- -## 2. Reserved intent_names +## 2. Reserved intent_name -This specification reserves two intent_names at the +This specification reserves a single intent_name at the OVOS-PIPELINE-1 §7.3 registry: | Reserved intent_name | Meaning of a Match bearing this name | |----------------------|--------------------------------------| | `stop` | A specific active handler should cease activity for the inbound `session_id`. Dispatched on `:stop` where the target is the LIFO head of positive pong responders (§4). | -| `global_stop` | The stop plugin's escalation when no specific handler can absorb the stop. Dispatched on `:global_stop`; the handler emits the `ovos.stop` broadcast (§5). | -Skills and other pipelines **MUST NOT** register either name -under OVOS-INTENT-4. A registration naming a reserved -intent_name is malformed per OVOS-INTENT-4 §5.3 — consumers log -at WARN and do not index. - -Reserving `global_stop` is what gates PIPELINE-1 §7.1's -suppression of the `active_handlers` push for the stop plugin's -own self-dispatch: the orchestrator does not distinguish -skill from pipeline-plugin dispatches, so suppression is keyed -strictly off the reserved-name registry. Without the -reservation, the stop plugin's `pipeline_id` would be pushed -into `active_handlers` on every global stop. +Skills and other pipelines **MUST NOT** register `stop` under +OVOS-INTENT-4. A registration naming the reserved intent_name +is malformed per OVOS-INTENT-4 §5.3 — consumers log at WARN and +do not index. The reservation prevents competing skill-level +matches from bypassing the §4 cascade. + +The stop plugin's escalation path uses the intent_name +`global_stop` for its own self-dispatch +(`:global_stop`, §5). This name is +plugin-internal and not reserved: the dispatch shape namespaces +the topic under `` and cannot collide with any +skill's handler. The stop plugin's `pipeline_id` lands in +`session.active_handlers` via PIPELINE-1 §7.1's stamp on this +dispatch like any other — this is the ordinary behaviour of the +list as a recency-ordered dispatch record, not a special case. --- @@ -146,9 +146,9 @@ Inside `match`: any `response_mode` entry it owns. Return `Match(skill_id=, intent_name="stop", updated_session=...)`. PIPELINE-1 §7.1's dispatch-time push - is suppressed for reserved-name dispatches (§7.3), so the - removal carried in `updated_session` is the final state at - dispatch. + is suppressed for the reserved `stop` intent_name (§7.3), + so the removal carried in `updated_session` is the final + state at dispatch. 5. If no positive responder exists, return a `global_stop` Match constructed per §5. @@ -219,8 +219,8 @@ proportional to the number of genuinely stoppable handlers. When a stop utterance cannot be cascaded to a specific handler, the stop plugin escalates to a global broadcast. The escalation -path uses the reserved intent_name `global_stop` as a -self-dispatch (§2). +path uses the intent_name `global_stop` as a plugin-internal +self-dispatch (not a reserved name; see §2). A `global_stop` self-dispatch is emitted in three cases: @@ -239,10 +239,11 @@ where `updated_session` is the inbound session with: - `active_handlers` emptied — global stop terminates every active handler, and the cleared list is the accurate - post-stop state. Because `global_stop` is a reserved - intent_name (§2), PIPELINE-1 §7.1's stamping push is - suppressed — `updated_session` is the final state at - dispatch; + post-stop state. PIPELINE-1 §7.1 then stamps + `` onto the head as it does for any + dispatch, leaving `active_handlers = [{skill_id: }]` + after dispatch — the ordinary outcome of the stamping rule, + not a special case; - `response_mode` removed entirely (§6.1). ### 5.2 Dispatch and broadcast @@ -440,9 +441,10 @@ Dispatch topics fire the handler-lifecycle trio per PIPELINE-1 ### The orchestrator **MUST**: -- treat OVOS-INTENT-4 registrations naming `stop` or - `global_stop` as malformed per INTENT-4 §5.3 — log at WARN - and decline to index. +- treat OVOS-INTENT-4 registrations naming `stop` as malformed + per INTENT-4 §5.3 — log at WARN and decline to index. + Registrations naming `global_stop` are accepted normally — + the name is not reserved (§2). --- From 7522173ee932ae41f8dd440c89814a0516080015 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 16:14:12 +0100 Subject: [PATCH 11/21] STOP-1/PIPELINE-1: active_handlers entries carry activated_at; cascade picks by timestamp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the LIFO-by-position cascade selection with explicit MRU-by-timestamp. The active_handlers list is a set keyed by skill_id with eviction, not an insertion-ordered list — its "recency" is properly determined by the activated_at timestamp each entry carries, not by where it happens to sit in the array. SESSION-1 field registry — entry shape restored to {skill_id, activated_at, ...}. PIPELINE-1 §7.1 — orchestrator stamps {skill_id, activated_at: } on dispatch. The list is described as "a recency record keyed by activated_at" rather than "MRU by list position". STOP-1 §2 reservation table, §3.2, §4.1 step 4 — cascade selection rewritten as "the most recently activated (highest activated_at) positive pong responder" instead of "LIFO head among positives". Wording consistent across the spec. APPENDIX §4.8 — same fix in the rationale paragraph. Co-Authored-By: Claude Opus 4.7 (1M context) --- ovos-pipeline-1.md | 10 ++++++---- ovos-session-1.md | 2 +- ovos-stop-1.md | 11 ++++++----- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ovos-pipeline-1.md b/ovos-pipeline-1.md index ae44b490..824f2964 100644 --- a/ovos-pipeline-1.md +++ b/ovos-pipeline-1.md @@ -808,10 +808,12 @@ The dispatch Message's `context` (OVOS-MSG-1 §4): the match is self-addressed (`skill_id == pipeline_id`, §7.0), both context keys carry the same identifier. - **`session.active_handlers` push.** The orchestrator **MUST** - push `{skill_id: }` onto the head of - `session.active_handlers`, evicting any prior entry with the same - `skill_id`. The list is a recency-ordered (MRU) record of which - skills are currently active for the session. The push is + push `{skill_id: , activated_at: }` onto `session.active_handlers`, + evicting any prior entry with the same `skill_id`. The list is + a recency record keyed by `activated_at` — consumers determine + "most recently activated" by comparing timestamps, not by list + position. The push is **suppressed** only for dispatches on reserved intent_names listed in §7.3 — a reserved-name dispatch represents a continuation of an already-active skill's participation or its diff --git a/ovos-session-1.md b/ovos-session-1.md index 2486de74..84ef9484 100644 --- a/ovos-session-1.md +++ b/ovos-session-1.md @@ -192,7 +192,7 @@ session and persist across utterances. | `detected_lang` | string (BCP-47) | §3.2 (this spec) | | `pipeline` | array of string | OVOS-PIPELINE-1 §5 | | `intent_context` | object | OVOS-CONTEXT-1 §2 | -| `active_handlers` | array of object `{skill_id, ...}` | OVOS-PIPELINE-1 §7.1 | +| `active_handlers` | array of object `{skill_id, activated_at, ...}` | OVOS-PIPELINE-1 §7.1 | | `response_mode` | object `{owner_id, expires_at}` | OVOS-CONVERSE-1 §2.2 | | `audio_transformers` | array of string | OVOS-TRANSFORM-1 §5 | | `utterance_transformers` | array of string | OVOS-TRANSFORM-1 §5 | diff --git a/ovos-stop-1.md b/ovos-stop-1.md index 42f671a3..a524b191 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -46,7 +46,7 @@ OVOS-PIPELINE-1 §7.3 registry: | Reserved intent_name | Meaning of a Match bearing this name | |----------------------|--------------------------------------| -| `stop` | A specific active handler should cease activity for the inbound `session_id`. Dispatched on `:stop` where the target is the LIFO head of positive pong responders (§4). | +| `stop` | A specific active handler should cease activity for the inbound `session_id`. Dispatched on `: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 the reserved intent_name @@ -84,8 +84,8 @@ the choice is a deployment concern. `Match.skill_id` MUST equal the target of the dispatch: -- for `intent_name: "stop"`, the LIFO head of positive pong - responders (§4.2); +- for `intent_name: "stop"`, the most recently activated + (highest `activated_at`) positive pong responder (§4.2); - for `intent_name: "global_stop"`, the stop plugin's own `pipeline_id`. @@ -140,8 +140,9 @@ Inside `match`: in `active_handlers` MUST be ignored; late pongs MAY be ignored. 4. If at least one positive responder exists, select the one - whose position in `session.active_handlers` is most recent - (LIFO head among positives). Construct `updated_session` by + with the highest `activated_at` in `session.active_handlers` + — the most recently activated skill among those that + reported they can stop. Construct `updated_session` by removing that `skill_id` from `active_handlers` and clearing any `response_mode` entry it owns. Return `Match(skill_id=, intent_name="stop", From a654cd4ef10bb1e53180cb96399a6e65b00ee33b Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 16:32:13 +0100 Subject: [PATCH 12/21] =?UTF-8?q?STOP-1:=20lean=20prescriptive=20rewrite?= =?UTF-8?q?=20=E2=80=94=20strip=20descriptive=20prose,=20cut=20rationale?= =?UTF-8?q?=20asides,=20tighten=20to=20normative=20skeleton?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- ovos-stop-1.md | 503 ++++++++++++++++++------------------------------- 1 file changed, 179 insertions(+), 324 deletions(-) diff --git a/ovos-stop-1.md b/ovos-stop-1.md index a524b191..58bff48a 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -2,283 +2,180 @@ **Spec ID:** OVOS-STOP-1 · **Version:** 1 · **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 and broadcasts a global -stop signal when no handler can absorb the request. The -intent_name `stop` is reserved at the OVOS-PIPELINE-1 §7.3 -registry; no other plugin or skill may register it. - -It builds on OVOS-MSG-1 (envelope, `forward` / `reply` / -`response`), OVOS-PIPELINE-1 (pipeline-plugin contract, dispatch -shape, lifecycle trio, reserved-name registry), -OVOS-SESSION-1 (session field registry — `active_handlers`, -`response_mode`), and OVOS-SESSION-2 (mutation boundaries and -session-keyed-state projection). `session.active_handlers` is -populated by OVOS-PIPELINE-1 §7.1's dispatch-time stamping rule -and drained by stop consumption defined here. - -The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, -**MAY**, and **RECOMMENDED** are used as in RFC 2119. +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 (`active_handlers` and +`response_mode` field registry), OVOS-SESSION-2 (mutation boundaries). + +The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, **MAY** +are used as in RFC 2119. --- ## 1. Scope -This specification defines the stop plugin role, the reserved -intent_name `stop`, the stoppability discovery and cascade -algorithm, the global broadcast namespace, and the session-scoping -obligations of stop subscribers. +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, voice-activity detection, -microphone or audio capture control, handler-side framework +It does **not** define: vocabulary file formats, matching algorithms, +confidence thresholds, audio capture control, handler-side framework APIs, or wake-word and barge-in policies. --- ## 2. Reserved intent_name -This specification reserves a single intent_name at the -OVOS-PIPELINE-1 §7.3 registry: - -| Reserved intent_name | Meaning of a Match bearing this name | -|----------------------|--------------------------------------| -| `stop` | A specific active handler should cease activity for the inbound `session_id`. Dispatched on `:stop` where the target is the most recently activated (highest `activated_at`) positive pong responder (§4). | +| Reserved intent_name | Meaning | +|----------------------|---------| +| `stop` | Cease activity for the inbound `session_id`. Dispatched on `: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 the reserved intent_name -is malformed per OVOS-INTENT-4 §5.3 — consumers log at WARN and -do not index. The reservation prevents competing skill-level -matches from bypassing the §4 cascade. - -The stop plugin's escalation path uses the intent_name -`global_stop` for its own self-dispatch -(`:global_stop`, §5). This name is -plugin-internal and not reserved: the dispatch shape namespaces -the topic under `` and cannot collide with any -skill's handler. The stop plugin's `pipeline_id` lands in -`session.active_handlers` via PIPELINE-1 §7.1's stamp on this -dispatch like any other — this is the ordinary behaviour of the -list as a recency-ordered dispatch record, not a special case. +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 (`:global_stop`, +§5), namespaced under its own `pipeline_id`. --- ## 3. The stop plugin role -The **stop plugin role** is a behavioural contract a pipeline -plugin (PIPELINE-1 §3) MAY adopt. A stop plugin is an ordinary -pipeline plugin — subject to the same denylist filtering, -first-match-wins iteration, and circuit-breaker rules — that -matches stop-command utterances and emits Matches under §2. +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. -### 3.1 Pipeline identity +### 3.1 Pipeline identity and dispatch target A stop plugin is loaded as one or more `pipeline_id` entries in -`session.pipeline`. The conventional three confidence tiers -`stop_high`, `stop_medium`, `stop_low` MAY be registered as -separate `pipeline_id`s or merged into one multi-tier plugin; -the choice is a deployment concern. - -`Match.skill_id` MUST equal the target of the dispatch: +`session.pipeline`. `Match.skill_id` MUST equal: -- for `intent_name: "stop"`, the most recently activated - (highest `activated_at`) positive pong responder (§4.2); -- for `intent_name: "global_stop"`, the stop plugin's own - `pipeline_id`. +- for `intent_name: "stop"` — the most recently activated positive + pong responder selected per §4; +- for `intent_name: "global_stop"` — the stop plugin's own `pipeline_id`. ### 3.2 Match obligations -In addition to the general PIPELINE-1 §4 contract: +A stop plugin **MUST**: -- A stop plugin MUST return `None` for any language for which it - cannot resolve stop vocabulary. -- A stop plugin MUST read `session.active_handlers` to drive the - cascade (§4). -- A stop plugin emitting the stoppability ping-pong (§4.2) - performs that exchange **inside `match`**. This is a documented - exception to the PIPELINE-1 §4.4 low-latency guidance, - justified by the stop plugin's escape-hatch position at the - head of the pipeline (§8). No other plugin is iterating during - this exchange. +- return `None` for any language for which it cannot resolve stop + vocabulary; +- read `session.active_handlers` to drive the §4 cascade; +- perform the ping-pong exchange (§4.2) inside `match`. ### 3.3 Vocabulary -A stop plugin SHOULD distinguish utterances expressing a generic -stop intention from utterances expressing an explicit -"stop everything" intention. Vocabulary file organisation is not -normative; only the resulting Match's `intent_name` is. - -The "stop everything" vocabulary maps directly to -`intent_name: "global_stop"` without consulting -`active_handlers`. The generic vocabulary triggers the §4 -cascade. +A stop plugin SHOULD distinguish generic stop utterances from explicit +"stop everything" utterances. Generic stop triggers the §4 cascade; +"stop everything" maps directly to `intent_name: "global_stop"` without +consulting `active_handlers`. --- ## 4. Generic stop — `intent_name: "stop"` -When the utterance matches generic stop vocabulary, the stop -plugin performs stoppability discovery against -`session.active_handlers` and either dispatches a directed stop -to a single handler or escalates to `global_stop`. - ### 4.1 Algorithm Inside `match`: -1. Read `session.active_handlers`. If empty, return a - `global_stop` Match constructed per §5. -2. Emit a single `ovos.stop.ping` broadcast and collect responses - on `ovos.stop.pong` up to a deployer-defined timeout - (RECOMMENDED default: 0.5 seconds). -3. Identify positive responders — those whose `skill_id` - appears in `session.active_handlers` and whose pong returned - `can_handle: true` within the window. Pongs from skills not - in `active_handlers` MUST be ignored; late pongs MAY be - ignored. -4. If at least one positive responder exists, select the one - with the highest `activated_at` in `session.active_handlers` - — the most recently activated skill among those that - reported they can stop. Construct `updated_session` by - removing that `skill_id` from `active_handlers` and clearing - any `response_mode` entry it owns. Return - `Match(skill_id=, intent_name="stop", - updated_session=...)`. PIPELINE-1 §7.1's dispatch-time push - is suppressed for the reserved `stop` intent_name (§7.3), - so the removal carried in `updated_session` is the final - state at dispatch. -5. If no positive responder exists, return a `global_stop` Match - constructed per §5. - -### 4.2 Ping and pong - -`ovos.stop.ping` is a single broadcast addressing every active -handler at once; `ovos.stop.pong` is a single shared response -topic carrying the responder's `skill_id` in the payload. This -shape avoids any need for the stop plugin to enumerate per-skill -topics. - -**Ping** — `ovos.stop.ping`: - -Payload MAY be empty. The inbound `session_id` is carried by the -Message's session context (OVOS-MSG-1) and identifies the -session for which feasibility is being queried. - -**Pong** — `ovos.stop.pong`: +1. Read `session.active_handlers`. If empty, return a `global_stop` + Match per §5. +2. Emit `ovos.stop.ping` and collect `ovos.stop.pong` responses up to + a deployer-defined timeout (RECOMMENDED default: 0.5 s). +3. Identify positive responders: pongs where `can_handle: true` and + `skill_id` appears in `session.active_handlers`. Pongs from skills + not in `active_handlers` MUST be ignored; late pongs MAY be ignored. +4. If at least one positive responder exists, select the entry with the + highest `activated_at` in `session.active_handlers`. Construct + `updated_session` removing that `skill_id` from `active_handlers` + and clearing any `response_mode` entry it owns. Return + `Match(skill_id=, intent_name="stop", updated_session=...)`. +5. If no positive responder exists, return a `global_stop` Match per §5. + +### 4.2 Ping and pong shape + +**`ovos.stop.ping`** — broadcast. Payload MAY be empty. The inbound +`session_id` is carried in Message context (OVOS-MSG-1). + +**`ovos.stop.pong`** — shared reply topic. ```json -{ "skill_id": "music.skill", "can_handle": true } +{ "skill_id": "example.skill", "can_handle": true } ``` | Field | Type | Required | Meaning | |-------|------|----------|---------| -| `skill_id` | string | yes | The `owner_id` of the responding handler. | -| `can_handle` | boolean | yes | Whether the handler can process a stop request *for the inbound `session_id`*. | +| `skill_id` | string | yes | The `skill_id` of the responding handler. | +| `can_handle` | boolean | yes | Whether the handler can stop for the inbound `session_id`. | -The ping-pong is a request/response pair under OVOS-MSG-1 §5.3. -A handler that does not respond within the timeout window is -treated as `can_handle: false`. Silence indicates only that the -handler did not opt in to the discovery protocol. +A handler that does not respond within the timeout is treated as +`can_handle: false`. A handler MUST respond `can_handle: true` only +when it has stoppable activity for the inbound `session_id`. -A handler MUST respond only when it has stoppable activity for -the inbound `session_id`. A handler that is active for one -`session_id` but cannot stop its activity for the inbound -`session_id` MUST either respond `can_handle: false` or remain -silent. +### 4.3 Dispatch -### 4.3 Dispatch and skill stop handler +The orchestrator dispatches `:stop` per the standard +PIPELINE-1 §7 contract. The target's stop handler fires the +handler-lifecycle trio (`ovos.intent.handler.start`, `.complete`, +`.error`). -The Match's dispatch follows the standard PIPELINE-1 §7 contract. -The orchestrator emits `:stop`; the target -skill's stop handler runs as an ordinary intent handler, firing -the standard handler-lifecycle trio -(`ovos.intent.handler.start`, `.complete`, `.error`). - -The skill's stop handler MUST cease only the activity keyed to -the inbound `session_id`. A skill serving multiple sessions in -parallel MUST NOT interrupt activity belonging to a different -session. +The handler MUST cease only the activity keyed to the inbound +`session_id`. ### 4.4 Self-pruning of `active_handlers` -A handler that cannot be stopped — by design, by current state, -or for the relevant session — SHOULD remove itself from -`session.active_handlers` so that future ping rounds bypass it -entirely. The removal MAY be communicated by emitting any -session-carrying Message with the updated list; the orchestrator -and downstream consumers observe the next inbound session -without the entry. Self-pruning is the static complement to the -runtime ping-pong; together they keep the discovery cost -proportional to the number of genuinely stoppable handlers. +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. --- -## 5. Global stop escalation +## 5. Global stop — `intent_name: "global_stop"` -When a stop utterance cannot be cascaded to a specific handler, -the stop plugin escalates to a global broadcast. The escalation -path uses the intent_name `global_stop` as a plugin-internal -self-dispatch (not a reserved name; see §2). +### 5.1 Trigger conditions -A `global_stop` self-dispatch is emitted in three cases: +A `global_stop` Match is returned in three cases: -- explicit "stop everything" vocabulary match (§3.3); +- explicit "stop everything" vocabulary (§3.3); - generic stop with empty `active_handlers` (§4.1 step 1); - generic stop with no positive pong responders (§4.1 step 5). -### 5.1 Match construction - -A `global_stop` Match MUST be: - -`Match(skill_id=, intent_name="global_stop", -updated_session=...)` +### 5.2 Match construction -where `updated_session` is the inbound session with: - -- `active_handlers` emptied — global stop terminates every - active handler, and the cleared list is the accurate - post-stop state. PIPELINE-1 §7.1 then stamps - `` onto the head as it does for any - dispatch, leaving `active_handlers = [{skill_id: }]` - after dispatch — the ordinary outcome of the stamping rule, - not a special case; -- `response_mode` removed entirely (§6.1). - -### 5.2 Dispatch and broadcast - -The orchestrator dispatches `:global_stop`. -Because `skill_id` equals the stop plugin's own `pipeline_id`, -the dispatch is uniquely routed to the stop handler regardless -of any skill that happens to use the same intent_name. The -stop handler emits the universal broadcast: - -| Topic | Direction | Summary | -|-------|-----------|---------| -| `ovos.stop` | stop handler → all | Cease all activity. | - -Payload MAY be empty. Every component performing user-visible -activity (audio playback, TTS, media, timers, animation, the -converse plugin's polling loop) MUST subscribe to `ovos.stop` -and cease its activity on receipt. +``` +Match( + skill_id=, + intent_name="global_stop", + updated_session= +) +``` -`ovos.stop` is not a dispatch — it does not follow the -`:` shape and does not fire the -handler-lifecycle trio. The trio fires for the `global_stop` -dispatch itself. +PIPELINE-1 §7.1 stamps `` onto `active_handlers` at +dispatch time (the name `global_stop` is not reserved, so suppression +does not apply). -The namespace `ovos.stop.*` is reserved by this specification -for future stop-related signals. +### 5.3 Broadcast -### 5.3 Session scoping +The stop handler dispatched by `:global_stop` MUST +emit `ovos.stop` — a universal broadcast. Every component performing +user-visible activity MUST subscribe to `ovos.stop` and cease activity +for the broadcast's `session_id`. -A subscriber to `ovos.stop` MUST cease only the activity it has -running for the broadcast's inbound `session_id`. A TTS engine -speaking concurrently for two sessions stops only the session -that issued the stop; a media player playing for one session -does not interrupt another session's playback. +`ovos.stop` is not a dispatch topic — it does not follow the +`:` shape and does not fire the handler-lifecycle +trio. The namespace `ovos.stop.*` is reserved by this specification. --- @@ -286,173 +183,131 @@ does not interrupt another session's playback. ### 6.1 `response_mode` -If `session.response_mode` is present, a stop plugin SHOULD clear -it via `Match.updated_session` whenever it matches: - -- for `intent_name: "stop"`, clear the entry whose `owner_id` - matches the dispatch target; -- for `intent_name: "global_stop"`, remove `response_mode` - entirely. +A stop plugin SHOULD clear `session.response_mode` via +`Match.updated_session`: -Per PIPELINE-1 §6.3, session mutations carried by -`Match.updated_session` are committed at dispatch time; the -clear is durable even if the handler crashes mid-execution. The -semantics and ownership of the field are defined elsewhere; this -spec only requires that a stop operation cancels any wait the -stopped target was holding. +- for `intent_name: "stop"` — clear the entry whose `owner_id` matches + the dispatch target; +- for `intent_name: "global_stop"` — remove `response_mode` entirely. ### 6.2 `active_handlers` -`session.active_handlers` is populated by PIPELINE-1 §7.1's -dispatch-time stamping rule on every ordinary dispatch. The -stamping push is suppressed for reserved intent_names (§7.3) — -`stop`, `converse`, `response` — so reserved-name dispatches -never add to the list. - -A stop plugin MUST drain the list via `Match.updated_session`, -which is committed pre-dispatch (PIPELINE-1 §4.2): - -- a `stop` Match MUST remove the dispatch target entry only, - leaving the rest of the list intact; -- a `global_stop` Match MUST empty `active_handlers` entirely - (§5.1). - -Pre-dispatch mutation is the cleanest defined boundary for -session state changes: the orchestrator commits -`updated_session`, suppresses the push (because the dispatch -is on a reserved name or self-addressed to the stop plugin), -and the downstream lifecycle sees the post-stop list. No -consumer-side removal protocol is needed; no race between an -optimistic pre-removal and a failed handler exists because -removal and dispatch happen atomically at the same boundary. +A stop plugin MUST drain `active_handlers` via `Match.updated_session` +(committed pre-dispatch per PIPELINE-1 §4.2): + +- `stop` Match — remove the dispatch target entry only; +- `global_stop` Match — empty `active_handlers` 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`. ### 6.3 Denylists A stop plugin MUST honour `session.blacklisted_skills` and -`session.blacklisted_intents` (PIPELINE-1 §5.3–§5.4). A handler -whose `owner_id` appears in `blacklisted_skills` MUST NOT be -pinged or selected as a stop target. +`session.blacklisted_intents` (PIPELINE-1 §5). A handler whose +`skill_id` appears in `blacklisted_skills` MUST NOT be pinged or +selected as a stop target. --- ## 7. Pipeline positioning 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. -This positioning is the user-facing escape hatch from any -in-flight conversational state and is also what makes the -in-match ping-pong of §3.2 latency-safe: no other plugin is -iterating during the discovery window. +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. -Lower-confidence stop stages MAY be interleaved with -intent-matching stages so that ambiguous stop-like utterances do -not pre-empt ordinary intents. A typical ordering: +Typical ordering: ``` session.pipeline: [ - "stop_high", # escape hatch + "stop_high", "converse", - "intent_matcher_high", + "intent_high", "stop_medium", - "intent_matcher_medium", + "intent_medium", "stop_low", - "intent_matcher_low" + "intent_low" ] ``` --- -## 8. Bus surface summary +## 8. Bus surface -| Topic | Direction | Purpose | Defined in | -|-------|-----------|---------|------------| -| `ovos.stop.ping` | stop plugin → all | Stoppability ping (broadcast) | §4.2 | -| `ovos.stop.pong` | skill → stop plugin | Stoppability response (shared) | §4.2 | -| `:stop` | orchestrator → target skill | Skill-directed stop dispatch | §4.3 | -| `:global_stop` | orchestrator → stop handler | Global stop dispatch | §5.2 | -| `ovos.stop` | stop handler → all | Universal stop broadcast | §5.2 | +| Topic | Direction | Purpose | +|-------|-----------|---------| +| `ovos.stop.ping` | stop plugin → all | Stoppability query (broadcast) | +| `ovos.stop.pong` | skill → stop plugin | Stoppability response | +| `:stop` | orchestrator → skill | Skill-directed stop dispatch | +| `:global_stop` | orchestrator → stop handler | Global stop dispatch | +| `ovos.stop` | stop handler → all | Universal stop broadcast | -Dispatch topics fire the handler-lifecycle trio per PIPELINE-1 -§7; no other topic in this table does. +Dispatch topics (`<…>:stop`, `<…>:global_stop`) fire the +handler-lifecycle trio. No other topic in this table does. --- ## 9. Conformance -### A stop pipeline plugin **MUST**: +### Stop pipeline plugin — MUST: -- match per PIPELINE-1 §4, returning either `intent_name: "stop"` - or `intent_name: "global_stop"` and never any other value; -- set `Match.skill_id` per §3.1 — the dispatch target for `stop`, - the plugin's own `pipeline_id` for `global_stop`; -- return `None` when no stop vocabulary matches or when no - vocabulary is available for the requested `lang`; +- return `Match` with `intent_name` of exactly `"stop"` or + `"global_stop"`, never any other value; +- set `Match.skill_id` per §3.1; +- return `None` when no stop vocabulary matches or the requested + `lang` is unsupported; - read `session.active_handlers` to drive the cascade (§4.1); -- communicate session mutations exclusively through - `Match.updated_session`; -- on a `stop` Match, remove the dispatch target from - `session.active_handlers` via `Match.updated_session`; on a - `global_stop` Match, empty `active_handlers` entirely (§6.2); -- honour `session.blacklisted_skills` and - `session.blacklisted_intents` (§6.3); +- communicate all session mutations through `Match.updated_session`; +- remove the dispatch target from `active_handlers` on a `stop` Match; + empty `active_handlers` entirely on a `global_stop` Match (§6.2); +- honour `session.blacklisted_skills` and `session.blacklisted_intents` (§6.3); - subscribe to `:global_stop` and emit `ovos.stop` from that handler. -### A stop pipeline plugin **SHOULD**: +### Stop pipeline plugin — SHOULD: - clear `session.response_mode` via `Match.updated_session` (§6.1); -- skip the ping-pong and return `global_stop` immediately when - `active_handlers` is empty. +- return `global_stop` immediately when `active_handlers` is empty, + without emitting a ping. -### A deployment that includes a stop plugin **SHOULD**: +### Deployment — SHOULD: -- place the highest-confidence stop stage first in - `session.pipeline` (§7); +- place the highest-confidence stop stage first in `session.pipeline` (§7); - configure stop vocabulary for every supported language. -### A skill that participates in stop **MUST**: - -- subscribe to **both** `:stop` and `ovos.stop`. - `:stop` carries the skill-directed cascade - dispatch (§4.3); `ovos.stop` carries the global broadcast - (§5.2). The two subscriptions are not alternatives — a skill - receives one or the other depending on the cascade outcome, - and must be ready for either; -- on receiving `:stop`, cease only the activity - keyed to the inbound `session_id`; -- on receiving `ovos.stop`, cease all activity keyed to the - inbound `session_id`; +### Skill — MUST: + +- subscribe to both `:stop` and `ovos.stop`; +- on `:stop`, cease activity for the inbound `session_id`; +- on `ovos.stop`, cease all activity for the inbound `session_id`; - treat duplicate stop dispatches and `ovos.stop` broadcasts as idempotent. -### A skill that participates in stop **SHOULD**: +### Skill — SHOULD: -- subscribe to `ovos.stop.ping` and reply on `ovos.stop.pong` - with its own `skill_id` and `can_handle` reflecting feasibility - *for the inbound `session_id`* — or remain silent if it has no - stoppable activity for that session; -- remove itself from `session.active_handlers` when it cannot - be stopped, so that future ping rounds bypass it (§4.4). +- subscribe to `ovos.stop.ping` and reply on `ovos.stop.pong` with + `skill_id` and `can_handle` reflecting feasibility for the inbound + `session_id`, or remain silent if it has no stoppable activity; +- remove itself from `session.active_handlers` when it cannot be + stopped (§4.4). -### Every non-skill component performing user-visible activity **MUST**: +### Non-skill component performing user-visible activity — MUST: -- subscribe to `ovos.stop` and cease activity keyed to the - inbound `session_id` on receipt. +- subscribe to `ovos.stop` and cease activity for the inbound + `session_id` on receipt. -### The orchestrator **MUST**: +### Orchestrator — MUST: -- treat OVOS-INTENT-4 registrations naming `stop` as malformed - per INTENT-4 §5.3 — log at WARN and decline to index. - Registrations naming `global_stop` are accepted normally — - the name is not reserved (§2). +- treat OVOS-INTENT-4 registrations naming `stop` as malformed — + log at WARN and decline to index. --- ## See also -- *Utterance Lifecycle and Pipeline Specification* (OVOS-PIPELINE-1) -- *Active Handlers and Interactive Response Specification* (OVOS-CONVERSE-1) -- *Bus Message Specification* (OVOS-MSG-1) -- *Session Carrier Wire Shape Specification* (OVOS-SESSION-1) -- *Session Lifecycle and State Ownership Specification* (OVOS-SESSION-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 From 51f48772c564b91fd7b06f0e80a16015450c4787 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 16:47:45 +0100 Subject: [PATCH 13/21] =?UTF-8?q?STOP-1:=20fix=20review=20findings=20?= =?UTF-8?q?=E2=80=94=20can=5Fhandle=20semantics,=20response=5Fmode=20MUST,?= =?UTF-8?q?=20timeout=20bound,=20multi-tier=20global=5Fstop,=20drop=20stop?= =?UTF-8?q?.response?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- ovos-stop-1.md | 121 +++++++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 54 deletions(-) diff --git a/ovos-stop-1.md b/ovos-stop-1.md index 58bff48a..58f7b1dc 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -29,7 +29,8 @@ subscribers. It does **not** define: vocabulary file formats, matching algorithms, confidence thresholds, audio capture control, handler-side framework -APIs, or wake-word and barge-in policies. +APIs, wake-word and barge-in policies, or post-stop in-flight +interaction teardown (a skill-side or orchestrator-side concern). --- @@ -44,8 +45,8 @@ 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 (`:global_stop`, -§5), namespaced under its own `pipeline_id`. +uses it for its own self-dispatch (`:global_stop`, §5), +namespaced under its own `pipeline_id`. --- @@ -53,17 +54,25 @@ uses it for its own self-dispatch (`:global_stop`, 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. +subject to the same denylist filtering, first-match-wins iteration, and +circuit-breaker rules as every other pipeline plugin. ### 3.1 Pipeline identity and dispatch target A stop plugin is loaded as one or more `pipeline_id` entries in -`session.pipeline`. `Match.skill_id` MUST equal: +`session.pipeline`. Multiple confidence tiers (`stop_high`, +`stop_medium`, `stop_low`) MAY be registered as separate `pipeline_id`s +or merged into one plugin. When registered as separate entries, all +tiers MUST share a single `global_stop` dispatch handler — identified +by a common `pipeline_id` — so that exactly one `ovos.stop` broadcast +is emitted per global stop event. -- for `intent_name: "stop"` — the most recently activated positive - pong responder selected per §4; -- for `intent_name: "global_stop"` — the stop plugin's own `pipeline_id`. +`Match.skill_id` MUST equal: + +- for `intent_name: "stop"` — the most recently activated positive pong + responder selected per §4; +- for `intent_name: "global_stop"` — the shared `pipeline_id` whose + handler emits `ovos.stop`. ### 3.2 Match obligations @@ -91,8 +100,9 @@ Inside `match`: 1. Read `session.active_handlers`. If empty, return a `global_stop` Match per §5. -2. Emit `ovos.stop.ping` and collect `ovos.stop.pong` responses up to - a deployer-defined timeout (RECOMMENDED default: 0.5 s). +2. Emit `ovos.stop.ping` and collect `ovos.stop.pong` responses within + a deployer-configured timeout (RECOMMENDED default: 0.5 s; + SHOULD NOT exceed 1 s). 3. Identify positive responders: pongs where `can_handle: true` and `skill_id` appears in `session.active_handlers`. Pongs from skills not in `active_handlers` MUST be ignored; late pongs MAY be ignored. @@ -117,20 +127,29 @@ Inside `match`: | Field | Type | Required | Meaning | |-------|------|----------|---------| | `skill_id` | string | yes | The `skill_id` of the responding handler. | -| `can_handle` | boolean | yes | Whether the handler can stop for the inbound `session_id`. | +| `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 `:stop`. +A handler with no current activity MUST respond `can_handle: false` +or remain silent. A handler that does not respond within the timeout +is treated as `can_handle: false`. -A handler that does not respond within the timeout is treated as -`can_handle: false`. A handler MUST respond `can_handle: true` only -when it has stoppable activity for the inbound `session_id`. +### 4.3 Dispatch and stop handler obligations -### 4.3 Dispatch +The orchestrator dispatches `:stop` per PIPELINE-1 §7, +firing the handler-lifecycle trio (`ovos.intent.handler.start`, +`.complete`, `.error`). -The orchestrator dispatches `:stop` per the standard -PIPELINE-1 §7 contract. The target's stop handler fires the -handler-lifecycle trio (`ovos.intent.handler.start`, `.complete`, -`.error`). +The stop handler MUST: -The handler MUST cease only the activity keyed to the inbound +- cease the activity it declared stoppable, scoped to the inbound + `session_id`; +- treat a second `:stop` or `ovos.stop` arriving while + already stopping as idempotent. + +The stop handler MUST NOT interrupt activity belonging to a different `session_id`. ### 4.4 Self-pruning of `active_handlers` @@ -155,23 +174,23 @@ A `global_stop` Match is returned in three cases: ``` Match( - skill_id=, + skill_id=, intent_name="global_stop", updated_session= ) ``` -PIPELINE-1 §7.1 stamps `` onto `active_handlers` at -dispatch time (the name `global_stop` is not reserved, so suppression -does not apply). +PIPELINE-1 §7.1 stamps `` onto `active_handlers` +at dispatch time (the name `global_stop` is not reserved, so stamping +suppression does not apply). ### 5.3 Broadcast -The stop handler dispatched by `:global_stop` MUST -emit `ovos.stop` — a universal broadcast. Every component performing -user-visible activity MUST subscribe to `ovos.stop` and cease activity -for the broadcast's `session_id`. +The handler dispatched by `:global_stop` MUST emit +`ovos.stop`. Every component performing user-visible activity MUST +subscribe to `ovos.stop` and cease activity for the broadcast's +`session_id`. `ovos.stop` is not a dispatch topic — it does not follow the `:` shape and does not fire the handler-lifecycle @@ -183,13 +202,15 @@ trio. The namespace `ovos.stop.*` is reserved by this specification. ### 6.1 `response_mode` -A stop plugin SHOULD clear `session.response_mode` via -`Match.updated_session`: +A stop plugin MUST clear `session.response_mode` via `Match.updated_session`: - for `intent_name: "stop"` — clear the entry whose `owner_id` matches the dispatch target; - for `intent_name: "global_stop"` — remove `response_mode` entirely. +An uncleared `response_mode` for a stopped skill would route the next +utterance to that skill as if it were still awaiting a response. + ### 6.2 `active_handlers` A stop plugin MUST drain `active_handlers` via `Match.updated_session` @@ -253,24 +274,20 @@ handler-lifecycle trio. No other topic in this table does. ### Stop pipeline plugin — MUST: -- return `Match` with `intent_name` of exactly `"stop"` or - `"global_stop"`, never any other value; +- return `intent_name` of exactly `"stop"` or `"global_stop"` (§2, §3.1); - set `Match.skill_id` per §3.1; -- return `None` when no stop vocabulary matches or the requested - `lang` is unsupported; -- read `session.active_handlers` to drive the cascade (§4.1); -- communicate all session mutations through `Match.updated_session`; -- remove the dispatch target from `active_handlers` on a `stop` Match; - empty `active_handlers` entirely on a `global_stop` Match (§6.2); +- return `None` when no stop vocabulary matches or `lang` is unsupported; +- collect pong responses within a window SHOULD NOT exceeding 1 s (§4.1); +- ignore pongs from skills absent from `session.active_handlers` (§4.1); +- clear `session.response_mode` via `Match.updated_session` (§6.1); +- drain `active_handlers` via `Match.updated_session` (§6.2); - honour `session.blacklisted_skills` and `session.blacklisted_intents` (§6.3); -- subscribe to `:global_stop` and emit `ovos.stop` - from that handler. +- subscribe to `:global_stop` and emit `ovos.stop` (§5.3). ### Stop pipeline plugin — SHOULD: -- clear `session.response_mode` via `Match.updated_session` (§6.1); -- return `global_stop` immediately when `active_handlers` is empty, - without emitting a ping. +- return `global_stop` immediately when `active_handlers` is empty (§4.1 step 1); +- place all confidence tiers under a shared `pipeline_id` for `global_stop` (§3.1). ### Deployment — SHOULD: @@ -280,28 +297,24 @@ handler-lifecycle trio. No other topic in this table does. ### Skill — MUST: - subscribe to both `:stop` and `ovos.stop`; -- on `:stop`, cease activity for the inbound `session_id`; +- on `:stop`, cease stoppable activity for the inbound `session_id` (§4.3); - on `ovos.stop`, cease all activity for the inbound `session_id`; -- treat duplicate stop dispatches and `ovos.stop` broadcasts as - idempotent. +- treat duplicate stop dispatches and `ovos.stop` broadcasts as idempotent. ### Skill — SHOULD: - subscribe to `ovos.stop.ping` and reply on `ovos.stop.pong` with - `skill_id` and `can_handle` reflecting feasibility for the inbound - `session_id`, or remain silent if it has no stoppable activity; -- remove itself from `session.active_handlers` when it cannot be - stopped (§4.4). + `can_handle` reflecting stoppable activity for the inbound `session_id` (§4.2); +- remove itself from `session.active_handlers` when it cannot be stopped (§4.4). ### Non-skill component performing user-visible activity — MUST: -- subscribe to `ovos.stop` and cease activity for the inbound - `session_id` on receipt. +- subscribe to `ovos.stop` and cease activity for the inbound `session_id`. ### Orchestrator — MUST: - treat OVOS-INTENT-4 registrations naming `stop` as malformed — - log at WARN and decline to index. + log at WARN and decline to index (§2). --- From fd78d4ace6c208a8d955e22b240fb3e5b45172dc Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 17:01:01 +0100 Subject: [PATCH 14/21] =?UTF-8?q?STOP-1=20=C2=A76.2:=20cross-ref=20convers?= =?UTF-8?q?e=5Fhandlers=20(CONVERSE-1);=20stop=20drain=20does=20not=20affe?= =?UTF-8?q?ct=20converse=20eligibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- ovos-stop-1.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ovos-stop-1.md b/ovos-stop-1.md index 58f7b1dc..f7f4a375 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -213,6 +213,13 @@ utterance to that skill as if it were still awaiting a response. ### 6.2 `active_handlers` +`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. +Draining `active_handlers` on stop does **not** affect +`converse_handlers` — a stopped skill remains eligible for converse +turns until its TTL expires or it self-removes. + A stop plugin MUST drain `active_handlers` via `Match.updated_session` (committed pre-dispatch per PIPELINE-1 §4.2): From cfc27b7fef26db566f70c7e1154ce0bf4a342021 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 17:06:48 +0100 Subject: [PATCH 15/21] =?UTF-8?q?STOP-1:=20fix=20dependency=20attribution,?= =?UTF-8?q?=20=C2=A79=20MUST/SHOULD,=20grammar,=20global=5Fstop=20drains?= =?UTF-8?q?=20converse=5Fhandlers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dependencies: active_handlers owned by PIPELINE-1, not SESSION-1 - global_stop updated_session: empty converse_handlers (CONVERSE-1 §2.1) as well as active_handlers - §6.2: explain global_stop rationale for draining converse_handlers - §9 MUST list: move empty-on-no-active into MUST; fix pong-timeout grammar; split SHOULD out - §9 SHOULD: add skip-ping-when-empty and 1s-timeout bullets Co-Authored-By: Claude Sonnet 4.6 --- ovos-stop-1.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ovos-stop-1.md b/ovos-stop-1.md index f7f4a375..50b1799e 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -12,8 +12,9 @@ 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 (`active_handlers` and -`response_mode` field registry), OVOS-SESSION-2 (mutation boundaries). +`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. @@ -176,7 +177,8 @@ A `global_stop` Match is returned in three cases: Match( skill_id=, intent_name="global_stop", - updated_session= ) ``` @@ -224,7 +226,11 @@ A stop plugin MUST drain `active_handlers` via `Match.updated_session` (committed pre-dispatch per PIPELINE-1 §4.2): - `stop` Match — remove the dispatch target entry only; -- `global_stop` Match — empty `active_handlers` entirely. +- `global_stop` Match — empty `active_handlers` entirely and empty + `converse_handlers` (OVOS-CONVERSE-1 §2.1) entirely. A global stop + ends all active engagement; leaving skills in the converse eligibility + list would allow the converse plugin to continue polling them until + their TTL expires. The stamping push (PIPELINE-1 §7.1) is suppressed for the reserved intent_name `stop`, so the removal in `updated_session` is the final @@ -284,16 +290,19 @@ handler-lifecycle trio. No other topic in this table does. - return `intent_name` of exactly `"stop"` or `"global_stop"` (§2, §3.1); - set `Match.skill_id` per §3.1; - return `None` when no stop vocabulary matches or `lang` is unsupported; -- collect pong responses within a window SHOULD NOT exceeding 1 s (§4.1); +- collect pong responses within a deployer-configured timeout (§4.1); - ignore pongs from skills absent from `session.active_handlers` (§4.1); - clear `session.response_mode` via `Match.updated_session` (§6.1); - drain `active_handlers` via `Match.updated_session` (§6.2); +- on `global_stop`, also empty `converse_handlers` via `Match.updated_session` (§6.2); +- return `global_stop` when `active_handlers` is empty or no positive pong responder exists (§4.1 steps 1, 5); - honour `session.blacklisted_skills` and `session.blacklisted_intents` (§6.3); - subscribe to `:global_stop` and emit `ovos.stop` (§5.3). ### Stop pipeline plugin — SHOULD: -- return `global_stop` immediately when `active_handlers` is empty (§4.1 step 1); +- configure the ping-pong timeout to not exceed 1 s (§4.1); +- skip the ping and return `global_stop` immediately when `active_handlers` is empty (§4.1 step 1); - place all confidence tiers under a shared `pipeline_id` for `global_stop` (§3.1). ### Deployment — SHOULD: From 088148540379ee119164f7c7c4bacaf41a380438 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 17:13:07 +0100 Subject: [PATCH 16/21] =?UTF-8?q?STOP-1=20=C2=A75.2:=20consolidate=20globa?= =?UTF-8?q?l=5Fstop=20cleanup;=20=C2=A75.2=20note=20stop-plugin=20converse?= =?UTF-8?q?=20re-entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §5.2: explicit Match construction block clearing active_handlers, converse_handlers, and response_mode atomically; prose asserting stop plugin owns all three cleanups - §5.2: note that global_stop stamp onto active_handlers is intentional — stop plugin MAY converse for follow-up clarifications - §6.1: collapse to targeted-stop-only obligation; cross-ref §5.2 for global_stop case to eliminate duplication Co-Authored-By: Claude Sonnet 4.6 --- ovos-stop-1.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/ovos-stop-1.md b/ovos-stop-1.md index 50b1799e..64dd44a1 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -173,19 +173,30 @@ A `global_stop` Match is returned in three cases: ### 5.2 Match construction +The `global_stop` Match MUST carry a fully-cleaned `updated_session`: + ``` Match( - skill_id=, - intent_name="global_stop", - updated_session= + skill_id = , + intent_name = "global_stop", + updated_session = ) ``` +All three fields are cleared atomically at match time via +`Match.updated_session` (PIPELINE-1 §4.2), before dispatch. The stop +plugin owns this cleanup entirely — no downstream component needs to +inspect or drain these fields as a consequence of a global stop. + PIPELINE-1 §7.1 stamps `` onto `active_handlers` at dispatch time (the name `global_stop` is not reserved, so stamping -suppression does not apply). +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 such as "not the cooking timer" — provided it +registers a converse handler. ### 5.3 Broadcast @@ -204,11 +215,11 @@ trio. The namespace `ovos.stop.*` is reserved by this specification. ### 6.1 `response_mode` -A stop plugin MUST clear `session.response_mode` via `Match.updated_session`: - -- for `intent_name: "stop"` — clear the entry whose `owner_id` matches - the dispatch target; -- for `intent_name: "global_stop"` — remove `response_mode` entirely. +For `intent_name: "stop"`, a stop plugin MUST clear the +`session.response_mode` entry whose `owner_id` matches the dispatch +target, via `Match.updated_session`. (For `intent_name: "global_stop"`, +`response_mode` is removed entirely as part of the §5.2 Match +construction.) An uncleared `response_mode` for a stopped skill would route the next utterance to that skill as if it were still awaiting a response. From 06846be4c1694937fb0212a4ac466e80b91a89a9 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 17:41:26 +0100 Subject: [PATCH 17/21] =?UTF-8?q?STOP-1:=20prescriptive=20audit=20pass=20?= =?UTF-8?q?=E2=80=94=20cut=20rationale=20prose,=20fix=20normative=20gaps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §3.1: drop confusing multi-tier shared-pipeline_id prose; replace with single MUST (exactly one ovos.stop per global stop event per session); fold §3.2 match obligations into §3.1 - §3.2 (was §3.3): tighten vocabulary SHOULD to normative form - §4.1 step 2: drop non-RFC-2119 "RECOMMENDED"; use plain "default" - §4.2: add explicit rule that non-responding handlers are treated as can_handle: false and are ineligible as stop targets; note cascade escalates to global_stop — closes the legacy-skill gap - §4.3: define idempotency — duplicate stop MUST be treated as no-op, not a second stop sequence - §6.1: cut rationale sentence - §6.2: cut rationale sentences; keep normative drain rules - §6.3: add blacklisted_intents scope rule — applies to dispatched intent_name, not to the ping broadcast - §5.2: cut "stop plugin owns cleanup" rationale sentence - §9: remove inconsistent SHOULD (step-1 skip is already a MUST in algorithm); remove redundant multi-tier SHOULD; add non-responding handler MUST to plugin conformance; tighten idempotency wording Co-Authored-By: Claude Sonnet 4.6 --- ovos-stop-1.md | 93 +++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/ovos-stop-1.md b/ovos-stop-1.md index 64dd44a1..671b94c5 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -60,36 +60,30 @@ circuit-breaker rules as every other pipeline plugin. ### 3.1 Pipeline identity and dispatch target -A stop plugin is loaded as one or more `pipeline_id` entries in -`session.pipeline`. Multiple confidence tiers (`stop_high`, -`stop_medium`, `stop_low`) MAY be registered as separate `pipeline_id`s -or merged into one plugin. When registered as separate entries, all -tiers MUST share a single `global_stop` dispatch handler — identified -by a common `pipeline_id` — so that exactly one `ovos.stop` broadcast -is emitted per global stop event. +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"` — the shared `pipeline_id` whose - handler emits `ovos.stop`. +- for `intent_name: "global_stop"` — the `pipeline_id` whose handler + emits `ovos.stop`. -### 3.2 Match obligations - -A stop plugin **MUST**: +A stop plugin MUST: - return `None` for any language for which it cannot resolve stop vocabulary; - read `session.active_handlers` to drive the §4 cascade; - perform the ping-pong exchange (§4.2) inside `match`. -### 3.3 Vocabulary +### 3.2 Vocabulary -A stop plugin SHOULD distinguish generic stop utterances from explicit -"stop everything" utterances. Generic stop triggers the §4 cascade; -"stop everything" maps directly to `intent_name: "global_stop"` without -consulting `active_handlers`. +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. --- @@ -102,8 +96,7 @@ Inside `match`: 1. Read `session.active_handlers`. If empty, return a `global_stop` Match per §5. 2. Emit `ovos.stop.ping` and collect `ovos.stop.pong` responses within - a deployer-configured timeout (RECOMMENDED default: 0.5 s; - SHOULD NOT exceed 1 s). + a deployer-configured timeout (default: 0.5 s; SHOULD NOT exceed 1 s). 3. Identify positive responders: pongs where `can_handle: true` and `skill_id` appears in `session.active_handlers`. Pongs from skills not in `active_handlers` MUST be ignored; late pongs MAY be ignored. @@ -133,9 +126,13 @@ Inside `match`: `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 `:stop`. + A handler with no current activity MUST respond `can_handle: false` -or remain silent. A handler that does not respond within the timeout -is treated as `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. ### 4.3 Dispatch and stop handler obligations @@ -147,8 +144,8 @@ The stop handler MUST: - cease the activity it declared stoppable, scoped to the inbound `session_id`; -- treat a second `:stop` or `ovos.stop` arriving while - already stopping as idempotent. +- 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`. @@ -167,7 +164,7 @@ 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.3); +- 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). @@ -187,16 +184,13 @@ Match( ``` All three fields are cleared atomically at match time via -`Match.updated_session` (PIPELINE-1 §4.2), before dispatch. The stop -plugin owns this cleanup entirely — no downstream component needs to -inspect or drain these fields as a consequence of a global stop. +`Match.updated_session` (PIPELINE-1 §4.2), before dispatch. PIPELINE-1 §7.1 stamps `` 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 such as "not the cooking timer" — provided it -registers a converse handler. +follow-up clarification — provided it registers a converse handler. ### 5.3 Broadcast @@ -217,31 +211,22 @@ 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 `owner_id` matches the dispatch -target, via `Match.updated_session`. (For `intent_name: "global_stop"`, +target, via `Match.updated_session`. For `intent_name: "global_stop"`, `response_mode` is removed entirely as part of the §5.2 Match -construction.) - -An uncleared `response_mode` for a stopped skill would route the next -utterance to that skill as if it were still awaiting a response. +construction. ### 6.2 `active_handlers` `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. -Draining `active_handlers` on stop does **not** affect -`converse_handlers` — a stopped skill remains eligible for converse -turns until its TTL expires or it self-removes. A stop plugin MUST drain `active_handlers` via `Match.updated_session` (committed pre-dispatch per PIPELINE-1 §4.2): - `stop` Match — remove the dispatch target entry only; - `global_stop` Match — empty `active_handlers` entirely and empty - `converse_handlers` (OVOS-CONVERSE-1 §2.1) entirely. A global stop - ends all active engagement; leaving skills in the converse eligibility - list would allow the converse plugin to continue polling them until - their TTL expires. + `converse_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 @@ -250,9 +235,14 @@ state. It is not suppressed for `global_stop`. ### 6.3 Denylists A stop plugin MUST honour `session.blacklisted_skills` and -`session.blacklisted_intents` (PIPELINE-1 §5). A handler whose -`skill_id` appears in `blacklisted_skills` MUST NOT be pinged or -selected as a stop target. +`session.blacklisted_intents` (PIPELINE-1 §5): + +- `blacklisted_skills`: a handler whose `skill_id` appears in this list + MUST NOT be pinged or selected as a stop target; +- `blacklisted_intents`: applies to the dispatched intent_name (`"stop"` + or `"global_stop"`). A stop plugin MUST return `None` if the matched + intent_name appears in `blacklisted_intents`. This list does not + affect the ping broadcast. --- @@ -300,21 +290,22 @@ handler-lifecycle trio. No other topic in this table does. - return `intent_name` of exactly `"stop"` or `"global_stop"` (§2, §3.1); - set `Match.skill_id` per §3.1; -- return `None` when no stop vocabulary matches or `lang` is unsupported; +- return `None` when no stop vocabulary matches or `lang` is unsupported (§3.1); - collect pong responses within a deployer-configured timeout (§4.1); - ignore pongs from skills absent from `session.active_handlers` (§4.1); -- clear `session.response_mode` via `Match.updated_session` (§6.1); +- treat non-responding handlers as `can_handle: false` (§4.2); +- clear `session.response_mode` for the dispatch target via `Match.updated_session` (§6.1); - drain `active_handlers` via `Match.updated_session` (§6.2); - on `global_stop`, also empty `converse_handlers` via `Match.updated_session` (§6.2); - return `global_stop` when `active_handlers` is empty or no positive pong responder exists (§4.1 steps 1, 5); -- honour `session.blacklisted_skills` and `session.blacklisted_intents` (§6.3); -- subscribe to `:global_stop` and emit `ovos.stop` (§5.3). +- honour `session.blacklisted_skills` and `session.blacklisted_intents` per §6.3; +- subscribe to `:global_stop` and emit `ovos.stop` (§5.3); +- emit exactly one `ovos.stop` broadcast per global stop event per session (§3.1). ### Stop pipeline plugin — SHOULD: - configure the ping-pong timeout to not exceed 1 s (§4.1); -- skip the ping and return `global_stop` immediately when `active_handlers` is empty (§4.1 step 1); -- place all confidence tiers under a shared `pipeline_id` for `global_stop` (§3.1). +- provide explicit "stop everything" vocabulary mapping to `global_stop` without cascade (§3.2). ### Deployment — SHOULD: @@ -326,7 +317,7 @@ handler-lifecycle trio. No other topic in this table does. - subscribe to both `:stop` and `ovos.stop`; - on `:stop`, cease stoppable activity for the inbound `session_id` (§4.3); - on `ovos.stop`, cease all activity for the inbound `session_id`; -- treat duplicate stop dispatches and `ovos.stop` broadcasts as idempotent. +- treat a duplicate `:stop` or `ovos.stop` as a no-op (§4.3). ### Skill — SHOULD: From 0a96972eff9f125d4c6864a2ec0405dcb99fd4ce Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 17:54:46 +0100 Subject: [PATCH 18/21] STOP-1: close seven spec gaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §4.1 step 4: add tie-breaking rule — equal activated_at selects the latest list position (most recently stamped) - §4.2 ping: note session_id carried in Message context per MSG-1 - §4.2 pong: MUST use reply derivation so routing metadata is preserved for remote/client-side skills; source/destination are layer-2 only - §4.3: note that updated_session is not rolled back on handler .error - §5.3: reference MSG-1 for session_id on ovos.stop broadcast - §6.1: add "if no matching entry, field is left unchanged" - §6.3: clarify blacklisted_intents applies to the resolved intent_name; a stop utterance escalated to global_stop is gated by the global_stop entry, not the stop entry - §9 Skill SHOULD: tighten pong wording to match reply-derivation MUST Co-Authored-By: Claude Sonnet 4.6 --- ovos-stop-1.md | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/ovos-stop-1.md b/ovos-stop-1.md index 671b94c5..928e47c6 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -101,7 +101,9 @@ Inside `match`: `skill_id` appears in `session.active_handlers`. Pongs from skills not in `active_handlers` MUST be ignored; late pongs MAY be ignored. 4. If at least one positive responder exists, select the entry with the - highest `activated_at` in `session.active_handlers`. Construct + highest `activated_at` in `session.active_handlers`. If two entries + share the same `activated_at`, select the entry appearing latest in + the list (most recently stamped). Construct `updated_session` removing that `skill_id` from `active_handlers` and clearing any `response_mode` entry it owns. Return `Match(skill_id=, intent_name="stop", updated_session=...)`. @@ -109,10 +111,15 @@ Inside `match`: ### 4.2 Ping and pong shape -**`ovos.stop.ping`** — broadcast. Payload MAY be empty. The inbound -`session_id` is carried in Message context (OVOS-MSG-1). +**`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. +**`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. ```json { "skill_id": "example.skill", "can_handle": true } @@ -150,6 +157,10 @@ The stop handler MUST: 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. + ### 4.4 Self-pruning of `active_handlers` A handler that cannot be stopped SHOULD remove itself from @@ -196,8 +207,8 @@ follow-up clarification — provided it registers a converse handler. The handler dispatched by `:global_stop` MUST emit `ovos.stop`. Every component performing user-visible activity MUST -subscribe to `ovos.stop` and cease activity for the broadcast's -`session_id`. +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 `:` shape and does not fire the handler-lifecycle @@ -211,7 +222,8 @@ 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 `owner_id` matches the dispatch -target, via `Match.updated_session`. For `intent_name: "global_stop"`, +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. @@ -240,9 +252,11 @@ A stop plugin MUST honour `session.blacklisted_skills` and - `blacklisted_skills`: a handler whose `skill_id` appears in this list MUST NOT be pinged or selected as a stop target; - `blacklisted_intents`: applies to the dispatched intent_name (`"stop"` - or `"global_stop"`). A stop plugin MUST return `None` if the matched - intent_name appears in `blacklisted_intents`. This list does not - affect the ping broadcast. + or `"global_stop"`). A stop plugin MUST return `None` if the resolved + intent_name appears in `blacklisted_intents`. A `stop` utterance that + would resolve to `global_stop` (§4.1 steps 1 or 5) is subject to the + `global_stop` entry, not the `stop` entry. This list does not affect + the ping broadcast. --- @@ -321,8 +335,8 @@ handler-lifecycle trio. No other topic in this table does. ### Skill — SHOULD: -- subscribe to `ovos.stop.ping` and reply on `ovos.stop.pong` with - `can_handle` reflecting stoppable activity for the inbound `session_id` (§4.2); +- subscribe to `ovos.stop.ping` and respond with a `reply`-derived + `ovos.stop.pong` carrying `can_handle` for the inbound `session_id` (§4.2); - remove itself from `session.active_handlers` when it cannot be stopped (§4.4). ### Non-skill component performing user-visible activity — MUST: From 3baed796835470b71f3c6340594117ad2de54863 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 18:25:48 +0100 Subject: [PATCH 19/21] =?UTF-8?q?PIPELINE-1=20=C2=A77.3:=20fix=20section?= =?UTF-8?q?=20reference=20=E2=80=94=20routing=20is=20=C2=A77.2,=20not=20?= =?UTF-8?q?=C2=A77.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §7.3 previously cited '§7.1 routing' but routing (subscription discipline) is §7.2; §7.1 covers context stamping and the active_handlers push. Expand to list all three affected subsections explicitly. Co-Authored-By: Claude Sonnet 4.6 --- ovos-pipeline-1.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ovos-pipeline-1.md b/ovos-pipeline-1.md index 824f2964..68e2ba45 100644 --- a/ovos-pipeline-1.md +++ b/ovos-pipeline-1.md @@ -879,13 +879,14 @@ pipeline plugin role. A reserved intent_name is one that: A reservation is a **namespace lease**, not a dispatch modification. Dispatches on reserved intent_names fire §7.1 -routing and §8 handler-trio identically to ordinary dispatches. -The one exception is the §7.1 `session.active_handlers` push, -which is suppressed on reserved-name dispatches — a reserved -name represents a continuation or termination of an already- -active skill's participation, not a fresh activation. The -reserving specification gets exclusive use of the name across -the deployment's skill set; it gets no other privilege. +context stamping, §7.2 routing, and §8 handler-trio identically +to ordinary dispatches. The one exception is the +`session.active_handlers` push defined in §7.1, which is +suppressed on reserved-name dispatches — a reserved name +represents a continuation or termination of an already-active +skill's participation, not a fresh activation. The reserving +specification gets exclusive use of the name across the +deployment's skill set; it gets no other privilege. Reservations currently in force: From c519744c928e4fdf5ca3beb220d4d186ca816e97 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Wed, 27 May 2026 18:29:26 +0100 Subject: [PATCH 20/21] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 47416b6b..a63842a5 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,6 @@ below). Adoption is voluntary; conformance, once adopted, is not. | OVOS-CONTEXT-1 | [Intent Context](intent-context.md) | 1 | [Draft — in review (PR #18)](https://github.com/OpenVoiceOS/architecture/pull/18) | | OVOS-CONVERSE-1 | [Active Handlers and Interactive Response](converse.md) | 1 | [Draft — in review (PR #25)](https://github.com/OpenVoiceOS/architecture/pull/25) | | OVOS-STOP-1 | [Stop Pipeline Plugin](ovos-stop-1.md) | 1 | [Draft — in review (PR #33)](https://github.com/OpenVoiceOS/architecture/pull/33) | -| OVOS-COMMON-QUERY-1 | [Common Query Pipeline Plugin](common-query.md) | 1 | Draft | Each spec carries its own scope statement, design rationale, and conformance section in its header. Open the document for the full From 8ef3292bc2a38f4df8136acd62638b653f6959c9 Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Wed, 27 May 2026 18:40:05 +0100 Subject: [PATCH 21/21] Update ovos-stop-1.md --- ovos-stop-1.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ovos-stop-1.md b/ovos-stop-1.md index 928e47c6..8e9eaafd 100644 --- a/ovos-stop-1.md +++ b/ovos-stop-1.md @@ -250,10 +250,10 @@ A stop plugin MUST honour `session.blacklisted_skills` and `session.blacklisted_intents` (PIPELINE-1 §5): - `blacklisted_skills`: a handler whose `skill_id` appears in this list - MUST NOT be pinged or selected as a stop target; + MUST NOT be selected as a stop target; - `blacklisted_intents`: applies to the dispatched intent_name (`"stop"` - or `"global_stop"`). A stop plugin MUST return `None` if the resolved - intent_name appears in `blacklisted_intents`. A `stop` utterance that + or `"global_stop"`). A stop plugin MUST not resolve a intent_name + that appears in `blacklisted_intents`. A `stop` utterance that would resolve to `global_stop` (§4.1 steps 1 or 5) is subject to the `global_stop` entry, not the `stop` entry. This list does not affect the ping broadcast.