Repository navigation
General optimization sweep — reduce memory use and improve app-wide performance #103
Description
Activity
This is the current performance optimization sweep PR. It addresses the first high-confidence batch from the research pass: streamed transcript/history paths, bounded telemetry overhead, reduced renderer layout churn, cached worktree probes/summaries, AI Workspace registry churn reduction, orchestration wait polling reduction, and feed render projection splitting.
PR #216 has been merged with a merge commit: #216
This partially resolves the general optimization sweep by landing the first high-confidence batch: streamed transcript/history paths, bounded telemetry and debug-log overhead, reduced renderer layout churn, cached worktree probes/summaries, AI Workspace registry churn reduction, orchestration wait polling reduction, and feed render projection splitting.
Leaving this issue open for the remaining broader performance follow-ups.
Further optimization audit at
5d641845(2026-09-05). Read README, AGENTS.md, agent-code-conventions, rendering ownership architecture and relevant subsystem comments. No application code/branches/builds changed, no live app restart/profile/agent control, and no private transcript data used. Existing modified package-lock.json was preserved. B7's #795 implementation was not touched.New prioritized, reproducible findings
- bug(subagents): overlapping refreshes fold child transcript records repeatedly #802 — correctness first: overlapping subagent refreshes fold the same bytes repeatedly. Actual Codex public parent-entry bursts produced up to 50 turns/emissions from one child response; Claude's tick seam similarly duplicated one tool call. Serialize refreshes with a trailing dirty pass, retaining late completion/appends and stop/partial-line semantics. Exact duplication count is scheduling-dependent. This is extra I/O/CPU and wrong UI counts, not a hypothesis about a slow render.
- perf(subagents): bound repeated full-tree discovery for missing Codex rollouts #803 — bound missing-child path discovery. One unresolved Codex child across 1,000 empty synthetic rollout files caused 1,010 stats per scan; a 10-record parent burst caused 10,100 stats. Three later sequential refreshes still caused 3,030 stats. Share discovery, cache misses with retry/invalidation, and use directory-entry types. Tradeoff: freshness latency must stay bounded; serializing bug(subagents): overlapping refreshes fold child transcript records repeatedly #802 alone is insufficient.
- bug(remote): bound outgoing queues when paired clients stop draining #804 — cap remote socket queues. Real isolated loopback WebSockets plus the unchanged production broadcast method retained 33,015,411 queued bytes after 512 synthetic frames to a paused client; readyState remained OPEN. Add a shared byte budget/overflow-resync policy to broadcast and direct replies. Preserve transcript order; snapshot coalescing alone cannot make lossless event queues bounded.
- perf(remote): bound live transcript retention for unviewed sessions #805 — bound remote transcript retention. The real store/Claude mapper retained all 4,096 unique entries, 32 MiB of text, 4,096 tool-index entries and seen UUIDs for a session with zero view subscriptions. Introduce bounded live windows/index eviction or interest-based backfill. Tradeoff: scroll/history/semantic ownership and duplicate suppression must survive eviction.
Issue bodies contain pinned file references, synthetic reproduction procedures, measurements, expected behavior and acceptance criteria. Existing open and closed issues were searched with gh before creating each separate report. The reproduction suite was repeated: duplicate-fold evidence, filesystem-operation counts, growing socket queues and retained collection cardinalities all reproduced.
A5 context: current status, not a second copy of the old findings
- Worktree no-op replay remains in source. LiveWorktreeReconciler.observe calls rebuild even when no newly relevant evidence was extracted; successful TTL-expired catalog refreshes notify unconditionally, and rebuild folds the retained evidence window. Prefer catalog semantic equality and an unchanged-evidence fast path; preserve hydration/catalog-race repair and time-based context rules. Additional no-op index persistence evidence and a corrected diagnostic-gate status were added to perf(diagnostics): default-off and off-thread the always-on diagnostic subsystems #767.
- Broad React subscriptions remain: useWorkspace still selects the complete runtime map and returns the broad workspace surface. Continue perf(renderer): subscribe panes per session and add memo boundaries instead of re-rendering the whole tree per frame #763. The settings-store and closed-modal fixes perf(app-state): avoid settings serialization and subscriber notifications on unrelated workspace updates #781/perf(renderer): stop closed workspace modals scanning and sorting every agent #782 are present and closed.
- Terminal/screen work is partly improved: perf(screen): session:screen frames are 50% duplicate strings and 99% spinner-only diffs #746/perf(headless): skip derived screen work for spinner-only repaints inside the headless packages #765 are closed and current source has the manager spinner gate plus headless spinner-only snapshot handling. Keyed PTY routing and shared WebGL lifecycle are present (
workspace/terminal/sessionDataDispatcher.ts,xtermWebglRenderer.ts; perf(terminal): one agent-pty-data dispatcher per window and the WebGL renderer for xterm hosts #768 remains open for broader ownership work). Screen interest perf(ipc): gate session:screen delivery on renderer interest and split the picker onto its own channel #762, persistent terminal ownership perf(terminal): own xterm instances outside the React tree (per-session terminal registry) #760, and serialized attach snapshots perf(terminal): serialise the current screen for pane attach instead of replaying 512 KiB of raw PTY output #766 remain separate open opportunities. Do not treat the older per-host global listener or all-spinner full-derivation claims as current unchanged behavior. - Rendering diagnostics are mixed: perf(feed-debug): persistence appends on every runtimes replacement instead of batching by time #748's persistence pacing is present; the renderer memory sampler now gates before work when telemetry is off. Broader optional diagnostic overhead remains perf(diagnostics): default-off and off-thread the always-on diagnostic subsystems #767. No old CPU percentages or private-run measurements were re-attributed to this revision.
Measurement limits and next investigation
Confirmed evidence here is source control flow, collection sizes, transport queue bytes, and actual synthetic filesystem calls. Logical text bytes and socket bufferedAmount are not V8 heap/RSS. One warm directory-scan sample was 13 ms/15 ms CPU for one record and 45 ms/116 ms CPU for ten; timing varies with machine load and is secondary to call counts.
Unmeasured hypotheses: the contribution of each finding to live multi-agent stalls, phone tab termination, total CPU, and FPS. Do not promise percentage gains or identify these as causes of past OOMs from this evidence. After focused fixes, compare the same synthetic workloads, then collect a consented/non-disruptive app baseline (main event-loop latency, per-session scan counts, renderer commit count, bytes/queues) across idle/streaming/resume states. Larger process-isolation work #764/#769 should be prioritized with that remaining workload evidence; moving duplicate work off-main does not remove it.
- addedtype:perfPerformance or resource usePerformance or resource useneeds-ownerOwner decision neededOwner decision needed
on Sep 25, 2026 Closing: an umbrella whose work now lives in focused issues (triage 2026-09-27, main
a70403ef).This catch-all has no acceptance criteria that could ever be met.
- Delivered: many performance PRs merged under it, including Performance optimization sweep #216, perf(provider-switch): stop the compaction wait from pinning the conversation #721–perf(workspace): keep terminals alive across Reader, Spotlight and Settings #757, perf(terminal): reduce repaint and inline interaction overhead #783, perf(renderer): remove redundant shared-store and hidden modal work #785 and fix(codex): bound the proxy mirror on disk and take it off the main process's synchronous path #1332. The four findings from the 2026-09-05 comment (bug(subagents): overlapping refreshes fold child transcript records repeatedly #802–perf(remote): bound live transcript retention for unviewed sessions #805) are closed, as are perf(ipc): gate session:screen delivery on renderer interest and split the picker onto its own channel #762, perf(renderer): subscribe panes per session and add memo boundaries instead of re-rendering the whole tree per frame #763 and perf(terminal): one agent-pty-data dispatcher per window and the WebGL renderer for xterm hosts #768.
- Still open, each with its own issue: perf(terminal): own xterm instances outside the React tree (per-session terminal registry) #760 (xterm outside React), perf(sessions): move the session host (node-pty, headless terminals, parsers, transcript tailing) to a utilityProcess #764 (session host in a utilityProcess), perf(terminal): serialise the current screen for pane attach instead of replaying 512 KiB of raw PTY output #766 (screen snapshot on attach), perf(sessions): run session index and history loads off the main thread #769 (history loads off main) and perf(diagnostics): move the session recorder and feed-debug sinks off the main process #1346 (diagnostic sinks off main).
Reopen when: never as an umbrella. New performance findings get their own issue.
Use case
Agent Code now runs many long-lived agent sessions, transcript tails, semantic streams, debug buffers, terminal PTYs, renderer panes, Monaco instances, and performance instrumentation at the same time. The app should get a dedicated optimization sweep focused on keeping memory growth, CPU churn, IPC volume, and renderer re-render cost under control as real users keep 5-20+ agents open.
This is not a single known regression. It is a cross-cutting hardening pass: look for places where the app holds too much state, recomputes too often, ships too much data across IPC, or renders more than the visible UI needs.
Goals
Initial audit areas
Main process
src/main/sessionManager.ts— per-session buffers (terminalBuffers,agentPtyBuffers), listener cleanup, session lifecycle maps, attach/replay behavior.src/main/sessions/historyLoader.tsand transcript loading paths — avoid whole-file reads where streaming/tail counts are enough.src/main/sessions/jsonlCoalescer.ts,src/shared/runtime/streamJsonl.ts,jsonlTailerpaths — check backpressure, batching, and duplicate parsing.src/main/worktreeActivity/*andsrc/main/sessionIndex.ts— scanner/index cost and whether it repeats work unnecessarily.src/main/performance/*— ensure watchdog/perf logging stays bounded and env-gated work is actually off when disabled.ghostJournal,dictationJournal,pasteDebugJournal, feed debug logs) — verify retention, batching, and in-memory queues are bounded.Renderer
src/renderer/src/workspace/mergedEntries.ts,workspace/ghosts.ts, semantic reducers — preserve reference stability and avoid repeated allocation on no-op ticks.src/renderer/src/features/feed/ui/*— feed row memoization, markdown rendering, code block/Monaco cost, table rendering, streaming turn updates.src/renderer/src/workspace/hook/ipc/useIpcSubscriptions.ts— subscription fanout, per-event state writes, batching opportunities.src/renderer/src/workspace/workspaceStore.tsand command/selectors — avoid broad store updates that re-render the whole app.DebugPanel,FeedDebugPanel,HtmlDebugPanel,ProxyDebugPanel, future DevDebugPanel) — ensure disabled panels do not subscribe/process heavy streams.Provider/headless packages
packages/claude-code-headlessandpackages/codex-headless— screen snapshot cadence, semantic event volume, proxy adapter buffering, condition evaluation cost, transcript parser allocations.packages/agent-transcript-parser— conversion/ghost merge paths over large transcripts.Measurement plan
Possible fixes to investigate
Relationship to existing issues
docs/design/perf-and-watchdog.mdonce the current performance architecture is clarified.Non-goals