Skip to content

perf(sessions): persist conversation build caches across task re-opens - #76788

Open
arnohillen wants to merge 6 commits into
masterfrom
posthog-code/persist-conversation-derived-cache
Open

perf(sessions): persist conversation build caches across task re-opens#76788
arnohillen wants to merge 6 commits into
masterfrom
posthog-code/persist-conversation-derived-cache

Conversation

@arnohillen

Copy link
Copy Markdown
Contributor

Ports PostHog/code#3976 onto products/desktop/ after the desktop import (the code repo's main is frozen). Original commits and authorship are preserved via git am.

Problem

Clicking a task in the sidebar takes seconds before the transcript renders, even when the session data is already warm in the store. The task detail route fully unmounts on navigation, and the incremental conversation builder (useConversationItems) and thread grouper both live in component-scoped refs, so every re-open falls back to a full O(n) re-parse of the transcript on the main thread. yieldToPaint already documents the mount blocking "for hundreds of ms"; with multi-MB transcripts it is seconds.

Changes

  • New conversationDerivedCache module: a small LRU cache (8 tasks per scope, backed by lru-cache, already a workspace dependency via harness) keyed per call site + task that keeps the incremental builder state and thread grouper alive across mounts.
  • useConversationItems accepts an optional persist key; without one, behavior is unchanged (per-component ref).
  • ConversationView and AcpChatThread opt in with distinct scopes; ChatThreadFooter now receives footerState and usage from AcpChatThread instead of running its own duplicate parse.
  • Entries are swept when the session residency system evicts a session's events (or the session is torn down), so the memory reclaim that eviction exists for still works. Entries for tasks that never had a session in the store (e.g. archive surfaces) are exempt from sweeping and reclaim via a 30 minute TTL instead.

Warm re-opens (session still in the store) now reuse already-built items: idle sessions return the memoized result identity, and streaming sessions take the append-only fast path because the store keeps appending while the view is unmounted and immer preserves element identity.

Compared to the source PR, the only monorepo-specific change is the lockfile: instead of carrying the source pnpm-lock.yaml hunk blindly, the products/desktop/pnpm-lock.yaml importer entry was regenerated for the new packages/ui dependency (same resolution, lru-cache@11.2.5).

How did you test this code?

  • Unit tests for the cache module: entry persistence, scope isolation, LRU bound, eviction sweep on evictEvents/removeSession, and the no-session exemption (guards rebuild churn on archive surfaces).
  • Hook tests: identical result identity across remount with a persist key (idle), completed-turn item reuse across remount while streaming, and unchanged rebuild behavior without a persist key.
  • Verified in the monorepo from products/desktop/: pnpm install --frozen-lockfile, pnpm typecheck (24/24 packages), and the full @posthog/ui vitest suite (305 files, 2519 tests passing).

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

Not applicable (desktop app internals), skip-inkeep-docs label applied.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

This PR is a port of PostHog/code#3976, recreated here by PostHog Code following the /porting-code-prs skill: patch series applied with git am --directory=products/desktop/ (original authorship and commit messages preserved), source lockfile hunk excluded and re-derived against the monorepo lockfile instead. Part 1 of 3 ported PRs on task re-open latency.


Created with PostHog Code

Re-opening a task unmounts and remounts the whole detail view, so the
incremental conversation builder and thread grouper (both component-scoped)
were rebuilt from scratch on every click, re-parsing the full transcript on
the main thread. For large sessions this blocks for seconds.

Keep both in a module-level LRU cache keyed per call site and task, so a
re-open reuses the already-built items. Entries are dropped when the
residency system evicts a session's events, so memory reclaim still works.

Generated-By: PostHog Code
Task-Id: 5d24ea17-aec0-4334-884e-c2639867e260
The cap counted scope+task entries in one map, so the three opted-in
scopes consumed three slots per task and only ~2 tasks stayed fully
cached instead of 8. Group entries per scope and cap within each scope.

Generated-By: PostHog Code
Task-Id: 5d24ea17-aec0-4334-884e-c2639867e260
lru-cache v11 is already a workspace dependency (harness), so this swaps
the hand-rolled Map re-insertion LRU for the battle-tested implementation
with identical semantics: get() refreshes recency, max evicts per scope.

Generated-By: PostHog Code
Task-Id: 5d24ea17-aec0-4334-884e-c2639867e260
- Sweep entries by the session run they were built from, latched only
  once that run's events are store-resident: a re-run's residency no
  longer keeps a stale entry alive, and sessions with no resident events
  no longer cause sweep churn
- Pin the cache entry for the mounted lifetime so LRU eviction never
  forces a still-mounted view (e.g. a 3x3 command-center grid, one over
  the cap) onto a fresh builder every render
- Stop persisting ChatThreadFooter's duplicate full parse: AcpChatThread
  now passes footerState and usage down exactly like ChatThread, halving
  per-task derived retention and removing the chat-thread-footer scope
- Merge the two cache registries into one entry per scope+task,
  subscribe at module scope, skip sweeps when the sessions slice is
  unchanged, and add a 30 minute TTL so sessionless (archive) entries
  reclaim on their own
- Type the scope as a closed union and accept an optional taskId in the
  persist key so call sites drop the ternaries

Generated-By: PostHog Code
Task-Id: 47cdc119-1c24-42ec-ac36-74c43cdd7f4e
Not run in CI (test globs only match *.test.*). Numbers land in the PR
description; rerun with pnpm vitest bench in packages/ui.

Generated-By: PostHog Code
Task-Id: 47cdc119-1c24-42ec-ac36-74c43cdd7f4e
Regenerated for the new packages/ui dependency; same resolution the
source PR locked (11.2.5).

Generated-By: PostHog Code
Task-Id: 96e133e1-f570-432f-b8ff-bce4202f5b7f
@arnohillen arnohillen added the skip-inkeep-docs Use this label to skip an Inkeep docs PR in posthog.com label Aug 3, 2026
@arnohillen arnohillen self-assigned this Aug 3, 2026
@arnohillen arnohillen added the skip-inkeep-docs Use this label to skip an Inkeep docs PR in posthog.com label Aug 3, 2026
@trunk-io

trunk-io Bot commented Aug 3, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "chore(sessions): add lru-cache to the ui..." | Re-trigger Greptile

@trunk-io

trunk-io Bot commented Aug 3, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

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

Labels

skip-inkeep-docs Use this label to skip an Inkeep docs PR in posthog.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant