Skip to content

session

Session persistence — markdown file legacy + Supabase PostgreSQL manager.

SQLSessionManager

Supabase PostgreSQL session persistence with per-user isolation.

All queries are parameterized and run via asyncio.to_thread() through the supabase-py client.

__init__(_unused='')

Create the manager (stateless — keeps the legacy signature).

add_turn(session_id, turn_data, dek=None) async

Persist one turn as two message rows (user + assistant).

Assigns the next turn_number, encrypts content fields when a DEK is available, and bumps the session's updated_at.

Parameters:

Name Type Description Default
session_id str

Target session id.

required
turn_data dict

Dict with prompt, response, tools_called, cited_sources, chunks, timestamp.

required
dek bytes | None

Optional data key for runtime encryption.

None

delete_session(session_id, user_id) async

Delete a user-owned session and all its messages.

Returns False when the session does not exist or belongs to another user (nothing deleted).

get_session(session_id, user_id, dek=None) async

Load one session with merged turns, decrypted via the DEK.

Rows written before runtime encryption are lazily re-keyed when a DEK is available. Returns None when the session does not belong to the user.

get_session_preview(session_id, user_id, dek=None) async

Return a sidebar preview — first 3 turns, truncated.

list_sessions(user_id, role='user', dek=None) async

List a user's sessions with decrypted titles and turn counts.

Parameters:

Name Type Description Default
user_id str

Owner UUID.

required
role str

Ignored (kept for CLI compatibility).

'user'
dek bytes | None

Optional data key — encrypted titles decrypt only when it is provided; otherwise they surface as empty.

None

Returns:

Type Description
list[dict]

List of session dicts (id, title, created, updated, axes,

list[dict]

turns), newest first.

owns_session(session_id, user_id) async

Return True if the session exists and belongs to the user.

purge_legacy() async

Delete plaintext (pre-encryption) rows and blank legacy titles.

One-click admin cleanup for data that predates runtime encryption. Encrypted rows are never touched. Returns counts of deleted message rows and cleared titles.

rekey_user(user_id, dek) async

Bulk-encrypt every plaintext field belonging to a user.

Runs after a client first presents its DEK post-rollout (the login-triggered lazy re-key). Encrypts session titles and message fields in place; encrypted rows are skipped. Returns the number of rows rewritten. Best-effort — a failure on one row must not abort the sweep.

start_new_session(user_id, session_id, title, axes='{}', dek=None) async

Create a session row; the title is encrypted when a DEK is set.

SessionManager

Legacy markdown-file session persistence.

Maintained for migration and backward compatibility with CLI. For web API, use SQLSessionManager instead.

__init__(sessions_dir, axes=None)

Create the manager, ensuring the sessions directory exists.

Parameters:

Name Type Description Default
sessions_dir Path | str

Directory holding one .md file per session.

required
axes dict | None

Initial axis values (defaults to a balanced config).

None

add_turn(turn)

Append one conversation turn to the session file.

The turn dict carries prompt, response, chunks (RAG context), tools_called, cited_sources, and timestamp. Appends a '## Turn N' section and refreshes the file's Last Updated.

get_title()

Return the first turn's prompt truncated to 60 chars.

list_sessions()

List up to 50 session metadata dicts, newest first.

load_session(session_id_or_prefix)

Load a session by exact id or id prefix into current state.

Returns the metadata dict, or None when no session matches.

set_axes(axes)

Merge new axis values over the current configuration.

start_new(first_prompt)

Create a fresh session file with a header derived from the prompt.

The session id is YYYYMMDD_HHMMSS_slug where the slug comes from the first 50 chars of the prompt. In-memory turns reset.

turns_to_history(messages)

Convert merged session turns into LLM history messages.

Each turn has a prompt and response; empty parts are skipped so a half-saved turn (e.g. interrupted reply) never produces an empty message in the model's context.