Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,25 @@ version 2: its `{{ … }}` sequences become substitution points, and its
OVOS-PIPELINE-1 §7.3), the global-stop path (`global_stop` →
`ovos.stop` broadcast), and the session-scoping obligations over
`session.active_handlers`.
- Consistency audit: §4.1 step 5 — a generic stop with no positive pong
responder falls back to the most recently activated `active_handlers`
entry (recency-targeted stop) and MUST NOT escalate to `global_stop`;
a pong can be absent for reasons unrelated to stoppability, and a
missed sub-second reply must never trigger a session-wide wipe of
converse state, response modes, and unrelated skills' activity —
`global_stop` is reserved for explicit global-stop vocabulary (§3.2)
and the empty-`active_handlers` case (§5.1 lists exactly these two
triggers); §4.1 ping-pong timeout stated as a RECOMMENDED default
(0.5 s, SHOULD NOT exceed 1 s) with the hardcoded window value removed
from the step-5 rationale; §5.2 `<shared_pipeline_id>` defined
precisely — the `pipeline_id` of the stop plugin instance whose
`match` produced the Match, shared across multiple §3.1 pipeline
entries resolving to that instance, which MUST deduplicate to exactly
one `ovos.stop` broadcast per global stop event per session; §6.3
`blacklisted_intents` rule restated operationally (never return a
Match whose `<skill_id>:<intent_name>` appears in the list);
cross-spec note that the pong boolean's field name (`can_handle`) is
protocol-local.

## OVOS-COMMON-QUERY-1 — Common Query Pipeline Plugin

Expand Down
68 changes: 53 additions & 15 deletions stop-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ interaction teardown (a skill-side or orchestrator-side concern).

| Reserved intent_name | Meaning |
|----------------------|---------|
| `stop` | Cease activity for the inbound `session_id`. Dispatched on `<target_skill_id>:stop` where the target is the most recently activated (highest `activated_at`) positive pong responder (§4). |
| `stop` | Cease activity for the inbound `session_id`. Dispatched on `<target_skill_id>:stop` where the target is the most recently activated (highest `activated_at`) positive pong responder, or — when no positive pong arrives in time — the most recently activated `active_handlers` entry (§4). |

Skills and other pipelines **MUST NOT** register `stop` under
OVOS-INTENT-4. A registration naming this intent_name is malformed per
Expand Down Expand Up @@ -67,8 +67,9 @@ entries are deployed, the implementation MUST ensure exactly one

`Match.skill_id` MUST equal:

- for `intent_name: "stop"` — the most recently activated positive pong
responder selected per §4;
- for `intent_name: "stop"` — the target selected per §4 (the most
recently activated positive pong responder, or the recency fallback
of §4.1 step 5);
- for `intent_name: "global_stop"` — the `pipeline_id` whose handler
emits `ovos.stop`.

Expand Down Expand Up @@ -96,7 +97,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 within
a deployer-configured timeout (default: 0.5 s; SHOULD NOT exceed 1 s).
a deployer-configured timeout (RECOMMENDED default: 0.5 s; SHOULD
NOT exceed 1 s — stop sits on the most latency-sensitive user
path there is: the user is asking for something to cease).
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.
Expand All @@ -107,7 +110,22 @@ Inside `match`:
`updated_session` removing that `skill_id` from `active_handlers`
and clearing any `response_mode` entry it owns. Return
`Match(skill_id=<that_skill_id>, intent_name="stop", updated_session=...)`.
5. If no positive responder exists, return a `global_stop` Match per §5.
5. If no positive responder exists but `active_handlers` is non-empty,
the stop plugin MUST fall back to recency: select the entry with the
highest `activated_at` in `session.active_handlers` (ties broken as
in step 4) and return a
`Match(skill_id=<that_skill_id>, intent_name="stop", updated_session=...)`
constructed exactly as in step 4. The plugin MUST NOT escalate to
`global_stop` on a missed pong: a pong can be absent for reasons
unrelated to stoppability — a skill busy in its handler thread, or a
satellite whose round-trip exceeds the ping window — and escalating
a missed sub-second reply to a session-wide wipe destroys converse
state,
response modes, and activity belonging to unrelated skills.
Recency-targeted stop degrades gracefully: at worst the target has
nothing to stop and the dispatch is a no-op (§4.3). `global_stop`
remains reserved for explicit global-stop vocabulary (§3.2) and the
empty-`active_handlers` case (step 1).

### 4.2 Ping and pong shape

Expand All @@ -130,16 +148,22 @@ topic name.
| `skill_id` | string | yes | The `skill_id` of the responding handler. |
| `can_handle` | boolean | yes | Whether the handler has stoppable activity for the inbound `session_id`. |

The boolean's field name is protocol-specific: this spec and
OVOS-FALLBACK-1 use `can_handle`, OVOS-CONVERSE-1's poll uses
`result`, and OVOS-COMMON-QUERY-1 uses `can_answer`. Each name is
normative only within its own protocol.

`can_handle: true` asserts that the handler has user-visible or
session-affecting activity in progress for the inbound `session_id`
**and** is prepared to cease it on receipt of `<skill_id>:stop`.

A handler with no current activity MUST respond `can_handle: false`
or remain silent. A handler that does not subscribe to
`ovos.stop.ping`, or does not respond within the timeout, is treated
as `can_handle: false` and is ineligible as a stop target for that
ping round. If no handler declares stoppability, the cascade escalates
to `global_stop` per §4.1 step 5.
as `can_handle: false` for that ping round. If no handler declares
stoppability, the cascade falls back to the most recently activated
`active_handlers` entry per §4.1 step 5 — it does not escalate to
`global_stop`.

### 4.3 Dispatch and stop handler obligations

Expand Down Expand Up @@ -173,11 +197,14 @@ with the updated list, so that future ping rounds bypass it.

### 5.1 Trigger conditions

A `global_stop` Match is returned in three cases:
A `global_stop` Match is returned in two cases:

- explicit "stop everything" vocabulary (§3.2);
- generic stop with empty `active_handlers` (§4.1 step 1);
- generic stop with no positive pong responders (§4.1 step 5).
- generic stop with empty `active_handlers` (§4.1 step 1).

A generic stop with no positive pong responders does **not** trigger
`global_stop`; it falls back to the recency-selected target (§4.1
step 5).

### 5.2 Match construction

Expand All @@ -197,6 +224,15 @@ Match(
All three fields are cleared atomically at match time via
`Match.updated_session` (PIPELINE-1 §4.2), before dispatch.

`<shared_pipeline_id>` is defined as the `pipeline_id` of the stop
plugin instance whose `match` produced this Match — the identity the
`global_stop` dispatch topic addresses and PIPELINE-1 §7.1 stamps.
It is "shared" in the §3.1 sense: when a deployment lists several
stop entries in `session.pipeline` that resolve to the same plugin
instance, they share this one `pipeline_id`, and the instance MUST
deduplicate so that exactly one `ovos.stop` broadcast is emitted per
global stop event per session.

PIPELINE-1 §7.1 stamps `<shared_pipeline_id>` onto `active_handlers`
at dispatch time (the name `global_stop` is not reserved, so stamping
suppression does not apply). This is intentional: the stop plugin MAY
Expand Down Expand Up @@ -252,9 +288,10 @@ A stop plugin MUST honour `session.blacklisted_skills` and
- `blacklisted_skills`: a handler whose `skill_id` appears in this list
MUST NOT be selected as a stop target;
- `blacklisted_intents`: applies to the dispatched intent_name (`"stop"`
or `"global_stop"`). A stop plugin MUST not resolve a intent_name
that appears in `blacklisted_intents`. A `stop` utterance that
would resolve to `global_stop` (§4.1 steps 1 or 5) is subject to the
or `"global_stop"`). A stop plugin MUST NOT return a `Match` whose
`<Match.skill_id>:<Match.intent_name>` appears in
`blacklisted_intents`. A `stop` utterance that
would resolve to `global_stop` (§4.1 step 1) is subject to the
`global_stop` entry, not the `stop` entry. This list does not affect
the ping broadcast.

Expand Down Expand Up @@ -311,7 +348,8 @@ handler-lifecycle trio. No other topic in this table does.
- 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);
- return `global_stop` only for empty `active_handlers` or explicit global-stop vocabulary (§4.1 step 1, §3.2);
- with no positive pong responder and non-empty `active_handlers`, target the highest-`activated_at` entry with `intent_name: "stop"` rather than escalating (§4.1 step 5);
- honour `session.blacklisted_skills` and `session.blacklisted_intents` per §6.3;
- subscribe to `<own_pipeline_id>:global_stop` and emit `ovos.stop` (§5.3);
- emit exactly one `ovos.stop` broadcast per global stop event per session (§3.1).
Expand Down