From d979eeda9f11f017b034f844df06493c00618212 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 2 Mar 2026 19:47:38 +0000 Subject: [PATCH] perf(runtime): reduce slack prep and qmd cache-key overhead --- src/memory/search-manager.ts | 34 ++++++++------------ src/slack/monitor/message-handler/prepare.ts | 12 ++++--- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/memory/search-manager.ts b/src/memory/search-manager.ts index 95b23379e..64c48078a 100644 --- a/src/memory/search-manager.ts +++ b/src/memory/search-manager.ts @@ -24,8 +24,9 @@ export async function getMemorySearchManager(params: { const resolved = resolveMemoryBackendConfig(params); if (resolved.backend === "qmd" && resolved.qmd) { const statusOnly = params.purpose === "status"; - const cacheKey = buildQmdCacheKey(params.agentId, resolved.qmd); + let cacheKey: string | undefined; if (!statusOnly) { + cacheKey = buildQmdCacheKey(params.agentId, resolved.qmd); const cached = QMD_MANAGER_CACHE.get(cacheKey); if (cached) { return { manager: cached }; @@ -51,9 +52,15 @@ export async function getMemorySearchManager(params: { return await MemoryIndexManager.get(params); }, }, - () => QMD_MANAGER_CACHE.delete(cacheKey), + () => { + if (cacheKey) { + QMD_MANAGER_CACHE.delete(cacheKey); + } + }, ); - QMD_MANAGER_CACHE.set(cacheKey, wrapper); + if (cacheKey) { + QMD_MANAGER_CACHE.set(cacheKey, wrapper); + } return { manager: wrapper }; } } catch (err) { @@ -217,22 +224,7 @@ class FallbackMemoryManager implements MemorySearchManager { } function buildQmdCacheKey(agentId: string, config: ResolvedQmdConfig): string { - return `${agentId}:${stableSerialize(config)}`; -} - -function stableSerialize(value: unknown): string { - return JSON.stringify(sortValue(value)); -} - -function sortValue(value: unknown): unknown { - if (Array.isArray(value)) { - return value.map((entry) => sortValue(entry)); - } - if (value && typeof value === "object") { - const sortedEntries = Object.keys(value as Record) - .toSorted((a, b) => a.localeCompare(b)) - .map((key) => [key, sortValue((value as Record)[key])]); - return Object.fromEntries(sortedEntries); - } - return value; + // ResolvedQmdConfig is assembled in a stable field order in resolveMemoryBackendConfig. + // Fast stringify avoids deep key-sorting overhead on this hot path. + return `${agentId}:${JSON.stringify(config)}`; } diff --git a/src/slack/monitor/message-handler/prepare.ts b/src/slack/monitor/message-handler/prepare.ts index 13ca763c1..2819d5e59 100644 --- a/src/slack/monitor/message-handler/prepare.ts +++ b/src/slack/monitor/message-handler/prepare.ts @@ -66,13 +66,17 @@ export async function prepareSlackMessage(params: { topic?: string; purpose?: string; } = {}; - let channelType = message.channel_type; - if (!channelType || channelType !== "im") { + let resolvedChannelType = normalizeSlackChannelType(message.channel_type, message.channel); + // D-prefixed channels are always direct messages. Skip channel lookups in + // that common path to avoid an unnecessary API round-trip. + if (resolvedChannelType !== "im" && (!message.channel_type || message.channel_type !== "im")) { channelInfo = await ctx.resolveChannelName(message.channel); - channelType = channelType ?? channelInfo.type; + resolvedChannelType = normalizeSlackChannelType( + message.channel_type ?? channelInfo.type, + message.channel, + ); } const channelName = channelInfo?.name; - const resolvedChannelType = normalizeSlackChannelType(channelType, message.channel); const isDirectMessage = resolvedChannelType === "im"; const isGroupDm = resolvedChannelType === "mpim"; const isRoom = resolvedChannelType === "channel" || resolvedChannelType === "group";