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
Session — ovos_bus_client/session.py:263
Each Session holds:
| Attribute | Type | Description |
|---|---|---|
session_id | str | UUID; "default" is the reserved in-process session |
lang | str | BCP-47 language tag (standardised on assignment) |
pipeline | List[str] | Ordered intent pipeline stage identifiers |
active_skills | List[List] | [skill_id, last_touch_timestamp] pairs |
utterance_states | Dict[str, UtteranceState] | Per-skill INTENT or RESPONSE state |
context | IntentContextManager | Conversational entity/frame stack |
site_id | str | Physical location identifier |
is_speaking | bool | Audio output active flag |
is_recording | bool | Microphone active flag |
blacklisted_skills | List[str] | Skills excluded for this session |
blacklisted_intents | List[str] | Intents excluded for this session |
persona_id | Optional[str] | Persona override for this session |
expiration_seconds | int | TTL; -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:
- Reads
message.context["session"]and deserialises it. - Merges any top-level
langkey frommessage.contextormessage.dataif absent from the session dict. - Falls back to
SessionManager.default_sessionwhen 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
SessionManager — ovos_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):
- Receive an external request (HTTP / HiveMind message).
- Look up or create a
Sessionkeyed bysession_idfrom the external client. - Inject it into
message.context["session"]before emitting to the bus. - 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.