Skip to content

General optimization sweep — reduce memory use and improve app-wide performance #103

Description

@Juliusolsson05

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

  • Lower idle memory footprint with multiple sessions open.
  • Reduce memory growth during long-running sessions and large transcript resumes.
  • Reduce main-process CPU churn from tailers, screen parsing, condition evaluation, debug writers, and perf collection.
  • Reduce renderer churn from workspace selectors, feed rows, markdown/code rendering, semantic streaming, and modal/panel subscriptions.
  • Make heavy debug/perf features cheap when disabled and bounded when enabled.
  • Add measurement points where needed so future optimization work is evidence-driven.

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.ts and transcript loading paths — avoid whole-file reads where streaming/tail counts are enough.
  • src/main/sessions/jsonlCoalescer.ts, src/shared/runtime/streamJsonl.ts, jsonlTailer paths — check backpressure, batching, and duplicate parsing.
  • src/main/worktreeActivity/* and src/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.
  • Debug journals (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.ts and command/selectors — avoid broad store updates that re-render the whole app.
  • Debug panels (DebugPanel, FeedDebugPanel, HtmlDebugPanel, ProxyDebugPanel, future DevDebugPanel) — ensure disabled panels do not subscribe/process heavy streams.

Provider/headless packages

  • packages/claude-code-headless and packages/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

  1. Define a reproducible baseline: fresh launch, 1 agent, 5 agents, 20 agents, large resumed transcript, long-running streaming turn, idle after heavy use.
  2. Capture main/renderer heap and CPU from existing performance tools.
  3. Record IPC event volume and largest payload classes during the baseline scenarios.
  4. Identify top memory retainers and hot render paths before changing code.
  5. Land optimizations in small PRs with before/after numbers.

Possible fixes to investigate

  • Replace remaining whole-file transcript reads with streaming/tail/count APIs where possible.
  • Add hard caps or eviction to any per-session debug/runtime buffers that can grow with time.
  • Batch high-frequency semantic/screen/IPC updates before committing renderer state.
  • Split Zustand selectors or state writes so one session tick does not invalidate unrelated panes.
  • Ensure feed rows and markdown/code blocks are memoized by stable identity and do not reparse on unrelated ticks.
  • Lazy-mount heavy debug panels and expensive modals only when visible/enabled.
  • Move derived session/picker indexes to cached main-process indexes rather than recomputing in renderer modals.
  • Audit cleanup paths for killed, buried, detached, switched, and naturally exited sessions so maps/listeners/timers cannot leak.

Relationship to existing issues

Non-goals

  • Do not rewrite the app architecture just to chase theoretical wins.
  • Do not remove useful diagnostics; gate and bound them.
  • Do not optimize without a baseline. Each substantive change should say what scenario it improves and how it was measured.

Activity

  1. Juliusolsson05 commented on May 18, 2026

    @Juliusolsson05
    OwnerAuthor

    Linked PR: #216 #216

    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.

  2. Juliusolsson05 commented on May 18, 2026

    @Juliusolsson05
    OwnerAuthor

    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.

  3. Juliusolsson05 commented on Sep 5, 2026

    @Juliusolsson05
    OwnerAuthor

    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

    1. 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.
    2. 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.
    3. 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.
    4. 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

    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.

  4. Juliusolsson05 commented on Sep 27, 2026

    @Juliusolsson05
    OwnerAuthor

    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.

    Reopen when: never as an umbrella. New performance findings get their own issue.

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

    needs-ownerOwner decision neededtype:perfPerformance or resource use

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions