[object Object]

← back to Ken

Ken: retry/backoff in geminiAnalyze for shared-Ollama 'server busy' contention (stays all-local, best-effort)

d83c6eeb36773adbd4a2031b49d9e0bffdeb7601 · 2026-07-14 12:34:27 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit d83c6eeb36773adbd4a2031b49d9e0bffdeb7601
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 14 12:34:27 2026 -0700

    Ken: retry/backoff in geminiAnalyze for shared-Ollama 'server busy' contention (stays all-local, best-effort)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 kalshi-dash/server.js | 69 ++++++++++++++++++++++++++++++++-------------------
 1 file changed, 44 insertions(+), 25 deletions(-)

diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index 1105f63..82fe64b 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -142,32 +142,51 @@ const KEN_LOCAL_MODEL = process.env.KEN_LOCAL_MODEL || 'hermes3:8b';
 const GEMINI_MODEL = KEN_LOCAL_MODEL; // kept name so the dashboard's model label still resolves
 
 async function geminiAnalyze(prompt, maxTokens = 1024) {
-  try {
-    const controller = new AbortController();
-    const timeout = setTimeout(() => controller.abort(), 45000);
-    const res = await fetch(`${OLLAMA_URL}/api/generate`, {
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json' },
-      signal: controller.signal,
-      body: JSON.stringify({
-        model: KEN_LOCAL_MODEL,
-        prompt,
-        stream: false,
-        format: 'json', // constrain output to valid JSON (callers JSON.parse the result)
-        options: { temperature: 0.3, num_predict: maxTokens },
-      }),
-    });
-    clearTimeout(timeout);
-    if (!res.ok) return null;
-    const data = await res.json();
-    let text = data?.response || null;
-    // thinking-style models (qwen etc.) may wrap output in <think>…</think>; strip it
-    if (text) text = text.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
-    return text || null;
-  } catch (e) {
-    console.log(`[Ken/Local] Error: ${e.message}`);
-    return null;
+  // The Mac's Ollama is shared with other pipelines (vision enrichment etc.), so under
+  // load it can 200-OK with an empty response + {error:"server busy … maximum pending
+  // requests exceeded"}. Retry those transients with backoff so Ken's AI layer rides
+  // through contention instead of silently switching off. Still all-LOCAL ($0).
+  const MAX_TRIES = 3;
+  for (let attempt = 1; attempt <= MAX_TRIES; attempt++) {
+    try {
+      const controller = new AbortController();
+      const timeout = setTimeout(() => controller.abort(), 45000);
+      const res = await fetch(`${OLLAMA_URL}/api/generate`, {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        signal: controller.signal,
+        body: JSON.stringify({
+          model: KEN_LOCAL_MODEL,
+          prompt,
+          stream: false,
+          format: 'json', // constrain output to valid JSON (callers JSON.parse the result)
+          options: { temperature: 0.3, num_predict: maxTokens },
+        }),
+      });
+      clearTimeout(timeout);
+      if (!res.ok) return null;
+      const data = await res.json();
+      // Ollama-busy transient: retry with backoff before giving up.
+      if (data?.error && /busy|pending requests|overloaded/i.test(data.error)) {
+        if (attempt < MAX_TRIES) {
+          console.log(`[Ken/Local] Ollama busy (try ${attempt}/${MAX_TRIES}) — backing off`);
+          await new Promise(r => setTimeout(r, 1500 * attempt));
+          continue;
+        }
+        console.log(`[Ken/Local] Ollama still busy after ${MAX_TRIES} tries — skipping AI enhance this cycle`);
+        return null;
+      }
+      let text = data?.response || null;
+      // thinking-style models (qwen etc.) may wrap output in <think>…</think>; strip it
+      if (text) text = text.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
+      return text || null;
+    } catch (e) {
+      console.log(`[Ken/Local] Error (try ${attempt}/${MAX_TRIES}): ${e.message}`);
+      if (attempt < MAX_TRIES) { await new Promise(r => setTimeout(r, 1500 * attempt)); continue; }
+      return null;
+    }
   }
+  return null;
 }
 
 // AI-Enhanced Signal Scoring — local Ollama analyzes top signals for smarter predictions

← 16c240e Ken: fix Weather page 400 — server now handles 'get_data' ac  ·  back to Ken  ·  Ken: scoped set-confidence.sh — fix endpoint to /api/trading c49a8e1 →