From d0c6d1f6d042807e1d3af065fb4c1587732922a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 16 Apr 2026 21:59:18 -0400 Subject: [PATCH] perf(db): resolve chain of command in a single recursive CTE to eliminate N+1 queries --- src/main/database/agents-hierarchy.ts | 50 ++++++++++++++++----------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/main/database/agents-hierarchy.ts b/src/main/database/agents-hierarchy.ts index 9760a99e..7e873078 100644 --- a/src/main/database/agents-hierarchy.ts +++ b/src/main/database/agents-hierarchy.ts @@ -18,27 +18,35 @@ export function buildAgentHierarchyMethods( ) { return { getChainOfCommand(agentId: string): AgentRecord[] { - const chain: AgentRecord[] = []; - const visited = new Set(); - let current: AgentRecord | undefined; - try { - current = deps.getAgent(agentId); - } catch { - return chain; - } - while (current?.reportsTo) { - if (visited.has(current.reportsTo)) { - break; - } - visited.add(current.reportsTo); - try { - current = deps.getAgent(current.reportsTo); - chain.push(current); - } catch { - break; - } - } - return chain; + const rows = readAll( + `WITH RECURSIVE chain AS ( + SELECT id, company_id as companyId, name, role, title, department, status, + reports_to as reportsTo, connector_id as connectorId, workspace_id as workspaceId, + model, capabilities, budget_monthly_usd as budgetMonthlyUsd, + spent_monthly_usd as spentMonthlyUsd, metadata_json as metadataJson, + heartbeat_enabled as heartbeatEnabled, heartbeat_interval_sec as heartbeatIntervalSec, + last_heartbeat_at as lastHeartbeatAt, created_at as createdAt, updated_at as updatedAt, + 0 as depth + FROM agents WHERE id = ? + UNION ALL + SELECT a.id, a.company_id, a.name, a.role, a.title, a.department, a.status, + a.reports_to, a.connector_id, a.workspace_id, + a.model, a.capabilities, a.budget_monthly_usd, + a.spent_monthly_usd, a.metadata_json, + a.heartbeat_enabled, a.heartbeat_interval_sec, + a.last_heartbeat_at, a.created_at, a.updated_at, + c.depth + 1 + FROM agents a JOIN chain c ON a.id = c.reportsTo + WHERE c.depth < 20 + ) + SELECT * FROM chain ORDER BY depth`, + agentId, + ); + // Skip self (depth 0); return ancestors from direct manager upward. + return rows.slice(1).map((record) => ({ + ...record, + heartbeatEnabled: toBoolean(record.heartbeatEnabled), + })) as AgentRecord[]; }, getDirectReports(agentId: string, companyId: string): AgentRecord[] {