History

perf(server): skip history reads for metadata commands (#9758)

Theo Browne committed 2 months ago

Commit 63659191 parent 77b655c2 files · +69 −43Browse files

Unified · Split

2 files
  1. apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +46 −26
  2. apps/server/src/orchestration/Layers/ProviderCommandReactor.ts +23 −17
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+46−26
@@ -29,6 +29,7 @@ import * as Deferred from "effect/Deferred"; · 28 unmodified lines
2929 import * as Exit from "effect/Exit";
3030 import * as Layer from "effect/Layer";
3131 import * as ManagedRuntime from "effect/ManagedRuntime";
32+import * as Option from "effect/Option";
3233 import * as PubSub from "effect/PubSub";
3334 import * as Scope from "effect/Scope";
3435 import * as Stream from "effect/Stream";
@@ -170,6 +171,7 @@ describe("ProviderCommandReactor", () => { · 135 unmodified lines
170171 readonly threadModelSelection?: ModelSelection;
171172 readonly sessionModelSwitch?: "unsupported" | "in-session";
172173 readonly requiresNewThreadForModelChange?: boolean;
174+ readonly unreadableHistory?: boolean;
173175 readonly titleRegenerationCompletionDispatchFailures?: number;
174176 readonly titleRegenerationBeforeStart?: "one" | "two";
175177 readonly serverActivation?: Effect.Effect<void>;
@@ -512,6 +514,23 @@ describe("ProviderCommandReactor", () => { · 336 unmodified lines
512514 createdAt: now,
513515 }),
514516 );
517+ if (input?.unreadableHistory === true) {
518+ // Metadata commands must not decode this unrelated message body.
519+ await runtime.runPromise(
520+ Effect.gen(function* () {
521+ const sql = yield* SqlClient.SqlClient;
522+ yield* sql`
523+ INSERT INTO projection_thread_messages (
524+ message_id, thread_id, turn_id, role, text, attachments_json,
525+ is_streaming, created_at, updated_at
526+ ) VALUES (
527+ 'old-unreadable-message', 'thread-1', NULL, 'assistant',
528+ 'Old assistant output', 'invalid json', 0, ${now}, ${now}
529+ )
530+ `;
531+ }),
532+ );
533+ }
515534 if (input?.titleRegenerationBeforeStart === "two") {
516535 await Effect.runPromise(
517536 engine.dispatch({
@@ -561,6 +580,7 @@ describe("ProviderCommandReactor", () => { · 43 unmodified lines
561580
562581 return {
563582 engine,
583+ snapshotQuery,
564584 readModel: () => Effect.runPromise(snapshotQuery.getSnapshot()),
565585 readPendingTurnStarts: () =>
566586 runtime!.runPromise(
@@ -3533,8 +3553,8 @@ describe("ProviderCommandReactor", () => { · 2966 unmodified lines
35333553 });
35343554 });
35353555
3536− it("reacts to thread.approval.respond by forwarding provider approval response", async () => {
3537− const harness = await createHarness();
3556+ it("forwards approval responses without reading unrelated message bodies", async () => {
3557+ const harness = await createHarness({ unreadableHistory: true });
35383558 const now = "2026-01-01T00:00:00.000Z";
35393559
35403560 await Effect.runPromise(
@@ -3566,7 +3586,7 @@ describe("ProviderCommandReactor", () => { · 25 unmodified lines
35663586 }),
35673587 );
35683588
3569− await waitFor(() => harness.respondToRequest.mock.calls.length === 1);
3589+ await harness.drain();
35703590 expect(harness.respondToRequest.mock.calls[0]?.[0]).toEqual({
35713591 threadId: "thread-1",
35723592 requestId: "approval-request-1",
@@ -3574,8 +3594,8 @@ describe("ProviderCommandReactor", () => { · 1 unmodified line
35743594 });
35753595 });
35763596
3577− it("reacts to thread.user-input.respond by forwarding structured user input answers", async () => {
3578− const harness = await createHarness();
3597+ it("forwards user input answers without reading unrelated message bodies", async () => {
3598+ const harness = await createHarness({ unreadableHistory: true });
35793599 const now = "2026-01-01T00:00:00.000Z";
35803600
35813601 await Effect.runPromise(
@@ -3609,7 +3629,7 @@ describe("ProviderCommandReactor", () => { · 27 unmodified lines
36093629 }),
36103630 );
36113631
3612− await waitFor(() => harness.respondToUserInput.mock.calls.length === 1);
3632+ await harness.drain();
36133633 expect(harness.respondToUserInput.mock.calls[0]?.[0]).toEqual({
36143634 threadId: "thread-1",
36153635 requestId: "user-input-request-1",
@@ -3823,12 +3843,12 @@ describe("ProviderCommandReactor", () => { · 207 unmodified lines
38233843 expect(resolvedActivity).toBeUndefined();
38243844 });
38253845
3826− it("reacts to thread.session.stop by stopping provider session and clearing thread session state", async () => {
3827− const harness = await createHarness();
3828− const now = "2026-01-01T00:00:00.000Z";
3846+ effectIt.effect("stops a provider session without reading unrelated message bodies", () =>
3847+ Effect.gen(function* () {
3848+ const harness = yield* Effect.promise(() => createHarness({ unreadableHistory: true }));
3849+ const now = "2026-01-01T00:00:00.000Z";
38293850
3830− await harness.runEffect(
3831− harness.engine.dispatch({
3851+ yield* harness.engine.dispatch({
38323852 type: "thread.session.set",
38333853 commandId: CommandId.make("cmd-session-set-for-stop"),
38343854 threadId: ThreadId.make("thread-1"),
@@ -3843,27 +3863,27 @@ describe("ProviderCommandReactor", () => { · 8 unmodified lines
38433863 updatedAt: now,
38443864 },
38453865 createdAt: now,
3846− }),
3847− );
3866+ });
38483867
3849− await harness.runEffect(
3850− harness.engine.dispatch({
3868+ yield* harness.engine.dispatch({
38513869 type: "thread.session.stop",
38523870 commandId: CommandId.make("cmd-session-stop"),
38533871 threadId: ThreadId.make("thread-1"),
38543872 createdAt: now,
3855− }),
3856− );
3873+ });
38573874
3858− await waitFor(() => harness.stopSession.mock.calls.length === 1);
3859− const readModel = await harness.readModel();
3860− const thread = readModel.threads.find((entry) => entry.id === ThreadId.make("thread-1"));
3861− expect(thread?.session).not.toBeNull();
3862− expect(thread?.session?.status).toBe("stopped");
3863− expect(thread?.session?.threadId).toBe("thread-1");
3864− expect(thread?.session?.providerInstanceId).toBe(ProviderInstanceId.make("codex_work"));
3865− expect(thread?.session?.activeTurnId).toBeNull();
3866− });
3875+ yield* Effect.promise(() => harness.drain());
3876+ expect(harness.stopSession).toHaveBeenCalledWith({ threadId: ThreadId.make("thread-1") });
3877+ const thread = yield* harness.snapshotQuery
3878+ .getThreadShellById(ThreadId.make("thread-1"))
3879+ .pipe(Effect.map(Option.getOrThrow));
3880+ expect(thread.session).not.toBeNull();
3881+ expect(thread.session?.status).toBe("stopped");
3882+ expect(thread.session?.threadId).toBe("thread-1");
3883+ expect(thread.session?.providerInstanceId).toBe(ProviderInstanceId.make("codex_work"));
3884+ expect(thread.session?.activeTurnId).toBeNull();
3885+ }),
3886+ );
38673887
38683888 effectIt.effect("stops a ready provider session after automatic settlement", () =>
38693889 Effect.gen(function* () {
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+23−17
@@ -419,7 +419,7 @@ const make = Effect.gen(function* () { · 418 unmodified lines
419419 readonly detail: string;
420420 readonly createdAt: string;
421421 }) {
422− const thread = yield* resolveThread(input.threadId);
422+ const thread = yield* resolveThreadShell(input.threadId);
423423 if (!thread) {
424424 return;
425425 }
@@ -447,7 +447,7 @@ const make = Effect.gen(function* () { · 21 unmodified lines
447447 compactingThreadIds.delete(threadId);
448448 return;
449449 }
450− const thread = yield* resolveThread(threadId);
450+ const thread = yield* resolveThreadShell(threadId);
451451 if (!thread?.session) return;
452452 if (
453453 thread.session.status !== "starting" &&
@@ -525,7 +525,13 @@ const make = Effect.gen(function* () { · 71 unmodified lines
525525 );
526526 });
527527
528− const resolveThread = Effect.fnUntraced(function* (threadId: ThreadId) {
528+ const resolveThreadShell = Effect.fnUntraced(function* (threadId: ThreadId) {
529+ return yield* projectionSnapshotQuery
530+ .getThreadShellById(threadId)
531+ .pipe(Effect.map(Option.getOrUndefined));
532+ });
533+
534+ const resolveThreadDetail = Effect.fnUntraced(function* (threadId: ThreadId) {
529535 return yield* projectionSnapshotQuery
530536 .getThreadDetailById(threadId, { activityKinds: [] })
531537 .pipe(Effect.map(Option.getOrUndefined));
@@ -571,7 +577,7 @@ const make = Effect.gen(function* () { · 39 unmodified lines
571577 readonly pendingTurnStart?: boolean;
572578 },
573579 ) {
574− const thread = yield* resolveThread(threadId);
580+ const thread = yield* resolveThreadShell(threadId);
575581 if (!thread) {
576582 return yield* Effect.die(new Error(`Thread '${threadId}' was not found in read model.`));
577583 }
@@ -834,7 +840,7 @@ const make = Effect.gen(function* () { · 256 unmodified lines
834840 readonly interactionMode?: "default" | "plan";
835841 readonly createdAt: string;
836842 }) {
837− const thread = yield* resolveThread(input.threadId);
843+ const thread = yield* resolveThreadShell(input.threadId);
838844 if (!thread) {
839845 return yield* Effect.die(
840846 new Error(`Thread '${input.threadId}' was not found in read model.`),
@@ -975,7 +981,7 @@ const make = Effect.gen(function* () { · 134 unmodified lines
975981 );
976982 if (!generated) return;
977983
978− const thread = yield* resolveThread(input.threadId);
984+ const thread = yield* resolveThreadShell(input.threadId);
979985 if (!thread) return;
980986 if (!canReplaceThreadTitle(thread.title, input.titleSeed)) {
981987 return;
@@ -1007,7 +1013,7 @@ const make = Effect.gen(function* () { · 25 unmodified lines
10071013 return { _tag: "Superseded" } as const;
10081014 }
10091015
1010− const thread = yield* resolveThread(event.payload.threadId);
1016+ const thread = yield* resolveThreadDetail(event.payload.threadId);
10111017 if (!thread || thread.titleRegeneration?.requestId !== requestId) {
10121018 return { _tag: "Superseded" } as const;
10131019 }
@@ -1040,7 +1046,7 @@ const make = Effect.gen(function* () { · 26 unmodified lines
10401046 return { _tag: "Completed", title: undefined } as const;
10411047 }
10421048
1043− const latestThread = yield* resolveThread(event.payload.threadId);
1049+ const latestThread = yield* resolveThreadShell(event.payload.threadId);
10441050 if (
10451051 !latestThread ||
10461052 latestThread.titleRegeneration?.requestId !== requestId ||
@@ -1177,7 +1183,7 @@ const make = Effect.gen(function* () { · 130 unmodified lines
11771183 return;
11781184 }
11791185
1180− const thread = yield* resolveThread(event.payload.threadId);
1186+ const thread = yield* resolveThreadDetail(event.payload.threadId);
11811187 if (!thread) {
11821188 return;
11831189 }
@@ -1366,7 +1372,7 @@ const make = Effect.gen(function* () { · 182 unmodified lines
13661372 "Context compaction requires an existing conversation.",
13671373 );
13681374 }
1369− const latestThread = yield* resolveThread(event.payload.threadId);
1375+ const latestThread = yield* resolveThreadShell(event.payload.threadId);
13701376 if (
13711377 compactingThreadIds.has(event.payload.threadId) ||
13721378 latestThread?.session?.status === "starting" ||
@@ -1436,7 +1442,7 @@ const make = Effect.gen(function* () { · 63 unmodified lines
14361442 const processTurnInterruptRequested = Effect.fn("processTurnInterruptRequested")(function* (
14371443 event: Extract<ProviderIntentEvent, { type: "thread.turn-interrupt-requested" }>,
14381444 ) {
1439− const thread = yield* resolveThread(event.payload.threadId);
1445+ const thread = yield* resolveThreadShell(event.payload.threadId);
14401446 if (!thread) {
14411447 return;
14421448 }
@@ -1459,7 +1465,7 @@ const make = Effect.gen(function* () { · 16 unmodified lines
14591465
14601466 const detail = formatFailureDetail(cause);
14611467 return Effect.gen(function* () {
1462− const latestThread = yield* resolveThread(event.payload.threadId);
1468+ const latestThread = yield* resolveThreadShell(event.payload.threadId);
14631469 const latestSession = latestThread?.session;
14641470 if (
14651471 !latestSession ||
@@ -1487,7 +1493,7 @@ const make = Effect.gen(function* () { · 21 unmodified lines
14871493 );
14881494 }),
14891495 );
1490− const stoppedThread = yield* resolveThread(event.payload.threadId);
1496+ const stoppedThread = yield* resolveThreadShell(event.payload.threadId);
14911497 const stoppedSession = stoppedThread?.session;
14921498 if (
14931499 !stoppedSession ||
@@ -1531,7 +1537,7 @@ const make = Effect.gen(function* () { · 37 unmodified lines
15311537 const processApprovalResponseRequested = Effect.fn("processApprovalResponseRequested")(function* (
15321538 event: Extract<ProviderIntentEvent, { type: "thread.approval-response-requested" }>,
15331539 ) {
1534− const thread = yield* resolveThread(event.payload.threadId);
1540+ const thread = yield* resolveThreadShell(event.payload.threadId);
15351541 if (!thread) {
15361542 return;
15371543 }
@@ -1575,7 +1581,7 @@ const make = Effect.gen(function* () { · 37 unmodified lines
15751581 function* (
15761582 event: Extract<ProviderIntentEvent, { type: "thread.user-input-response-requested" }>,
15771583 ) {
1578− const thread = yield* resolveThread(event.payload.threadId);
1584+ const thread = yield* resolveThreadShell(event.payload.threadId);
15791585 if (!thread) {
15801586 return;
15811587 }
@@ -1619,7 +1625,7 @@ const make = Effect.gen(function* () { · 37 unmodified lines
16191625 const processSessionStopRequested = Effect.fn("processSessionStopRequested")(function* (
16201626 event: Extract<ProviderIntentEvent, { type: "thread.session-stop-requested" }>,
16211627 ) {
1622− const thread = yield* resolveThread(event.payload.threadId);
1628+ const thread = yield* resolveThreadShell(event.payload.threadId);
16231629 if (!thread) {
16241630 return;
16251631 }
@@ -1696,7 +1702,7 @@ const make = Effect.gen(function* () { · 70 unmodified lines
16961702 yield* threadTitleRegenerationWorker.enqueue(event);
16971703 return;
16981704 case "thread.runtime-mode-set": {
1699− const thread = yield* resolveThread(event.payload.threadId);
1705+ const thread = yield* resolveThreadShell(event.payload.threadId);
17001706 if (!thread?.session || thread.session.status === "stopped") {
17011707 return;
17021708 }

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t