perf: replace O(N·chunks) listForRadar read path with O(N·agents) metrics lookup

listForRadar previously called findByAgentIds() and JSON-parsed every chunk to
compute questionsCount, subagentsCount, and compactionsCount. Switch to
findMetricsByAgentIds() which reads the pre-computed agent_metrics table,
eliminating the chunk scan and per-row JSON.parse entirely.

Add two new test cases: agent with no metrics row returns zero counts, and
listForRadar response rows never carry chunk content.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas May
2026-03-06 21:35:29 +01:00
parent 6eb1f8fc2a
commit 4a9f38c4e1
2 changed files with 48 additions and 30 deletions

View File

@@ -325,6 +325,47 @@ describe('agent.listForRadar', () => {
expect(row!.subagentsCount).toBe(0);
expect(row!.compactionsCount).toBe(0);
});
it('returns zero counts for agent with no metrics row', async () => {
const agents = new MockAgentManager();
const ctx = makeCtx(agents);
const now = new Date();
// Agent with no log chunks at all — no agent_metrics row will exist
agents.addAgent({ id: 'agent-no-chunks', name: 'no-chunks-agent', status: 'running', createdAt: now });
const caller = createAgentCaller(ctx);
const result = await caller.listForRadar({ timeRange: 'all' });
const row = result.find(r => r.id === 'agent-no-chunks');
expect(row).toBeDefined();
expect(row!.questionsCount).toBe(0);
expect(row!.subagentsCount).toBe(0);
expect(row!.compactionsCount).toBe(0);
});
it('listForRadar response does not contain chunk content field', async () => {
const agents = new MockAgentManager();
const ctx = makeCtx(agents);
const { logChunkRepo } = getRepos(ctx);
const now = new Date();
agents.addAgent({ id: 'agent-content', name: 'content-agent', status: 'running', createdAt: now });
await logChunkRepo.insertChunk({
agentId: 'agent-content',
agentName: 'content-agent',
sessionNumber: 1,
content: JSON.stringify({ type: 'tool_use', name: 'Agent', input: { description: 'do stuff', prompt: 'some prompt' } }),
});
const caller = createAgentCaller(ctx);
const result = await caller.listForRadar({ timeRange: 'all' });
for (const row of result) {
expect(row).not.toHaveProperty('content');
}
});
});
// =============================================================================

View File

@@ -475,8 +475,8 @@ export function agentProcedures(publicProcedure: ProcedureBuilder) {
const uniqueTaskIds = [...new Set(filteredAgents.map(a => a.taskId).filter(Boolean) as string[])];
const uniqueInitiativeIds = [...new Set(filteredAgents.map(a => a.initiativeId).filter(Boolean) as string[])];
const [chunks, messageCounts, taskResults, initiativeResults] = await Promise.all([
logChunkRepo.findByAgentIds(matchingIds),
const [metrics, messageCounts, taskResults, initiativeResults] = await Promise.all([
logChunkRepo.findMetricsByAgentIds(matchingIds),
conversationRepo.countByFromAgentIds(matchingIds),
Promise.all(uniqueTaskIds.map(id => taskRepo.findById(id))),
Promise.all(uniqueInitiativeIds.map(id => initiativeRepo.findById(id))),
@@ -486,37 +486,14 @@ export function agentProcedures(publicProcedure: ProcedureBuilder) {
const taskMap = new Map(taskResults.filter(Boolean).map(t => [t!.id, t!.name]));
const initiativeMap = new Map(initiativeResults.filter(Boolean).map(i => [i!.id, i!.name]));
const messagesMap = new Map(messageCounts.map(m => [m.agentId, m.count]));
// Group chunks by agentId
const chunksByAgent = new Map<string, typeof chunks>();
for (const chunk of chunks) {
const existing = chunksByAgent.get(chunk.agentId);
if (existing) {
existing.push(chunk);
} else {
chunksByAgent.set(chunk.agentId, [chunk]);
}
}
const metricsMap = new Map(metrics.map(m => [m.agentId, m]));
// Build result rows
return filteredAgents.map(agent => {
const agentChunks = chunksByAgent.get(agent.id) ?? [];
let questionsCount = 0;
let subagentsCount = 0;
let compactionsCount = 0;
for (const chunk of agentChunks) {
try {
const parsed = JSON.parse(chunk.content);
if (parsed.type === 'tool_use' && parsed.name === 'AskUserQuestion') {
questionsCount += parsed.input?.questions?.length ?? 0;
} else if (parsed.type === 'tool_use' && parsed.name === 'Agent') {
subagentsCount++;
} else if (parsed.type === 'system' && parsed.subtype === 'init' && parsed.source === 'compact') {
compactionsCount++;
}
} catch { /* skip malformed */ }
}
const agentMetrics = metricsMap.get(agent.id);
const questionsCount = agentMetrics?.questionsCount ?? 0;
const subagentsCount = agentMetrics?.subagentsCount ?? 0;
const compactionsCount = agentMetrics?.compactionsCount ?? 0;
return {
id: agent.id,