perf: share bootstrap transcript tail parse across members

During launch, the bootstrap-wait loop polls each member and, per member, re-read
and re-JSON.parsed the same growing transcript tail (readRecentBootstrapTranscriptOutcome
was the top main-thread JS hotspot at ~21% during bootstrap, ~40% with its helpers).
The same file was parsed once per member per poll.

Memoize the parsed tail by (filePath, mtime, size) in a shared cache so the file is
read + parsed once per change and reused across all members. The per-member filter
and failure/success scan is byte-for-byte the same logic; only the redundant read +
JSON.parse is removed. Cache is bounded (LRU, same cap as the outcome cache) and
invalidated on mtime/size change, matching the existing outcome cache semantics.

Adds a test asserting the tail is parsed once and shared while per-member outcome
detection is unchanged.
This commit is contained in:
777genius 2026-05-30 01:05:54 +03:00
parent 5d63ecfe32
commit 35b76f1354
2 changed files with 135 additions and 29 deletions

View file

@ -704,6 +704,19 @@ interface BootstrapTranscriptOutcomeCandidate {
parsedAgentName: string | null;
}
interface ParsedBootstrapTranscriptTailLine {
rawTimestamp: string | null;
timestampMs: number;
text: string | null;
parsedAgentName: string | null;
}
interface ParsedBootstrapTranscriptTailCacheEntry {
mtimeMs: number;
size: number;
lines: ParsedBootstrapTranscriptTailLine[];
}
import type {
ActiveToolCall,
AgentActionMode,
@ -3375,6 +3388,13 @@ export class TeamProvisioningService {
string,
BootstrapTranscriptOutcomeCacheEntry
>();
// Shared parsed-tail cache keyed by filePath (validated by mtime+size) so the
// same growing transcript is read + JSON.parsed ONCE per change instead of once
// per member per poll. The per-member outcome scan below is unchanged.
private readonly parsedBootstrapTranscriptTailCache = new Map<
string,
ParsedBootstrapTranscriptTailCacheEntry
>();
private readonly bootstrapTranscriptOutcomeLookupCache = new Map<
string,
BootstrapTranscriptOutcomeLookupCacheEntry
@ -30303,44 +30323,24 @@ export class TeamProvisioningService {
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) {
return null;
}
await handle.read(buffer, 0, buffer.length, start);
const lines = buffer.toString('utf8').split('\n');
if (start > 0) {
lines.shift();
}
// Parse the transcript tail once per (filePath, mtime, size) and share it
// across members. The per-member filter/scan below is byte-for-byte the same
// logic as before; only the redundant read + JSON.parse is now memoized.
const parsedLines = await this.getParsedBootstrapTranscriptTail(handle, filePath, stat);
const shouldCollectBootstrapContext = options.allowAnonymousFailure !== true;
const bootstrapContextMembers = new Set<string>();
const candidates: BootstrapTranscriptOutcomeCandidate[] = [];
for (const rawLine of lines) {
const line = rawLine?.trim();
if (!line) continue;
let parsed: { timestamp?: unknown } | null = null;
try {
parsed = JSON.parse(line) as { timestamp?: unknown };
} catch {
continue;
}
const timestampMs =
typeof parsed.timestamp === 'string' ? Date.parse(parsed.timestamp) : Number.NaN;
for (const parsedLine of parsedLines) {
const { timestampMs, parsedAgentName, text, rawTimestamp } = parsedLine;
if (sinceMs != null && (!Number.isFinite(timestampMs) || timestampMs < sinceMs)) {
continue;
}
const parsedAgentName =
typeof (parsed as { agentName?: unknown }).agentName === 'string'
? (parsed as { agentName?: string }).agentName?.trim().toLowerCase() || null
: null;
if (
parsedAgentName &&
!matchesObservedMemberNameForExpected(parsedAgentName, normalizedMemberName)
) {
continue;
}
const text = extractTranscriptMessageText(parsed);
if (!text) {
continue;
}
@ -30354,9 +30354,7 @@ export class TeamProvisioningService {
candidates.push({
text,
observedAt:
typeof parsed.timestamp === 'string' && parsed.timestamp.trim().length > 0
? parsed.timestamp.trim()
: new Date().toISOString(),
rawTimestamp && rawTimestamp.length > 0 ? rawTimestamp : new Date().toISOString(),
parsedAgentName,
});
}
@ -30428,6 +30426,73 @@ export class TeamProvisioningService {
].join('\0');
}
private async getParsedBootstrapTranscriptTail(
handle: fs.promises.FileHandle,
filePath: string,
stat: { mtimeMs: number; size: number }
): Promise<ParsedBootstrapTranscriptTailLine[]> {
const cached = this.parsedBootstrapTranscriptTailCache.get(filePath);
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
return cached.lines;
}
const lines: ParsedBootstrapTranscriptTailLine[] = [];
const start = Math.max(0, stat.size - TeamProvisioningService.BOOTSTRAP_FAILURE_TAIL_BYTES);
const length = stat.size - start;
if (length > 0) {
const buffer = Buffer.alloc(length);
await handle.read(buffer, 0, length, start);
const rawLines = buffer.toString('utf8').split('\n');
if (start > 0) {
rawLines.shift();
}
for (const rawLine of rawLines) {
const line = rawLine?.trim();
if (!line) continue;
let parsed: { timestamp?: unknown; agentName?: unknown } | null = null;
try {
parsed = JSON.parse(line) as { timestamp?: unknown; agentName?: unknown };
} catch {
continue;
}
const rawTimestamp =
typeof parsed.timestamp === 'string' && parsed.timestamp.trim().length > 0
? parsed.timestamp.trim()
: null;
const timestampMs =
typeof parsed.timestamp === 'string' ? Date.parse(parsed.timestamp) : Number.NaN;
const parsedAgentName =
typeof parsed.agentName === 'string'
? parsed.agentName.trim().toLowerCase() || null
: null;
const text = extractTranscriptMessageText(parsed);
lines.push({ rawTimestamp, timestampMs, text, parsedAgentName });
}
}
this.setParsedBootstrapTranscriptTailCacheEntry(filePath, {
mtimeMs: stat.mtimeMs,
size: stat.size,
lines,
});
return lines;
}
private setParsedBootstrapTranscriptTailCacheEntry(
filePath: string,
entry: ParsedBootstrapTranscriptTailCacheEntry
): void {
if (
!this.parsedBootstrapTranscriptTailCache.has(filePath) &&
this.parsedBootstrapTranscriptTailCache.size >=
TeamProvisioningService.BOOTSTRAP_TRANSCRIPT_OUTCOME_CACHE_MAX_ENTRIES
) {
const oldestKey = this.parsedBootstrapTranscriptTailCache.keys().next().value;
if (oldestKey) {
this.parsedBootstrapTranscriptTailCache.delete(oldestKey);
}
}
this.parsedBootstrapTranscriptTailCache.set(filePath, entry);
}
private setBootstrapTranscriptOutcomeCacheEntry(
cacheKey: string,
entry: BootstrapTranscriptOutcomeCacheEntry

View file

@ -21167,6 +21167,47 @@ describe('TeamProvisioningService', () => {
});
});
it('parses a bootstrap transcript tail once and shares it across members', async () => {
const teamName = 'zz-unit-bootstrap-transcript-shared-parse';
const transcriptPath = path.join(tempProjectsBase, 'bootstrap-shared-parse.jsonl');
await fsPromises.writeFile(
transcriptPath,
`${JSON.stringify({
timestamp: '2026-05-24T09:25:42.904Z',
agentName: 'alice',
text: `member briefing for alice on team "${teamName}" (${teamName}). Ready.`,
})}\n`,
'utf8'
);
const updatedAt = new Date(Date.now() + 5_000);
await fsPromises.utimes(transcriptPath, updatedAt, updatedAt);
const svc = new TeamProvisioningService();
const aliceOutcome = await privateHarness(svc).readRecentBootstrapTranscriptOutcome(
transcriptPath,
null,
'alice',
teamName
);
const bobOutcome = await privateHarness(svc).readRecentBootstrapTranscriptOutcome(
transcriptPath,
null,
'bob',
teamName
);
// Per-member detection is unchanged: alice's briefing is a success, the same
// line is not attributed to bob.
expect(aliceOutcome).toMatchObject({ kind: 'success', source: 'member_briefing' });
expect(bobOutcome).toBeNull();
// The transcript tail is parsed once and shared: a single cache entry for the
// file rather than one parse per member.
expect((svc as unknown as Record<string, Map<string, unknown>>).parsedBootstrapTranscriptTailCache.size).toBe(
1
);
});
it('caches persisted bootstrap transcript outcome lookup between close polling reads', async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-05-03T12:00:00.000Z'));