feat(tui): implement SQLite persistence for TUI session state #8702

Open
opened 2026-04-13 22:24:18 +00:00 by HAL9000 · 1 comment
Owner

Summary

The TUI's session state (PersonaState, SessionView) is entirely in-memory and is lost on every restart. The infrastructure layer already has a full SQLite/SQLAlchemy stack with SessionModel, SessionMessageModel, and session_service.py, but the TUI is completely disconnected from it. The PersonaState uses a plain dict[str, str] for active_by_session and preset_by_session with no persistence beyond YAML-backed persona files.

Confirmed Gap (code evidence)

src/cleveragents/tui/app.py — in-memory SessionView, no persistence

@dataclass(slots=True)
class SessionView:
    """Minimal per-session TUI view model."""
    session_id: str
    transcript: list[str]
    # No created_at, no SQLite-backed ID, no persistence

src/cleveragents/tui/persona/state.py — in-memory dicts only

@dataclass(slots=True)
class PersonaState:
    registry: PersonaRegistry
    active_by_session: dict[str, str] = field(default_factory=dict)
    preset_by_session: dict[str, str] = field(default_factory=dict)
    # No SQLite backing; lost on restart

src/cleveragents/tui/persona/registry.py — YAML-only state

def load_state(self) -> dict[str, Any]:
    if not self.state_path.exists():
        return {}
    raw = yaml.safe_load(self.state_path.read_text(encoding="utf-8")) or {}
    # Only persists last_persona; no session list, no transcripts

Infrastructure layer has SQLite models but TUI does not use them

src/cleveragents/infrastructure/database/models.py defines SessionModel and SessionMessageModel. src/cleveragents/application/services/session_service.py provides create(), get(), delete(), export_session(), import_session(). The TUI's TuiCommandRouter._session_export and _session_import already call container.session_service() — but the TUI never calls session_service.create() on startup, so the session is never persisted.

Required Work

1. TUI session bootstrap

On TUI startup (in run_tui()), call session_service.create() to persist the default session to SQLite and store the returned session_id in SessionView. On subsequent launches, restore the last active session from SQLite.

2. TuiSessionPersistence adapter

Add src/cleveragents/tui/session_persistence.py:

  • TuiSessionPersistence class wrapping SessionService.
  • bootstrap_session(session_id: str | None) -> str — create or restore a session.
  • save_message(session_id: str, role: str, content: str) -> None — append a message to the SQLite transcript.
  • restore_transcript(session_id: str) -> list[str] — load the conversation transcript on resume.
  • list_sessions() -> list[dict] — enumerate persisted sessions for session:list.
  • delete_session(session_id: str) -> None — hard-delete from SQLite.

3. Wire PersonaState active session to SQLite

When PersonaState.set_active_persona() is called, also update the session record in SQLite (via TuiSessionPersistence) so the active persona is restored on next launch.

4. Conversation transcript persistence

Each time the TUI renders a message to #conversation, append it to the SQLite session_messages table via TuiSessionPersistence.save_message(). On session resume, reload the transcript and populate SessionView.transcript.

5. Migration

Ensure the existing SessionModel / SessionMessageModel tables are created by the migration runner on first launch (they already exist in the schema; verify migration_runner.py covers them).

6. Tests

  • Unit tests (features/tui/session_persistence/): bootstrap, save_message, restore_transcript, list_sessions, delete_session.
  • Integration tests (robot/tui/): restart TUI and verify transcript is restored from SQLite.

Acceptance Criteria

  • TuiSessionPersistence class exists in src/cleveragents/tui/session_persistence.py and is fully typed.
  • TUI startup creates or restores a SQLite-backed session.
  • Conversation messages are appended to session_messages in SQLite.
  • session:list returns sessions from SQLite, not just in-memory state.
  • Restarting the TUI restores the last active session and its transcript.
  • All quality gates pass (nox -e lint typecheck unit_tests integration_tests).

Metadata

  • Commit Message: feat(tui): implement SQLite persistence for TUI session state
  • Branch: feat/tui-sqlite-session-persistence
  • Type: Feature

Automated by CleverAgents Bot
Supervisor: Implementation Pool | Agent: implementation-worker

## Summary The TUI's session state (`PersonaState`, `SessionView`) is **entirely in-memory** and is lost on every restart. The infrastructure layer already has a full SQLite/SQLAlchemy stack with `SessionModel`, `SessionMessageModel`, and `session_service.py`, but the TUI is completely disconnected from it. The `PersonaState` uses a plain `dict[str, str]` for `active_by_session` and `preset_by_session` with no persistence beyond YAML-backed persona files. ## Confirmed Gap (code evidence) ### `src/cleveragents/tui/app.py` — in-memory `SessionView`, no persistence ```python @dataclass(slots=True) class SessionView: """Minimal per-session TUI view model.""" session_id: str transcript: list[str] # No created_at, no SQLite-backed ID, no persistence ``` ### `src/cleveragents/tui/persona/state.py` — in-memory dicts only ```python @dataclass(slots=True) class PersonaState: registry: PersonaRegistry active_by_session: dict[str, str] = field(default_factory=dict) preset_by_session: dict[str, str] = field(default_factory=dict) # No SQLite backing; lost on restart ``` ### `src/cleveragents/tui/persona/registry.py` — YAML-only state ```python def load_state(self) -> dict[str, Any]: if not self.state_path.exists(): return {} raw = yaml.safe_load(self.state_path.read_text(encoding="utf-8")) or {} # Only persists last_persona; no session list, no transcripts ``` ### Infrastructure layer has SQLite models but TUI does not use them `src/cleveragents/infrastructure/database/models.py` defines `SessionModel` and `SessionMessageModel`. `src/cleveragents/application/services/session_service.py` provides `create()`, `get()`, `delete()`, `export_session()`, `import_session()`. The TUI's `TuiCommandRouter._session_export` and `_session_import` already call `container.session_service()` — but the TUI never calls `session_service.create()` on startup, so the session is never persisted. ## Required Work ### 1. TUI session bootstrap On TUI startup (in `run_tui()`), call `session_service.create()` to persist the default session to SQLite and store the returned `session_id` in `SessionView`. On subsequent launches, restore the last active session from SQLite. ### 2. `TuiSessionPersistence` adapter Add `src/cleveragents/tui/session_persistence.py`: - `TuiSessionPersistence` class wrapping `SessionService`. - `bootstrap_session(session_id: str | None) -> str` — create or restore a session. - `save_message(session_id: str, role: str, content: str) -> None` — append a message to the SQLite transcript. - `restore_transcript(session_id: str) -> list[str]` — load the conversation transcript on resume. - `list_sessions() -> list[dict]` — enumerate persisted sessions for `session:list`. - `delete_session(session_id: str) -> None` — hard-delete from SQLite. ### 3. Wire `PersonaState` active session to SQLite When `PersonaState.set_active_persona()` is called, also update the session record in SQLite (via `TuiSessionPersistence`) so the active persona is restored on next launch. ### 4. Conversation transcript persistence Each time the TUI renders a message to `#conversation`, append it to the SQLite `session_messages` table via `TuiSessionPersistence.save_message()`. On session resume, reload the transcript and populate `SessionView.transcript`. ### 5. Migration Ensure the existing `SessionModel` / `SessionMessageModel` tables are created by the migration runner on first launch (they already exist in the schema; verify `migration_runner.py` covers them). ### 6. Tests - **Unit tests** (`features/tui/session_persistence/`): bootstrap, save_message, restore_transcript, list_sessions, delete_session. - **Integration tests** (`robot/tui/`): restart TUI and verify transcript is restored from SQLite. ## Acceptance Criteria - [ ] `TuiSessionPersistence` class exists in `src/cleveragents/tui/session_persistence.py` and is fully typed. - [ ] TUI startup creates or restores a SQLite-backed session. - [ ] Conversation messages are appended to `session_messages` in SQLite. - [ ] `session:list` returns sessions from SQLite, not just in-memory state. - [ ] Restarting the TUI restores the last active session and its transcript. - [ ] All quality gates pass (`nox -e lint typecheck unit_tests integration_tests`). ## Metadata - **Commit Message**: `feat(tui): implement SQLite persistence for TUI session state` - **Branch**: `feat/tui-sqlite-session-persistence` - **Type**: Feature --- **Automated by CleverAgents Bot** Supervisor: Implementation Pool | Agent: implementation-worker
Author
Owner

Triage Decision [AUTO-OWNR-7]

Verified

SQLite persistence for TUI session state is explicitly listed in the v3.7.0 milestone acceptance criteria: "Session persistence (SQLite at ~/.local/state/cleveragents/tui.db)". This is a Must Have for the TUI milestone.

  • Type: Feature
  • MoSCoW: Must Have — explicitly in v3.7.0 milestone acceptance criteria
  • Priority: High — required for TUI milestone completion
  • Milestone: v3.7.0

Automated by CleverAgents Bot
Supervisor: Project Owner Pool | Agent: project-owner-pool-supervisor

## Triage Decision [AUTO-OWNR-7] **Verified** ✅ SQLite persistence for TUI session state is explicitly listed in the v3.7.0 milestone acceptance criteria: "Session persistence (SQLite at ~/.local/state/cleveragents/tui.db)". This is a Must Have for the TUI milestone. - **Type:** Feature - **MoSCoW:** Must Have — explicitly in v3.7.0 milestone acceptance criteria - **Priority:** High — required for TUI milestone completion - **Milestone:** v3.7.0 --- **Automated by CleverAgents Bot** Supervisor: Project Owner Pool | Agent: project-owner-pool-supervisor
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#8702
No description provided.