Skip to content

feat(performance): ship local evidence, history, and profiling - #958

Merged
Juliusolsson05 merged 15 commits into
mainfrom
feat/performance-incidents
Sep 13, 2026
Merged

Juliusolsson05 merged 15 commits into
mainfrom
feat/performance-incidents

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Sep 13, 2026 •

Copy link
Copy Markdown
Owner

The existing live dashboard explains current load, but it cannot answer what made an earlier interaction slow or collect a bounded diagnostic artifact. This change completes the local performance system: Agent Code continuously retains bounded metrics and incident evidence, exposes history and profiles in the Performance monitor and command palette, and saves privacy-filtered reports only to a destination the user chooses.

What changed

  • Time startup, session readiness, prompt delivery, first provider/render output, IPC, transcript work, terminal writes, persistence, worktrees, orchestration, and dictation at their real boundaries.
  • Detect main/renderer stalls, long tasks, memory pressure, and slow operations with sleep and visibility exclusions, hysteresis, cooldowns, per-scope captures, and bounded before/after evidence. Dropped monitoring records are reported as coverage (history points and truncated captures), not as incidents.
  • Persist 1-second, 10-second, and 1-minute history tiers across restarts with peak-preserving rollups; cap timeline pages, incident counts, evidence, disk use, and in-memory queues.
  • Add timeline and incident detail views plus commands that open Recordings to save a report or record a trace, with the saved path and Reveal visible.
  • Add single-owner Chromium trace and main CPU profiling, picker-based heap capture, app-owned capture scratch swept at startup, owner-close cancellation, safe file reveal, and bounded shutdown flushing.
  • Add deterministic benchmark/qualification tooling and an Electron helper smoke test.

Reports are local-only. There is no upload or sharing path, and reports exclude prompts, transcript text, paths, audio, environment values, stacks, credentials, and raw run IDs.

Review round 1 resolutions

Two reviewers (correctness and performance) reported 26 findings on fd14a227. All valid findings are fixed in this head:

  • Incident correctness: windows are registered at BrowserWindow creation, so a renderer that never boots is detected. Captures are per scope instead of one global slot. Heartbeat staleness requires both the helper receive age and main's source-clock age, so a transport backlog is not reported as a renderer stall.
  • Shutdown durability: the coordinator drains every queued batch before flushing. The worker accepts the final batch, interrupts open captures, force-records, then closes the open rollup buckets before replying.
  • Clear History also resets in-memory incidents, evidence, and operation histograms, so deleted data is not re-persisted.
  • History store:
    • An in-memory index replaces full rescans, and a coalesced bounded writer replaces the unbounded promise chain.
    • Tier files are compacted only once 25% has expired. Expired files and runs are deleted.
    • A torn JSONL tail is repaired before indexing or appending.
    • Status is derived from the index. Resolution is chosen by data age and coverage.
    • Capturing incidents are repaired in every retained run.
    • Report operation histograms are attributed per whole run.
  • Query lane: a bounded FIFO replaces the single slot. limit is validated. The timeline keeps its last page when a read is delayed. (Round 2 replaced crash-budget refunds for slow queries with query abandonment.)
  • Producer loss: a restarted producer's counter is counted as new loss, and producers flush early once a full batch is waiting. (Round 2 made this exact with per-generation baselines and spaced the flushes.)
  • First-output timing: the timer is armed only by provider acceptance, so a queued prompt is cancelled instead of being timed against the running turn. This covers remote and agent-management prompts too. Renderer cancellation stays local, and the cancel IPC channel is removed.
  • transcript.parse is one observation per read call instead of one per line.
  • Captures: heap snapshots use a native picker with a size estimate and a disk-headroom check on both volumes. Traces, heap snapshots, and reports write their temporary files into app-owned directories instead of beside the user's destination.

Review round 2 resolutions

Two reviewers re-reviewed 43cefe77. Both requested changes, with no blockers. All valid findings are fixed:

  • History safety:
    • A partial startup index can no longer lead retention to delete a week of unindexed history.
    • Each file is indexed independently.
    • A failed append re-arms tail repair.
    • A missing run folder is recreated.
    • Cross-volume moves never truncate an existing destination.
  • Incident accuracy:
    • Captures are keyed by rule and scope.
    • Windows that hang before first show are detected, after a 15 s boot grace.
    • Heartbeats and main samples travel in a priority lane.
    • Silence right after dropped records is not blamed on the renderer.
    • Clock rollback is handled in the engine and the rollups.
    • The first loss count after a helper restart is a baseline.
  • Helper resilience:
    • A slow query is abandoned instead of killing the helper.
    • Shutdown admits only the flush and abandons an in-flight export.
    • Incident details are rebuilt only when their summary changes.
  • Loss accounting: baselines are kept per producer generation, so reloads and late reports each count exactly once.
  • UI:
    • The Timeline never shows a stale page for a different range, and retries while paused.
    • Clear History reports cleared, cancelled, busy, unavailable or failed.
  • First-output timing: starts at Enter for every provider and is settled once by acceptance.

Review round 3 resolutions

Both reviewers confirmed their round-2 findings as resolved on 6db43987 and reported follow-ups. All valid follow-ups are fixed:

  • Stall detection: a missing heartbeat is excused only for loss that can contain heartbeats (priority-lane or failed-batch loss), and only for one staleness interval. Unrelated drops can no longer hide a frozen renderer.
  • Mutating work: report exports and history clears are never abandoned at their deadline. The helper is restarted instead, so the UI never reports an outcome the helper later contradicts. In-memory evidence is reset only after a clear succeeds.
  • Clear History: the UI now distinguishes "nothing was sent" from "sent but unconfirmed".
  • Helper resilience: a scan that never answers restarts the helper instead of stacking stuck reads.
  • Stored history: clock steps backward of up to two buckets merge into the open bucket, and larger steps start a new bucket. Only a missing runs directory counts as having no runs. A run pruned mid-write is not recreated.
  • First-output timing: Codex submits are always settled in main, even when a hidden tile cancelled the local render timer.

Validation

  • tsc -b (node + web projects)
  • Focused suites (performance main/shared/preload, transcript loader, session recovery, Performance Monitor, command palette, workspace commands): 43 files, 371 tests passed.
  • Earlier on this branch: npm run build, npm run smoke:monitoring (three consecutive packaged-helper runs), npm run benchmark:monitoring (100,000 records, 1.458 µs p99, zero drops), npm run qualify:monitoring (all 120 maximum-fleet process generations, zero process-record drops, queue and batch bytes below 2 MiB).

The cold Electron/Node helper measured 67.94–68.06 MiB RSS, below the revised 80 MiB helper target documented in the qualification record. Eight-hour active use, 24-hour disk growth, signed arm64, and Intel measurements remain rollout qualification evidence.

Closes #956
Part of #944

🤖 Generated with Claude Code

https://claude.ai/code/session_01WKUB1nGkTZW1BhtNSXuR6M

Juliusolsson05 and others added 15 commits September 12, 2026 15:47
Address both round-1 reviews of the monitoring system:

- Register windows at creation; per-scope incident captures; loss is
  coverage, not an incident; staleness judged on receive and source clocks.
- Drain all queued batches before the quit flush; worker persists final
  interrupted captures and open rollup buckets; Clear History resets
  in-memory evidence and histograms.
- History store: in-memory index, coalesced bounded writer, peak rollups,
  threshold compaction, expired run deletion, torn-tail repair, age and
  coverage resolution, per-run report operations, app-owned export scratch.
- Bounded query FIFO with refunded query timeouts; validated limits;
  producer loss generations; threshold producer flushes.
- First-output armed by provider acceptance; local-only renderer cancel.
- One transcript.parse observation per read call.
- Heap snapshot picker, app-owned capture scratch swept at startup, and
  palette commands that open Recordings with path and Reveal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKUB1nGkTZW1BhtNSXuR6M
Reading `delivery.acceptance.kind` threw for a provider result without
acceptance, and the surrounding catch turned a successful delivery into a
transport failure. Read acceptance defensively so instrumentation can only
observe the outcome, never change it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKUB1nGkTZW1BhtNSXuR6M
History safety: gate retention on a completed startup index, index each
file independently, re-arm tail repair after a failed append, recreate a
missing run folder, and never truncate a destination on cross-volume moves.

Incident accuracy: key captures by rule and scope, detect windows that hang
before first show (15 s boot grace), deliver heartbeats and main samples in a
priority lane, ignore silence right after dropped records, handle wall-clock
rollback in the engine and rollups, and baseline the first loss count.

Helper resilience: abandon slow queries instead of killing the helper, admit
only the flush during shutdown, abandon an in-flight export at quit, and
rebuild incident details only when their summary changes.

Loss is tracked per producer generation. The Timeline never shows a page for
another range and retries while paused. Clear History reports its outcome.
First-output timing starts at Enter for every provider and is settled once.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKUB1nGkTZW1BhtNSXuR6M
Stall detection now excuses a missing heartbeat only for loss that can hold
heartbeats (priority-lane evictions and failed batches) and only for one
staleness interval, so unrelated drops cannot hide a frozen renderer.

Report exports and history clears are never abandoned at their deadline; the
helper is restarted instead, and in-memory evidence resets only after a clear
succeeds. Clear History distinguishes "not sent" from "sent, unconfirmed".
A scan that never answers restarts the helper instead of stacking stuck reads.

Rollups merge only small backward clock steps, readdir errors other than
ENOENT are no longer treated as "no runs", a run pruned mid-write is never
recreated, and Codex submits are always settled in main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKUB1nGkTZW1BhtNSXuR6M
Shutdown no longer abandons an in-flight export or history clear: the flush
waits for it through the drain window and is skipped if it never replies, so
a quit cannot re-persist cleared history or race an export onto the user's
file. Clear History reports "sent" only once the request was dispatched. A
failed partial clear forces the next snapshot to rewrite surviving incidents
and operations, and current-run incidents are mirrored in memory only after
their write succeeded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKUB1nGkTZW1BhtNSXuR6M
Resolve the single conflict in src/main/index.ts by keeping both additions at
window creation: the app.startup timing observation, then the extension
platform's startup activation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKUB1nGkTZW1BhtNSXuR6M
@Juliusolsson05
Juliusolsson05 merged commit 8434415 into main Sep 13, 2026
2 checks passed
@Juliusolsson05
Juliusolsson05 deleted the feat/performance-incidents branch September 13, 2026 03:11
Juliusolsson05 added a commit that referenced this pull request Sep 20, 2026
Main gained three quit-time owners after this branch was cut, and each
had to be placed in the committed-shutdown composition rather than left
on before-quit, which runs before the renderer's unload vote:

- The extension runtime (#577). Its PAUSE stays on before-quit: it is
  the one preparation that must finish before windows unload, and it is
  reversible (Keep Editing resumes it). Its disposal is a new committed
  stage, `stopExtensions`, which runs beside sessions and workflows —
  extensions run user code — and only AFTER startup settles, because
  startup creates the runtime and an earlier stop would complete against
  nothing and keep that receipt.
- The performance monitor and trace drain (#958), with its 1.8s/2s
  bounded race, folded into the `performance` stage. Main also ran
  monitor/watchdog/performanceService stops in will-quit; those are
  already stages here, so the duplicates are dropped.
- tmux recovery now goes through restoration's canonical envelope
  decoder. A quit committed mid-read must not authorize killing tmux
  sessions, so the startup assertion moved inside the reader, where it
  becomes the decoder's read-failure path and withholds cleanup.

`workflowShutdownPromise`/`Complete` are gone with the before-quit
handler that used them; workflow stop is a committed stage.

The shutdown diagram was rewritten as a flowchart in main's current
convention (accTitle/accDescr/scope, #954) and says what the composition
actually does: the editor decides first and Keep Editing leaves every
service live; only a committed quit stops execution; a failed stop keeps
the app and its process lock. Re-rendered with the pinned toolchain;
every other SVG restored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance monitoring: ship incidents, history, local reports, and profiles

1 participant