Repository navigation
perf(j5): field-scoped thread reads outside the Crew services - #810
Conversation
J5's full-thread reads (getThreadProjection) run on the writer connection since upstream moved getThreadRecords to a reader, so every one queues behind event commits. Move the sites outside the in-flight Crew stack to getThreadRecords with only the fields, and filters, each caller uses. Part of #278. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
This review includes 33 billable files and costs up to $8.25.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Or wait 12 minutes for your next included review. View limit details
|
Jacksondr5
left a comment
There was a problem hiding this comment.
Combined review from the panel (reviewer-opus, Claude Opus 5.5; reviewer-sol, GPT Sol 6.1). We reviewed 2a632e5 separately and agree: no findings that need a change.
What we checked:
- Fields. Every swapped site asks only for fields it uses, and the narrowed
Pick<…>signatures (latestSeatNotice,queuedSeatDigest,runFailureDetail,selectCurrentProposedPlan,hasFact,inboxAnswerableApprovals) make the typecheck enforce that. - Filters drop only rows the caller already ignored. We checked each against its consumer:
deliverAgent: the usage-limit hold (usageLimitBlockedRun) reads onlyerroritems. The message checks only look at this delivery's owndeliveryMessageId.latestSteerableRun,peerSteeringRunandrequestedSteeringRunread only unfiltered fields (runs, provider turns, threads, sessions).cancelAgentandlanded: only the one message is narrowed, and all runs stay loaded, soheldRunand the run lookup are unchanged.CrewSeatFinishNotifier: the Captain read'smessageRoles: ["user"]matches therole === "user"checks inlatestSeatNoticeandqueuedSeatDigest. The seat read's run anderrorfilter matchesrunFailureDetail. All runs stay loaded forlatestActiveRunandoutstandingFinish.QueuedRunWatchdog: the signal facts it writes and dedupes on aretype: "error"items withrunIdset to that run, so the filtered read still sees them.SilenceDetector.failureDetail,ArtifactRunFinalizationObserver(plansstays unfiltered) andCrewRuntimeRequestService(approval_requestonly, which is allinboxAnswerableApprovalsreads) each match their consumer.
- Forks.
readProjection's fork recursion only fillsvisibleTurnItems, whichgetThreadRecordscannot load, and no J5 server code reads it. No swapped site sees different rows on a fork. - Not-found handling.
OrchestratorV2.getThreadRecordswraps a store not-found exactly asgetThreadProjectiondoes, sogetThreadRecordsIfPresentandworkspaceRoot'sisMissingThreadkeep their meaning. - Read-only connection. Every swapped read runs after a commit, outside any writer transaction: the run-finalization observers from the effect worker, and
deliverAgent/cancelAgentoutside the worker'ssql.withTransactionblocks. The reader sees the same committed rows the writer would. - Spans. No
Effect.fnnames changed or were dropped. The newreadRunFactsis a plain function, which is fine because its callers carry the spans. - Remaining full reads. At head, every one is in the PR's "Deferred to part 2" list:
CrewLaunchReporter,CrewLaunchService,CrewProposalService,SpawnAgentService,mcp/handlers.ts, the cascade settle path, and the old helper. - Tests. No sleeps, polling or timeouts. The two new real-store tests cover the two places where a filter decides the rows. Moving the stale-snapshot hook in
DeliveryTransport.integrationtogetThreadRecordsis correct: without it, the race tests would pass without exercising the race. The usage-limit integration cases now exercise the filtereddeliverAgentread on the real store.
One process note, not a code finding: DeliveryTransport.integration.test.ts and CrewCaptainArchiveCascade.test.ts are also changed by open PRs #758 and #756. The edits here only rename fake methods, which the source swap requires, so whichever PR merges second will need a small rebase.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
Since upstream pingdotgg#23902 (in J5 since #705),
getThreadRecordsreads on a separate reader connection, whilegetThreadProjectionstill reads on the writer. So every J5 full-thread read waits behind event commits, including the ones that run after every run and on every delivery. Part of #278.What changed
Every J5 server site outside the in-flight Crew rework stack now calls
getThreadRecordsand asks only for the fields it uses, with afilterwhere one fits:ArtifactRunFinalizationObserver(after every run)plans,turnItemsfiltered to that run'sproposed_planitemsagentHandoffObserver(after every run)runsfiltered to that runDeliveryTransport.deliverAgentruns, provider turns/threads/sessions, the delivery's one message, and only theerrorturn items (all the usage-limit hold reads)DeliveryTransport.cancelAgent,landedrunsCrewSeatFinishNotifierruns+ that run'serroritems; Captain:runs+usermessages; sweep:runsQueuedRunWatchdog(scan, VCS fact)erroritems (its dedup facts are error items of the run)SilenceDetector.failureDetailerroritemsCrewRuntimeRequestServiceruntimeRequests+approval_requestitemsCrewStopService,ArchiveAgentServicestaterunsArchiveAgentService.readFacts,PlaybookCrewRelay×2,CrewCaptainArchiveCascaderestore/sweep,playbooks/workspace+workspaceRoot,agentInvocation,ThreadLineage[]getThreadRecordsIfPresentsits next togetThreadProjectionIfPresentand applies the same not-found rule. The old helper stays until its Crew-stack callers move in part 2.Forks.
getThreadProjectionfollows a fork's source history andgetThreadRecordsdoesn't. InProjectionStore.readProjectionthat history only fillsvisibleTurnItems. Every other field is the thread's own rows in both reads, and no J5 server code readsvisibleTurnItems. So no swapped site sees different rows on a fork.No behaviour change. Each filter leaves out only rows its caller already threw away.
Tests
getThreadRecords.DeliveryTransport.integrationruns on the real SQL store. Its stale-snapshot hook now wrapsgetThreadRecords, so the two race tests still feed a stale read todeliverAgent; after the swap they had been passing without testing the race.turnItemTypesfilter fails it: checked by mutating it.Deferred to part 2 (files touched by open Crew-stack PRs #750–#762, #770)
CrewProposalService(2 reads),CrewLaunchReporter(4),CrewLaunchService(1),SpawnAgentService(2 full reads, plus shell reads),a2a/mcp/handlers.ts(2)CrewCaptainArchiveCascadesettle cascade: needs the whole shell (threadShellFromProjection); follow upstream fix(server): avoid full shell read on resume pingdotgg/t3code#23987's narrow-query pattern or keep itgetThreadProjectionIfPresentonce those callers moveAlready narrow, nothing to do:
nativeQuestionInboxRead,NativeQuestionNotifier,receiverBacklog,SilenceDetector.readRunsAndMessages.Live testing
Server-only change, so no UI. Runtime check on an isolated test server (the
j5-live-testhome in this worktree, never the live install) at2a632e5:That run used both run-finalization observers and
deliverAgentwith the narrowed target read. Deliveries to fixture threads (and to the fork) were also delivered, but their runs failed: they try to resume the fixture's fake Claude session (Session … not found). That comes from the fixtures, not from this change.Not exercised live:
cancelAgent(covered byDeliveryTransport.integration), seat-finish notices, Crew stop and runtime requests (unit tests only).Checklist
vp run check:changed origin/j5/main: format, lint, typecheck, graphql and the 19 test files beside the changes pass. Knip fails on existing unused exports, none added here.Built by Claude Opus 5.5 in Claude Code (J5 Code Crew builder seat).
🤖 Generated with Claude Code