From 83a6b334c3ab56914c8fd5fb549860d2cb213b1f Mon Sep 17 00:00:00 2001 From: canvas-bot Date: Mon, 15 Jun 2026 21:42:58 +0400 Subject: [PATCH] perf(agent): cap recall + 2s deadline so LLM fallback always fires Trace showed every Assistant submit triggered 60+ sequential GET /api/ea2/flow/{key} requests via agentMemory.recall before llmClient.chat ran. With a slow EA2 demo, recall took longer than the test wait so the LLM fetch never started, the agent reply never rendered, and [llm] telemetry never logged. Two fixes: 1. agentMemory.listAll(cap) - default 200, recall uses 30. Edges are returned newest-first by EA2 so the 30-cap keeps the most recent vault entries and drops the long-tail history that was pinning the network. 2. routeAgentInput wraps recall in a 2s AbortController. If recall doesn't finish in 2s, hints[] stays empty and llmFallback runs immediately. Recall is best-effort context, never a blocker. --- src/lib/agentMemory.ts | 7 ++++--- src/lib/agentTools.ts | 7 ++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lib/agentMemory.ts b/src/lib/agentMemory.ts index 2582d1c..b8d18d5 100644 --- a/src/lib/agentMemory.ts +++ b/src/lib/agentMemory.ts @@ -97,7 +97,7 @@ export const agentMemory = { return mem._key; }, - async listAll(email: string, signal?: AbortSignal): Promise { + async listAll(email: string, signal?: AbortSignal, cap = 200): Promise { const vaultKey = vaultKeyFor(email); const ok = await ensureVault(vaultKey, email, signal); if (!ok) return []; @@ -107,7 +107,8 @@ export const agentMemory = { const memKeys = (edges as any[]) .filter((e) => e?.role === "presentation") .map((e) => (e._to || "").split("/").pop()) - .filter(Boolean) as string[]; + .filter(Boolean) + .slice(0, cap) as string[]; const memos = await Promise.all( memKeys.map(async (k) => { try { @@ -132,7 +133,7 @@ export const agentMemory = { * Score = count of unique stem matches. Ties broken by recency. */ async recall(email: string, query: string, limit = 5, signal?: AbortSignal): Promise { - const all = await agentMemory.listAll(email, signal); + const all = await agentMemory.listAll(email, signal, 30); if (!query.trim() || all.length === 0) return all.slice(0, limit); const stems = new Set( query.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2) diff --git a/src/lib/agentTools.ts b/src/lib/agentTools.ts index c374914..d88f157 100644 --- a/src/lib/agentTools.ts +++ b/src/lib/agentTools.ts @@ -240,7 +240,12 @@ export async function routeAgentInput(text: string, ctx: ToolContext): Promise recallDeadline.abort(), 2000); + hints = await agentMemory.recall(ctx.userEmail, trimmed, 3, recallDeadline.signal); + clearTimeout(recallTimer); + } catch { /* recall is best-effort; never blocks the user */ } const llmReply = await llmFallback(trimmed, ctx, hints); if (llmReply) return llmReply; if (hints.length > 0) {