#64897·zed

Every thread save triggers a full `threads.db` metadata reload (multi-MB overflow reads, no rate floor)

Author: ihivaRitikCreated Sep 28, 2026Updated Sep 28, 2026
Labelsstate:needs triage

Summary

run_save_worker (crates/agent/src/agent.rs) calls ThreadStore::reload() after every successful thread save. That reload runs ThreadsDatabase::list_threads() (crates/agent/src/db.rs), a metadata-only SELECT — but the metadata columns sit after the multi-MB data blob in each row, so SQLite walks through thread overflow pages to read them: ~142 MB read per call against a ~145 MB store.

There is also no rate floor between saves (the worker rewrites back-to-back for as long as observes keep firing), so in a long session we measured ~1.1M pread64 calls in 30 s (~150 MB/s) from the worker threads, ~7.6 TB cumulative reads and ~39.5 GB written over one session.

Version notes

Measured on 1.20.1; the code path is unchanged on main today (run_save_worker → reload → list_threads), so we believe it still applies.

Possible fixes

  • Upsert the saved row into the in-memory store instead of reloading all metadata — the save worker already knows exactly what changed. (The open PR #64834 touches image-heavy save memory, but not this reload path.)
  • Or floor/batch the reload (one reload after a quiet period instead of one per save).
  • Longer term: move the blob into a side table so metadata scans never touch it.

We run a fork-local save-rate floor as an experiment; it takes the store's read traffic from ~453 MB/min to ~9 MB/min in the same workload. Happy to open a PR for the upsert or the floor if either is wanted.