1# Performance regression checks
2
3The v2 transport has a focused regression command:
4
5```bash
6vp run test:perf:v2-wire
7```
8
9It exercises the real v2 projection and client reducers. This matters because the older
10built-app benchmark fixtures seed v1 orchestration events; running those fixtures against a v2
11client can produce reassuring timings while missing the active transport path.
12
13The v2 checks pin these invariants:
14
15- Client cold opens target 75 recent timeline rows and about 1 MiB of encoded data. Required
16 control records and the at-least-one-row rule can exceed the byte target; it is not a hard cap.
17- The bounded snapshot does not retain the full duplicate conversation-message table.
18- Older activity is fetched in bounded HTTP pages and merged without disturbing the live scroll
19 window.
20- Clients with confirmed history paging opt into socket snapshot fallbacks that use the same
21 bounded recent-history window and carry the cursor needed to fetch older history. Older clients
22 and WebSocket-only starts retain the compatible full-snapshot fallback.
23- Snapshot queries bound timeline reads per fork ancestor, not total rows: dependency and
24 control records still accompany the window. Smaller socket frames alone do not establish a
25 whole-server memory reduction.
26- Resume catch-up checks the original 1 MiB persisted-payload budget before decoding, then
27 replays at most 128 thread events and 1 MiB of projected event JSON before falling back to a
28 snapshot. Raw JSON bytes are not a heap-memory cap. Raising this budget merely because output
29 projects small can increase server allocations on repeated large tool updates.
30- Raw command output, dynamic-tool result bodies, and inline file-change bodies are omitted at
31 the wire boundary, including small outputs. Explicit failure flags and bounded result IDs
32 preserve status and grouped action counts without shipping those bodies. Persisted events
33 remain complete; the existing diff endpoints still provide file content when requested.
34 Explicit cross-environment transcript exports preserve full tool bodies so the destination
35 agent can read the context. The export rejects timeline JSON above the file attachment limit
36 before sending it; the client still applies its attachment limit to the complete file.
37- The initial shell contains active navigation rows only. Archived rows use the dedicated archive
38 query, and transcript message bodies stay in thread detail regardless of message size.
39- Shell resume sends deltas plus compact repository-enrichment metadata, not another full project
40 and thread snapshot.
41- Auto, steer, and restart sends resolve delivery from authoritative state inside the
42 server's per-thread dispatch lock. Model selection and identified-checkpoint rollback also
43 dispatch without first fetching a full thread projection when the server advertises support.
44 Older servers retain projection-based validation. Explicit start sends already skipped that
45 read, so this saving does not apply to every client send path.
46
47When changing projection schemas, paging, shell synchronization, or thread state, run this command
48alongside the focused package typechecks and a real-client pass on every affected surface. The
49transport performance fixtures validate contract encoding and compare pre-compression RPC JSON
50for the same synthetic workload, preserving historical application-object counts separately.
51These are neither compressed WebSocket captures nor measurements of server RSS, and command
52fixtures exclude unrelated settings calls and subscription events. Payload budgets belong in
53these tests so regressions fail locally.
54
55## Event store and startup
56
57Sequence cursors must seek an index rather than scan retained history. The application sequence
58index contains project events and v2 thread events; the per-thread sequence index contains only v2
59thread events. Legacy-only and unknown threads must return zero without scanning other threads.
60Catch-up queries keep sequence ranges and optional thread or command predicates indexable.
61
62Startup selects recovery candidates from current projection state before reading full thread
63projections. Candidate selection includes queued runs, pending runtime requests, live provider
64sessions, background work, and unfinished delegated deliveries. Archived threads still participate
65where recovery requires them. Completed thread history is not loaded merely to discover that no
66work remains.
67
68Projection verification decodes canonical rows in bounded pages, including shared provider records
69and fork ancestry. Rebuild replays event pages through a fixed sequence within its transaction;
70it must not collect the entire event log before projecting it.
71
72The focused regression coverage lives in `OrchestrationEventStore.sequence.test.ts`,
73`ProjectionRecovery.test.ts`, and the provider runtime recovery tests under `apps/server/src`.
74
75## Background work and navigation
76
77The settlement worker reads active, unpinned threads without explicit settlement overrides from
78`ProjectionStore.getSettlementCandidates`. It reads the latest run and user-message timestamps,
79rejects active runs and pending requests, and checks background work using the same derivation as
80the shell. It does not load archived histories, fork ancestry, transcript counts, or provider
81sessions. The orchestrator still validates the current thread before applying settlement.
82
83Read receipts use `ProjectionStore.getThread` to load only canonical thread metadata. A visit
84advances `lastVisitedAt` monotonically without changing `updatedAt` or decoding history. Shell
85queries use a covering thread/run index for item counts and a partial index for nonterminal
86background items, including idle items. Latest user-message reads use an index ordered by update
87time.
88
89Command palette results keep live VCS and linked pull-request queries leased to the visible
90viewport, including a small overscan region. Cached badges remain available while leases are
91released. Local Git status reads skip divergence calculations; callers that need ahead/behind
92counts keep the full status path. Cached fetch failures share the existing backoff and log only
93the actual failed attempt. Unchanged desktop environment bootstrap reads retain their array
94identity so polling does not invalidate subscribers.
95
96Local trace records and provider event logs use asynchronous, serialized file writes and rotation.
97Batching synchronous appends still stalls the server event loop on slow storage, especially on
98Windows. While a trace write is slow, producers keep up to one trace file of records, so the spans
99that time a stall survive it; records drop only while the disk stays stuck. Provider logging
100retains backpressure. Shutdown drains accepted writes before closing their scope.
101
102Relay awareness queues one pending publication per thread and retains a follow-up when the thread
103changes during a send. Transcript deltas and tool output do not enqueue awareness work. Run,
104request, and relevant thread metadata events do. Configuration is checked before shell reads, and
105connection changes invalidate published identities so relinking can publish unchanged state.
106Failed publications retry the latest state up to five times with exponential delays. A new
107relevant event resets the budget; successful publication, disabling, or unlinking cancels retries.
108After the budget is exhausted, publication waits for another relevant event instead of generating
109continuous traffic during an outage.
110
111Focused coverage: `ProjectionSettlement.test.ts`, `ThreadSettlementService.test.ts`,
112`runtimeLayer.test.ts`, `AgentAwarenessRelay.test.ts`, `GitVcsDriverCore.test.ts`,
113`ThreadStatusIndicators.subscriptions.test.tsx`, and `desktopLocal.test.ts`.