Skip to content

OVOS-CONVERSE-1: Active Handlers and Interactive Response#25

Open
JarbasAl wants to merge 44 commits into
devfrom
spec/converse1-active-handlers
Open

OVOS-CONVERSE-1: Active Handlers and Interactive Response#25
JarbasAl wants to merge 44 commits into
devfrom
spec/converse1-active-handlers

Conversation

@JarbasAl

@JarbasAl JarbasAl commented May 25, 2026

Copy link
Copy Markdown
Member

Companion issue: #24

Summary

Defines the imperative continuous-dialog surface: the converse plugin role that polls active handlers before intent matching, and the response-mode mechanism for collecting the next utterance exclusively.

What the spec covers

  • §2 — Two session fields:
    • session.converse_handlers — recency-ordered list of {skill_id, activated_at} entries; deployer-tunable cap; TTL-pruned pre-poll
    • session.response_mode — single {skill_id, expires_at} entry when a handler holds the next utterance exclusively
  • §3 — Converse plugin role: pure matcher (PIPELINE-1 §7.0); checks response_mode first (pre-empts poll), then polls converse_handlers via unicast ping/pong
  • §4 — Converse poll: <skill_id>.converse.ping / <skill_id>.converse.pong (unicast dotted-addressed, sequential, stops at first claim). Handler-lifecycle trio NOT fired during poll
  • §5 — Response mode: session-resident {skill_id, expires_at}; entered/left via in-handler mutation; single-shot delivery via <skill_id>:response dispatch
  • §6 — Activation lifecycle: automatic on dispatch, direct mutation, transformer-driven
  • §7 — Stop integration: targeted stop removes from active_handlers; global stop empties both
  • §8 — Bus surface (two dispatch topics, one poll pair)

Key design decisions

  • Poll is unicast, not broadcast. <skill_id>.converse.ping asks one skill at a time in activated_at order; stops at first claimer
  • Response mode is session-resident. No bus topics for enter/leave — pure in-handler session mutation
  • Converse and response dispatches are ordinary PIPELINE-1 §7 dispatches on reserved intent names — no special routing

Companion edits (same PR)

  • ovos-pipeline-1.mdactive_handlers ordering clarification; sort by activated_at
  • ovos-stop-1.mdresponse_mode field reference fix

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@JarbasAl, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 17 minutes and 12 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 230bffac-d917-4f85-8533-4a579a4dc684

📥 Commits

Reviewing files that changed from the base of the PR and between ce3d6c6 and aed3477.

📒 Files selected for processing (3)
  • README.md
  • converse.md
  • ovos-session-1.md
📝 Walkthrough

Walkthrough

Adds OVOS-CONVERSE-1 (draft) specification: defines session.converse_handlers and session.response_mode wire shapes and lifecycles, a converse-plugin poll/match contract with response-mode pre-emption, interactive response collection/cancellation semantics, bus/topic definitions, conformance obligations, and clarifying edits to session mutation visibility and appendix wording.

Changes

OVOS-CONVERSE-1 Specification and Session Semantics

Layer / File(s) Summary
Specification charter and core data contracts
converse.md (1–79, 83–160), ovos-session-1.md (196, 666)
Introduces OVOS-CONVERSE-1 draft, scope/reserved names, and the two session fields: session.converse_handlers (array of {skill_id, activated_at}) and session.response_mode (single-holder with expires_at).
converse_handlers activation & lifecycle
converse.md (163–279)
Activation pathways for session.converse_handlers, automatic head-stamping on owner dispatch, optional TTL pruning at defined boundaries, transformer mutation timing/authorization, and invariants post-mutation.
Converse plugin poll/match contract
converse.md (282–488)
Plugin match order (response-mode pre-emption then polling), per-owner <owner_id>.converse.request/.response round-trip with timeouts, winner selection by highest activated_at, reserved converse Match semantics, and structured error codes including "done".
Interactive response collection & cancellation
converse.md (490–653)
How handlers enter/exit response mode via session.response_mode, plugin delivers next utterance by clearing the holder and dispatching <owner_id>:response, cancellation paths, transformer cancellation effects, and STOP-1 interrupt semantics.
Bus topics and evaluation order
converse.md (656–714)
Defines introspection and poll delivery topics, reserved-intent dispatch topics, and deterministic evaluation-order interaction with OVOS-CONTEXT-1 (response-mode pre-emption → converse polling → intent matching).
Conformance requirements and non-goals
converse.md (717–889)
Termination via ovos.utterance.handled, conformance obligations for orchestrator/plugins/handlers/observers/transformers, reserved-name registration rules, invariants, and explicit non-goals.
Handler in-place session mutation visibility
ovos-session-2.md (258–267)
Normative clarification: handler-side in-place session mutations are bus-visible only if the handler emits at least one Message carrying the mutated session (e.g., ovos.utterance.speak or ovos.session.sync).
Appendix divergence wording tweak
appendix/divergences.md (131)
Reworded example in Match.updated_session divergence description to reference clearing/setting session.response_mode instead of decrementing a response-mode counter.

Sequence Diagram (high-level match flow):

sequenceDiagram
  participant Orchestrator
  participant ConversePlugin
  participant OwnerSkill
  Orchestrator->>ConversePlugin: invoke match
  ConversePlugin->>ConversePlugin: check session.response_mode
  alt response_mode unexpired
    ConversePlugin->>OwnerSkill: dispatch <owner_id>:response (deliver next utterance)
  else no response_mode
    ConversePlugin->>OwnerSkill: <owner_id>.converse.request (poll)
    OwnerSkill-->>ConversePlugin: <owner_id>.converse.response (result: true/false, error_code)
    ConversePlugin->>Orchestrator: return reserved intent `converse` or proceed to next handler
  end
Loading

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues:

Possibly related PRs:

"A rabbit pens a draft with care,
converse fields tucked in the air,
plugins poll, handlers wait—so sweet,
sessions dance to activated feet. 🐇✨"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title references the main spec being introduced (OVOS-CONVERSE-1) and highlights two core concepts (active handlers and interactive response), which align with the primary changes in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch spec/converse1-active-handlers

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 added a commit that referenced this pull request May 25, 2026
…NVERSE-1)

OVOS-CONVERSE-1 (PR #25) claims two session fields under SESSION-1
§2.1's field registry:

- active_handlers — recency stack of [owner_id, activated_at] pairs
- response_mode   — single owner_id string when set, absent otherwise

Add both to the §3 informative roster of claimed fields and to the
§6 owner-spec list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 25, 2026
Update the forward-reference now that the companion imperative
continuous-dialog specification (OVOS-CONVERSE-1, PR #25) is in
flight. Names the two session fields it owns
(response_mode, active_handlers) and cites the §7 evaluation-
order rule that disambiguates the two surfaces for the
orchestrator.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@converse.md`:
- Around line 253-267: Tighten the authorization rule for direct mutations of
session.active_handlers: update the text around MSG-1 / CONTEXT-1 to state that
if the emitter's self-attribution (as defined by OVOS-INTENT-4 §3.1 for skills
via context["skill_id"] or PIPELINE-1 §3.1 for plugins via
context["pipeline_id"]) is missing or cannot be resolved, the orchestrator MUST
reject any direct mutation that adds, removes, or re-orders entries whose
owner_id differs from the emitter; keep the existing allowance that an emitter
MAY add/refresh/remove entries where owner_id equals its resolved
self-attribution and explicitly call out that unresolved identity => rejection.
- Around line 339-341: The cross-reference is wrong: the sentence about a
transformer mutating `session.response_mode` currently points to §5.5 but should
point to §5.4 where transformer-driven cancellation is defined; update the
reference in the sentence that mentions `session.response_mode` to cite §5.4
(and adjust any nearby parenthetical or explanatory text if it assumes
session-expiry behavior from §5.5).
🪄 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: 169ef9d9-38e8-4543-ae7e-7d7e24273a73

📥 Commits

Reviewing files that changed from the base of the PR and between 73fec74 and c814f64.

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

Comment thread converse.md Outdated
Comment on lines +253 to +267
Any component MAY mutate `session.active_handlers` in place
through the direct-mutation pathway of MSG-1 (the same pathway
CONTEXT-1 §5.3 cites for intent-context entries), subject to the
authorization rule below. The canonical use case is an owner
removing itself from the list after declining a converse poll
without going through the dispatch decline path.

**Authorization.** A direct mutation that adds, removes, or
re-orders an entry whose `owner_id` differs from the emitter's
**self-attribution** (per OVOS-INTENT-4 §3.1 for skills via
`context["skill_id"]`, or per PIPELINE-1 §3.1 for pipeline plugins
via `context["pipeline_id"]`) **MUST** be rejected by the
orchestrator. An emitter **MAY** freely add, refresh, or remove
the entry whose `owner_id` equals its own self-attribution.

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

Tighten direct-mutation authorization for non-attributed emitters.

Any component MAY mutate is too broad versus the self-attribution model (skill_id / pipeline_id). Please explicitly require rejection when emitter identity is missing or unresolvable, otherwise mutation authority is ambiguous for non-handler emitters.

Suggested spec text adjustment
-Any component MAY mutate `session.active_handlers` in place
+Any component MAY request mutation of `session.active_handlers` in place
 through the direct-mutation pathway of MSG-1 ...
@@
-**Authorization.** A direct mutation that adds, removes, or
+**Authorization.** The orchestrator MUST first resolve emitter self-attribution
+from Message.context (`skill_id` or `pipeline_id`). If neither is present,
+the mutation MUST be rejected. A direct mutation that adds, removes, or
 re-orders an entry whose `owner_id` differs from the emitter's
 **self-attribution** ... **MUST** be rejected by the orchestrator.

Also applies to: 1041-1043

🤖 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 `@converse.md` around lines 253 - 267, Tighten the authorization rule for
direct mutations of session.active_handlers: update the text around MSG-1 /
CONTEXT-1 to state that if the emitter's self-attribution (as defined by
OVOS-INTENT-4 §3.1 for skills via context["skill_id"] or PIPELINE-1 §3.1 for
plugins via context["pipeline_id"]) is missing or cannot be resolved, the
orchestrator MUST reject any direct mutation that adds, removes, or re-orders
entries whose owner_id differs from the emitter; keep the existing allowance
that an emitter MAY add/refresh/remove entries where owner_id equals its
resolved self-attribution and explicitly call out that unresolved identity =>
rejection.

Comment thread converse.md Outdated
Comment on lines +339 to +341
A transformer that mutates `session.response_mode` while a wait
window is open **MUST** be understood by the deployer to
cancel that window per §5.5.

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

Fix incorrect cross-reference for transformer-triggered cancellation.

This points to §5.5, but transformer-driven cancellation is defined in §5.4. §5.5 is session-expiry handling.

Suggested fix
-cancel that window per §5.5.
+cancel that window per §5.4.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
A transformer that mutates `session.response_mode` while a wait
window is open **MUST** be understood by the deployer to
cancel that window per §5.5.
A transformer that mutates `session.response_mode` while a wait
window is open **MUST** be understood by the deployer to
cancel that window per §5.4.
🤖 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 `@converse.md` around lines 339 - 341, The cross-reference is wrong: the
sentence about a transformer mutating `session.response_mode` currently points
to §5.5 but should point to §5.4 where transformer-driven cancellation is
defined; update the reference in the sentence that mentions
`session.response_mode` to cite §5.4 (and adjust any nearby parenthetical or
explanatory text if it assumes session-expiry behavior from §5.5).

JarbasAl added a commit that referenced this pull request May 25, 2026
Add a §7 subsection codifying the registry mechanism by which other
specs reserve intent_names and modify dispatch semantics. Currently
in force: converse and response (both per OVOS-CONVERSE-1, PR #25),
both using dispatch suppression (the reserving spec's match-phase
or delivery-path code emits the dispatch directly; the orchestrator
MUST NOT re-dispatch on the returned Match).

Renumbers prior §7.3 (in-process equivalence) to §7.4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 25, 2026
Add new error_code 'reserved_name' and a §3.5 cross-reference to
OVOS-PIPELINE-1 §7.3's reserved-intent-name registry. The orchestrator
MUST reject registration payloads that name an intent_name reserved
by a companion specification.

Currently reserved: converse and response, both by OVOS-CONVERSE-1
(PR #25). The reservation registry is owned by PIPELINE-1; this
spec is the enforcement point at registration time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 25, 2026
CONVERSE-1 §3.4 (PR #25) explicitly cites the metadata-transformer
hook as the recommended position for mutating session.active_handlers
and session.response_mode. Add this to the §3.3 list of permitted
mutations, with the §5.4 cancellation-semantics back-reference for
mid-wait holder changes.

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

OVOS-CONVERSE-1 (PR #25) fills the multi-turn conversation gap that §7 previously listed as planned work. Update §1.2 stack description to include it, and drop the §7 gap entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JarbasAl JarbasAl marked this pull request as draft May 25, 2026 23:39
JarbasAl added a commit that referenced this pull request May 25, 2026
…ymorphism; drop dispatch suppression

Three coordinated changes to make the pipeline-plugin model fully
uniform and to remove a structural exception that OVOS-CONVERSE-1
required:

- §4.2 — drop the "plugin MUST NOT emit during match" prohibition.
  The match contract is the single obligation: return a Match or
  null. Plugins MAY emit bus Messages during match if their
  matching strategy requires it (a converse plugin polling active
  handlers is the canonical case). The spec retains a SHOULD NOT
  on session-field mutation during match — declined-match
  corruption is still real for the few session fields used by
  later iteration stages, but bus emissions are not policed.

- §7.0 (new) — codify identifier polymorphism explicitly. Three
  component shapes own dispatched handlers: plain skill, plain
  pipeline plugin with bundled handler, hybrid plugin-skill. For
  the hybrid case, "if a pipeline plugin registers any intent
  under OVOS-INTENT-4, that plugin's pipeline_id MUST equal its
  skill_id" — one identifier, two roles. skill_id conceptually
  names the voice application; pipeline_id names the matching
  engine; a component that is both carries one identifier filling
  both roles.

- §7.1 — replace the exclusive "stamp skill_id XOR pipeline_id"
  rule with two independent stamps. context["skill_id"] applies
  when owner_id is registered as a skill; context["pipeline_id"]
  applies when owner_id is loaded as a pipeline plugin. For a
  hybrid plugin-skill both stamps land and carry the same
  identifier.

- §7.3 — simplify the reserved-intent-name registry. Drop the
  "dispatch suppression" clause that bent §6's match-then-
  dispatch flow. A reservation is now a namespace lease only:
  skills can't register the name, and a pipeline plugin's Match
  bearing the name dispatches normally per §7 (orchestrator emits
  <owner_id>:<reserved_name>, handler subscribed to that topic
  runs). Updated the converse/response entries in the table to
  reflect this — the converse plugin polls active handlers via
  whatever wire shape OVOS-CONVERSE-1 defines (no longer the
  dispatch topic), then returns a Match that the orchestrator
  dispatches normally.

Also updates §4.1 Match.owner_id description to cite §7.0's
three-shape model rather than the prior implicit OR.

Companion PR: OVOS-CONVERSE-1 (#25) needs a matching restructure
to drop its dispatch-suppression dependency and rely on the
relaxed §4.2 instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 26, 2026
Add a §7 subsection codifying the registry mechanism by which other
specs reserve intent_names and modify dispatch semantics. Currently
in force: converse and response (both per OVOS-CONVERSE-1, PR #25),
both using dispatch suppression (the reserving spec's match-phase
or delivery-path code emits the dispatch directly; the orchestrator
MUST NOT re-dispatch on the returned Match).

Renumbers prior §7.3 (in-process equivalence) to §7.4.

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

Three coordinated changes to make the pipeline-plugin model fully
uniform and to remove a structural exception that OVOS-CONVERSE-1
required:

- §4.2 — drop the "plugin MUST NOT emit during match" prohibition.
  The match contract is the single obligation: return a Match or
  null. Plugins MAY emit bus Messages during match if their
  matching strategy requires it (a converse plugin polling active
  handlers is the canonical case). The spec retains a SHOULD NOT
  on session-field mutation during match — declined-match
  corruption is still real for the few session fields used by
  later iteration stages, but bus emissions are not policed.

- §7.0 (new) — codify identifier polymorphism explicitly. Three
  component shapes own dispatched handlers: plain skill, plain
  pipeline plugin with bundled handler, hybrid plugin-skill. For
  the hybrid case, "if a pipeline plugin registers any intent
  under OVOS-INTENT-4, that plugin's pipeline_id MUST equal its
  skill_id" — one identifier, two roles. skill_id conceptually
  names the voice application; pipeline_id names the matching
  engine; a component that is both carries one identifier filling
  both roles.

- §7.1 — replace the exclusive "stamp skill_id XOR pipeline_id"
  rule with two independent stamps. context["skill_id"] applies
  when owner_id is registered as a skill; context["pipeline_id"]
  applies when owner_id is loaded as a pipeline plugin. For a
  hybrid plugin-skill both stamps land and carry the same
  identifier.

- §7.3 — simplify the reserved-intent-name registry. Drop the
  "dispatch suppression" clause that bent §6's match-then-
  dispatch flow. A reservation is now a namespace lease only:
  skills can't register the name, and a pipeline plugin's Match
  bearing the name dispatches normally per §7 (orchestrator emits
  <owner_id>:<reserved_name>, handler subscribed to that topic
  runs). Updated the converse/response entries in the table to
  reflect this — the converse plugin polls active handlers via
  whatever wire shape OVOS-CONVERSE-1 defines (no longer the
  dispatch topic), then returns a Match that the orchestrator
  dispatches normally.

Also updates §4.1 Match.owner_id description to cite §7.0's
three-shape model rather than the prior implicit OR.

Companion PR: OVOS-CONVERSE-1 (#25) needs a matching restructure
to drop its dispatch-suppression dependency and rely on the
relaxed §4.2 instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JarbasAl JarbasAl force-pushed the spec/converse1-active-handlers branch from 6849af0 to 5cd70e2 Compare May 26, 2026 13:38
JarbasAl added a commit that referenced this pull request May 26, 2026
…NVERSE-1)

OVOS-CONVERSE-1 (PR #25) claims two session fields under SESSION-1
§2.1's field registry:

- active_handlers — recency stack of [owner_id, activated_at] pairs
- response_mode   — single owner_id string when set, absent otherwise

Add both to the §3 informative roster of claimed fields and to the
§6 owner-spec list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 26, 2026
Update the forward-reference now that the companion imperative
continuous-dialog specification (OVOS-CONVERSE-1, PR #25) is in
flight. Names the two session fields it owns
(response_mode, active_handlers) and cites the §7 evaluation-
order rule that disambiguates the two surfaces for the
orchestrator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 26, 2026
CONVERSE-1 §3.4 (PR #25) explicitly cites the metadata-transformer
hook as the recommended position for mutating session.active_handlers
and session.response_mode. Add this to the §3.3 list of permitted
mutations, with the §5.4 cancellation-semantics back-reference for
mid-wait holder changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 26, 2026
Add new error_code 'reserved_name' and a §3.5 cross-reference to
OVOS-PIPELINE-1 §7.3's reserved-intent-name registry. The orchestrator
MUST reject registration payloads that name an intent_name reserved
by a companion specification.

Currently reserved: converse and response, both by OVOS-CONVERSE-1
(PR #25). The reservation registry is owned by PIPELINE-1; this
spec is the enforcement point at registration time.

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

OVOS-CONVERSE-1 (PR #25) fills the multi-turn conversation gap that §7 previously listed as planned work. Update §1.2 stack description to include it, and drop the §7 gap entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 26, 2026
* SESSION-1 v1: prescriptive session-carrier spec

Formalizes Message.context.session as a versioned carrier. Lists only
the fields already claimed by normative specifications in flight
(MSG-1 §4: session_id, lang; PIPELINE-1 §5: pipeline; CONTEXT-1 §2:
context; TRANSFORM-1 §5: six *_transformers). Fixes a field-registry
mechanism (§2.1) so future specs can claim further fields without
amending this document.

Prescriptive, not descriptive — fields carried by current
implementations but unclaimed by any spec are explicitly out of scope.

Closes the OVOS-MSG-1 §4 deferral ("to be formalized in a future
session specification").

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

* SESSION-1: take ownership of lang + add language-resolution hierarchy

§3 table: lang owner is §3.2 (this spec), not MSG-1.
§3.2 (new): full lang definition — BCP-47, session vs payload
language split, data.lang vs session.lang distinction, and the
binding language-resolution priority order (stt_lang > request_lang
> detected_lang > data.lang > session.lang > deployment default).
Future specs introducing a new language signal MUST declare where
they insert in this order.
§3.3: wire weight (renumbered).

Intro: stripped meta-commentary; SESSION-1 stands on its own.

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

* SESSION-1 §3.2: TTS keys on payload data.lang, not session.lang

TTS narrates already-produced text whose language was fixed at
render time. session.lang is the fallback only when the payload
carries no language signal.

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

* SESSION-1 §3.2: language signals as session-scoped fields, optional consolidation

Adds stt_lang, request_lang, detected_lang as session fields
alongside lang. Each has a normative meaning (what kind of language
signal it carries, who populates it). Consolidation into a single
value for an operation is left to the consumer / orchestrator and is
stage-dependent — the spec describes shape, not policy. §3.2.5 lists
a sensible default ordering as informative guidance only.

data.lang remains per-payload and is explicitly NOT a session field;
consumers reading payload content language read data.lang directly.

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

* SESSION-1 §3.2.2: secondary_langs field — ordered fallback pool

Adds secondary_langs as a session field: BCP-47 array of additional
languages the participant speaks/understands, ordered by preference,
not including lang. Three normative use cases called out:
constraining lang-detection predictions to the candidate set;
fallback selection when lang cannot be served; gating outputs in
unrecognized languages.

Hint not an authorization boundary — consumer MAY ignore.

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

* SESSION-1 §3.2.4: request_lang is an emitter-reported hint, not an override

Reframes request_lang per design clarification: it carries the
language the emitter expected/reported at emission time (e.g. the
language assigned to the triggering wake word in a multi-wakeword
setup), not an authoritative pin.

User may speak a different language than the emitter expected;
downstream stages MUST NOT treat request_lang as a guarantee and
MUST NOT override contradictory stt_lang / detected_lang values on
its strength alone. May be used as a prior / tie-breaker.

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

* SESSION-1 §3.2.3: tts_lang field — output preference independent of input

Adds tts_lang as a session field carrying the participant's
preferred output language. When omitted, assistant replies in input
language (status quo). When set, text-rendering stages SHOULD render
in tts_lang if capable, otherwise fall back to secondary_langs / a
translation transformer.

Does not influence TTS voice directly (voice keys on data.lang per
§3.2.1) — but determines what data.lang the upstream renderer sets,
so the cascade is single-preference-controls-all-output-stages.

Renumbers §3.2.4–§3.2.8 to accommodate.

Simplifies the bidirectional-translation transformer use case from
'always translate I/O' to 'translate post-hoc only when renderer
cannot natively serve tts_lang'.

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

* SESSION-1 §3.2.3: rename tts_lang → output_lang

Field affects every text-rendering stage (dialog, prompt, response
composition, GUI text), not just TTS. output_lang reflects the
broader scope.

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

* SESSION-1 §2.5: tighten omission-as-single-deferral-mechanism with RFC 2119

Adds explicit MUSTs: producer MUST defer by omitting; consumer MUST
fill with its deployment default. Spec provides no other deferral
surface (no null, no sentinel, no unset Message). Closes the
implicit-normative-prose gap flagged in the review.

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

* SESSION-1: rename context field to intent_context (CONTEXT-1 rename)

* SESSION-1 §3.3, §3.4: claim blacklisted_skills, blacklisted_intents, site_id

Three new session fields registered in §3:

- blacklisted_skills / blacklisted_intents (§3.3) — per-session
  negative pipeline filters. Pipeline plugins SHOULD honour them;
  internal handling of would-match-but-blacklisted candidates is
  unspecified. The orchestrator MUST act as backstop and filter any
  non-conformant plugin's returned match, treating a filtered match
  as a decline.

- site_id (§3.4) — opaque group identifier. Field slot reserved in
  the registry to prevent future collision; no consumer is defined
  by SESSION-1 and no value (including "unknown") carries reserved
  meaning.

§4.1 default-materialization rule extended to forbid populating any
of the three new fields when synthesizing a default session.

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

* SESSION-1 §3.3: require fully-qualified <owner_id>:<intent_name> for blacklisted_intents

intent_name is only unique within an owner. Allowing a bare
intent_name form would let a single entry silently denylist every
same-named intent across every skill and pipeline plugin in the
deployment — a sharp footgun. Restrict the field to the dispatch-topic
shape <owner_id>:<intent_name>.

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

* SESSION-1 §3.3: note layer-2 use of denylists for multi-tenant authorization

Informative paragraph: the denylists composed with session_id /
source / destination give a layer-2 substrate (HiveMind being the
canonical example) the authorization surface needed for multi-tenant
setups, without SESSION-1 itself defining an authorization model.
The §4 propagation rule plus the orchestrator backstop of §3.3 mean
the policy enforces end-to-end through the single-flip routing
model with no per-hop re-authorization.

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

* SESSION-1 §3.3: drop HiveMind name from layer-2 paragraph

Normative spec bodies stay implementation-agnostic — concrete
projects belong in APPENDIX.md, not in the spec text. The paragraph
now refers to "a layer-2 system (per OVOS-MSG-1 §3.4 / §4.4)"
abstractly.

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

* SESSION-1: move denylist semantics to PIPELINE-1; add blacklisted_pipelines slot

Per the registry pattern §3 already follows (pipeline → PIPELINE-1,
intent_context → CONTEXT-1, *_transformers → TRANSFORM-1), the
denylist fields' semantics belong in their owner spec, not in
SESSION-1. Runtime filtering of match results is PIPELINE-1's
concern.

- Drop §3.3 body (Negative pipeline filters) entirely. The
  SHOULD/MUST plugin contract, orchestrator backstop, and informative
  layer-2 paragraph move to PIPELINE-1 §5 (separate PR).
- Update §3 registry: blacklisted_skills / blacklisted_intents now
  point at OVOS-PIPELINE-1 §5 as owner.
- Add third row blacklisted_pipelines (orchestrator skips them
  during iteration); also owned by PIPELINE-1 §5.
- Renumber §3.4 site_id → §3.3, §3.5 Wire weight → §3.4.
- Reword §3.3 site_id paragraph to drop "current OVOS code"
  reference per the spec-bodies-stay-implementation-agnostic rule.
- Extend §4.1 default-materialization forbidden-fields list with
  blacklisted_pipelines.

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

* SESSION-1: fix internal cross-refs and producer override-field list

- §3.2 preamble: §3.2.5 → §3.2.7 (the informative consolidation section).
- §3.2.5 request_lang body: §3.2.3 → §3.2.4 (stt_lang) and §3.2.5 → §3.2.6 (detected_lang).
- §3.2.3 output_lang body: data.lang reference §3.2.1 → §3.2.8 (the per-payload section, not the session-preference lang).
- §6 producer-MUST-NOT list: the per-component override enumeration named the field as "context"; the field is intent_context. Extended the list to include all current override fields (the three blacklists and site_id were missed when they were added).
- §6 trailing pointer §3.3 → §3.4 (Wire weight, after the earlier renumbering).

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

* SESSION-1 §4.1: back-cite MSG-1 §4.3 'any device-local fields' permission

Audit B7: MSG-1 §4.3 lets a materialized default carry 'any
device-local fields the implementation chooses'; SESSION-1 §4.1
narrows that for the §3 closed set. Not a contradiction (later spec
wins) but the relationship was implicit. Added an explicit sentence
naming the narrowing and a closing sentence noting fields outside
the §3 closed set remain governed by MSG-1 §4.3 alone.

Avoids touching MSG-1 itself (which would require a version bump
for what is effectively a back-cite).

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

* SESSION-1 §3.4: promote wire-weight clause to canonical SHOULD-omit rule

Establish §3.4 as the single canonical statement of the
omit-when-wire-equivalent-to-omission rule for the entire spec
family. Three named cases:

1. session_id == 'default' — SHOULD omit, since absent / {} /
   'default' are all wire-equivalent per §3.1.
2. Per-component override matching the deployment default —
   SHOULD NOT populate (existing rule, preserved).
3. Empty array on a list-valued override — wire-equivalent to
   omission, SHOULD omit. Covers blacklists, *_transformers,
   pipeline.

The rule is SHOULD, not MUST: redundant default-valued fields
are non-optimal but conformant. Other specs claiming fields via
§2.1 inherit this rule without restating it.

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

* SESSION-1 §3, §4.1: register six blacklisted_*_transformers fields

Reserve the six per-type transformer denylist slots in the §3
registry, all pointing at OVOS-TRANSFORM-1 §5.2 as owner. Parallel
to the existing six *_transformers chain-override fields owned by
OVOS-TRANSFORM-1 §5.1; completes the per-type partition.

§4.1 default-materialization forbidden list extended with the six
new fields.

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

* SESSION-1 §3, §6: register active_handlers and response_mode (OVOS-CONVERSE-1)

OVOS-CONVERSE-1 (PR #25) claims two session fields under SESSION-1
§2.1's field registry:

- active_handlers — recency stack of [owner_id, activated_at] pairs
- response_mode   — single owner_id string when set, absent otherwise

Add both to the §3 informative roster of claimed fields and to the
§6 owner-spec list.

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

* SESSION-1 §1, §6: cite OVOS-SESSION-2 for lifecycle (in flight at PR #27)

OVOS-SESSION-2 is now the normative reference for session
lifecycle and resumption semantics that SESSION-1 §1 and §6
previously listed as out-of-scope future work. Replace the
forward-references with explicit cross-refs to SESSION-2.

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

* SESSION-1 §3: response_mode wire type is object (not string)

CONVERSE-1's session.response_mode is a structured object
{owner_id, expires_at, auto_continue} per its §2.2 (after the
OVOS-SESSION-2 §2.4 projection-mandate restructure). The
§3 roster row previously listed it as 'string' from the
original per-dispatch-marker design; sync to the current type.

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

* SESSION-1: fix MUST NOT / SHOULD NOT inconsistency in conformance §6

§3.4 body already states SHOULD NOT for emitting deployment-default
values. The producer MUST NOT in §6 conformance was stricter than
intended — a producer that cannot determine the deployment default
is non-optimal but conformant. Add a clarifying parenthetical.

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

* SESSION-1: fix §2 subsection ordering (2.5→2.1), bad MSG-1 §4.3 cross-refs, MUST NOT/SHOULD NOT inconsistency, active_handlers wire type

* SESSION-1: update active_handlers/response_mode wire types to match CONVERSE-1 simplification

* SESSION-1: update active_handlers wire type to array of {id, activated_at}

Reflects CONVERSE-1 change restoring activation timestamps.

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

* SESSION-1 §2.2: drop propagation/scope columns; state invariants in prose

All session fields propagate unchanged (MSG-1 §4) and are
session-scoped by definition. No use case exists for non-propagating
or per-message fields; the machinery introduced error handling
and edge cases without benefit.

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

* SESSION-1 §2.2: remove propagation/scope steps; update stale references

Steps 3 (propagation) and 4 (scope) dropped from the claim checklist.
All session fields propagate unchanged and are session-scoped by
definition — no per-field override mechanism exists. Updated §4
propagation prose and §6 conformance checklist to match.

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

* SESSION-1: final review fixes

- §3.1: clarify "default" means "interact with device-local session",
  not "originated from device"; document legitimate remote-client
  impersonation pattern (remote control, home automation); layer-2
  auth concern, not spec's to gate
- §3.2.2: secondary_langs MUST NOT contain lang "at time of emission"
- §3.2.7: replace single priority chain with per-stage guidance;
  informative chain was wrong for TTS (should key on data.lang, not
  request_lang)
- §3.3 site_id: replace placeholder text with concrete definition;
  primary consumer is routing/output-locality policy; MUST NOT be
  overwritten by forwarding components
- §4: add SESSION-2 §2.6 cross-ref to mutation permission bullet
- §4.1: make materialization exclusion rule abstract (no hardcoded
  field names); durable against future registry additions
- §6: tighten producer propagation rule to allow mutations at
  permitted boundaries (SESSION-2 §2.6)

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 26, 2026
* docs: OVOS-PIPELINE-1 — pipeline-plugin abstraction (draft)

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>

* pipeline: keep session.pipeline; constrain owner_id colon rule

- 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>

* pipeline §7: forbid `:` in skill_id / pipeline_id / intent_name

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>

* PIPELINE-1: release as v2 (incompatible with current OVOS)

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>

* pipeline: drop transformer chain (out of scope); refine V2 rationale

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>

* README: PIPELINE-1 reading-order — host-side → orchestrator-side; drop transformers mention

* scope: drop APPENDIX/CHANGELOG/README; PR #11 is pipeline.md only per 1-file rule

* OVOS-PIPELINE-1: orchestrator may be split across cooperating processes

§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>

* PIPELINE-1: SESSION-1 / MSG-1 hoists, trio MUST, per-pipeline introspection

§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>

* PIPELINE-1 §8: handler-trio is orchestrator-owned, not handler-owned

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>

* PIPELINE-1 §4: state utterance argument shape explicitly at the plugin 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>

* PIPELINE-1 §9.1: de-prescribe recognizer_loop:utterance topic name

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>

* PIPELINE-1 §10: clarify split-orch ownership of per-pipeline_id queries

* PIPELINE-1 §7.1: orchestrator stamps context.skill_id on dispatch

Per design clarification: the dispatch topic <skill_id>:<intent_name>
implicitly tells the orchestrator which skill_id to stamp on
context. By stamping at dispatch time, all skill emissions derived
from the dispatch (via forward/reply) inherit the correct
skill_id — INTENT-4 §3.1's loader-side enforcement becomes
automatic for the dispatch path. Any drift between
context.skill_id and the dispatch <owner_id> on a skill emission
is non-conformant and detectable.

Plugin-bundled handlers (owner_id is a pipeline_id, not a
skill_id) do NOT get skill_id stamped — they identify via
pipeline_id, per the existing dispatch polymorphism.

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

* PIPELINE-1 §5: claim the three blacklist session fields with full semantics

SESSION-1's §3 registry pattern: SESSION-1 fixes the field slot, the
owner spec defines semantics. The denylist fields registered in
SESSION-1 §3 point here; this commit writes the semantics they need.

§5 restructured into a multi-field section:

- §5.1 session.pipeline (the existing body, unchanged)
- §5.2 session.blacklisted_pipelines — orchestrator-only filter,
  exists because pipeline is a positive whitelist and an origin that
  doesn't know the deployment default needs a negative knob
- §5.3 session.blacklisted_skills — two-tier (plugins SHOULD
  honour, orchestrator MUST backstop). Plugin internal handling of
  would-match-but-blacklisted candidates is explicitly unspecified
- §5.4 session.blacklisted_intents — same two-tier shape. Entries
  MUST be fully-qualified <owner_id>:<intent_name>; bare intent_name
  is rejected to prevent silent cross-skill collision
- §5.5 Composition — effective pipeline = (session.pipeline OR
  deployment default) minus blacklisted_pipelines, computed once
  per utterance. blacklisted_skills/_intents apply per Match during
  iteration. Explicit pipeline wins; blacklisted_pipelines is the
  ergonomic alternative when the origin lacks default knowledge
- §5.6 (informative) Layer-2 substrate authorization — denylists +
  source/destination + session_id give a layer-2 substrate the
  authorization surface for multi-tenant setups, riding the
  single-flip routing model with no per-hop re-authorization

§1 scope updated to list the four session fields owned here.

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

* PIPELINE-1 §5: fix composition rule — policy overrides preference, not alternatives

Previous wording in §5.5 framed session.pipeline and
blacklisted_pipelines as alternatives ("explicit pipeline wins;
blacklist is the ergonomic alternative"). That misread the model.

Correct layering:

- session.pipeline is the **preference channel** — any session
  origin (local, remote, layer-2-attached, programmatic) MAY
  populate it as a request. No authorization implied.
- The orchestrator narrows the request to what is loaded
  (availability) and what policy permits (the denylists).
- Denylists override preference: a pipeline_id requested in
  session.pipeline and also listed in blacklisted_pipelines is
  dropped. Same per-Match for blacklisted_skills/_intents.

§5.5 rewritten around three explicit stages: preference,
availability, policy. Empty effective pipeline → no-match, without
silent fallback to the default-session pipeline (a fallback would
let a policy-rejected request pull in an ordering nobody approved).

§5.1 reframed as preference channel; clarified that the
"deployment default" is in practice the pipeline configured for
the reserved session_id == "default" session (OVOS-SESSION-1 §3.1),
which the orchestrator owns.

§5.2 reframed as policy channel; explicit "MUST NOT be invoked
even if requested in session.pipeline".

§5.6 reframed around the intended client/layer-2 split: clients
request via session.pipeline; layer-2 substrates that own the
session enforce via the denylists. The two roles use different
fields, and §5.5 layering makes enforcement automatic.

Also scrubbed example pipeline_ids ("padatious-*", "adapt-*")
that named real engines, replacing with generic "template-*" /
"keyword-*" per the no-named-projects rule for spec bodies.

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

* PIPELINE-1: claim context['pipeline_id'] for plugin self-identification; fix §5.4 lang note; tighten §9.1 lang fallback

Three fixes from the cross-spec audit:

- §3.1 (new): plugins MUST stamp Message.context['pipeline_id']
  on every emission, parallel to OVOS-INTENT-4 §3.1's
  context['skill_id'] rule for skills. This gives downstream specs
  (CONTEXT-1 §5.2 attribution of plugin-emitted ovos.context.set;
  future telemetry / audit consumers) a wire-level source for
  plugin attribution. Includes the emitter vs subject distinction
  (context['pipeline_id'] vs data['pipeline_id']), reserved-key
  precedence on derivations, mutual exclusion with
  context['skill_id'] (a Message carries one or the other, never
  both), and orchestrator-side loader enforcement.

- §5.4 blacklisted_intents: added paragraph noting the field is
  language-agnostic. INTENT-4 §3.2 keys intent identity on
  (skill_id, intent_name, lang); blacklisted_intents keys only on
  (owner_id, intent_name) so a single entry denies both en-US and
  de-DE registrations of the same intent. Per-language denial is
  expressed via session.lang narrowing the matchable set.

- §9.1 entry-topic lang field: previously anchored a normative
  MUST on OVOS-SESSION-1 §3.2.7 — which is explicitly informative
  consolidation guidance. Rewrote to name two normative sources
  (session.lang from §3.2.1, then deployment default) and note
  that the §3.2.7 cascade applies to downstream stages, not to
  this entry-point field.

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

* PIPELINE-1 §3.1: drop spurious INTENT-4 §3.1.4 sub-section ref

INTENT-4 §3.1's emitter-vs-subject treatment is in an unnumbered
subsection; cite the parent §3.1 to avoid drift.

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

* PIPELINE-1 §3.1: drop strip-and-mutual-exclude rule; allow identity keys to coexist

Reviewer feedback: the prior 'Reserved-key precedence' paragraph
required plugins emitting via forward/reply to strip an inherited
context['skill_id'] and forbade the two keys appearing together.
That conflicts with the natural MSG-1 §5 derivation semantics
(forward preserves context unchanged) and breaks chain
traceability — a plugin emission flowing from a skill dispatch
legitimately carries both keys: the skill it dispatched from AND
the plugin actually emitting.

Reworked the paragraph to acknowledge coexistence: forward/reply
preserve upstream identity stamps; the emitting plugin
additionally stamps its own pipeline_id. When an attribution
consumer needs to pick one, it uses most-specific identity wins,
codified in CONTEXT-1 §5.2.

Also reworded 'is the emitter' to 'is the plugin's
self-attribution' since 'emitter' is ambiguous in chains crossing
multiple component types.

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

* PIPELINE-1 §3.1: split stamp rule into originate vs modify-in-place; update transformer cross-ref

Aligning with the uniform MUST-stamp-on-originate-or-modify rule
adopted across INTENT-4, PIPELINE-1, and TRANSFORM-1. The plugin
binds the context['pipeline_id'] stamp obligation both on free-form
bus emission (originate) and on in-place mutation of a Message it
processes (modify in place).

Updated the coexistence paragraph to reference the new
TRANSFORM-1 §1.3 per-type keys (six <type>_transformer_id slots,
not a single generic transformer_id) and to point to the
lifecycle-position precedence in CONTEXT-1 §5.2.

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

* PIPELINE-1 §3.1: stamp rule covers derivations placed on the bus by the plugin

User feedback: a Message.reply or .response derivation that the
plugin performs and emits IS a plugin emission — the resulting
Message-on-wire is caused by the plugin and MUST carry
context['pipeline_id']. Earlier wording carved derivations out
('a fresh emission, not a forward/reply/response derivation'),
which was wrong.

Rewrote the stamp rule around 'places on the bus' covering both
fresh emissions and derivations (forward, reply, response) the
plugin performs and emits. The derivation mechanism is irrelevant;
what binds is that the plugin caused a Message to appear on the
bus. Modify-in-place rule preserved as a separate clause covering
context/data/session mutations that don't themselves emit.

Also updated the cross-spec ref to the new
<type>_transformer_ids (plural-list) shape from TRANSFORM-1 §1.3.

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

* PIPELINE-1 §5.2 §5.3 §5.4: drop [] != omission distinction; align with SESSION-1 §3.4 SHOULD-omit

Per design decision (option B): the deliberate '[] is explicit
none-denied, distinct from omission' semantics in the blacklist
fields are dropped. [] is now wire-equivalent to omission for all
three denylist fields; both resolve to the deployment default at
consumption per SESSION-1 §2.5. Producers SHOULD omit empty
arrays rather than emit them.

The use case the distinction served — a session explicitly
clearing a deployment-default denial via [] — is rare enough to
not warrant the footgun. Layer-2 substrates wanting to grant
'bypass deployment default' permissions to a peer should use a
smaller deployment default in the first place.

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

* PIPELINE-1 §3.1, §6.1, §6.2: forward exemption + post-match-pre-dispatch window + circuit-breaker

Three audit findings actioned:

- §3.1 stamp rule: pure-forward propagation exempt. .reply and
  .response are authorial actions that MUST stamp pipeline_id;
  .forward is propagation and MUST NOT overwrite an inherited
  context['pipeline_id']. Preserves upstream attribution chains
  when a downstream plugin merely propagates an earlier plugin's
  emission. Authors wanting to claim authorship use .reply,
  .response, or fresh emit.

- §6.1 lifecycle pseudocode: added the post-match-pre-dispatch
  window where CONTEXT-1 §5.3 sanctions engine-side context
  mutation and TRANSFORM-1 §3.4 inserts the intent-transformer
  chain. Also annotated where the utterance, metadata, dialog,
  and TTS transformer chains plug in. A reader of PIPELINE-1
  alone no longer gets a wrong picture of the lifecycle ordering.

- §6.2 plugin-exception handling: added a circuit-breaker clause.
  Orchestrators SHOULD track per-plugin exception counts and
  SHOULD drop a plugin from the session's effective pipeline
  after a deployer-tunable threshold (typically three) of
  consecutive failures. Optional informative
  ovos.pipeline.dropped diagnostic event named.

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

* PIPELINE-1 §9.1: rename entry topic recognizer_loop:utterance -> ovos.utterance.handle

The Mycroft-era recognizer_loop:utterance fails the naming
conventions of OVOS-MSG-1 §2.1.2 on three counts:

- ':' used as a segment separator, where ':' is reserved for the
  <owner_id>:<intent_name> dispatch topic shape (MSG-1 §2.1.1).
- Leading segment names an implementation role ('recognizer loop',
  the Mycroft audio-input service) rather than a stable
  assistant root.
- Does not pair with the past-tense terminal event
  ovos.utterance.handled per the request/terminal verb-pairing
  convention of MSG-1 §2.1.2(d).

Renamed to ovos.utterance.handle: dot-separated hierarchy, stable
ovos. root, imperative-mood request verb pairing with handled.

§9.1 now prescribes the topic name directly; removed the
'name deferred to a future spec' language. §6.1 lifecycle diagram
and other in-spec entry-topic refs updated. Migration cost
acknowledged: a transitional deployment MAY subscribe to both
names; the legacy topic has no normative status under this spec.

Depends on MSG-1 v2 (spec/msg1-v2-topic-naming) for the §2.1.2
naming conventions cited.

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

* PIPELINE-1 §9.1: drop migration meta-commentary

Remove the 'Migration from recognizer_loop:utterance' paragraph.
The spec now states only the current normative rule
(ovos.utterance.handle is the entry topic). Historical
predecessor mapping lives in APPENDIX §6.7.

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

* PIPELINE-1 §4, §9.1: match signature takes explicit optional lang; orchestrator MUST NOT synthesize

§4 match contract: signature becomes match(utterance, lang,
session). lang is OPTIONAL, sourced from Message.data.lang.
Orchestrator passes it through when present; MUST NOT synthesize
from session.lang or any §3.2 signal when absent. Plugin that
needs language and receives lang=None MAY consult session for
§3.2 signals or apply its own policy. Same shape and rationale
as TRANSFORM-1 §3.0.

§9.1 entry-topic lang field: rewritten. Previous wording said
the orchestrator MUST fall back to session.lang — that was
wrong; the orchestrator MUST NOT fabricate a value. data.lang
is present only when the producer authoritatively knows the
content language; its absence is a faithful 'unknown' signal
that consumers resolve per their own stage-appropriate policy.

§6.1 pseudocode and §11 conformance refs updated for the new
match signature.

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

* PIPELINE-1: post-audit fixes — Match.lang, empty-list short-circuit, cancellation terminal row, conformance entry-topic

Four audit findings actioned:

- §4.1 Match shape: add optional 'lang' field. Resolves the
  TRANSFORM-1 §3.0 contradiction where intent transformers were
  told to read Match.lang as the language source, but Match
  didn't declare the field. Authoritative-by-construction for a
  plugin that received a non-None lang parameter; plugins that
  determine lang otherwise (multilingual matcher, content-
  language detector, hard-coded engine) MAY set it freely;
  absent when the plugin doesn't commit to a language.

- §6.2 empty-list short-circuit: when the utterance-transformer
  chain returns an empty list, the orchestrator MUST NOT invoke
  any pipeline plugin (match()'s input contract is non-empty).
  Proceeds directly to complete_intent_failure. Cancellation
  context (TRANSFORM-1 §8.1) on the same return takes
  precedence over no-match.

- §6.4 terminal-events table: cancellation added as a third
  row, cross-referencing TRANSFORM-1 §8.2. The 'every utterance
  ends in handled' invariant remains intact.

- §11 conformance: subscribe to ovos.utterance.handle directly,
  dropping the stale 'name deferred; current deployments use
  recognizer_loop:utterance' bullet. §9.1 has already prescribed
  the new name; the conformance bullet was contradicting its
  own spec.

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

* PIPELINE-1 §7.3: reserved-intent-name and dispatch-suppression mechanism

Add a §7 subsection codifying the registry mechanism by which other
specs reserve intent_names and modify dispatch semantics. Currently
in force: converse and response (both per OVOS-CONVERSE-1, PR #25),
both using dispatch suppression (the reserving spec's match-phase
or delivery-path code emits the dispatch directly; the orchestrator
MUST NOT re-dispatch on the returned Match).

Renumbers prior §7.3 (in-process equivalence) to §7.4.

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

* PIPELINE-1 §4.2, §7.0, §7.1, §7.3: relax match contract; explicit polymorphism; drop dispatch suppression

Three coordinated changes to make the pipeline-plugin model fully
uniform and to remove a structural exception that OVOS-CONVERSE-1
required:

- §4.2 — drop the "plugin MUST NOT emit during match" prohibition.
  The match contract is the single obligation: return a Match or
  null. Plugins MAY emit bus Messages during match if their
  matching strategy requires it (a converse plugin polling active
  handlers is the canonical case). The spec retains a SHOULD NOT
  on session-field mutation during match — declined-match
  corruption is still real for the few session fields used by
  later iteration stages, but bus emissions are not policed.

- §7.0 (new) — codify identifier polymorphism explicitly. Three
  component shapes own dispatched handlers: plain skill, plain
  pipeline plugin with bundled handler, hybrid plugin-skill. For
  the hybrid case, "if a pipeline plugin registers any intent
  under OVOS-INTENT-4, that plugin's pipeline_id MUST equal its
  skill_id" — one identifier, two roles. skill_id conceptually
  names the voice application; pipeline_id names the matching
  engine; a component that is both carries one identifier filling
  both roles.

- §7.1 — replace the exclusive "stamp skill_id XOR pipeline_id"
  rule with two independent stamps. context["skill_id"] applies
  when owner_id is registered as a skill; context["pipeline_id"]
  applies when owner_id is loaded as a pipeline plugin. For a
  hybrid plugin-skill both stamps land and carry the same
  identifier.

- §7.3 — simplify the reserved-intent-name registry. Drop the
  "dispatch suppression" clause that bent §6's match-then-
  dispatch flow. A reservation is now a namespace lease only:
  skills can't register the name, and a pipeline plugin's Match
  bearing the name dispatches normally per §7 (orchestrator emits
  <owner_id>:<reserved_name>, handler subscribed to that topic
  runs). Updated the converse/response entries in the table to
  reflect this — the converse plugin polls active handlers via
  whatever wire shape OVOS-CONVERSE-1 defines (no longer the
  dispatch topic), then returns a Match that the orchestrator
  dispatches normally.

Also updates §4.1 Match.owner_id description to cite §7.0's
three-shape model rather than the prior implicit OR.

Companion PR: OVOS-CONVERSE-1 (#25) needs a matching restructure
to drop its dispatch-suppression dependency and rely on the
relaxed §4.2 instead.

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

* PIPELINE-1 §4.1, §4.2, §6: Match.updated_session as the match-phase session-mutation channel

Per current ovos-core (sess = match.updated_session or
SessionManager.get(message)), a plugin's match operation MAY
mutate session state and return the new snapshot via the
optional Match.updated_session field. The orchestrator then uses
that snapshot for the dispatch and every downstream stage; a
declining plugin returns null, so its experimental mutations are
discarded at the plugin boundary and never reach later plugins
in iteration.

Updates:
- §4.1 Match table: add updated_session optional field
- §4.2: explicit mechanism + 'plugins that mutate session in
  place without populating updated_session are non-conformant'
- §6 flow diagram: insert 'session = match.updated_session or
  session' immediately after a non-null match, before the
  post-match-pre-dispatch window

This is what makes match-phase mutation safe under §6.2
first-match-wins: only the claiming plugin's session changes
land, declined plugins' mutations are scoped to their match call
and discarded.

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

* PIPELINE-1 §7.0, §7.1: collapse handler-owner shapes; pipeline_id == skill_id always

Per design feedback: the prior 'hybrid plugin-skill vs plain
plugin with bundled handler' distinction wasn't load-bearing.
Collapse to two shapes:

- Plain skill: handler reached via skill_id, intents matched by
  some other plugin via INTENT-4 registrations
- Pipeline plugin with bundled handlers: handler reached via
  pipeline_id == skill_id (the plugin owns its own intent
  matching and handler dispatch); MUST NOT register its
  intent_names under INTENT-4 (would create circular dependency)

Passive-index registration: a pipeline plugin with handlers
SHOULD publish its intent_names via the per-pipeline
introspection topic ovos.pipeline.<pipeline_id>.intents.list so
deployment-wide tools that enumerate handlers can see them.
This is one-way declaration, not INTENT-4 registration.

§7.1 stamping simplified accordingly:
- context['skill_id'] = owner_id ALWAYS (skill_id is the
  universal voice-app identity)
- context['pipeline_id'] = owner_id WHEN owner is loaded as a
  plugin (both keys carry the same value for plugin-handlers)

§4.1 Match.owner_id description simplified to two shapes.

skill_id is conceptually the voice_app_id; pipeline_id is the
matching-engine id; a plugin that is both carries one identifier
filling both roles.

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

* PIPELINE-1 §7.0: pure-matcher plugin shape; plain skill + reserved intent_names

Two clarifications to the handler-owner table:

- A 'pure-matcher' pipeline plugin shape exists alongside the
  two handler-owner shapes. Padatious / Adapt (skill-intent
  matchers) and the OVOS-CONVERSE-1 converse plugin (reserved-
  intent-name matcher) are pure matchers: they are pipeline
  plugins per §3 but not handler-owners; their match returns
  matches whose owner_id is some OTHER component's identity.
  They have a pipeline_id but no skill_id.

- A plain skill may also handle a reserved-intent-name dispatch
  topic when a companion spec defines one. A skill that
  implements a converse method subscribes to
  <own_skill_id>:converse via framework convention, not via
  INTENT-4 registration (which would be rejected per §7.3).
  The 'plain skill' row's INTENT-4 path is the normal case;
  reserved-name framework-convention subscriptions extend it
  without changing the dispatch shape.

Removes 'converse' from the bundled-handler-plugin examples
list — the converse plugin is a pure matcher, not a
bundled-handler plugin.

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

* PIPELINE-1 §4.2, §6: tighten match-phase scope; explicit null-match path

Two clarifications from the review:

- §4.2 'plugins MUST mutate via updated_session' explicitly
  scoped to match-phase only. Handler-time, transformer-time,
  and MSG-1-direct-mutation pathways for session changes are
  governed by their own specs and unaffected by §4.2's rule.

- §6 flow diagram restructured to show the null-match branch
  explicitly: 'if match is None: continue # any plugin-side
  updated_session is discarded'. Makes the F5 review point
  visible — declined plugins' updated_session never reaches
  the next iteration. Indentation adjusted accordingly for
  the subsequent ovos.intent.matched / dispatch / trio /
  end-marker block (one level shallower now that the
  inner-if collapsed).

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

* PIPELINE-1 §2: brief note on SESSION-2 stateless-for-named, owns-default model

PIPELINE-1's orchestrator role description predates SESSION-2
and didn't mention the statelessness model. A reader of
PIPELINE-1 alone might assume the orchestrator is fully
stateful. Add a one-paragraph note pointing to SESSION-2 as the
owner of the state-ownership model and stating the working
assumption: the orchestrator is stateless for named sessions
and holds persistent state only for session_id == 'default'.

Editorial; no normative behaviour change.

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

* PIPELINE-1: fix three spec bugs

- §11 conformance: remove incorrect "side-effect-free during match"
  MUST — §4.2 body explicitly permits bus emissions from match
- §9.1: fix bad cross-reference §2.1.2 → §2.1 (MSG-1 has no §2.1.2)
- §9.5: revert accidental status-field addition; payload MAY be empty

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

* MSG-1 §2.1.1: update identifier-constraint wording to per-topic-shape rule (PR #29)

* PIPELINE-1: fix 5 audit gaps

- §4.4 (new): match-phase timeout SHOULD, parallel to §8.3 handler timeout;
  timed-out match treated as exception, session unchanged, no bus event
- §4.1: Match.utterance made required; plugin that does not track winner
  MUST use first candidate as fallback; orchestrator forwards verbatim
- §7.1: dispatch.data.lang changed to conditional; populated from Match.lang,
  fallback to entry-topic lang, omitted when neither source provides one;
  handlers MUST treat as optional
- §6.1: clarify dialog/TTS chain timing — ovos.utterance.handled fires at
  handler completion, not after audio rendering; audio output is fully
  decoupled from pipeline (chat deployments have no audio output);
  dialog/TTS chains are causal, not synchronization barriers
- §9.3: complete_intent_failure payload table added (utterances, lang, both
  optional); consistent with other bus-event schemas in §9

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

* PIPELINE-1 §9.6: define ovos.utterance.speak as NL output exit point

Frames PIPELINE-1 as defining the natural-language I/O boundary of the
voice assistant: ovos.utterance.handle (entry) and ovos.utterance.speak
(exit) are the symmetric NL input and output surfaces. Everything between
is pipeline; everything downstream of speak (TTS, audio queueing, display)
is out of scope and belongs to the output-path companion specification.

- §1: add ovos.utterance.speak to scope bullet
- §6.1 flow diagram: add ovos.utterance.speak (×0..N) step; update
  "(TTS rendering)" label to "(output-path delivery)" to reflect decoupling
- §6.1 prose: reference §9.6 by topic name instead of "speak Message"
- §6.4 terminal events table: add speak ×0..N to matched-by-plugin path
- §9.6 (new): full definition — topic naming rationale, payload schema
  (utterance required, lang optional), derivation via forward/reply from
  dispatch (session + skill_id propagation), multiplicity rule (zero
  permitted for silent handlers), broadcast, output-path out of scope

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

* PIPELINE-1: simplification pass — 7 structural fixes

1. Fix §4.3/§4.4 ordering: capture map is §4.3, timeout is §4.4 (was swapped)
2. §3.1 stamp rule: drop modify-in-place paragraph (unenforceable); fix
   framing — .reply/.response are routing metadata, not authorial actions;
   only fresh constructions require stamping; all derivations preserve
   inherited context["pipeline_id"]
3. §3.1 coexistence paragraph: compressed from 4 sentences to 2
4. §7.0: remove "plain skill + reserved intent_names" paragraph → moved to
   §7.3; remove "passive index registration" SHOULD → moved to §10 intro;
   closing summary sentence retained
5. §2: drop "plugins are diverse" flavor paragraph; move deployment SHOULD
   (load at least one INTENT-4-consuming plugin) to §11 conformance under
   new "A deployment SHOULD" block
6. §5 intro: drop "§5.5 fixes..." navigation hint sentence
7. §11: drop Non-goals block (duplicates §1)

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

* PIPELINE-1 §3.1: rewrite identity stamping to two clear rules

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>

* PIPELINE-1 §3.1: simplify to two sentences

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>

* PIPELINE-1 §3.1: pipeline_id stamped at dispatch, propagates via MSG-1

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>

* PIPELINE-1: pre-merge fixes

- §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>

* PIPELINE-1 §7.0: drop two-shapes framing; dispatch is uniform

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>

* PIPELINE-1: replace owner_id with skill_id throughout

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>

* PIPELINE-1: reframe as foundational spec; drop INTENT-4 from builds-on

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>

* PIPELINE-1: rename complete_intent_failure → ovos.intent.unmatched

Follows MSG-1 dot-namespaced topic convention. Symmetric with
ovos.intent.matched.

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

* PIPELINE-1 §4.4: add latency discipline — defer long-running work to 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>

* PIPELINE-1: captures→slots, utterance→utterances param, drop 3 non-normative 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>

* PIPELINE-1 §6.1: dialog/TTS transformers run in output layer after .handled

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>

* PIPELINE-1 §6.2: trim circuit-breaker; drop ovos.pipeline.dropped topic

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>

* PIPELINE-1 §7.1: drop skill_id and intent_name from dispatch payload

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>

* PIPELINE-1 §7.1: mandate lang in dispatch payload

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>

* PIPELINE-1: mandate Match.lang — plugin owns language resolution

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>

* PIPELINE-1: drop §7.4 In-process equivalence

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

* PIPELINE-1 §6.5: long-running handlers and nested utterance lifecycles

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>

* PIPELINE-1: pre-merge polish pass

- §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>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 26, 2026
Split specs into intent / bus / orchestrator stacks. Add all 11
specs including in-review ones (INTENT-4 #9, INTENT-2 v3 #4,
TRANSFORM-1 #20, CONTEXT-1 #18, CONVERSE-1 #25). Add role-based
reading order.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 26, 2026
* docs: README — full spec-set refresh for the in-flight stack

Update the README to reflect the full spec set landing together:
the original intent stack (INTENT-1/-2/-3, MSG-1) plus the
in-flight specs (INTENT-4, SESSION-1, SESSION-2, PIPELINE-1,
TRANSFORM-1, CONTEXT-1, CONVERSE-1).

Changes:

- Specification table reorganised into three stacks — intent,
  bus, orchestrator — each with a one-paragraph narrative. This
  is the structure APPENDIX §1.2 already uses; the README now
  mirrors it for consistency.
- New 'Where to start' section with four reading-order paths
  matching common audiences: skill author, plugin author,
  orchestrator author, architecture surveyor. Addresses the
  'no clear entry point' friction first-time readers hit when
  the set went from 4 to 11 specs.
- New 'How this compares to other voice frameworks' section
  summarising APPENDIX §2's positioning (Home Assistant /
  hassil, Rasa, Alexa / Dialogflow, Rhasspy / Hermes, Wyoming).
  Brief; points at APPENDIX for detail.
- Reference-implementation section split: ovos-spec-tools
  covers the intent stack; bus and orchestrator stacks are
  acknowledged as not-yet-having-ground-up-reference-impl with
  pointer to APPENDIX §5 divergence catalogue.
- New 'Implementation status' section: clarifies the spec-set
  Draft→stable transition is tracked at #5; intent stack is
  most aligned with current ovos-core; known gaps cited from
  APPENDIX §7.
- Contributing section adds the one-file-per-PR rule (per
  AGENTS.md repo convention) and clarifies dev vs master
  targeting.
- Updated draft warning to reference APPENDIX §5 divergence
  catalogue and link to #5.

No normative-spec changes; README and supporting-metadata only.

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

* README: establish voice OS framing; add OS-analogy table

Replace "voice assistant ecosystem" opening with "voice operating
system" framing. Add "What a voice operating system is" section with
OS-analogy table (scheduler, IPC, shared memory, process supervision,
loadable modules, syscall ABI) and the portability consequence.

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

* README: full spec table — three stacks, open PR links

Split specs into intent / bus / orchestrator stacks. Add all 11
specs including in-review ones (INTENT-4 #9, INTENT-2 v3 #4,
TRANSFORM-1 #20, CONTEXT-1 #18, CONVERSE-1 #25). Add role-based
reading order.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 26, 2026
… PIPELINE-1 (#14)

* docs: APPENDIX — audit-driven corrections (pipeline + registration model)

Applies corrections found by auditing claims against actual OVOS
source code:

1. **§6.7 enable/disable_intent legacy names corrected** to the
   real `mycroft.skill.enable_intent` / `mycroft.skill.disable_intent`.

2. **§6.4 direct-bus-subscribe claim broadened** — verified the
   standard ovos-padatious-pipeline-plugin and
   ovos-adapt-pipeline-plugin both subscribe directly to
   registration topics, not just downstream plugins.

3. **§6.4 "side-effects during match" softened** — audit confirms
   the official match_* methods are already side-effect-free; the
   skill-activation emit is orchestrator-side, not plugin-side.
   Rule reframed as forward-looking discipline.

4. **§3 / §4 / §6.4: PIPELINE-1 *refines* the plugin model rather
   than *introducing* it.** OVOSPipelineFactory, pipeline_plugins
   dict, _PIPELINE_MIGRATION_MAP, and the official plugin set
   already exist. PIPELINE-1's actual contribution narrows to:
   formalizing the contract, `<owner_id>:<intent_name>`
   polymorphism, universal `ovos.utterance.handled` end-marker,
   and the renames.

5. **§3 / §4 / §6.4: tier convention is compatible, not a
   divergence.** From the bus each tier is already a distinct
   `pipeline_id` in `Session.pipeline`. How a Python plugin class
   internally serves multiple `pipeline_id`s (one class with
   match_high/medium/low methods, an orchestrator-side
   suffix-decoder, separate plugin instances, etc.) is
   implementation choice the spec does not constrain.

6. **§4 / §6.4: registrations-are-broadcast is compatible, not a
   divergence.** OVOS already broadcasts registrations on the
   bus; plugins already subscribe directly. INTENT-4 does not
   change this — it only renames topics into the `ovos.intent.*`
   namespace (see §6.7). Migration is a string replacement.
   What IS new is the orchestrator's passive registration index
   that backs `ovos.intent.list` / `.describe` — that's added as
   a separate §6.4 divergence ("new orchestrator responsibility,
   not a change to existing behaviour").

7. **§6.6 adds note on engine-specific introspection topics**
   (`intent.service.adapt.*`, `intent.service.padatious.get`) —
   plugin-defined surface; spec does not claim authority over
   them.

No spec-body changes; APPENDIX only.

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

* docs: APPENDIX §6.4 — drop the "dissolution" divergence

Same logic as the broadcast-registrations correction: the
orchestrator already treats every loaded plugin uniformly, and
`IntentHandlerMatch.match_type` is an opaque string the plugin
chooses — nothing in current code prevents a plugin from setting
`match_type = "<pipeline_id>:<intent_name>"` and being dispatched
to itself. The `<owner_id>:<intent_name>` polymorphism PIPELINE-1
names is therefore already supported; the spec only writes down a
convention current code allows but does not document.

Design rationale around the polymorphism stays in §3/§4 — it is
useful explicit naming. But it is not a divergence and should not
sit in the divergence catalogue.

§6.4 now contains a single real divergence: the orchestrator's
new passive registration index backing `ovos.intent.list` /
`.describe`. Everything else in §6.4 is forward-looking
discipline or a workshop bug, not an architectural change.

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

* APPENDIX: keep session.pipeline (revert the rename row)

PIPELINE-1 now keeps the existing `session.pipeline` field name
instead of renaming it to `pipeline_stages`. Drop the §6.2
rename row and revert the prose mentions.

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

* APPENDIX §7: note utterance-transformer chain as a deferred spec (out of scope for PIPELINE-1)

* APPENDIX §4 / §7: design notes for OVOS-CONTEXT-1 and OVOS-TRANSFORM-1

Per the new "dedicated APPENDIX PR" policy, consolidating the
prior-art and design-deviation notes from the OVOS-CONTEXT-1
(PR #18) and OVOS-TRANSFORM-1 (PR #20) work into this PR.
Those spec PRs are now scoped to their own spec files only;
the discussion / cross-spec touchups / in-tree prior art
all live here.

Adds to §4 Design rationale:
- "Intent context (CONTEXT-1)" — the Adapt-only origins, the
  two-scope (private/shared) formalization, jurebes /
  nebulento / palavreado as prior art for excludes_context,
  the engine-side §5.3 mutation pathway resolving the
  PIPELINE-1 §4.2 contradiction.
- "Transformer plugins (TRANSFORM-1)" — the architectural-
  pattern framing, intent transformers as the system-typing
  home, the nine concrete in-tree plugins as prior art, the
  ascending-vs-descending priority deviation called out,
  cancellation alignment with existing plugin convention,
  and the language disambiguation hierarchy mirroring current
  ovos-core code paths.

Removes from §7 Known gaps:
- "Intent context" bullet (formalized in CONTEXT-1).
- "The utterance-transformer chain" bullet (formalized in
  TRANSFORM-1).

* APPENDIX: SESSION-1 rationale; introspection patterns; revised divergences

§4 — new 'Session (SESSION-1)' rationale subsection: why it exists,
   prescriptive-not-descriptive scope, omission-as-deferral
   semantics, four language signals.
§4 'Transformer plugins' — language-disambiguation note updated:
   hierarchy moved out of TRANSFORM-1 to SESSION-1 §3.2; transformer
   types now just named as natural producers of signals,
   consolidation is consumer's stage-dependent choice.
§6.4 architectural divergences — add: handler-trio ownership shifted
   to orchestrator (third-party handler code carries no obligation);
   per-pipeline_id intent introspection (PIPELINE-1 §10); CONTEXT-1
   scope discriminator. Update ovos.utterance.handled note to
   reflect the trio-ownership shift (workshop fix is now in the
   wrapper, not the handler).
§6.5.1 (new) — introspection-patterns table comparing INTENT-4,
   PIPELINE-1, CONTEXT-1, TRANSFORM-1 surfaces. Three shared
   properties (pull-query is source of truth, no completeness
   signal, per-process slices under split orchestrators). Notes
   naming-convention inconsistency as candidate follow-up.
§6.6 — remove obsolete 'session shape deferred' note; replace with
   SESSION-1 ownership statement.

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

* APPENDIX: update §6.5.1 topic-naming (resolved); add new §6.4 divergences

§6.5.1: topic-naming inconsistency is now resolved — all four .list
   surfaces use ovos.<domain>.<verb>. Update the table and replace
   the 'not yet uniform' note with a rename log.
§6.4: add four new divergence entries:
   - Skill self-identification on every emission (INTENT-4 §3.1)
   - recognizer_loop:utterance de-prescribed (PIPELINE-1 §9.1)
   - .list topics standardized
   - (keeps the existing scope-discriminator / handler-trio /
     per-pipeline_id / utterance.handled entries)

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

* APPENDIX: cleanup — drop draft-history meta-commentary

Stand-alone design notes, not a changelog.

§4 design rationale: rewrite Session block and TRANSFORM-1 lang
   bullet to describe current design, not 'moved from earlier draft'.
§6.4 divergences: rewrite handler-trio / trio-ownership / scope-
   discriminator / skill_id-emission / recognizer_loop /
   topic-naming entries to state current design, not contrast with
   earlier drafts.
§6.5.1 introspection patterns: drop 'in this round' rename note.
§9 (rewritten 'Design history' → 'The spec set, in three stacks'):
   drop §9.3 audit-driven-refinement entirely (changelog content);
   merge §9.1 + §9.2 into one tighter section about how the eight
   specs partition and what reference implementations exist.
§10 compatibility levels: soften 'was previously spoken of at' to
   'is spoken of at'; replace the 'no longer describes' framing
   with a forward-looking 'tuple covering all eight specs is a
   planned follow-up'.

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

* APPENDIX: update divergence catalog for CONTEXT-1 key-shape collapse + dispatch stamping

§6.4: rewrite the CONTEXT-1 scope-discriminator entry to reflect
   the bigger change — scope AND origin both collapsed into the
   key shape. requires_context discriminator is the surviving
   surface (default private).
§6.4: rewrite the skill_id-on-every-emission entry to lead with
   the structural enforcement (dispatch stamping + forward/reply
   inheritance), with loader interception as a follow-up rather
   than the primary path.

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

* APPENDIX: clarify topic-naming claim as prefix-uniform, verb depth varies

* APPENDIX §6.5.1: flag the 'intent' word collision across three introspection topics

Cross-spec audit B1: 'intent' plays three different roles across
the four-spec introspection table — registered intents (INTENT-4),
compiled-in-a-matcher intents (PIPELINE-1), and intent-transformer
plugins (TRANSFORM-1). The shapes are deliberate and the payloads
are distinct, but the topic strings read confusingly at a glance.
Added an informative paragraph naming the three meanings and
clarifying that ovos.transformer.intent.list follows the per-chain
ovos.transformer.<type>.list pattern, where 'intent' is the chain
type — not a listing of intents.

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

* APPENDIX §4 Transformer: design note on the six per-type self-identification keys

Document the rationale for TRANSFORM-1 §1.3 claiming six per-type
context keys (audio_transformer_id, utterance_transformer_id, ...)
rather than a single generic transformer_id. Two arguments: (1)
role preservation across the six-stage chain, mirroring the
per-type partition that already exists in §1.1 registries, §5
session overrides, and §6 introspection topics; (2) multi-type-
plugin disambiguation, since §1.1 permits a single transformer_id
across types and a generic context key would erase the role at
emit time.

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

* APPENDIX §4 Transformer: record list-valued attribution, denylist symmetry, and the per-type field-count tradeoff

Four design notes capturing the recent TRANSFORM-1 evolution:

- Update the existing per-type self-id bullet to reflect the
  plural list-valued context keys (audio_transformer_ids etc.,
  not the older singular names).
- New bullet: list-valued attribution preserves full chain
  provenance per type; the last entry is the most-recent stamp.
  Skills and pipelines stay single-string because they originate
  rather than chain.
- New bullet: per-type denylists (six blacklisted_*_transformers)
  complete the policy surface, mirroring PIPELINE-1's
  pipeline/blacklisted_pipelines pair. Three-stage composition
  (preference → availability → policy) parallels PIPELINE-1 §5.5.
- New bullet: acknowledge the per-type 'explosion' (12 session
  fields + 6 context keys), defend the choice against the
  transformer_<type>:<name> prefix-encoding alternative (direct
  lookup vs prefix parsing), note that SHOULD-omit makes the
  common case zero-cost on the wire, and document the
  object-valued form as a clean fallback if the field count ever
  proves painful in practice.

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

* APPENDIX §4 CONTEXT-1: rationale for default-private scope

Add design-rationale paragraph explaining why ovos.context.set
defaults to private scope when the canonical worked example
(Person → Bob) is naturally cross-skill. Three reasons: migration
fidelity (current Adapt set_context is effectively skill-private),
safer footgun direction (accidental shared-leak is harder to
debug than accidental cross-skill miss), and authorability
(cross-skill coordination deserves a conscious explicit scope).

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

* APPENDIX §6: record recognizer_loop:utterance -> ovos.utterance.handle rename

Move the entry-topic from §6.1 'already aligned' to §6.4
'architectural divergences' — it is no longer a name kept
verbatim, since PIPELINE-1 §9.1 now prescribes
ovos.utterance.handle. Rationale paragraph cites the three
MSG-1 §2.1.2 naming convention violations: ':' as separator,
implementation-role leading segment, missing request/terminal
verb pairing.

Migration cost spelled out (every audio-input service emits,
every intent-service handler subscribes: ovos-dinkum-listener,
ovos-simple-listener, ovos-audio, ovos-core/intent_services).

§6.7 predecessor-topic table updated.

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

* APPENDIX: §2.5 Rasa/hassil/ASK/Mycroft comparisons; §6.5.2 session-field + stamp-rule cheat-sheets

Two informative additions:

- §2.5 (new): extends the §2 comparison set with Rasa, hassil,
  ASK / Dialogflow, and Mycroft. Locates the CONTEXT-1 design
  against Rasa's policy-engine-coupled forms; locates
  TRANSFORM-1 §3.4 against ASK/Dialogflow built-in entity types
  as the injectable open contract; documents Mycroft as the
  predecessor whose ad-hoc model the spec family formalizes.

- §6.5.2 (new): session-field cheat-sheet consolidating the 26
  fields claimed across SESSION-1, PIPELINE-1, TRANSFORM-1, and
  CONTEXT-1 into a single reference table — owner spec, role
  (preference / policy / signal / identity), empty-array
  semantics. Followed by a stamp-rule cheat-sheet covering the
  three component-identity context-key surfaces (skill_id,
  pipeline_id, <type>_transformer_ids) and their behaviour
  across origination, .reply / .response, and .forward.

Both reduce cross-spec bouncing for implementers.

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

* APPENDIX: reorganize from 10 sections to 7, restructure for flow

The appendix had become a dumping ground after multiple rounds
of additions. Restructured with clear narrative flow:

§1 About the OVOS specifications — formalization framing,
   the three-stack overview (was §9), compatibility levels
   (was §10), reference implementations + ecosystem tooling
   (folds in ovos-spec-tools from §9 and ovos-localize from §8).

§2 Comparison with other voice-assistant systems — merges
   the HA/Rhasspy material (was §2) with the Rasa/ASK/
   Dialogflow/Mycroft/hassil material (was §2.5) into a
   single comparator section, ordered by relevance: HA &
   Rhasspy (shared lineage) → open-vs-closed structural
   argument → Mycroft (predecessor) → Rasa (CONTEXT-1
   comparator) → ASK/Dialogflow → hassil (grammar-only) →
   summary of where OVOS leads/follows/differs.

§3 Architectural patterns — the bus as substrate (was §5)
   and the pipeline-plugin model (was §3) grouped as the
   two cross-cutting architectural moves. Bus-substrate
   section gains an explicit subsection on the layer-2
   authorization story (preference / policy split).

§4 Design rationale, per specification — was §4 itself but
   now systematically per-spec (INTENT-1+2+3 grouped,
   MSG-1, SESSION-1, INTENT-4, PIPELINE-1, CONTEXT-1,
   TRANSFORM-1). Stale references purged; recently added
   rationales (most-specific-wins precedence, bidirectional
   lang propagation, per-type denylists, etc.) folded in.

§5 Where the specs differ from current OVOS code — was §6
   but reorganized: removed the §6.5.1 introspection-
   patterns table and §6.5.2 cheat-sheets (they aren't
   divergences from code, they're implementer reference —
   moved to §6). Renumbered to §5.1–§5.7.

§6 Implementer reference — new top-level section gathering
   the cross-spec reference tables that were scattered:
   topic-name conventions (with the 'intent' overload
   clarification), session-field cheat-sheet,
   component-identity stamp-rule cheat-sheet, introspection
   patterns table. These don't belong inside a 'divergences
   from code' section; they're how-to material for fresh
   implementers.

§7 Known gaps and planned work — unchanged content, last
   section. Trimmed stale entries about CONTEXT-1 and
   TRANSFORM-1 as 'planned' (they've shipped); added
   conversation-level evaluation infrastructure as a gap.

Net: same content, far more navigable. Cross-references
updated throughout.

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

* APPENDIX §2: drop Mycroft comparator subsection; renumber 2.4-2.7 to 2.3-2.6

Mycroft AI Inc shut down in 2023; the fork is years old and the
intervening design is not Mycroft's. Keeping a 'comparison to
predecessor' subsection over-attributes the architecture and
mis-frames OVOS as a derivative project rather than a long-
running open project in its own right.

Section §2 is now a comparison with currently-relevant
voice-assistant systems only:

- §2.1 Home Assistant and Rhasspy (shared grammar lineage)
- §2.2 Closed domain vs open ecosystem
- §2.3 Rasa
- §2.4 Amazon ASK / Google Dialogflow
- §2.5 hassil
- §2.6 Summary

Collateral: dropped Mycroft from the project-name list in the
intro and from the comparator enumeration in the §2.6 summary.
Legacy topic strings that happen to contain 'mycroft' in their
literal name remain in the §5 divergence tables and §5.7
predecessor-topic mapping as factual code references.

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

* APPENDIX §3.3: external-protocol interoperability injection points

Make the family's interop story explicit rather than implied. New
§3.3 catalogues three injection points where external protocols
plug into the spec family:

1. Pipeline plugins as the dispatch-layer adapter — LLM APIs
   (OpenAI Chat Completions and compatible), deterministic
   template matchers (hassil), external intent classifiers,
   agent-tool protocols (MCP).
2. Transformer chains as the artifact-pipeline adapter —
   bidirectional translation, STT validators, content-policy
   filters, acoustic-event detectors.
3. Bus boundary as the wire-level adapter — Wyoming
   bridges, MQTT-based stacks, HiveMind-style layer-2
   substrates.

Per-protocol notes for Wyoming, OpenAI, MCP, hassil, MQTT,
A2A — naming where each plugs in. The single-flip routing and
no-central-state stance (§3.1) are what make the bus-boundary
adapter feasible without modifying the assistant core.

Concrete suggestion: a translation tool between OVOS-INTENT-2
locale resources and HA's hassil/intents YAML would let the
two corpora cross-pollinate mechanically. Added to §7 known
gaps as planned tooling.

The three injection points are intentionally not exhaustive —
they're the points the spec family deliberately keeps clean. A
protocol needing deeper integration is a signal of
architectural overlap rather than complementarity.

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

* APPENDIX: add CONVERSE-1 to orchestrator-stack narrative; close multi-turn gap

OVOS-CONVERSE-1 (PR #25) fills the multi-turn conversation gap that §7 previously listed as planned work. Update §1.2 stack description to include it, and drop the §7 gap entry.

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

* APPENDIX §5.3, §5.4: update for PIPELINE-1 §4.2 relaxation + §7.0 polymorphism collapse

Two divergence-catalogue entries updated to reflect the
PIPELINE-1 restructure:

- The §5.4 'side-effect-free during match' entry is rewritten
  as 'match contract is the single obligation' — match's only
  MUST is returning Match-or-null; bus emissions during match
  are allowed; session mutation during match is via
  Match.updated_session (explicit channel).

- New §5.4 entry: 'Match.updated_session as the match-phase
  session channel' — promotes the existing ovos-core code
  pattern `sess = match.updated_session or
  SessionManager.get(message)` to a normative Match field.
  Claiming plugin's mutations land; declined plugin's
  mutations drop at the boundary.

- The §5.3 'Dispatch payload uses polymorphic owner_id' entry
  is rewritten as 'unified owner_id' — reflects PIPELINE-1
  §7.0's collapse to two handler-owner shapes (plain skill,
  pipeline plugin with bundled handlers where pipeline_id ==
  skill_id) plus the pure-matcher recognition. Notes the
  conceptual mapping skill_id ≈ voice_app_id, pipeline_id ≈
  matching-engine id.

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

* APPENDIX §1.2, §7: SESSION-2 fills the lifecycle gap

OVOS-SESSION-2 (in flight at PR #27) defines session lifecycle
and state ownership. Update:

- §1.2 orchestrator-stack narrative adds SESSION-2 to the stack
  description with one-line summary of its scope (stateless
  orchestrator for named sessions, orchestrator-owned default
  session, projection mandate).

- §7 gap entry rewritten: SESSION-2 lands the lifecycle piece;
  what remains deferred is the set of session preference fields
  that need to be claimed under SESSION-1 §2.1 by their owning
  specs (preferences / OCP / persona / locale).

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

* APPENDIX §1.2: SESSION-2 narrative — SHOULD-project + MAY-internal (not 'mandate')

Sync with SESSION-2 §2.4 relaxation (commit 6a882c8). The
projection pathway is SHOULD-when-practical; plugins MAY hold
internal state with full lifecycle ownership and best-effort
resumption.

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

* APPENDIX §5.2.1: document ovos.session.sync / update_default for removal

These ovos-core topics are not defined by any spec. SESSION-2 §6.4
explicitly avoids naming them. They should be retired in favour of
clients reading session state from normal Message flow
(ovos.utterance.handled or any other session-carrying Message).

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

* README + APPENDIX §1.0: establish voice OS framing

README intro replaced: "voice assistant ecosystem" → "voice
operating system" with an OS-analogy table (scheduler, IPC,
shared memory, process supervision, loadable modules, syscall ABI).

APPENDIX §1.0 (new): The voice operating system concept — two
conflations addressed: (1) voice assistant product (closed,
vertically integrated vs open platform); (2) LLM wrapper (LLMs
fit as pipeline plugins, utterance/dialog/metadata transformers —
one possible multi-role deployment, not the architecture itself).

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

* revert: move README voice-OS framing to its own PR (#28)

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

* APPENDIX: fix stale PIPELINE-1 refs; slim redundant prose

- owner_id → skill_id throughout (§3.2, §3.3, §4.5, §4.6)
- match(utterance,…) → match(utterances,…) (§4.5)
- Match.captures → Match.slots (§4.7)
- complete_intent_failure → ovos.intent.unmatched in §5.1/§5.3/§5.7;
  add rename row to §5.2 table
- Dispatch payload block in §5.3 rewritten: {lang, utterance, slots},
  handler-lifecycle uses {skill_id, intent_name, optional exception}
- §5.5: add ovos.intent.unmatched and ovos.utterance.speak entries
- §2.5 hassil: drop standalone subsection; fix §2 intro cross-ref
- §1.3 compat levels: condense to bullets
- §1.4: drop ovos-localize "honest notes" paragraph
- §3.1.3: trim to essential bus-substrate mechanics
- §4.7: trim per-type-explosion and per-type-self-id bullets
- §5.4: trim rename and match-contract entries

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl added a commit that referenced this pull request May 26, 2026
* docs: OVOS-INTENT-4 — intent and entity registration and dispatch bus contract

Defines the full bus lifecycle of an intent on top of OVOS-MSG-1 and
OVOS-INTENT-3: registration, match notification, dispatch, and the
handler-lifecycle trio.

Topics defined:
- ovos.intent.register.keyword / .template — atomic per-intent
  registration (replaces register_vocab + register_intent /
  padatious:register_intent dance)
- ovos.entity.register — entity-hint registration (replaces
  padatious:register_entity)
- ovos.intent.deregister / .enable / .disable, ovos.entity.deregister,
  ovos.skill.deregister
- ovos.intent.list / .describe — introspection
- ovos.intent.matched — broadcast match-result notification
- <skill_id>:<intent_name> — dispatch topic, kept verbatim from
  current OVOS so existing skill handler subscriptions need no change
- ovos.intent.handler.{start,complete,error} — handler-lifecycle
  trio (renamed from mycroft.skill.handler.* for uniformity)

Architectural model: the host is the sole bus consumer of skill-
originated registration topics and delegates matching to its engines
through a host-internal interface. Engines do not subscribe to the
bus topics defined here. Aligned to OVOS-INTENT-3's keyword/template
duality.

The handler reference is local to the skill and never crosses the
bus; only the dispatch Message and the handler-lifecycle messages do.

APPENDIX additions:
- New §4 subsection: INTENT-4 design rationale (host as sole consumer,
  atomic registration, kept dispatch topic, trio not response, rename)
- §6 (Where specs differ from OVOS) extended with INTENT-4-specific
  subsections: topic renames, payload shape changes, architectural
  divergences, things deliberately not changed
- §9.1 design history updated to mention five specs in two stacks

This is the spec that needs more review and more implementation effort
than MSG-1 — it changes registration topic names and payload shapes,
introduces the host-mediated architectural model (today some engines
subscribe directly to bus topics), and prescribes a uniform `captures`
map for dispatch payloads.

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

* docs: INTENT-4 — distinguish utterance-layer from handler-layer lifecycle

The current §12 trio (handler-layer) and §10 match notification were
read as the only lifecycle signals around an utterance. They aren't.
ovos-core's pipeline emits utterance-layer messages that operate at
a different level and must not be confused with handler signals.

Verified flow in ovos-core/intent_services/service.py:

  recognizer_loop:utterance → entry
  ├─ transformer cancel: ovos.utterance.cancelled + ovos.utterance.handled
  ├─ pipeline match: <skill_id>:<intent_name> → handler trio
  │                  + ovos.utterance.handled (from workshop _on_event_end)
  └─ no match:       complete_intent_failure + ovos.utterance.handled

ovos.utterance.handled is the universal end-marker, fired on every
terminal path; complete_intent_failure is the intent-layer failure;
the handler trio is purely handler-layer. The previous Appendix A
entry calling ovos.utterance.handled "subsumed by
ovos.intent.handler.complete" was wrong — they're at different
lifecycle levels.

Changes:

INTENT-4 §1 scope: explicitly defers the broader utterance lifecycle
and the intent-layer failure signal to the future pipeline spec.

INTENT-4 §12.6 (new): "This trio is handler-layer only" — table of
the three lifecycle levels (utterance / intent matching / handler)
and how implementers should distinguish them. Points forward to §15.

INTENT-4 §15 (new): "Other utterance-lifecycle messages (out of
scope)" with four subsections:
- §15.1 ovos.utterance.handled is utterance-layer, not handler-layer
- §15.2 complete_intent_failure and handler error are different
- §15.3 The deferred trio — table of the three trios and what
  this spec formalizes vs defers
- §15.4 A known asymmetry in current OVOS: workshop's
  _on_event_error doesn't emit ovos.utterance.handled. The spec
  declares the invariant normative; the workshop fix is a
  separate implementation bug.

INTENT-4 Appendix A: fix the wrong "subsumed" claim on
ovos.utterance.handled (mark as unchanged with §15.1 explanation);
add three new rows for complete_intent_failure,
ovos.utterance.cancelled, recognizer_loop:utterance — all marked
unchanged-and-out-of-scope, pointing at §15.

APPENDIX §6.4: add the workshop _on_event_error asymmetry as a known
architectural divergence with a forward reference to INTENT-4 §15.4.

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

* docs: OVOS-INTENT-4 — pure registration-protocol spec; appendix moved out

Refactors INTENT-4 around the pipeline-plugin model from
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.

The bus-level utterance lifecycle (match-result notification,
dispatch, handler-lifecycle trio) moved out to OVOS-PIPELINE-1
where it belongs.

Sections removed (moved to OVOS-PIPELINE-1):
- The match-result message (ovos.intent.matched)
- Dispatch (<skill_id>:<intent_name>)
- Handler-lifecycle messages (ovos.intent.handler.*)
- Other utterance-lifecycle messages

Sections rewritten:
- §1 Scope — shorter; registration topics only; explicit pointer
  to PIPELINE-1 for the rest.
- §2 Architectural model — "registrations are broadcast" (no
  central consumer or router). Orchestrator is passive indexer.
- §3.2 Responses — plugin-optional; no orchestrator handshake.
- §3.3 Error codes — plugin-emitted only; drops
  `no_compatible_engine` (no central rejection party).
- §4 Topics table — broadcast direction marked explicitly.
- §10 Introspection — reframed as orchestrator's passive
  registration index.
- §11 Conformance — split per-party (skill MUST, plugin MAY,
  orchestrator MUST).

Also folds in:
- Rename host → orchestrator (companion to INTENT-3 v1.1 in #13).
- Removal of the in-spec legacy-mapping appendix (the spec body
  is now timeless; predecessor-topic mapping is implementation
  history and belongs in APPENDIX.md, covered in a separate
  commit).

The spec body now references no specific code paths, no Mycroft
history, no "current OVOS" qualifiers. All such context lives in
APPENDIX.md.

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

* INTENT-4: release as v2 (incompatible with current OVOS)

Per the draft-stage versioning policy, v1 is reserved for content
drop-in compatible with current OVOS. INTENT-4's topic-namespace
rename and the orchestrator passive registration index both
require OVOS-side changes, so the first release ships as v2.

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

* INTENT-4 CHANGELOG: clarify V2 trigger is the topic rename, not the passive registry

* GLOSSARY: fix stale session.pipeline_stages → session.pipeline

* scope: drop APPENDIX/CHANGELOG/GLOSSARY/README/intent-definition; PR #9 is intent-registration.md only per 1-file rule

* OVOS-INTENT-4: announce-on-load + pull-query pattern; skills are authoritative responders

Two related changes around §10 introspection:

§2 — orchestrator's passive index is now MAY, not MUST. Skills
are the authoritative source for their own registrations. The
orchestrator is a logical role; it MAY be implemented as multiple
cooperating processes (PIPELINE-1 §2), each maintaining its own
index slice and responding independently to §10 queries.

§10 — reframed as **broadcast-query / scatter-response** to
explicitly handle load-ordering:
- Registration topics (§5-§8) are themselves load-time
  announcements. Consumers that subscribed before the skill loaded
  receive them in real time; consumers that started later have
  missed them.
- Pull-query via §10 topics is the catch-up mechanism. Skills
  MUST respond to queries that match their own registrations
  (authoritative source). Orchestrator MAY also respond from its
  passive index as a convenience. Consumers aggregate responses
  across responders.
- The bus is async with no completeness signal; consumers wanting
  guaranteed completeness keep their own roster of expected
  responders and time out non-responders.

§11 conformance updated:
- Skills MUST respond to §10 queries for their own registrations.
- Orchestrator's index becomes MAY (was MUST). When maintained, the
  prior MUST-bullets apply. An orchestrator without an index is
  conformant — skills handle the introspection load.

This aligns INTENT-4 with the same pattern TRANSFORM-1 §6 uses:
producers MAY announce on load (these are the §5-§8 broadcasts);
consumers MUST query when they need accurate state because load
ordering between producer and consumer on the bus is not
guaranteed.

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

* INTENT-4: orchestrator owns the manifest; skills have no .list duty

§10 (rewritten): the introspection surface is the orchestrator-
   owned manifest of observed registrations. Skills have NO
   introspection obligation under this spec — they emit
   registrations once at load and move on. Per-pipeline-plugin
   compiled state is queried via OVOS-PIPELINE-1 §10 (distinct
   surface). Adds pull-query-is-source-of-truth boilerplate
   parallel to TRANSFORM-1 §6 / CONTEXT-1 §5.4.
§3.3: add idempotent-deregistration carve-out. Plugins SHOULD
   reject unknown_intent / unknown_entity / unknown_skill but MAY
   treat as no-op success during shutdown. Producers wanting
   idempotent removal MAY ignore unknown_* codes.
§11 conformance: drop skill MUST-respond to §10 queries (no
   skill-side obligation). Orchestrator promoted from MAY-index to
   MUST-maintain-manifest + MUST-serve-§10-queries. Split-
   orchestrator: per-process manifest slices, consumers aggregate.
See-also: add MSG-1 §2.1.1 reference, SESSION-1.

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

* INTENT-4: skill_id emission discipline; RFC 2119 tightening; split-orch ref

§3.1 (new): A skill MUST set Message.context["skill_id"] on every
   Message it emits. This is the authoritative attribution surface
   for skill-originated bus traffic; observers MUST NOT infer the
   originating skill from data fields or topic names. Specifically
   a skill discipline — orchestrator and infrastructure components
   identify via source and component-specific context keys.
§3 renumbered: §3.1 Identity → §3.2; §3.2 Responses → §3.3; §3.3
   error_codes → §3.4. All internal cross-refs updated.
§2: tighten with explicit MUST NOT on producer-side acknowledgement
   reliance. Reduce split-orchestrator restatement to a one-sentence
   reference to PIPELINE-1 §2. Drop the "skill-side authoritative
   record" language that no longer applies after the §10 rewrite
   (orchestrator owns the manifest now; skills have no .list duty).
§11 skill conformance: add skill_id-on-every-emission line.

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

* INTENT-4 §3.1: fix source-as-identity wording — source is opaque metadata

Per design correction: no spec prescribes source as an identity
surface for any component. It is opaque metadata typically populated
by the originator and propagated by MSG-1 §5 derivations.
Components that need to identify themselves use their own
component-specific reserved context keys (pipeline_id,
transformer_id, future entry-service identity).

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

* INTENT-4 §3.1: clarify data.skill_id vs context.skill_id + loader enforcement

data.skill_id (when defined by a topic's payload schema) is the
SUBJECT of the operation — a search filter, a target, an owning
skill. context.skill_id is the EMITTER. The two MAY differ.

Adds orchestrator-side enforcement rule: a skill loader MUST
intercept the skill's emit pathway and enforce
context.skill_id == loaded skill's skill_id whenever possible, so
non-compliant handlers can't escape attribution. Places the
discipline on infrastructure, not skill authors.

Cross-references CONTEXT-1 §5.2 as an example of a topic owning
spec that rejects messages lacking context.skill_id.

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

* INTENT-4 §3.3: producers MUST NOT block waiting for .response; use §10 instead

Closes the response-timing gap: producers naively blocking on
.response could wait indefinitely (the spec already says emission
is plugin-discretionary). Explicit MUST NOT block, plus pointer to
§10 manifest query as the supported verification path.

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

* INTENT-4 §3.1: structural enforcement via dispatch context.skill_id stamping

Per design clarification: the cleanest enforcement path is
structural, not interception-based. The orchestrator stamps
context.skill_id from the dispatch topic prefix
(PIPELINE-1 §7.1); skill emissions via forward/reply inherit
automatically. Interception is reserved for emissions outside
the dispatch path (background workers, initiative-driven traffic).

A drift between context.skill_id and the dispatch <skill_id> is
malformed and detectable — the spec now names this as the
authoritative non-conformance signal.

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

* INTENT-4 §4: fix PIPELINE-1 section pointer §§9–10 → §§7–9

Cross-spec audit: the trailing pointer in §4's topic table cited
'OVOS-PIPELINE-1 §§9–10' for match notification, dispatch, and
the handler-lifecycle trio. PIPELINE-1 actually houses these in
§§7–9 (§7 dispatch, §8 handler-lifecycle, §9 utterance-layer
events). Old reference pre-dated a PIPELINE-1 numbering revision.

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

* INTENT-4 §3.1: split stamp rule (originate + modify); add coexistence-with-other-identity-keys paragraph; update Other component types cross-ref

Aligning skills' rule with the uniform MUST-stamp-on-originate-or-
modify discipline adopted across PIPELINE-1 §3.1 and TRANSFORM-1
§1.3.

Added explicit coexistence paragraph: a Message MAY carry other
component-identity keys (context['pipeline_id'], the six
<type>_transformer_id keys) in addition to context['skill_id'].
MSG-1 §5 derivations preserve all such keys; this spec doesn't
require skills to strip them, and consumers MUST NOT treat
multiple identity keys as malformed. Attribution resolves via
CONTEXT-1 §5.2 precedence.

Other component types subsection updated to name the parallel
disciplines (PIPELINE-1 §3.1, TRANSFORM-1 §1.3) and to list the
six per-type transformer keys explicitly.

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

* INTENT-4 §3.1: stamp rule covers derivations placed on the bus by the skill

Same correction as PIPELINE-1 §3.1: a derivation (forward, reply,
response) the skill performs and emits IS a skill emission. The
resulting Message-on-wire MUST carry context['skill_id'] regardless
of derivation mechanism. The most common case — a handler's .speak
forwarded from a dispatch — is already structurally satisfied
because the dispatch-stamping rule propagates the skill's own id
through .forward; the explicit rule binds for derivations from
non-dispatch sources.

Also updated cross-spec refs to TRANSFORM-1 plural list-valued
<type>_transformer_ids shape.

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

* INTENT-4 §3.1, §3.3: forward exemption + tighten .response MUST/SHOULD

- §3.1 stamp rule: pure-forward propagation exempt. Same shape
  as the PIPELINE-1 §3.1 / TRANSFORM-1 §1.3 changes: .reply /
  .response are authorial and MUST stamp; .forward is propagation
  and MUST NOT overwrite inherited context['skill_id']. The
  dispatch-stamping rule keeps the typical .forward-from-dispatch
  path correct by construction (the inherited value is already
  the skill's own id).

- §3.3 response asymmetry: a consuming plugin now MUST emit
  ok: false on rejection (silent rejection is non-conformant —
  the producer cannot distinguish 'rejected here' from 'no
  consumer'), and SHOULD emit ok: true on success. The
  asymmetry is justified: rejection carries actionable
  information; success is more reliably observed via §10
  introspection than per-emission ack.

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

* INTENT-4 §8.4: relax MUST-reject to SHOULD; align with §3.4 idempotent-deregistration carve-out

§3.4 already permitted plugins to respond {ok: true} on
idempotent deregistration (a deliberate carve-out for skill
shutdown sequences). §8.4 still demanded MUST-reject with
unknown_*. The two contradicted.

Aligned: §8.4 is now SHOULD-reject, MAY-no-op-success, with
cross-reference to §3.4's carve-out. Both responses are equally
conformant; choice is per-plugin.

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

* INTENT-4 §3.4, §3.5: reject registrations of reserved intent_names

Add new error_code 'reserved_name' and a §3.5 cross-reference to
OVOS-PIPELINE-1 §7.3's reserved-intent-name registry. The orchestrator
MUST reject registration payloads that name an intent_name reserved
by a companion specification.

Currently reserved: converse and response, both by OVOS-CONVERSE-1
(PR #25). The reservation registry is owned by PIPELINE-1; this
spec is the enforcement point at registration time.

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

* INTENT-4 §3.1: drop authorial-action framing; cross-ref PIPELINE-1 §7.1

Replace "authorial action" / "modify-in-place" / .reply/.response
authorship prose with a concise rule. The dispatch-stamping
enforcement detail re-specified here now simply points to
PIPELINE-1 §7.1, which owns it.

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

* INTENT-4: fix stale topic name, terminology, and grammar

- §1 non-goals: complete_intent_failure → ovos.intent.unmatched
- §5.1: "captures map" → Match.slots (consistent with PIPELINE-1)
- §7.1 / §7 field ref: INTENT-2 §4.4 → §4.3 (correct section)
- §5.3, §6.2, §6.3, §7.2, §8.4: "A orchestrator" → "An orchestrator"

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

* INTENT-4: fix four merge blockers

- §3.5 vs §11 contradiction resolved: reserved_name rejection is now
  a plugin obligation, not the orchestrator's — the orchestrator is
  a passive manifest listener and MUST NOT synthesize responses
  (§11 is now internally consistent)
- §8.5: "retains the definition and handler binding" → "retains the
  definition in the manifest" — handler bindings never cross the bus
  (§9), the orchestrator has no knowledge of them
- §8.4: "a orchestrator" → "an orchestrator" (lowercase missed by
  earlier replace_all)
- §10.2: "file forms expanded to inline where possible" → normative
  rule: describe response is always inline form; if file-only,
  orchestrator MUST omit definition and include a warning

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

* INTENT-4 §3.2 / §8.1: fix two stale cross-refs; tighten §8.1 reset rule

- §3.2 lang field cited OVOS-MSG-1 §4.2 (layer-2 picture) for the
  data.lang vs session.lang distinction; correct target is
  OVOS-SESSION-1 §3.2 (Language signals). Also pin lang
  case-insensitivity per SESSION-1 §3.2.
- §3.2 closing line referenced §7 for replacement semantics; §7 is
  entity registration. Correct target is §8.1.
- §8.1 said replacement preserves enabled/disabled state "unless the
  producer explicitly resets it" — but §8.5 phrased the same carve-out
  as "unless the producer deregisters first." Align on "deregister
  first," the bus-observable operation.

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

* INTENT-4: pre-merge polish — trim §3.1, pin empty-expansion, Postel keys, multi-plugin .response, drop §10.2 warning string

- §3.1: collapse the orchestrator-enforcement and other-component-
  types sub-sections from ~3 pages to one combined paragraph. The
  normative content (loader-side stamping, dispatch-path structural
  conformance, symmetry with pipeline_id / transformer_ids) is
  retained verbatim; the per-key enumeration of TRANSFORM-1's six
  ids is dropped — readers consult TRANSFORM-1 §1.3 directly.

- §5.3 (keyword constraint validity): add the missing
  empty-expansion check — a vocabulary descriptor whose samples
  expand to zero non-empty entries (INTENT-1 §3.6) is malformed.
  Also pin consumer-side Postel rule: a producer MUST include all
  four keys, but a consumer SHOULD treat a missing key as an empty
  array rather than rejecting.

- §6.3 (template malformed payloads): same INTENT-1 §3.6 carve-out
  as keyword — a template that expands to zero non-empty samples
  is malformed.

- §3.3 (responses): define multi-plugin consumption semantics —
  multiple plugins MAY each emit independent .response Messages;
  any ok: false is authoritative for that plugin's rejection only;
  no cross-plugin aggregation rule; manifest of §10 is the single
  answer to "did anyone consume this?"

- §10.2 (describe response): drop the stringly-typed `warning`
  field for unresolvable file: registrations. Absence of
  `definition` on an `ok: true` response now carries that signal
  on its own — no enum churn, no free-form prose on the wire.

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

* INTENT-4: drop .response acknowledgement and data["skill_id"] noise

Two simplifications:

1. Registrations become fire-and-forget. The .response reply
   mechanism (§3.3), the error_code enum (§3.4), and every
   "MUST reject with error_code: …" clause across §§5.3, 5.5,
   6.2, 6.3, 7.2, 8.4, 8.5 are gone. A consuming plugin that
   judges a registration malformed (or finds it names a reserved
   intent_name) simply doesn't index it; the manifest of §10
   reflects what landed. Producers verify via §10 query, never
   by waiting on an ack. Deregistration becomes naturally
   idempotent (no_op semantics) without the prior unknown_*
   error-code carve-out.

2. The context["skill_id"] vs data["skill_id"] disambiguation
   sub-section (~30 lines) is dropped, along with the §3.2
   "data is authoritative" paragraph. Payloads still carry
   skill_id as a payload field where the topic schema needs
   it — that's just a field name, not a discourse that earns
   half a section of normative prose.

3. Version bumped to 3.

Net effect: -153 lines, removes a class of edge cases
(multi-plugin conflicting .response, silent vs structured
rejection, error_code precedence) without losing any
observable behaviour — the manifest was already specified
as the single source of truth, the .response was always
optional, and the error_codes were always plugin-emitted.

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

* INTENT-4 §3.1: merge "Coexistence with other identity keys" into the enforcement sub-section

The standalone paragraph repeated the pipeline_id / transformer_ids
enumeration that the "Enforcement and other component types"
sub-section already covered, and split the cross-component
discipline across two places. Merge: drop the standalone paragraph,
absorb its three normative bits (consumers MUST NOT treat extra
keys as malformed; skill MUST NOT strip keys it did not set;
CONTEXT-1 §5.2 precedence for single-owner attribution) into one
sentence at the end of the existing sub-section.

Also tighten the "authoritative attribution key" paragraph: drop
the parenthetical list of observer types (loggers, audit,
analytics, telemetry, debug tooling, manifest) — the abstract
"observers" carries the meaning without the enumeration.

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

* INTENT-4: aggressive simplification pass — drop duplicates and verbose restatements

Net: -122 lines (780 → 658), no normative change.

- §1: scope "does not define" list compacted from 7 bullets to 5;
  redundant predecessor-topic-mapping bullet dropped (already in
  APPENDIX), language-fallback bullet shortened, handler-reference
  collapsed into the INTENT-3 bullet.
- §2: trimmed the "no plugin consumes it" worked example, dropped
  the split-orchestrator paragraph (lives in §10), collapsed the
  "two consequences" bullets, joined the manifest description into
  one paragraph.
- §3.1: dropped the standalone "authoritative attribution key"
  paragraph (overlapped with the rule above it) and the entire
  "Consumer-side" sub-section (its content is implicit in
  "context.skill_id is the only attribution key"). Merged the
  must-not-reject diagnostic into the opening paragraph.
- §3.2 reserved-names: collapsed three paragraphs to one — the
  authoritative-registry pointer was the only normative content.
  lang cell tightened from a paragraph to one sentence.
- §4 trailing paragraph: 5 lines → 1.
- §5.5 file-form rule: tightened, renamed "single-orchestrator only"
  to "single-host only" — describes the constraint, not a deployment
  shape.
- §8.1 replacement: tightened reset clause.
- §9 handler reference: 3 paragraphs to 1.
- §10 introduction: collapsed 5 paragraphs (load-time announcements,
  introspection surface, per-pipeline cross-ref, split orchestrator,
  pull-query) into 3, keeping the pull-query-is-truth rule prominent.
- §10.2 describe response: tightened the file:-form omit-definition
  rule, dropped the verbose "re-issue against an orchestrator that
  shares filesystem" instruction.
- §11 conformance: dropped two redundant skill paragraphs (already
  implied by §10 + manifest), dropped the trailing PIPELINE-1 §10
  cross-ref paragraph (already in §10) and the orchestrator-
  responsibilities filler.

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

* INTENT-4 v4: address final-review findings — log floors, reserved flag, scope cleanup

Closes the design gaps from the pre-merge review.

Normative additions:
- §3.1: orchestrator MUST (was SHOULD) log absent or mismatched
  context["skill_id"] at WARN. The "authoritative attribution key"
  framing demanded strict enforcement.
- §5.3 / §6.3 / §7.2: the rejecting plugin MUST log malformed-payload
  rejections at WARN with skill_id, intent_name/entity_name, lang,
  and a one-line reason. Fire-and-forget removed the .response
  channel; the WARN log is now the producer's only debugging signal.
- §3.2 + §10.1: reserved-name registrations are flagged with
  reserved: true in ovos.intent.list responses. The split
  enforcement (plugins don't index, manifest does) now surfaces the
  misregistration through introspection instead of vanishing.
- §3.2 / §11: explicit "replacement is per-(skill_id, intent_name,
  lang); other languages unaffected" — closes a cross-language
  atomicity question.
- §8.4: pointer to PIPELINE-1 for races between deregistration and
  in-flight match (utterance lifecycle owner's concern).
- §8.5: one-sentence rationale on why enable/disable is bus-level
  rather than skill-side gating — external tooling (admin UI,
  A/B experiments) suppression without skill-code changes.
- §5.5: producers SHOULD prefer inline unconditionally; file form
  is portable only under shared filesystem.
- §10.2: consumers SHOULD fall back to PIPELINE-1 §10 per-plugin
  introspection when ovos.intent.describe omits definition for an
  unexpandable file: registration.

Scope cleanup:
- §3.1: cut the cross-component over-reach paragraph (pipeline_id,
  transformer_ids enumeration). INTENT-4 should not prescribe
  cross-component identity discipline; one-liner pointer to
  CONTEXT-1 §5.2 for single-owner attribution is sufficient.
  Removes two fragile forward refs (TRANSFORM-1 §1.3, the
  PIPELINE-1 §3.1 enumeration).
- §5.3: dropped the producer-strict / consumer-lenient
  contradiction. A payload missing required keys is malformed,
  full stop.

§11 conformance updated to match: plugin MUST log rejections;
orchestrator MUST flag reserved entries and log skill_id drift.

Version bumped to 4.

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

* INTENT-4 v5: drop file: form entirely — wire format is inline-only

A wire format spec has no business carrying producer-side filesystem
paths. Locale resource files (.voc, .intent, .entity, .blacklist —
OVOS-INTENT-2) are an authoring convenience; a skill loader reads
them and inlines their expanded content before emitting the
registration payload. File paths never appear on the bus.

Removed:
- §5.1 file-form vocabulary descriptor example and the samples-XOR-
  file rule.
- §5.5 entire "File form is single-host only" section.
- §6.1 file-form template registration example, the `file` and
  `blacklist_file` fields from the field table.
- §6.3 "both samples and file present" and "both blacklist and
  blacklist_file present" malformed-payload bullets.
- §7.1 file-form entity registration example, the `file` field from
  the field table.
- §7.2 "both or neither of samples/file" rule, replaced with the
  simpler "samples is missing or empty" rule.
- §10.2 "registered via file: that we can't expand" omit-definition
  carve-out — definition is always the broadcast payload as-is.
- §11 conformance §5.5 cross-ref and the §§5.5 entry in the plugin
  malformed-registration list.

Added (§5.1): one-paragraph explanation that locale resource files
are a producer-side authoring convenience and never cross the bus.

Eliminates a class of silent failure (unresolvable file: paths
producing empty manifest entries with no diagnostic) and removes
the awkward "single-host only" hedge that admitted the feature was
broken for the deployment shapes the spec ostensibly targets.

Version bumped to 5.

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

* Update version number in intent-registration.md

* INTENT-4: allow dual-method registration — same intent under both keyword and template

Same handler, same intent identity, two training-data representations
on the wire. Different pipeline plugins consume different methods;
either match dispatches to the same <skill_id>:<intent_name> topic.
Whether to use one or both, and how to resolve a tie if both produce
a match, is pipeline policy (OVOS-PIPELINE-1).

Changes:

- §3.2: split the identity triple (skill_id, intent_name, lang) from
  the registration key (skill_id, intent_name, lang, method). The
  triple identifies an intent; the quadruple is what the manifest
  indexes and what §8.1 replacement targets. A keyword and a template
  registration of the same triple are independent manifest entries.

- §3.2: new paragraph stating dual-method registration is explicitly
  permitted, what it means (two representations of the same handler),
  and that conflict resolution is pipeline policy.

- §3.2: entity registrations clarified as having no method axis
  (entities are a single shape).

- §8.2 deregister: clarified that the wire payload targets the triple
  and removes ALL methods under it. No per-method deregistration —
  ergonomic default. A skill wanting to keep one method
  re-registers it.

- §8.5 enable/disable: same — target the triple, affect all methods.

- §10.1 list: explicit note that dual-method intents appear as two
  entries distinguished by `method`.

- §10.2 describe: request gains an optional `method` filter; success
  response reshaped to a `definitions: [{method, definition}, ...]`
  array so both methods can be returned when no filter is given.

- §11 conformance: dropped the "never both for a single intent"
  prohibition; replaced with explicit permission, cross-ref §3.2.

- §11 orchestrator: replacement-is-per-key clause updated to name the
  quadruple key and note both other languages and the other-method
  entry are unaffected.

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

* INTENT-4: final-review polishes + PIPELINE-1 §7.3 realignment

Three-file change. The PIPELINE-1 edit is included here (rather than
in its own one-file PR) because PIPELINE-1 is already merged on dev
and the §7.3 drift is a direct consequence of INTENT-4's
post-merge-prep simplifications — they need to land together to
avoid leaving dev with contradictory normative content.

INTENT-4 polishes (intent-registration.md):

- §3.2: dual-method registration paragraph clarifies that producers
  MAY ship divergent suppression vocabularies per method (different
  `excluded` for keyword vs different `blacklist` for template); each
  plugin honours only its own method's suppression.
- §5.3: strengthened the WARN-log contract — MUST include the
  rejecting topic alongside skill_id/intent_name/lang/reason because
  the same triple may be valid as keyword and malformed as template
  (or vice versa). Structured logging RECOMMENDED.
- §8.5: explicit footgun-mitigation note — to retain only one method,
  deregister the triple (removes both) and re-register the desired one.
- §10.2: pin `definitions[]` ordering to `keyword`, `template` when
  two entries are returned so consumers can rely on positional access.
- §10.2: include `reserved: true` at top level when the intent_name
  is reserved (§3.2), so a `.describe` consumer can diagnose a
  reserved-name misregistration without round-tripping through `.list`.

PIPELINE-1 fix (pipeline.md):

- §7.3 reserved-intent_names: realign with INTENT-4's actual handling.
  Previously cited `OVOS-INTENT-4 §3.5` (the section was consolidated
  into §3.2 during INTENT-4's pre-merge simplifications) and an
  `error_code: "reserved_name"` mechanism (INTENT-4 dropped .response
  entirely). Now correctly states: consuming pipeline plugin MUST NOT
  index, MUST log per INTENT-4 §5.3; orchestrator manifest still
  indexes the broadcast with `reserved: true`.
- Version bumped to 3.

CHANGELOG: added OVOS-PIPELINE-1 (v1/v2/v3) and OVOS-INTENT-4 (v1)
sections; PIPELINE-1's prior versions were merged without a changelog
entry, so the first two entries are housekeeping.

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

* INTENT-4 / PIPELINE-1: reserved-name registrations are just malformed payloads

Collapses the entire reserved-name machinery into the existing
malformed-payload rules. The orchestrator no longer indexes reserved
registrations for diagnostic visibility, the `reserved: true` flag
in §10.1 / §10.2 is gone, and every consumer (orchestrator manifest
and pipeline plugins alike) treats a reserved intent_name as one
more bullet in the malformed-payload list — log at WARN, do not
index.

INTENT-4:
- §3.2: reserved-name paragraph reduced to one sentence pointing at
  the §5.3 / §6.3 malformed-payload rules.
- §5.3 / §6.3: added "intent_name reserved by another spec" as the
  first bullet of each malformed-payload list.
- §10.1: dropped `reserved: true` flag from list-response entries
  and the descriptive paragraph; reserved registrations don't appear
  in the manifest at all.
- §10.2: dropped the `reserved: true` field from describe responses.
- §11: orchestrator conformance no longer enumerates a
  reserved-flagging duty; plugin conformance folds reserved-name
  rejection into the malformed-registration cross-ref.

PIPELINE-1:
- §7.3: realigned the reserved-name rejection contract — registrations
  naming a reserved intent_name are malformed; every consumer
  (orchestrator + plugins) treats them under INTENT-4 §5.3. Removes
  the stale `OVOS-INTENT-4 §3.5` reference and the prior
  manifest-indexes-with-flag wording.
- No version bump (per project preference, drafts stay at their
  current version until stable).

CHANGELOG: dropped the staged PIPELINE-1 v3 entry; the §7.3 fix
lands as a non-version-bumped draft polish.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JarbasAl JarbasAl force-pushed the spec/converse1-active-handlers branch from d4eba6f to 5b64704 Compare May 27, 2026 17:35
JarbasAl added a commit that referenced this pull request May 27, 2026
…TOP-1

- README: remove 'in review' PR links for CONVERSE-1 (#25) and STOP-1
  (#33); both now merged; add STOP-1 to reading-order bullets
- appendix/divergences: replace stale 'response-mode counter' reference
  with current session.response_mode field name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JarbasAl and others added 2 commits May 27, 2026 20:07
Imperative complement to OVOS-CONTEXT-1's declarative gating. Formalises
the three currently-informal continuous-dialog surfaces:

- session.active_handlers: recency stack of recently-engaged owners,
  claimed under SESSION-1's field registry; auto-activated on dispatch,
  explicit activate/deactivate events, optional TTL prune.
- The converse plugin role: a pipeline-plugin behavioural contract that
  offers each active owner the chance to claim the utterance before
  subsequent stages, via <owner_id>.converse.request/response with a
  reserved 'converse' match-result tag.
- Interactive response collection: session.response_modes (single-holder
  invariant), enable/disable, <owner_id>.get_response delivery that
  bypasses every pipeline stage, timeout/retry/cancel wire shape.

Defines evaluation order against CONTEXT-1 (response-mode pre-empts;
converse claim before intent stages; gates apply to intent stages),
extends PIPELINE-1's universal end-marker with converse- and
response-mode-specific status values, and lists three orchestrator,
plugin, handler, observer conformance roles.

Single-file PR per repo policy.

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

Bus surface cut from 14 topics to 7 (3 pairs + 1 delivery):
- drop ovos.converse.activate / .deactivate — automatic dispatch
  activation (§3.1) plus direct session mutation (§3.2) cover
  every case without a third mechanism
- collapse response_mode.enable / .disable into a single
  response_mode.set with mode: "response" | "intent"
- drop ovos.converse.response_mode.retry — orchestrator-driven
  retry is incoherent on the wire (only the handler can re-prompt);
  handler-level retry loops are just two sets
- drop the data.deactivate self-deactivate shortcut on
  converse.response — supplanted by direct mutation per §3.2

Structural rather than asserted invariants:
- session.response_modes (map with single-holder rule) →
  session.response_mode (string | null). One slot = one string;
  the single-holder invariant is the shape, not a clause.
- drop session.active_handler_ttl field; TTL becomes a deployer
  policy value by symmetry with the per-owner converse timeout
  and the size cap (both already deployer-tuned, not session-
  carried).

Underspecification fixes:
- §5.1 specifies what happens when the existing holder re-sets
  (resets the window) vs another owner (rejects with
  response_mode_held)
- §5.3 adds optional auto_continue counter on set, addressing
  the race between get_response emission and handler re-set
- §5.6 specifies session-expiry mid-wait: orchestrator emits
  get_response with status: "cancelled" before reaping

Still v1 — version bumps happen on merge, not in-PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JarbasAl and others added 6 commits May 27, 2026 20:07
- §4.1: permit parallel poll, selection by highest activated_at (not
  list position or arrival order); SHOULD skip blacklisted owners
- §4.2: poll response MUST use .reply derivation for remote-skill
  routing, matching STOP-1 §4.2 rationale
- §4.5: clarify done-removal channel on null return — in-place session
  mutation is the sole permitted exception to PIPELINE-1 §4.2
- §5.1: identity check promoted from SHOULD to MUST; unrecognised
  owner_id in response_mode is a correctness hazard
- §5.1: orchestrator-clock note for expires_at evaluation; SHOULD
  account for clock skew in distributed deployments
- §6.1: introspection response returns full {id, activated_at} objects,
  not bare owner_id strings
- §9.3: .converse.request subscription demoted MUST → SHOULD; silence
  is already defined as result:false/timeout, mandatory subscription
  forces no-op boilerplate on non-converse skills

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Separate MUST and SHOULD handler obligations into distinct labelled
lists; the prior edit left the opening "MUST:" with a SHOULD as its
first bullet.

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

:converse — handler volunteered via poll; still must parse the utterance
:response — handler is captive recipient of a solicited answer; no NLU
decision needed

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

- §4.2: poll MUST include NLU/parsing; result:true is a binding
  commitment; done signal belongs in the poll response
- §4.3: semantic contract corrected — handler MUST handle fully on
  :converse dispatch, declining at dispatch time is non-conformant
- §9.3: SHOULD bullet updated to reflect that poll is the decision
  point, not a pre-filter

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

- README: remove 'in review' PR links for CONVERSE-1 (#25) and STOP-1
  (#33); both now merged; add STOP-1 to reading-order bullets
- appendix/divergences: replace stale 'response-mode counter' reference
  with current session.response_mode field name

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

Rename the key in converse_handlers entries from `id` to
`skill_id` for consistency with the active_handlers field shape
(OVOS-PIPELINE-1 §7.1, updated by STOP-1 §6.2).

Also register converse_handlers and response_mode in the
ovos-session-1.md field registry, and update the stale
active_handlers ownership reference from CONVERSE-1 to PIPELINE-1.

Co-Authored-By: JarbasAI <jarbasai@users.noreply.github.com>
@JarbasAl JarbasAl force-pushed the spec/converse1-active-handlers branch from 0d09ce2 to bd2bcbf Compare May 27, 2026 19:16
@JarbasAl JarbasAl changed the title OVOS-CONVERSE-1: Active Handlers and Interactive Response (draft) OVOS-CONVERSE-1: Active Handlers and Interactive Response May 27, 2026
@JarbasAl JarbasAl marked this pull request as ready for review May 27, 2026 23:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@converse.md`:
- Around line 110-113: The spec currently treats session.converse_handlers as an
ordered list where index 0 is newest, which conflicts with the desired
timestamp-based recency; update the text for converse_handlers to state it is
unordered on-wire (an omitted/absent session.converse_handlers is equivalent to
[]), and require consumers to determine recency by sorting the entries by their
activated_at timestamp in descending order (newest first) when iterating or
resolving owners; reference the field names session.converse_handlers and the
activated_at timestamp in the description so implementers know to sort by
activated_at rather than rely on array index.
🪄 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: 002d685b-aed1-48dd-b6e6-912221420749

📥 Commits

Reviewing files that changed from the base of the PR and between c814f64 and e8a852a.

📒 Files selected for processing (4)
  • appendix/divergences.md
  • converse.md
  • ovos-session-1.md
  • ovos-session-2.md

Comment thread converse.md
Comment on lines +110 to +113
The list is ordered **head-first by recency**: index `0` is the
most recently activated owner; index `n-1` is the least recently
activated of the surviving owners. An omitted or absent
`session.converse_handlers` is equivalent to `[]`.

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

Align converse_handlers wire semantics with timestamp-based recency (not list position).

This section makes on-wire order normative (index 0 == newest), which conflicts with the stated contract for this PR and creates interoperability risk when producers/transformers reorder arrays. Define the field as unordered on-wire and require consumers to sort by activated_at descending when evaluating recency/iteration.

Proposed spec text adjustment
-The list is ordered **head-first by recency**: index `0` is the
-most recently activated owner; index `n-1` is the least recently
-activated of the surviving owners. An omitted or absent
-`session.converse_handlers` is equivalent to `[]`.
+`session.converse_handlers` is an unordered on-wire set of entries.
+Consumers MUST determine recency by sorting entries on
+`activated_at` descending (most recent first) at read time.
+An omitted or absent `session.converse_handlers` is equivalent to `[]`.
🤖 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 `@converse.md` around lines 110 - 113, The spec currently treats
session.converse_handlers as an ordered list where index 0 is newest, which
conflicts with the desired timestamp-based recency; update the text for
converse_handlers to state it is unordered on-wire (an omitted/absent
session.converse_handlers is equivalent to []), and require consumers to
determine recency by sorting the entries by their activated_at timestamp in
descending order (newest first) when iterating or resolving owners; reference
the field names session.converse_handlers and the activated_at timestamp in the
description so implementers know to sort by activated_at rather than rely on
array index.

JarbasAl and others added 2 commits May 28, 2026 07:47
When a handler enters response mode and emits ovos.utterance.speak
as the prompt, it MUST set listen: true (PIPELINE-1 §9.6). The flag
is a protocol-level statement that a follow-up utterance is expected;
every output consumer re-opens the user input channel accordingly.
Omitting it is non-conformant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
owner_id was used throughout as the handler-identity concept.
skill_id is the universal identifier per the architecture model.
All 46 occurrences replaced globally; two tautological lines from
the activation-update algorithm reworded to be concrete.

Also: ovos.converse.active.list.response had session_id as a data
field — removed; session identity is read from context.session.session_id.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@converse.md`:
- Around line 133-137: The docs define session.response_mode as containing
{skill_id, expires_at} while SESSION-1 defines the session registry entry as
{owner_id, expires_at}, causing a wire-shape mismatch; pick one canonical key
and update both places to match (either rename session.response_mode.skill_id →
owner_id everywhere or change SESSION-1.owner_id → skill_id everywhere), update
any references/comments that mention session.response_mode, SESSION-1, skill_id,
or owner_id to the chosen name, and ensure the prose clarifies that the chosen
identifier MUST NOT contain ':' and that expires_at accepts integer or float.
🪄 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: 5a857b3e-4782-4d18-8964-4fb79c28ff92

📥 Commits

Reviewing files that changed from the base of the PR and between e8a852a and ce3d6c6.

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

Comment thread converse.md
Comment on lines +133 to +137
| Key | Type | Required | Meaning |
|-----|------|----------|---------|
| `skill_id` | string | yes | The handler that holds response mode for this session. MUST NOT contain `:` (MSG-1 §2.1.1 — colon-separated topic shapes use this identifier as a component). |
| `expires_at` | number | yes | Unix-seconds wall-clock time after which the wait window is stale. MAY be a float; consumers MUST accept integer and float forms. The plugin MUST discard a stale entry on the next match (§5.2) — the holder's framework-side timer drives the user-facing reaction. |

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

Cross-spec wire-shape mismatch for session.response_mode key name.

This file defines session.response_mode as {skill_id, expires_at}, but SESSION-1 currently lists {owner_id, expires_at}. That contract split can break implementers relying on SESSION-1 as the registry source. Please align both specs to one canonical key name in this PR.

🤖 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 `@converse.md` around lines 133 - 137, The docs define session.response_mode as
containing {skill_id, expires_at} while SESSION-1 defines the session registry
entry as {owner_id, expires_at}, causing a wire-shape mismatch; pick one
canonical key and update both places to match (either rename
session.response_mode.skill_id → owner_id everywhere or change
SESSION-1.owner_id → skill_id everywhere), update any references/comments that
mention session.response_mode, SESSION-1, skill_id, or owner_id to the chosen
name, and ensure the prose clarifies that the chosen identifier MUST NOT contain
':' and that expires_at accepts integer or float.

Standardise on ping/pong for pipeline-plugin capability-discovery
exchanges per AGENTS.md §4.5. The unicast dotted-addressed form
<skill_id>.converse.ping / <skill_id>.converse.pong is consistent
with ovos.stop.ping / ovos.stop.pong and the new fallback.ping /
fallback.pong convention.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-1 conflict (preserve converse_handlers, apply skill_id compliance fix)
@JarbasAl

Copy link
Copy Markdown
Member Author

Merge-readiness: resolved.

Conflicts resolved (commit aed3477):

  • ovos-session-1.md §3 registry: genuine semantic conflict between two independently correct changes.
    • dev (compliance: owner_id → skill_id sweep across dev specs #46 compliance) renamed owner_id → skill_id in the response_mode field.
    • PR branch added a new converse_handlers | array of object {skill_id, activated_at} | OVOS-CONVERSE-1 §2.1 row.
    • Both are normatively required. Resolution: kept converse_handlers row (PR's normative content) AND applied skill_id (compliance fix) to response_mode. The PR's own commit ce3d6c6 already replaced owner_id in converse.md but had missed the parallel entry in ovos-session-1.md — this merge corrects that omission.

Added FALLBACK-1, COMMON-QUERY-1, BRIDGE-1 in-review rows to README spec table.

Semantic flag: The converse_handlers field added to ovos-session-1.md is a normative addition to the session wire shape. This is consistent with how OVOS-STOP-1 (PR #33) added stop-related session fields — but reviewers should confirm that OVOS-SESSION-1's version field in the spec header is bumped before merge (the field registry change is a normative change to that spec).

CI: GitHub shows UNSTABLE status but no workflow files exist in repo; CodeRabbit review has passed. UNSTABLE is likely a transient stale-check state.

Note on #55: converse.md filename reference in README will need updating if filename-rename PR merges first.

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