Summary
Description
Session state in rag-service/main.py is distributed across three distinct storage layers written by different code paths on different schedules:
| Layer |
File |
Written by |
Frequency |
| Global snapshot |
data/sessions.json |
cleanup_expired_sessions |
On eviction/cleanup |
| Per-session metadata |
data/<session_id>/session_meta.json |
_flush_dirty_sessions background thread |
Every SESSION_FLUSH_INTERVAL_SECONDS |
| Session registry |
data/session_registry.json |
persist_session_registry_entry |
On upload + TTL update |
load_sessions() reads all three layers and applies them in a specific order: sessions.json first, then session_meta.json as an authoritative overlay (chat + flashcards), with last_accessed used as a tiebreaker. None of this is documented anywhere — not in README.md, not in CONTRIBUTING.md, not in docstrings on the functions themselves.
A contributor who adds a new session field (e.g. a notes field) has no documented guidance on:
- Which layer to write it to.
- Whether to add it to
_snapshot_session_for_persistence, save_sessions_unlocked, or persist_session_registry_entry.
- What happens to the field on server restart if the flush interval hasn't fired.
- How the field behaves during session recovery via
_recover_session_unlocked.
This has already produced real inconsistencies: flashcards are stored in session_meta.json but not in session_registry.json; processing_progress is stored in the in-memory sessions dict but not flushed to any file; retrieval_cache is explicitly zeroed on persist but this decision is undocumented.
Affected Files
README.md — no architecture section
CONTRIBUTING.md — no data-layer guidance for contributors
rag-service/main.py — load_sessions, save_sessions_unlocked, _flush_dirty_sessions, _snapshot_session_for_persistence, persist_session_registry_entry, _recover_session_unlocked
Steps to reproduce
N/A
Expected behavior
N/A
Actual behavior
N/A
Additional context
No response
Summary
Description
Session state in
rag-service/main.pyis distributed across three distinct storage layers written by different code paths on different schedules:data/sessions.jsoncleanup_expired_sessionsdata/<session_id>/session_meta.json_flush_dirty_sessionsbackground threadSESSION_FLUSH_INTERVAL_SECONDSdata/session_registry.jsonpersist_session_registry_entryload_sessions()reads all three layers and applies them in a specific order:sessions.jsonfirst, thensession_meta.jsonas an authoritative overlay (chat + flashcards), withlast_accessedused as a tiebreaker. None of this is documented anywhere — not inREADME.md, not inCONTRIBUTING.md, not in docstrings on the functions themselves.A contributor who adds a new session field (e.g. a
notesfield) has no documented guidance on:_snapshot_session_for_persistence,save_sessions_unlocked, orpersist_session_registry_entry._recover_session_unlocked.This has already produced real inconsistencies:
flashcardsare stored insession_meta.jsonbut not insession_registry.json;processing_progressis stored in the in-memorysessionsdict but not flushed to any file;retrieval_cacheis explicitly zeroed on persist but this decision is undocumented.Affected Files
README.md— no architecture sectionCONTRIBUTING.md— no data-layer guidance for contributorsrag-service/main.py—load_sessions,save_sessions_unlocked,_flush_dirty_sessions,_snapshot_session_for_persistence,persist_session_registry_entry,_recover_session_unlockedSteps to reproduce
N/A
Expected behavior
N/A
Actual behavior
N/A
Additional context
No response