perf: cache persisted bootstrap outcome lookups
This commit is contained in:
parent
d0c6fdd28c
commit
9be096f864
2 changed files with 152 additions and 2 deletions
|
|
@ -693,6 +693,11 @@ interface BootstrapTranscriptOutcomeCacheEntry {
|
|||
outcome: BootstrapTranscriptOutcome | null;
|
||||
}
|
||||
|
||||
interface BootstrapTranscriptOutcomeLookupCacheEntry {
|
||||
expiresAtMs: number;
|
||||
outcome: BootstrapTranscriptOutcome | null;
|
||||
}
|
||||
|
||||
import type {
|
||||
ActiveToolCall,
|
||||
AgentActionMode,
|
||||
|
|
@ -3295,9 +3300,10 @@ export class TeamProvisioningService {
|
|||
private static readonly SAME_TEAM_RUN_START_SKEW_MS = 1_000;
|
||||
private static readonly SAME_TEAM_PERSIST_RETRY_MS = 2_000;
|
||||
private static readonly AGENT_RUNTIME_SNAPSHOT_CACHE_TTL_MS = 2_000;
|
||||
private static readonly PERSISTED_AGENT_RUNTIME_SNAPSHOT_CACHE_TTL_MS = 5_000;
|
||||
private static readonly PERSISTED_AGENT_RUNTIME_SNAPSHOT_CACHE_TTL_MS = 10_000;
|
||||
private static readonly AGENT_RUNTIME_RESOURCE_HISTORY_LIMIT = 60;
|
||||
private static readonly BOOTSTRAP_TRANSCRIPT_OUTCOME_CACHE_MAX_ENTRIES = 2_048;
|
||||
private static readonly PERSISTED_BOOTSTRAP_TRANSCRIPT_OUTCOME_LOOKUP_CACHE_TTL_MS = 10_000;
|
||||
private static readonly MAX_RUNTIME_TREE_PIDS_PER_ROOT = 64;
|
||||
private static readonly MAX_RUNTIME_USAGE_PIDS_PER_SNAPSHOT = 512;
|
||||
private static readonly RUNTIME_PROCESS_TABLE_TIMEOUT_MS = 1_500;
|
||||
|
|
@ -3363,6 +3369,10 @@ export class TeamProvisioningService {
|
|||
string,
|
||||
BootstrapTranscriptOutcomeCacheEntry
|
||||
>();
|
||||
private readonly bootstrapTranscriptOutcomeLookupCache = new Map<
|
||||
string,
|
||||
BootstrapTranscriptOutcomeLookupCacheEntry
|
||||
>();
|
||||
private readonly teamOpLocks = new Map<string, Promise<void>>();
|
||||
private readonly leadInboxRelayInFlight = new Map<string, Promise<number>>();
|
||||
private readonly relayedLeadInboxMessageIds = new Map<string, Set<string>>();
|
||||
|
|
@ -30101,6 +30111,19 @@ export class TeamProvisioningService {
|
|||
memberName: string,
|
||||
sinceMs: number | null
|
||||
): Promise<BootstrapTranscriptOutcome | null> {
|
||||
const lookupCacheKey = this.buildBootstrapTranscriptOutcomeLookupCacheKey(
|
||||
teamName,
|
||||
memberName,
|
||||
sinceMs
|
||||
);
|
||||
const cachedLookup = this.getPersistedBootstrapTranscriptOutcomeLookupCacheEntry(
|
||||
teamName,
|
||||
lookupCacheKey
|
||||
);
|
||||
if (cachedLookup !== undefined) {
|
||||
return this.cloneBootstrapTranscriptOutcome(cachedLookup);
|
||||
}
|
||||
|
||||
let summaries: Awaited<ReturnType<TeamMemberLogsFinder['findMemberLogs']>>;
|
||||
try {
|
||||
summaries = await this.memberLogsFinder.findMemberLogs(teamName, memberName, sinceMs);
|
||||
|
|
@ -30127,7 +30150,69 @@ export class TeamProvisioningService {
|
|||
...(await this.readBootstrapTranscriptOutcomesInProjectRoot(teamName, memberName, sinceMs))
|
||||
);
|
||||
|
||||
return this.selectLatestBootstrapTranscriptOutcome(outcomes);
|
||||
const outcome = this.selectLatestBootstrapTranscriptOutcome(outcomes);
|
||||
this.setPersistedBootstrapTranscriptOutcomeLookupCacheEntry(teamName, lookupCacheKey, outcome);
|
||||
return outcome;
|
||||
}
|
||||
|
||||
private cloneBootstrapTranscriptOutcome(
|
||||
outcome: BootstrapTranscriptOutcome | null
|
||||
): BootstrapTranscriptOutcome | null {
|
||||
return outcome ? { ...outcome } : null;
|
||||
}
|
||||
|
||||
private buildBootstrapTranscriptOutcomeLookupCacheKey(
|
||||
teamName: string,
|
||||
memberName: string,
|
||||
sinceMs: number | null
|
||||
): string {
|
||||
return [teamName.trim().toLowerCase(), memberName.trim().toLowerCase(), sinceMs ?? ''].join(
|
||||
'\0'
|
||||
);
|
||||
}
|
||||
|
||||
private getPersistedBootstrapTranscriptOutcomeLookupCacheEntry(
|
||||
teamName: string,
|
||||
cacheKey: string
|
||||
): BootstrapTranscriptOutcome | null | undefined {
|
||||
if (this.getTrackedRunId(teamName) || this.runtimeAdapterRunByTeam.has(teamName)) {
|
||||
return undefined;
|
||||
}
|
||||
const cached = this.bootstrapTranscriptOutcomeLookupCache.get(cacheKey);
|
||||
if (!cached) {
|
||||
return undefined;
|
||||
}
|
||||
if (cached.expiresAtMs <= Date.now()) {
|
||||
this.bootstrapTranscriptOutcomeLookupCache.delete(cacheKey);
|
||||
return undefined;
|
||||
}
|
||||
return cached.outcome;
|
||||
}
|
||||
|
||||
private setPersistedBootstrapTranscriptOutcomeLookupCacheEntry(
|
||||
teamName: string,
|
||||
cacheKey: string,
|
||||
outcome: BootstrapTranscriptOutcome | null
|
||||
): void {
|
||||
if (this.getTrackedRunId(teamName) || this.runtimeAdapterRunByTeam.has(teamName)) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!this.bootstrapTranscriptOutcomeLookupCache.has(cacheKey) &&
|
||||
this.bootstrapTranscriptOutcomeLookupCache.size >=
|
||||
TeamProvisioningService.BOOTSTRAP_TRANSCRIPT_OUTCOME_CACHE_MAX_ENTRIES
|
||||
) {
|
||||
const oldestKey = this.bootstrapTranscriptOutcomeLookupCache.keys().next().value;
|
||||
if (oldestKey) {
|
||||
this.bootstrapTranscriptOutcomeLookupCache.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
this.bootstrapTranscriptOutcomeLookupCache.set(cacheKey, {
|
||||
expiresAtMs:
|
||||
Date.now() +
|
||||
TeamProvisioningService.PERSISTED_BOOTSTRAP_TRANSCRIPT_OUTCOME_LOOKUP_CACHE_TTL_MS,
|
||||
outcome: this.cloneBootstrapTranscriptOutcome(outcome),
|
||||
});
|
||||
}
|
||||
|
||||
private async readRecentBootstrapTranscriptOutcome(
|
||||
|
|
|
|||
|
|
@ -3405,6 +3405,11 @@ describe('TeamProvisioningService', () => {
|
|||
vi.setSystemTime(new Date('2026-05-03T12:00:06.000Z'));
|
||||
await (svc as any).getLiveTeamAgentRuntimeMetadata('runtime-team');
|
||||
|
||||
expect(listRuntimeProcessTableForCurrentPlatform).toHaveBeenCalledTimes(1);
|
||||
|
||||
vi.setSystemTime(new Date('2026-05-03T12:00:11.000Z'));
|
||||
await (svc as any).getLiveTeamAgentRuntimeMetadata('runtime-team');
|
||||
|
||||
expect(listRuntimeProcessTableForCurrentPlatform).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
|
|
@ -21134,6 +21139,66 @@ describe('TeamProvisioningService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('caches persisted bootstrap transcript outcome lookup between close polling reads', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2026-05-03T12:00:00.000Z'));
|
||||
const teamName = 'zz-unit-bootstrap-transcript-lookup-cache';
|
||||
const memberName = 'tom';
|
||||
const transcriptPath = path.join(tempProjectsBase, 'bootstrap-lookup-cache.jsonl');
|
||||
const svc = new TeamProvisioningService();
|
||||
const harness = svc as any;
|
||||
const findMemberLogs = vi.fn(async () => [{ filePath: transcriptPath }]);
|
||||
const readRecentBootstrapTranscriptOutcome = vi.fn(async () => ({
|
||||
kind: 'success',
|
||||
observedAt: '2026-05-24T09:25:42.904Z',
|
||||
source: 'member_briefing',
|
||||
}));
|
||||
const readBootstrapTranscriptOutcomesInProjectRoot = vi.fn(async () => []);
|
||||
harness.memberLogsFinder = { findMemberLogs };
|
||||
harness.readRecentBootstrapTranscriptOutcome = readRecentBootstrapTranscriptOutcome;
|
||||
harness.readBootstrapTranscriptOutcomesInProjectRoot =
|
||||
readBootstrapTranscriptOutcomesInProjectRoot;
|
||||
|
||||
const firstOutcome = await harness.findBootstrapTranscriptOutcome(teamName, memberName, 123);
|
||||
vi.setSystemTime(new Date('2026-05-03T12:00:06.000Z'));
|
||||
const secondOutcome = await harness.findBootstrapTranscriptOutcome(teamName, memberName, 123);
|
||||
|
||||
expect(secondOutcome).toEqual(firstOutcome);
|
||||
expect(findMemberLogs).toHaveBeenCalledTimes(1);
|
||||
expect(readRecentBootstrapTranscriptOutcome).toHaveBeenCalledTimes(1);
|
||||
expect(readBootstrapTranscriptOutcomesInProjectRoot).toHaveBeenCalledTimes(1);
|
||||
|
||||
vi.setSystemTime(new Date('2026-05-03T12:00:11.000Z'));
|
||||
await harness.findBootstrapTranscriptOutcome(teamName, memberName, 123);
|
||||
|
||||
expect(findMemberLogs).toHaveBeenCalledTimes(2);
|
||||
expect(readRecentBootstrapTranscriptOutcome).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('does not use persisted bootstrap transcript outcome lookup cache for tracked runs', async () => {
|
||||
const teamName = 'zz-unit-bootstrap-transcript-active-lookup-cache';
|
||||
const memberName = 'tom';
|
||||
const transcriptPath = path.join(tempProjectsBase, 'bootstrap-active-lookup-cache.jsonl');
|
||||
const svc = new TeamProvisioningService();
|
||||
const harness = svc as any;
|
||||
const findMemberLogs = vi.fn(async () => [{ filePath: transcriptPath }]);
|
||||
const readRecentBootstrapTranscriptOutcome = vi.fn(async () => ({
|
||||
kind: 'success',
|
||||
observedAt: '2026-05-24T09:25:42.904Z',
|
||||
source: 'member_briefing',
|
||||
}));
|
||||
harness.memberLogsFinder = { findMemberLogs };
|
||||
harness.readRecentBootstrapTranscriptOutcome = readRecentBootstrapTranscriptOutcome;
|
||||
harness.readBootstrapTranscriptOutcomesInProjectRoot = vi.fn(async () => []);
|
||||
harness.aliveRunByTeam.set(teamName, 'run-1');
|
||||
|
||||
await harness.findBootstrapTranscriptOutcome(teamName, memberName, 123);
|
||||
await harness.findBootstrapTranscriptOutcome(teamName, memberName, 123);
|
||||
|
||||
expect(findMemberLogs).toHaveBeenCalledTimes(2);
|
||||
expect(readRecentBootstrapTranscriptOutcome).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('caches persisted member spawn statuses between close polling reads', async () => {
|
||||
const teamName = 'zz-unit-persisted-status-cache';
|
||||
const svc = new TeamProvisioningService();
|
||||
|
|
|
|||
Loading…
Reference in a new issue