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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,26 @@ version 2: its `{{ … }}` sequences become substitution points, and its
by `(session_id, skill_id, entity_name, lang)`. §12 — the orchestrator
keys the manifest by the quintuple and serves session-aware
`ovos.intent.list` queries.
- Consistency and design review: §5.2/§6.1 — an absent list-valued key
equals an empty list and MUST NOT be treated as malformed (the
all-four-keys shape-stability requirement is dropped). §5.3/§6.3 —
unknown payload fields are ignored and preserved, never malformed;
this is what carries companion-spec fields (e.g. OVOS-CONTEXT-1
gating declarations). §2 — manifest non-gating scoped to registration
processing; read-only consultation by other specifications is
permitted. §8.4/§11.1/§11.3 — session scoping for deregistration
reads exclusively from `context.session.session_id`; the
payload-level `session_id` field is removed. §8.5 —
enable/disable of an unregistered intent is a no-op; disabled-state
survival across a plugin reload is out of scope (recover via the
manifest). §10 — cold-start recovery: a skill SHOULD re-emit its
registration set on observing the deployment's readiness
announcement; re-emission is idempotent per §8.1. §10.2 — describe
responses are keyed on the `method` field, with `keyword`-then-
`template` ordering RECOMMENDED. §12 — orchestrator passivity
qualified by the §3.2 reserved-`intent_name` exclusion; §3.2 identity
citation corrected to INTENT-3 §3; language-fallback deferral
restated as an out-of-scope statement.

## OVOS-AUDIO-IN-1 — Audio Input Service

Expand Down
142 changes: 94 additions & 48 deletions intent-4.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ It does **not** define:
`ovos.intent.unmatched` — all owned by OVOS-PIPELINE-1;
- **session lifecycle** — `session` is carried opaquely per
OVOS-MSG-1;
- **language fallback** — when no registration matches the utterance
language exactly. Deferred to a future spec.
- **language fallback** — what happens when no registration matches
the utterance language exactly is out of scope for this
specification.

---

Expand All @@ -82,8 +83,12 @@ registration method).

The **orchestrator** (OVOS-INTENT-3 §6.1) maintains the manifest
(§10): a passive index built from observed registrations,
observability-only. It does not gate matching, influence
consumption, or block re-registration. Plugins are observably
observability-only. In processing registrations it does not gate
matching, influence consumption, or block re-registration. Other
specifications **MAY** consult the manifest read-only during the
utterance lifecycle (for example, OVOS-PIPELINE-1 §6.2's
`required_slots` backstop); such consultation does not make the
manifest a gate on registration processing. Plugins are observably
pluggable — adding or removing one is a deployment concern; bus
traffic and the manifest are unaffected.

Expand Down Expand Up @@ -144,7 +149,7 @@ For an **intent**:
| `lang` | string | yes | BCP-47 (INTENT-2 §2), case-insensitive (SESSION-1 §3.2). The language of the resource being registered — distinct from `session.lang`. |

The triple `(skill_id, intent_name, lang)` identifies an **intent**
(INTENT-3 §6.1). For manifest indexing and replacement (§8.1), the
(INTENT-3 §3). For manifest indexing and replacement (§8.1), the
**registration key** is the quadruple
`(skill_id, intent_name, lang, method)` — `method` being `keyword`
(§5) or `template` (§6). Registering a quadruple that matches an
Expand Down Expand Up @@ -259,14 +264,17 @@ Field reference:

| Field | Type | Required | Meaning (per INTENT-3 §4.2) |
|-------|------|----------|----|
| `required` | array of vocabulary descriptors | yes | Every required vocabulary MUST occur in the utterance. |
| `optional` | array of vocabulary descriptors | yes | Captured if it occurs; absence does not prevent a match. |
| `one_of` | array of arrays of vocabulary descriptors | yes | Each inner array is one **group**; at least one member of each group MUST occur. |
| `excluded` | array of vocabulary descriptors | yes | If any of these occurs, the intent MUST NOT match. |

Empty arrays are permitted. A producer **MUST** include all four keys, even
when empty, so the payload is shape-stable and consumers can rely on
positional semantics.
| `required` | array of vocabulary descriptors | no (absent = `[]`) | Every required vocabulary MUST occur in the utterance. |
| `optional` | array of vocabulary descriptors | no (absent = `[]`) | Captured if it occurs; absence does not prevent a match. |
| `one_of` | array of arrays of vocabulary descriptors | no (absent = `[]`) | Each inner array is one **group**; at least one member of each group MUST occur. |
| `excluded` | array of vocabulary descriptors | no (absent = `[]`) | If any of these occurs, the intent MUST NOT match. |

Empty arrays are permitted, and an **absent** list-valued key is
equivalent to an empty list — a consumer **MUST NOT** treat a payload
as malformed merely because a list-valued key is omitted. Requiring
empty keys on the wire would add nothing a consumer can rely on
(the §5.3 validity rules operate on the resolved values either way)
while turning every producer omission into a spurious rejection.

### 5.3 Constraint validity

Expand All @@ -287,9 +295,16 @@ malformed-payload rules:
template that expands to a non-empty sample (OVOS-INTENT-1 §3.6).
A descriptor that yields zero non-empty samples is malformed.

A producer **MUST** include all four top-level keys (`required`,
`optional`, `one_of`, `excluded`); a payload missing any of them is
malformed.
An absent `required`, `optional`, `one_of`, or `excluded` key is
read as an empty list (§5.2); the validity rules above apply to the
resolved values.

**Unknown payload fields** are not malformed: a consumer **MUST**
ignore fields it does not recognise and **MUST NOT** reject a
registration because of them. This is what lets companion
specifications (e.g. OVOS-CONTEXT-1's `requires_context` /
`excludes_context`) ride on the registration payload as additional
fields; the orchestrator's manifest preserves them (§10.2).

A consuming plugin **MUST NOT** index a registration that violates
these rules. The rejecting plugin **MUST** log the rejection at
Expand Down Expand Up @@ -340,6 +355,10 @@ Field reference:
| `blacklist` | array of strings | no | Slot-free phrases (INTENT-2 §4.3) whose occurrence suppresses the match (INTENT-3 §5.5). |
| `required_slots` | array of strings | no | Slot names the engine MUST extract for a match to be valid (INTENT-3 §5.3). |

As in §5.2, an absent list-valued key (`blacklist`,
`required_slots`) is equivalent to an empty list; `samples` is the
one list a producer must supply, and it must be non-empty (§6.3).

### 6.2 Slot sets

Templates in `samples` MAY declare **different sets of named slots**;
Expand All @@ -360,7 +379,8 @@ A consuming plugin **MUST NOT** index a template registration in which:

The §5.3 WARN-log rule applies: the rejecting plugin **MUST** log
the rejection with `skill_id`, `intent_name`, `lang`, and a
one-line reason.
one-line reason. The §5.3 unknown-field rule also applies: unknown
payload fields **MUST** be ignored, not treated as malformed.

---

Expand Down Expand Up @@ -449,18 +469,17 @@ registered under that `skill_id`. Payload:
{ "skill_id": "music.skill" }
```

An optional `session_id` field narrows the removal to registrations
scoped to that session (§11):

```json
{ "skill_id": "music.skill", "session_id": "satellite-abc" }
```
The removal is scoped to the `session_id` read from
`context.session.session_id` of the Message (§11.1) — never from
`Message.data`. A deregistration arriving under the default session
removes the `"default"`-scoped registrations; one arriving under a
satellite's session removes only that session's registrations (§11.3).

This is the message an orchestrator emits, or that a skill sends to
the orchestrator, when a skill is unloaded (INTENT-3 §6.1). A bridge
SHOULD emit `ovos.skill.deregister` with the satellite's `session_id`
for every skill the satellite registered when the satellite
disconnects (OVOS-BRIDGE-1 §3).
SHOULD emit `ovos.skill.deregister` carrying the satellite's session
in `context` for every skill the satellite registered when the
satellite disconnects (OVOS-BRIDGE-1 §3).

Deregistering an intent, entity, or skill that is not currently
registered is a **no-op**: registrations are fire-and-forget, every
Expand Down Expand Up @@ -497,8 +516,16 @@ deregisters the triple (§8.2, removes both methods) and re-registers
just the desired one.

Enabling an already-enabled intent, or disabling an already-disabled
intent, is a no-op. Re-registration (§8.1) preserves enabled/disabled
state unless the producer deregisters first.
intent, is a no-op. Enabling or disabling an intent that is not
currently registered is likewise a no-op — like deregistration
(§8.4), each consumer processes the message independently and one
without a matching record has nothing to change. Re-registration
(§8.1) preserves enabled/disabled state unless the producer
deregisters first. Whether a consuming plugin's disabled-state
record survives a plugin reload is **out of scope**: a reloaded
plugin that needs the current enabled/disabled state recovers it by
querying the manifest (§10.1), whose `enabled` field reflects the
latest state.

---

Expand Down Expand Up @@ -535,6 +562,18 @@ instead; the surfaces are distinct (declared vs compiled).
Under a split orchestrator (OVOS-PIPELINE-1 §2), each process
answers from its own slice; consumers aggregate.

**Cold-start recovery.** The asymmetry cuts both ways: a skill that
registered before the orchestrator (or a consuming plugin) started
has emitted into the void, and because registrations are
fire-and-forget (§2) nothing tells it so — the manifest stays
permanently empty for that skill. A skill therefore **SHOULD**
re-emit its full registration set when it observes the deployment's
readiness announcement — the broadcast by which the orchestrator
signals it is up and consuming (the topic is deployment-defined and
not owned by this specification). Re-emission is safe by
construction: replacement is implicit (§8.1), so a duplicate
registration is idempotent.

Two read-only topics:

### 10.1 `ovos.intent.list`
Expand Down Expand Up @@ -595,9 +634,10 @@ Response (`ovos.intent.describe.response`):
where each `definition` is the §5 or §6 payload as it was broadcast.
The array carries one entry when `method` was specified or only one
method was registered, two entries when both methods exist and no
filter was given. When two entries are returned, the orchestrator
**MUST** emit them in the order `keyword`, `template` so consumers
can rely on positional access.
filter was given. Each entry is self-identifying via its `method`
field; consumers **MUST** key on `method`, not on array position.
When two entries are returned, emitting them in the order
`keyword`, `template` is **RECOMMENDED** for stable output.
- On unknown intent, `{ "ok": false, "error": "..." }`.

The orchestrator **MAY** restrict access to introspection topics;
Expand Down Expand Up @@ -657,17 +697,19 @@ default intent.
### 11.3 Deregistration and session teardown

`ovos.intent.deregister` and `ovos.entity.deregister` remove the
entry whose full key matches, including `session_id`. An omitted
`session_id` in the deregistration payload removes the entry from
`"default"` only — it does not remove session-scoped registrations
with the same `(skill_id, intent_name, lang)`.

`ovos.skill.deregister` with an optional `session_id` field (§8.4)
removes all registrations for that skill scoped to that session. A
bridge SHOULD emit `ovos.skill.deregister` with the satellite's
`session_id` for each satellite skill when the satellite disconnects,
to clean up the satellite's session-scoped registrations from the
orchestrator's index.
entry whose full key matches, including `session_id`. As for every
message in this specification, the `session_id` is read from
`context.session.session_id` (§11.1) — never from `Message.data`. A
deregistration arriving under the default session removes the
`"default"`-scoped entry only — it does not remove session-scoped
registrations with the same `(skill_id, intent_name, lang)`.

`ovos.skill.deregister` (§8.4) removes all registrations for that
skill scoped to the session read from `context.session.session_id`.
A bridge SHOULD emit `ovos.skill.deregister` carrying the
satellite's session in `context` for each satellite skill when the
satellite disconnects, to clean up the satellite's session-scoped
registrations from the orchestrator's index.

### 11.4 Pipeline plugin visibility

Expand Down Expand Up @@ -716,7 +758,10 @@ protocol is needed; the existing destination-based routing
OVOS-INTENT-1 and OVOS-INTENT-2.

A skill **SHOULD** query the manifest (§10) to confirm a
registration landed; there is no acknowledgement.
registration landed; there is no acknowledgement. A skill **SHOULD**
re-emit its registrations on observing the deployment's readiness
announcement (§10) — cold-start recovery for a late-starting
orchestrator or consumer.

### A **pipeline plugin** (consumer) **MAY**:

Expand Down Expand Up @@ -751,11 +796,12 @@ OVOS-PIPELINE-1's concern.
- honour `ovos.intent.enable` / `ovos.intent.disable` in the
manifest (§8.5) — the `enabled` field of §10.1 reflects the
latest state;
- on receiving `ovos.skill.deregister` with a `session_id` field,
remove all manifest entries for that `(session_id, skill_id)` pair
(§8.4, §11.3);
- **NOT** validate, reject, route, or gate any registration message.
The orchestrator is a passive listener for the manifest, not a
- on receiving `ovos.skill.deregister`, remove all manifest entries
for the `(session_id, skill_id)` pair, with `session_id` read from
`context.session.session_id` (§8.4, §11.1, §11.3);
- **NOT** validate, reject, route, or gate any registration message
beyond the reserved-`intent_name` exclusion of §3.2. The
orchestrator is a passive listener for the manifest, not a
routing party.

The orchestrator's other responsibilities — matching, dispatch,
Expand Down