Sessions
A session keeps conversation history across multiple calls, independent of which provider answers any given turn.
Creating and using a session
session = air.session("user-123")
await session.chat("My name is Alex")
response = await session.chat("What is my name?")air.session(session_id) returns a Session bound to that id string and to AIR's configured context store. It does not touch storage by itself; a session only appears in storage once you call chat(...) on it.
Session.chat(message, provider=None):
messageis astr(becomes a single userMessage) or aChatRequest.provideris an optional string. If given, it overridesChatRequest.providerfor this call only, and behaves exactly like explicit provider selection onair.chat(...)(see core/routing.md): no fallback, and if the name is not registered,ProviderNotFoundErroris raised before anything is stored for this turn.- Returns the same
ChatResponsethatair.chat(...)would return.
Internally, Session.chat(...) loads all prior messages for this session id, prepends them to the new message(s), calls air.chat(...) with the combined ChatRequest, then appends the new user message(s) and the assistant's reply to the store. This means routing, fallback, intelligent routing, and context optimization all apply to session calls exactly as they do to direct air.chat(...) calls; Session does not duplicate any of that logic.
Manual provider override and switching providers mid session
await session.chat("Hello", provider="gemini")
await session.chat("Continue this", provider="groq")
await session.chat("Continue again") # back to normal automatic routingBecause the stored history uses AIR's own Message/Role format, not any provider's wire format, switching providers between turns (whether by explicit override or by automatic fallback) does not lose or corrupt context. Whichever provider answers a given turn receives the same accumulated history as any other provider would.
Retrieving history
history = await session.history()Returns a list of dicts, one per stored message, each with role, content, provider, model, and created_at keys. provider and model are only set on assistant messages (the provider/model that produced that reply); they are None on user messages.
Clearing and deleting
await session.clear() # removes stored messages, session id is still known
await session.delete() # removes the session entirelyFor the SQLite backend, clear() deletes message rows but keeps the session's row in the internal sessions table, so the id still shows up from air.list_sessions(). delete() removes both the messages and the session's row.
Listing sessions
session_ids = await air.list_sessions()In memory backend (default)
If you construct AIR() without persist=..., sessions are held in an InMemoryContextStore, a plain Python dict kept for the life of the process. No file is created. This is the default so that constructing AIR() never has side effects on your filesystem.
SQLite backend
air = AIR(persist="path/to/air.db")Passing a file path to persist switches the context store to SQLiteContextStore, using Python's standard library sqlite3 module, guarded by an asyncio.Lock per AIR instance so concurrent async operations against the same AIR instance do not corrupt the database. This is not designed for multiple separate processes writing to the same SQLite file at once.
The schema has two tables:
sessions(session_id TEXT PRIMARY KEY, created_at REAL NOT NULL)messages(id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, role TEXT, content TEXT, provider TEXT, model TEXT, created_at REAL)
Reopening AIR(persist="path/to/air.db") against the same file restores all previously stored sessions; air.list_sessions() and session.history() reflect what was persisted in a prior process.
Call air.close() to close the underlying SQLite connection when you are done with an AIR instance that used persist=.... This is a no-op (safe to call) if you are using the default in memory store.
What is and is not persisted
Verified from context/memory.py, context/sqlite.py, and context/session.py:
- Stored:
session_id, messagerole, messagecontent(plain text), theprovidername andmodelstring that produced an assistant reply, and a timestamp. - Never stored: API keys, provider client objects, or any provider specific request/response format. Only AIR's own
Message/Rolerepresentation is stored.
Context optimization interaction
Long sessions are still subject to context optimization before each request is sent to a provider; see features/context-optimization.md. The full original history in the session store is never trimmed or overwritten by optimization; only the copy sent to the provider for that one request is shortened.