From a9b335c1fc07a95839e7a165742a3680b355817e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 23:35:02 +0000 Subject: [PATCH 1/2] perf(server): stop remapping every thread on each thread event updateThread ran threads.map over every thread the server knows about (deleted ones included) for each thread event, streaming deltas included. Find the one index and copy the array instead. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Apa4PipuLUNhbdABEvHXJB --- apps/server/src/orchestration/projector.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/server/src/orchestration/projector.ts b/apps/server/src/orchestration/projector.ts index 85d9db3fdfed..1c2ef5e90305 100644 --- a/apps/server/src/orchestration/projector.ts +++ b/apps/server/src/orchestration/projector.ts @@ -117,12 +117,18 @@ function settledTurnStateForSessionStatus( } } +// Runs for every thread event (including streaming deltas) against every +// thread the server has ever seen, so copy the array rather than map it. function updateThread( threads: ReadonlyArray, threadId: ThreadId, patch: ThreadPatch, -): OrchestrationThread[] { - return threads.map((thread) => (thread.id === threadId ? { ...thread, ...patch } : thread)); +): ReadonlyArray { + const index = threads.findIndex((thread) => thread.id === threadId); + if (index === -1) return threads; + const next = threads.slice(); + next[index] = { ...threads[index]!, ...patch }; + return next; } /** Patch that swaps a thread's links and re-derives the legacy single-PR field from them. */ From b68cc2a4204797d6c23f0622bfdfe2577fc326bf Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 23:54:32 +0000 Subject: [PATCH 2/2] perf(server): scan threads once for message and activity events Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Apa4PipuLUNhbdABEvHXJB --- apps/server/src/orchestration/projector.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/server/src/orchestration/projector.ts b/apps/server/src/orchestration/projector.ts index 1c2ef5e90305..dd418b95b9bc 100644 --- a/apps/server/src/orchestration/projector.ts +++ b/apps/server/src/orchestration/projector.ts @@ -125,7 +125,15 @@ function updateThread( patch: ThreadPatch, ): ReadonlyArray { const index = threads.findIndex((thread) => thread.id === threadId); - if (index === -1) return threads; + return index === -1 ? threads : patchThreadAt(threads, index, patch); +} + +/** For callers that already located the thread and must not scan again. */ +function patchThreadAt( + threads: ReadonlyArray, + index: number, + patch: ThreadPatch, +): ReadonlyArray { const next = threads.slice(); next[index] = { ...threads[index]!, ...patch }; return next; @@ -773,7 +781,8 @@ export function projectEvent( event.type, "payload", ); - const thread = nextBase.threads.find((entry) => entry.id === payload.threadId); + const threadIndex = nextBase.threads.findIndex((entry) => entry.id === payload.threadId); + const thread = nextBase.threads[threadIndex]; if (!thread) { return nextBase; } @@ -821,7 +830,7 @@ export function projectEvent( return { ...nextBase, - threads: updateThread(nextBase.threads, payload.threadId, { + threads: patchThreadAt(nextBase.threads, threadIndex, { messages: cappedMessages, updatedAt: event.occurredAt, }), @@ -1061,7 +1070,8 @@ export function projectEvent( "payload", ).pipe( Effect.map((payload) => { - const thread = nextBase.threads.find((entry) => entry.id === payload.threadId); + const threadIndex = nextBase.threads.findIndex((entry) => entry.id === payload.threadId); + const thread = nextBase.threads[threadIndex]; if (!thread) { return nextBase; } @@ -1075,7 +1085,7 @@ export function projectEvent( return { ...nextBase, - threads: updateThread(nextBase.threads, payload.threadId, { + threads: patchThreadAt(nextBase.threads, threadIndex, { activities, updatedAt: event.occurredAt, }),