SQLite-based implementation of session storage. This implementation stores conversation history in a SQLite database. By default, uses an in-memory database that is lost when the process ends. For persistent storage, provide a file path.
| 15 | |
| 16 | |
| 17 | class SQLiteSession(SessionABC): |
| 18 | """SQLite-based implementation of session storage. |
| 19 | |
| 20 | This implementation stores conversation history in a SQLite database. |
| 21 | By default, uses an in-memory database that is lost when the process ends. |
| 22 | For persistent storage, provide a file path. |
| 23 | """ |
| 24 | |
| 25 | session_settings: SessionSettings | None = None |
| 26 | _file_locks: ClassVar[dict[Path, threading.RLock]] = {} |
| 27 | _file_lock_counts: ClassVar[dict[Path, int]] = {} |
| 28 | _file_locks_guard: ClassVar[threading.Lock] = threading.Lock() |
| 29 | |
| 30 | def __init__( |
| 31 | self, |
| 32 | session_id: str, |
| 33 | db_path: str | Path = ":memory:", |
| 34 | sessions_table: str = "agent_sessions", |
| 35 | messages_table: str = "agent_messages", |
| 36 | session_settings: SessionSettings | None = None, |
| 37 | ): |
| 38 | """Initialize the SQLite session. |
| 39 | |
| 40 | Args: |
| 41 | session_id: Unique identifier for the conversation session |
| 42 | db_path: Path to the SQLite database file. Defaults to ':memory:' (in-memory database) |
| 43 | sessions_table: Name of the table to store session metadata. Defaults to |
| 44 | 'agent_sessions' |
| 45 | messages_table: Name of the table to store message data. Defaults to 'agent_messages' |
| 46 | session_settings: Session configuration settings including default limit for |
| 47 | retrieving items. If None, uses default SessionSettings(). |
| 48 | """ |
| 49 | self.session_id = session_id |
| 50 | self.session_settings = session_settings or SessionSettings() |
| 51 | self.db_path = db_path |
| 52 | self.sessions_table = sessions_table |
| 53 | self.messages_table = messages_table |
| 54 | self._local = threading.local() |
| 55 | self._connections: set[sqlite3.Connection] = set() |
| 56 | self._connections_lock = threading.Lock() |
| 57 | self._closed = False |
| 58 | |
| 59 | # For in-memory databases, we need a shared connection to avoid thread isolation |
| 60 | # For file databases, we use thread-local connections for better concurrency |
| 61 | self._is_memory_db = str(db_path) == ":memory:" |
| 62 | self._lock_path: Path | None = None |
| 63 | self._lock_released = False |
| 64 | if self._is_memory_db: |
| 65 | self._lock = threading.RLock() |
| 66 | else: |
| 67 | self._lock_path, self._lock = self._acquire_file_lock(Path(self.db_path)) |
| 68 | |
| 69 | try: |
| 70 | if self._is_memory_db: |
| 71 | self._shared_connection = sqlite3.connect(":memory:", check_same_thread=False) |
| 72 | self._shared_connection.execute("PRAGMA journal_mode=WAL") |
| 73 | self._init_db_for_connection(self._shared_connection) |
| 74 | else: |
no outgoing calls