Skip to content
Merged
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
48 changes: 31 additions & 17 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,38 @@ tool does not recognize the token and cannot expand the template.

## OVOS-INTENT-4 — Intent and Entity Registration Bus Contract

### 1
### 2

- Initial draft. Bus contract for declaring intents and entities, the
wire companion to OVOS-INTENT-3. Defines registration topics
(`ovos.intent.register.keyword` / `.template`, `ovos.entity.register`),
deregistration / enable / disable, and orchestrator-owned manifest
introspection (`ovos.intent.list` / `.describe`). Atomic keyword
registration with inline `required` / `optional` / `one_of` /
`excluded` vocabulary descriptors. Structured identity via the
`(skill_id, intent_name, lang)` triple plus a `method` axis for
manifest indexing — a single intent MAY be registered under both
keyword and template methods as two training-data representations.
Fire-and-forget broadcast model: no `.response` acknowledgements;
manifest presence is the only success signal. Consuming plugins MUST
log malformed-payload rejections at WARN with full identifiers and
the rejecting topic. File paths never cross the bus — INTENT-2 locale
files are a producer-side authoring convenience expanded inline by
the skill loader before emission.
- Bus contract for declaring intents and entities, the wire companion to
OVOS-INTENT-3. Registration topics (`ovos.intent.register.keyword` /
`.template`, `ovos.entity.register`), deregistration / enable / disable,
and orchestrator-owned manifest introspection (`ovos.intent.list` /
`.describe`). Keyword registration carries inline `required` / `optional`
/ `one_of` / `excluded` vocabulary descriptors. A single intent MAY be
registered under both keyword and template methods. Registrations are
fire-and-forget broadcasts: no `.response` acknowledgement; manifest
presence is the only success signal. Consuming plugins log
malformed-payload rejections at WARN with full identifiers and the
rejecting topic. File paths never cross the bus — locale files are
expanded inline before emission.
- §6 — `required_slots`: an optional array on the
`ovos.intent.register.template` payload listing slot names the engine
MUST extract for a match to be valid (OVOS-INTENT-3 §5.3). §6.2 —
templates MAY declare different slot sets (OVOS-INTENT-1 §5.5). §6.3 —
a `required_slots` entry naming a slot no template declares is malformed.
- §11 — session-scoped registration. The registration key is the quintuple
`(session_id, skill_id, intent_name, lang, method)`, read from
`context.session.session_id`; `session_id == "default"` is the global
scope every session inherits (§11.2). Deregistration MAY narrow to one
`session_id` (§8.4); session teardown removes its session-scoped
registrations (§11.3). A plugin that does not implement session scoping
treats every registration as global (§11.4). §11.5 — the orchestrator
routes a session-scoped intent only to utterances whose session matches
the registration's `session_id`.
- §8.1 — intent replacement is keyed by the quintuple, entity replacement
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.

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

Expand Down
184 changes: 155 additions & 29 deletions intent-4.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Intent and Entity Registration Bus Contract

**Spec ID:** OVOS-INTENT-4 · **Version:** 1 · **Status:** Draft
**Spec ID:** OVOS-INTENT-4 · **Version:** 2 · **Status:** Draft

This document defines the **bus messages** a skill uses to declare its
intents and entities. It is the wire format for intent registration —
Expand Down Expand Up @@ -47,7 +47,11 @@ and the orchestrator-provided introspection interface:
- the **enable** and **disable** messages — temporary suppression of a
registered intent without losing its definition;
- the **introspection** messages — list and describe registrations,
served by the orchestrator's passive registration index (§10).
served by the orchestrator's passive registration index (§10);
- the **session-scoped registration** model (§11) — how every
registration is automatically keyed to the registering session,
enabling per-session skill sets and distributed satellite
deployments.

It does **not** define:

Expand Down Expand Up @@ -300,7 +304,7 @@ malformed as template (or vice versa, §3.2). Structured logging is
### 5.4 No `.blacklist`

A `.blacklist` is **not** used with keyword intents. The `excluded` role is
the keyword-intent suppression mechanism (INTENT-3 §4.2, §5.4).
the keyword-intent suppression mechanism (INTENT-3 §4.2, §5.5).

---

Expand All @@ -323,7 +327,8 @@ INTENT-1 §3).
"(play|put on) {query} (on|using) {engine}",
"i want to listen to {query}"
],
"blacklist": ["trailer", "music video"]
"blacklist": ["trailer", "music video"],
"required_slots": ["query"]
}
```

Expand All @@ -332,13 +337,15 @@ Field reference:
| Field | Type | Required | Meaning |
|-------|------|----------|---------|
| `samples` | array of strings | yes | OVOS-INTENT-1 templates with named slots (INTENT-1 §3, §5). |
| `blacklist` | array of strings | no | Slot-free phrases (INTENT-2 §4.3) whose occurrence suppresses the match (INTENT-3 §5.4). |
| `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). |

### 6.2 Slot-consistency
### 6.2 Slot sets

Every template in `samples` **MUST** declare the same set of named slots —
the slot-consistency rule of INTENT-1 §5.5. A consuming plugin **MUST NOT**
index a registration that violates the slot-consistency rule.
Templates in `samples` MAY declare **different sets of named slots**;
the engine extracts only the slots declared by the template that best
matches (INTENT-1 §5.5, INTENT-3 §5.1). A consuming plugin MUST accept
registrations with differing slot sets across templates.

### 6.3 Malformed payloads

Expand All @@ -348,7 +355,8 @@ A consuming plugin **MUST NOT** index a template registration in which:
- `samples` is missing or empty;
- a template is not parsable as OVOS-INTENT-1 §3 grammar;
- a template expands to zero non-empty samples (OVOS-INTENT-1 §3.6);
- the slot sets of the templates differ (§6.2).
- `required_slots` names a slot that is not declared by any template in
`samples` (INTENT-3 §5.3).

The §5.3 WARN-log rule applies: the rejecting plugin **MUST** log
the rejection with `skill_id`, `intent_name`, `lang`, and a
Expand Down Expand Up @@ -396,11 +404,14 @@ rejecting plugin **MUST** log the rejection with `skill_id`,

### 8.1 Replacement is implicit

Registering an intent whose `(skill_id, intent_name, lang)` triple
matches an existing registration **replaces** it (INTENT-3 §6.1) —
no prior deregister needed. Replacement preserves enabled/disabled
state (§8.5); a producer that wants to reset that state deregisters
first. Same rule for entities keyed on `(skill_id, entity_name, lang)`.
Registering an intent whose `(session_id, skill_id, intent_name, lang, method)`
quintuple matches an existing registration **replaces** it
(INTENT-3 §6.1) — no prior deregister needed. Replacement preserves
enabled/disabled state (§8.5); a producer that wants to reset that
state deregisters first. The same rule applies to entities, keyed on
the quadruple `(session_id, skill_id, entity_name, lang)` — entities
have no `method` axis. The `session_id` is read from
`context.session.session_id` (§11.1) — never from `Message.data`.

### 8.2 `ovos.intent.deregister`

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

This is the message an orchestrator emits, or that a skill sends to the orchestrator, when a
skill is unloaded (INTENT-3 §6.1).
An optional `session_id` field narrows the removal to registrations
scoped to that session (§11):

```json
{ "skill_id": "music.skill", "session_id": "satellite-abc" }
```

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

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 @@ -521,11 +542,15 @@ Two read-only topics:
Lists registered intents. Request payload:

```json
{ "skill_id": "music.skill", "lang": "en-US" }
{ "skill_id": "music.skill", "lang": "en-US", "session_id": "satellite-abc" }
```

Both fields are **optional filters**: omitting `skill_id` returns every
skill's intents; omitting `lang` returns every language. An intent
All fields are **optional filters**: omitting `skill_id` returns every
skill's intents; omitting `lang` returns every language; omitting
`session_id` returns intents from all sessions (global view). When
`session_id` is provided the response returns the **effective pool**
for that session: `"default"` intents plus session-specific intents
(§11.2), not the raw index for that session alone. An intent
registered under both methods (§3.2) appears as two entries
distinguished by `method`.

Expand All @@ -540,16 +565,18 @@ Response (`ovos.intent.list.response`):
"intent_name": "play_music",
"lang": "en-US",
"method": "template",
"enabled": true
"enabled": true,
"session_id": "default"
}
]
}
```

Each entry carries `skill_id`, `intent_name`, `lang`, a `method` of
`"keyword"` or `"template"` (INTENT-3 §2), and an `enabled` boolean
(§8.5). Reserved-name registrations are malformed (§3.2) and do not
appear in the manifest.
`"keyword"` or `"template"` (INTENT-3 §2), an `enabled` boolean
(§8.5), and the `session_id` under which the intent was registered.
Reserved-name registrations are malformed (§3.2) and do not appear
in the manifest.

### 10.2 `ovos.intent.describe`

Expand Down Expand Up @@ -578,7 +605,98 @@ authorization is out of scope.

---

## 11. Conformance
## 11. Session-scoped registration

### 11.1 Every registration is session-keyed

The orchestrator keys every registration by the `session_id` it
reads from `context.session.session_id` — the
`context` field of the bus Message envelope, never from
`Message.data`. This is a strict requirement: `session_id` in
`data` would allow a producer to register intents under an arbitrary
session it does not own. Reading from `context` means the
`session_id` is set by the session the producer is running under,
not by anything the producer chooses to assert in its payload.

No change to the registration message shape is required — every bus
Message already carries a session in `context` (OVOS-MSG-1). The
full registration key becomes the quintuple
`(session_id, skill_id, intent_name, lang, method)`; the prior
quadruple `(skill_id, intent_name, lang, method)` is the special
case where `session_id == "default"`.

Skills running on the local device register under `"default"` because
the local device uses the default session (OVOS-SESSION-2 §5). Skills
running on a remote satellite register under whatever `session_id`
the satellite's session carries. No new message, no new field, no
coordination protocol.

### 11.2 Inheritance — `"default"` is the global scope

The **effective intent pool** for a session X is:

```
pool(X) = { intents registered under "default" }
∪ { intents registered under session_id == X }
− { entries blacklisted by session X's blacklist fields }
```

Every session implicitly inherits the full `"default"` set.
Session-scoped registrations extend the pool — they never narrow it.
Narrowing is the exclusive job of the `blacklisted_skills`,
`blacklisted_intents`, and `blacklisted_pipelines` session fields
(OVOS-PIPELINE-1 §5, OVOS-SESSION-1 §3).

If the same `(skill_id, intent_name, lang, method)` exists in both
`"default"` and session X, both index entries are retained and both
appear in the matching pool. The existing first-match-wins iteration
(OVOS-PIPELINE-1 §6) determines which is used; the blacklist is the
explicit suppression mechanism if the satellite wants to shadow a
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.

### 11.4 Pipeline plugin visibility

A pipeline plugin that wishes to support session-scoped matching
SHOULD receive the effective pool for the current session's
`session_id` when performing a match, i.e. the union described in
§11.2. Plugins that do not implement session-scoped matching
continue to operate against the `"default"` pool only and remain
conformant; they simply cannot match session-specific intents.

How the orchestrator communicates the effective pool to a plugin is
an implementation concern outside this specification. The normative
requirement is that the pool delivered to a matching plugin for
session X MUST include all entries satisfying §11.2.

### 11.5 Dispatch routing for session-scoped skills

When the orchestrator dispatches a session-scoped intent — one
registered under a non-default `session_id` — the dispatch Message
is a `.reply()` of the inbound utterance, which sets
`context.destination` to the originating participant's `source`. A
bridge conformant with OVOS-BRIDGE-1 §3.2 will route that dispatch
back to the satellite that owns the session. No special routing
protocol is needed; the existing destination-based routing
(OVOS-MSG-1 §3, OVOS-BRIDGE-1 §3.2) handles it transparently.

---

## 12. Conformance

### A **skill** (producer of registration messages) **MUST**:

Expand Down Expand Up @@ -619,15 +737,23 @@ OVOS-PIPELINE-1's concern.

- subscribe to every registration topic (§§5–8) and maintain the
**manifest** — a passive index built from observed broadcasts;
- key every manifest entry by the quintuple
`(session_id, skill_id, intent_name, lang, method)`, reading
`session_id` from `context.session.session_id` of the registration
message (§11.1);
- serve `ovos.intent.list` and `ovos.intent.describe` queries
against the manifest, returning the shape of §10.1 / §10.2;
- treat a re-registration with the same key as replacement of the
prior manifest entry (§8.1); the key is the quadruple
`(skill_id, intent_name, lang, method)`, so other languages and
the other-method entry for the same intent are unaffected;
when the query includes a `session_id`, return the effective pool
for that session per §11.2;
- treat a re-registration with the same quintuple as replacement of
the prior manifest entry (§8.1); other `session_id`s, languages,
and methods for the same intent are unaffected;
- 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
routing party.
Expand Down