Session Lifecycle and State Ownership Specification
August 13, 2026 · View on GitHub
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 six companion specifications:
- the Bus Message Specification (OVOS-MSG-1) — the envelope,
routing keys,
forward/reply/responsederivations, 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, thesession_id == "default"reservation, and the omission-not-nullrule; - the Utterance Lifecycle and Pipeline Specification
(OVOS-PIPELINE-1) — the per-utterance lifecycle, the
Match.updated_sessionchannel that match-phase session mutations travel on, and the universal end-markerovos.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 pathway for their cross-utterance state (intent-context entries for CONTEXT-1; the converse-handler list and response-mode wait window for CONVERSE-1), making it resumption-safe by construction.
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, when components SHOULD project their cross-utterance state into session-resident fields vs hold it internally, the session mutation discipline (§2.6), and how views converge without any push topic (§2.7);
- the client-side merge rules (§3) — how a client tracks
session updates from assistant-emitted Messages, keyed on
session_idalone; - the resumption semantics (§4) — what makes a conversation resumable across arbitrary elapsed time or orchestrator restart;
- the default-session ownership rule (§5) — the one exception to statelessness; the orchestrator holds the default session as persistent in-process state;
- conformance (§6) 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_idit wants is conformant; trust boundaries are someone else's spec; - cross-client session sharing — two clients holding the
same
session_idwould race on session state; coordination is out of scope. A layer-2 system that routes Messages to specific clients (using MSG-1source/destination) can disambiguate which client owns a givensession_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 for it);
- lifecycle observability events (
ovos.session.start/.endor 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: a component MUST continue to function when such a cache is absent, and MUST NOT read one as durable state.
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)
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.
(Rationale, informative.) 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.
Behaviour rules for the default-session store are in §5.
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 SHOULD project that state into a session-resident
field it owns 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.
Ownership of a session field is established only by the claiming mechanic of SESSION-1 §2.2, which is available to a specification, not to a component. A component whose projected field is claimed by no specification is still conformant: that field is non-normative per SESSION-1 §2.3 and rides the unknown-field tolerance of SESSION-1 §2.4 — every consumer carries it and none is bound to interpret it. What such a component does not get is a normative reading by anyone else. A component that wants its field honoured by other participants needs a specification to claim it.
A component MAY instead hold authoritative cross-utterance state internally when projection is impractical. (The following examples are informative.)
- 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 continue to function correctly when no other component or client knows the state exists or compensates for its absence — it MUST NOT make its own correctness depend on such knowledge or compensation.
(Informative.) 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
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.
Trust and authorization are layer-2 concerns (§1); this spec
places no constraint on what session_id or session value a
client sends. (Informative.) This rule is what obliges a
boundary component that governs a client by injecting policy
fields into its session to keep doing so on every Message;
OVOS-BRIDGE-1 §4.1 states that obligation as the gate invariant.
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
matchmay return aMatch.updated_session. The commit mechanic is PIPELINE-1 §4.2's and is not restated here; - handler boundaries — a dispatched handler (skill or
plugin-bundled handler per PIPELINE-1 §7.0) MAY mutate
session in-place, within the limit that it writes only fields
it owns. OVOS-MSG-1 §4.1 otherwise forbids a producer to
modify a session present on the source Message; the
owned-field allowance in that section is what makes this
boundary conformant, and a handler write to a field it does
not own remains forbidden. The handler's emissions via
forward/reply/response(OVOS-MSG-1 §5) carry the mutated session forward. A handler that emits no Message has no bus-visible way to propagate its session mutations. The handler-lifecycle trio.complete(PIPELINE-1 §8) is orchestrator-emitted from the dispatch context the orchestrator holds — it does not reflect handler-side in-place changes, particularly for handlers running out-of-process. A handler that mutates session and needs that state visible in terminal events MUST emit at least one Message (typicallyovos.utterance.speak).
Session mutation discipline. A handler SHOULD NOT mutate
session fields unless the mutation is necessary for the
handler's function or is explicitly prescribed by another
specification. Incidental mutations add state that clients and
observers must track, increase the risk of session-state races
in multi-component deployments, and make session evolution
harder to reason about. When another spec prescribes a
mutation (e.g. a handler removing itself from
session.active_handlers per OVOS-STOP-1 §4.4), that
prescription is the authority; this discipline rule does not
override it.
Incidental bus events never mutate the working session. Bus events emitted outside these boundaries — the asynchronous, normal-event-handler kind that any component may emit at any time — carry a session but do not update anyone's working state. The orchestrator MUST NOT merge the session of such a Message into the working session snapshot for the utterance in progress, and a component MUST NOT make its own behaviour depend on such a merge having happened. The bus is asynchronous and not part of the utterance lifecycle (§2.1).
Such a Message MAY still affect subsequent utterances on the session: the client receives it and merges per §3, and the merged state arrives on the next inbound Message. A component that relies on that effect MUST tolerate it landing no earlier than the next utterance.
There is no out-of-band mutation channel. A component that
needs a session change to take effect MUST effect it at one
of the boundaries above, in a lifecycle it participates in: a
transformer hook it implements, a Match.updated_session it
returns, or a handler invocation it is dispatched into. A
component that participates in no lifecycle at the moment it
wants the change MUST wait until it does. What it emits in
the meantime is an ordinary bus event under the rule above: the
client may merge it per §3 and carry it back, and no working
session is revised by it.
2.7 Convergence without push
This specification defines no topic on which any participant pushes a session at another. Session state converges two ways, and only these two.
At start-up, by shared derivation. Processes co-located with the orchestrator share one device and therefore one default session (§2.3). Each derives its initial view of that session from the deployment configuration. The source is the same for all of them, so the views agree by construction and no handshake, bootstrap request, or announcement is needed to make them agree.
At runtime, by adoption. A consumer adopts the session of
any Message it acts on or renders. This is the rule §3 states
for clients, and it holds for every consumer: a co-located
process that renders a speak, observes a terminal event, or
processes any other session-bearing Message takes that session
as its current view of that session_id. The orchestrator's
default-session store (§5) is authoritative for what rides the
orchestrator's own emissions, so a process that adopts from
observed traffic converges on the store by following it.
(Rationale, informative.) A view that converges by adoption has exactly one writer per session — the client for a named session (§2.5), the orchestrator for the default session (§5). No process ever broadcasts its own view at anyone, so two processes cannot clobber each other's default session, and there is no ordering question between competing pushes to resolve.
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.
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.
session_id uniquely identifies the channel; it is the only
key that matters for client-side session merging. No other
matching predicate is normative.
3.2 Every assistant-emitted Message carries an updated session
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.
Where to adopt. "Latest received" is not a well-defined selector on this bus. The bus is asynchronous and guarantees no delivery order (§2.1), so two Messages emitted in a known order may be observed in the opposite one; a client that adopts whichever arrived last can therefore install an older assistant view over a newer one. This specification consequently does not define an ordering, and no client may assume one.
It is instead RECOMMENDED that a client adopt at the
universal end-marker ovos.utterance.handled (PIPELINE-1 §9.5),
which is emitted exactly once per utterance (§3.3) and so needs
no ordering to be unambiguous.
Adopt from what you render. Independently of that
convergence point, a client adopts the session of any
user-facing Message it renders — a speak it voices, a page
it draws, any Message it turns into something the user
perceives. Rendering is acting on the Message, and the session
that arrived with it is the assistant's view at the point the
assistant produced what the user is now receiving.
This is what covers an interaction the assistant starts on its
own. A proactive speak outside any utterance round carries its
session exactly as a speak inside one does; the client that
renders it adopts that session and carries it back on its next
inbound Message. No separate push mechanism is needed, and none
exists (§2.7).
A client MAY adopt incrementally from Messages observed mid-round, and MAY run a more elaborate policy (field-by-field merge, selecting by emitter identity). Both are conformant, with one warning that applies to every incremental policy: a session adopted mid-round may be stale by the time it is stored, and the client has no reliable way to detect that it is. A client that adopts incrementally SHOULD therefore also adopt at the §3.3 convergence point, which supersedes whatever the incremental policy accumulated during the round.
3.3 ovos.utterance.handled is the canonical convergence point
When a client wants a single canonical "round is over" snapshot,
the universal end-marker ovos.utterance.handled (PIPELINE-1
§9.5) 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.
4. Resumption semantics
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
"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).
4.2 What is resumption-safe
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: 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.
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 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. (The following illustrations are informative.)
- 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.
5. The default-session ownership rule
5.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_sessionper PIPELINE-1 §4.2, in-handler mutations) propagate into the store through the standard derivation chain.
Merge semantics for inbound default-session Messages. An omitted inbound field leaves 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 a deliberate deviation from SESSION-1 §2.1, and this specification owns it. SESSION-1 §2.1 says an omitted field is filled at consumption with the consumer's deployment default. That rule is about consuming a session; it presumes the consumer has no better answer than its configuration. For the default session the orchestrator does have a better answer: it is the authoritative holder of the session, so it fills an omission from its store instead of from its defaults. The deviation is confined to writes into the default-session store. SESSION-1 §2.1 continues to govern everywhere else, including every named session and including how components downstream consume the session the orchestrator then emits. This is the natural complement to the stateless-named-session rule: the default-session store fills the role the client plays for named sessions.
Field replacement is the default; a field whose claiming
specification defines a finer intra-field merge resolves by that
rule instead. At this version that is session.intent_context,
merged entry-by-entry per OVOS-CONTEXT-1 §5.3.
Every wire carrier is a snapshot; merge semantics are
store-internal. A session on the wire — carried on an utterance
Message (SESSION-1 §2.1), committed as a Match.updated_session
(below) — declares complete state: an omitted field means not
set, and it resolves to the deployment default at consumption.
The delta reading, where an omitted field means no opinion and
the current value stands, applies on exactly one pathway: the
write into the orchestrator's own default-session store described
above. That is a rule about how the orchestrator writes its own
store — server-local, and not a semantics any Message carries on
the bus. Which reading
applies is a property of the pathway, fixed here, never of the
producer's intent.
Field tolerance applies at store-write. A session written into the store may carry keys the orchestrator does not recognise. SESSION-1 §2.4's unknown-field tolerance applies: the orchestrator MUST NOT reject the write and MUST NOT strip the unrecognised keys, and it MUST carry them on the sessions it subsequently derives from the store. SESSION-1 §2.5's malformed-carrier rules apply unchanged.
Wholesale replacement on the match pathway. The
field-by-field merge above governs inbound client Messages.
A committed Match.updated_session (PIPELINE-1 §4.2) is a
complete snapshot instead: the orchestrator MUST replace the
working session snapshot with it wholesale. A field absent from
Match.updated_session is therefore not preserved — it reverts
to the deployment default at consumption, exactly as any
omitted field does under SESSION-1 §2.1. It is not deleted in
any stronger sense, and the distinction matters: a consumer
never sees a hole, it sees a field that resolves to the default.
Two writes escape wholesale replacement, because other specifications fix them as applying on top of the committed snapshot rather than inside it:
- the
session.active_handlerspush of PIPELINE-1 §7.1, which that section defines as applied afterMatch.updated_sessionis committed; - the entry-level
intent_contextmerge of OVOS-CONTEXT-1 §5.3, which merges entry-by-entry and so is not expressible as a whole-map replacement.
Wholesale replacement exists only on the match pathway. An inbound client Message cannot drop a stored default-session field by omitting it — it can only overwrite the field by sending a new value.
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.1). Components keyed on the default session lose their state.
(Rationale, informative.) 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.
5.3 Component reliance on default-session continuity
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. Conformance
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.
6.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 §5, with the merge, derive and restart semantics of §5.1 and §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 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.handledcarrying the final round session (PIPELINE-1 §9.5), as the 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.
6.3 Component
A component that holds session_id-keyed state across
utterances SHOULD:
- project that state into a session-resident field per §2.4 —
a field claimed by a specification under SESSION-1 §2.2 where
one exists, otherwise an unclaimed field carried under
SESSION-1 §2.4's tolerance with no normative reading by
anyone else — via the appropriate in-utterance pathway:
Match.updated_sessionfor pipeline plugins per PIPELINE-1 §4.2, direct mutation for transformers and handlers per §2.6; - on every inbound Message, read its state from
sessionrather 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 (§4.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 events to communicate with other components; their effect on session, if any, lands on subsequent utterances.
A component SHOULD NOT mutate session fields in its handler unless the mutation is necessary or prescribed by another specification (§2.6 discipline rule). A component that needs a session change effects it at a §2.6 boundary of a lifecycle it participates in; there is no out-of-band pathway (§2.7).
6.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_idvalues it uses, per §2.5; - include that state in
Message.context.sessionon every inbound Message it emits.
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 make every round self-sufficient: it MUST carry on the inbound session all state it needs the assistant to act on, and it MUST NOT omit state on the assumption that the orchestrator retained it from a previous round.
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 §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
authoritative state — and only because the orchestrator's
default-session store is that state for the local device.
7. Bus topics
This specification defines no bus topic. Session travels on the carrier OVOS-SESSION-1 defines, mutates at the lifecycle boundaries of §2.6, and converges by adoption (§2.7, §3). The per-utterance session propagation (§2.6) and the end-marker (§3.3) travel on topics owned by OVOS-PIPELINE-1.
A deployment MAY define a further topic of its own on which the orchestrator publishes the default-session state (§5) as a diagnostic, so that interested observers can track default-session evolution without processing every response Message. This specification defines no name, payload, or consumer for such a topic; it is deployment policy and carries no conformance weight.
8. 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. §5.2 makes restart-loss explicit and intentional; persistence is deployer policy if desired.