diff --git a/apps/server/src/orchestration-v2/ProjectionStore.test.ts b/apps/server/src/orchestration-v2/ProjectionStore.test.ts index c6187a97ea8db..3949c6891840f 100644 --- a/apps/server/src/orchestration-v2/ProjectionStore.test.ts +++ b/apps/server/src/orchestration-v2/ProjectionStore.test.ts @@ -472,6 +472,62 @@ it.effect("record reads restrict provider histories to delivery identities", () })).providerThreads, [], ); + const ownerNodeId = NodeId.make("node:delivery-identities:owner"); + yield* store.apply({ + id: EventId.make("event:delivery-identities:owner-node"), + type: "node.updated", + threadId, + runId: RunId.make("run:delivery-identities:rolled-back"), + nodeId: ownerNodeId, + driver, + occurredAt: now, + payload: { + id: ownerNodeId, + threadId, + runId: RunId.make("run:delivery-identities:rolled-back"), + rootNodeId: NodeId.make("node:delivery-identities:rolled-back"), + parentNodeId: NodeId.make("node:delivery-identities:rolled-back"), + kind: "subagent", + status: "completed", + countsForRun: false, + providerThreadId: null, + providerTurnId: null, + nativeItemRef: null, + runtimeRequestId: null, + checkpointScopeId: null, + startedAt: now, + completedAt: now, + }, + }); + yield* store.apply({ + id: EventId.make("event:delivery-identities:owned"), + type: "provider-thread.updated", + threadId, + occurredAt: now, + payload: { + id: ProviderThreadId.make("provider-thread:owned"), + appThreadId: ThreadId.make("thread:delivery-identities:child"), + driver, + providerInstanceId, + providerSessionId: null, + ownerNodeId, + nativeThreadRef: null, + nativeConversationHeadRef: null, + lastRunOrdinal: null, + handoffIds: [], + forkedFrom: null, + status: "idle", + firstRunOrdinal: 2, + createdAt: now, + updatedAt: now, + }, + }); + assert.deepEqual( + (yield* store.getThreadRecords(threadId, ["providerThreads"])).providerThreads.map( + (row) => row.id, + ), + ["provider-thread:history", "provider-thread:selected", "provider-thread:owned"], + ); // Unselected history must not be decoded, even if its payload is unreadable. yield* sql`UPDATE orchestration_v2_projection_provider_threads SET payload_json = '{}' WHERE provider_thread_id = 'provider-thread:history'`; diff --git a/apps/server/src/orchestration-v2/ProjectionStore.ts b/apps/server/src/orchestration-v2/ProjectionStore.ts index b5a0f2cfb5556..109c7edb88f38 100644 --- a/apps/server/src/orchestration-v2/ProjectionStore.ts +++ b/apps/server/src/orchestration-v2/ProjectionStore.ts @@ -1961,6 +1961,16 @@ export const layer: Layer.Layer = OR item.payload_json LIKE '%"viewedImagePath"%' OR item.payload_json LIKE '%"toolName":"%delegate_task"%')`; + // Start from the few owned provider threads; a thread can have a large settled node history. + const nodesOwningProviderThreads = (threadId: ThreadId) => sql` + SELECT owner.node_id + FROM orchestration_v2_projection_provider_threads AS owned + INDEXED BY orchestration_v2_projection_provider_threads_owner_idx + CROSS JOIN orchestration_v2_projection_nodes AS owner + ON owner.node_id = owned.owner_node_id + WHERE owned.owner_node_id IS NOT NULL AND owner.thread_id = ${threadId} + `; + const keepRecordedRunField = (payload: Statement.Fragment, path: string) => sql` CASE WHEN json_type(excluded.payload_json, ${path}) IS NULL @@ -3495,11 +3505,7 @@ export const layer: Layer.Layer = SELECT payload_json FROM orchestration_v2_projection_provider_threads WHERE (thread_id = ${threadId} - OR owner_node_id IN ( - SELECT node_id - FROM orchestration_v2_projection_nodes - WHERE thread_id = ${threadId} - ) + OR owner_node_id IN (${nodesOwningProviderThreads(threadId)}) OR provider_thread_id IN ( SELECT provider_thread_id FROM orchestration_v2_projection_subagents @@ -5040,7 +5046,7 @@ export const layer: Layer.Layer = sql`SELECT payload_json FROM orchestration_v2_projection_provider_threads WHERE provider_thread_id = ${target.providerThreadId} AND ( thread_id = ${threadId} - OR owner_node_id IN (SELECT node_id FROM orchestration_v2_projection_nodes WHERE thread_id = ${threadId}) + OR owner_node_id IN (${nodesOwningProviderThreads(threadId)}) OR provider_thread_id IN (SELECT provider_thread_id FROM orchestration_v2_projection_subagents WHERE thread_id = ${threadId}) )`, sql`SELECT payload_json FROM orchestration_v2_projection_provider_turns @@ -5280,7 +5286,7 @@ export const layer: Layer.Layer = sql`SELECT payload_json FROM orchestration_v2_projection_provider_threads WHERE provider_thread_id = ${run?.providerThreadId ?? null} AND (thread_id = ${threadId} - OR owner_node_id IN (SELECT node_id FROM orchestration_v2_projection_nodes WHERE thread_id = ${threadId}) + OR owner_node_id IN (${nodesOwningProviderThreads(threadId)}) OR provider_thread_id IN (SELECT provider_thread_id FROM orchestration_v2_projection_subagents WHERE thread_id = ${threadId}))`, sql<{ readonly ordinal: number }>`SELECT ordinal_within_scope AS ordinal FROM orchestration_v2_projection_checkpoints @@ -5319,7 +5325,7 @@ export const layer: Layer.Layer = sql`SELECT payload_json FROM orchestration_v2_projection_provider_threads WHERE provider_thread_id = ${target.providerThreadId} AND (thread_id = ${threadId} - OR owner_node_id IN (SELECT node_id FROM orchestration_v2_projection_nodes WHERE thread_id = ${threadId}) + OR owner_node_id IN (${nodesOwningProviderThreads(threadId)}) OR provider_thread_id IN (SELECT provider_thread_id FROM orchestration_v2_projection_subagents WHERE thread_id = ${threadId}))`, sql`SELECT payload_json FROM orchestration_v2_projection_checkpoints WHERE thread_id = ${threadId} AND checkpoint_id = ${target.checkpointId}`,