From 2a4ea8a42188181e62f6cc71fe69c2513d901ea1 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Tue, 26 May 2026 02:13:46 +0100 Subject: [PATCH 1/6] OVOS-SESSION-2: Session Lifecycle and State Ownership (draft v1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion lifecycle spec to OVOS-SESSION-1 (which defines only wire shape and explicitly defers lifecycle). Closes the "session lifecycle" gap APPENDIX §7 has tracked since the spec set's inception, and is the normative reference CONTEXT-1 and CONVERSE-1 have been forward-referencing. What the spec defines: - §2 State-ownership model: - 2.1 Bus is stateless transport - 2.2 Orchestrator is stateless for named sessions - 2.3 Orchestrator owns session_id == "default" - 2.4 The projection mandate — components MUST project cross-utterance session-keyed state into session-resident fields they own (via Match.updated_session per PIPELINE-1 §4.2). Drives resumption parity (§5) - 2.5 Clients own their named sessions; persistence is their choice - 2.6 In-place session mutation only at transformer, pipeline, and handler boundaries — bus events do not mutate session in the current utterance - §3 Per-utterance round: client emits → assistant runs PIPELINE-1 lifecycle → emits N responses, each carrying session at emission point → client receives and updates - §4 Client-side merge rules — minimal and permissive: - session_id is the only key (no routing-data filtering; session_id uniquely identifies channels) - every assistant-emitted Message carries a valid session - ovos.utterance.handled is the canonical convergence point (no new topic needed) - §5 Resumption is implicit. A client re-emits a previously- used session_id with held state at any time; orchestrator's statelessness for named sessions makes it work uniformly. Resumption-safe = session-resident. Per §2.4 projection mandate, nothing component-held is non-resumption-safe in a conformant deployment - §6 Default-session ownership: orchestrator holds persistent in-process state for "default"; restart drops it (acceptable for local-device "fresh start" semantics); components keyed on default get effective persistence within deployment lifetime - §7 Conformance for bus, orchestrator, component, client, and the special-case default-session client - §8 Non-goals — no persistence protocol, no auth, no cross-client coordination, no lifecycle observability events The §2.4 projection mandate has cascading effects on existing in-flight specs (CONVERSE-1 in particular). Follow-up PRs will land separately to revise CONVERSE-1's response-mode wait state to project to session.response_mode as a structured field and drop the side-band set/clear topics now made redundant. Single file per AGENTS.md repo policy. Co-Authored-By: Claude Opus 4.7 (1M context) --- session-lifecycle.md | 610 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 610 insertions(+) create mode 100644 session-lifecycle.md diff --git a/session-lifecycle.md b/session-lifecycle.md new file mode 100644 index 0000000..f9e9a48 --- /dev/null +++ b/session-lifecycle.md @@ -0,0 +1,610 @@ +# Session Lifecycle and State Ownership Specification + +**Spec ID:** OVOS-SESSION-2 · **Version:** 1 · **Status:** Draft + +This document defines **who owns session state**, **when it is +mutated**, **how it propagates between client and assistant**, and +**how a conversation resumes** after arbitrary elapsed time or +across an orchestrator restart. + +It is the lifecycle complement to OVOS-SESSION-1, which defines +the wire shape of the `session` carrier and explicitly defers +lifecycle (SESSION-1 §1 / §6 non-goals). Where SESSION-1 fixes +*what `session` looks like on the bus*, this specification fixes +*who is allowed to mutate it, when, and how its state survives +across utterances*. + +The central principle is **statelessness with one named +exception**: the orchestrator and the message bus hold no +authoritative session state for any session except the reserved +`session_id == "default"` (SESSION-1 §3.1), which the orchestrator +fully owns. Every other session is **client-owned**: a participant +on the user side of the bus boundary holds the authoritative state +for its own `session_id` and persists it however it chooses. This +arrangement makes conversations resumable after arbitrary elapsed +time, lets an orchestrator restart without losing client-side +continuity, and lets multiple orchestrators in a deployment serve +the same session without coordination. + +It builds on four companion specifications: + +- the *Bus Message Specification* (OVOS-MSG-1) — the envelope, + routing keys, `forward` / `reply` / `response` derivations, + and the asynchronous nature of the bus this spec relies on; +- the *Session Carrier Wire Shape Specification* (OVOS-SESSION-1) — + the JSON shape of `session`, the field registry, the + `session_id == "default"` reservation, and the + omission-not-`null` rule; +- the *Utterance Lifecycle and Pipeline Specification* + (OVOS-PIPELINE-1) — the per-utterance lifecycle, the + `Match.updated_session` channel that match-phase session + mutations travel on, and the universal end-marker + `ovos.utterance.handled`; +- the *Intent Context Specification* (OVOS-CONTEXT-1) and the + *Active Handlers and Interactive Response Specification* + (OVOS-CONVERSE-1) — consumers of this spec's projection + mandate (§2.4); they hold session-keyed state and therefore + fall under the rule that all such state lives in + session-resident fields. + +The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, +**MAY**, and **RECOMMENDED** are used as in RFC 2119. + +--- + +## 1. Scope + +This specification defines: + +- the **state-ownership model** (§2) — who holds session state, + what is permitted to mutate it, and what the projection + mandate requires of components that maintain cross-utterance + state; +- the **per-utterance round** (§3) — the flow of a single + utterance from client emission to assistant response; +- the **client-side merge rules** (§4) — how a client tracks + session updates from assistant-emitted Messages, keyed on + `session_id` alone; +- the **resumption semantics** (§5) — what makes a conversation + resumable across arbitrary elapsed time or orchestrator + restart; +- the **default-session ownership rule** (§6) — the one + exception to statelessness, codifying current ovos-core + `SessionManager.default_session` behaviour; +- **conformance** (§7) for the four roles (bus, orchestrator, + component, client). + +This specification does **not** define: + +- **the wire shape of `session`** — owned by OVOS-SESSION-1; +- **the semantics of any individual session field** — owned by + the field's claiming specification; +- **persistence format** for client-held session state — every + client chooses its own storage (in-process memory, local + database, encrypted blob, etc.); +- **session authentication or authorization** — a layer-2 + concern built on top of OVOS-MSG-1 §3.4. A client that sends + *any* `session_id` it wants is conformant; trust boundaries + are someone else's spec; +- **cross-client session sharing** — two clients holding the + same `session_id` would race on session state; coordination + is out of scope; +- **session migration between orchestrators** — handled + implicitly by the §2.2 stateless rule (any orchestrator can + serve any named session because no orchestrator holds state + for it); +- **lifecycle observability events** (`ovos.session.start` / + `.end` or similar) — deferred to a future observability + specification if needed; not required for correctness here. + +--- + +## 2. The state-ownership model + +### 2.1 The bus is stateless transport + +The message bus (OVOS-MSG-1) holds no session state. It +delivers Messages and does not interpret their `session` +carrier. A Message dropped, delayed, or duplicated by the bus +has no effect on any party's session state beyond what that +party reads off the Message. + +This is structural: OVOS-MSG-1 §3 defines the bus as a +publish/subscribe substrate with no per-session machinery. +Stateless transport is what makes the rest of this spec +possible. + +### 2.2 The orchestrator is stateless for named sessions + +For every `session_id` other than the reserved `"default"` +(SESSION-1 §3.1), the orchestrator **MUST NOT** maintain +authoritative session state across utterances. Each inbound +Message carrying such a `session_id` brings its own session +snapshot, which the orchestrator processes during the +utterance lifecycle (PIPELINE-1 §6) — mutating in place only +at transformer and pipeline boundaries (§2.6 below) — and +emits forward on its response Messages. Between utterances on +a named session, the orchestrator holds no state for that +session. + +The orchestrator **MAY** maintain a transient per-utterance +cache (the inbound session it is currently processing, the +Match it has produced, etc.); such caches are utterance-scoped +and discarded at end-of-utterance. They are **not** +cross-utterance state and **MUST NOT** be relied upon by any +component as durable. + +A consequence: any orchestrator in a deployment can serve any +inbound Message on any named session. No coordination is +required because no orchestrator holds state another would +need to consult. Cross-orchestrator load-balancing, failover, +and restart are all transparent at the session layer. + +### 2.3 The orchestrator owns `session_id == "default"` + +The reserved value `session_id == "default"` (SESSION-1 §3.1) +marks a Message as originating from the local device. The +orchestrator **MUST** maintain persistent in-process state for +this single session, keyed under `"default"` — the +authoritative default-session store. + +This is the one exception to §2.2. The local device is a +client of the orchestrator that runs in the same process tree +as the orchestrator itself; making the orchestrator hold its +state is the simplest representation of that physical +co-location. It also formalises the existing ovos-core pattern +of `SessionManager.default_session` (see APPENDIX §5). + +Behaviour rules for the default-session store are in §6. + +### 2.4 The projection mandate + +A component (a pipeline plugin, a transformer, any other +participant) that holds `session_id`-keyed state **across** +utterances **MUST** project that state into a session-resident +field it owns (claimed under SESSION-1 §2.1). The projection +flows through the pipeline plugin's `Match.updated_session` +channel (PIPELINE-1 §4.2): the component writes its state into +the field on every match it produces, the orchestrator carries +the updated session forward, and the next utterance arrives +with the state already populated for the component to read. + +The projection mandate is what makes resumption (§5) work +uniformly across all components. A component that holds +authoritative state outside the session carrier is, by +definition, not resumption-safe — its state evaporates on +orchestrator restart and is invisible to other orchestrators +in a multi-orchestrator deployment. + +Transient **in-utterance** caches are permitted (a plugin may +build helper structures for its match call, a transformer may +batch context lookups), but cross-utterance state **MUST** be +projected. The distinction is lifecycle-scoped: anything that +needs to outlive the current `ovos.utterance.handled` +(PIPELINE-1 §9) must be in `session`. + +### 2.5 Clients own their named sessions + +A **client** is any participant on the user side of the bus +boundary — the local device for the default session, a remote +peer over a layer-2 substrate for any other session. A client +that uses a named `session_id` (anything other than +`"default"`) **MUST** be its own authoritative store for that +session's state. Persistence format and lifetime are entirely +the client's choice: in-process memory for the duration of a +process, a SQLite file across restarts, an encrypted blob in +the user's cloud, anything else. + +The client is free to send any `session_id` with any +`session` value at any time (§1: authentication is out of +scope). A client emitting Messages on a session it has never +seen before, on a session belonging to a different participant +it has decided to impersonate, or on a session with a +fabricated `session_id` is all wire-conformant — the +orchestrator processes them identically. Trust is a layer-2 +concern. + +### 2.6 When session mutates in place + +In-place session mutations during an utterance lifecycle +happen only at these boundaries: + +- **transformer boundaries** — any of OVOS-TRANSFORM-1's six + hooks (audio, utterance, metadata, intent, dialog, TTS); +- **pipeline boundaries** — a pipeline plugin's `match` may + return a `Match.updated_session` per PIPELINE-1 §4.2; the + orchestrator MUST apply it as `session = match.updated_session + or session` immediately on a non-null match; +- **handler boundaries** — a dispatched handler (skill or + plugin-bundled handler per PIPELINE-1 §7.0) MAY mutate + session in-place; its emissions via `forward` / `reply` / + `response` (OVOS-MSG-1 §5) carry the mutated session + forward. + +Bus events emitted *outside* these boundaries — the +asynchronous, normal-event-handler kind that any component may +emit at any time — **MUST NOT** be expected to mutate session +state in the current utterance. The bus is asynchronous and +not part of the utterance lifecycle (§2.1). + +A bus-emitted Message that carries a mutated session **MAY** +affect subsequent utterances on that session (its updated +session is received by the client and merged per §4), but +**MUST NOT** be expected to affect the utterance during which +it was emitted. + +--- + +## 3. The per-utterance round + +A **round** is the full processing of one inbound utterance, +from client emission to assistant response. Every round follows +the same shape: + +### 3.1 Client emission + +The client emits an inbound Message — typically +`ovos.utterance.handle` (PIPELINE-1 §9) carrying an utterance +to process, but the rule applies to any inbound Message kind. +The Message carries the client's current local session in +`Message.context.session` (OVOS-MSG-1 §4, SESSION-1 §2). + +The session the client emits is its **authoritative local +state** at emission time. It includes every session-resident +field the client has accumulated from prior rounds, including +fields populated by components under the §2.4 projection +mandate that the client has merged per §4. + +### 3.2 Assistant processing + +The assistant runs the OVOS-PIPELINE-1 §6 utterance lifecycle: + +- transformer chains (audio, utterance, metadata) run before + pipeline iteration; +- pipeline plugins iterate per `session.pipeline`; +- the first plugin to return a non-null `Match` wins; +- `session = match.updated_session or session` is applied + immediately on a non-null match (PIPELINE-1 §4.2); +- post-match transformer chain (intent) runs; +- the orchestrator dispatches `:` + (PIPELINE-1 §7); +- the dispatched handler runs and may mutate session in-place + per §2.6; +- post-handler transformer chains (dialog, TTS) run on the + handler's emissions; +- the orchestrator emits the universal end-marker + `ovos.utterance.handled` (PIPELINE-1 §9) with the final + session. + +### 3.3 Assistant emissions + +During and after the lifecycle, the assistant emits zero or +more response Messages: `ovos.intent.matched`, the +handler-lifecycle trio (`ovos.intent.handler.start` / +`.complete` / `.error`), any number of handler-derived speak / +forward / reply Messages, dialog-transformer outputs, +TTS-stage Messages, and the terminal `ovos.utterance.handled`. + +Each of these Messages carries `Message.context.session` at the +emission point. By construction: + +- pipeline-plugin emissions during match carry the inbound + session (the plugin has not yet returned a `Match`); +- post-match emissions carry the post-`Match.updated_session` + snapshot; +- handler-derived emissions carry the dispatch session as + mutated by the handler; +- the end-marker carries the final session for the round. + +### 3.4 Client reception + +The client observes the response Messages and updates its local +session per §4 — keyed on its own `session_id`, the only filter +the spec recognises. After the round ends, the client's local +session reflects the post-utterance state and is used as the +inbound session for its next emission. + +--- + +## 4. Client-side merge rules + +These rules are intentionally minimal and permissive. The spec +fixes what is *available* for a client to merge from; the +client decides what to *use*. + +### 4.1 Session_id is the only key + +A client **MAY** update its local session tracking from any +Message it observes carrying a `session_id` matching its own. +`session_id` uniquely identifies the channel; it is the only +key that matters for client-side session merging. No other +matching predicate is normative. + +A client that does not update its session from observed +Messages is also conformant (it discards continuity, but the +wire contract does not require any client to track state). This +spec only specifies what is *available* on the wire; clients +are free to ignore it. + +### 4.2 Every assistant-emitted Message carries an updated session + +By the rules of §3.2 and PIPELINE-1 §4.2 / §5, every +assistant-emitted Message carries a valid session at its +emission point. A client that adopts any one such Message's +session has, by definition, a snapshot consistent with the +assistant's view at that point in the round. + +Adopting the **latest received** session is the simplest +client policy and is conformant. More elaborate policies +(field-by-field merge across multiple observed Messages, +selecting by emitter identity, etc.) are also conformant; the +spec does not prescribe. + +### 4.3 `ovos.utterance.handled` is the canonical convergence point + +When a client wants a single canonical "this round is over" +snapshot, the PIPELINE-1 §9 universal end-marker +`ovos.utterance.handled` is the recommended adoption point: it +is emitted exactly once per utterance on every terminal path, +and the session it carries is the assistant's final state for +the round. + +A client may: + +- adopt only the end-marker's session (simplest model; + intermediate updates ignored until the round ends); +- adopt incrementally per §4.1 throughout the round; +- combine both — incremental tracking with end-marker + canonical override. + +All three are conformant. The choice is observability vs. +latency vs. complexity. + +--- + +## 5. Resumption semantics + +### 5.1 Resumption is implicit + +A client **MAY** re-emit a previously-used `session_id` with +its locally-held session state at any time. There is no +"session resume" handshake on the wire: the inbound Message's +session IS the resume. The orchestrator processes it via the +stateless rule of §2.2 — it neither knows nor cares whether the +session has been seen before, was last seen seconds or years +ago, or was previously served by a different orchestrator. + +Resumption works because the orchestrator carries no +cross-utterance state for the session (§2.2), and the client +carries the full state on the inbound Message (§2.5). + +### 5.2 What is resumption-safe + +Resumption-safe state is the union of: + +- every field claimed under SESSION-1 §3's registry (language + signals, `pipeline`, `intent_context`, `active_handlers`, + `response_mode`, the transformer chains, the blacklists, + `site_id`, and any future-claimed field); +- the projected state of every component bound by §2.4 — by + construction, since the projection mandate requires + cross-utterance state to live in session-resident fields. + +Resumption is **field-by-field**: a client that drops or +replaces individual fields gets the corresponding fall-back +behaviour at the consumer (SESSION-1 §2.5: omitted fields +resolve to deployment defaults). A client that resumes a +session minus `intent_context` enters with a fresh declarative +state but retains the rest. + +### 5.3 What is not resumption-safe + +Anything not session-resident is not resumption-safe. This is +exactly the space §2.4 forbids components from holding: +authoritative cross-utterance state outside session is a +conformance violation. A conformant deployment has nothing +non-resumption-safe by construction. + +A transient in-utterance cache (§2.4) is, by definition, +gone at end-of-utterance and therefore trivially absent from +any future round; resumption neither preserves nor needs it. + +--- + +## 6. The default-session ownership rule + +### 6.1 Persistent orchestrator-held state + +The orchestrator **MUST** maintain persistent in-process state +for `session_id == "default"`, keyed under `"default"`. This is +the **default-session store**. + +The default-session store is updated continuously during +orchestrator operation: + +- every inbound Message bearing `session_id == "default"` (or + equivalent — omitted session, empty session, explicit + default, all per SESSION-1 §3.1) is merged into the store as + part of the utterance lifecycle; +- every outbound Message on the default session derives from + the store, so handlers and components see the current + default state on the dispatch they receive; +- session mutations during the lifecycle (transformer + boundaries §2.6, `Match.updated_session` per PIPELINE-1 + §4.2, in-handler mutations) propagate into the store + through the standard derivation chain. + +The merge semantics for inbound default-session Messages are +"last-write-wins per field": an inbound field value replaces +the stored value for that field. Omitted inbound fields leave +the stored field unchanged. This is symmetric with §4's +permissive client-side merge. + +### 6.2 Restart semantics + +The default-session store is **process-local**. An orchestrator +restart discards it; the default session reverts to deployment +defaults (the empty session, with every field falling back per +SESSION-1 §2.5). Components keyed on the default session lose +their state. + +This is acceptable for the default session by design: the +default session represents the local device, which is typically +co-located with the orchestrator process. A restart of the +orchestrator is a restart of the device's voice stack; +discarding the default-session state matches user expectation +of a "fresh start" after restart. + +Deployments that want default-session persistence across +restarts MAY implement orchestrator-side persistence (writing +the store to disk on shutdown, restoring on start). This is +deployment policy; the spec does not require it. + +### 6.3 Component reliance on default-session continuity + +Components consuming the default session **MAY** rely on the +orchestrator's continuity within a single deployment lifetime. +A pipeline plugin that holds session-keyed state and projects +to a session-resident field per §2.4 finds that, for the +default session, the projected field is reliably preserved +across utterances (the orchestrator's store holds it). For a +named session, the same projection is preserved only as long +as the client holds the session locally. + +CONVERSE-1's `session.response_mode` wait window, CONTEXT-1's +`session.intent_context` entries, and any future +session-projected state are therefore **reliable on the local +device** (default session) and **best-effort on remote peers** +(named sessions held by the client). For named sessions the +client's local persistence policy determines whether wait +windows actually survive a several-day gap. + +### 6.4 Default-session sync to clients + +The orchestrator **MAY** emit the default-session state as a +diagnostic on a deployer-defined topic (the existing +`ovos.session.sync` / `ovos.session.update_default` pattern in +current ovos-core is one such mechanism). This is informative; +no spec-level consumer is defined. + +--- + +## 7. Conformance + +### 7.1 Bus + +The message bus **MUST** be stateless with respect to session. +It **MUST NOT** interpret, mutate, persist, or special-case +`Message.context.session` for any reason. Delivery is the bus's +contract; session is opaque to it. + +### 7.2 Orchestrator + +An orchestrator that claims conformance to this specification +**MUST**: + +- treat every named session (`session_id != "default"`) as + stateless per §2.2 — no cross-utterance state held outside + what the inbound Message brings; +- hold the default session as persistent in-process state per + §6, with the merge / derive / restart semantics of §6.1 / + §6.2; +- apply in-place session mutations only at the boundaries of + §2.6 (transformer, pipeline-match, handler); +- propagate session forward unchanged on every Message + derivation per OVOS-MSG-1 §5 and SESSION-1 §4, except where + the §2.6 boundaries dictate mutation; +- emit the universal end-marker `ovos.utterance.handled` + carrying the final round session (PIPELINE-1 §9), as the + client-side convergence point of §4.4. + +An orchestrator **MUST NOT** require any client to declare +session-start / session-end / session-id-allocation events +before processing an inbound Message. Clients send what they +send; the orchestrator processes what arrives. + +### 7.3 Component + +A component (pipeline plugin, transformer, dispatched handler, +introspection observer) **MUST**: + +- project any `session_id`-keyed state it holds across + utterances into a session-resident field it claims under + SESSION-1 §2.1 (the §2.4 mandate); +- write that state into the field via the appropriate + in-utterance pathway — `Match.updated_session` for pipeline + plugins per PIPELINE-1 §4.2, direct mutation for + transformers and handlers per §2.6; +- treat transient in-utterance caches as utterance-scoped, + discarding them at end-of-utterance; +- read its state from `session` on every inbound Message, + rather than from a cross-utterance internal store. + +A component **MUST NOT** rely on bus events (the asynchronous +kind that fire outside the utterance lifecycle) to mutate +session state in the current utterance (§2.6). It MAY emit such +events to communicate with other components; their effect on +session, if any, lands on subsequent utterances. + +### 7.4 Client + +A **client** (any participant on the user side of the bus +boundary that uses a named `session_id`) **MUST**: + +- hold its own authoritative session state for the + `session_id` values it uses, per §2.5; +- include that state in `Message.context.session` on every + inbound Message it emits. + +A client **MAY**: + +- update its local session from any Message it observes per §4; +- choose any persistence format, lifetime, and lifecycle for + its local session; +- re-emit a previously-used `session_id` at any time per §5; +- send any `session_id` and any `session` value, including + fabricated ones (trust is a layer-2 concern, §2.5). + +A client **MUST NOT**: + +- expect the orchestrator to remember any session state for it + between rounds — every round MUST be self-sufficient via the + inbound session. + +### 7.5 Default-session client + +The local device, which uses `session_id == "default"`, is a +special-case client. Because the orchestrator owns the default +session per §6, the local device **MAY** omit `Message.context.session` +or emit `session: {}` (SESSION-1 §3.1's equivalent forms) and +rely on the orchestrator's stored state. This is the only place +this spec recognizes a client that does not carry its own +authoritative state — and only because the orchestrator's +default-session store *is* that state for the local device. + +--- + +## 8. Non-goals + +This specification deliberately does not: + +- define a **session-store protocol** for client-side + persistence — every client picks its own; +- define **session authentication or authorization** — layer-2 + on top of MSG-1 §3.4; +- define **cross-client session sharing or coordination** — + two clients holding the same `session_id` race; out of scope; +- define **session migration between orchestrators** — handled + implicitly by §2.2 statelessness; any orchestrator can serve + any named session because none holds state for it; +- define **lifecycle observability events** (`ovos.session.start` + / `.end`, etc.) — deferred to a future spec if needed; not + required for correctness; +- define **per-field encryption or selective field exposure** — + the session is one JSON object and is propagated as a whole; +- define **default-session persistence across orchestrator + restart** — §6.2 makes restart-loss explicit; deployer + policy if desired; +- replace **OVOS-SESSION-1** — that spec owns the wire shape + and the field registry; this spec owns the lifecycle that + rides on top. From 4b4131ec41f0d0cfb8ddb1bc2d752fc36415521d Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Tue, 26 May 2026 02:38:51 +0100 Subject: [PATCH 2/6] =?UTF-8?q?SESSION-2=20=C2=A72.4,=20=C2=A75,=20=C2=A77?= =?UTF-8?q?.3:=20relax=20projection=20mandate=20to=20SHOULD-with-MAY-inter?= =?UTF-8?q?nal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per design feedback: the strict MUST-project rule was too tight. Real-world plugins (LLM transcripts, media playback state, learned personalization, anything tied to external resources) cannot practically project into session — the state is too large, too coupled to external resources, or too sensitive. Relax §2.4 to SHOULD-project-when-practical with an explicit MAY-internal alternative. A component that takes the internal path: - owns its state lifecycle in full (persistence, expiry, eviction, multi-orchestrator coordination if applicable) - offers best-effort resumption with no normative guarantee — a user asking "unpause the music" months later may or may not get a useful reaction, plugin's choice - MUST NOT expect other components to know its state exists Updates: - §2.4 rewritten with concrete examples (LLM history, media playback state, personalization, external-resource ties) and explicit responsibility transfer - §5.2 'projected state of every component bound by §2.4' → 'projected state of every component that elected the SHOULD-project pathway' - §5.3 rewritten: plugin-internal state is permitted with best-effort resumption; spec defines no protocol for it; resumption parity across plugins is not guaranteed - §7.3 Component conformance split: MUST list keeps in-utterance cache discipline; SHOULD list governs projection; MAY allows internal state with the responsibility/resumption caveats - §1 scope and 'builds on' list resoften language - §3.1 cross-ref likewise Both pathways are conformant; the choice is per plugin. The CONVERSE-1 converse plugin still chooses projection (its state is small and naturally session-coupled); the spec explicitly calls this out as one example of the SHOULD path. Co-Authored-By: Claude Opus 4.7 (1M context) --- session-lifecycle.md | 165 +++++++++++++++++++++++++++++-------------- 1 file changed, 113 insertions(+), 52 deletions(-) diff --git a/session-lifecycle.md b/session-lifecycle.md index f9e9a48..d6c6b67 100644 --- a/session-lifecycle.md +++ b/session-lifecycle.md @@ -42,10 +42,10 @@ It builds on four companion specifications: `ovos.utterance.handled`; - the *Intent Context Specification* (OVOS-CONTEXT-1) and the *Active Handlers and Interactive Response Specification* - (OVOS-CONVERSE-1) — consumers of this spec's projection - mandate (§2.4); they hold session-keyed state and therefore - fall under the rule that all such state lives in - session-resident fields. + (OVOS-CONVERSE-1) — both elect the §2.4 SHOULD-project + pathway for their cross-utterance state (intent-context + entries, active-handler list, response-mode wait window + respectively), making it resumption-safe by construction. The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, **MAY**, and **RECOMMENDED** are used as in RFC 2119. @@ -57,9 +57,10 @@ The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, This specification defines: - the **state-ownership model** (§2) — who holds session state, - what is permitted to mutate it, and what the projection - mandate requires of components that maintain cross-utterance - state; + what is permitted to mutate it, and when components SHOULD + project their cross-utterance state into session-resident + fields vs hold it internally with best-effort resumption + semantics; - the **per-utterance round** (§3) — the flow of a single utterance from client emission to assistant response; - the **client-side merge rules** (§4) — how a client tracks @@ -157,31 +158,66 @@ of `SessionManager.default_session` (see APPENDIX §5). Behaviour rules for the default-session store are in §6. -### 2.4 The projection mandate +### 2.4 Project state into session when practical; plugin-internal state is permitted A component (a pipeline plugin, a transformer, any other participant) that holds `session_id`-keyed state **across** -utterances **MUST** project that state into a session-resident -field it owns (claimed under SESSION-1 §2.1). The projection -flows through the pipeline plugin's `Match.updated_session` -channel (PIPELINE-1 §4.2): the component writes its state into -the field on every match it produces, the orchestrator carries -the updated session forward, and the next utterance arrives -with the state already populated for the component to read. - -The projection mandate is what makes resumption (§5) work -uniformly across all components. A component that holds -authoritative state outside the session carrier is, by -definition, not resumption-safe — its state evaporates on -orchestrator restart and is invisible to other orchestrators -in a multi-orchestrator deployment. - -Transient **in-utterance** caches are permitted (a plugin may -build helper structures for its match call, a transformer may -batch context lookups), but cross-utterance state **MUST** be -projected. The distinction is lifecycle-scoped: anything that -needs to outlive the current `ovos.utterance.handled` -(PIPELINE-1 §9) must be in `session`. +utterances **SHOULD** project that state into a session-resident +field it owns (claimed under SESSION-1 §2.1) when projection is +practical. Projection flows through the pipeline plugin's +`Match.updated_session` channel (PIPELINE-1 §4.2) or through +in-place mutation at transformer / handler boundaries (§2.6). +Projected state is **resumption-safe by construction** — it +travels with the session, survives orchestrator restart, and +moves transparently across multi-orchestrator deployments. + +A component **MAY** instead hold authoritative cross-utterance +state internally when projection is impractical. Realistic +examples: + +- a **language-model plugin** holding a multi-turn conversation + transcript that is too large to ride on every session-carrying + Message; +- a **media pipeline plugin** holding playback positions, queued + playlists, or user-favourite catalogues backed by external + service APIs; +- a **personalization component** holding learned preferences, + trained classifiers, or any state tied to local model + artefacts; +- any plugin whose state is intrinsically tied to external + resources (sockets, processes, files, accounts) that cannot + be serialised into a JSON session field meaningfully. + +A component that takes this path: + +- **owns its state lifecycle in full** — persistence (or not), + expiry, eviction, multi-orchestrator coordination if the + deployment has multiple orchestrators, and any privacy or + access-control concerns the state raises; +- offers **best-effort resumption with no normative guarantee**. + A user resuming "unpause the music" months later may or may + not get a useful reaction — the plugin may have evicted the + playback state, the underlying media process may no longer + exist, the user's playlist may have changed, or the plugin + may have persisted the state and handle the resume cleanly. + The spec does not bind the outcome of any plugin-internal + resumption attempt; +- MUST NOT expect other components or clients to know its + state exists or to compensate for its absence. + +The CONVERSE-1 converse plugin (§5 there) is one example of a +plugin that chooses to project all its cross-utterance state — +the response-mode wait window is small, simple, and naturally +session-coupled, so the SHOULD-project path is the obvious fit. +LLM, media, and personalization plugins typically pick the +plugin-internal path. Both are conformant; the choice is per +plugin. + +Transient **in-utterance** caches (helper structures built +during a single `match` call, batched lookups within a +transformer chain) are always permitted regardless of projection +choice — they are utterance-scoped, discarded at +end-of-utterance, never cross-utterance state. ### 2.5 Clients own their named sessions @@ -252,8 +288,8 @@ The Message carries the client's current local session in The session the client emits is its **authoritative local state** at emission time. It includes every session-resident field the client has accumulated from prior rounds, including -fields populated by components under the §2.4 projection -mandate that the client has merged per §4. +fields populated by components that elected the §2.4 +SHOULD-project pathway and that the client has merged per §4. ### 3.2 Assistant processing @@ -386,9 +422,9 @@ Resumption-safe state is the union of: signals, `pipeline`, `intent_context`, `active_handlers`, `response_mode`, the transformer chains, the blacklists, `site_id`, and any future-claimed field); -- the projected state of every component bound by §2.4 — by - construction, since the projection mandate requires - cross-utterance state to live in session-resident fields. +- the projected state of every component that elected the + SHOULD-project pathway of §2.4 — resumption-safe by + construction since it lives in session-resident fields. Resumption is **field-by-field**: a client that drops or replaces individual fields gets the corresponding fall-back @@ -397,17 +433,35 @@ resolve to deployment defaults). A client that resumes a session minus `intent_context` enters with a fresh declarative state but retains the rest. -### 5.3 What is not resumption-safe - -Anything not session-resident is not resumption-safe. This is -exactly the space §2.4 forbids components from holding: -authoritative cross-utterance state outside session is a -conformance violation. A conformant deployment has nothing -non-resumption-safe by construction. - -A transient in-utterance cache (§2.4) is, by definition, -gone at end-of-utterance and therefore trivially absent from -any future round; resumption neither preserves nor needs it. +### 5.3 Plugin-internal state — best-effort resumption + +State held internally by a component per §2.4's MAY-internal +pathway is governed by the holding component's own design. The +spec defines no protocol for plugin-internal state; the +plugin chooses what to persist, what to evict, and what +"resume" means for its own state. A client cannot expect +parity across components: + +- a chat-history-holding LLM plugin may resume a months-old + conversation seamlessly because it persisted the transcript; +- the same client trying to resume "unpause the music" may find + the media plugin's playback state long gone, because the + plugin evicted it after a deployer-configured TTL or because + the underlying media process restarted; +- a personalization component may retain learned preferences + forever, may drop them on restart, or may rebuild them on + demand — entirely up to the plugin. + +This is not a defect of the spec; it is the cost of allowing +plugins to hold state too large or too coupled to external +resources to project into session. Plugins that want resumption +parity with the projection pathway can adopt projection (§2.4); +plugins that prefer internal state accept best-effort +resumption. + +A transient in-utterance cache (§2.4) is, by definition, gone +at end-of-utterance and therefore trivially absent from any +future round; resumption neither preserves nor needs it. --- @@ -528,18 +582,25 @@ send; the orchestrator processes what arrives. A component (pipeline plugin, transformer, dispatched handler, introspection observer) **MUST**: -- project any `session_id`-keyed state it holds across - utterances into a session-resident field it claims under - SESSION-1 §2.1 (the §2.4 mandate); -- write that state into the field via the appropriate +- treat transient in-utterance caches as utterance-scoped, + discarding them at end-of-utterance. + +A component that holds `session_id`-keyed state across +utterances **SHOULD**: + +- project that state into a session-resident field it claims + under SESSION-1 §2.1 (per §2.4), via the appropriate in-utterance pathway — `Match.updated_session` for pipeline plugins per PIPELINE-1 §4.2, direct mutation for transformers and handlers per §2.6; -- treat transient in-utterance caches as utterance-scoped, - discarding them at end-of-utterance; -- read its state from `session` on every inbound Message, +- on every inbound Message, read its state from `session` rather than from a cross-utterance internal store. +A component **MAY** instead hold cross-utterance state +internally per §2.4 when projection is impractical, in which +case it MUST take full responsibility for state lifecycle and +accept best-effort resumption (§5.3). + A component **MUST NOT** rely on bus events (the asynchronous kind that fire outside the utterance lifecycle) to mutate session state in the current utterance (§2.6). It MAY emit such From d9c870edb803a71ad951c14b42a873ad30cdd099 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Tue, 26 May 2026 16:42:02 +0100 Subject: [PATCH 3/6] SESSION-2: audit fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §2.3: align "default" framing with SESSION-1 §3.1 (interact with device-local session, not "originates from device") - §3.2: clarify PIPELINE-1 is authoritative for lifecycle detail - §5.2: SESSION-1 §2.5 → §2.1 (correct cross-ref for omission rule) - §6.1: restate merge rule as omission-preserve first, present-replaces second; drop "last-write-wins" framing that clashed with SESSION-1 §2.1 - §6.4: drop ovos.session.sync / ovos.session.update_default topic names; deployer-defined topic only, no normative name - §7.2: §4.4 → §4.3 (end-marker convergence point cross-ref) - §8: collapse to one-paragraph; §1 is the canonical non-goals list Co-Authored-By: Claude Sonnet 4.6 --- session-lifecycle.md | 64 ++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/session-lifecycle.md b/session-lifecycle.md index d6c6b67..c3aa539 100644 --- a/session-lifecycle.md +++ b/session-lifecycle.md @@ -144,10 +144,10 @@ and restart are all transparent at the session layer. ### 2.3 The orchestrator owns `session_id == "default"` The reserved value `session_id == "default"` (SESSION-1 §3.1) -marks a Message as originating from the local device. The -orchestrator **MUST** maintain persistent in-process state for -this single session, keyed under `"default"` — the -authoritative default-session store. +means "interact with the device-local session." The orchestrator +**MUST** maintain persistent in-process state for this single +session, keyed under `"default"` — the authoritative +default-session store. This is the one exception to §2.2. The local device is a client of the orchestrator that runs in the same process tree @@ -293,7 +293,9 @@ SHOULD-project pathway and that the client has merged per §4. ### 3.2 Assistant processing -The assistant runs the OVOS-PIPELINE-1 §6 utterance lifecycle: +The assistant runs the OVOS-PIPELINE-1 §6 utterance lifecycle; +PIPELINE-1 is the authoritative source for the lifecycle detail. +The following is a summary for SESSION-2 readers: - transformer chains (audio, utterance, metadata) run before pipeline iteration; @@ -428,7 +430,7 @@ Resumption-safe state is the union of: Resumption is **field-by-field**: a client that drops or replaces individual fields gets the corresponding fall-back -behaviour at the consumer (SESSION-1 §2.5: omitted fields +behaviour at the consumer (SESSION-1 §2.1: omitted fields resolve to deployment defaults). A client that resumes a session minus `intent_context` enters with a fresh declarative state but retains the rest. @@ -488,11 +490,14 @@ orchestrator operation: §4.2, in-handler mutations) propagate into the store through the standard derivation chain. -The merge semantics for inbound default-session Messages are -"last-write-wins per field": an inbound field value replaces -the stored value for that field. Omitted inbound fields leave -the stored field unchanged. This is symmetric with §4's -permissive client-side merge. +The merge semantics for inbound default-session Messages follow +SESSION-1 §2.1's omission rule: **omitted inbound fields leave +the stored field unchanged** (the stored value is the +orchestrator's last authoritative value for that field); a +**present inbound field replaces the stored value** for that +field. This is the natural complement to the stateless-named- +session rule: the default-session store fills the role the +client plays for named sessions. ### 6.2 Restart semantics @@ -536,10 +541,10 @@ windows actually survive a several-day gap. ### 6.4 Default-session sync to clients The orchestrator **MAY** emit the default-session state as a -diagnostic on a deployer-defined topic (the existing -`ovos.session.sync` / `ovos.session.update_default` pattern in -current ovos-core is one such mechanism). This is informative; -no spec-level consumer is defined. +diagnostic on a deployer-defined topic, so that interested +observers can track default-session evolution without processing +every response Message. No normative topic name or consumer is +defined here; this is deployment policy. --- @@ -570,7 +575,7 @@ An orchestrator that claims conformance to this specification the §2.6 boundaries dictate mutation; - emit the universal end-marker `ovos.utterance.handled` carrying the final round session (PIPELINE-1 §9), as the - client-side convergence point of §4.4. + client-side convergence point of §4.3. An orchestrator **MUST NOT** require any client to declare session-start / session-end / session-id-allocation events @@ -647,25 +652,8 @@ default-session store *is* that state for the local device. ## 8. Non-goals -This specification deliberately does not: - -- define a **session-store protocol** for client-side - persistence — every client picks its own; -- define **session authentication or authorization** — layer-2 - on top of MSG-1 §3.4; -- define **cross-client session sharing or coordination** — - two clients holding the same `session_id` race; out of scope; -- define **session migration between orchestrators** — handled - implicitly by §2.2 statelessness; any orchestrator can serve - any named session because none holds state for it; -- define **lifecycle observability events** (`ovos.session.start` - / `.end`, etc.) — deferred to a future spec if needed; not - required for correctness; -- define **per-field encryption or selective field exposure** — - the session is one JSON object and is propagated as a whole; -- define **default-session persistence across orchestrator - restart** — §6.2 makes restart-loss explicit; deployer - policy if desired; -- replace **OVOS-SESSION-1** — that spec owns the wire shape - and the field registry; this spec owns the lifecycle that - rides on top. +See §1 for the full list of non-goals. This section adds one +clarification: **default-session persistence across orchestrator +restart** is not defined here. §6.2 makes restart-loss +explicit and intentional; persistence is deployer policy if +desired. From 46dcf9caa18a47932cefd043d5a176ade9bb135f Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Tue, 26 May 2026 16:50:48 +0100 Subject: [PATCH 4/6] =?UTF-8?q?SESSION-2:=20simplify=20=E2=80=94=20drop=20?= =?UTF-8?q?=C2=A73=20per-utterance=20round,=20trim=20verbose=20prose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop §3 per-utterance round entirely (was narrative glue restating PIPELINE-1 and §2; replaced with PIPELINE-1 §6 cross-ref at §2.2) - §4.1 second paragraph removed ("also conformant to ignore the spec") - §7.3 trivial MUST dropped ("treat caches as utterance-scoped") - §2.5 second paragraph collapsed to one sentence (trust = layer-2) - §5.2 field list replaced with "every field in SESSION-1 §3 registry" - §6.3 cut by half; key point preserved - §7.4 MAY list collapsed to one sentence pointing at §3 and §4 - §8 non-goals already collapsed; fix §5.2 cross-ref in it - Renumber: old §5→§4, §6→§5, §7→§6, §8→§7; all internal refs updated Co-Authored-By: Claude Sonnet 4.6 --- session-lifecycle.md | 264 +++++++++++-------------------------------- 1 file changed, 69 insertions(+), 195 deletions(-) diff --git a/session-lifecycle.md b/session-lifecycle.md index c3aa539..c0e3960 100644 --- a/session-lifecycle.md +++ b/session-lifecycle.md @@ -61,18 +61,16 @@ This specification defines: project their cross-utterance state into session-resident fields vs hold it internally with best-effort resumption semantics; -- the **per-utterance round** (§3) — the flow of a single - utterance from client emission to assistant response; -- the **client-side merge rules** (§4) — how a client tracks +- the **client-side merge rules** (§3) — how a client tracks session updates from assistant-emitted Messages, keyed on `session_id` alone; -- the **resumption semantics** (§5) — what makes a conversation +- the **resumption semantics** (§4) — what makes a conversation resumable across arbitrary elapsed time or orchestrator restart; -- the **default-session ownership rule** (§6) — the one +- the **default-session ownership rule** (§5) — the one exception to statelessness, codifying current ovos-core `SessionManager.default_session` behaviour; -- **conformance** (§7) for the four roles (bus, orchestrator, +- **conformance** (§6) for the four roles (bus, orchestrator, component, client). This specification does **not** define: @@ -156,7 +154,7 @@ state is the simplest representation of that physical co-location. It also formalises the existing ovos-core pattern of `SessionManager.default_session` (see APPENDIX §5). -Behaviour rules for the default-session store are in §6. +Behaviour rules for the default-session store are in §5. ### 2.4 Project state into session when practical; plugin-internal state is permitted @@ -231,14 +229,9 @@ the client's choice: in-process memory for the duration of a process, a SQLite file across restarts, an encrypted blob in the user's cloud, anything else. -The client is free to send any `session_id` with any -`session` value at any time (§1: authentication is out of -scope). A client emitting Messages on a session it has never -seen before, on a session belonging to a different participant -it has decided to impersonate, or on a session with a -fabricated `session_id` is all wire-conformant — the -orchestrator processes them identically. Trust is a layer-2 -concern. +Trust and authorization are layer-2 concerns (§1); this spec +places no constraint on what `session_id` or `session` value a +client sends. ### 2.6 When session mutates in place @@ -265,92 +258,19 @@ not part of the utterance lifecycle (§2.1). A bus-emitted Message that carries a mutated session **MAY** affect subsequent utterances on that session (its updated -session is received by the client and merged per §4), but +session is received by the client and merged per §3), but **MUST NOT** be expected to affect the utterance during which it was emitted. --- -## 3. The per-utterance round - -A **round** is the full processing of one inbound utterance, -from client emission to assistant response. Every round follows -the same shape: - -### 3.1 Client emission - -The client emits an inbound Message — typically -`ovos.utterance.handle` (PIPELINE-1 §9) carrying an utterance -to process, but the rule applies to any inbound Message kind. -The Message carries the client's current local session in -`Message.context.session` (OVOS-MSG-1 §4, SESSION-1 §2). - -The session the client emits is its **authoritative local -state** at emission time. It includes every session-resident -field the client has accumulated from prior rounds, including -fields populated by components that elected the §2.4 -SHOULD-project pathway and that the client has merged per §4. - -### 3.2 Assistant processing - -The assistant runs the OVOS-PIPELINE-1 §6 utterance lifecycle; -PIPELINE-1 is the authoritative source for the lifecycle detail. -The following is a summary for SESSION-2 readers: - -- transformer chains (audio, utterance, metadata) run before - pipeline iteration; -- pipeline plugins iterate per `session.pipeline`; -- the first plugin to return a non-null `Match` wins; -- `session = match.updated_session or session` is applied - immediately on a non-null match (PIPELINE-1 §4.2); -- post-match transformer chain (intent) runs; -- the orchestrator dispatches `:` - (PIPELINE-1 §7); -- the dispatched handler runs and may mutate session in-place - per §2.6; -- post-handler transformer chains (dialog, TTS) run on the - handler's emissions; -- the orchestrator emits the universal end-marker - `ovos.utterance.handled` (PIPELINE-1 §9) with the final - session. - -### 3.3 Assistant emissions - -During and after the lifecycle, the assistant emits zero or -more response Messages: `ovos.intent.matched`, the -handler-lifecycle trio (`ovos.intent.handler.start` / -`.complete` / `.error`), any number of handler-derived speak / -forward / reply Messages, dialog-transformer outputs, -TTS-stage Messages, and the terminal `ovos.utterance.handled`. - -Each of these Messages carries `Message.context.session` at the -emission point. By construction: - -- pipeline-plugin emissions during match carry the inbound - session (the plugin has not yet returned a `Match`); -- post-match emissions carry the post-`Match.updated_session` - snapshot; -- handler-derived emissions carry the dispatch session as - mutated by the handler; -- the end-marker carries the final session for the round. - -### 3.4 Client reception - -The client observes the response Messages and updates its local -session per §4 — keyed on its own `session_id`, the only filter -the spec recognises. After the round ends, the client's local -session reflects the post-utterance state and is used as the -inbound session for its next emission. - ---- - -## 4. Client-side merge rules +## 3. Client-side merge rules These rules are intentionally minimal and permissive. The spec fixes what is *available* for a client to merge from; the client decides what to *use*. -### 4.1 Session_id is the only key +### 3.1 Session_id is the only key A client **MAY** update its local session tracking from any Message it observes carrying a `session_id` matching its own. @@ -358,51 +278,32 @@ Message it observes carrying a `session_id` matching its own. key that matters for client-side session merging. No other matching predicate is normative. -A client that does not update its session from observed -Messages is also conformant (it discards continuity, but the -wire contract does not require any client to track state). This -spec only specifies what is *available* on the wire; clients -are free to ignore it. - -### 4.2 Every assistant-emitted Message carries an updated session - -By the rules of §3.2 and PIPELINE-1 §4.2 / §5, every -assistant-emitted Message carries a valid session at its -emission point. A client that adopts any one such Message's -session has, by definition, a snapshot consistent with the -assistant's view at that point in the round. - -Adopting the **latest received** session is the simplest -client policy and is conformant. More elaborate policies -(field-by-field merge across multiple observed Messages, -selecting by emitter identity, etc.) are also conformant; the -spec does not prescribe. +### 3.2 Every assistant-emitted Message carries an updated session -### 4.3 `ovos.utterance.handled` is the canonical convergence point +Per PIPELINE-1 §4.2 and §5, every assistant-emitted Message +carries a valid session at its emission point. A client that +adopts any one such Message's session has a snapshot consistent +with the assistant's view at that point in the round. -When a client wants a single canonical "this round is over" -snapshot, the PIPELINE-1 §9 universal end-marker -`ovos.utterance.handled` is the recommended adoption point: it -is emitted exactly once per utterance on every terminal path, -and the session it carries is the assistant's final state for -the round. +Adopting the **latest received** session is the simplest client +policy. More elaborate policies (field-by-field merge, selecting +by emitter identity) are also conformant; the spec does not +prescribe. -A client may: +### 3.3 `ovos.utterance.handled` is the canonical convergence point -- adopt only the end-marker's session (simplest model; - intermediate updates ignored until the round ends); -- adopt incrementally per §4.1 throughout the round; -- combine both — incremental tracking with end-marker - canonical override. - -All three are conformant. The choice is observability vs. -latency vs. complexity. +When a client wants a single canonical "round is over" snapshot, +the PIPELINE-1 §9 universal end-marker `ovos.utterance.handled` +is the recommended adoption point: emitted exactly once per +utterance on every terminal path, carrying the assistant's final +session for the round. A client may also adopt incrementally per +§3.1, or combine both; all are conformant. --- -## 5. Resumption semantics +## 4. Resumption semantics -### 5.1 Resumption is implicit +### 4.1 Resumption is implicit A client **MAY** re-emit a previously-used `session_id` with its locally-held session state at any time. There is no @@ -416,26 +317,19 @@ Resumption works because the orchestrator carries no cross-utterance state for the session (§2.2), and the client carries the full state on the inbound Message (§2.5). -### 5.2 What is resumption-safe - -Resumption-safe state is the union of: +### 4.2 What is resumption-safe -- every field claimed under SESSION-1 §3's registry (language - signals, `pipeline`, `intent_context`, `active_handlers`, - `response_mode`, the transformer chains, the blacklists, - `site_id`, and any future-claimed field); -- the projected state of every component that elected the - SHOULD-project pathway of §2.4 — resumption-safe by - construction since it lives in session-resident fields. +Resumption-safe state is every field in the SESSION-1 §3 +registry, plus the projected state of any component that +elected §2.4's SHOULD-project pathway — resumption-safe by +construction since it lives in session-resident fields. -Resumption is **field-by-field**: a client that drops or -replaces individual fields gets the corresponding fall-back -behaviour at the consumer (SESSION-1 §2.1: omitted fields -resolve to deployment defaults). A client that resumes a -session minus `intent_context` enters with a fresh declarative -state but retains the rest. +Resumption is **field-by-field**: omitted fields resolve to +deployment defaults at the consumer (SESSION-1 §2.1). A client +that resumes without `intent_context` enters with a fresh +context but retains every other field. -### 5.3 Plugin-internal state — best-effort resumption +### 4.3 Plugin-internal state — best-effort resumption State held internally by a component per §2.4's MAY-internal pathway is governed by the holding component's own design. The @@ -467,9 +361,9 @@ future round; resumption neither preserves nor needs it. --- -## 6. The default-session ownership rule +## 5. The default-session ownership rule -### 6.1 Persistent orchestrator-held state +### 5.1 Persistent orchestrator-held state The orchestrator **MUST** maintain persistent in-process state for `session_id == "default"`, keyed under `"default"`. This is @@ -487,7 +381,7 @@ orchestrator operation: default state on the dispatch they receive; - session mutations during the lifecycle (transformer boundaries §2.6, `Match.updated_session` per PIPELINE-1 - §4.2, in-handler mutations) propagate into the store + §5.2, in-handler mutations) propagate into the store through the standard derivation chain. The merge semantics for inbound default-session Messages follow @@ -499,12 +393,12 @@ field. This is the natural complement to the stateless-named- session rule: the default-session store fills the role the client plays for named sessions. -### 6.2 Restart semantics +### 5.2 Restart semantics The default-session store is **process-local**. An orchestrator restart discards it; the default session reverts to deployment defaults (the empty session, with every field falling back per -SESSION-1 §2.5). Components keyed on the default session lose +SESSION-1 §2.1). Components keyed on the default session lose their state. This is acceptable for the default session by design: the @@ -519,26 +413,17 @@ restarts MAY implement orchestrator-side persistence (writing the store to disk on shutdown, restoring on start). This is deployment policy; the spec does not require it. -### 6.3 Component reliance on default-session continuity - -Components consuming the default session **MAY** rely on the -orchestrator's continuity within a single deployment lifetime. -A pipeline plugin that holds session-keyed state and projects -to a session-resident field per §2.4 finds that, for the -default session, the projected field is reliably preserved -across utterances (the orchestrator's store holds it). For a -named session, the same projection is preserved only as long -as the client holds the session locally. +### 5.3 Component reliance on default-session continuity -CONVERSE-1's `session.response_mode` wait window, CONTEXT-1's -`session.intent_context` entries, and any future -session-projected state are therefore **reliable on the local -device** (default session) and **best-effort on remote peers** -(named sessions held by the client). For named sessions the -client's local persistence policy determines whether wait -windows actually survive a several-day gap. +Components **MAY** rely on default-session continuity within a +single deployment lifetime: a §2.4-projected field (e.g. +`session.response_mode`, `session.intent_context`) is reliably +preserved across utterances because the orchestrator's store +holds it. For named sessions the same field is preserved only +as long as the client holds it locally — best-effort on remote +peers. -### 6.4 Default-session sync to clients +### 5.4 Default-session sync to clients The orchestrator **MAY** emit the default-session state as a diagnostic on a deployer-defined topic, so that interested @@ -548,16 +433,16 @@ defined here; this is deployment policy. --- -## 7. Conformance +## 6. Conformance -### 7.1 Bus +### 6.1 Bus The message bus **MUST** be stateless with respect to session. It **MUST NOT** interpret, mutate, persist, or special-case `Message.context.session` for any reason. Delivery is the bus's contract; session is opaque to it. -### 7.2 Orchestrator +### 6.2 Orchestrator An orchestrator that claims conformance to this specification **MUST**: @@ -566,8 +451,8 @@ An orchestrator that claims conformance to this specification stateless per §2.2 — no cross-utterance state held outside what the inbound Message brings; - hold the default session as persistent in-process state per - §6, with the merge / derive / restart semantics of §6.1 / - §6.2; + §5, with the merge / derive / restart semantics of §5.1 / + §5.2; - apply in-place session mutations only at the boundaries of §2.6 (transformer, pipeline-match, handler); - propagate session forward unchanged on every Message @@ -575,20 +460,14 @@ An orchestrator that claims conformance to this specification the §2.6 boundaries dictate mutation; - emit the universal end-marker `ovos.utterance.handled` carrying the final round session (PIPELINE-1 §9), as the - client-side convergence point of §4.3. + client-side convergence point of §3.3. An orchestrator **MUST NOT** require any client to declare session-start / session-end / session-id-allocation events before processing an inbound Message. Clients send what they send; the orchestrator processes what arrives. -### 7.3 Component - -A component (pipeline plugin, transformer, dispatched handler, -introspection observer) **MUST**: - -- treat transient in-utterance caches as utterance-scoped, - discarding them at end-of-utterance. +### 6.3 Component A component that holds `session_id`-keyed state across utterances **SHOULD**: @@ -604,7 +483,7 @@ utterances **SHOULD**: A component **MAY** instead hold cross-utterance state internally per §2.4 when projection is impractical, in which case it MUST take full responsibility for state lifecycle and -accept best-effort resumption (§5.3). +accept best-effort resumption (§4.3). A component **MUST NOT** rely on bus events (the asynchronous kind that fire outside the utterance lifecycle) to mutate @@ -612,7 +491,7 @@ session state in the current utterance (§2.6). It MAY emit such events to communicate with other components; their effect on session, if any, lands on subsequent utterances. -### 7.4 Client +### 6.4 Client A **client** (any participant on the user side of the bus boundary that uses a named `session_id`) **MUST**: @@ -622,14 +501,9 @@ boundary that uses a named `session_id`) **MUST**: - include that state in `Message.context.session` on every inbound Message it emits. -A client **MAY**: - -- update its local session from any Message it observes per §4; -- choose any persistence format, lifetime, and lifecycle for - its local session; -- re-emit a previously-used `session_id` at any time per §5; -- send any `session_id` and any `session` value, including - fabricated ones (trust is a layer-2 concern, §2.5). +A client **MAY** update its local session per §3, choose any +persistence format and lifetime, and re-emit a previously-used +`session_id` at any time (§4). A client **MUST NOT**: @@ -637,11 +511,11 @@ A client **MUST NOT**: between rounds — every round MUST be self-sufficient via the inbound session. -### 7.5 Default-session client +### 6.5 Default-session client The local device, which uses `session_id == "default"`, is a special-case client. Because the orchestrator owns the default -session per §6, the local device **MAY** omit `Message.context.session` +session per §5, the local device **MAY** omit `Message.context.session` or emit `session: {}` (SESSION-1 §3.1's equivalent forms) and rely on the orchestrator's stored state. This is the only place this spec recognizes a client that does not carry its own @@ -650,10 +524,10 @@ default-session store *is* that state for the local device. --- -## 8. Non-goals +## 7. Non-goals See §1 for the full list of non-goals. This section adds one clarification: **default-session persistence across orchestrator -restart** is not defined here. §6.2 makes restart-loss +restart** is not defined here. §5.2 makes restart-loss explicit and intentional; persistence is deployer policy if desired. From 7426bc678f0f28105603e55305c6a5916924099f Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Tue, 26 May 2026 17:43:07 +0100 Subject: [PATCH 5/6] SESSION-2: add TRANSFORM-1 to intro; note source/destination for cross-client disambiguation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Intro: add TRANSFORM-1 as fifth companion spec; its six transformer hooks are the normative session-mutation boundaries cited in §2.6 - §1 non-goals: cross-client session sharing note now mentions that MSG-1 source/destination routing is the layer-2 disambiguation mechanism for which client owns a given session_id Co-Authored-By: Claude Sonnet 4.6 --- session-lifecycle.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/session-lifecycle.md b/session-lifecycle.md index c0e3960..700ddaf 100644 --- a/session-lifecycle.md +++ b/session-lifecycle.md @@ -26,7 +26,7 @@ time, lets an orchestrator restart without losing client-side continuity, and lets multiple orchestrators in a deployment serve the same session without coordination. -It builds on four companion specifications: +It builds on five companion specifications: - the *Bus Message Specification* (OVOS-MSG-1) — the envelope, routing keys, `forward` / `reply` / `response` derivations, @@ -40,6 +40,10 @@ It builds on four companion specifications: `Match.updated_session` channel that match-phase session mutations travel on, and the universal end-marker `ovos.utterance.handled`; +- the *Transformer Plugin Specification* (OVOS-TRANSFORM-1) — + defines six transformer hooks (audio, utterance, metadata, + intent, dialog, TTS) that are normative session-mutation + boundaries per §2.6; - the *Intent Context Specification* (OVOS-CONTEXT-1) and the *Active Handlers and Interactive Response Specification* (OVOS-CONVERSE-1) — both elect the §2.4 SHOULD-project @@ -87,7 +91,10 @@ This specification does **not** define: are someone else's spec; - **cross-client session sharing** — two clients holding the same `session_id` would race on session state; coordination - is out of scope; + is out of scope. A layer-2 system that routes Messages to + specific clients (using MSG-1 `source` / `destination`) + can disambiguate which client owns a given `session_id`, but + that routing policy is a layer-2 responsibility; - **session migration between orchestrators** — handled implicitly by the §2.2 stateless rule (any orchestrator can serve any named session because no orchestrator holds state From bea22358c9f79c4ab6d8710bfe10e10ffa5871bb Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Tue, 26 May 2026 17:52:30 +0100 Subject: [PATCH 6/6] SESSION-2: remove ovos-core / SessionManager named references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Specs must be timeless and standalone — no named implementation projects. Replace two prose references to ovos-core and SessionManager.default_session with implementation-agnostic descriptions. Co-Authored-By: Claude Sonnet 4.6 --- session-lifecycle.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/session-lifecycle.md b/session-lifecycle.md index 700ddaf..db9ab4c 100644 --- a/session-lifecycle.md +++ b/session-lifecycle.md @@ -72,8 +72,8 @@ This specification defines: resumable across arbitrary elapsed time or orchestrator restart; - the **default-session ownership rule** (§5) — the one - exception to statelessness, codifying current ovos-core - `SessionManager.default_session` behaviour; + exception to statelessness; the orchestrator holds the + default session as persistent in-process state; - **conformance** (§6) for the four roles (bus, orchestrator, component, client). @@ -158,8 +158,8 @@ This is the one exception to §2.2. The local device is a client of the orchestrator that runs in the same process tree as the orchestrator itself; making the orchestrator hold its state is the simplest representation of that physical -co-location. It also formalises the existing ovos-core pattern -of `SessionManager.default_session` (see APPENDIX §5). +co-location. This is the simplest representation of that physical +co-location. Behaviour rules for the default-session store are in §5.