[object Object]

← back to Ventura Corridor

feat(news): nightly launchd jobs + patient summarizer backoff

0a78f9e7240402b261b0e1eb7a478e2e31275f81 · 2026-05-07 10:38:50 -0700 · SteveStudio2

- launchd/com.steve.ventura-corridor-news-scrape.plist
    Runs `npm run crawl:news` at 03:15 daily. Polite defaults
    (4 concurrent, 500ms host gap, 10s timeout).
- launchd/com.steve.ventura-corridor-news-summarize.plist
    Runs `npm run news:summarize -- --limit=200` at 05:30 daily, two hours
    after the scraper, so fresh items get qwen3:14b summaries before the
    morning corpus-summary email at 09:00.
- Both registered + loaded into ~/Library/LaunchAgents and copied into
  the project's launchd/ dir for version-tracking + reproducibility.
- summarize_news.ts backoff bumped from 8s/16s/32s/64s (max ~2min) to
  60s/120s/240s/480s/960s/1920s (max ~62min). Magazine-gen 2000-feature
  runs hold Mac1 for hours; the old backoff exhausted before capacity
  opened. Patient backoff lets the 05:30 launchd drain even if the
  generator runs long.

Verified: launchctl list shows both plists loaded; Steve's 2000-feature
generate_features will finish before 05:30 most nights.

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

Files touched

Diff

commit 0a78f9e7240402b261b0e1eb7a478e2e31275f81
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Thu May 7 10:38:50 2026 -0700

    feat(news): nightly launchd jobs + patient summarizer backoff
    
    - launchd/com.steve.ventura-corridor-news-scrape.plist
        Runs `npm run crawl:news` at 03:15 daily. Polite defaults
        (4 concurrent, 500ms host gap, 10s timeout).
    - launchd/com.steve.ventura-corridor-news-summarize.plist
        Runs `npm run news:summarize -- --limit=200` at 05:30 daily, two hours
        after the scraper, so fresh items get qwen3:14b summaries before the
        morning corpus-summary email at 09:00.
    - Both registered + loaded into ~/Library/LaunchAgents and copied into
      the project's launchd/ dir for version-tracking + reproducibility.
    - summarize_news.ts backoff bumped from 8s/16s/32s/64s (max ~2min) to
      60s/120s/240s/480s/960s/1920s (max ~62min). Magazine-gen 2000-feature
      runs hold Mac1 for hours; the old backoff exhausted before capacity
      opened. Patient backoff lets the 05:30 launchd drain even if the
      generator runs long.
    
    Verified: launchctl list shows both plists loaded; Steve's 2000-feature
    generate_features will finish before 05:30 most nights.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 .../com.steve.ventura-corridor-news-scrape.plist   | 40 ++++++++++++++++++++++
 ...com.steve.ventura-corridor-news-summarize.plist | 38 ++++++++++++++++++++
 src/jobs/summarize_news.ts                         | 11 +++---
 3 files changed, 85 insertions(+), 4 deletions(-)

diff --git a/launchd/com.steve.ventura-corridor-news-scrape.plist b/launchd/com.steve.ventura-corridor-news-scrape.plist
new file mode 100644
index 0000000..401f21e
--- /dev/null
+++ b/launchd/com.steve.ventura-corridor-news-scrape.plist
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>Label</key>
+  <string>com.steve.ventura-corridor-news-scrape</string>
+  <key>ProgramArguments</key>
+  <array>
+    <string>/bin/bash</string>
+    <string>-lc</string>
+    <string>cd /Users/stevestudio2/Projects/ventura-corridor &amp;&amp; /usr/local/bin/npm run crawl:news</string>
+  </array>
+  <key>StartCalendarInterval</key>
+  <dict>
+    <key>Hour</key>
+    <integer>3</integer>
+    <key>Minute</key>
+    <integer>15</integer>
+  </dict>
+  <key>StandardOutPath</key>
+  <string>/Users/stevestudio2/Projects/ventura-corridor/logs/news-scrape.out.log</string>
+  <key>StandardErrorPath</key>
+  <string>/Users/stevestudio2/Projects/ventura-corridor/logs/news-scrape.err.log</string>
+  <key>EnvironmentVariables</key>
+  <dict>
+    <key>PATH</key>
+    <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
+    <key>PGHOST</key>
+    <string>/tmp</string>
+    <key>NEWS_TIMEOUT_MS</key>
+    <string>10000</string>
+    <key>NEWS_HOST_GAP_MS</key>
+    <string>500</string>
+    <key>NEWS_CONCURRENCY</key>
+    <string>4</string>
+  </dict>
+  <key>RunAtLoad</key>
+  <false/>
+</dict>
+</plist>
diff --git a/launchd/com.steve.ventura-corridor-news-summarize.plist b/launchd/com.steve.ventura-corridor-news-summarize.plist
new file mode 100644
index 0000000..7bdfa6d
--- /dev/null
+++ b/launchd/com.steve.ventura-corridor-news-summarize.plist
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>Label</key>
+  <string>com.steve.ventura-corridor-news-summarize</string>
+  <key>ProgramArguments</key>
+  <array>
+    <string>/bin/bash</string>
+    <string>-lc</string>
+    <string>cd /Users/stevestudio2/Projects/ventura-corridor &amp;&amp; /usr/local/bin/npm run news:summarize -- --limit=200</string>
+  </array>
+  <key>StartCalendarInterval</key>
+  <dict>
+    <key>Hour</key>
+    <integer>5</integer>
+    <key>Minute</key>
+    <integer>30</integer>
+  </dict>
+  <key>StandardOutPath</key>
+  <string>/Users/stevestudio2/Projects/ventura-corridor/logs/news-summarize.out.log</string>
+  <key>StandardErrorPath</key>
+  <string>/Users/stevestudio2/Projects/ventura-corridor/logs/news-summarize.err.log</string>
+  <key>EnvironmentVariables</key>
+  <dict>
+    <key>PATH</key>
+    <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
+    <key>PGHOST</key>
+    <string>/tmp</string>
+    <key>OLLAMA_URL</key>
+    <string>http://100.94.103.98:11434</string>
+    <key>OLLAMA_MODEL</key>
+    <string>qwen3:14b</string>
+  </dict>
+  <key>RunAtLoad</key>
+  <false/>
+</dict>
+</plist>
diff --git a/src/jobs/summarize_news.ts b/src/jobs/summarize_news.ts
index e9f2808..427cf8c 100644
--- a/src/jobs/summarize_news.ts
+++ b/src/jobs/summarize_news.ts
@@ -51,11 +51,14 @@ async function callOllama(prompt: string, attempt = 0): Promise<string> {
     }),
     signal: AbortSignal.timeout(TIMEOUT_MS)
   });
-  // Mac1 magazine-gen runs the same Ollama hot — 503 "server busy" is common.
-  // Backoff up to 4 times: 8s, 16s, 32s, 64s. After that surface the error.
+  // Mac1 magazine-gen runs the same Ollama hot — 503 "server busy" is common
+  // and can persist for hours on a 2000-feature run. Backoff up to 6 times:
+  // 60s, 120s, 240s, 480s, 960s, 1920s. Total ~62min wait before giving up.
+  // The summarize job is normally invoked at 5:30am via launchd long after
+  // generate_features completes, so this almost always succeeds first try.
   if (res.status === 503 || res.status === 429) {
-    if (attempt >= 4) throw new Error(`ollama ${res.status}: backoff exhausted`);
-    const delay = 8000 * Math.pow(2, attempt);
+    if (attempt >= 6) throw new Error(`ollama ${res.status}: backoff exhausted`);
+    const delay = 60000 * Math.pow(2, attempt);
     await sleep(delay);
     return callOllama(prompt, attempt + 1);
   }

← e8b2f73 feat(news): /api/news/recent + /news.html magazine surface  ·  back to Ventura Corridor  ·  feat(enrich): website-discovery resilience + category priori 828ab33 →