Skip to content

aimu.sessions

Multi-user session storage: per-conversation state keyed by channel:sender, so one process serves many users/chats. See how-to: use sessions.

Sync, matching the MemoryStore / ConversationManager family; the async assistant loop calls it directly.

aimu.sessions.Session dataclass

Session(key: str, messages: list[dict] = list(), memory_namespace: Optional[str] = None, metadata: dict[str, Any] = dict())

One conversation's persisted state.

Attributes:

Name Type Description
key str

The session key (see :func:session_key).

messages list[dict]

Conversation history as list[dict] in OpenAI format (plain data).

memory_namespace Optional[str]

Optional scope a caller can pass to a MemoryStore to isolate this session's memories (e.g. a collection name / prefix). The store ABC is unchanged.

metadata dict[str, Any]

Free-form, opaque to the library (display name, locale, ...).

aimu.sessions.SessionSummary dataclass

SessionSummary(key: str, message_count: int, memory_namespace: Optional[str], metadata: dict[str, Any])

One stored session, without its messages.

Everything a :class:Session carries except messages, plus how many of them there are. That definition is the point: a caller listing conversations wants titles and timestamps, and reading every transcript to get them is the cost this type exists to avoid.

message_count is the one field that is not a projection, since it is computed over the very thing being excluded. It is here because "how many messages" is the most universal summary fact about a conversation, and recovering it otherwise means the full read again.

Attributes:

Name Type Description
key str

The session key (see :func:session_key).

message_count int

How many messages the stored session holds.

memory_namespace Optional[str]

The stored session's memory_namespace, unchanged.

metadata dict[str, Any]

A detached copy of the stored session's metadata. Mutating it does nothing until the corresponding :class:Session is fetched, changed, and saved.

aimu.sessions.SessionStore

Bases: ABC

A store of :class:Session state keyed by session_key(channel, sender).

get returns a detached snapshot (mutating it does nothing until save); save persists.

get abstractmethod

get(key: str) -> Session

Return the session for key, or a fresh empty :class:Session if none is stored.

save abstractmethod

save(session: Session) -> None

Persist session (create or replace by session.key).

list_keys abstractmethod

list_keys() -> list[str]

Return the keys of all saved sessions.

delete abstractmethod

delete(key: str) -> None

Delete the session for key. A no-op if no such session exists.

close

close() -> None

Release resources. Default no-op.

list_summaries

list_summaries() -> list[SessionSummary]

Every stored session without its messages, in unspecified order.

Concrete rather than abstract, so an existing implementation keeps working: this default is correct everywhere and slow anywhere a full read is expensive, and a store that can do better overrides it. The same reasoning as :meth:close's default no-op.

Deep-copies each session's metadata before handing it back, rather than trusting whatever detachment get() already did. SessionSummary.metadata promises a caller can mutate it freely, and this default has no way to know how deep an arbitrary subclass's get() copies: this library's own InMemorySessionStore.get copies only one level, which is enough for a Session a caller is expected to fetch, change, and save again, but not enough for a summary nobody saves. Overriding list_summaries (as TinyDBSessionStore does) can skip this copy when the override's own read path already returns freshly-built data.

No ordering parameter and no paging, because both need a sort key and every candidate lives in metadata, which this library treats as opaque. A caller sorts the result by whichever of its own keys it means.

aimu.sessions.InMemorySessionStore

InMemorySessionStore()

Bases: SessionStore

A non-durable :class:SessionStore backed by a dict. Sessions are lost on process exit.

get and save copy, so the stored state is detached from the returned/passed object (same contract as the persisted stores).

list_summaries

list_summaries() -> list[SessionSummary]

Summaries with deeply-copied metadata.

Deeper than :meth:get's one-level copy, deliberately. get hands back a whole Session a caller is expected to save again, so a shared nested value is visible there; a summary is a read-only snapshot nobody saves, and a caller mutating one must not be able to reach stored state at all.

aimu.sessions.TinyDBSessionStore

TinyDBSessionStore(db_path: str = 'sessions.json')

Bases: SessionStore

A durable :class:SessionStore persisting one document per session to a TinyDB file.

list_summaries

list_summaries() -> list[SessionSummary]

Summaries from one table read, projecting the messages out.

The reason this override exists: the inherited default is one whole-file parse per stored session, and TinyDB's JSON storage re-reads and re-parses the entire file on every operation. Rows come back freshly parsed, so no copy is needed for detachment.

aimu.sessions.SessionLocks

SessionLocks()

Lazily-created per-key asyncio.Lock.

Serializes a single session's turns (shared message state, ordering) while letting different sessions run concurrently::

locks = SessionLocks()
async with locks(key):
    ...  # this session's turn

Created locks are retained, so the same key always returns the same lock. Bound to the running event loop via asyncio.Lock; use one SessionLocks per assistant process.

aimu.sessions.session_key

session_key(channel: Optional[str], sender: Optional[str]) -> str

Canonical session key from a message's channel + sender.

Single-user transports (no channel/sender) collapse to "default:default", so single-user usage needs no ceremony.