Background Work Should Be Bounded and Supersedable

Background work should be bounded and supersedable

Background work is not a moral debt. The system does not have to finish every old job just because it started it.

If the work follows moving state, newest truth matters more than old completeness. A queue changed, a playlist refreshed, a sync cursor advanced, a cache entry was rewritten. Finishing the stale job may waste provider budget and make the app feel slower for no user-visible gain.

The rule

Background work needs both a capacity bound and a freshness rule.

The capacity bound keeps it from eating the machine. The freshness rule tells it when to stop caring.

Practical shapes

This is the background-work cousin of Bounded Concurrency First. Bounded concurrency says "do not run all of it at once." Superseding says "do not run old work just because it is still queued."

Spotuify example

When Spotuify gets a fresh playback queue, cache warming should follow that queue snapshot. If another snapshot arrives before warming finishes, the older snapshot should stop being authoritative.

That matters because music queues are alive. The user can skip, reorder, add tracks, or switch context. The daemon should warm what is likely to be useful next, not heroically complete obsolete work from five interactions ago.

The shipped queue warmer uses this shape: finite channel, generation numbers,
small batches, recent-URI dedupe, and best-effort failure handling. It warms
metadata, cover art, lyrics, index rows, and next-track audio preload only while
that work still points at the newest useful queue truth.

Mxr example

In Mxr, the old idea of automatic post-sync summary backfill was wrong for large mailboxes. One sync tick on a 100k-message account can touch far too many threads; spawning a summary job for every changed thread turns "nice to have later" into runtime pressure now.

The current shape is better: no sync-wide summary backfill. Clients request summaries on demand, and the TUI's lazy open-triggered path is debounced so arrowing through the list does not fire an LLM call for every row. If the user leaves before a result returns, the daemon can still cache it, but the TUI does not let that stale result overwrite the current view.

Provider sync needs a slightly different discipline. You cannot simply drop a
sync that is already talking to Gmail, because the cursor and provider state
matter. But you can bound it: put a timeout around provider work, release the
per-account lane, record the failure, and let the next cycle try again. See
Provider Lanes Need Account Scope.

Worth Your Time example

Worth Your Time uses the same freshness rule at two speeds. Mobile onboarding signs each film-suggestion request with the current taste description and genre selection. If the user changes either input before a response arrives, the older response cannot replace the new grid.

Taste-profile generation can run much longer, so it carries the profile version that started the job. A write is accepted only if that version is still current. The old work may finish, but it no longer owns the truth.

See also