Skip to content

LLM-642: conversation retention in the app, with quota accounting and tombstones - #334

Merged
jeffdafoe merged 2 commits into
mainfrom
LLM-642-conversation-retention
Sep 2, 2026
Merged

jeffdafoe merged 2 commits into
mainfrom
LLM-642-conversation-retention

Conversation

@jeffdafoe

Copy link
Copy Markdown
Owner

Conversation notes were retired by raw SQL in scripts/db-cleanup.sh (root cron, 03:15): soft-delete at retention+1 days, hard-delete a window later. That bypassed namespace_usage (home counted 51 MB against 18 MB live), and the hard-delete made the memory-sync diff report every retired session as missing, so the client re-uploaded it as a new row with a new created_at: 359 home / 363 work sessions on 2026-08-21 alone, and three more on the home box on 09-02, sixteen hours after retirement.

Change

  • services/cleanup.js: retireConversations() runs as task 3 of the nightly cleanup cron. Ages a conversation by its session_date (created_at fallback), soft-deletes with deleteNote-style quota accounting, then a window later empties content and chunks but keeps the row as a tombstone (metadata.purged_at). runDecayCleanup now credits the quota too, with a deleted_at IS NULL guard.
  • routes/agent.js: the sync diff counts deleted and tombstoned rows as present (classifySessions); stale only when the file outgrew every size the server holds.
  • services/documents.js: restoreNote refuses a tombstone (410); updateUsage exported.
  • scripts/db-cleanup.sh: conversation steps removed.
  • migrations/MEM-145: re-date re-uploaded conversations to their session date (888 rows), rebuild namespace_usage from live rows, reword the config key. Rehearsed twice on a clone of production: counters land exactly on the live totals; the first nightly run then retires 424 home / 420 work sessions.

Verification: node --test 220/220 (9 new). code_review round 1 findings all applied (safe date parsing, session-date predicate, decay race, honest counts, id case).

Ticket: https://jeffdafoe.atlassian.net/browse/LLM-642

Agent: Home

🤖 Generated with Claude Code

jeffdafoe and others added 2 commits September 2, 2026 16:21
… tombstones

Conversation notes were retired by raw SQL in scripts/db-cleanup.sh (root
cron, 03:15): soft-delete at retention+1 days, hard-delete a window later.
That bypassed namespace_usage, so the quota never drained (home counted
51 MB against 18 MB live), and the hard-delete made the memory-sync diff
report every retired session as missing, so the client re-uploaded it as a
new row with a new created_at — 359 home / 363 work sessions on 2026-08-21
alone, and the same three on this box today, 16 hours after retirement.

- services/cleanup.js: retireConversations() runs as task 3 of the nightly
  cleanup cron. Soft-delete credits the quota like deleteNote; the second
  stage empties content and chunks but keeps the row as a tombstone with a
  purged_at stamp. runDecayCleanup now credits the quota too.
- routes/agent.js: the sync diff counts deleted and tombstoned rows as
  present (classifySessions); stale only when the file outgrew every size
  the server holds.
- services/documents.js: restoreNote refuses a tombstone (410); updateUsage
  exported.
- scripts/db-cleanup.sh: conversation steps removed.
- migrations/MEM-145: re-date re-uploaded conversations to their session
  date, rebuild namespace_usage from live rows, reword the config key.
  Rehearsed on a clone of production: counters land exactly on the live
  totals; the first nightly run then retires 424 home / 420 work sessions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ing, decay race, honest counts

- retireConversations ages a conversation by metadata.session_date (the
  session's own date), falling back to created_at; pg_input_is_valid guards
  the cast, so a malformed client-supplied date skips instead of throwing.
  Same guard in MEM-145's re-dating step.
- runDecayCleanup's UPDATE now requires deleted_at IS NULL, so a note
  deleted between the candidate SELECT and the UPDATE is not credited twice.
- softDeleted reports the UPDATE's row count; chunk failures are counted and
  logged separately instead of shrinking the retired count.
- classifySessions lowercases the client id before lookup; test added.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jeffdafoe
jeffdafoe merged commit 15c74e0 into main Sep 2, 2026
9 checks passed
@jeffdafoe
jeffdafoe deleted the LLM-642-conversation-retention branch September 2, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant