Session and SessionManager

July 16, 2026 · View on GitHub

Session state is the primary mechanism by which OVOS tracks per-user, per-device conversation context across the intent pipeline, skills, chat agents, and HiveMind agents.

Session

Sessionovos_bus_client/session.py:263

Each Session holds:

AttributeTypeDescription
session_idstrUUID; "default" is the reserved in-process session
langstrBCP-47 language tag (standardised on assignment)
pipelineList[str]Ordered intent pipeline stage identifiers
active_skillsList[List][skill_id, last_touch_timestamp] pairs
utterance_statesDict[str, UtteranceState]Per-skill INTENT or RESPONSE state
contextIntentContextManagerConversational entity/frame stack
site_idstrPhysical location identifier
is_speakingboolAudio output active flag
is_recordingboolMicrophone active flag
blacklisted_skillsList[str]Skills excluded for this session
blacklisted_intentsList[str]Intents excluded for this session
persona_idOptional[str]Persona override for this session
expiration_secondsintTTL; -1 means never expires

Sessions serialize to/from plain dicts via Session.serialize() / Session.deserialize()ovos_bus_client/session.py:441,493. They are carried inside message.context["session"] on every bus message.

Omitted and empty override fields

On the list-valued override fields — pipeline, the three blacklisted_*, and the *_transformers chains — an empty list is wire-equivalent to an absent key (OVOS-SESSION-1 §3.4). Both mean "let the orchestrator decide", and a consumer resolves them to its own deployment default from ovos-config. An explicit null is malformed and is treated as absent. So blacklisted_intents: [] does not assert "this session blacklists nothing"; to do that, the deployment default must itself be empty.

Session.serialize() therefore omits these fields when they are empty, rather than restating the deployment default on every Message — §3.4 exists precisely to keep the session from adding hundreds of bytes to every forward, reply, and cross-process hop. Read them off a deserialized Session, whose attributes are always concrete lists, rather than indexing the raw serialized dict, where an absent key is normal.

Intent-context removal tombstones

Session.remove_intent_context() / clear_intent_context() (and the legacy Session.context view's remove_context / clear_context) do not pop entries from session.intent_context — they replace them in place with a null tombstone. OVOS-CONTEXT-1 §5.3 propagates deletions as null entries in the ovos.session.sync payload, and a popped key would simply be absent from the payload, which §5.3 defines as "unchanged" — the orchestrator would keep the entry alive. The tombstone keeps the deletion visible in every serialized snapshot of the session until it is applied.

A tombstone is never live (§2): CONTEXT-1 gating, §7 slot fill, and the legacy frame-stack projection all treat it as absent, the receiving ovos.session.sync merge deletes the key, and the orchestrator's §4 pre-match prune reaps it locally. Consequence for readers: test an entry for liveness (ovos_spec_tools.context.is_live) or truthiness, never bare key membership — "person" in session.intent_context is True while a removal is still propagating.

Session.from_message

Session.from_message(message)ovos_bus_client/session.py:537

The canonical way to obtain a Session from an incoming bus message. It:

  1. Reads message.context["session"] and deserialises it.
  2. Merges any top-level lang key from message.context or message.data if absent from the session dict.
  3. Falls back to SessionManager.default_session when no session context exists.
from ovos_bus_client.session import Session

def handle_utterance(message):
    sess = Session.from_message(message)
    print(sess.session_id, sess.lang)

SessionManager

SessionManagerovos_bus_client/session.py:568

An in-process, class-level registry (SessionManager.sessions: Dict[str, Session]). Downstream consumers must not persist sessions independently — always go through SessionManager so that state remains consistent within the process.

get

SessionManager.get(message)ovos_bus_client/session.py:638

Returns the Session for a message, registering it in the sessions dict if its session_id is not "default". Falls back to default_session when no message or no session context is available.

from ovos_bus_client.session import SessionManager

sess = SessionManager.get(message)

update

SessionManager.update(sess, make_default=False)ovos_bus_client/session.py:618

Writes a session back into the registry. Pass make_default=True to promote it to the default session (also forces session_id = "default").

sess = SessionManager.get(message)
sess.lang = "pt-pt"
SessionManager.update(sess)

Bus synchronisation

SessionManager.connect_to_bus(bus)ovos_bus_client/session.py:583 — registers listeners for recognizer_loop:* and ovos.session.* events and immediately pushes the current default session to ovos-core via ovos.session.update_default.

Usage pattern for chat agents and HiveMind agents

The pattern expected by downstream consumers (e.g. ovos-messagebus-chat-plugin, hivemind-ovos-agent-plugin):

  1. Receive an external request (HTTP / HiveMind message).
  2. Look up or create a Session keyed by session_id from the external client.
  3. Inject it into message.context["session"] before emitting to the bus.
  4. After the pipeline completes, call SessionManager.get(reply_message) to retrieve the updated session and persist it back to the external client context.

This ensures multi-turn state (active skills, converse queue, context frames) is preserved correctly across turns without sharing state between unrelated callers.