fix(chat): audit fixes — retention, rag_project_id, cleanup scheduler, tool rounds

- cleanup_old_conversations now excludes briefing conversations (was
  silently deleting briefing history after the retention window)
- list_conversations response now includes rag_project_id, matching the
  shape returned by the single-conversation GET endpoint
- create_conversation_from_article: removed duplicate async_session import
  (_session2 was a copy of the same import); consolidated into one
- MAX_TOOL_ROUNDS fixed from 5→6 to match the actual range(6) loop;
  loop updated to range(MAX_TOOL_ROUNDS) so the constant is accurate
- Chat retention cleanup moved from per-request (every GET /conversations)
  to a daily scheduled job in event_scheduler.py; route no longer runs
  a DB write on every read

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 12:30:41 -04:00
parent edfed6b5bb
commit 738245af5c
4 changed files with 59 additions and 19 deletions
@@ -1,7 +1,8 @@
"""Scheduler jobs for calendar events.
"""Scheduler jobs for background maintenance tasks.
- Reminder notifications: checks every 5 minutes for due reminders.
- Reminder notifications: checks every 5 minutes for due event reminders.
- CalDAV pull sync: runs every hour for all users with CalDAV configured.
- Chat retention cleanup: runs daily, deleting old conversations per user setting.
Uses the same BackgroundScheduler pattern as briefing_scheduler.py.
"""
@@ -97,6 +98,44 @@ def _run_caldav_sync_threadsafe(loop: asyncio.AbstractEventLoop) -> None:
asyncio.run_coroutine_threadsafe(_run_caldav_sync(), loop)
# ---------------------------------------------------------------------------
# Chat retention cleanup job
# ---------------------------------------------------------------------------
async def _run_chat_retention_cleanup() -> None:
"""Delete old conversations for all users according to their retention setting."""
from sqlalchemy import select as sa_select # noqa: PLC0415
from fabledassistant.models.user import User # noqa: PLC0415
from fabledassistant.services.chat import cleanup_old_conversations # noqa: PLC0415
from fabledassistant.services.settings import get_setting # noqa: PLC0415
async with async_session() as session:
result = await session.execute(sa_select(User.id))
user_ids = [row[0] for row in result.all()]
total_deleted = 0
for user_id in user_ids:
try:
retention_str = await get_setting(user_id, "chat_retention_days", "90")
try:
retention_days = int(retention_str)
except (ValueError, TypeError):
retention_days = 90
if retention_days > 0:
deleted = await cleanup_old_conversations(user_id, retention_days)
total_deleted += deleted
except Exception:
logger.warning("Chat retention cleanup failed for user %d", user_id, exc_info=True)
if total_deleted:
logger.info("Chat retention cleanup: deleted %d conversation(s)", total_deleted)
def _run_chat_retention_threadsafe(loop: asyncio.AbstractEventLoop) -> None:
asyncio.run_coroutine_threadsafe(_run_chat_retention_cleanup(), loop)
# ---------------------------------------------------------------------------
# Lifecycle
# ---------------------------------------------------------------------------
@@ -126,8 +165,17 @@ def start_event_scheduler(loop: asyncio.AbstractEventLoop) -> None:
replace_existing=True,
)
# Chat retention cleanup once per day
_scheduler.add_job(
_run_chat_retention_threadsafe,
trigger=IntervalTrigger(hours=24),
args=[loop],
id="chat_retention_cleanup",
replace_existing=True,
)
_scheduler.start()
logger.info("Event scheduler started (reminders every 5m, CalDAV sync every 1h)")
logger.info("Event scheduler started (reminders every 5m, CalDAV sync every 1h, chat cleanup every 24h)")
def stop_event_scheduler() -> None: