From 5e247eb39c7687c4b42ee623ce05cff0d5ec6a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Trellu?= Date: Tue, 13 Oct 2026 12:35:44 -0400 Subject: [PATCH 01/11] spec(ready-1): a boot-readiness signal gates the utterance lifecycle Proposes OVOS-READY-1: an ovos.ready namespace the orchestrator emits readiness transitions on, and one requirement on OVOS-PIPELINE-1's utterance lifecycle -- while unready, decline with a short localized dialog instead of entering the pipeline loop at all. Motivated by a production finding (OpenVoiceOS/ovos-core#1027): right after a restart, a pipeline's bounded match call can exceed its bound from boot contention alone, get declined as designed, and fall through to the next pipeline -- which can match a mid-conversation utterance like a bare 'yes' to an unrelated literal entity instead of continuing the conversation it belonged to. Raising the bound only narrows the race; it does not remove the window. This gates the lifecycle on the orchestrator's own readiness instead. A reference implementation and production measurement are tracked in OpenVoiceOS/ovos-core#1027. Co-Authored-By: Claude Opus 5.5 --- README.md | 1 + ready-1.md | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 ready-1.md diff --git a/README.md b/README.md index 6f4cb64a..82027998 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ The **Version** column carries the specification's compatibility class | ID | Document | Version | Status | |----|----------|---------|--------| | OVOS-PIPELINE-1 | [Utterance Lifecycle and Pipeline](pipeline-1.md) | 2 | Draft | +| OVOS-READY-1 | [Boot Readiness and the Utterance Lifecycle](ready-1.md) | 1 | Draft | | OVOS-TRANSFORM-1 | [Transformer Plugins](transformer.md) | 2 | Draft | | OVOS-CONTEXT-1 | [Intent Context](intent-context.md) | 2 | Draft | | OVOS-CONVERSE-1 | [Active Handlers and Interactive Response](converse.md) | 2 | Draft | diff --git a/ready-1.md b/ready-1.md new file mode 100644 index 00000000..2a9b14c7 --- /dev/null +++ b/ready-1.md @@ -0,0 +1,97 @@ +# Boot Readiness and the Utterance Lifecycle + +**Spec ID:** OVOS-READY-1 · **Version:** 1 · **Status:** Draft + +This document defines a **readiness state** the orchestrator tracks +during boot, and the one requirement it places on the utterance +lifecycle (OVOS-PIPELINE-1 §6/§9.1): an utterance that arrives before +the orchestrator is ready **MUST NOT** enter the pipeline loop at all. + +It builds on OVOS-PIPELINE-1 (the utterance lifecycle and pipeline +plugin abstraction this spec gates) and OVOS-MSG-1 (the envelope and +session carrier its new topic travels in). + +The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, +**RECOMMENDED** and **MAY** are used as in RFC 2119. + +## 1. Motivation + +Right after a restart, while plugins are still loading languages, +training models and warming caches, an utterance can legally reach a +pipeline whose `match` (OVOS-PIPELINE-1 §4) call exceeds its bound +(§4.4) purely from contention, not from the utterance being +unmatchable. The call is declined as designed, and the loop falls +through to the next pipeline — which, for a mid-conversation +utterance like a bare "yes", can match an unrelated literal entity +instead of continuing the conversation it belonged to. Observed in +production: OpenVoiceOS/ovos-core#1027. + +Raising the bound only delays the same race; it does not remove the +window one utterance can land in. The orchestrator already knows +whether it is still loading. This specification makes that knowledge +observable and gates the lifecycle on it, instead of asking every +pipeline's bound to absorb boot contention it was never sized for. + +## 2. The `ovos.ready` namespace + +- `ovos.ready.state` (bus message, no payload required) — the + orchestrator's current readiness, emitted on every transition. + `context.ready: bool` carries the state so a late subscriber does + not need a second round-trip to learn it. +- `ovos.ready.query` / `ovos.ready.state` reply — a late subscriber + asks and gets exactly one reply, the same way `mycroft.ready` (now + superseded by this state for the orchestrator's own gating; a + producer **MAY** keep emitting it per VERSIONING.md's one-cycle + removal window) could not be queried after the fact. + +An implementation **MUST** emit `ovos.ready.state` with +`context.ready: false` at orchestrator start, and with +`context.ready: true` exactly once, when it considers itself ready +to match utterances. It **MAY** become unready again later (a +plugin reload, a dependency outage) and **MUST** emit the state +change if it does. + +"Ready" is implementation-defined per deployment: at minimum it +**MUST NOT** report ready before every configured pipeline plugin has +completed its own first-load registration. A plugin whose boot work +is inherently per-language (training, prototype loading) **MAY** +report its own partial readiness; the orchestrator's aggregate state +is not ready until none of its plugins are still in that partial +state for the deployment's configured language set. + +## 3. Gating the utterance lifecycle + +While `ovos.ready.state`'s `context.ready` is `false`, the +orchestrator **MUST NOT** run the pipeline loop (OVOS-PIPELINE-1 §6) +for an incoming `ovos.utterance.handle` (§9.1). It **MUST** instead: + +1. speak a short, localized "still starting" response on + `ovos.utterance.speak` (§9.6), in the utterance's language where a + translation exists, falling back to the deployment default + language otherwise; +2. emit `ovos.utterance.handled` as it would for a normal utterance, + so a caller does not wait on a terminal event that never arrives; +3. do none of the pipeline-loop side effects (§8's handler-lifecycle + trio, `ovos.intent.matched` / `.unmatched`): none of them ran. + +This is a **decline-with-dialog**, not a silent drop and not an +error: a caller gets a prompt response either way, and it says why, +rather than guessing from silence or risking a wrong match. + +## 4. Conformance + +A conforming orchestrator: + +- emits `ovos.ready.state` on every readiness transition (§2); +- answers `ovos.ready.query` with the current state; +- never runs the pipeline loop while unready, substituting the + decline-with-dialog of §3; +- resumes ordinary pipeline matching the moment it reports ready, + with no residual gating once `context.ready` is `true`. + +--- + +Proposed alongside a reference implementation in ovos-core (a boot- +state check early in `handle_utterance`, gated on this same signal) +and measured on a production 11-language hub: see the linked PR/issue +discussion. From 5c28db0a99131a8b67228eb7c0b14fc6377abdb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Trellu?= Date: Tue, 13 Oct 2026 12:57:45 -0400 Subject: [PATCH 02/11] spec(ready-1): define ready as pipelines warm, not process status; fix reading order and the re-announce contradiction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit + the actual production timeline (OpenVoiceOS/ovos-core#1027): - README.md: OVOS-READY-1 was in the index but missing from the orchestrator reading order. - ready-1.md §2 contradicted itself: 'emit true exactly once' alongside 'MAY become unready again, MUST emit the change' leaves a consumer that only saw the first true with no way to know it went stale. Now: emit on every transition, in either direction; ovos.ready.query answers the current state, never a cached first answer. - ready-1.md redefined: 'ready' is the aggregate of every pipeline plugin's own warm/cold report, not the orchestrator's process status. The production timeline shows process status was already positive for 20+ seconds before the misroute, while the pipeline itself was still mid-first-compile. A plugin serving its last state while re-training for a config change is warm (it has a real prior state); the same behaviour on a process that never finished a FIRST compile is serving nothing, or stale state, and must decline instead. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- ready-1.md | 67 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 82027998..664b8954 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ 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, 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. +- *Building an orchestrator?* MSG-1 → SESSION-1 → SESSION-2 → PIPELINE-1 → READY-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, diff --git a/ready-1.md b/ready-1.md index 2a9b14c7..60d47e92 100644 --- a/ready-1.md +++ b/ready-1.md @@ -27,10 +27,18 @@ instead of continuing the conversation it belonged to. Observed in production: OpenVoiceOS/ovos-core#1027. Raising the bound only delays the same race; it does not remove the -window one utterance can land in. The orchestrator already knows -whether it is still loading. This specification makes that knowledge -observable and gates the lifecycle on it, instead of asking every -pipeline's bound to absorb boot contention it was never sized for. +window one utterance can land in. Nor is the orchestrator's own +process status the fix: in the production occurrence above, the +process had already reported ready for over twenty seconds before the +misroute, while the pipeline that should have handled the utterance +was still "compiling in background, serving its last compiled state" +— on a process that had never finished a first compile, there was no +prior state to serve. The pipeline plugin itself already knows this +distinction internally; this specification makes it observable and +gates the lifecycle on the aggregate of every plugin's own state, +instead of asking every pipeline's bound to absorb boot contention it +was never sized for, or conflating process status with plugin +readiness. ## 2. The `ovos.ready` namespace @@ -45,19 +53,44 @@ pipeline's bound to absorb boot contention it was never sized for. removal window) could not be queried after the fact. An implementation **MUST** emit `ovos.ready.state` with -`context.ready: false` at orchestrator start, and with -`context.ready: true` exactly once, when it considers itself ready -to match utterances. It **MAY** become unready again later (a -plugin reload, a dependency outage) and **MUST** emit the state -change if it does. - -"Ready" is implementation-defined per deployment: at minimum it -**MUST NOT** report ready before every configured pipeline plugin has -completed its own first-load registration. A plugin whose boot work -is inherently per-language (training, prototype loading) **MAY** -report its own partial readiness; the orchestrator's aggregate state -is not ready until none of its plugins are still in that partial -state for the deployment's configured language set. +`context.ready: false` at orchestrator start, and **MUST** emit it +again, with the new value, on every transition between `false` and +`true` in either direction — not only the first time it becomes +ready. "Ready" is not a one-shot milestone: a skill install, a locale +change, a plugin reload or a dependency outage **MAY** make the +orchestrator unready again after it was ready, and a consumer that +only ever saw one `true` and never looked again would keep routing +into a pipeline that has gone cold. `ovos.ready.query` **MUST** answer +with the current state, not a cached first answer. + +"Ready" is **not** the orchestrator's own process status: a process +can legitimately be alive and answering health checks while a +pipeline plugin it hosts is still mid-boot. Process status is a floor +(an orchestrator that has not started cannot be ready), not the +signal, because a plugin's own loading work routinely outlives it — +see §1's production example, where the orchestrator's process status +was already positive for the entire window the bug occurred in. + +`ovos.ready`'s `context.ready` is instead the aggregate of every +configured pipeline plugin's own **warm** state: a plugin that has +language- or model-dependent boot work (training, prototype loading, +cache warming) reports its own transition from cold to warm on the +bus (a per-plugin topic is implementation-defined; the orchestrator +is the one required subscriber). The orchestrator is ready once every +plugin that reports a warm state has reported warm, for the +deployment's configured language set. A plugin that exposes no such +signal **MUST** be treated as warm by default — this never blocks +readiness on a signal that cannot exist. + +A plugin serving its last-known state while still re-training for a +configuration change it has not finished (common for an incremental +retrain) is warm: it has a genuine prior state to serve. The same +behaviour on a plugin that has **never** finished its first load is +not the same thing — there is no prior state, so "serving the last +compiled state" is serving nothing, or a stale placeholder. A +pipeline **MUST** decline (OVOS-PIPELINE-1 §4.4's bounded-match +timeout, or an explicit no-match) rather than serve partial or stale +state while it has not yet reported warm for the first time. ## 3. Gating the utterance lifecycle From 7577efd745b6fdba1482d525fd6d57b285efda07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Trellu?= Date: Tue, 13 Oct 2026 12:58:37 -0400 Subject: [PATCH 03/11] spec(ready-1): prescriptive rewrite, no code/issue references or meta commentary JarbasAl: specs here are prescriptive, not descriptive, and do not mention current code or specific incidents outside an appendix. Dropped the 'Motivation' narrative (issue numbers, code names, a production timeline) for a 'Scope' section in this repo's usual shape, and the closing meta-commentary paragraph. The warm-state redefinition and the re-announce-on-every-transition requirement from the prior revision are kept, written as plain requirements with no narrative around them. --- ready-1.md | 154 ++++++++++++++++++++++++----------------------------- 1 file changed, 71 insertions(+), 83 deletions(-) diff --git a/ready-1.md b/ready-1.md index 60d47e92..253667ee 100644 --- a/ready-1.md +++ b/ready-1.md @@ -2,10 +2,10 @@ **Spec ID:** OVOS-READY-1 · **Version:** 1 · **Status:** Draft -This document defines a **readiness state** the orchestrator tracks -during boot, and the one requirement it places on the utterance -lifecycle (OVOS-PIPELINE-1 §6/§9.1): an utterance that arrives before -the orchestrator is ready **MUST NOT** enter the pipeline loop at all. +This document defines a **readiness state** the orchestrator tracks, +and the one requirement it places on the utterance lifecycle +(OVOS-PIPELINE-1 §6/§9.1): an utterance that arrives while the +orchestrator is not ready **MUST NOT** enter the pipeline loop at all. It builds on OVOS-PIPELINE-1 (the utterance lifecycle and pipeline plugin abstraction this spec gates) and OVOS-MSG-1 (the envelope and @@ -14,83 +14,70 @@ session carrier its new topic travels in). The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED** and **MAY** are used as in RFC 2119. -## 1. Motivation - -Right after a restart, while plugins are still loading languages, -training models and warming caches, an utterance can legally reach a -pipeline whose `match` (OVOS-PIPELINE-1 §4) call exceeds its bound -(§4.4) purely from contention, not from the utterance being -unmatchable. The call is declined as designed, and the loop falls -through to the next pipeline — which, for a mid-conversation -utterance like a bare "yes", can match an unrelated literal entity -instead of continuing the conversation it belonged to. Observed in -production: OpenVoiceOS/ovos-core#1027. - -Raising the bound only delays the same race; it does not remove the -window one utterance can land in. Nor is the orchestrator's own -process status the fix: in the production occurrence above, the -process had already reported ready for over twenty seconds before the -misroute, while the pipeline that should have handled the utterance -was still "compiling in background, serving its last compiled state" -— on a process that had never finished a first compile, there was no -prior state to serve. The pipeline plugin itself already knows this -distinction internally; this specification makes it observable and -gates the lifecycle on the aggregate of every plugin's own state, -instead of asking every pipeline's bound to absorb boot contention it -was never sized for, or conflating process status with plugin -readiness. +## 1. Scope + +This specification defines: + +- the **readiness state** (§2) — `true` once every pipeline plugin + that reports one is warm, `false` otherwise; +- the `ovos.ready` **namespace** (§2) — the state topic and the query + a late subscriber uses; +- the **per-plugin warm report** (§2.1) a pipeline plugin emits; +- the **lifecycle gate** (§3) — what the orchestrator does with an + utterance while not ready. + +It does **not** define: + +- **how a pipeline plugin decides it is warm** — implementation- and + plugin-specific (a language's model finishing its first compile, a + cache finishing its first load, or any other plugin-defined + condition); +- **what "ready" means for anything other than the pipeline loop** — + a deployment MAY use `ovos.ready` for other gating, but this + specification only constrains utterance handling. ## 2. The `ovos.ready` namespace -- `ovos.ready.state` (bus message, no payload required) — the - orchestrator's current readiness, emitted on every transition. - `context.ready: bool` carries the state so a late subscriber does - not need a second round-trip to learn it. -- `ovos.ready.query` / `ovos.ready.state` reply — a late subscriber - asks and gets exactly one reply, the same way `mycroft.ready` (now - superseded by this state for the orchestrator's own gating; a - producer **MAY** keep emitting it per VERSIONING.md's one-cycle - removal window) could not be queried after the fact. - -An implementation **MUST** emit `ovos.ready.state` with -`context.ready: false` at orchestrator start, and **MUST** emit it -again, with the new value, on every transition between `false` and -`true` in either direction — not only the first time it becomes -ready. "Ready" is not a one-shot milestone: a skill install, a locale -change, a plugin reload or a dependency outage **MAY** make the -orchestrator unready again after it was ready, and a consumer that -only ever saw one `true` and never looked again would keep routing -into a pipeline that has gone cold. `ovos.ready.query` **MUST** answer -with the current state, not a cached first answer. - -"Ready" is **not** the orchestrator's own process status: a process -can legitimately be alive and answering health checks while a -pipeline plugin it hosts is still mid-boot. Process status is a floor -(an orchestrator that has not started cannot be ready), not the -signal, because a plugin's own loading work routinely outlives it — -see §1's production example, where the orchestrator's process status -was already positive for the entire window the bug occurred in. - -`ovos.ready`'s `context.ready` is instead the aggregate of every -configured pipeline plugin's own **warm** state: a plugin that has -language- or model-dependent boot work (training, prototype loading, -cache warming) reports its own transition from cold to warm on the -bus (a per-plugin topic is implementation-defined; the orchestrator -is the one required subscriber). The orchestrator is ready once every -plugin that reports a warm state has reported warm, for the -deployment's configured language set. A plugin that exposes no such -signal **MUST** be treated as warm by default — this never blocks -readiness on a signal that cannot exist. - -A plugin serving its last-known state while still re-training for a -configuration change it has not finished (common for an incremental -retrain) is warm: it has a genuine prior state to serve. The same -behaviour on a plugin that has **never** finished its first load is -not the same thing — there is no prior state, so "serving the last -compiled state" is serving nothing, or a stale placeholder. A -pipeline **MUST** decline (OVOS-PIPELINE-1 §4.4's bounded-match -timeout, or an explicit no-match) rather than serve partial or stale -state while it has not yet reported warm for the first time. +- `ovos.ready.state` (bus message) — the orchestrator's current + readiness. `context.ready: bool` carries the state. An + implementation **MUST** emit this message with `context.ready: + false` at orchestrator start, and **MUST** emit it again, with the + new value, on every transition between `false` and `true` in + either direction — not only the first time it becomes ready. + Readiness is not a one-shot milestone: an orchestrator **MAY** + become unready again after reporting ready, and **MUST** announce + that too. +- `ovos.ready.query` — a late subscriber asks and gets exactly one + `ovos.ready.state` reply, carrying the current state, never a + cached first answer. + +The orchestrator's readiness is the aggregate of every configured +pipeline plugin's own warm state (§2.1): ready once every plugin that +reports one has reported warm, not ready otherwise. A plugin that +reports no warm state **MUST** be treated as warm by default, so +readiness never blocks on a signal that does not exist. Process +status (whether the orchestrator's process itself has started) is a +necessary floor — an unstarted orchestrator cannot be ready — but is +not sufficient on its own: a process can be running and answering +health checks while a pipeline plugin it hosts has boot work left to +do. + +### 2.1 Per-plugin warm report + +A pipeline plugin with boot work that can leave it serving a stale or +partial answer (a language- or model-dependent compile, a cache that +has not finished its first load) reports its own cold-to-warm +transition to the orchestrator; the specific topic is +implementation-defined between a plugin and its orchestrator. A +plugin that has reported warm once **MUST NOT** report cold again +merely because it has later boot work to redo (an incremental +retrain, a configuration change): it already has a genuine prior +state to serve, which is the condition this report exists to +distinguish FROM. A plugin that has never reported warm has no prior +state at all, and **MUST NOT** answer a `match` (OVOS-PIPELINE-1 §4) +call with a stale or partial result in that state — it **MUST** +decline instead (§4.4's bounded-match timeout, or an explicit +no-match). ## 3. Gating the utterance lifecycle @@ -122,9 +109,10 @@ A conforming orchestrator: - resumes ordinary pipeline matching the moment it reports ready, with no residual gating once `context.ready` is `true`. ---- +A conforming pipeline plugin that has a warm state (§2.1): -Proposed alongside a reference implementation in ovos-core (a boot- -state check early in `handle_utterance`, gated on this same signal) -and measured on a production 11-language hub: see the linked PR/issue -discussion. +- reports cold before it reports warm for the first time; +- reports warm exactly once for its first readiness, and never + reports cold again afterward; +- does not answer a `match` call with stale or partial state before + its first warm report. From 1c3f41b18ce8066029b803732c499219f9f76492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Trellu?= Date: Tue, 13 Oct 2026 13:01:31 -0400 Subject: [PATCH 04/11] spec(ready-1): allow an error sound when no localized dialog exists; other subsystems MAY join the readiness aggregate JarbasAl, two review comments: - the decline-with-dialog step MAY play a non-verbal error sound instead of a dialog in the wrong language, when no localized string exists for the resolved language; implementation-defined which. - the readiness aggregate is pipeline plugins at minimum; an implementation MAY fold other subsystems it depends on (audio output, a listener, a GUI surface) into the same ovos.ready aggregate. This specification itself requires only pipeline plugins. Co-Authored-By: Claude Opus 5.5 --- ready-1.md | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/ready-1.md b/ready-1.md index 253667ee..34ffdc5b 100644 --- a/ready-1.md +++ b/ready-1.md @@ -51,16 +51,22 @@ It does **not** define: `ovos.ready.state` reply, carrying the current state, never a cached first answer. -The orchestrator's readiness is the aggregate of every configured -pipeline plugin's own warm state (§2.1): ready once every plugin that -reports one has reported warm, not ready otherwise. A plugin that -reports no warm state **MUST** be treated as warm by default, so -readiness never blocks on a signal that does not exist. Process -status (whether the orchestrator's process itself has started) is a -necessary floor — an unstarted orchestrator cannot be ready — but is -not sufficient on its own: a process can be running and answering -health checks while a pipeline plugin it hosts has boot work left to -do. +The orchestrator's readiness is **at least** the aggregate of every +configured pipeline plugin's own warm state (§2.1): ready once every +plugin that reports one has reported warm, not ready otherwise. A +plugin that reports no warm state **MUST** be treated as warm by +default, so readiness never blocks on a signal that does not exist. +Process status (whether the orchestrator's process itself has +started) is a necessary floor — an unstarted orchestrator cannot be +ready — but is not sufficient on its own: a process can be running +and answering health checks while a pipeline plugin it hosts has boot +work left to do. + +An implementation **MAY** fold other subsystems it depends on to +serve a request correctly (audio output, a listener, a GUI surface) +into the same aggregate, so that `ovos.ready` does not report ready +while one of those is still unavailable. Pipeline plugins are the +only subsystem this specification itself requires in the aggregate. ### 2.1 Per-plugin warm report @@ -88,7 +94,10 @@ for an incoming `ovos.utterance.handle` (§9.1). It **MUST** instead: 1. speak a short, localized "still starting" response on `ovos.utterance.speak` (§9.6), in the utterance's language where a translation exists, falling back to the deployment default - language otherwise; + language otherwise; an implementation that has no localized string + at all for the resolved language **MAY** play a non-verbal error + sound instead of a dialog in the wrong language -- the choice + between the two is implementation-defined; 2. emit `ovos.utterance.handled` as it would for a normal utterance, so a caller does not wait on a terminal event that never arrives; 3. do none of the pipeline-loop side effects (§8's handler-lifecycle From cfd82e6d750f89b9955b51e948a00140582916eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Trellu?= Date: Tue, 13 Oct 2026 13:13:04 -0400 Subject: [PATCH 05/11] spec(ready-1): trim the per-plugin section, exact dialog-language rule, a stable error identifier JarbasAl, three more threads: - dropped the Scope bullet that only said 'we don't specify how a plugin decides it's warm' -- not worth a non-specification; trimmed 2.1 to the plain requirement (report cold once, warm once, no regression for later work the plugin still answers correctly through). - the lifecycle gate now says exactly what he asked: MAY respond with a localized dialog or an error sound; if a dialog is spoken it MUST be localized into the session's own language, not a deployment default in the wrong language. - added a stable error identifier (ovos.ready.not_ready) carried in context.error on whichever response the gate sent, so a listener can tell this reply apart from an ordinary one. Co-Authored-By: Claude Opus 5.5 --- ready-1.md | 57 ++++++++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/ready-1.md b/ready-1.md index 34ffdc5b..1502d002 100644 --- a/ready-1.md +++ b/ready-1.md @@ -26,15 +26,9 @@ This specification defines: - the **lifecycle gate** (§3) — what the orchestrator does with an utterance while not ready. -It does **not** define: - -- **how a pipeline plugin decides it is warm** — implementation- and - plugin-specific (a language's model finishing its first compile, a - cache finishing its first load, or any other plugin-defined - condition); -- **what "ready" means for anything other than the pipeline loop** — - a deployment MAY use `ovos.ready` for other gating, but this - specification only constrains utterance handling. +It does **not** define **what "ready" means for anything other than +the pipeline loop** — a deployment MAY use `ovos.ready` for other +gating, but this specification only constrains utterance handling. ## 2. The `ovos.ready` namespace @@ -70,20 +64,15 @@ only subsystem this specification itself requires in the aggregate. ### 2.1 Per-plugin warm report -A pipeline plugin with boot work that can leave it serving a stale or -partial answer (a language- or model-dependent compile, a cache that -has not finished its first load) reports its own cold-to-warm -transition to the orchestrator; the specific topic is -implementation-defined between a plugin and its orchestrator. A -plugin that has reported warm once **MUST NOT** report cold again -merely because it has later boot work to redo (an incremental -retrain, a configuration change): it already has a genuine prior -state to serve, which is the condition this report exists to -distinguish FROM. A plugin that has never reported warm has no prior -state at all, and **MUST NOT** answer a `match` (OVOS-PIPELINE-1 §4) -call with a stale or partial result in that state — it **MUST** -decline instead (§4.4's bounded-match timeout, or an explicit -no-match). +A pipeline plugin reports its own cold-to-warm transition to the +orchestrator once it has finished enough of its own loading to answer +a `match` (OVOS-PIPELINE-1 §4) call correctly, rather than with a +stale or partial result; the specific topic is implementation-defined +between a plugin and its orchestrator. Readiness, once reported, does +not regress for later work the plugin does on its own schedule (a +retrain, a configuration change) while still able to answer correctly +from what it already has: a plugin reports cold once, before its +first warm report, and warm exactly once after that. ## 3. Gating the utterance lifecycle @@ -91,16 +80,20 @@ While `ovos.ready.state`'s `context.ready` is `false`, the orchestrator **MUST NOT** run the pipeline loop (OVOS-PIPELINE-1 §6) for an incoming `ovos.utterance.handle` (§9.1). It **MUST** instead: -1. speak a short, localized "still starting" response on - `ovos.utterance.speak` (§9.6), in the utterance's language where a - translation exists, falling back to the deployment default - language otherwise; an implementation that has no localized string - at all for the resolved language **MAY** play a non-verbal error - sound instead of a dialog in the wrong language -- the choice - between the two is implementation-defined; -2. emit `ovos.utterance.handled` as it would for a normal utterance, +1. respond with either a short "still starting" dialog on + `ovos.utterance.speak` (§9.6) or a non-verbal error sound — the + choice is implementation-defined. If a dialog is spoken, it + **MUST** be localized into the session's own language (OVOS- + SESSION-1); an implementation that has no such localization + **MUST** play the error sound instead, never a dialog in the + wrong language; +2. carry a stable error identifier, `ovos.ready.not_ready`, in + `context.error` on whichever response §1 sent, so a listener on + either topic can tell this reply apart from an ordinary one and + knows why it was declined; +3. emit `ovos.utterance.handled` as it would for a normal utterance, so a caller does not wait on a terminal event that never arrives; -3. do none of the pipeline-loop side effects (§8's handler-lifecycle +4. do none of the pipeline-loop side effects (§8's handler-lifecycle trio, `ovos.intent.matched` / `.unmatched`): none of them ran. This is a **decline-with-dialog**, not a silent drop and not an From bdf582ec43fb41d5e04c6d695d36cf301b093067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Trellu?= Date: Tue, 13 Oct 2026 17:54:07 -0400 Subject: [PATCH 06/11] spec(ready-1): readiness also requires the skills the orchestrator routes to to be registered Co-Authored-By: Claude Code --- ready-1.md | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/ready-1.md b/ready-1.md index 1502d002..5f347556 100644 --- a/ready-1.md +++ b/ready-1.md @@ -19,7 +19,8 @@ The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, This specification defines: - the **readiness state** (§2) — `true` once every pipeline plugin - that reports one is warm, `false` otherwise; + that reports one is warm and the skills the orchestrator routes to + are registered, `false` otherwise; - the `ovos.ready` **namespace** (§2) — the state topic and the query a late subscriber uses; - the **per-plugin warm report** (§2.1) a pipeline plugin emits; @@ -45,11 +46,13 @@ gating, but this specification only constrains utterance handling. `ovos.ready.state` reply, carrying the current state, never a cached first answer. -The orchestrator's readiness is **at least** the aggregate of every -configured pipeline plugin's own warm state (§2.1): ready once every -plugin that reports one has reported warm, not ready otherwise. A -plugin that reports no warm state **MUST** be treated as warm by -default, so readiness never blocks on a signal that does not exist. +The orchestrator's readiness is **at least** the aggregate of two +things: every configured pipeline plugin's own warm state (§2.1), and +the registration of the skills it routes to (§2.2). It is ready once +every plugin that reports a warm state has reported warm and the +skills are registered, not ready otherwise. A plugin that reports no +warm state **MUST** be treated as warm by default, so readiness never +blocks on a signal that does not exist. Process status (whether the orchestrator's process itself has started) is a necessary floor — an unstarted orchestrator cannot be ready — but is not sufficient on its own: a process can be running @@ -59,8 +62,9 @@ work left to do. An implementation **MAY** fold other subsystems it depends on to serve a request correctly (audio output, a listener, a GUI surface) into the same aggregate, so that `ovos.ready` does not report ready -while one of those is still unavailable. Pipeline plugins are the -only subsystem this specification itself requires in the aggregate. +while one of those is still unavailable. Pipeline plugins and skill +registration are the only subsystems this specification itself +requires in the aggregate. ### 2.1 Per-plugin warm report @@ -74,6 +78,27 @@ retrain, a configuration change) while still able to answer correctly from what it already has: a plugin reports cold once, before its first warm report, and warm exactly once after that. +### 2.2 Skill registration + +Warm pipelines are not enough. A pipeline can answer correctly from +what is registered, and an utterance that belongs to a skill which has +not registered yet is matched to whatever else is registered, or to +nothing. The orchestrator therefore **MUST NOT** report ready until +the skills it will route to have been registered: that is, until the +component that loads skills has finished its first load and signalled +so. The signal is implementation-defined between the orchestrator and +that component. + +Only the **first** load counts. Once the orchestrator has seen the +first load complete, a skill that is installed, upgraded or reloaded +later **MUST NOT** make the orchestrator unready: that skill's +registration is the loading component's own business, and refusing +every utterance for one skill would cost far more than the one +utterance it could misroute. An orchestrator that starts after the +loading component has already finished **MUST** be able to learn that +(by asking it), and **MUST NOT** wait for a signal that was sent +before it was listening. + ## 3. Gating the utterance lifecycle While `ovos.ready.state`'s `context.ready` is `false`, the @@ -106,6 +131,9 @@ A conforming orchestrator: - emits `ovos.ready.state` on every readiness transition (§2); - answers `ovos.ready.query` with the current state; +- does not report ready before the first skill load is complete, and + does not report unready again for a later skill load or reload + (§2.2); - never runs the pipeline loop while unready, substituting the decline-with-dialog of §3; - resumes ordinary pipeline matching the moment it reports ready, From e2fa5ed540ba280a171c9e5bd5b56ef487093c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Trellu?= Date: Tue, 13 Oct 2026 18:10:51 -0400 Subject: [PATCH 07/11] spec(ready-1): a plugin may report loss of readiness; name the response-language rule; fix the step reference Co-Authored-By: Claude Code --- ready-1.md | 59 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/ready-1.md b/ready-1.md index 5f347556..944719a2 100644 --- a/ready-1.md +++ b/ready-1.md @@ -72,11 +72,18 @@ A pipeline plugin reports its own cold-to-warm transition to the orchestrator once it has finished enough of its own loading to answer a `match` (OVOS-PIPELINE-1 §4) call correctly, rather than with a stale or partial result; the specific topic is implementation-defined -between a plugin and its orchestrator. Readiness, once reported, does -not regress for later work the plugin does on its own schedule (a -retrain, a configuration change) while still able to answer correctly -from what it already has: a plugin reports cold once, before its -first warm report, and warm exactly once after that. +between a plugin and its orchestrator. + +A plugin reports cold at start, and warm when it can answer. It +**MAY** report cold again at any later time, and **MUST** do so when +it can no longer answer a `match` call correctly (its model or index +was discarded, say, or it is serving partial state it cannot vouch +for); it **MUST** report warm again once it can. Later work that +leaves the plugin able to answer correctly from what it already has +(a retrain in the background, a configuration change) is not a loss of +readiness and **MUST NOT** be reported as one. The orchestrator +**MUST** treat a cold report at any time as not ready (§2) and stop +running the pipeline loop until the plugin reports warm again. ### 2.2 Skill registration @@ -89,12 +96,18 @@ component that loads skills has finished its first load and signalled so. The signal is implementation-defined between the orchestrator and that component. -Only the **first** load counts. Once the orchestrator has seen the -first load complete, a skill that is installed, upgraded or reloaded -later **MUST NOT** make the orchestrator unready: that skill's -registration is the loading component's own business, and refusing -every utterance for one skill would cost far more than the one -utterance it could misroute. An orchestrator that starts after the +The same test applies here as in §2.1: readiness is lost when the +orchestrator can no longer answer correctly, not when one part of it +is busy. Once the orchestrator has seen the first load complete, a +skill that is installed, upgraded or reloaded later **MUST NOT** make +the orchestrator unready: the other skills still answer correctly, +that one skill's registration is the loading component's own +business, and refusing every utterance for one skill would cost far +more than the one utterance it could misroute. If the loading +component reports that it has lost its registrations as a whole (it +restarted and the skills are gone), that is a loss of readiness and +the orchestrator **MAY** treat it as one until the first load +completes again. An orchestrator that starts after the loading component has already finished **MUST** be able to learn that (by asking it), and **MUST NOT** wait for a signal that was sent before it was listening. @@ -108,12 +121,15 @@ for an incoming `ovos.utterance.handle` (§9.1). It **MUST** instead: 1. respond with either a short "still starting" dialog on `ovos.utterance.speak` (§9.6) or a non-verbal error sound — the choice is implementation-defined. If a dialog is spoken, it - **MUST** be localized into the session's own language (OVOS- - SESSION-1); an implementation that has no such localization - **MUST** play the error sound instead, never a dialog in the - wrong language; + **MUST** be rendered in the language OVOS-SESSION-1 §3.2.3 gives + for responses: `output_lang` when the session sets it, otherwise + the utterance language resolved at intake (§3.2.7). When there is + no such localization for `output_lang`, it **MAY** fall back to + the input-side language as §3.2.3 allows. An implementation that + has no localization in either **MUST** play the error sound + instead, never a dialog in some other language; 2. carry a stable error identifier, `ovos.ready.not_ready`, in - `context.error` on whichever response §1 sent, so a listener on + `context.error` on whichever response step 1 sent, so a listener on either topic can tell this reply apart from an ordinary one and knows why it was declined; 3. emit `ovos.utterance.handled` as it would for a normal utterance, @@ -142,7 +158,10 @@ A conforming orchestrator: A conforming pipeline plugin that has a warm state (§2.1): - reports cold before it reports warm for the first time; -- reports warm exactly once for its first readiness, and never - reports cold again afterward; -- does not answer a `match` call with stale or partial state before - its first warm report. +- reports warm when it can answer a `match` call correctly; +- reports cold whenever it can no longer answer correctly, and warm + again once it can (§2.1); +- does not report cold for background work that leaves it able to + answer correctly; +- does not answer a `match` call with stale or partial state while + its last report is cold, or before its first warm report. From 7604b9ef8a96dda2fbd68a879c92743ed143b265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Trellu?= Date: Wed, 14 Oct 2026 09:12:54 -0400 Subject: [PATCH 08/11] spec(ready-1): ready is the orchestrator's ProcessState.READY; pipelines are loading or loaded Co-Authored-By: Claude Sonnet 5.5 --- ready-1.md | 187 ++++++++++++++++++++--------------------------------- 1 file changed, 69 insertions(+), 118 deletions(-) diff --git a/ready-1.md b/ready-1.md index 944719a2..584dc5ca 100644 --- a/ready-1.md +++ b/ready-1.md @@ -2,10 +2,10 @@ **Spec ID:** OVOS-READY-1 · **Version:** 1 · **Status:** Draft -This document defines a **readiness state** the orchestrator tracks, -and the one requirement it places on the utterance lifecycle -(OVOS-PIPELINE-1 §6/§9.1): an utterance that arrives while the -orchestrator is not ready **MUST NOT** enter the pipeline loop at all. +This document defines when the orchestrator is ready, and the one +requirement it places on the utterance lifecycle (OVOS-PIPELINE-1 +§6/§9.1): an utterance that arrives before the orchestrator can answer +**MUST NOT** enter the pipeline loop at all. It builds on OVOS-PIPELINE-1 (the utterance lifecycle and pipeline plugin abstraction this spec gates) and OVOS-MSG-1 (the envelope and @@ -18,104 +18,63 @@ The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, This specification defines: -- the **readiness state** (§2) — `true` once every pipeline plugin - that reports one is warm and the skills the orchestrator routes to - are registered, `false` otherwise; -- the `ovos.ready` **namespace** (§2) — the state topic and the query - a late subscriber uses; -- the **per-plugin warm report** (§2.1) a pipeline plugin emits; -- the **lifecycle gate** (§3) — what the orchestrator does with an - utterance while not ready. - -It does **not** define **what "ready" means for anything other than -the pipeline loop** — a deployment MAY use `ovos.ready` for other -gating, but this specification only constrains utterance handling. - -## 2. The `ovos.ready` namespace - -- `ovos.ready.state` (bus message) — the orchestrator's current - readiness. `context.ready: bool` carries the state. An - implementation **MUST** emit this message with `context.ready: - false` at orchestrator start, and **MUST** emit it again, with the - new value, on every transition between `false` and `true` in - either direction — not only the first time it becomes ready. - Readiness is not a one-shot milestone: an orchestrator **MAY** - become unready again after reporting ready, and **MUST** announce - that too. -- `ovos.ready.query` — a late subscriber asks and gets exactly one - `ovos.ready.state` reply, carrying the current state, never a - cached first answer. - -The orchestrator's readiness is **at least** the aggregate of two -things: every configured pipeline plugin's own warm state (§2.1), and -the registration of the skills it routes to (§2.2). It is ready once -every plugin that reports a warm state has reported warm and the -skills are registered, not ready otherwise. A plugin that reports no -warm state **MUST** be treated as warm by default, so readiness never -blocks on a signal that does not exist. -Process status (whether the orchestrator's process itself has -started) is a necessary floor — an unstarted orchestrator cannot be -ready — but is not sufficient on its own: a process can be running -and answering health checks while a pipeline plugin it hosts has boot -work left to do. - -An implementation **MAY** fold other subsystems it depends on to -serve a request correctly (audio output, a listener, a GUI surface) -into the same aggregate, so that `ovos.ready` does not report ready -while one of those is still unavailable. Pipeline plugins and skill -registration are the only subsystems this specification itself -requires in the aggregate. - -### 2.1 Per-plugin warm report - -A pipeline plugin reports its own cold-to-warm transition to the -orchestrator once it has finished enough of its own loading to answer -a `match` (OVOS-PIPELINE-1 §4) call correctly, rather than with a -stale or partial result; the specific topic is implementation-defined -between a plugin and its orchestrator. - -A plugin reports cold at start, and warm when it can answer. It -**MAY** report cold again at any later time, and **MUST** do so when -it can no longer answer a `match` call correctly (its model or index -was discarded, say, or it is serving partial state it cannot vouch -for); it **MUST** report warm again once it can. Later work that -leaves the plugin able to answer correctly from what it already has -(a retrain in the background, a configuration change) is not a loss of -readiness and **MUST NOT** be reported as one. The orchestrator -**MUST** treat a cold report at any time as not ready (§2) and stop -running the pipeline loop until the plugin reports warm again. - -### 2.2 Skill registration - -Warm pipelines are not enough. A pipeline can answer correctly from -what is registered, and an utterance that belongs to a skill which has -not registered yet is matched to whatever else is registered, or to -nothing. The orchestrator therefore **MUST NOT** report ready until -the skills it will route to have been registered: that is, until the -component that loads skills has finished its first load and signalled -so. The signal is implementation-defined between the orchestrator and -that component. - -The same test applies here as in §2.1: readiness is lost when the -orchestrator can no longer answer correctly, not when one part of it -is busy. Once the orchestrator has seen the first load complete, a -skill that is installed, upgraded or reloaded later **MUST NOT** make -the orchestrator unready: the other skills still answer correctly, -that one skill's registration is the loading component's own -business, and refusing every utterance for one skill would cost far -more than the one utterance it could misroute. If the loading -component reports that it has lost its registrations as a whole (it -restarted and the skills are gone), that is a loss of readiness and -the orchestrator **MAY** treat it as one until the first load -completes again. An orchestrator that starts after the -loading component has already finished **MUST** be able to learn that -(by asking it), and **MUST NOT** wait for a signal that was sent -before it was listening. +- what **ready** means for the orchestrator (§2): its process status + reaches `READY` once every configured pipeline plugin is loaded; +- how a pipeline plugin that loads in the background says so (§2.1); +- the skills condition on answering (§2.2); +- the **lifecycle gate** (§3), what the orchestrator does with an + utterance until it can answer. + +## 2. Ready + +The orchestrator's process status (`ProcessState.READY`, queried with +`mycroft..is_ready`) **MUST NOT** reach `READY` until every +configured pipeline plugin is loaded. A pipeline is either loading or +loaded; there is no third state. The status is the readiness signal: +this specification adds no topic of its own for it. + +A component that cannot answer yet **MUST NOT** report `READY` to +make another component's start-up simpler. In particular, the work a +pipeline needs from skills (compiling their registrations) cannot +count as the pipeline loading before `READY`, because skills are not +started until the orchestrator is `READY`; that work belongs to §2.2. + +### 2.1 Pipeline loading + +A pipeline plugin that finishes loading after its constructor returns +(a model, an index, a compile) announces `ovos.pipeline.loading` +(`data.pipeline_id`) when that work begins and `ovos.pipeline.loaded` +(`data.pipeline_id`) when it ends. The orchestrator **MUST** hold +`READY` back while a pipeline announced before the plugin load step +ended is still loading. A plugin that loads synchronously announces +nothing and counts as loaded. A plugin **MUST** announce `loaded` +when its work ends for any reason, failure included: a failed load is +not a load in progress. + +A plugin that announces `loading` for work caused by a registration +(retraining after a skill registered) is not holding back `READY` +with it, because that announcement arrives after `READY`; it is read +by §2.2. + +### 2.2 Skills + +An utterance that belongs to a skill which has not registered yet, or +whose registration a pipeline has not absorbed yet, is matched to +whatever else is registered. The orchestrator therefore **MUST NOT** +answer utterances until the component that loads skills has finished +its first load (signalled by `mycroft.skills.initialized`, or by the +skills status being `READY`) and no pipeline is loading. + +This holds once, at first start. A skill installed, upgraded or +reloaded later, or a pipeline that retrains for it, **MUST NOT** stop +the orchestrator answering. An orchestrator that starts after the +skill component finished **MUST** ask it (`mycroft.skills.is_ready`) +rather than wait for a signal sent before it was listening. A +deployment with no skill component MAY turn this condition off. ## 3. Gating the utterance lifecycle -While `ovos.ready.state`'s `context.ready` is `false`, the -orchestrator **MUST NOT** run the pipeline loop (OVOS-PIPELINE-1 §6) +Until the orchestrator is `READY` and §2.2 is satisfied, it **MUST NOT** run the pipeline loop (OVOS-PIPELINE-1 §6) for an incoming `ovos.utterance.handle` (§9.1). It **MUST** instead: 1. respond with either a short "still starting" dialog on @@ -145,23 +104,15 @@ rather than guessing from silence or risking a wrong match. A conforming orchestrator: -- emits `ovos.ready.state` on every readiness transition (§2); -- answers `ovos.ready.query` with the current state; -- does not report ready before the first skill load is complete, and - does not report unready again for a later skill load or reload - (§2.2); -- never runs the pipeline loop while unready, substituting the +- does not report `READY` while a pipeline plugin it loads is loading + (§2, §2.1); +- does not answer before the first skill load is complete and no + pipeline is loading, and does not stop answering for a later skill + load or retrain (§2.2); +- never runs the pipeline loop before then, substituting the decline-with-dialog of §3; -- resumes ordinary pipeline matching the moment it reports ready, - with no residual gating once `context.ready` is `true`. - -A conforming pipeline plugin that has a warm state (§2.1): - -- reports cold before it reports warm for the first time; -- reports warm when it can answer a `match` call correctly; -- reports cold whenever it can no longer answer correctly, and warm - again once it can (§2.1); -- does not report cold for background work that leaves it able to - answer correctly; -- does not answer a `match` call with stale or partial state while - its last report is cold, or before its first warm report. +- resumes ordinary pipeline matching the moment it can answer. + +A conforming pipeline plugin that loads in the background announces +`ovos.pipeline.loading` and `ovos.pipeline.loaded` (§2.1), and does +not answer a `match` call with partial state while loading. From 292bf574aa2a2b98c8b17a1836a52f136ed12711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Trellu?= Date: Wed, 14 Oct 2026 09:13:51 -0400 Subject: [PATCH 09/11] spec(ready-1): keep the text prescriptive Co-Authored-By: Claude Sonnet 5.5 --- ready-1.md | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/ready-1.md b/ready-1.md index 584dc5ca..8228f4be 100644 --- a/ready-1.md +++ b/ready-1.md @@ -27,17 +27,14 @@ This specification defines: ## 2. Ready -The orchestrator's process status (`ProcessState.READY`, queried with -`mycroft..is_ready`) **MUST NOT** reach `READY` until every +The orchestrator's process status (`ProcessState.READY`) **MUST NOT** reach `READY` until every configured pipeline plugin is loaded. A pipeline is either loading or loaded; there is no third state. The status is the readiness signal: this specification adds no topic of its own for it. -A component that cannot answer yet **MUST NOT** report `READY` to -make another component's start-up simpler. In particular, the work a -pipeline needs from skills (compiling their registrations) cannot -count as the pipeline loading before `READY`, because skills are not -started until the orchestrator is `READY`; that work belongs to §2.2. +Work that depends on skills, such as compiling their registrations, +is not part of loading: skills start only after the orchestrator is +`READY`. It is covered by §2.2. ### 2.1 Pipeline loading @@ -51,19 +48,14 @@ nothing and counts as loaded. A plugin **MUST** announce `loaded` when its work ends for any reason, failure included: a failed load is not a load in progress. -A plugin that announces `loading` for work caused by a registration -(retraining after a skill registered) is not holding back `READY` -with it, because that announcement arrives after `READY`; it is read -by §2.2. - ### 2.2 Skills An utterance that belongs to a skill which has not registered yet, or whose registration a pipeline has not absorbed yet, is matched to whatever else is registered. The orchestrator therefore **MUST NOT** answer utterances until the component that loads skills has finished -its first load (signalled by `mycroft.skills.initialized`, or by the -skills status being `READY`) and no pipeline is loading. +its first load (signalled by the skill component, for example +`mycroft.skills.initialized`) and no pipeline is loading. This holds once, at first start. A skill installed, upgraded or reloaded later, or a pipeline that retrains for it, **MUST NOT** stop From 72fd6a61ab02296f53b5069940135777409b1076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Trellu?= Date: Wed, 14 Oct 2026 09:24:11 -0400 Subject: [PATCH 10/11] spec(ready-1): describe the abstract system only; pipelines are loaded or loading Co-Authored-By: Claude Sonnet 5.5 --- ready-1.md | 146 +++++++++++++++++++++++++---------------------------- 1 file changed, 70 insertions(+), 76 deletions(-) diff --git a/ready-1.md b/ready-1.md index 8228f4be..fd717b04 100644 --- a/ready-1.md +++ b/ready-1.md @@ -8,8 +8,7 @@ requirement it places on the utterance lifecycle (OVOS-PIPELINE-1 **MUST NOT** enter the pipeline loop at all. It builds on OVOS-PIPELINE-1 (the utterance lifecycle and pipeline -plugin abstraction this spec gates) and OVOS-MSG-1 (the envelope and -session carrier its new topic travels in). +plugin abstraction this spec gates). The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED** and **MAY** are used as in RFC 2119. @@ -18,93 +17,88 @@ The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, This specification defines: -- what **ready** means for the orchestrator (§2): its process status - reaches `READY` once every configured pipeline plugin is loaded; -- how a pipeline plugin that loads in the background says so (§2.1); -- the skills condition on answering (§2.2); -- the **lifecycle gate** (§3), what the orchestrator does with an - utterance until it can answer. +- when the orchestrator is **ready** (§2): every configured pipeline + is able to match intents, and the skills have been registered; +- the **lifecycle gate** (§3): what the orchestrator does with an + utterance until it is ready. ## 2. Ready -The orchestrator's process status (`ProcessState.READY`) **MUST NOT** reach `READY` until every -configured pipeline plugin is loaded. A pipeline is either loading or -loaded; there is no third state. The status is the readiness signal: -this specification adds no topic of its own for it. - -Work that depends on skills, such as compiling their registrations, -is not part of loading: skills start only after the orchestrator is -`READY`. It is covered by §2.2. - -### 2.1 Pipeline loading - -A pipeline plugin that finishes loading after its constructor returns -(a model, an index, a compile) announces `ovos.pipeline.loading` -(`data.pipeline_id`) when that work begins and `ovos.pipeline.loaded` -(`data.pipeline_id`) when it ends. The orchestrator **MUST** hold -`READY` back while a pipeline announced before the plugin load step -ended is still loading. A plugin that loads synchronously announces -nothing and counts as loaded. A plugin **MUST** announce `loaded` -when its work ends for any reason, failure included: a failed load is -not a load in progress. - -### 2.2 Skills - -An utterance that belongs to a skill which has not registered yet, or -whose registration a pipeline has not absorbed yet, is matched to -whatever else is registered. The orchestrator therefore **MUST NOT** -answer utterances until the component that loads skills has finished -its first load (signalled by the skill component, for example -`mycroft.skills.initialized`) and no pipeline is loading. - -This holds once, at first start. A skill installed, upgraded or -reloaded later, or a pipeline that retrains for it, **MUST NOT** stop -the orchestrator answering. An orchestrator that starts after the -skill component finished **MUST** ask it (`mycroft.skills.is_ready`) -rather than wait for a signal sent before it was listening. A -deployment with no skill component MAY turn this condition off. +A pipeline is either **loading** or **loaded**. It is loaded when it +is able to match intents against what is registered with it: its +models, indexes and any other state it needs to answer a `match` +(OVOS-PIPELINE-1 §4) call are in place. It does not stop being loaded +because more intents are registered later; it keeps serving what it +had and gains the new intents when it has absorbed them. + +The orchestrator is **ready** once every configured pipeline is +loaded and the skills it routes to have been registered (§2.2). +Readiness is reached once and is not lost: a pipeline gaining intents +or a skill being installed later does not make the orchestrator +unready. + +A pipeline that fails to load does not hold readiness back for ever. +It cannot match, so the orchestrator treats it as not configured and +reports the failure the way it reports any other error; nothing more +needs to be specified here, and no further signal is defined. + +An implementation **MAY** consider itself not ready until other +subsystems it depends on to serve a request correctly (audio output, +a listener, a GUI surface) are ready too. + +### 2.1 Skills + +An utterance that belongs to a skill that has not registered yet is +matched to whatever else is registered, or to nothing. The orchestrator +therefore **MUST NOT** be ready before the component that loads skills +has finished its first load. How the orchestrator learns this is +implementation-defined, but it **MUST** be able to learn it when it +starts after the first load already finished, rather than wait for a +notification sent before it was listening. + +Only the first load counts. A skill installed, upgraded or reloaded +afterwards **MUST NOT** make the orchestrator unready. + +A deployment with no component that loads skills has nothing to wait +for here. ## 3. Gating the utterance lifecycle -Until the orchestrator is `READY` and §2.2 is satisfied, it **MUST NOT** run the pipeline loop (OVOS-PIPELINE-1 §6) -for an incoming `ovos.utterance.handle` (§9.1). It **MUST** instead: +Until the orchestrator is ready (§2), it **MUST NOT** run the pipeline +loop (OVOS-PIPELINE-1 §6) for an incoming `ovos.utterance.handle` +(§9.1). It **MUST** instead: 1. respond with either a short "still starting" dialog on - `ovos.utterance.speak` (§9.6) or a non-verbal error sound — the - choice is implementation-defined. If a dialog is spoken, it - **MUST** be rendered in the language OVOS-SESSION-1 §3.2.3 gives - for responses: `output_lang` when the session sets it, otherwise - the utterance language resolved at intake (§3.2.7). When there is - no such localization for `output_lang`, it **MAY** fall back to - the input-side language as §3.2.3 allows. An implementation that - has no localization in either **MUST** play the error sound - instead, never a dialog in some other language; -2. carry a stable error identifier, `ovos.ready.not_ready`, in - `context.error` on whichever response step 1 sent, so a listener on - either topic can tell this reply apart from an ordinary one and - knows why it was declined; + `ovos.utterance.speak` (§9.6) or a non-verbal error sound; the + choice is implementation-defined. A spoken dialog **MUST** be + rendered in the language OVOS-SESSION-1 §3.2.3 gives for + responses: `output_lang` when the session sets it, otherwise the + utterance language resolved at intake (§3.2.7). An implementation + with no localization for that language **MUST** play the error + sound instead, never a dialog in some other language; +2. carry the error identifier `ovos.ready.not_ready` in + `context.error` of whichever response step 1 sent, so a listener + can tell this reply apart from an ordinary one and knows why it was + declined; 3. emit `ovos.utterance.handled` as it would for a normal utterance, so a caller does not wait on a terminal event that never arrives; -4. do none of the pipeline-loop side effects (§8's handler-lifecycle - trio, `ovos.intent.matched` / `.unmatched`): none of them ran. +4. produce none of the pipeline-loop side effects (§8's + handler-lifecycle trio, `ovos.intent.matched` / `.unmatched`): none + of them ran. -This is a **decline-with-dialog**, not a silent drop and not an -error: a caller gets a prompt response either way, and it says why, -rather than guessing from silence or risking a wrong match. +A caller gets a prompt response either way, and it says why, rather +than guessing from silence or risking a wrong match. ## 4. Conformance A conforming orchestrator: -- does not report `READY` while a pipeline plugin it loads is loading - (§2, §2.1); -- does not answer before the first skill load is complete and no - pipeline is loading, and does not stop answering for a later skill - load or retrain (§2.2); -- never runs the pipeline loop before then, substituting the - decline-with-dialog of §3; -- resumes ordinary pipeline matching the moment it can answer. - -A conforming pipeline plugin that loads in the background announces -`ovos.pipeline.loading` and `ovos.pipeline.loaded` (§2.1), and does -not answer a `match` call with partial state while loading. +- is not ready while a configured pipeline is loading or before the + first skill load is complete (§2, §2.1); +- once ready, stays ready (§2); +- never runs the pipeline loop while not ready, substituting the + decline of §3; +- resumes ordinary matching as soon as it is ready. + +A conforming pipeline does not answer a `match` call with partial +state while it is loading. From f2e082e9f01605d1db8c223d1a4c7e2a7933aea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Trellu?= Date: Wed, 14 Oct 2026 09:26:23 -0400 Subject: [PATCH 11/11] spec(ready-1): the skills condition covers all initial skills, not the first one Co-Authored-By: Claude Sonnet 5.5 --- ready-1.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/ready-1.md b/ready-1.md index fd717b04..d2660c71 100644 --- a/ready-1.md +++ b/ready-1.md @@ -50,14 +50,15 @@ a listener, a GUI surface) are ready too. An utterance that belongs to a skill that has not registered yet is matched to whatever else is registered, or to nothing. The orchestrator -therefore **MUST NOT** be ready before the component that loads skills -has finished its first load. How the orchestrator learns this is -implementation-defined, but it **MUST** be able to learn it when it -starts after the first load already finished, rather than wait for a -notification sent before it was listening. - -Only the first load counts. A skill installed, upgraded or reloaded -afterwards **MUST NOT** make the orchestrator unready. +therefore **MUST NOT** be ready until every skill the component that +loads skills started with has finished loading. How the orchestrator +learns this is implementation-defined, but it **MUST** be able to +learn it when it starts after that load already finished, rather than +wait for a notification sent before it was listening. + +This applies to the initial set of skills only. A skill installed, +upgraded or reloaded afterwards **MUST NOT** make the orchestrator +unready. A deployment with no component that loads skills has nothing to wait for here. @@ -93,8 +94,8 @@ than guessing from silence or risking a wrong match. A conforming orchestrator: -- is not ready while a configured pipeline is loading or before the - first skill load is complete (§2, §2.1); +- is not ready while a configured pipeline is loading or while any + of the initial skills is still loading (§2, §2.1); - once ready, stays ready (§2); - never runs the pipeline loop while not ready, substituting the decline of §3;