Repository navigation
feat(db): add study_session and study_turn persistence model - #312
Open
longobucco wants to merge 1 commit into
Open
longobucco wants to merge 1 commit into
longobucco wants to merge 1 commit into
Conversation
Add the data-model layer for server-side study session persistence (study_session, study_turn tables + Alembic migration + repository + service) following the existing function-based repository pattern. Citations are stored as resolvable anchors (doc/page/slide/chunk_id), not the lossy HTTP-facing DTO. No API or dispatch-path integration yet — that lands in follow-up PRs per the design doc. Refs #136
lucaosti
requested changes
Nov 10, 2026
lucaosti
left a comment
Collaborator
There was a problem hiding this comment.
Thanks Luca, splitting #136 into small PRs is the right call, and the design decisions (discrete sessions, anchor-only citations, cascading turns) make sense to me. Since this layer is what the next four PRs build on, a few things are worth settling now:
append_turndoesn't take auser_id, so it can add a turn to any user's session. With a made-upsession_idit creates an orphan row on SQLite and would be an IntegrityError on Postgres. Could it takeuser_id, check ownership, and return None likeget_sessiondoes?- Timestamps: the repo writes naive local time (
datetime.now()), while the model'sonupdate=func.now()writes UTC in another format. Renaming a session currently pushes it down the list. Usingdatetime.now(timezone.utc).isoformat()everywhere, as #311 does, and dropping thefunc.now()default and onupdate would fix it. - Indexes: could you declare
(user_id, course_id, last_active_at)onstudy_sessionand(session_id, created_at)onstudy_turn, in both the model and the migration? The parity test will still pass. - SQLite doesn't enforce
ON DELETE CASCADEhere because foreign keys aren't turned on. The ORM cascade covers it today, but a comment or a test that says so would help. Course deletion doesn't remove sessions either; fine to decide in PR2/PR3. - As we asked on #309, please move the design doc out of
docs/superpowers/, for example todocs/design/, and keep only the decisions rather than the step-by-step plan. - Migration numbering: #311 also adds
0009on top of0008. Whichever lands second becomes0010withdown_revision = "0009".
Smaller: list_sessions runs a separate count query per session, and a stored citation without doc_id makes get_session raise, which will matter for the localStorage import in PR4.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed and why
First of five PRs implementing #136 (server-side persistence of study sessions,
replacing browser
localStorage). This PR adds only the data-model layer:study_session/study_turnSQLAlchemy models (app/db/models.py)0009_study_sessions.pyapp/repositories/study_session_repo.py),following the existing
progress_repo.pypattern — no class-basedunit-of-work exists in this codebase, so this matches what's actually used.
app/services/study_session_service.py) converting ORMrows to Pydantic DTOs (
app/schemas/study_session_schemas.py), scoped byuser_idso cross-user access returnsNonerather than leaking existence.No API endpoints and no dispatch-path hook yet — those are PR2 and PR3. This
PR only lays the DB layer so each later PR stays small and reviewable.
Design decisions (made and documented in
docs/superpowers/specs/2026-11-06-study-session-persistence-design.md):per course) — needed for "session list + resume + delete" from the issue.
doc_id,doc_name,section,page/slide,chunk_id,chunk_type) plus a short snippet —not the full evidence-chunk text, to bound storage growth given the
no-automatic-expiry decision.
study_turncascades onstudy_sessiondeletion (ON DELETE CASCADE).session_eventsketchedin
docs/agent-memory-plan.mdPhase 4 — PR5 will add a note therereconciling the two instead of leaving them as parallel designs.
How it was verified
New unit tests in
tests/unit/test_study_session_service.pycover: sessioncreation, per-user/per-course scoping, turn append + citation round-trip,
cross-user access returning
None, and cascade delete of turns.tests/integration/test_migrations.py::test_alembic_head_matches_models(schema parity between
alembic upgrade headandBase.metadata.create_all)passes — the migration was adjusted to not add indexes the models don't
declare.
Things reviewers should watch for
(no new endpoints, no behavior change for existing users).
study_turn.citations_jsonis a JSON text column rather than a normalizedtable, matching how
course_documentand other models already storevariable-shaped JSON in this codebase.
hook + feedback linkage, frontend, retention/docs) is in the linked design
doc.
Refs #136