AIR

Sessions

A session keeps conversation history across multiple calls, independent of which provider answers any given turn.

Creating and using a session

python
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):

  • message is a str (becomes a single user Message) or a ChatRequest.
  • provider is an optional string. If given, it overrides ChatRequest.provider for this call only, and behaves exactly like explicit provider selection on air.chat(...) (see core/routing.md): no fallback, and if the name is not registered, ProviderNotFoundError is raised before anything is stored for this turn.
  • Returns the same ChatResponse that air.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

python
await session.chat("Hello", provider="gemini")
await session.chat("Continue this", provider="groq")
await session.chat("Continue again")   # back to normal automatic routing

Because 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

python
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

python
await session.clear()   # removes stored messages, session id is still known
await session.delete()  # removes the session entirely

For 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

python
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

python
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, message role, message content (plain text), the provider name and model string 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/Role representation 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.