Skip to content

P2: threads:changed regrew to 4.6 MB per push (1,093 archived rows, hypotheses and suggestions for every thread) #1385

Closed

Description

@currentbits

Follow-up to #617 (closed). The main-side listThreads cache landed, but the payload itself grew with the store.

Measured (production listThreads against a copy of the live store, 2026-09-29)

  • 1,829 rows, 4.59 MB JSON / 4.52 MB v8 structured clone per push
  • 1,093 of those rows (60%) are archived threads
  • Fattest fields summed over all rows: hypotheses 1.13 MB, suggestions 1.0 MB, sandbox 216 KB. These ride on every row, although the sidebar only needs them for the open thread.
  • Main-side cost is small (decorate 4 ms, serialize 3 ms). The cost is IPC plus the renderer's deserialize and reconcileThreadList of 4.6 MB each time.

Frequency

stampLastEvent rewrites lastEventAt every ~5 s per active thread (runner.js stampLastEvent). That replaces the threads array, which invalidates the cache, so the next pushThreadsChanged ships the full list. With 5 threads working that's ~1 full push/s (~4.5 MB/s). Sampled renderer CPU at 7–17% while 5 threads streamed.

Fix direction

  • Drop hypotheses and suggestions (and probably sandbox) from list rows; the detail view already loads per thread.
  • Exclude archived rows from the live push and fetch them when the Archived view opens, or send row deltas keyed by id.
  • Optionally keep lastEventAt off the list-invalidating path (watchdog-only field).

Activity

  1. added and removed on Sep 29, 2026
  2. currentbits commented on Sep 29, 2026

    @currentbits
    OwnerAuthor

    First cut is in #1389 (merged, 3988ab5): list rows drop hypotheses and suggestions, taking the push from 4.59 MB to 2.44 MB on the live-store copy.

    Still open here:

    • Exclude the 1,093 archived rows from the live push; this needs a decision on how the Archived view loads.
    • Keep lastEventAt stamps from invalidating the list cache every 5 s per active thread.
  3. currentbits commented on Sep 29, 2026

    @currentbits
    OwnerAuthor

    Correction: the frequency claim in this issue is wrong. I measured it with a real createRunner, a fake Claude streaming text deltas and tool calls, and 300 ballast threads. The runner sends threads:changed 3 times per turn (both a 1.3 s and a 12.7 s turn), and none of those pushes repeat an identical array. stampLastEvent updates the store but does not trigger a push; pushes happen at turn state transitions and on UI/IPC actions.

    So the cost is a few pushes per turn times the payload size. #1389 already cut the payload by 47%. The remaining item, excluding archived rows, is lower priority than the original "~4.5 MB/s" framing suggested, and deduping identical pushes would save nothing on the runner path. The renderer CPU I sampled during streaming is more likely detail pushes than list pushes.

  4. added and removed on Oct 9, 2026
  5. currentbits commented on Oct 9, 2026

    @currentbits
    OwnerAuthor

    Fixed: fields dropped (3988ab5, #1389) and pushes moved to row patches (8e77940). Archived rows still ride the full base push; reopen if that measures large again. Closing in the 2026-10-09 board cleanup (#1532).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions