Skip to content

OVOS-PIPELINE-1 — utterance lifecycle and pipeline (draft)#11

Merged
JarbasAl merged 57 commits into
devfrom
spec/pipeline1-utterance-lifecycle
May 26, 2026
Merged

OVOS-PIPELINE-1 — utterance lifecycle and pipeline (draft)#11
JarbasAl merged 57 commits into
devfrom
spec/pipeline1-utterance-lifecycle

Conversation

@JarbasAl
Copy link
Copy Markdown
Member

@JarbasAl JarbasAl commented May 23, 2026

OVOS-PIPELINE-1 — Utterance Lifecycle and Pipeline

Formalises the orchestrator-side utterance lifecycle and the pipeline-plugin machinery.

What the spec defines

  • Utterance lifecycle — from utterance ingestion to the universal end-marker ovos.utterance.handled, with four guaranteed-terminal paths: cancelled, matched, unmatched, matched-with-handler-error.
  • Pipeline-plugin model — a pipeline is an ordered list of pipeline_id strings in session.pipeline; each pipeline_id resolves to a plugin instance offering match(utterance, lang, message) → StageMatch | null. First-match-wins; match is side-effect-free.
  • Utterance argument shape — a non-empty list of candidate strings, possibly the output of the utterance-transformer chain. Plugins MUST accept this shape.
  • Dispatch topic<owner_id>:<intent_name>, where owner_id is polymorphically a skill_id or pipeline_id. Skill IDs, pipeline IDs, and intent names MUST NOT contain :.
  • Orchestrator-stamped context.skill_id — the orchestrator stamps Message.context["skill_id"] from the dispatch topic prefix when dispatching to a skill, so forward/reply derivations carry it structurally.
  • Handler-lifecycle trioovos.intent.handler.start / .complete / .error. The orchestrator owns the trio and wraps every handler invocation; handler code carries no protocol obligation. The orchestrator emits .error on handler timeout.
  • Per-pipeline introspectionovos.pipeline.<pipeline_id>.intents.list / .response. Under a split orchestrator the plugin's hosting process answers.
  • Confidence tiers — high/medium/low convention lets one plugin participate at multiple priority positions.
  • Per-session pipeline configuration via session.pipeline.
  • Conformance — orchestrator, plugin, and transformer roles.

Out of scope

  • Internal behaviours of non-intent stages (converse, fallback, common_query, ocp, persona, stop).
  • How session.pipeline is set or updated.
  • Plugin loading / discovery.
  • ASR n-best ranking semantics within stages.
  • Definition of the audio-input ingestion topic.

Status

Draft (v2).

Summary by CodeRabbit

  • Documentation
    • Added comprehensive specification for the utterance lifecycle and pipeline orchestration system, including plugin contract definitions, session management, dispatcher routing, handler lifecycle events, and bus event specifications.
    • Included conformance requirements for deployments, orchestrators, and pipeline plugins.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d0964d29-f752-4ef5-9c5c-f91c1a82e0af

📥 Commits

Reviewing files that changed from the base of the PR and between 2822531 and b7579a8.

📒 Files selected for processing (1)
  • pipeline.md

📝 Walkthrough

Walkthrough

This PR introduces pipeline.md, a comprehensive specification (OVOS-PIPELINE-1 v2) defining the utterance lifecycle, orchestrator/pipeline-plugin abstraction, session state management, event semantics, dispatch routing, and conformance requirements for the OVOS pipeline system.

Changes

Pipeline Orchestrator Specification

Layer / File(s) Summary
Specification preamble and pipeline plugin contract
pipeline.md
Document header with normative keywords and scope; match(utterances, lang, session) -> Match | None contract specifying Match shape, updated_session semantics, timeout discipline, exception handling as decline, and first-match-wins iteration.
Session field ownership and state composition
pipeline.md
Session fields owned by spec: session.pipeline, session.blacklisted_pipelines, session.blacklisted_skills, session.blacklisted_intents, with layered composition order (preference → availability → policy) and backstop denylist filtering.
Utterance lifecycle, transformer chain, and handler phases
pipeline.md
Complete utterance lifecycle with transformer chain ordering, first-match-wins plugin iteration, empty utterance handling, termination invariant of exactly one ovos.utterance.handled per entry, and nested handler lifecycle with concurrency guarantee.
Dispatch routing, context stamping, and handler-lifecycle events
pipeline.md
Dispatch on <skill_id>:<intent_name>, required context["skill_id"] and context["pipeline_id"] stamping, payload fields and validation, handler subscription discipline, reserved intent names (converse, response), and orchestrator-emitted handler-lifecycle trio with optional timeout.
Per-pipeline introspection and conformance requirements
pipeline.md
Per-pipeline introspection query/response topics (ovos.pipeline.<pipeline_id>.intents.list / .response) and conformance requirements for deployments, orchestrators, and plugins.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • OpenVoiceOS/architecture#19: This specification explicitly integrates the transformer chain lifecycle that OVOS-TRANSFORM-1 defines, including transformer ordering within the utterance pipeline.

Possibly related PRs

  • OpenVoiceOS/architecture#27: Aligns with session-state ownership and in-utterance mutation boundaries, particularly the Match.updated_session application and convergence on ovos.utterance.handled.

Poem

🐰 A pipeline takes shape, hop by hop,
Match wins first, plugins never stop,
Sessions compose, denylists flow,
Handlers run deep, events all aglow,
One utterance handled—the promise, the show! 🎤

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch spec/pipeline1-utterance-lifecycle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@JarbasAl JarbasAl force-pushed the spec/pipeline1-utterance-lifecycle branch from d8e49b2 to 499ce40 Compare May 24, 2026 00:28
JarbasAl added a commit that referenced this pull request May 24, 2026
Refactors INTENT-4 around the refined pipeline-plugin model
discussed for OVOS-PIPELINE-1:

- Registrations are **broadcast** on the bus. There is no central
  party that owns, validates, or routes them. Pipeline plugins
  (OVOS-PIPELINE-1) consume what they want. The orchestrator
  passively indexes everything for the introspection topics.
- INTENT-4 shrinks ~30% (1100 → 736 lines) by moving the bus-level
  utterance lifecycle (match-result notification, dispatch,
  handler-lifecycle trio) into OVOS-PIPELINE-1, where it belongs.
- Title becomes "Intent and Entity Registration Bus Contract"
  (was "...and Dispatch"). The spec is now purely about how a
  skill puts an intent on the bus — what happens to it after
  that is OVOS-PIPELINE-1's domain.

Sections removed (moved to OVOS-PIPELINE-1):

- §10 The match-result message (ovos.intent.matched)
- §11 Dispatch (<skill_id>:<intent_name>)
- §12 Handler-lifecycle messages (ovos.intent.handler.*)
- §15 Other utterance-lifecycle messages (out of scope)

Sections rewritten:

- §1 Scope — much shorter; lists registration topics only,
  explicit pointer to PIPELINE-1 for the rest
- §2 Architectural model — "registrations are broadcast" instead
  of "host as sole consumer". The orchestrator is now described
  as a passive indexer, not a routing party.
- §3.2 Responses — now plugin-optional; no orchestrator handshake.
  Producers must not require a response.
- §3.3 Error codes — drops `no_compatible_engine` (no central
  rejection party); the remaining four codes are plugin-emitted on
  `.response` rejections only.
- §4 Topics table — broadcast direction marked explicitly;
  references to the moved topics are removed.
- §10 Introspection — reframed as the orchestrator's passive
  registration index; clearer that it reflects what skills
  declared, not what plugins matched.
- §11 Conformance — split per-party (skill MUST, plugin MAY,
  orchestrator MUST). Orchestrator's only responsibility is the
  passive index.

Also folds in the "host → orchestrator" rename (companion to
INTENT-3 v2 in #13). 81 occurrences renamed; one explanatory
sentence added in §2.

CHANGELOG, README, GLOSSARY updated to match.

Cross-spec composition:
- OVOS-MSG-1: unchanged
- OVOS-INTENT-3 v2 (#13): provides the renamed "orchestrator" term
- OVOS-INTENT-4 (this PR): pure registration protocol
- OVOS-PIPELINE-1 (#11, draft): owns everything else (orchestrator
  contract, pipeline plugins, utterance lifecycle, dispatch,
  handler-lifecycle trio, terminal events)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JarbasAl JarbasAl force-pushed the spec/pipeline1-utterance-lifecycle branch from 499ce40 to 986023e Compare May 24, 2026 01:38
@JarbasAl JarbasAl changed the title OVOS-PIPELINE-1 — utterance lifecycle and pipeline (draft, companion to #9) OVOS-PIPELINE-1 — utterance lifecycle and pipeline (draft) May 24, 2026
@JarbasAl JarbasAl force-pushed the spec/pipeline1-utterance-lifecycle branch from d765e67 to 3877683 Compare May 24, 2026 03:49
JarbasAl added a commit that referenced this pull request May 24, 2026
JarbasAl added a commit that referenced this pull request May 25, 2026
…atch.updated_session

Restructure the converse plugin role so it is structurally
indistinguishable from any other pipeline plugin under
OVOS-PIPELINE-1, with no spec-level exceptions:

- No dispatch suppression. A converse-plugin Match dispatches on
  <owner_id>:converse via PIPELINE-1 §7 normally; the orchestrator
  fires the full handler-trio and end-marker. Same for
  <owner_id>:response under §5.2. The reserved intent_names are a
  namespace lease (PIPELINE-1 §7.3), not a dispatch modification.

- Match.updated_session is the canonical channel for the converse
  plugin's match-phase session mutations (decrementing
  auto_continue, removing response_mode, pre-promoting the
  claimer to the active-list head). Per PIPELINE-1 §4.2 the
  orchestrator picks up updated_session only when the match
  claims; declined-poll session mutations are discarded.

- Polymorphism aligns with PIPELINE-1 §7.0's two-shape model:
  plain skill, or pipeline plugin with bundled handlers
  (pipeline_id == skill_id). The converse plugin itself is the
  second shape; it publishes its bundled-handler intent_names
  (converse, response) to the per-pipeline passive index
  (PIPELINE-1 §7.0 / §10), NOT via OVOS-INTENT-4 registration.

- §3 activation lifecycle adds the match-phase mutation pathway
  (§4.2 Match.updated_session) as the orchestrator-side
  equivalent of automatic §3.1 activation, applied at match-time
  rather than dispatch-time.

- §4.2 poll round-trip uses dotted addressed topics
  <owner_id>.converse.request / .response — NOT dispatches. The
  poll is a pure decision query; user-facing work happens only
  in the dispatched <owner_id>:converse handler invocation.

- §5.2 response-mode delivery: the converse plugin's match
  returns a Match on intent_name "response" carrying
  updated_session with decremented auto_continue; orchestrator
  dispatches <owner_id>:response per PIPELINE-1 §7. Pre-emption
  of other intent stages is structural (first-match-wins,
  deployer configures converse plugin at front of
  session.pipeline) rather than a "no pipeline stage runs"
  exception.

Companion PR: PIPELINE-1 (#11) carries the matching changes
(§4.1 Match.updated_session, §4.2 emit-during-match allowance,
§7.0 collapsed two-shape polymorphism, §7.3 simplified
reservation registry).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 26, 2026
…re matcher

The converse plugin produces matches whose owner_id is some
OTHER component's identity (the claiming active handler, or
the response-mode holder). It bundles no handler and has no
skill_id — the dispatched <owner_id>:converse and
<owner_id>:response topics are handled by the OWNER (typically a
skill via framework convention), not by the converse plugin
itself.

This is the 'pure matcher' shape PIPELINE-1 §7.0 distinguishes
from 'pipeline plugin with bundled handlers' (fallback, persona,
OCP — those DO bundle handlers and DO have pipeline_id ==
skill_id).

Fixes in:
- §1 reserved-intent-names bullet — drop 'bundled-handler set'
  framing; describe as 'pure matcher' producing matches addressed
  to other components
- §4 leading paragraph — drop bundled-handler description; clarify
  the plugin publishes its intent_name set for observability, not
  because it owns the handlers
- §9.2 conformance — drop 'MUST be addressable as
  plugin-with-bundled-handlers'; replace with 'is a pure matcher
  in §7.0 terms'; drop the MUST-NOT-register-under-INTENT-4
  clause (the plugin has no skill_id so it can't register anyway)
- §9.2 passive-index bullet — describe as 'intent_names it
  produces matches on'

Companion PR: PIPELINE-1 (#11) adds the pure-matcher shape
explicitly and notes that plain skills handle reserved-intent-
name dispatches via framework convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
pipeline.md (1)

1330-1343: ⚡ Quick win

Consider adding frequently-referenced companion specs to "See also" section.

The document references OVOS-TRANSFORM-1 (§§6.1, 6.2, multiple transformer chains), OVOS-CONTEXT-1 (§§3.1, 4.2, 6.1), and OVOS-CONVERSE-1 (§§7.0, 7.3) throughout, but these specs are not listed in the "See also" section. Adding them would improve navigability for implementers who need to understand the full context.

📚 Suggested addition
 - *Intent Definition Specification* (OVOS-INTENT-3) — the intent
   concept and the orchestrator role.
+- *Transformer Chain Specification* (OVOS-TRANSFORM-1) — the
+  utterance, metadata, intent, dialog, and TTS transformer chains
+  integrated into the lifecycle of §6.
+- *Context Management Specification* (OVOS-CONTEXT-1) — the
+  intent_context promotion and decay mechanisms referenced in the
+  post-match-pre-dispatch window.
+- *Converse Specification* (OVOS-CONVERSE-1) — the reserved
+  intent_names `converse` and `response` and their semantics.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pipeline.md` around lines 1330 - 1343, Add the three frequently-referenced
companion specs to the "See also" section: include bullet items for
OVOS-TRANSFORM-1 (referenced in §§6.1, 6.2 and transformer chains),
OVOS-CONTEXT-1 (referenced in §§3.1, 4.2, 6.1) and OVOS-CONVERSE-1 (referenced
in §§7.0, 7.3) using the same prose style as the existing entries (brief title
plus parenthetical description), ensuring the new bullets appear alongside the
existing "Bus Message Specification", "Session Specification", "Intent and
Entity Registration Bus Contract" and "Intent Definition Specification" entries
and reference the `session` / `pipeline` context where appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pipeline.md`:
- Line 928: Update the dispatch payload docs so `data.lang` is optional to match
upstream optionality: change the dispatch payload table entry for `lang` from
"yes" to "no" and add a short note clarifying that dispatch MAY omit `data.lang`
when neither Match.lang nor the entry topic provide it; ensure this is
consistent with the optional `Match.lang` symbol and the entry topic `data.lang`
symbol and remove or reconcile any text that currently forbids synthesizing a
lang fallback so the spec is internally consistent.
- Line 108: The document mistakenly references "OVOS-SESSION-2" at the phrase
"(OVOS-SESSION-1 §3.1). The full state-ownership model is owned by"—confirm
whether the intended spec is OVOS-SESSION-1 or OVOS-SESSION-2 and then either
(a) replace "OVOS-SESSION-2" with "OVOS-SESSION-1" where referenced, or (b) add
"OVOS-SESSION-2" to the companion-spec list in the introduction (the companion
specs block around lines 17-26) so the reference is consistent; update the
phrase containing "OVOS-SESSION-2" accordingly.

---

Nitpick comments:
In `@pipeline.md`:
- Around line 1330-1343: Add the three frequently-referenced companion specs to
the "See also" section: include bullet items for OVOS-TRANSFORM-1 (referenced in
§§6.1, 6.2 and transformer chains), OVOS-CONTEXT-1 (referenced in §§3.1, 4.2,
6.1) and OVOS-CONVERSE-1 (referenced in §§7.0, 7.3) using the same prose style
as the existing entries (brief title plus parenthetical description), ensuring
the new bullets appear alongside the existing "Bus Message Specification",
"Session Specification", "Intent and Entity Registration Bus Contract" and
"Intent Definition Specification" entries and reference the `session` /
`pipeline` context where appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4683241a-8fb0-43db-891b-ada44e5ab7fd

📥 Commits

Reviewing files that changed from the base of the PR and between f685e8a and 2822531.

📒 Files selected for processing (1)
  • pipeline.md

Comment thread pipeline.md

The orchestrator is **stateless for named sessions** and holds
persistent state only for the reserved `session_id == "default"`
(OVOS-SESSION-1 §3.1). The full state-ownership model is owned by
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Verify the spec reference: OVOS-SESSION-2 vs OVOS-SESSION-1.

The document references "OVOS-SESSION-2" here, but the companion specs listed in the introduction (lines 17-26) include only "OVOS-SESSION-1." Either OVOS-SESSION-2 should be added to the companion spec list, or this reference should be corrected to OVOS-SESSION-1 if that's the intended spec.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pipeline.md` at line 108, The document mistakenly references "OVOS-SESSION-2"
at the phrase "(OVOS-SESSION-1 §3.1). The full state-ownership model is owned
by"—confirm whether the intended spec is OVOS-SESSION-1 or OVOS-SESSION-2 and
then either (a) replace "OVOS-SESSION-2" with "OVOS-SESSION-1" where referenced,
or (b) add "OVOS-SESSION-2" to the companion-spec list in the introduction (the
companion specs block around lines 17-26) so the reference is consistent; update
the phrase containing "OVOS-SESSION-2" accordingly.

Comment thread pipeline.md Outdated
|-------|------|----------|---------|
| `owner_id` | string | yes | The `Match.owner_id` — the topic's prefix, repeated for the handler's convenience. |
| `intent_name` | string | yes | The `Match.intent_name` — the topic's suffix. |
| `lang` | string | yes | The language the utterance was recognized in (`data.lang`, OVOS-MSG-1 §4.2). |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clarify the requirement for data.lang in dispatch payload.

The dispatch payload table (line 928) marks lang as "yes" (required), but:

  • Match.lang is optional (§4.1, line 314, marked "no" in the Required column)
  • The entry topic data.lang is optional (§9.1, line 1108)

If both the entry topic and the Match can omit lang, it's unclear how the dispatch payload can always include a required lang field. The spec should either:

  1. Clarify that data.lang in dispatch defaults to a specific value (e.g., empty string, "unknown", or a session-derived fallback) when both sources are absent, or
  2. Mark data.lang as optional in the dispatch payload table to match the optionality upstream.

The current wording at line 294-296 prohibits the orchestrator from synthesizing a lang value, which suggests option 2 (marking dispatch data.lang as optional) is more consistent with the design.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~928-~928: Do not mix variants of the same word (‘recognize’ and ‘recognise’) within a single text.
Context: ... | yes | The language the utterance was recognized in (data.lang, OVOS-MSG-1 §4.2). | | ...

(EN_WORD_COHERENCY)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pipeline.md` at line 928, Update the dispatch payload docs so `data.lang` is
optional to match upstream optionality: change the dispatch payload table entry
for `lang` from "yes" to "no" and add a short note clarifying that dispatch MAY
omit `data.lang` when neither Match.lang nor the entry topic provide it; ensure
this is consistent with the optional `Match.lang` symbol and the entry topic
`data.lang` symbol and remove or reconcile any text that currently forbids
synthesizing a lang fallback so the spec is internally consistent.

JarbasAl and others added 12 commits May 26, 2026 14:34
Defines the orchestrator and the pipeline-plugin abstraction:
opaque-pipeline_id black boxes the orchestrator iterates in
session.pipeline_stages order per utterance, first-match-wins.
Plugins expose one operation — match(utterance, session) →
Match | None, side-effect-free — and are otherwise black boxes.

The orchestrator handles dispatch, notifications, and terminal
events. Dispatch topic is <owner_id>:<intent_name> where owner
is either a skill_id (skill-owned handler) or a pipeline_id
(plugin-bundled handler). From outside, skills and plugin-bundled
handlers are indistinguishable.

Utterance-layer events:
- recognizer_loop:utterance (entry)
- ovos.intent.matched (positive match notification)
- ovos.utterance.cancelled (transformer cancellation)
- complete_intent_failure (no plugin claimed)
- ovos.utterance.handled (universal end-marker)

Handler-lifecycle trio:
- ovos.intent.handler.start / .complete / .error

Transformer chain: pre-pipeline modification or cancellation.

Per-plugin behavioural contracts (converse, fallback, etc.) are
out of scope — plugins are black boxes; each defines itself.

The spec body is timeless: no mycroft references, no
implementation-code citations, no "where this differs from
current OVOS" appendix in the spec itself. Current-OVOS context
and divergence catalogues belong in APPENDIX.md (covered in a
separate commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Revert field rename: keep `session.pipeline` (not
  `pipeline_stages`). The legacy name stays; no need to break
  observers for a clarity-only edit.

- Dispatch topic `<owner_id>:<intent_name>` (§7): clarify that
  the split is at the FIRST `:`. skill_id and pipeline_id MUST
  NOT contain `:`; intent_name MAY contain further `:` so
  handlers can namespace dispatched topics inside their own
  surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Simpler than first-colon-wins. The dispatch topic
`<owner_id>:<intent_name>` now contains exactly one `:`, so the
split is trivially unambiguous and there is no edge case to
specify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per the draft-stage versioning policy, v1 is reserved for content
drop-in compatible with current OVOS. PIPELINE-1 adds the
orchestrator passive registration index and normalizes the
universal `ovos.utterance.handled` end-marker across all terminal
paths (current workshop misses it on the error path) — both
require OVOS-side changes, so the first release ships as v2.

Also update the CHANGELOG bullet that still referenced the
defunct `session.pipeline_stages` rename — kept as
`session.pipeline`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related cleanups:

1. **Remove the transformer chain from the normative spec.**
   Transformers are pre-pipeline message modifiers — they don't
   match, they don't dispatch, they only mutate the message or
   cancel it. Their loading/ordering/contract is a separate
   concern and doesn't need to ride along with the pipeline
   plugin spec. §10 deleted; §6.1 flow simplified to two
   terminal paths (matched / no-match); §6.4 terminal-events
   table loses the cancelled row; §9.3 (`ovos.utterance.cancelled`)
   deleted and §9.4–§9.6 renumbered to §9.3–§9.5; §11 conformance
   transformer block deleted and §11 renumbered to §10. Non-goals
   list updated to explicitly exclude any pre-pipeline utterance-
   transformer chain.

2. **Refine the V2 rationale** in the CHANGELOG to match the
   V0/V1/V2 framing: the trigger for V2 is the handler-lifecycle
   rename (mycroft.skill.handler.* → ovos.intent.handler.*) which
   actively breaks observers of the legacy names. The passive
   registration index and the universal `ovos.utterance.handled`
   end-marker would have been V1-compatible on their own —
   missing them degrades experience (empty introspection, missed
   end-marker on workshop's error path) but doesn't break V0
   producers/consumers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
§2 — the orchestrator is the logical role; it MAY be implemented
as a single process or as multiple cooperating processes (a
natural split runs audio-input / utterance-handling / audio-output
as separate services). From the spec's perspective those processes
together are "the orchestrator"; the split is a deployment /
containerization choice. Pipeline plugins, the loaded-plugin set,
and the match contract live in the orchestrator process that
implements the utterance lifecycle (utterance-handling under the
audio-boundary split).

Generic voice-OS framing (no "current OVOS does X" wording in the
spec body).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ection

§3 pipeline_id: drop colon-rule restatement; reference MSG-1 §2.1.1.
§5 session.pipeline: drop field restatement; reference SESSION-1
   §2.1 claim and §2.5 deployment-default fallback. Tighten partial-
   unknown rule (orchestrator MUST NOT fall back to deployment
   default merely because one identifier is unknown).
§7 dispatch topic: drop colon-rule restatement; reference MSG-1
   §2.1.1.
§8.1 handler trio: tighten from SHOULD to MUST — handler MUST emit
   exactly one terminal event (complete / error). start stays
   SHOULD. Reason: §9.5 universal end-marker and §8.3 timeout
   bookkeeping depend on terminal event being deterministic.
§10 (new): per-pipeline_id intent introspection. Pull-query topic
   ovos.pipeline.<pipeline_id>.intents.list with scatter-response
   pattern. No aggregate query — consumers walk per-pipeline.
   Pull-query is source of truth; load-time broadcasts are MAY,
   consumers MUST NOT rely on them.
§11 (renumbered) conformance: handler MUST emit terminal event
   (was SHOULD); pipeline plugin MUST respond to per-pipeline_id
   introspection queries; orchestrator MUST NOT synthesize trio
   events. Non-goals: session shape moved to SESSION-1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per design clarification: third-party handler code carries no
obligation under this specification. The orchestrator wraps every
handler invocation and emits the trio itself — start before the
call, complete on normal return, error on exception or timeout.

§8.1: trio MUSTs now bind the orchestrator. No handler-side
participation required.
§8.3: rename 'Orchestrator timeout' → 'Handler timeout'. On timeout
the orchestrator emits .error (it owns the topic), then
ovos.utterance.handled. Drops the prior 'orchestrator MUST NOT
synthesize .error' rule since the orchestrator now owns it
unambiguously.
§11 handler section: replaced with a 'no normative obligation'
clause. The spec binds the orchestrator that invokes the handler,
not the handler.

Also addresses the workshop utterance.handled asymmetry: workshop
acts as orchestrator-ish wrapper but didn't emit the trio. Under
this revision the wrapper IS responsible — that's the right
ownership.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n boundary

A plugin needs the shape contract without having to read TRANSFORM-1
§3.2 to infer it. Now: 'a non-empty list of candidate strings, may
have been modified by utterance-transformer chain, all in the same
language, no particular order, plugin chooses how to weight'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entry-topic name is deferred to a separate spec covering audio-input
↔ assistant-core wire contracts. Current OVOS uses
recognizer_loop:utterance for compatibility; conformant orchestrators
MAY subscribe to that name in the interim. What IS normative is the
behaviour after entry: §6 lifecycle, §9.5 end-marker, §§7-8
obligations. The entry name and payload shape are not.

Internal refs updated to 'entry topic (§9.1)' style throughout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl and others added 18 commits May 26, 2026 18:29
Drop all derivation/routing/authorship framing — that belongs in MSG-1.
The section now states exactly what is needed:
- orchestrator stamps context["pipeline_id"] before each match call
- orchestrator stamps context["skill_id"] = owner_id on every dispatch
Both fields flow automatically from there; no plugin author policy required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Orchestrator stamps pipeline_id on match selection. Handler identity
(skill_id) is INTENT-4's rule, applies to bundled-handler plugins
identically to plain skills — fully polymorphic, nothing to add here.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clarify that pipeline_id first appears on the dispatch Message (§7.1),
not at match time. MSG-1 derivation semantics carry it through handler
emissions with no further action required by the plugin or handler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- §1: conformance cross-ref corrected to §11 (was §10)
- §9 intro: drop hardcoded "five" event count
- §7.0: remove named project references from pure-matcher paragraph
  (golden rule — describe by role, not by implementation name)
- §7.1: replace "via forward (MSG-1 §5.1)" with "MSG-1 derivation
  semantics" — routing mechanics belong in MSG-1, not here;
  drop redundant skill_id drift paragraph (INTENT-4 §3.1 owns it)
- §9.6: replace explicit forward/reply prescription with
  "derives from the dispatch Message per MSG-1 §5 derivation semantics"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The "plain skill vs plugin-with-bundled-handlers" distinction was
architectural noise — there is no difference. A match has an owner_id
which is the skill_id of the handler. If that is the plugin itself,
skill_id == pipeline_id. Same dispatch, same handler obligations, same
INTENT-4 rules. Plugin just skipped the registration bus round-trip.

- §7.0: rewritten to state the single rule plainly; table and
  "identifier polymorphism" heading removed
- §7.1: pipeline_id stamp rule simplified — always stamped from the
  producing plugin; no conditional on handler-owner shape
- §2: remove "two dispatch shapes" paragraph (no longer needed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
owner_id was an abstract alias for skill_id with no distinct meaning.
Now that the two-shapes framing is gone, the abstraction serves no purpose.
Every match, dispatch topic, payload field, and prose reference now uses
skill_id directly, consistent with INTENT-3/4 and the rest of the spec set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
INTENT-4 is an optional layer built on top of PIPELINE-1, not a
dependency of it. Move INTENT-4 to a "See also" note. Reframe the
opening paragraph so the spec presents itself as the NL entry/exit
boundary rather than a companion to the intent specs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Follows MSG-1 dot-namespaced topic convention. Symmetric with
ovos.intent.matched.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…handler

Plugins SHOULD return from match immediately and defer expensive work
(model inference, network calls) to the handler phase. Canonical
example: an LLM plugin can match instantly and generate in the handler.
Orchestrator SHOULD surface match-phase duration as an observable metric.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rmative paragraphs

- Match.captures → Match.slots throughout (§4.1, §4.3, §7.1, §9.2, §6.1)
- match(utterance,…) → match(utterances,…) in signature, §4 inputs, §11
- Drop naming-convention explanation paragraphs from §9.1 and §9.6
- Drop §4.2 "flow diagram reflects this" cross-ref (diagram already shows it)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…andled

They are not part of the pipeline lifecycle; move them outside the
flow diagram's break point and correct the prose to say they run
just before TTS rendering in the output layer, not inside the
dispatch path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Collapsed the 10-line circuit-breaker block to 4 lines — the SHOULD
discipline is preserved, the over-specified event topic and payload
are dropped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both are encoded in the topic (<skill_id>:<intent_name>); repeating
them in data is redundant. A handler that needs them splits the topic
on ':'. §9.2 ovos.intent.matched keeps them (broadcast, no topic
encoding).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
By dispatch time the pipeline must have resolved a content language.
lang is now required in the dispatch data; a match with no lang and
no entry-topic lang is treated as declined. Match.lang updated to
reflect the same obligation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Match.lang is now required. Language resolution is the plugin's
explicit responsibility: use the entry-topic lang hint, session
signals, or any other policy, then declare the result. A Match
without lang is malformed and treated as declined. Dispatch lang
is taken directly from Match.lang with no orchestrator fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents the get_response nesting pattern: inner utterance lifecycle
runs to completion (including its own .handled) while the outer handler
is blocked. Mandates orchestrator concurrency (deadlock otherwise).
Clarifies that the per-entry .handled invariant applies independently
to each ovos.utterance.handle. Cross-references CONVERSE-1 §5 for the
response routing mechanism.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- §2: fix sentence run-on
- §4.2/§9.1/§9.6: remove double blank lines
- §6.1: drop "(highlighted)" — not meaningful in markdown
- §11 plugin MUST: add lang to the required Match fields
- §11 orchestrator MUST: add §6.5 concurrency obligation
- §10.1: fix confusing Match.skill_id parenthetical → pipeline_id

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@JarbasAl JarbasAl marked this pull request as ready for review May 26, 2026 19:16
@JarbasAl JarbasAl merged commit 3a22133 into dev May 26, 2026
1 check was pending
JarbasAl added a commit that referenced this pull request May 27, 2026
…atch.updated_session

Restructure the converse plugin role so it is structurally
indistinguishable from any other pipeline plugin under
OVOS-PIPELINE-1, with no spec-level exceptions:

- No dispatch suppression. A converse-plugin Match dispatches on
  <owner_id>:converse via PIPELINE-1 §7 normally; the orchestrator
  fires the full handler-trio and end-marker. Same for
  <owner_id>:response under §5.2. The reserved intent_names are a
  namespace lease (PIPELINE-1 §7.3), not a dispatch modification.

- Match.updated_session is the canonical channel for the converse
  plugin's match-phase session mutations (decrementing
  auto_continue, removing response_mode, pre-promoting the
  claimer to the active-list head). Per PIPELINE-1 §4.2 the
  orchestrator picks up updated_session only when the match
  claims; declined-poll session mutations are discarded.

- Polymorphism aligns with PIPELINE-1 §7.0's two-shape model:
  plain skill, or pipeline plugin with bundled handlers
  (pipeline_id == skill_id). The converse plugin itself is the
  second shape; it publishes its bundled-handler intent_names
  (converse, response) to the per-pipeline passive index
  (PIPELINE-1 §7.0 / §10), NOT via OVOS-INTENT-4 registration.

- §3 activation lifecycle adds the match-phase mutation pathway
  (§4.2 Match.updated_session) as the orchestrator-side
  equivalent of automatic §3.1 activation, applied at match-time
  rather than dispatch-time.

- §4.2 poll round-trip uses dotted addressed topics
  <owner_id>.converse.request / .response — NOT dispatches. The
  poll is a pure decision query; user-facing work happens only
  in the dispatched <owner_id>:converse handler invocation.

- §5.2 response-mode delivery: the converse plugin's match
  returns a Match on intent_name "response" carrying
  updated_session with decremented auto_continue; orchestrator
  dispatches <owner_id>:response per PIPELINE-1 §7. Pre-emption
  of other intent stages is structural (first-match-wins,
  deployer configures converse plugin at front of
  session.pipeline) rather than a "no pipeline stage runs"
  exception.

Companion PR: PIPELINE-1 (#11) carries the matching changes
(§4.1 Match.updated_session, §4.2 emit-during-match allowance,
§7.0 collapsed two-shape polymorphism, §7.3 simplified
reservation registry).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 27, 2026
…re matcher

The converse plugin produces matches whose owner_id is some
OTHER component's identity (the claiming active handler, or
the response-mode holder). It bundles no handler and has no
skill_id — the dispatched <owner_id>:converse and
<owner_id>:response topics are handled by the OWNER (typically a
skill via framework convention), not by the converse plugin
itself.

This is the 'pure matcher' shape PIPELINE-1 §7.0 distinguishes
from 'pipeline plugin with bundled handlers' (fallback, persona,
OCP — those DO bundle handlers and DO have pipeline_id ==
skill_id).

Fixes in:
- §1 reserved-intent-names bullet — drop 'bundled-handler set'
  framing; describe as 'pure matcher' producing matches addressed
  to other components
- §4 leading paragraph — drop bundled-handler description; clarify
  the plugin publishes its intent_name set for observability, not
  because it owns the handlers
- §9.2 conformance — drop 'MUST be addressable as
  plugin-with-bundled-handlers'; replace with 'is a pure matcher
  in §7.0 terms'; drop the MUST-NOT-register-under-INTENT-4
  clause (the plugin has no skill_id so it can't register anyway)
- §9.2 passive-index bullet — describe as 'intent_names it
  produces matches on'

Companion PR: PIPELINE-1 (#11) adds the pure-matcher shape
explicitly and notes that plain skills handle reserved-intent-
name dispatches via framework convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 27, 2026
…atch.updated_session

Restructure the converse plugin role so it is structurally
indistinguishable from any other pipeline plugin under
OVOS-PIPELINE-1, with no spec-level exceptions:

- No dispatch suppression. A converse-plugin Match dispatches on
  <owner_id>:converse via PIPELINE-1 §7 normally; the orchestrator
  fires the full handler-trio and end-marker. Same for
  <owner_id>:response under §5.2. The reserved intent_names are a
  namespace lease (PIPELINE-1 §7.3), not a dispatch modification.

- Match.updated_session is the canonical channel for the converse
  plugin's match-phase session mutations (decrementing
  auto_continue, removing response_mode, pre-promoting the
  claimer to the active-list head). Per PIPELINE-1 §4.2 the
  orchestrator picks up updated_session only when the match
  claims; declined-poll session mutations are discarded.

- Polymorphism aligns with PIPELINE-1 §7.0's two-shape model:
  plain skill, or pipeline plugin with bundled handlers
  (pipeline_id == skill_id). The converse plugin itself is the
  second shape; it publishes its bundled-handler intent_names
  (converse, response) to the per-pipeline passive index
  (PIPELINE-1 §7.0 / §10), NOT via OVOS-INTENT-4 registration.

- §3 activation lifecycle adds the match-phase mutation pathway
  (§4.2 Match.updated_session) as the orchestrator-side
  equivalent of automatic §3.1 activation, applied at match-time
  rather than dispatch-time.

- §4.2 poll round-trip uses dotted addressed topics
  <owner_id>.converse.request / .response — NOT dispatches. The
  poll is a pure decision query; user-facing work happens only
  in the dispatched <owner_id>:converse handler invocation.

- §5.2 response-mode delivery: the converse plugin's match
  returns a Match on intent_name "response" carrying
  updated_session with decremented auto_continue; orchestrator
  dispatches <owner_id>:response per PIPELINE-1 §7. Pre-emption
  of other intent stages is structural (first-match-wins,
  deployer configures converse plugin at front of
  session.pipeline) rather than a "no pipeline stage runs"
  exception.

Companion PR: PIPELINE-1 (#11) carries the matching changes
(§4.1 Match.updated_session, §4.2 emit-during-match allowance,
§7.0 collapsed two-shape polymorphism, §7.3 simplified
reservation registry).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 27, 2026
…re matcher

The converse plugin produces matches whose owner_id is some
OTHER component's identity (the claiming active handler, or
the response-mode holder). It bundles no handler and has no
skill_id — the dispatched <owner_id>:converse and
<owner_id>:response topics are handled by the OWNER (typically a
skill via framework convention), not by the converse plugin
itself.

This is the 'pure matcher' shape PIPELINE-1 §7.0 distinguishes
from 'pipeline plugin with bundled handlers' (fallback, persona,
OCP — those DO bundle handlers and DO have pipeline_id ==
skill_id).

Fixes in:
- §1 reserved-intent-names bullet — drop 'bundled-handler set'
  framing; describe as 'pure matcher' producing matches addressed
  to other components
- §4 leading paragraph — drop bundled-handler description; clarify
  the plugin publishes its intent_name set for observability, not
  because it owns the handlers
- §9.2 conformance — drop 'MUST be addressable as
  plugin-with-bundled-handlers'; replace with 'is a pure matcher
  in §7.0 terms'; drop the MUST-NOT-register-under-INTENT-4
  clause (the plugin has no skill_id so it can't register anyway)
- §9.2 passive-index bullet — describe as 'intent_names it
  produces matches on'

Companion PR: PIPELINE-1 (#11) adds the pure-matcher shape
explicitly and notes that plain skills handle reserved-intent-
name dispatches via framework convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant