perf: cache bootstrap transcript outcomes

This commit is contained in:
777genius 2026-05-29 12:58:15 +03:00
parent 0b97985474
commit fa242d9ff6
2 changed files with 132 additions and 2 deletions

View file

@ -687,6 +687,12 @@ type BootstrapTranscriptOutcome =
reason: string;
};
interface BootstrapTranscriptOutcomeCacheEntry {
mtimeMs: number;
size: number;
outcome: BootstrapTranscriptOutcome | null;
}
import type {
ActiveToolCall,
AgentActionMode,
@ -3290,6 +3296,7 @@ export class TeamProvisioningService {
private static readonly SAME_TEAM_PERSIST_RETRY_MS = 2_000;
private static readonly AGENT_RUNTIME_SNAPSHOT_CACHE_TTL_MS = 2_000;
private static readonly AGENT_RUNTIME_RESOURCE_HISTORY_LIMIT = 60;
private static readonly BOOTSTRAP_TRANSCRIPT_OUTCOME_CACHE_MAX_ENTRIES = 2_048;
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;
@ -3350,6 +3357,10 @@ export class TeamProvisioningService {
string,
PersistedTranscriptClaudeLogsCacheEntry
>();
private readonly bootstrapTranscriptOutcomeCache = new Map<
string,
BootstrapTranscriptOutcomeCacheEntry
>();
private readonly teamOpLocks = new Map<string, Promise<void>>();
private readonly leadInboxRelayInFlight = new Map<string, Promise<number>>();
private readonly relayedLeadInboxMessageIds = new Map<string, Set<string>>();
@ -30102,12 +30113,24 @@ export class TeamProvisioningService {
.filter(Boolean)
)
);
const cacheKey = this.buildBootstrapTranscriptOutcomeCacheKey({
filePath,
sinceMs,
memberName: normalizedMemberName,
teamName,
allowAnonymousFailure: options.allowAnonymousFailure === true,
contextMemberNames,
});
try {
handle = await fs.promises.open(filePath, 'r');
const stat = await handle.stat();
if (!stat.isFile() || stat.size <= 0) {
return null;
}
const cached = this.bootstrapTranscriptOutcomeCache.get(cacheKey);
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
return cached.outcome;
}
const start = Math.max(0, stat.size - TeamProvisioningService.BOOTSTRAP_FAILURE_TAIL_BYTES);
const buffer = Buffer.alloc(stat.size - start);
if (buffer.length === 0) {
@ -30155,6 +30178,7 @@ export class TeamProvisioningService {
}
const hasUnambiguousMatchingBootstrapContext =
bootstrapContextMembers.size === 1 && bootstrapContextMembers.has(normalizedMemberName);
let outcome: BootstrapTranscriptOutcome | null = null;
for (let index = lines.length - 1; index >= 0; index -= 1) {
const line = lines[index]?.trim();
if (!line) continue;
@ -30196,13 +30220,21 @@ export class TeamProvisioningService {
) {
continue;
}
return { kind: 'failure', observedAt, reason };
outcome = { kind: 'failure', observedAt, reason };
break;
}
const successSource = getBootstrapTranscriptSuccessSource(text, teamName, memberName);
if (successSource) {
return { kind: 'success', observedAt, source: successSource };
outcome = { kind: 'success', observedAt, source: successSource };
break;
}
}
this.setBootstrapTranscriptOutcomeCacheEntry(cacheKey, {
mtimeMs: stat.mtimeMs,
size: stat.size,
outcome,
});
return outcome;
} catch {
return null;
} finally {
@ -30212,6 +30244,46 @@ export class TeamProvisioningService {
return null;
}
private buildBootstrapTranscriptOutcomeCacheKey(input: {
filePath: string;
sinceMs: number | null;
memberName: string;
teamName: string;
allowAnonymousFailure: boolean;
contextMemberNames: readonly string[];
}): string {
const normalizedContextMembers = Array.from(
new Set(input.contextMemberNames.map((name) => name.trim().toLowerCase()).filter(Boolean))
)
.sort()
.join('\0');
return [
input.filePath,
input.sinceMs ?? '',
input.memberName,
input.teamName.trim().toLowerCase(),
input.allowAnonymousFailure ? '1' : '0',
normalizedContextMembers,
].join('\0');
}
private setBootstrapTranscriptOutcomeCacheEntry(
cacheKey: string,
entry: BootstrapTranscriptOutcomeCacheEntry
): void {
if (
!this.bootstrapTranscriptOutcomeCache.has(cacheKey) &&
this.bootstrapTranscriptOutcomeCache.size >=
TeamProvisioningService.BOOTSTRAP_TRANSCRIPT_OUTCOME_CACHE_MAX_ENTRIES
) {
const oldestKey = this.bootstrapTranscriptOutcomeCache.keys().next().value;
if (oldestKey) {
this.bootstrapTranscriptOutcomeCache.delete(oldestKey);
}
}
this.bootstrapTranscriptOutcomeCache.set(cacheKey, entry);
}
private async readBootstrapTranscriptOutcomesInProjectRoot(
teamName: string,
memberName: string,

View file

@ -623,6 +623,13 @@ type TeamProvisioningServicePrivateHarness = {
readProcessUsageStatsByPid: (
pids: readonly number[]
) => Promise<Map<number, { rssBytes?: number; cpuPercent?: number }>>;
readRecentBootstrapTranscriptOutcome: (
filePath: string,
sinceMs: number | null,
memberName: string,
teamName: string,
options?: { allowAnonymousFailure?: boolean; contextMemberNames?: readonly string[] }
) => Promise<{ kind: string; observedAt: string; source?: string; reason?: string } | null>;
};
function privateHarness(svc: TeamProvisioningService): TeamProvisioningServicePrivateHarness {
@ -21025,6 +21032,57 @@ describe('TeamProvisioningService', () => {
expect(result.statuses.tom?.runtimeDiagnosticSeverity).toBeUndefined();
});
it('refreshes cached bootstrap transcript outcome when the transcript file changes', async () => {
const teamName = 'zz-unit-bootstrap-transcript-cache-refresh';
const memberName = 'tom';
const transcriptPath = path.join(tempProjectsBase, 'bootstrap-cache.jsonl');
const writeTranscriptText = async (text: string, timestamp: string): Promise<void> => {
await fsPromises.writeFile(
transcriptPath,
`${JSON.stringify({
timestamp,
agentName: memberName,
text,
})}\n`,
'utf8'
);
const updatedAt = new Date(Date.now() + 5_000);
await fsPromises.utimes(transcriptPath, updatedAt, updatedAt);
};
await writeTranscriptText(
`member briefing for ${memberName} on team "${teamName}" (${teamName}). Ready.`,
'2026-05-24T09:25:42.904Z'
);
const svc = new TeamProvisioningService();
const firstOutcome = await privateHarness(svc).readRecentBootstrapTranscriptOutcome(
transcriptPath,
null,
memberName,
teamName
);
expect(firstOutcome).toMatchObject({ kind: 'success', source: 'member_briefing' });
await writeTranscriptText(
'bootstrap failed: model not found during teammate startup',
'2026-05-24T09:26:42.904Z'
);
const secondOutcome = await privateHarness(svc).readRecentBootstrapTranscriptOutcome(
transcriptPath,
null,
memberName,
teamName
);
expect(secondOutcome).toMatchObject({
kind: 'failure',
reason: 'bootstrap failed: model not found during teammate startup',
});
});
it('does not heal cleanup-finalized launch failures from stale bootstrap-state confirmation', async () => {
allowConsoleLogs();
const teamName = 'zz-unit-cleanup-finalized-stale-bootstrap-ignored';