Reduce archived conversation disk usage with cold storage#4016
Reduce archived conversation disk usage with cold storage#4016Quicksaver wants to merge 36 commits into
Conversation
- Preserve binary SQL values across archive round trips - Keep cold bundles authoritative until attachments restore safely - Bound restore memory and tolerate compatible schema changes
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review This PR introduces a complete cold storage system for archived conversations, including new database schema, compression/restore logic, durable lifecycle management, and UI changes. The scope and complexity of this new feature warrant thorough human review. You can customize Macroscope's approvability policy. Learn more. |
- Serialize archive-tree lifecycle and recheck archived shells - Restore cleanup-pending bundles before unarchive commits - Preserve retry state for writer and filesystem failures
There was a problem hiding this comment.
One Effect service convention issue found in the new ThreadColdStorage service. See the inline comment.
Posted via Macroscope — Effect Service Conventions
- Reference-count archive-tree lock users and waiters - Remove lock entries after the final operation releases them
- Define the service members inline with Context.Service - Use the inferred Service type in the layer and orchestration test
- Match archived attachments by exact persisted ids - Resume cleanup pending manifests without shell rows - Preserve attachment metadata until durable delete cleanup succeeds
- Reuse archive filtering for project rows and navigation - Cover persisted and optimistic archive visibility
- Treat persisted shell data as fast-paint cache only - Resume WebSocket events from freshly loaded snapshots
- Describe cold archive, restoration, and deletion behavior - Record sidebar consistency requirements and development ports
- Document authoritative shell refresh, event replay, and deferred cache writes - Preserve mobile archive timestamp and shell subscription safeguards
- Query existing archive manifest columns - Derive chunk counts from the archive chunk table
- Scope the Effect Vitest extension to beta.78 - Regenerate the lockfile with updated peer resolution
…data-savings # Conflicts: # apps/server/src/server.test.ts # apps/web/src/components/Sidebar.tsx # apps/web/src/hooks/useThreadActions.ts # packages/client-runtime/src/rpc/client.ts # packages/client-runtime/src/state/shell-sync.test.ts # packages/client-runtime/src/state/shell.ts
- Revalidate HTTP shell state with a socket-owned snapshot - Reserve hot archived rows until unarchive succeeds or rolls back - Add focused synchronization and storage regression coverage
- Stop before dispatch when storage cannot restore or reserve rows - Cover the missing-archive rejection without appending an event
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 925af20. Configure here.
- Scope restored reservations to their original archive timestamp - Add coverage for re-archive after finalization failure

Summary
This adds compressed cold storage for archived conversations. Once a thread is archived, its conversation data and attachments are moved out of the hot state database into a dedicated
archive.sqlitebundle, while unarchiving restores the complete thread tree before applying the domain command.The lifecycle is durable and restart-safe: migrations 035/036 queue existing archived and deleted threads, background work resumes interrupted archive/delete jobs, and restoration keeps the cold bundle authoritative until the unarchive command commits successfully. Attachment ownership is preserved from exact persisted IDs and cold-bundle filenames throughout archive, restore, and durable deletion.
Archived thread shells remain consistent across clients without relying on compacted event history: each WebSocket session reconciles an authoritative shell snapshot, then replays live changes without losing archive removals during the handoff.
What Changed
archive.sqlite.cleanup_pendingbundles, and prevented queued archive work or post-commit failures from undoing a successful restore.cleanup_pendingmanifests even when the archived shell row is missing, while preserving exact attachment metadata until durable delete cleanup succeeds.t3-sqlite-stateinspection path for both hotstate.sqlitedata and coldarchive.sqlitemanifests/chunks.Why
With fairly moderate usage, the user
.t3folder grew to more than 30 GB. That is far too much local data growth for normal use, and without lifecycle controls it gets out of hand very quickly. Archived and deleted conversations need to stop accumulating indefinitely in hot storage while remaining reliably recoverable when a user unarchives them.Validation
pnpm exec vp checkpassed with exit 0; it reported 9 pre-existing lint warnings outside the changed files.pnpm exec vp run typecheckpassed with exit 0.pnpm exec vp run lint:mobilepassed with exit 0; optional SwiftLint, ktlint, and detekt checks were skipped because those tools are not installed locally.No threads yet.Proof
No attached visual proof is necessary; the data lifecycle and sidebar interaction are covered by the automated and isolated Playwright validation above.
Note
High Risk
Large change to conversation persistence, deletion, migrations that backfill all archived/deleted threads, and orchestration dispatch around unarchive—data loss or stuck states would be high impact if lifecycle boundaries fail.
Overview
Adds gzip-compressed cold storage in a separate
archive.sqliteso archived threads keep only a lightweight shell instate.sqlitewhile messages, events, attachments, and related projections move into chunked bundles; provider/terminal logs are dropped on archive and are not restored.Server lifecycle is extended with
ThreadColdStorage, migrations 035/036 (manifests, delete queue, legacy compaction), and a background worker that archives onthread.archived, permanently deletes onthread.deleted, and resumes pending work after restart. Unarchive goes through restore → domain command → finalize or rollback so failed commands re-archive and successful commits drop the cold bundle without unrelated unarchives wiping data.Clients hide archived threads optimistically in the sidebar (web) and show in-progress unarchive on web/mobile; shell sync revalidates via a socket-owned snapshot when completion markers are supported so archive removals are not lost after event compaction.
t3-sqlite-stategains--database archivefor inspecting cold manifests/chunks.Reviewed by Cursor Bugbot for commit 6c124a6. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add cold storage archival and deletion lifecycle for conversations
ThreadColdStorageservice andThreadColdArchive/DeletedThreadCleanupQueuemigrations (035/036) that move archived and deleted thread data to a separatearchive.sqlitedatabase, compressing row data with gzip.ThreadDeletionReactorto handlethread.archivedandthread.deletedevents by enqueuing archive, delete, and legacy-compaction jobs; on startup it backfills pending jobs from the cold storage manifest tables.thread.unarchivedispatch inOrchestrationEnginewith restore, rollback, and finalization steps against cold storage so unarchiving is transactional.t3-sqlite-stateCLI with a--database state|archiveflag to inspect either database;archive.sqlitepath is now derived fromServerDerivedPaths.Macroscope summarized 6c124a6.