From 3f78db3985ab09746bde86ba3c718acfc34eeb81 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 27 May 2026 22:50:09 +0100 Subject: [PATCH 1/7] OVOS-PERSONA-1: Persona specification (v1 draft) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Conceptual definition of a persona as a complete conversational agent, with summon/dismiss interaction rules, no-persona mode, pipeline-positioning constraints, and an out-of-band query interface. - §2: conceptual definition (identity, personality, capabilities) - §3: persona_id session field (matching existing Session.persona_id) - §4: no-persona mode (deterministic pipeline without persona) - §5: summon rules (activating a persona by setting persona_id) - §6: dismiss rules (deactivating via stop cascade or mutation) - §7: simplified match contract (check persona_id, consume all) - §8: handler contract, multi-turn via converse, out-of-band query - §9: multiple persona coexistence (identity namespace, selection) - §10: pipeline positioning (skills and stop before persona) - §11: bus surface - §12: conformance Co-Authored-By: big-pickle --- persona.md | 551 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 551 insertions(+) create mode 100644 persona.md diff --git a/persona.md b/persona.md new file mode 100644 index 0000000..11217b2 --- /dev/null +++ b/persona.md @@ -0,0 +1,551 @@ +# Persona Specification + +**Spec ID:** OVOS-PERSONA-1 · **Version:** 1 · **Status:** Draft + +This specification defines the concept of a **persona** in a +voice-operating-system pipeline — a complete conversational agent +that, when active, claims every utterance that reaches its pipeline +stage and generates natural-language responses. It defines the +`persona_id` session field used to select the active persona, the +interaction rules for summoning and dismissing personas, and the +pipeline-positioning constraints that let the orchestrator enforce +deterministic skills-first behaviour with personas acting as a +fallback layer. + +It builds on four companion specifications: + +- the *Utterance Lifecycle and Pipeline Specification* + (OVOS-PIPELINE-1) — the pipeline-plugin contract, the `Match` + shape, dispatch, the handler-lifecycle trio, and + `session.active_handlers`; +- the *Bus Message Specification* (OVOS-MSG-1) — the envelope, + routing keys, session carrier, and derivations every Message + defined here travels in; +- the *Session Carrier Wire Shape Specification* (OVOS-SESSION-1) — + the session field registry and the omission rule; +- the *Active Handlers and Interactive Response Specification* + (OVOS-CONVERSE-1) — the conversation cycle that routes follow-up + utterances to the persona plugin during multi-turn interactions. + +The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, +**MAY**, and **RECOMMENDED** are used as in RFC 2119. + +--- + +## 1. Scope + +This specification defines: + +- **what a persona is** (§2) — the conceptual definition of a persona + as a complete conversational agent; +- **the `persona_id` session field** (§3) — the session-resident + field that identifies which persona, if any, is active for the + current session; +- **no-persona mode** (§4) — the deterministic skills-only pipeline + when no persona is active; +- **summon (activating a persona)** (§5) — how a persona is + activated for a session; +- **dismiss (deactivating a persona)** (§6) — how a persona is + deactivated; +- **the match contract** (§7) — how a persona plugin claims utterances + when active; +- **the handler contract** (§8) — how the handler generates responses, + including the out-of-band query interface; +- **multiple persona coexistence** (§9) — interaction rules when + multiple persona plugins or identities are present; +- **pipeline positioning** (§10) — where persona stages sit in the + pipeline relative to skills, stop, and fallback stages; +- **bus surface** (§11); +- **conformance** (§12). + +It does **not** define: + +- **the internal machinery** of a persona — whether the handler uses a + language model, a rule-based engine, a retrieval system, a solver + chain, or any other approach is entirely the plugin's business. + The spec fixes only the observable bus contract; +- **persona configuration format** — the system prompt, identity + definition, solver wiring, or capability declaration is a + deployment concern; +- **conversation-history persistence format** — the plugin MAY hold + history internally or project it into session fields; either is + conformant; +- **vocabulary files, matching algorithms, or confidence thresholds** + — the plugin decides when to claim an utterance; +- **GUI, TTS, or output-layer behaviour** — response delivery beyond + `ovos.utterance.speak` is out of scope. + +--- + +## 2. What is a persona + +A **persona** is a complete conversational agent — an assistant with +its own identity, personality, and capabilities. From the user's +perspective, summoning a persona replaces the deterministic pipeline +with a different agent. Each persona has: + +- an **identity** — a `persona_id` string that uniquely names it + within a deployment; +- a **personality** — the system prompt, behaviour, and response + style that characterise it; +- **capabilities** — the set of solvers, knowledge sources, or tools + it can invoke to answer the user. + +A persona plugin is a pipeline plugin (PIPELINE-1 §3) that hosts one +or more personas. When a persona is active for a session, the plugin +claims every utterance that reaches its pipeline stage and returns a +natural-language response via its bundled handler. + +This specification does not prescribe the internal shape of a persona +— how its personality is defined, what solvers it chains, or how its +capabilities are declared. Those are implementation-specific. + +--- + +## 3. The `persona_id` session field + +This specification claims one optional session field per the +OVOS-SESSION-1 §2.1 registry mechanism. + +| Field | Wire type | Owner | +|-------|-----------|-------| +| `persona_id` | string | §3 (this specification) | + +`persona_id` is an opaque string identifying which persona identity +is active for the current session. The value space is +deployment-defined. This specification places no constraint on the +string beyond the opaque-string rules of OVOS-SESSION-1 §2.2 (no `:`, +no whitespace). + +**Semantics:** + +- When `persona_id` is **absent** (not set), no persona is active. + All persona stages in the pipeline MUST return `None` (§4). +- When `persona_id` is **present and non-empty**, the corresponding + persona is active. Persona stages whose supported identities + include this value MUST claim utterances that reach them (§7). +- An empty string is semantically equivalent to absent. + +**Propagation:** + +`persona_id` follows the standard session propagation rules of +OVOS-SESSION-1 §4: it is carried unchanged across all derivations +and persists across utterances in the same session. This lets a +client set `persona_id` once at the start of a session and have it +apply for the duration of the conversation. + +**Wire weight:** + +Per OVOS-SESSION-1 §3.4, a producer that intends no active persona +(the default) SHOULD omit the field rather than emit an empty value. + +--- + +## 4. No-persona mode + +**No-persona mode** is the pipeline state in which no persona is +active (`persona_id` is absent from the session). In this mode: + +- all persona stages **MUST** decline every utterance (return `None`); +- the pipeline operates as a purely deterministic, skill-driven + system — only intent-matching and fallback stages handle + utterances; +- persona-related behaviour is entirely absent. + +No-persona mode is the deployment default. Every session starts in +no-persona mode unless a client or layer-2 substrate sets +`persona_id` on the initial utterance. + +--- + +## 5. Summon (activating a persona) + +**Summon** is the act of activating a persona for a session. The +effect of summon is to set `persona_id` in the session. + +A summon occurs whenever `persona_id` appears in the inbound +session — whether placed there by: + +- the **client** on the initial utterance message; +- a **pipeline plugin** via `Match.updated_session` or handler-side + session mutation (OVOS-SESSION-2 §2.6); +- the **orchestrator** as a policy decision. + +This specification defines the **effect** of summon, not the +mechanism. The utterance that triggers the summon (for example, +"activate Alice") is matched by whichever plugin in the pipeline +handles summon intents. The summoning plugin sets `persona_id` as a +session mutation; the orchestrator applies it on the next utterance. + +**Unique identity:** A summon MUST reference an existing +`persona_id`. A summon that names an unknown persona has no effect: +the orchestrator or summoning plugin SHOULD log at WARN and leave +`persona_id` unchanged. + +--- + +## 6. Dismiss (deactivating a persona) + +**Dismiss** is the act of deactivating the active persona for a +session, returning the pipeline to no-persona mode (§4). The effect +of dismiss is to clear `persona_id` from the session. + +A dismiss occurs when `persona_id` is removed from the session by: + +- the **stop cascade** (OVOS-STOP-1) — clearing `persona_id` as + part of the escape-hatch behaviour. The stop pipeline plugin + SHOULD clear `persona_id` in its cascade algorithm so that + "stop" returns the session to deterministic mode; +- a **pipeline plugin** via handler-side session mutation; +- the **client** by omitting `persona_id` on a subsequent utterance. + +On dismiss: + +- any in-progress generation for the session **SHOULD** cease + immediately; +- the persona's entry in per-session state (conversation history, + streaming handles, etc.) **SHOULD** be preserved for resumption + if the same persona is re-summoned; +- the orchestrator **MUST NOT** enforce a session-scoped rate limit + or cooldown on re-summon; immediate re-summon of the same persona + is conformant. + +--- + +## 7. Match contract + +### 7.1 When to claim + +A persona plugin's `match` function checks the inbound session: + +1. If `session.persona_id` is absent or empty → return `None` + (no-persona mode, §4). +2. If `session.persona_id` is set to a value this plugin supports → + return a `Match`. +3. If `session.persona_id` is set to a value this plugin does NOT + support → return `None` (let another persona stage or fallback + handle it). + +A plugin that supports **all** persona values (a general-purpose +persona) claims every utterance with a non-empty `persona_id`. A +plugin that narrows by supported identities claims only those that +match its set. + +### 7.2 Active-persona catch-all + +When a persona is active (`persona_id` is set and matched), the +plugin **MUST** claim every utterance that reaches it, subject only +to its supported-identity check. This is the defining behavioural +characteristic of a persona: an active persona consumes everything +that reaches its pipeline stage. + +The plugin **MAY** apply lightweight gate logic before claiming +(language detection, minimum utterance length, blacklist), but it +**MUST NOT** use confidence thresholds or intent-matching to decide +whether to claim — those belong to the deterministic pipeline, not +to an active persona. + +### 7.3 Latency discipline + +Per PIPELINE-1 §4.4, a persona plugin **SHOULD** return a `Match` +immediately and defer all computationally expensive work (generation, +model inference, network calls) to the handler phase. The match phase +is a routing decision; the generation phase belongs in the handler. + +### 7.4 Match shape + +When claiming, the plugin returns a `Match` (PIPELINE-1 §4.1) with: + +| Field | Value | +|-------|-------| +| `skill_id` | The plugin's own `pipeline_id` (self-matching per PIPELINE-1 §7.0). | +| `intent_name` | A non-empty string chosen by the plugin (e.g. `"persona"`, `"chat"`). | +| `lang` | The resolved BCP-47 language tag of the match. | +| `slots` | MAY be empty. | +| `utterance` | The specific candidate string from the input list. | +| `updated_session` | Present when the plugin modifies session state as part of the match. | + +### 7.5 Session mutation at match time + +A persona plugin **MAY** mutate session state via +`Match.updated_session` (PIPELINE-1 §4.2): + +- set `session.persona_id` if the match resolves the persona identity + (for example, a summon intent matched by the persona plugin itself); +- modify any other session field it owns per the OVOS-SESSION-1 §2.1 + registry mechanism. + +The `updated_session` pathway is **only effective for a claiming +match**: a plugin that returns `None` has any match-phase session +mutations discarded at the plugin boundary. + +--- + +## 8. Handler contract + +### 8.1 Response generation + +The handler dispatched on `:` receives the +standard dispatch payload (PIPELINE-1 §7.1): `lang`, `utterance`, +`slots`. The handler generates a natural-language response and emits +it via `ovos.utterance.speak` (PIPELINE-1 §9.6). + +A handler **MAY** emit zero, one, or multiple +`ovos.utterance.speak` Messages: +- Zero emissions is conformant — a persona that acts silently (logs + the interaction, triggers an external side effect) without speaking + is valid. +- Multiple emissions are conveyed in order and the output stage + **SHOULD** preserve that order. + +### 8.2 Handler-side session mutation + +The handler **MAY** mutate session state in place per +OVOS-SESSION-2 §2.6 handler-boundary rules. All emissions via +`forward` / `reply` / `response` (OVOS-MSG-1 §5) carry the mutated +session forward. + +Typical handler-side mutations: +- modifying `session.pipeline` for subsequent utterances; +- setting or clearing `session.persona_id`; +- updating per-session persona state. + +### 8.3 Long-running handlers + +A persona handler **MAY** block for an unbounded duration +(PIPELINE-1 §6.5) — for example, to stream a response token by +token. Streaming is in-handler: the handler emits successive +`ovos.utterance.speak` Messages as each token or fragment becomes +available, then returns. + +Multi-turn interactions (the "get_response" pattern) are handled +through multiple consecutive dispatches. The handler per invocation: + +1. emits an `ovos.utterance.speak` with its prompt; +2. saves continuation state (in session fields or plugin-internal + storage, §8.4); +3. returns, allowing `ovos.intent.handler.complete` to fire; +4. on the next utterance, the converse plugin + (OVOS-CONVERSE-1) routes `:converse` to the persona, + and the handler resumes based on the saved state. + +A persona plugin that supports multi-turn **SHOULD** subscribe to +`:converse` to receive follow-up utterances. + +### 8.4 Conversation history + +A persona plugin **MAY** maintain conversation history keyed on +`session.session_id`, following the MAY-internal pathway of +OVOS-SESSION-2 §2.4. History that is too large to project into +session-resident fields (multi-turn transcripts, embeddings, model +state) is held in plugin-internal storage with best-effort +resumption. + +A persona plugin **SHOULD** project summary state into +session-resident fields when practical — for example, a +`persona_context` key carrying the current topic or a compact +representation of the conversation state — so that resumption +across orchestrator restarts or multi-orchestrator deployments +retains basic continuity even when full history is held internally. + +### 8.5 Out-of-band query + +A persona plugin **MAY** expose an out-of-band query interface that +lets any component (skill, CLI, plugin) ask the persona a question +without going through the pipeline. This is a request-response +pattern on two bus topics: + +| Topic | Direction | Purpose | +|-------|-----------|---------| +| `ovos.persona.ask` | any component → persona | Out-of-band query | +| `ovos.persona.ask.response` | persona → requesting component | Query response | + +The request payload: + +``` +{ + "persona_id": "", + "utterance": "", + "session_id": "" +} +``` + +The plugin generates a response for the specified `persona_id` and +emits it on `ovos.persona.ask.response`. The response payload +carries the same `utterance` alongside the generated reply, using +the `reply()` derivation (OVOS-MSG-1 §5) to route back to the +original caller's context. + +This interface is **not** part of the utterance lifecycle — it +bypasses the pipeline, the dispatch mechanism, and the +handler-lifecycle trio. It is a direct query that exists alongside +the pipeline-based flow. A persona plugin that implements this +**MUST** still support the pipeline-based match → dispatch flow +defined in §7–§8.4. + +The out-of-band query **MUST NOT** mutate `session.persona_id` or +change the active persona state. It is a stateless query within +the context provided. + +### 8.6 Stop awareness + +A persona handler **SHOULD** check for stop signals during +long-running generation. When the handler receives a stop signal +(via the mechanisms defined by OVOS-STOP-1) for its session, it +**MUST** cease generation and return promptly. + +--- + +## 9. Multiple persona coexistence + +A deployment MAY load multiple persona plugins under different +`pipeline_id` values, each hosting one or more `persona_id` values. + +**Identity namespace:** `persona_id` values are unique within a +deployment. Two plugins MUST NOT serve the same `persona_id`. A +deployment that attempts to register a duplicate `persona_id` is +misconfigured; the orchestrator SHOULD log at WARN and ignore the +duplicate. + +**Pipeline selection:** When `persona_id` is set, the pipeline +iterates through its stages as usual. The first persona stage whose +supported identities include the requested `persona_id` claims the +utterance (§7.1). If no persona plugin supports the requested +`persona_id`, iteration continues to fallback stages — the utterance +is handled by the deterministic fallback. + +**Multiple pipeline positions:** A deployment MAY place persona +stages at multiple positions in the pipeline (for example, a +fast-path persona early for dialogue and a heavyweight persona late +for reasoning). When multiple persona stages share the same +`persona_id`, only the first one that matches it in pipeline order +claims the utterance. + +--- + +## 10. Pipeline positioning + +A deployment that includes persona stages **SHOULD** place them +after deterministic intent-matching stages and after the stop stage. +This ordering ensures: + +1. **Skills first** — utterances targeting registered intents are + handled by their dedicated handlers before the persona sees them. +2. **Stop before persona** — the escape hatch can interrupt an active + persona. +3. **Persona as fallback** — when active, the persona receives only + what skills did not match. + +A typical ordering: + +``` +session.pipeline: [ + "stop_high", # interrupt (escape hatch) + "converse", # active-handler poll + "skill_high", # deterministic registered intents + "skill_medium", + "persona", # conversational agent (active persona) + "fallback_low" # last-resort fallback +] +``` + +A deployment **MAY** place a persona stage earlier when the persona +is specialised for a domain that should pre-empt general-purpose +matchers. Multiple persona stages at different pipeline positions +are conformant. + +--- + +## 11. Bus surface + +| Topic | Direction | Purpose | +|-------|-----------|---------| +| `:` | orchestrator → persona | First-match dispatch for the persona plugin (§8.1) | +| `:converse` | orchestrator → persona | Follow-up dispatch during multi-turn interactions (§8.3) | +| `ovos.persona.ask` | any component → persona | Out-of-band query (§8.5) | +| `ovos.persona.ask.response` | persona → any component | Query response (§8.5) | + +All dispatch topics follow the PIPELINE-1 §7 topic shape and fire the +handler-lifecycle trio (PIPELINE-1 §8). The persona handler emits +`ovos.utterance.speak` (PIPELINE-1 §9.6) for each natural-language +response it generates. + +A persona plugin **SHOULD** respond to +`ovos.pipeline..intents.list` per PIPELINE-1 §10, +listing the intent names it dispatches on. + +--- + +## 12. Conformance + +### A persona pipeline plugin **MUST**: + +- expose a `match(utterances, lang, session) → Match | None` + operation per PIPELINE-1 §4; +- return a `Match` with `skill_id` equal to its own `pipeline_id` + (self-matching identity, PIPELINE-1 §7.0); +- read `session.persona_id` in `match` and return `None` when the + field is absent or empty (§7.1); +- return `None` when `session.persona_id` is set to a value it does + not support (§7.1); +- claim every utterance that reaches it when `session.persona_id` is + set to a value it supports, subject only to lightweight gate logic + (§7.2); +- set `Match.lang` to the resolved language of the match; +- subscribe to `:` to receive its own + dispatch; +- derive each `ovos.utterance.speak` emission from the dispatch + Message per OVOS-MSG-1 §5 derivation semantics (PIPELINE-1 §9.6). + +### A persona pipeline plugin **SHOULD**: + +- return a `Match` immediately and defer generation to the handler + phase (§7.3); +- subscribe to `:converse` to support multi-turn + interactions (§8.3); +- cease generation on stop signals for its session (§8.5); +- respond to `ovos.pipeline..intents.list` per + PIPELINE-1 §10; +- project summary state into session-resident fields for resumption + safety (§8.4). + +### A persona pipeline plugin **MAY**: + +- support multiple `persona_id` values (§9); +- hold conversation history in plugin-internal storage per the + MAY-internal pathway of OVOS-SESSION-2 §2.4 (§8.4); +- set `session.persona_id` via `Match.updated_session` when the match + resolves the persona identity (§7.5); +- expose an out-of-band query interface on `ovos.persona.ask` / + `ovos.persona.ask.response` (§8.5). + +### A deployment that includes persona plugins **SHOULD**: + +- position persona stages after deterministic skills and after the + stop stage in `session.pipeline` (§10); +- ensure `persona_id` values are unique across all loaded persona + plugins (§9); +- document each persona plugin's supported `persona_id` values so + summoning plugins and introspection tooling can discover them. + +--- + +## See also + +- *Utterance Lifecycle and Pipeline Specification* (OVOS-PIPELINE-1) + — the pipeline-plugin contract, the `Match` shape, dispatch + polymorphism, the handler-lifecycle trio, and `ovos.utterance.speak`. +- *Bus Message Specification* (OVOS-MSG-1) — the envelope and + derivations used for all bus communication. +- *Session Carrier Wire Shape Specification* (OVOS-SESSION-1) — the + session field registry and the omission rule; the persona spec + claims the `persona_id` field via §2.1. +- *Session Lifecycle and State Ownership Specification* + (OVOS-SESSION-2) — the SHOULD-project / MAY-internal state + pathways and the mutation boundaries. +- *Stop Pipeline Plugin Specification* (OVOS-STOP-1) — the stop + cascade that clears `persona_id` on dismiss (§6). +- *Active Handlers and Interactive Response Specification* + (OVOS-CONVERSE-1) — the conversation cycle that routes follow-up + utterances to the persona plugin via `:converse` + (§8.3). From 2c9c0aa267838bb989b3f0db5e43d779f16cf1a3 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Thu, 28 May 2026 00:19:53 +0100 Subject: [PATCH 2/7] =?UTF-8?q?PERSONA-1=20=C2=A75,=C2=A77,=C2=A78,=C2=A79?= =?UTF-8?q?,=C2=A711,=C2=A712:=20summon=20mechanism,=20two-phase=20match,?= =?UTF-8?q?=20embedded=20commands,=20discovery,=20default=20persona,=20reg?= =?UTF-8?q?ister/deregister?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §5: self-summon and external-summon pathways; one-off query (ask) as non-activating embedded command; unknown persona query rule - §6: self-release via match added to dismiss pathways - §7.1: two-phase match — route 1 (embedded commands) before route 2 (persona_id catch-all) - §7.2, §7.5: updated for broader route 1 - §8.5: added rationale for out-of-band query - §8.7 (new): persona discovery (ovos.persona.list / .list.response) - §4: default persona deployment note - §9: dynamic register/deregister (ovos.persona.register / .deregister) - §11: bus surface table extended - §12: conformance updated for discovery (SHOULD) and register/deregister (MAY) - Cross-reference fix: §8.5→§8.6 in conformance stop item - §3, §4: softened absolute MUST-return-None to allow embedded commands Co-Authored-By: Claude Code --- persona.md | 205 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 162 insertions(+), 43 deletions(-) diff --git a/persona.md b/persona.md index 11217b2..5a4015f 100644 --- a/persona.md +++ b/persona.md @@ -120,7 +120,8 @@ no whitespace). **Semantics:** - When `persona_id` is **absent** (not set), no persona is active. - All persona stages in the pipeline MUST return `None` (§4). + Persona stages MUST return `None` for all utterances except + those matching an embedded persona command (§7.1 route 1). - When `persona_id` is **present and non-empty**, the corresponding persona is active. Persona stages whose supported identities include this value MUST claim utterances that reach them (§7). @@ -146,15 +147,21 @@ Per OVOS-SESSION-1 §3.4, a producer that intends no active persona **No-persona mode** is the pipeline state in which no persona is active (`persona_id` is absent from the session). In this mode: -- all persona stages **MUST** decline every utterance (return `None`); +- persona stages **MUST** decline every utterance that does not + match an embedded persona command (§7.1 route 1); - the pipeline operates as a purely deterministic, skill-driven system — only intent-matching and fallback stages handle utterances; -- persona-related behaviour is entirely absent. +- persona-related behaviour, except for embedded commands + (§7.1 route 1), is absent. No-persona mode is the deployment default. Every session starts in no-persona mode unless a client or layer-2 substrate sets -`persona_id` on the initial utterance. +`persona_id` on the initial utterance. Some deployments MAY +configure a default persona; in that case the orchestrator or a +session-initialisation component sets `persona_id` before the +first utterance, mimicking a client-side summon. This is a +deployment policy, not a plugin concern. --- @@ -163,23 +170,37 @@ no-persona mode unless a client or layer-2 substrate sets **Summon** is the act of activating a persona for a session. The effect of summon is to set `persona_id` in the session. -A summon occurs whenever `persona_id` appears in the inbound -session — whether placed there by: - -- the **client** on the initial utterance message; -- a **pipeline plugin** via `Match.updated_session` or handler-side - session mutation (OVOS-SESSION-2 §2.6); -- the **orchestrator** as a policy decision. - -This specification defines the **effect** of summon, not the -mechanism. The utterance that triggers the summon (for example, -"activate Alice") is matched by whichever plugin in the pipeline -handles summon intents. The summoning plugin sets `persona_id` as a -session mutation; the orchestrator applies it on the next utterance. +- **Self-summon.** The persona plugin itself detects the summon + utterance during `match` (§7.1 route 1), claims the utterance + (emitting a confirmation response), and sets `persona_id` via + `Match.updated_session`. The persona handles the summon utterance + directly; the updated `persona_id` activates the persona for + subsequent utterances. +- **One-off query.** The persona plugin detects an `ask` utterance + during `match` (§7.1 route 1), claims it, and generates a response + via its handler — but does **not** set `persona_id`. The session + state is unchanged; the persona answers the question without + activating permanently. This lets the pipeline handle "ask Alice + about X" as a single utterance without altering persona state. + If the referenced persona is not supported by this plugin, the + plugin SHOULD return `None` (letting the utterance fall through to + fallback) or MAY claim it with an error response. +- **External summon.** A component outside the persona plugin sets + `persona_id` on the inbound session. The persona plugin is not + involved in the summon utterance — it only sees the new + `persona_id` on the next utterance and activates accordingly. + External summon occurs whenever `persona_id` appears in the + inbound session, placed there by: + + - the **client** on the initial utterance message; + - a **pipeline plugin** (e.g., a skill with a registered intent) + via handler-side session mutation (OVOS-SESSION-2 §2.6); + - a **session sync** (`ovos.session.sync`) from any component; + - the **orchestrator** as a policy decision. **Unique identity:** A summon MUST reference an existing `persona_id`. A summon that names an unknown persona has no effect: -the orchestrator or summoning plugin SHOULD log at WARN and leave +the orchestrator or summoning component SHOULD log at WARN and leave `persona_id` unchanged. --- @@ -192,11 +213,15 @@ of dismiss is to clear `persona_id` from the session. A dismiss occurs when `persona_id` is removed from the session by: +- the **persona plugin itself** — detecting a release intent during + `match` (§7.1 route 1) and clearing `persona_id` via + `Match.updated_session`; - the **stop cascade** (OVOS-STOP-1) — clearing `persona_id` as part of the escape-hatch behaviour. The stop pipeline plugin SHOULD clear `persona_id` in its cascade algorithm so that "stop" returns the session to deterministic mode; - a **pipeline plugin** via handler-side session mutation; +- a **session sync** (`ovos.session.sync`) from any component; - the **client** by omitting `persona_id` on a subsequent utterance. On dismiss: @@ -216,26 +241,42 @@ On dismiss: ### 7.1 When to claim -A persona plugin's `match` function checks the inbound session: - -1. If `session.persona_id` is absent or empty → return `None` - (no-persona mode, §4). -2. If `session.persona_id` is set to a value this plugin supports → - return a `Match`. -3. If `session.persona_id` is set to a value this plugin does NOT - support → return `None` (let another persona stage or fallback - handle it). - -A plugin that supports **all** persona values (a general-purpose -persona) claims every utterance with a non-empty `persona_id`. A -plugin that narrows by supported identities claims only those that -match its set. +A persona plugin's `match` function evaluates two pathways in +order: + +1. **Embedded persona commands.** The plugin detects utterances + that reference persona functionality directly — summon, release, + one-off query (e.g. "ask Alice about X"), list personas, check + active persona. When one of these intents is detected (via the + plugin's own intent matching), the plugin returns a `Match`: + - summon/release intents set or clear `persona_id` via + `Match.updated_session`; + - one-off queries (`ask`) claim the utterance and dispatch to the + handler, which generates a response but does **not** change + `persona_id` — the session state is unchanged; + - list/check intents claim the utterance for introspection + responses without mutating session state. + This pathway runs **regardless** of the current + `session.persona_id` value — it is how the plugin self-summons, + handles one-off queries, or self-releases when no persona is yet + active. + +2. **Active-persona catch-all.** If no embedded command was + detected, the plugin checks `session.persona_id`: + - If `session.persona_id` is absent or empty → return `None` + (no-persona mode, §4). + - If `session.persona_id` is set to a value this plugin supports + → return a `Match`. + - If `session.persona_id` is set to a value this plugin does NOT + support → return `None` (let another persona stage or fallback + handle it). ### 7.2 Active-persona catch-all -When a persona is active (`persona_id` is set and matched), the -plugin **MUST** claim every utterance that reaches it, subject only -to its supported-identity check. This is the defining behavioural +When route 2 above applies (no embedded persona command detected, +and `persona_id` is present and supported), the plugin **MUST** +claim every utterance that reaches it, subject only to its +supported-identity check. This is the defining behavioural characteristic of a persona: an active persona consumes everything that reaches its pipeline stage. @@ -268,13 +309,16 @@ When claiming, the plugin returns a `Match` (PIPELINE-1 §4.1) with: ### 7.5 Session mutation at match time A persona plugin **MAY** mutate session state via -`Match.updated_session` (PIPELINE-1 §4.2): +`Match.updated_session` (PIPELINE-1 §4.2). Typical uses: -- set `session.persona_id` if the match resolves the persona identity - (for example, a summon intent matched by the persona plugin itself); +- set or clear `session.persona_id` as part of a summon or release + match (§7.1 route 1); - modify any other session field it owns per the OVOS-SESSION-1 §2.1 registry mechanism. +One-off query matches (the `ask` command) do **not** mutate +`persona_id` — the session state passes through unchanged. + The `updated_session` pathway is **only effective for a claiming match**: a plugin that returns `None` has any match-phase session mutations discarded at the plugin boundary. @@ -379,7 +423,10 @@ original caller's context. This interface is **not** part of the utterance lifecycle — it bypasses the pipeline, the dispatch mechanism, and the handler-lifecycle trio. It is a direct query that exists alongside -the pipeline-based flow. A persona plugin that implements this +the pipeline-based flow. The use case is information retrieval +(fact lookup, classification, brief generation) where a skill or +plugin needs the persona's answer without changing conversational +state or triggering the full utterance lifecycle. A persona plugin that implements this **MUST** still support the pipeline-based match → dispatch flow defined in §7–§8.4. @@ -394,6 +441,33 @@ long-running generation. When the handler receives a stop signal (via the mechanisms defined by OVOS-STOP-1) for its session, it **MUST** cease generation and return promptly. +### 8.7 Persona discovery + +A persona plugin **MAY** expose a discovery interface that lets any +component enumerate the `persona_id` values it supports. This is a +request-response pattern on two bus topics: + +| Topic | Direction | Purpose | +|-------|-----------|---------| +| `ovos.persona.list` | any component → persona | Enumerate supported persona identities | +| `ovos.persona.list.response` | persona → requesting component | Response listing supported identities | + +The plugin responds to `ovos.persona.list` with: + +``` +{ + "personas": ["alice", "bob"], + "pipeline_id": "" +} +``` + +This interface is independent of the utterance lifecycle. A +deployment that includes multiple persona stages MUST scope +responses per stage — each relevant plugin responds with its own +supported set. A component that needs the full deployment-wide +set MUST query all persona stages individually or use a +deployment-specific aggregation layer. + --- ## 9. Multiple persona coexistence @@ -421,6 +495,38 @@ for reasoning). When multiple persona stages share the same `persona_id`, only the first one that matches it in pipeline order claims the utterance. +**Dynamic registration.** A persona plugin MAY expose bus topics +for runtime persona management without restart: + +| Topic | Direction | Purpose | +|-------|-----------|---------| +| `ovos.persona.register` | any component → persona | Register a new persona at runtime | +| `ovos.persona.deregister` | any component → persona | Deregister an existing persona | + +The payload for `ovos.persona.register` conveys the persona +identity; the persona definition (wiring, solver chain, +personality) is loaded from the plugin's own configuration +sources, not from the message: + +``` +{ + "persona_id": "" +} +``` + +The payload for `ovos.persona.deregister`: + +``` +{ + "persona_id": "" +} +``` + +These topics are **MAY** — a deployment that does not need runtime +persona management can omit them. When present, the plugin validates +the `persona_id` namespace uniqueness rules (§9) and rejects +duplicate or unknown registrations. + --- ## 10. Pipeline positioning @@ -464,6 +570,10 @@ are conformant. | `:converse` | orchestrator → persona | Follow-up dispatch during multi-turn interactions (§8.3) | | `ovos.persona.ask` | any component → persona | Out-of-band query (§8.5) | | `ovos.persona.ask.response` | persona → any component | Query response (§8.5) | +| `ovos.persona.list` | any component → persona | Enumerate supported persona identities (§8.7) | +| `ovos.persona.list.response` | persona → any component | Supported-identity listing (§8.7) | +| `ovos.persona.register` | any component → persona | Runtime persona registration (§9) | +| `ovos.persona.deregister` | any component → persona | Runtime persona deregistration (§9) | All dispatch topics follow the PIPELINE-1 §7 topic shape and fire the handler-lifecycle trio (PIPELINE-1 §8). The persona handler emits @@ -484,8 +594,13 @@ listing the intent names it dispatches on. operation per PIPELINE-1 §4; - return a `Match` with `skill_id` equal to its own `pipeline_id` (self-matching identity, PIPELINE-1 §7.0); -- read `session.persona_id` in `match` and return `None` when the - field is absent or empty (§7.1); +- evaluate embedded persona commands (summon, release, one-off + query, list, check) in `match` **before** checking + `session.persona_id`, and handle each according to its type — + set or clear `persona_id` for summon/release, leave it unchanged + for one-off queries (§7.1 route 1); +- after the summon/release check, read `session.persona_id` and + return `None` when the field is absent or empty (§7.1); - return `None` when `session.persona_id` is set to a value it does not support (§7.1); - claim every utterance that reaches it when `session.persona_id` is @@ -503,9 +618,11 @@ listing the intent names it dispatches on. phase (§7.3); - subscribe to `:converse` to support multi-turn interactions (§8.3); -- cease generation on stop signals for its session (§8.5); +- cease generation on stop signals for its session (§8.6); - respond to `ovos.pipeline..intents.list` per PIPELINE-1 §10; +- respond to `ovos.persona.list` with its supported `persona_id` + values (§8.7); - project summary state into session-resident fields for resumption safety (§8.4). @@ -517,7 +634,9 @@ listing the intent names it dispatches on. - set `session.persona_id` via `Match.updated_session` when the match resolves the persona identity (§7.5); - expose an out-of-band query interface on `ovos.persona.ask` / - `ovos.persona.ask.response` (§8.5). + `ovos.persona.ask.response` (§8.5); +- support runtime persona management on `ovos.persona.register` / + `ovos.persona.deregister` (§9). ### A deployment that includes persona plugins **SHOULD**: From 3de0b5cc158895597e0ad4bd39076e907a0cb341 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Thu, 28 May 2026 08:34:12 +0100 Subject: [PATCH 3/7] PERSONA-1: fix all review issues; add persona lifecycle appendix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bugs fixed: - §6: "client by omitting persona_id" is wrong (omission ≠ clearing per SESSION-1 §4); corrected to explicit clearing. Stop-cascade dependency demoted from normative SHOULD on STOP-1 to informative deployment note. - §5: "summon MUST reference existing persona_id" contradicts self-summon (which creates the identity); replaced with "unknown persona falls through" - §9: MUST NOT duplicate persona_id vs. "multiple stages share same id" contradiction resolved — demoted to SHOULD NOT, behaviour described correctly - §8.6: SHOULD check + MUST cease → both MUST (the MUST was conditional on the SHOULD, making it toothless) - §8.3: add listen:true requirement on multi-turn speak prompt (CONVERSE-1 §5.1); explain why converse routes to persona (skill_id == pipeline_id) Underspecified fixed: - §8.5: add response payload table with field definitions; add source requirement for reply() routing; note on broadcast noise and mitigation - §8.4: remove suggested persona_context field name (not in SESSION-1 registry); require plugin-specific namespaced name claimed in registry - §11: add ovos.persona.activated / ovos.persona.dismissed signals with payload definitions and best-effort caveat Overspecified fixed: - §7.2: "MUST NOT use intent-matching" was too broad (route 1 IS intent- matching); rewritten to scope confidence-gating prohibition to route 2 only - §6: remove MUST NOT rate-limit (too specific, no clear rationale) Too loose fixed: - §7.1: one-off query MAY claim even for unsupported persona_id changed to MUST NOT (footgun: downstream plugin that actually handles the id never fires) - §7.2: removed "minimum utterance length" from allowed gate-logic examples Structural: - One-off query moved from §5 (Summon) to §7.1 where it belongs - §7.1 + §7.2 merged; catch-all rule and gate-logic restriction now in one place - §7.2/7.3/7.4/7.5 renumbered to 7.2/7.3/7.4 - §2: remove implementation vocabulary from capabilities bullet - Conformance updated throughout Appendix: - Add appendix/persona-flow.md: annotated bus sequences for summon, active-persona turn, multi-turn, self-release dismiss, stop-cascade dismiss, and out-of-band query Co-Authored-By: Claude Sonnet 4.6 --- APPENDIX.md | 1 + appendix/persona-flow.md | 144 +++++++++++++++++++++++++ persona.md | 228 ++++++++++++++++++++++++--------------- 3 files changed, 284 insertions(+), 89 deletions(-) create mode 100644 appendix/persona-flow.md diff --git a/APPENDIX.md b/APPENDIX.md index e7859ea..53d2d9d 100644 --- a/APPENDIX.md +++ b/APPENDIX.md @@ -34,3 +34,4 @@ The appendix content has been split into topic-specific files: | [appendix/divergences.md](appendix/divergences.md) | §5 | Where the specs differ from current OVOS code — divergences, renames, topic mapping | | [appendix/reference.md](appendix/reference.md) | §6 | Implementer reference — session-field cheat-sheet, stamp rules, introspection patterns | | [appendix/gaps.md](appendix/gaps.md) | §7 | Known gaps and planned work — deferred specs, tooling, corpora | +| [appendix/persona-flow.md](appendix/persona-flow.md) | §8 | Persona lifecycle — annotated bus sequences for summon, conversation, dismiss, and out-of-band query | diff --git a/appendix/persona-flow.md b/appendix/persona-flow.md new file mode 100644 index 0000000..c1683ff --- /dev/null +++ b/appendix/persona-flow.md @@ -0,0 +1,144 @@ +--- +[← APPENDIX.md](../APPENDIX.md) · Non-normative + +> **⚠️ AI-generated draft — not yet fully reviewed.** This content +> was produced by a large language model (Claude Code) and +> has not yet been fully reviewed for accuracy, completeness, or +> consistency with the specifications. The normative specifications +> themselves are human-reviewed; this appendix is supplementary +> context. Readers should verify claims before relying on them. + +# Persona lifecycle — annotated bus sequences + +This section shows the full observable bus sequence for the three +main persona lifecycle events: summon, a two-turn conversation, and +dismiss. All events are on the shared messagebus unless noted. +Session state changes are shown inline. + +--- + +## Summon via utterance ("hey alice") + +``` +ovos.utterance.handle [utterances=["hey alice"], session={persona_id: absent}] + │ + ├─ pipeline: stop_high → None + ├─ pipeline: converse → None (no active handler) + ├─ pipeline: skill_high → None + ├─ pipeline: persona → Match (route 1: embedded summon command detected) + │ Match.updated_session = {persona_id: "alice"} + │ + ovos.intent.matched + :persona [dispatch; session now has persona_id="alice"] + ovos.intent.handler.start + ovos.utterance.speak ["Sure, I'm Alice. How can I help?"] + ovos.persona.activated [persona_id="alice", session_id="..."] + ovos.intent.handler.complete + ovos.utterance.handled +``` + +Session state after: `{persona_id: "alice"}` — carried on all +subsequent utterances in this session. + +--- + +## Active-persona conversation turn + +``` +ovos.utterance.handle [utterances=["what's the weather?"], session={persona_id: "alice"}] + │ + ├─ pipeline: stop_high → None + ├─ pipeline: converse → None (no active response-mode) + ├─ pipeline: skill_high → None (or may match — skills run first) + ├─ pipeline: persona → Match (route 2: persona_id="alice" supported) + │ + ovos.intent.matched + :persona [dispatch] + ovos.intent.handler.start + ovos.utterance.speak ["It's 18 degrees and sunny."] + ovos.intent.handler.complete + ovos.utterance.handled +``` + +--- + +## Multi-turn (persona asks a follow-up question) + +``` +ovos.utterance.handle [utterances=["tell me a story"], session={persona_id: "alice"}] + │ + ├─ pipeline: persona → Match (route 2) + │ + :persona + ovos.intent.handler.start + ovos.utterance.speak ["What kind of story? (listen: true)"] + ← listen:true re-opens mic after TTS + [handler sets session.response_mode = {owner_id: pipeline_id, ...}] + ovos.intent.handler.complete + ovos.utterance.handled + + [user speaks reply] + +ovos.utterance.handle [utterances=["a dragon story"], session={persona_id: "alice", response_mode: {...}}] + │ + ├─ pipeline: converse → Match (response_mode held by persona plugin) + │ dispatches :response + │ + :response + ovos.intent.handler.start + ovos.utterance.speak ["Once upon a time, a dragon..."] + [handler clears session.response_mode] + ovos.intent.handler.complete + ovos.utterance.handled +``` + +--- + +## Dismiss via self-release ("goodbye alice") + +``` +ovos.utterance.handle [utterances=["goodbye alice"], session={persona_id: "alice"}] + │ + ├─ pipeline: persona → Match (route 1: release command detected) + │ Match.updated_session = {persona_id: absent} + │ + :persona + ovos.intent.handler.start + ovos.utterance.speak ["Goodbye! Switching back to normal mode."] + ovos.persona.dismissed [persona_id="alice", session_id="..."] + ovos.intent.handler.complete + ovos.utterance.handled +``` + +Session state after: `{persona_id: absent}` — no-persona mode +resumes. The next utterance goes through the full deterministic +pipeline. + +--- + +## Dismiss via stop cascade + +This is deployment-specific. A deployment that wires stop to clear +`persona_id` typically does so in its stop pipeline plugin's +cascade step, setting `persona_id` to absent in the session before +or after emitting the stop dispatch. The persona plugin then detects +the cleared field on the next utterance (if any) and emits +`ovos.persona.dismissed`. There is no dedicated stop↔persona bus +event; the session field change is the signal. + +--- + +## Out-of-band query (skill asks persona directly) + +``` +ovos.persona.ask [persona_id="alice", utterance="summarise X", source=] + │ + ├─ persona plugin (supports "alice") receives it + │ generates reply internally + │ + ovos.persona.ask.response [persona_id="alice", utterance="summarise X", response="..."] + routed via reply() back to +``` + +No pipeline interaction, no `ovos.utterance.handle`, no +handler-lifecycle trio. Session state is unchanged. diff --git a/persona.md b/persona.md index 5a4015f..d0951be 100644 --- a/persona.md +++ b/persona.md @@ -88,8 +88,7 @@ with a different agent. Each persona has: within a deployment; - a **personality** — the system prompt, behaviour, and response style that characterise it; -- **capabilities** — the set of solvers, knowledge sources, or tools - it can invoke to answer the user. +- **capabilities** — what it can answer or do for the user. A persona plugin is a pipeline plugin (PIPELINE-1 §3) that hosts one or more personas. When a persona is active for a session, the plugin @@ -176,15 +175,6 @@ effect of summon is to set `persona_id` in the session. `Match.updated_session`. The persona handles the summon utterance directly; the updated `persona_id` activates the persona for subsequent utterances. -- **One-off query.** The persona plugin detects an `ask` utterance - during `match` (§7.1 route 1), claims it, and generates a response - via its handler — but does **not** set `persona_id`. The session - state is unchanged; the persona answers the question without - activating permanently. This lets the pipeline handle "ask Alice - about X" as a single utterance without altering persona state. - If the referenced persona is not supported by this plugin, the - plugin SHOULD return `None` (letting the utterance fall through to - fallback) or MAY claim it with an error response. - **External summon.** A component outside the persona plugin sets `persona_id` on the inbound session. The persona plugin is not involved in the summon utterance — it only sees the new @@ -198,10 +188,10 @@ effect of summon is to set `persona_id` in the session. - a **session sync** (`ovos.session.sync`) from any component; - the **orchestrator** as a policy decision. -**Unique identity:** A summon MUST reference an existing -`persona_id`. A summon that names an unknown persona has no effect: -the orchestrator or summoning component SHOULD log at WARN and leave -`persona_id` unchanged. +**Unknown persona:** If no loaded plugin supports the requested +`persona_id`, the persona stage returns `None` and the utterance +falls through to fallback stages. No error event is emitted; the +utterance is handled as if no persona were active. --- @@ -211,18 +201,32 @@ the orchestrator or summoning component SHOULD log at WARN and leave session, returning the pipeline to no-persona mode (§4). The effect of dismiss is to clear `persona_id` from the session. -A dismiss occurs when `persona_id` is removed from the session by: +A dismiss occurs when `persona_id` is cleared from the session by: - the **persona plugin itself** — detecting a release intent during `match` (§7.1 route 1) and clearing `persona_id` via `Match.updated_session`; -- the **stop cascade** (OVOS-STOP-1) — clearing `persona_id` as - part of the escape-hatch behaviour. The stop pipeline plugin - SHOULD clear `persona_id` in its cascade algorithm so that - "stop" returns the session to deterministic mode; -- a **pipeline plugin** via handler-side session mutation; -- a **session sync** (`ovos.session.sync`) from any component; -- the **client** by omitting `persona_id` on a subsequent utterance. +- a **pipeline plugin** via handler-side session mutation + (OVOS-SESSION-2 §2.6); +- a **session sync** (`ovos.session.sync`) from any component that + explicitly sets `persona_id` to absent or empty; +- the **client** by explicitly setting `persona_id` to absent or + empty on a subsequent utterance. + +Note: session propagation (SESSION-1 §4) carries `persona_id` +unchanged when a message simply omits the field. Omission is not +dismissal — the client must explicitly clear the field. + +Deployments that want "stop" to return the session to no-persona +mode SHOULD include logic in their stop handling to clear +`persona_id` from the session. This is a deployment policy; it is +not imposed by this specification on OVOS-STOP-1. + +On dismiss, the persona plugin **SHOULD** emit +`ovos.persona.dismissed` (§11) so that UIs and other subscribers +can update state. The persona plugin detects dismissal when a +subsequent utterance arrives with `persona_id` absent after it was +previously set. On dismiss: @@ -230,10 +234,7 @@ On dismiss: immediately; - the persona's entry in per-session state (conversation history, streaming handles, etc.) **SHOULD** be preserved for resumption - if the same persona is re-summoned; -- the orchestrator **MUST NOT** enforce a session-scoped rate limit - or cooldown on re-summon; immediate re-summon of the same persona - is conformant. + if the same persona is re-summoned. --- @@ -253,7 +254,9 @@ order: `Match.updated_session`; - one-off queries (`ask`) claim the utterance and dispatch to the handler, which generates a response but does **not** change - `persona_id` — the session state is unchanged; + `persona_id` — the session state is unchanged. A plugin that + does not support the referenced `persona_id` **MUST** return + `None` and let the utterance fall through to a plugin that does; - list/check intents claim the utterance for introspection responses without mutating session state. This pathway runs **regardless** of the current @@ -266,34 +269,25 @@ order: - If `session.persona_id` is absent or empty → return `None` (no-persona mode, §4). - If `session.persona_id` is set to a value this plugin supports - → return a `Match`. + → the plugin **MUST** claim the utterance. This is the defining + behavioural characteristic of a persona: an active persona + consumes everything that reaches its pipeline stage. The plugin + **MAY** apply lightweight gate logic before claiming (language + detection, explicit blacklist), but **MUST NOT** use confidence + thresholds to decide whether to claim — confidence gating + belongs in the deterministic pipeline, not in an active persona. - If `session.persona_id` is set to a value this plugin does NOT support → return `None` (let another persona stage or fallback handle it). -### 7.2 Active-persona catch-all - -When route 2 above applies (no embedded persona command detected, -and `persona_id` is present and supported), the plugin **MUST** -claim every utterance that reaches it, subject only to its -supported-identity check. This is the defining behavioural -characteristic of a persona: an active persona consumes everything -that reaches its pipeline stage. - -The plugin **MAY** apply lightweight gate logic before claiming -(language detection, minimum utterance length, blacklist), but it -**MUST NOT** use confidence thresholds or intent-matching to decide -whether to claim — those belong to the deterministic pipeline, not -to an active persona. - -### 7.3 Latency discipline +### 7.2 Latency discipline Per PIPELINE-1 §4.4, a persona plugin **SHOULD** return a `Match` immediately and defer all computationally expensive work (generation, model inference, network calls) to the handler phase. The match phase is a routing decision; the generation phase belongs in the handler. -### 7.4 Match shape +### 7.3 Match shape When claiming, the plugin returns a `Match` (PIPELINE-1 §4.1) with: @@ -306,7 +300,7 @@ When claiming, the plugin returns a `Match` (PIPELINE-1 §4.1) with: | `utterance` | The specific candidate string from the input list. | | `updated_session` | Present when the plugin modifies session state as part of the match. | -### 7.5 Session mutation at match time +### 7.4 Session mutation at match time A persona plugin **MAY** mutate session state via `Match.updated_session` (PIPELINE-1 §4.2). Typical uses: @@ -365,13 +359,17 @@ available, then returns. Multi-turn interactions (the "get_response" pattern) are handled through multiple consecutive dispatches. The handler per invocation: -1. emits an `ovos.utterance.speak` with its prompt; +1. emits an `ovos.utterance.speak` with its prompt, carrying + `listen: true` (PIPELINE-1 §9.6, CONVERSE-1 §5.1) — this is + required so that the output layer re-opens the user input channel + after delivery; 2. saves continuation state (in session fields or plugin-internal storage, §8.4); 3. returns, allowing `ovos.intent.handler.complete` to fire; -4. on the next utterance, the converse plugin - (OVOS-CONVERSE-1) routes `:converse` to the persona, - and the handler resumes based on the saved state. +4. on the next utterance, the converse plugin (OVOS-CONVERSE-1) + routes `:converse` to the persona because + `skill_id == pipeline_id` (PIPELINE-1 §7.0), and the handler + resumes based on the saved state. A persona plugin that supports multi-turn **SHOULD** subscribe to `:converse` to receive follow-up utterances. @@ -386,11 +384,13 @@ state) is held in plugin-internal storage with best-effort resumption. A persona plugin **SHOULD** project summary state into -session-resident fields when practical — for example, a -`persona_context` key carrying the current topic or a compact -representation of the conversation state — so that resumption -across orchestrator restarts or multi-orchestrator deployments -retains basic continuity even when full history is held internally. +session-resident fields when practical — for example, a field +carrying the current topic or a compact representation of the +conversation state — so that resumption across orchestrator +restarts or multi-orchestrator deployments retains basic continuity +even when full history is held internally. Any session field used +for this purpose MUST be claimed in the SESSION-1 §2.1 registry +under a plugin-specific name to avoid collisions with other plugins. ### 8.5 Out-of-band query @@ -415,10 +415,32 @@ The request payload: ``` The plugin generates a response for the specified `persona_id` and -emits it on `ovos.persona.ask.response`. The response payload -carries the same `utterance` alongside the generated reply, using -the `reply()` derivation (OVOS-MSG-1 §5) to route back to the -original caller's context. +emits it on `ovos.persona.ask.response` using the `reply()` +derivation (OVOS-MSG-1 §5) to route back to the original caller. +The request Message MUST carry a `source` so that `reply()` has a +destination to address; a sourceless request cannot be routed back. + +The response payload: + +``` +{ + "persona_id": "", + "utterance": "", + "response": "" +} +``` + +| Field | Type | Required | Meaning | +|-------|------|----------|---------| +| `persona_id` | string | yes | The persona identity that generated the reply. | +| `utterance` | string | yes | Echo of the request `utterance`. | +| `response` | string | yes | The generated natural-language reply. | + +Because `ovos.persona.ask` is broadcast, all persona plugins +receive every request and must self-filter on `persona_id`. A +caller that knows which `pipeline_id` handles the target +`persona_id` (discovered via `ovos.persona.list`, §8.7) MAY +address the request directly to reduce noise. This interface is **not** part of the utterance lifecycle — it bypasses the pipeline, the dispatch mechanism, and the @@ -436,10 +458,10 @@ the context provided. ### 8.6 Stop awareness -A persona handler **SHOULD** check for stop signals during -long-running generation. When the handler receives a stop signal -(via the mechanisms defined by OVOS-STOP-1) for its session, it -**MUST** cease generation and return promptly. +A persona handler **MUST** check for stop signals during +long-running generation and **MUST** cease generation and return +promptly when a stop signal for its session is received (per +OVOS-STOP-1). ### 8.7 Persona discovery @@ -475,25 +497,26 @@ deployment-specific aggregation layer. A deployment MAY load multiple persona plugins under different `pipeline_id` values, each hosting one or more `persona_id` values. -**Identity namespace:** `persona_id` values are unique within a -deployment. Two plugins MUST NOT serve the same `persona_id`. A -deployment that attempts to register a duplicate `persona_id` is -misconfigured; the orchestrator SHOULD log at WARN and ignore the -duplicate. +**Identity namespace:** `persona_id` values SHOULD be unique within +a deployment — a given identity should be handled by exactly one +plugin. When two plugins both claim the same `persona_id`, the +first one in pipeline order claims every utterance for that +identity; the second never matches. Deployments SHOULD avoid this +ambiguity. If detected at runtime (e.g. via `ovos.persona.list` +responses), the deployer SHOULD log at WARN. **Pipeline selection:** When `persona_id` is set, the pipeline iterates through its stages as usual. The first persona stage whose supported identities include the requested `persona_id` claims the utterance (§7.1). If no persona plugin supports the requested `persona_id`, iteration continues to fallback stages — the utterance -is handled by the deterministic fallback. +is handled as if no persona were active (§5). -**Multiple pipeline positions:** A deployment MAY place persona -stages at multiple positions in the pipeline (for example, a -fast-path persona early for dialogue and a heavyweight persona late -for reasoning). When multiple persona stages share the same -`persona_id`, only the first one that matches it in pipeline order -claims the utterance. +**Multiple pipeline positions:** A deployment MAY place the same +persona plugin at multiple positions in the pipeline (for example, +an early fast-path and a late reasoning-heavy position). In that +case both entries have the same `pipeline_id` and the same supported +identities; the first position that executes claims the utterance. **Dynamic registration.** A persona plugin MAY expose bus topics for runtime persona management without restart: @@ -568,6 +591,8 @@ are conformant. |-------|-----------|---------| | `:` | orchestrator → persona | First-match dispatch for the persona plugin (§8.1) | | `:converse` | orchestrator → persona | Follow-up dispatch during multi-turn interactions (§8.3) | +| `ovos.persona.activated` | persona → broadcast | A persona became active for a session (§5) | +| `ovos.persona.dismissed` | persona → broadcast | A persona was deactivated for a session (§6) | | `ovos.persona.ask` | any component → persona | Out-of-band query (§8.5) | | `ovos.persona.ask.response` | persona → any component | Query response (§8.5) | | `ovos.persona.list` | any component → persona | Enumerate supported persona identities (§8.7) | @@ -575,6 +600,27 @@ are conformant. | `ovos.persona.register` | any component → persona | Runtime persona registration (§9) | | `ovos.persona.deregister` | any component → persona | Runtime persona deregistration (§9) | +**`ovos.persona.activated`** is emitted by the persona plugin when +it processes a summon (route 1, §7.1) or first detects an active +`persona_id` it supports on an inbound utterance. Payload: + +```json +{ "persona_id": "alice", "session_id": "abc123" } +``` + +**`ovos.persona.dismissed`** is emitted by the persona plugin when +it detects that `persona_id` has been cleared for a session it was +serving. Payload: + +```json +{ "persona_id": "alice", "session_id": "abc123" } +``` + +Both signals are best-effort: a plugin that crashes or is +unloaded before emitting them is conformant. Subscribers (UIs, +loggers) MUST NOT rely on these signals as authoritative session +state — the session itself is the source of truth. + All dispatch topics follow the PIPELINE-1 §7 topic shape and fire the handler-lifecycle trio (PIPELINE-1 §8). The persona handler emits `ovos.utterance.speak` (PIPELINE-1 §9.6) for each natural-language @@ -598,33 +644,38 @@ listing the intent names it dispatches on. query, list, check) in `match` **before** checking `session.persona_id`, and handle each according to its type — set or clear `persona_id` for summon/release, leave it unchanged - for one-off queries (§7.1 route 1); -- after the summon/release check, read `session.persona_id` and - return `None` when the field is absent or empty (§7.1); + for one-off queries; return `None` for one-off queries referencing + a `persona_id` it does not support (§7.1 route 1); +- after the embedded-command check, read `session.persona_id` and + return `None` when the field is absent or empty (§7.1 route 2); - return `None` when `session.persona_id` is set to a value it does - not support (§7.1); + not support (§7.1 route 2); - claim every utterance that reaches it when `session.persona_id` is - set to a value it supports, subject only to lightweight gate logic - (§7.2); + set to a value it supports, without confidence-gating (§7.1 + route 2); - set `Match.lang` to the resolved language of the match; - subscribe to `:` to receive its own dispatch; - derive each `ovos.utterance.speak` emission from the dispatch - Message per OVOS-MSG-1 §5 derivation semantics (PIPELINE-1 §9.6). + Message per OVOS-MSG-1 §5 derivation semantics (PIPELINE-1 §9.6); +- check for stop signals during long-running generation and cease + promptly when one is received for its session (§8.6). ### A persona pipeline plugin **SHOULD**: - return a `Match` immediately and defer generation to the handler - phase (§7.3); + phase (§7.2); - subscribe to `:converse` to support multi-turn interactions (§8.3); -- cease generation on stop signals for its session (§8.6); +- emit `ovos.persona.activated` on summon and `ovos.persona.dismissed` + on dismiss (§11); - respond to `ovos.pipeline..intents.list` per PIPELINE-1 §10; - respond to `ovos.persona.list` with its supported `persona_id` values (§8.7); - project summary state into session-resident fields for resumption - safety (§8.4). + safety, using a plugin-specific field name claimed in the + SESSION-1 registry (§8.4). ### A persona pipeline plugin **MAY**: @@ -632,7 +683,7 @@ listing the intent names it dispatches on. - hold conversation history in plugin-internal storage per the MAY-internal pathway of OVOS-SESSION-2 §2.4 (§8.4); - set `session.persona_id` via `Match.updated_session` when the match - resolves the persona identity (§7.5); + resolves the persona identity (§7.4); - expose an out-of-band query interface on `ovos.persona.ask` / `ovos.persona.ask.response` (§8.5); - support runtime persona management on `ovos.persona.register` / @@ -642,8 +693,7 @@ listing the intent names it dispatches on. - position persona stages after deterministic skills and after the stop stage in `session.pipeline` (§10); -- ensure `persona_id` values are unique across all loaded persona - plugins (§9); +- ensure each `persona_id` is served by at most one plugin (§9); - document each persona plugin's supported `persona_id` values so summoning plugins and introspection tooling can discover them. From 8a464f4f2e0a6d370f8a00ac53d5aa3012e63943 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Thu, 28 May 2026 08:39:27 +0100 Subject: [PATCH 4/7] PERSONA-1: remove session_id from ovos.persona.ask data payload session_id must not appear in message.data. The out-of-band query caller sets context.session.session_id for history scoping; the plugin reads from there. Added a clarifying sentence to that effect. Co-Authored-By: Claude Sonnet 4.6 --- persona.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/persona.md b/persona.md index d0951be..e8010f3 100644 --- a/persona.md +++ b/persona.md @@ -406,23 +406,24 @@ pattern on two bus topics: The request payload: -``` +```json { "persona_id": "", - "utterance": "", - "session_id": "" + "utterance": "" } ``` -The plugin generates a response for the specified `persona_id` and -emits it on `ovos.persona.ask.response` using the `reply()` -derivation (OVOS-MSG-1 §5) to route back to the original caller. -The request Message MUST carry a `source` so that `reply()` has a -destination to address; a sourceless request cannot be routed back. +The session for history lookup is read from `context.session.session_id` +of the request Message. The plugin generates a response for the +specified `persona_id` and emits it on `ovos.persona.ask.response` +using the `reply()` derivation (OVOS-MSG-1 §5) to route back to the +original caller. The request Message MUST carry a `source` so that +`reply()` has a destination to address; a sourceless request cannot +be routed back. The response payload: -``` +```json { "persona_id": "", "utterance": "", From 260f71a611a4451228a364cac0866e7a090b82be Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Thu, 28 May 2026 08:44:09 +0100 Subject: [PATCH 5/7] PERSONA-1: trim implementation leakage, fix bugs, tighten conformance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trim / implementation leakage: - §2: remove trailing paragraph that restates §1 "not define" - §3: remove explanatory sentence about why propagation matters - §4: remove tautological third bullet ("persona-related behaviour is absent") - §8.1: collapse verbose "Zero emissions is conformant" into the MAY line - §8.3: cut streaming token-by-token detail (covered by PIPELINE-1 §6.5); add MUST listen:true on get_response prompts; simplify multi-turn step list - §8.4: remove LLM-specific examples (embeddings, model state, transcripts); require session field be registered per SESSION-1 §2.1 rather than naming it - §9: remove internal "wiring, solver chain" comment from register payload - §10: collapse numbered justification list into one prose sentence Bug fixes: - §6: fix "client omitting persona_id" being listed as a dismiss mechanism — SESSION-1 §4 carries fields forward; only explicit clear counts; rewrite paragraph to reflect this and remove the rate-limit MUST NOT (impl concern) - §8.5: add response payload table; add MUST respond None for unknown persona_id - §8.6: both conditions become MUST (was SHOULD+MUST) - §9: MUST NOT serve same persona_id → SHOULD NOT (first match wins, not error) - §11: add ovos.persona.activated / ovos.persona.dismissed advisory signals - §12: move stop-awareness to separate MUST block; add listen:true to SHOULD; fix persona_id uniqueness wording Co-Authored-By: Claude Sonnet 4.6 --- persona.md | 326 +++++++++++++++++++++-------------------------------- 1 file changed, 130 insertions(+), 196 deletions(-) diff --git a/persona.md b/persona.md index e8010f3..cb5d871 100644 --- a/persona.md +++ b/persona.md @@ -88,17 +88,14 @@ with a different agent. Each persona has: within a deployment; - a **personality** — the system prompt, behaviour, and response style that characterise it; -- **capabilities** — what it can answer or do for the user. +- **capabilities** — the set of solvers, knowledge sources, or tools + it can invoke to answer the user. A persona plugin is a pipeline plugin (PIPELINE-1 §3) that hosts one or more personas. When a persona is active for a session, the plugin claims every utterance that reaches its pipeline stage and returns a natural-language response via its bundled handler. -This specification does not prescribe the internal shape of a persona -— how its personality is defined, what solvers it chains, or how its -capabilities are declared. Those are implementation-specific. - --- ## 3. The `persona_id` session field @@ -130,9 +127,7 @@ no whitespace). `persona_id` follows the standard session propagation rules of OVOS-SESSION-1 §4: it is carried unchanged across all derivations -and persists across utterances in the same session. This lets a -client set `persona_id` once at the start of a session and have it -apply for the duration of the conversation. +and persists across utterances in the same session. **Wire weight:** @@ -150,9 +145,7 @@ active (`persona_id` is absent from the session). In this mode: match an embedded persona command (§7.1 route 1); - the pipeline operates as a purely deterministic, skill-driven system — only intent-matching and fallback stages handle - utterances; -- persona-related behaviour, except for embedded commands - (§7.1 route 1), is absent. + utterances. No-persona mode is the deployment default. Every session starts in no-persona mode unless a client or layer-2 substrate sets @@ -175,6 +168,15 @@ effect of summon is to set `persona_id` in the session. `Match.updated_session`. The persona handles the summon utterance directly; the updated `persona_id` activates the persona for subsequent utterances. +- **One-off query.** The persona plugin detects an `ask` utterance + during `match` (§7.1 route 1), claims it, and generates a response + via its handler — but does **not** set `persona_id`. The session + state is unchanged; the persona answers the question without + activating permanently. This lets the pipeline handle "ask Alice + about X" as a single utterance without altering persona state. + If the referenced persona is not supported by this plugin, the + plugin SHOULD return `None` (letting the utterance fall through to + fallback) or MAY claim it with an error response. - **External summon.** A component outside the persona plugin sets `persona_id` on the inbound session. The persona plugin is not involved in the summon utterance — it only sees the new @@ -188,10 +190,10 @@ effect of summon is to set `persona_id` in the session. - a **session sync** (`ovos.session.sync`) from any component; - the **orchestrator** as a policy decision. -**Unknown persona:** If no loaded plugin supports the requested -`persona_id`, the persona stage returns `None` and the utterance -falls through to fallback stages. No error event is emitted; the -utterance is handled as if no persona were active. +**Unique identity:** A summon MUST reference an existing +`persona_id`. A summon that names an unknown persona has no effect: +the orchestrator or summoning component SHOULD log at WARN and leave +`persona_id` unchanged. --- @@ -201,40 +203,29 @@ utterance is handled as if no persona were active. session, returning the pipeline to no-persona mode (§4). The effect of dismiss is to clear `persona_id` from the session. -A dismiss occurs when `persona_id` is cleared from the session by: +A dismiss occurs when `persona_id` is explicitly cleared from the +session by: - the **persona plugin itself** — detecting a release intent during `match` (§7.1 route 1) and clearing `persona_id` via `Match.updated_session`; -- a **pipeline plugin** via handler-side session mutation - (OVOS-SESSION-2 §2.6); -- a **session sync** (`ovos.session.sync`) from any component that - explicitly sets `persona_id` to absent or empty; -- the **client** by explicitly setting `persona_id` to absent or - empty on a subsequent utterance. - -Note: session propagation (SESSION-1 §4) carries `persona_id` -unchanged when a message simply omits the field. Omission is not -dismissal — the client must explicitly clear the field. - -Deployments that want "stop" to return the session to no-persona -mode SHOULD include logic in their stop handling to clear -`persona_id` from the session. This is a deployment policy; it is -not imposed by this specification on OVOS-STOP-1. - -On dismiss, the persona plugin **SHOULD** emit -`ovos.persona.dismissed` (§11) so that UIs and other subscribers -can update state. The persona plugin detects dismissal when a -subsequent utterance arrives with `persona_id` absent after it was -previously set. - -On dismiss: - -- any in-progress generation for the session **SHOULD** cease - immediately; -- the persona's entry in per-session state (conversation history, - streaming handles, etc.) **SHOULD** be preserved for resumption - if the same persona is re-summoned. +- the **stop cascade** (OVOS-STOP-1) — clearing `persona_id` as + part of the escape-hatch behaviour. The stop plugin SHOULD clear + `persona_id` so that "stop" returns the session to + deterministic mode; +- a **pipeline plugin** via handler-side session mutation; +- a **session sync** (`ovos.session.sync`) from any component. + +Per OVOS-SESSION-1 §4, omitting `persona_id` from a message does +not clear it — the field is carried forward unchanged. A client +that wants to dismiss a persona must send an explicit clear (empty +string or absent field with intent to remove) via session sync or +a summoning plugin that handles the release intent. + +When dismiss is detected, any in-progress generation for the session +**SHOULD** cease. The persona's per-session state (conversation +history, etc.) **SHOULD** be preserved for resumption if the same +persona is re-summoned. --- @@ -254,9 +245,7 @@ order: `Match.updated_session`; - one-off queries (`ask`) claim the utterance and dispatch to the handler, which generates a response but does **not** change - `persona_id` — the session state is unchanged. A plugin that - does not support the referenced `persona_id` **MUST** return - `None` and let the utterance fall through to a plugin that does; + `persona_id` — the session state is unchanged; - list/check intents claim the utterance for introspection responses without mutating session state. This pathway runs **regardless** of the current @@ -269,25 +258,34 @@ order: - If `session.persona_id` is absent or empty → return `None` (no-persona mode, §4). - If `session.persona_id` is set to a value this plugin supports - → the plugin **MUST** claim the utterance. This is the defining - behavioural characteristic of a persona: an active persona - consumes everything that reaches its pipeline stage. The plugin - **MAY** apply lightweight gate logic before claiming (language - detection, explicit blacklist), but **MUST NOT** use confidence - thresholds to decide whether to claim — confidence gating - belongs in the deterministic pipeline, not in an active persona. + → return a `Match`. - If `session.persona_id` is set to a value this plugin does NOT support → return `None` (let another persona stage or fallback handle it). -### 7.2 Latency discipline +### 7.2 Active-persona catch-all + +When route 2 above applies (no embedded persona command detected, +and `persona_id` is present and supported), the plugin **MUST** +claim every utterance that reaches it, subject only to its +supported-identity check. This is the defining behavioural +characteristic of a persona: an active persona consumes everything +that reaches its pipeline stage. + +The plugin **MAY** apply lightweight gate logic before claiming +(language detection, minimum utterance length, blacklist), but it +**MUST NOT** use confidence thresholds or intent-matching to decide +whether to claim — those belong to the deterministic pipeline, not +to an active persona. + +### 7.3 Latency discipline Per PIPELINE-1 §4.4, a persona plugin **SHOULD** return a `Match` immediately and defer all computationally expensive work (generation, model inference, network calls) to the handler phase. The match phase is a routing decision; the generation phase belongs in the handler. -### 7.3 Match shape +### 7.4 Match shape When claiming, the plugin returns a `Match` (PIPELINE-1 §4.1) with: @@ -300,7 +298,7 @@ When claiming, the plugin returns a `Match` (PIPELINE-1 §4.1) with: | `utterance` | The specific candidate string from the input list. | | `updated_session` | Present when the plugin modifies session state as part of the match. | -### 7.4 Session mutation at match time +### 7.5 Session mutation at match time A persona plugin **MAY** mutate session state via `Match.updated_session` (PIPELINE-1 §4.2). Typical uses: @@ -328,13 +326,9 @@ standard dispatch payload (PIPELINE-1 §7.1): `lang`, `utterance`, `slots`. The handler generates a natural-language response and emits it via `ovos.utterance.speak` (PIPELINE-1 §9.6). -A handler **MAY** emit zero, one, or multiple -`ovos.utterance.speak` Messages: -- Zero emissions is conformant — a persona that acts silently (logs - the interaction, triggers an external side effect) without speaking - is valid. -- Multiple emissions are conveyed in order and the output stage - **SHOULD** preserve that order. +A handler **MAY** emit zero, one, or multiple `ovos.utterance.speak` +Messages. Multiple emissions are conveyed in order and the output stage +**SHOULD** preserve that order. ### 8.2 Handler-side session mutation @@ -351,25 +345,15 @@ Typical handler-side mutations: ### 8.3 Long-running handlers A persona handler **MAY** block for an unbounded duration -(PIPELINE-1 §6.5) — for example, to stream a response token by -token. Streaming is in-handler: the handler emits successive -`ovos.utterance.speak` Messages as each token or fragment becomes -available, then returns. - -Multi-turn interactions (the "get_response" pattern) are handled -through multiple consecutive dispatches. The handler per invocation: - -1. emits an `ovos.utterance.speak` with its prompt, carrying - `listen: true` (PIPELINE-1 §9.6, CONVERSE-1 §5.1) — this is - required so that the output layer re-opens the user input channel - after delivery; -2. saves continuation state (in session fields or plugin-internal - storage, §8.4); -3. returns, allowing `ovos.intent.handler.complete` to fire; -4. on the next utterance, the converse plugin (OVOS-CONVERSE-1) - routes `:converse` to the persona because - `skill_id == pipeline_id` (PIPELINE-1 §7.0), and the handler - resumes based on the saved state. +(PIPELINE-1 §6.5). When emitting a get_response-style prompt, the +`ovos.utterance.speak` Message **MUST** carry `listen: true` so that +the audio output service reopens the microphone after speech. + +Multi-turn interactions are handled through multiple consecutive +dispatches: the handler emits its prompt and returns; on the next +utterance, the converse plugin (OVOS-CONVERSE-1) routes +`:converse` to the persona because +`skill_id == pipeline_id` in CONVERSE-1's active-handler check. A persona plugin that supports multi-turn **SHOULD** subscribe to `:converse` to receive follow-up utterances. @@ -378,19 +362,14 @@ A persona plugin that supports multi-turn **SHOULD** subscribe to A persona plugin **MAY** maintain conversation history keyed on `session.session_id`, following the MAY-internal pathway of -OVOS-SESSION-2 §2.4. History that is too large to project into -session-resident fields (multi-turn transcripts, embeddings, model -state) is held in plugin-internal storage with best-effort -resumption. - -A persona plugin **SHOULD** project summary state into -session-resident fields when practical — for example, a field -carrying the current topic or a compact representation of the -conversation state — so that resumption across orchestrator -restarts or multi-orchestrator deployments retains basic continuity -even when full history is held internally. Any session field used -for this purpose MUST be claimed in the SESSION-1 §2.1 registry -under a plugin-specific name to avoid collisions with other plugins. +OVOS-SESSION-2 §2.4. History too large to project into session-resident +fields is held in plugin-internal storage with best-effort resumption. + +A persona plugin **SHOULD** project summary state into a +session-resident field registered per OVOS-SESSION-1 §2.1 — so that +resumption across orchestrator restarts or multi-orchestrator +deployments retains basic continuity even when full history is held +internally. ### 8.5 Out-of-band query @@ -406,42 +385,24 @@ pattern on two bus topics: The request payload: -```json -{ - "persona_id": "", - "utterance": "" -} -``` +| Field | Type | Required | Meaning | +|-------|------|----------|---------| +| `persona_id` | string | yes | Target persona identity. | +| `utterance` | string | yes | Query text. | +| `session_id` | string | no | Session context for history continuity. | -The session for history lookup is read from `context.session.session_id` -of the request Message. The plugin generates a response for the -specified `persona_id` and emits it on `ovos.persona.ask.response` -using the `reply()` derivation (OVOS-MSG-1 §5) to route back to the -original caller. The request Message MUST carry a `source` so that -`reply()` has a destination to address; a sourceless request cannot -be routed back. +The plugin generates a response for the specified `persona_id` using +the `reply()` derivation (OVOS-MSG-1 §5) to route back to the caller. +If `persona_id` is not supported by this plugin, the plugin **MUST** +respond with `None` rather than silently drop the request. The response payload: -```json -{ - "persona_id": "", - "utterance": "", - "response": "" -} -``` - | Field | Type | Required | Meaning | |-------|------|----------|---------| -| `persona_id` | string | yes | The persona identity that generated the reply. | -| `utterance` | string | yes | Echo of the request `utterance`. | -| `response` | string | yes | The generated natural-language reply. | - -Because `ovos.persona.ask` is broadcast, all persona plugins -receive every request and must self-filter on `persona_id`. A -caller that knows which `pipeline_id` handles the target -`persona_id` (discovered via `ovos.persona.list`, §8.7) MAY -address the request directly to reduce noise. +| `persona_id` | string | yes | The persona that answered. | +| `utterance` | string | yes | Echo of the query. | +| `response` | string | yes | The generated response text. | This interface is **not** part of the utterance lifecycle — it bypasses the pipeline, the dispatch mechanism, and the @@ -459,10 +420,9 @@ the context provided. ### 8.6 Stop awareness -A persona handler **MUST** check for stop signals during -long-running generation and **MUST** cease generation and return -promptly when a stop signal for its session is received (per -OVOS-STOP-1). +A persona handler **MUST** check for stop signals during long-running +generation and **MUST** cease generation and return promptly when a +stop signal (OVOS-STOP-1) arrives for its session. ### 8.7 Persona discovery @@ -498,26 +458,25 @@ deployment-specific aggregation layer. A deployment MAY load multiple persona plugins under different `pipeline_id` values, each hosting one or more `persona_id` values. -**Identity namespace:** `persona_id` values SHOULD be unique within -a deployment — a given identity should be handled by exactly one -plugin. When two plugins both claim the same `persona_id`, the -first one in pipeline order claims every utterance for that -identity; the second never matches. Deployments SHOULD avoid this -ambiguity. If detected at runtime (e.g. via `ovos.persona.list` -responses), the deployer SHOULD log at WARN. +**Identity namespace:** `persona_id` values are unique within a +deployment. Two plugins SHOULD NOT serve the same `persona_id`. A +deployment that attempts to register a duplicate `persona_id` is +misconfigured; the orchestrator SHOULD log at WARN and the first +match in pipeline order wins. **Pipeline selection:** When `persona_id` is set, the pipeline iterates through its stages as usual. The first persona stage whose supported identities include the requested `persona_id` claims the utterance (§7.1). If no persona plugin supports the requested `persona_id`, iteration continues to fallback stages — the utterance -is handled as if no persona were active (§5). +is handled by the deterministic fallback. -**Multiple pipeline positions:** A deployment MAY place the same -persona plugin at multiple positions in the pipeline (for example, -an early fast-path and a late reasoning-heavy position). In that -case both entries have the same `pipeline_id` and the same supported -identities; the first position that executes claims the utterance. +**Multiple pipeline positions:** A deployment MAY place persona +stages at multiple positions in the pipeline (for example, a +fast-path persona early for dialogue and a heavyweight persona late +for reasoning). When multiple persona stages share the same +`persona_id`, only the first one that matches it in pipeline order +claims the utterance. **Dynamic registration.** A persona plugin MAY expose bus topics for runtime persona management without restart: @@ -527,10 +486,7 @@ for runtime persona management without restart: | `ovos.persona.register` | any component → persona | Register a new persona at runtime | | `ovos.persona.deregister` | any component → persona | Deregister an existing persona | -The payload for `ovos.persona.register` conveys the persona -identity; the persona definition (wiring, solver chain, -personality) is loaded from the plugin's own configuration -sources, not from the message: +The payload for `ovos.persona.register`: ``` { @@ -556,15 +512,10 @@ duplicate or unknown registrations. ## 10. Pipeline positioning A deployment that includes persona stages **SHOULD** place them -after deterministic intent-matching stages and after the stop stage. -This ordering ensures: - -1. **Skills first** — utterances targeting registered intents are - handled by their dedicated handlers before the persona sees them. -2. **Stop before persona** — the escape hatch can interrupt an active - persona. -3. **Persona as fallback** — when active, the persona receives only - what skills did not match. +after deterministic intent-matching stages and after the stop stage, +so that skills handle their intents first, the escape hatch can +interrupt an active persona, and the persona acts as a catch-all +fallback. A typical ordering: @@ -592,35 +543,19 @@ are conformant. |-------|-----------|---------| | `:` | orchestrator → persona | First-match dispatch for the persona plugin (§8.1) | | `:converse` | orchestrator → persona | Follow-up dispatch during multi-turn interactions (§8.3) | -| `ovos.persona.activated` | persona → broadcast | A persona became active for a session (§5) | -| `ovos.persona.dismissed` | persona → broadcast | A persona was deactivated for a session (§6) | | `ovos.persona.ask` | any component → persona | Out-of-band query (§8.5) | | `ovos.persona.ask.response` | persona → any component | Query response (§8.5) | | `ovos.persona.list` | any component → persona | Enumerate supported persona identities (§8.7) | | `ovos.persona.list.response` | persona → any component | Supported-identity listing (§8.7) | | `ovos.persona.register` | any component → persona | Runtime persona registration (§9) | | `ovos.persona.deregister` | any component → persona | Runtime persona deregistration (§9) | +| `ovos.persona.activated` | persona → broadcast | A persona has become active for a session (best-effort) | +| `ovos.persona.dismissed` | persona → broadcast | A persona has been dismissed from a session (best-effort) | -**`ovos.persona.activated`** is emitted by the persona plugin when -it processes a summon (route 1, §7.1) or first detects an active -`persona_id` it supports on an inbound utterance. Payload: - -```json -{ "persona_id": "alice", "session_id": "abc123" } -``` - -**`ovos.persona.dismissed`** is emitted by the persona plugin when -it detects that `persona_id` has been cleared for a session it was -serving. Payload: - -```json -{ "persona_id": "alice", "session_id": "abc123" } -``` - -Both signals are best-effort: a plugin that crashes or is -unloaded before emitting them is conformant. Subscribers (UIs, -loggers) MUST NOT rely on these signals as authoritative session -state — the session itself is the source of truth. +`ovos.persona.activated` payload: `{ "persona_id": "...", "session_id": "..." }`. +`ovos.persona.dismissed` payload: `{ "persona_id": "...", "session_id": "..." }`. +These are advisory signals emitted on a best-effort basis; consumers +**MUST NOT** rely on them for correctness. Session state is authoritative. All dispatch topics follow the PIPELINE-1 §7 topic shape and fire the handler-lifecycle trio (PIPELINE-1 §8). The persona handler emits @@ -645,38 +580,36 @@ listing the intent names it dispatches on. query, list, check) in `match` **before** checking `session.persona_id`, and handle each according to its type — set or clear `persona_id` for summon/release, leave it unchanged - for one-off queries; return `None` for one-off queries referencing - a `persona_id` it does not support (§7.1 route 1); -- after the embedded-command check, read `session.persona_id` and - return `None` when the field is absent or empty (§7.1 route 2); + for one-off queries (§7.1 route 1); +- after the summon/release check, read `session.persona_id` and + return `None` when the field is absent or empty (§7.1); - return `None` when `session.persona_id` is set to a value it does - not support (§7.1 route 2); + not support (§7.1); - claim every utterance that reaches it when `session.persona_id` is - set to a value it supports, without confidence-gating (§7.1 - route 2); + set to a value it supports, subject only to lightweight gate logic + (§7.2); - set `Match.lang` to the resolved language of the match; - subscribe to `:` to receive its own dispatch; - derive each `ovos.utterance.speak` emission from the dispatch Message per OVOS-MSG-1 §5 derivation semantics (PIPELINE-1 §9.6); -- check for stop signals during long-running generation and cease - promptly when one is received for its session (§8.6). +- cease generation and return promptly on stop signals for its session + (§8.6). ### A persona pipeline plugin **SHOULD**: - return a `Match` immediately and defer generation to the handler - phase (§7.2); + phase (§7.3); - subscribe to `:converse` to support multi-turn interactions (§8.3); -- emit `ovos.persona.activated` on summon and `ovos.persona.dismissed` - on dismiss (§11); +- carry `listen: true` on `ovos.utterance.speak` when used as a + get_response prompt (§8.3); - respond to `ovos.pipeline..intents.list` per PIPELINE-1 §10; - respond to `ovos.persona.list` with its supported `persona_id` values (§8.7); -- project summary state into session-resident fields for resumption - safety, using a plugin-specific field name claimed in the - SESSION-1 registry (§8.4). +- project summary state into a session-resident field registered per + OVOS-SESSION-1 §2.1 for resumption safety (§8.4). ### A persona pipeline plugin **MAY**: @@ -684,7 +617,7 @@ listing the intent names it dispatches on. - hold conversation history in plugin-internal storage per the MAY-internal pathway of OVOS-SESSION-2 §2.4 (§8.4); - set `session.persona_id` via `Match.updated_session` when the match - resolves the persona identity (§7.4); + resolves the persona identity (§7.5); - expose an out-of-band query interface on `ovos.persona.ask` / `ovos.persona.ask.response` (§8.5); - support runtime persona management on `ovos.persona.register` / @@ -694,7 +627,8 @@ listing the intent names it dispatches on. - position persona stages after deterministic skills and after the stop stage in `session.pipeline` (§10); -- ensure each `persona_id` is served by at most one plugin (§9); +- ensure `persona_id` values do not overlap across loaded persona + plugins (§9); - document each persona plugin's supported `persona_id` values so summoning plugins and introspection tooling can discover them. From 50c442ceac6eed84ddac97e91c5aef19b483d1ff Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Thu, 28 May 2026 09:07:43 +0100 Subject: [PATCH 6/7] PERSONA-1: capability discovery, fallback_pipeline_id, dialog transformers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multi-persona coexistence (§9): - Reframe as the primary pluggable-capabilities mechanism - Add capability-based routing: routing skill/UI queries ovos.persona.list tags to select persona_id; plugin only declares, doesn't participate - Define persona-fallback pipeline_id: secondary pipeline position that claims utterances when persona_id is absent (route 3 catch-all) - Only one fallback stage SHOULD be active per pipeline Match contract (§7.1): - Add route 3: fallback_pipeline_id match rules — claim when persona_id absent or supported; return None when another persona's id is active Discovery (§8.7): - ovos.persona.list.response now carries per-persona objects with persona_id, name (optional), tags (optional freeform string array) - Response also carries fallback_pipeline_id when registered - Tags are advisory, vocabulary is deployment-defined Pipeline positioning (§10): - Distinguish main pipeline_id position (active-persona) from fallback_pipeline_id position (persona-fallback catch-all) - Updated typical ordering example with both positions §2 (What is a persona): - Explicit "black box" statement — internal machinery is out of scope - Add dialog transformers note: personality shaping belongs in TRANSFORM-1, not in the persona plugin §3: - Explicit single-value statement: one persona_id per session §4 (No-persona mode): - Remove default-persona policy paragraph (deployment concern) - Reference persona-fallback as the mechanism that handles utterances in no-persona mode §1 / See also: - Add output transformation to "does not define" §11 (Bus surface): - Add fallback_pipeline_id dispatch row §12 (Conformance): - Add SHOULD include tags in list response - Add MAY register fallback_pipeline_id - Add deployment SHOULD rules for fallback positioning and designation Co-Authored-By: Claude Sonnet 4.6 --- persona.md | 222 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 157 insertions(+), 65 deletions(-) diff --git a/persona.md b/persona.md index cb5d871..91eb60f 100644 --- a/persona.md +++ b/persona.md @@ -61,9 +61,9 @@ This specification defines: It does **not** define: - **the internal machinery** of a persona — whether the handler uses a - language model, a rule-based engine, a retrieval system, a solver - chain, or any other approach is entirely the plugin's business. - The spec fixes only the observable bus contract; + language model, a rule-based engine, a retrieval system, or any + other approach is entirely the plugin's business. The spec fixes + only the observable bus contract; - **persona configuration format** — the system prompt, identity definition, solver wiring, or capability declaration is a deployment concern; @@ -72,6 +72,9 @@ It does **not** define: conformant; - **vocabulary files, matching algorithms, or confidence thresholds** — the plugin decides when to claim an utterance; +- **output transformation** — cross-cutting personality shaping (tone, + register, post-processing) is handled by dialog transformers + (OVOS-TRANSFORM-1), not by this specification; - **GUI, TTS, or output-layer behaviour** — response delivery beyond `ovos.utterance.speak` is out of scope. @@ -86,16 +89,28 @@ with a different agent. Each persona has: - an **identity** — a `persona_id` string that uniquely names it within a deployment; -- a **personality** — the system prompt, behaviour, and response - style that characterise it; -- **capabilities** — the set of solvers, knowledge sources, or tools - it can invoke to answer the user. +- a **personality** — the behaviour and response style that + characterise it; +- **capabilities** — what it can answer or do for the user. + +A persona is a black box from this specification's perspective. Whether +it is implemented with a language model, a rule-based engine, a +retrieval system, or any other approach is entirely the plugin's +business. The spec fixes only the observable bus contract. A persona plugin is a pipeline plugin (PIPELINE-1 §3) that hosts one or more personas. When a persona is active for a session, the plugin claims every utterance that reaches its pipeline stage and returns a natural-language response via its bundled handler. +**Personality and dialog transformers.** A persona is responsible for +the content of its responses. Cross-cutting output transformations — +tone, verbosity, language register, post-processing — are better +handled by dialog transformers (OVOS-TRANSFORM-1 §3.5), which run +after `ovos.utterance.speak` regardless of which pipeline stage +generated the response. A deployment MAY combine both: a persona that +provides content and dialog transformers that shape it. + --- ## 3. The `persona_id` session field @@ -113,11 +128,16 @@ deployment-defined. This specification places no constraint on the string beyond the opaque-string rules of OVOS-SESSION-1 §2.2 (no `:`, no whitespace). +`persona_id` is a **single value** — exactly one persona identity may +be active per session at a time. Composition of multiple personas +within a single session is not in scope. + **Semantics:** - When `persona_id` is **absent** (not set), no persona is active. Persona stages MUST return `None` for all utterances except - those matching an embedded persona command (§7.1 route 1). + those matching an embedded persona command (§7.1 route 1) or + handled by a persona-fallback stage (§7.1 route 3). - When `persona_id` is **present and non-empty**, the corresponding persona is active. Persona stages whose supported identities include this value MUST claim utterances that reach them (§7). @@ -148,12 +168,10 @@ active (`persona_id` is absent from the session). In this mode: utterances. No-persona mode is the deployment default. Every session starts in -no-persona mode unless a client or layer-2 substrate sets -`persona_id` on the initial utterance. Some deployments MAY -configure a default persona; in that case the orchestrator or a -session-initialisation component sets `persona_id` before the -first utterance, mimicking a client-side summon. This is a -deployment policy, not a plugin concern. +no-persona mode unless a client or layer-2 substrate sets `persona_id` +on the initial utterance. In no-persona mode, utterances that reach a +persona-fallback stage (§7.1 route 3, §9) are still handled by that +stage; all other persona stages return `None`. --- @@ -256,13 +274,30 @@ order: 2. **Active-persona catch-all.** If no embedded command was detected, the plugin checks `session.persona_id`: - If `session.persona_id` is absent or empty → return `None` - (no-persona mode, §4). + (no-persona mode, §4); unless this plugin is registered as a + persona-fallback stage for this pipeline position (route 3). - If `session.persona_id` is set to a value this plugin supports → return a `Match`. - If `session.persona_id` is set to a value this plugin does NOT support → return `None` (let another persona stage or fallback handle it). +3. **Persona-fallback catch-all.** A persona plugin MAY register a + secondary `fallback_pipeline_id` (§9) in addition to its main + `pipeline_id`. When the pipeline invokes the plugin under its + `fallback_pipeline_id`, the match rules are: + - If `session.persona_id` is absent or empty → claim the utterance + (this is the fallback case — no persona is active and no other + stage matched). + - If `session.persona_id` is set to a value this plugin supports + → claim (consistent with route 2). + - If `session.persona_id` is set to a value another plugin supports + → return `None` (respect the active persona). + + The persona-fallback stage is how utterances are handled when no + skill matched and no persona is active. It is positioned at the end + of the pipeline, after all skill stages (§10). + ### 7.2 Active-persona catch-all When route 2 above applies (no embedded persona command detected, @@ -437,19 +472,42 @@ request-response pattern on two bus topics: The plugin responds to `ovos.persona.list` with: -``` +```json { - "personas": ["alice", "bob"], - "pipeline_id": "" + "pipeline_id": "", + "fallback_pipeline_id": "", + "personas": [ + { + "persona_id": "alice", + "name": "Alice", + "tags": ["cooking", "recipes", "food"] + }, + { + "persona_id": "bob", + "name": "Bob", + "tags": ["code", "python", "debugging"] + } + ] } ``` -This interface is independent of the utterance lifecycle. A -deployment that includes multiple persona stages MUST scope -responses per stage — each relevant plugin responds with its own -supported set. A component that needs the full deployment-wide -set MUST query all persona stages individually or use a -deployment-specific aggregation layer. +| Field | Type | Required | Meaning | +|-------|------|----------|---------| +| `pipeline_id` | string | yes | The plugin's main pipeline_id. | +| `fallback_pipeline_id` | string | no | The plugin's persona-fallback pipeline_id, if registered (§9). | +| `personas` | array | yes | One object per supported persona identity. | +| `personas[].persona_id` | string | yes | The persona identity (§3). | +| `personas[].name` | string | no | Human-readable display name. | +| `personas[].tags` | string[] | no | Freeform capability or domain tags. Vocabulary is deployment-defined. | + +`tags` are advisory — they exist so routing skills and UIs can make +informed summon decisions. This specification does not standardise tag +vocabulary; deployments SHOULD document their tag taxonomy. + +This interface is independent of the utterance lifecycle. Each persona +plugin responds with its own supported set. A component that needs the +full deployment-wide set MUST query each persona stage individually or +use a deployment-specific aggregation layer. --- @@ -457,26 +515,42 @@ deployment-specific aggregation layer. A deployment MAY load multiple persona plugins under different `pipeline_id` values, each hosting one or more `persona_id` values. - -**Identity namespace:** `persona_id` values are unique within a -deployment. Two plugins SHOULD NOT serve the same `persona_id`. A -deployment that attempts to register a duplicate `persona_id` is -misconfigured; the orchestrator SHOULD log at WARN and the first -match in pipeline order wins. - -**Pipeline selection:** When `persona_id` is set, the pipeline -iterates through its stages as usual. The first persona stage whose -supported identities include the requested `persona_id` claims the -utterance (§7.1). If no persona plugin supports the requested -`persona_id`, iteration continues to fallback stages — the utterance -is handled by the deterministic fallback. - -**Multiple pipeline positions:** A deployment MAY place persona -stages at multiple positions in the pipeline (for example, a -fast-path persona early for dialogue and a heavyweight persona late -for reasoning). When multiple persona stages share the same -`persona_id`, only the first one that matches it in pipeline order -claims the utterance. +This is the primary mechanism for pluggable personalities and +capabilities: each persona plugin is a self-contained agent with its +own identity, capabilities, and tags; a routing skill or UI selects +among them by setting `session.persona_id`. + +**Identity namespace:** `persona_id` values SHOULD be unique within +a deployment. When two plugins both claim the same `persona_id`, the +first one in pipeline order claims every utterance for that identity; +the second never matches. Deployments SHOULD avoid this; if detected +at runtime (e.g. via `ovos.persona.list` responses), the orchestrator +SHOULD log at WARN. + +**Capability-based routing.** A skill or UI that wants to select among +multiple loaded personas SHOULD query `ovos.persona.list`, collect the +`tags` arrays from all responses, and use them to pick a `persona_id` +to set on the session. The routing logic — tag matching, user +preference, context — is a deployment or skill concern, not a plugin +concern. The persona plugin only declares its tags; it does not +participate in selection. + +**Persona-fallback pipeline_id.** A persona plugin MAY register a +secondary pipeline position — its `fallback_pipeline_id` — that acts +as a catch-all when no persona is active and no skill matched. The +plugin exposes `fallback_pipeline_id` in its `ovos.persona.list` +response (§8.7). The orchestrator includes this id in +`session.pipeline` at the appropriate position (§10). When invoked +under this id, the plugin applies route 3 match logic (§7.1): it +claims utterances where `session.persona_id` is absent or matches a +supported identity, and returns `None` when another persona's id is +active. + +Only one persona-fallback stage SHOULD be active in a given pipeline. +When multiple plugins expose a `fallback_pipeline_id`, pipeline order +determines which one handles unmatched utterances. Deployments SHOULD +designate one persona as the fallback and exclude others' +`fallback_pipeline_id` from the active pipeline. **Dynamic registration.** A persona plugin MAY expose bus topics for runtime persona management without restart: @@ -488,23 +562,19 @@ for runtime persona management without restart: The payload for `ovos.persona.register`: -``` -{ - "persona_id": "" -} +```json +{ "persona_id": "" } ``` The payload for `ovos.persona.deregister`: -``` -{ - "persona_id": "" -} +```json +{ "persona_id": "" } ``` These topics are **MAY** — a deployment that does not need runtime persona management can omit them. When present, the plugin validates -the `persona_id` namespace uniqueness rules (§9) and rejects +the `persona_id` namespace uniqueness rules above and rejects duplicate or unknown registrations. --- @@ -513,23 +583,34 @@ duplicate or unknown registrations. A deployment that includes persona stages **SHOULD** place them after deterministic intent-matching stages and after the stop stage, -so that skills handle their intents first, the escape hatch can -interrupt an active persona, and the persona acts as a catch-all -fallback. +so that skills handle their intents first and the escape hatch can +interrupt an active persona. -A typical ordering: +A persona plugin's main `pipeline_id` (active-persona catch-all, +§7.1 route 2) SHOULD appear after skill stages. Its optional +`fallback_pipeline_id` (persona-fallback, §7.1 route 3) SHOULD +appear after all skill stages and at or near the end of the pipeline, +before any last-resort fallback. + +A typical ordering with both positions: ``` session.pipeline: [ - "stop_high", # interrupt (escape hatch) - "converse", # active-handler poll - "skill_high", # deterministic registered intents + "stop_high", # interrupt (escape hatch) + "converse", # active-handler poll + "skill_high", # deterministic registered intents "skill_medium", - "persona", # conversational agent (active persona) - "fallback_low" # last-resort fallback + "persona", # active-persona catch-all (route 2) + "persona_fallback", # persona-fallback catch-all (route 3) + "fallback_low" # last-resort fallback ] ``` +`persona` and `persona_fallback` are different pipeline_id values +registered by the same plugin. A deployment that does not use the +persona-fallback feature simply omits `persona_fallback` from the +pipeline. + A deployment **MAY** place a persona stage earlier when the persona is specialised for a domain that should pre-empt general-purpose matchers. Multiple persona stages at different pipeline positions @@ -541,8 +622,9 @@ are conformant. | Topic | Direction | Purpose | |-------|-----------|---------| -| `:` | orchestrator → persona | First-match dispatch for the persona plugin (§8.1) | +| `:` | orchestrator → persona | Active-persona dispatch (§8.1, §7.1 routes 1–2) | | `:converse` | orchestrator → persona | Follow-up dispatch during multi-turn interactions (§8.3) | +| `:` | orchestrator → persona | Persona-fallback dispatch (§7.1 route 3, §9) | | `ovos.persona.ask` | any component → persona | Out-of-band query (§8.5) | | `ovos.persona.ask.response` | persona → any component | Query response (§8.5) | | `ovos.persona.list` | any component → persona | Enumerate supported persona identities (§8.7) | @@ -611,6 +693,12 @@ listing the intent names it dispatches on. - project summary state into a session-resident field registered per OVOS-SESSION-1 §2.1 for resumption safety (§8.4). +### A persona pipeline plugin **SHOULD**: + +- include `tags` per persona in its `ovos.persona.list` response so + that routing skills and UIs can make informed summon decisions (§8.7, + §9). + ### A persona pipeline plugin **MAY**: - support multiple `persona_id` values (§9); @@ -620,6 +708,8 @@ listing the intent names it dispatches on. resolves the persona identity (§7.5); - expose an out-of-band query interface on `ovos.persona.ask` / `ovos.persona.ask.response` (§8.5); +- register a `fallback_pipeline_id` and apply route 3 match logic when + invoked under it (§7.1, §9); - support runtime persona management on `ovos.persona.register` / `ovos.persona.deregister` (§9). @@ -627,10 +717,12 @@ listing the intent names it dispatches on. - position persona stages after deterministic skills and after the stop stage in `session.pipeline` (§10); +- position the persona-fallback stage (`fallback_pipeline_id`) after + all skill stages and before last-resort fallback (§10); - ensure `persona_id` values do not overlap across loaded persona plugins (§9); -- document each persona plugin's supported `persona_id` values so - summoning plugins and introspection tooling can discover them. +- designate at most one persona-fallback stage in the active pipeline + (§9). --- From 8b5d186564245804006faace1c7f08c218261a55 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Thu, 28 May 2026 09:31:41 +0100 Subject: [PATCH 7/7] =?UTF-8?q?PERSONA-1:=20rename=20ask/ask.response=20?= =?UTF-8?q?=E2=86=92=20query/answer;=20fix=20session=5Fid=20in=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ovos.persona.ask / ovos.persona.ask.response → ovos.persona.query / ovos.persona.answer. The verb pair query/answer is unambiguous, drops the .response suffix, and fits the established pattern for data-exchange topics per AGENTS.md §4.5. Also remove session_id from the ovos.persona.query request payload — session context is read from context.session.session_id, never data. Co-Authored-By: Claude Sonnet 4.6 --- persona.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/persona.md b/persona.md index 91eb60f..449e140 100644 --- a/persona.md +++ b/persona.md @@ -415,8 +415,8 @@ pattern on two bus topics: | Topic | Direction | Purpose | |-------|-----------|---------| -| `ovos.persona.ask` | any component → persona | Out-of-band query | -| `ovos.persona.ask.response` | persona → requesting component | Query response | +| `ovos.persona.query` | any component → persona | Out-of-band query | +| `ovos.persona.answer` | persona → requesting component | Query response | The request payload: @@ -424,7 +424,9 @@ The request payload: |-------|------|----------|---------| | `persona_id` | string | yes | Target persona identity. | | `utterance` | string | yes | Query text. | -| `session_id` | string | no | Session context for history continuity. | + +Session context for history continuity is read from +`context.session.session_id` of the request Message. The plugin generates a response for the specified `persona_id` using the `reply()` derivation (OVOS-MSG-1 §5) to route back to the caller. @@ -625,8 +627,8 @@ are conformant. | `:` | orchestrator → persona | Active-persona dispatch (§8.1, §7.1 routes 1–2) | | `:converse` | orchestrator → persona | Follow-up dispatch during multi-turn interactions (§8.3) | | `:` | orchestrator → persona | Persona-fallback dispatch (§7.1 route 3, §9) | -| `ovos.persona.ask` | any component → persona | Out-of-band query (§8.5) | -| `ovos.persona.ask.response` | persona → any component | Query response (§8.5) | +| `ovos.persona.query` | any component → persona | Out-of-band query (§8.5) | +| `ovos.persona.answer` | persona → any component | Query response (§8.5) | | `ovos.persona.list` | any component → persona | Enumerate supported persona identities (§8.7) | | `ovos.persona.list.response` | persona → any component | Supported-identity listing (§8.7) | | `ovos.persona.register` | any component → persona | Runtime persona registration (§9) | @@ -706,8 +708,8 @@ listing the intent names it dispatches on. MAY-internal pathway of OVOS-SESSION-2 §2.4 (§8.4); - set `session.persona_id` via `Match.updated_session` when the match resolves the persona identity (§7.5); -- expose an out-of-band query interface on `ovos.persona.ask` / - `ovos.persona.ask.response` (§8.5); +- expose an out-of-band query interface on `ovos.persona.query` / + `ovos.persona.answer` (§8.5); - register a `fallback_pipeline_id` and apply route 3 match logic when invoked under it (§7.1, §9); - support runtime persona management on `ovos.persona.register` /