Skip to content

Commit 7449afa

Browse files
committed
perf(storage): read one invocation by run id
`readRunIfPresent`, `readInvocationIfPresent` and `SessionManager.readInvocation` had become `listSessionInvocations()` followed by `find`, and a Turn calls them four to six times. The header era answered these with a keyed row read. `RuntimeEventStore` gains an optional `readRunInvocation(sessionId, runId)`; the SQLite store answers it off the opening index, and a helper in core answers it from the inventory for stores that do not implement it, so callers state one intent either way. Generated-by: Claude Code
1 parent 27069c9 commit 7449afa

8 files changed

Lines changed: 55 additions & 12 deletions

File tree

packages/core/src/runtime-event-store.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@ export interface RuntimeEventStore {
8282
* fact and therefore never appear here.
8383
*/
8484
listSessionInvocations(sessionId: string): Promise<RuntimeInvocationRecord[]>;
85+
/**
86+
* One invocation by run id, absent when no opening fact names it. A store
87+
* that indexes openings answers this in one read; stores without the fast
88+
* path are answered from the inventory by `readRunInvocation`.
89+
*/
90+
readRunInvocation?(
91+
sessionId: string,
92+
runId: string,
93+
): Promise<RuntimeInvocationRecord | undefined>;
8594
appendRuntimeEvent(
8695
sessionId: string,
8796
runId: string,
@@ -121,6 +130,18 @@ export interface RuntimeEventStore {
121130
readSessionRuntimeEvents(sessionId: string): Promise<RuntimeEvent[]>;
122131
}
123132

133+
/** One invocation by run id, through the store's fast path when it has one. */
134+
export async function readRunInvocation(
135+
store: Pick<RuntimeEventStore, 'listSessionInvocations' | 'readRunInvocation'>,
136+
sessionId: string,
137+
runId: string,
138+
): Promise<RuntimeInvocationRecord | undefined> {
139+
if (store.readRunInvocation) return store.readRunInvocation(sessionId, runId);
140+
return (await store.listSessionInvocations(sessionId)).find(
141+
(invocation) => invocation.runId === runId,
142+
);
143+
}
144+
124145
export interface RuntimeRecoveryBundleStore extends RuntimeEventStore {
125146
readonly recoveryBundleCapability: typeof TOOL_RECOVERY_BUNDLE_CAPABILITY_V1;
126147
commitToolRecoveryBundle(input: RuntimeRecoveryBundleCommit): Promise<void>;

packages/runtime-host/src/server/canonical-turn-snapshot.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import { type ContextCompactionOutcome } from '@maka/core/events';
2121
import { truncateUtf8 } from '@maka/core/diagnostic-log';
2222
import { redactSecrets } from '@maka/core/redaction';
23+
import { readRunInvocation } from '@maka/core/runtime-event-store';
2324
import type { RuntimeInvocationRecord } from '@maka/core/runtime-invocation';
2425
import { classifyTerminalRuntimeLedger } from '@maka/runtime/terminal-run-commit';
2526
import type { ExecutionStoresWriter } from '@maka/storage/execution-stores';
@@ -152,9 +153,7 @@ async function readInvocationIfPresent(
152153
runId: string,
153154
): Promise<RuntimeInvocationRecord | undefined> {
154155
try {
155-
return (await stores.runtimeEventStore.listSessionInvocations(sessionId)).find(
156-
(invocation) => invocation.runId === runId,
157-
);
156+
return await readRunInvocation(stores.runtimeEventStore, sessionId, runId);
158157
} catch (error) {
159158
if (isMissingFile(error)) return undefined;
160159
throw error;

packages/runtime-host/src/server/hosted-execution-projection.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
type RuntimeInvocationRecord,
2424
} from '@maka/core/runtime-invocation';
2525
import { RuntimeMessageAuthorityInvariantError } from '@maka/runtime/message-authority';
26+
import { readRunInvocation } from '@maka/core/runtime-event-store';
2627
import type { ExecutionStoresWriter } from '@maka/storage/execution-stores';
2728
import { readCanonicalTurnSnapshot } from './canonical-turn-snapshot.js';
2829
import type { HostedExecutionRef, HostedExecutionSnapshot } from './hosted-execution-authority.js';
@@ -48,9 +49,7 @@ export class HostedExecutionProjectionReader {
4849
runId: string,
4950
): Promise<RuntimeInvocationRecord | undefined> {
5051
try {
51-
return (await this.stores.runtimeEventStore.listSessionInvocations(sessionId)).find(
52-
(invocation) => invocation.runId === runId,
53-
);
52+
return await readRunInvocation(this.stores.runtimeEventStore, sessionId, runId);
5453
} catch (error) {
5554
if (isMissingFile(error)) return undefined;
5655
throw error;

packages/runtime/src/session-manager.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,10 @@ import type { AgentRunEvent, AgentRunStore } from '@maka/core/agent-run';
122122
import type { ArtifactRecord } from '@maka/core/artifacts';
123123
import { invocationMatchesClaimTarget } from '@maka/core/runtime-boundary';
124124
import type { ContinuationClaimV1 } from '@maka/core/runtime-boundary';
125-
import type {
126-
RuntimeEventStore,
127-
RuntimeContinuationAuthorityStore,
125+
import {
126+
readRunInvocation,
127+
type RuntimeEventStore,
128+
type RuntimeContinuationAuthorityStore,
128129
} from '@maka/core/runtime-event-store';
129130
import type {
130131
RuntimeEvent,
@@ -1057,9 +1058,8 @@ export class SessionManager {
10571058

10581059
/** One invocation by run id. Absent means no opening fact ever named it. */
10591060
private async readInvocation(sessionId: string, runId: string): Promise<RuntimeInvocationRecord> {
1060-
const invocation = (await this.listInvocations(sessionId)).find(
1061-
(candidate) => candidate.runId === runId,
1062-
);
1061+
const store = this.deps.runtimeEventStore;
1062+
const invocation = store ? await readRunInvocation(store, sessionId, runId) : undefined;
10631063
if (!invocation) {
10641064
const error = new Error(`AgentRun ${runId} not found`) as Error & { code?: string };
10651065
error.code = 'ENOENT';

packages/storage/src/agent-run-store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ export type RuntimeEventScanResult = { readonly status: 'complete' | 'limit_exce
271271

272272
export interface DurableRuntimeEventStore extends RuntimeEventStore {
273273
listSessionInvocations(sessionId: string): Promise<RuntimeInvocationRecord[]>;
274+
readRunInvocation(sessionId: string, runId: string): Promise<RuntimeInvocationRecord | undefined>;
274275
listSessionInvocationsBounded(
275276
sessionId: string,
276277
limit: number,

packages/storage/src/execution-stores.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ export interface ExecutionRuntimeEventReader {
206206
* repairs it.
207207
*/
208208
listSessionInvocations(sessionId: string): Promise<RuntimeInvocationRecord[]>;
209+
readRunInvocation(sessionId: string, runId: string): Promise<RuntimeInvocationRecord | undefined>;
209210
listSessionInvocationsBounded(
210211
sessionId: string,
211212
limit: number,
@@ -552,6 +553,8 @@ async function createExecutionStoresForWrite<K extends StorageRootKind, E extend
552553
run(() => runtimeEventStore.readImmutableRuntimePrefix(input)),
553554
listSessionInvocations: (sessionId) =>
554555
run(() => runtimeEventStore.listSessionInvocations(sessionId)),
556+
readRunInvocation: (sessionId, runId) =>
557+
run(() => runtimeEventStore.readRunInvocation(sessionId, runId)),
555558
listSessionInvocationsBounded: (sessionId, limit) =>
556559
run(() => runtimeEventStore.listSessionInvocationsBounded(sessionId, limit)),
557560
listSessionInvocationsPage: (sessionId, input) =>
@@ -665,6 +668,8 @@ async function openExecutionStoresForRead<K extends StorageRootKind, E extends o
665668
run(() => runtimeEventStore.readImmutableRuntimeEvents(sessionId, runId)),
666669
listSessionInvocations: (sessionId) =>
667670
run(() => runtimeEventStore.listSessionInvocations(sessionId)),
671+
readRunInvocation: (sessionId, runId) =>
672+
run(() => runtimeEventStore.readRunInvocation(sessionId, runId)),
668673
listSessionInvocationsBounded: (sessionId, limit) =>
669674
run(() => runtimeEventStore.listSessionInvocationsBounded(sessionId, limit)),
670675
listSessionInvocationsPage: (sessionId, input) =>

packages/storage/src/runtime-event-persistence.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export type RuntimeEventReadPersistence = {
4747

4848
export interface RuntimeEventReadStore {
4949
listSessionInvocations(sessionId: string): Promise<RuntimeInvocationRecord[]>;
50+
readRunInvocation(sessionId: string, runId: string): Promise<RuntimeInvocationRecord | undefined>;
5051
listSessionInvocationsBounded(
5152
sessionId: string,
5253
limit: number,
@@ -96,6 +97,8 @@ export async function openRuntimeEventReadPersistence(input: {
9697
kind: 'sqlite',
9798
runtimeEventStore: Object.freeze({
9899
listSessionInvocations: (sessionId: string) => store.listSessionInvocations(sessionId),
100+
readRunInvocation: (sessionId: string, runId: string) =>
101+
store.readRunInvocation(sessionId, runId),
99102
listSessionInvocationsBounded: (sessionId: string, limit: number) =>
100103
store.listSessionInvocationsBounded(sessionId, limit),
101104
listSessionInvocationsPage: (sessionId: string, input: RuntimeInvocationPageInput) =>

packages/storage/src/sqlite-runtime-store.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,18 @@ export class SqliteRuntimeStore
555555
);
556556
}
557557

558+
async readRunInvocation(
559+
sessionId: string,
560+
runId: string,
561+
): Promise<RuntimeInvocationRecord | undefined> {
562+
assertRuntimeStorageSafeId(sessionId, 'Invalid session id');
563+
assertRuntimeStorageSafeId(runId, 'Invalid run id');
564+
return this.readTransaction(() => {
565+
const row = this.readInvocationOpeningsSync(sessionId, { direction: 'asc', runId }).at(0);
566+
return row ? this.completeInvocationRecordSync(row) : undefined;
567+
});
568+
}
569+
558570
/**
559571
* The first page of a Session's invocations, plus whether more exist.
560572
*
@@ -646,6 +658,7 @@ export class SqliteRuntimeStore
646658
limit?: number;
647659
before?: RuntimeInvocationPageCursor;
648660
invocationId?: string;
661+
runId?: string;
649662
},
650663
): Omit<RuntimeInvocationRecord, 'terminalEvent'>[] {
651664
const order = options.direction === 'desc' ? 'DESC' : 'ASC';
@@ -680,6 +693,7 @@ export class SqliteRuntimeStore
680693
)
681694
)
682695
WHERE (:invocationId IS NULL OR invocation_id = :invocationId)
696+
AND (:runId IS NULL OR run_id = :runId)
683697
AND (
684698
:beforeOpenedAt IS NULL
685699
OR opened_at < :beforeOpenedAt
@@ -691,6 +705,7 @@ export class SqliteRuntimeStore
691705
.all({
692706
sessionId,
693707
invocationId: options.invocationId ?? null,
708+
runId: options.runId ?? null,
694709
beforeOpenedAt: options.before?.openedAt ?? null,
695710
beforeInvocationId: options.before?.invocationId ?? null,
696711
limit: options.limit ?? -1,

0 commit comments

Comments
 (0)