[object Object]

← back to Slack To Steve

ingest: adaptive polling — 3s while a convo is active, 12s idle; 429 backoff w/ Retry-After

8c5a8be031044ecdb0efe6012781cbcf675408ca · 2026-07-13 09:55:14 -0700 · Steve Abrams

Files touched

Diff

commit 8c5a8be031044ecdb0efe6012781cbcf675408ca
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 13 09:55:14 2026 -0700

    ingest: adaptive polling — 3s while a convo is active, 12s idle; 429 backoff w/ Retry-After
---
 ingest.mjs | 30 ++++++++++++++++++++++++------
 1 file changed, 24 insertions(+), 6 deletions(-)

diff --git a/ingest.mjs b/ingest.mjs
index f00fa16..d117237 100644
--- a/ingest.mjs
+++ b/ingest.mjs
@@ -24,7 +24,10 @@ const ARMED_CHANNELS = (ENV.ARMED_CHANNELS || process.env.ARMED_CHANNELS || '').
 const armedFor = cid => AUTO_EXECUTE && (ARMED_CHANNELS.length === 0 || ARMED_CHANNELS.includes(cid));
 const CLAUDE_CMD = ENV.CLAUDE_CMD || process.env.CLAUDE_CMD || '/Users/macstudio3/.local/bin/claude';
 const WATCH = process.argv.includes('--watch') || process.env.WATCH === '1';
-const INTERVAL = parseInt(ENV.WATCH_INTERVAL_MS || process.env.WATCH_INTERVAL_MS || '90000', 10);
+// Adaptive polling: fast while a conversation is active, relaxed when idle.
+const HOT_MS = parseInt(ENV.HOT_INTERVAL_MS || process.env.HOT_INTERVAL_MS || '3000', 10);   // 3s while "using"
+const COLD_MS = parseInt(ENV.COLD_INTERVAL_MS || process.env.COLD_INTERVAL_MS || '12000', 10); // 12s when idle
+const HOT_WINDOW_MS = parseInt(ENV.HOT_WINDOW_MS || process.env.HOT_WINDOW_MS || '120000', 10); // stay hot 2 min after last activity
 const INBOX = path.join(DIR, 'data', 'inbox.jsonl');
 
 const RAILS = (msg) => `You are acting on a message Steve just sent you in his Slack channel — this is him talking to Claude to get work done. Do what he asks NOW, using all your tools/skills.
@@ -38,8 +41,14 @@ Steve's message:
 ${msg}`;
 
 function loadEnv(p) { const o = {}; try { for (const l of fs.readFileSync(p, 'utf8').split('\n')) { const m = l.match(/^([A-Z0-9_]+)=(.*)$/); if (m) o[m[1]] = m[2].replace(/^['"]|['"]$/g, ''); } } catch {} return o; }
-async function slack(method, params) {
+async function slack(method, params, retries = 2) {
   const r = await fetch('https://slack.com/api/' + method, { method: 'POST', headers: { Authorization: 'Bearer ' + TOKEN, 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams(params) });
+  if (r.status === 429 && retries > 0) {                 // rate-limited → respect Retry-After, back off
+    const wait = (parseInt(r.headers.get('retry-after') || '3', 10) + 1) * 1000;
+    log(`429 on ${method} — backing off ${wait}ms`);
+    await new Promise(res => setTimeout(res, wait));
+    return slack(method, params, retries - 1);
+  }
   return r.json();
 }
 const log = m => console.log(new Date().toISOString() + ' ' + m);
@@ -90,13 +99,22 @@ async function tick() {
     fs.mkdirSync(path.join(DIR, 'data'), { recursive: true });
     let total = 0;
     for (const cid of CHANNELS) total += await processChannel(cid);
-    log(`processed ${total} new message(s) from Steve · armed=${AUTO_EXECUTE} · channels=${CHANNELS.length}`);
+    if (total) log(`processed ${total} new message(s) from Steve · armed=${AUTO_EXECUTE} · channels=${CHANNELS.length}`);
+    return total;
   } finally { TICKING = false; }
 }
 
+let lastActivity = 0; // ms of the last time Steve posted something we handled
+function nextDelay() { return (Date.now() - lastActivity) < HOT_WINDOW_MS ? HOT_MS : COLD_MS; }
+async function loop() {
+  try { const n = await tick(); if (n > 0) lastActivity = Date.now(); }
+  catch (e) { log('tick error: ' + e.message); }
+  setTimeout(loop, nextDelay());   // adaptive: 3s while a convo is hot, 12s when idle
+}
+
 (async () => {
   if (!TOKEN || !CHANNELS.length || !STEVE) { log('CONFIG missing (SLACK_BOT_TOKEN / SLACK_CHANNEL_IDS / STEVE_USER_ID)'); process.exit(1); }
-  log(`slack ingest starting · armed=${AUTO_EXECUTE} · watch=${WATCH}`);
-  await tick();
-  if (WATCH) setInterval(() => tick().catch(e => log('tick error: ' + e.message)), INTERVAL);
+  log(`slack ingest starting · armed=${AUTO_EXECUTE} · watch=${WATCH} · hot=${HOT_MS}ms cold=${COLD_MS}ms`);
+  const n = await tick(); if (n > 0) lastActivity = Date.now();
+  if (WATCH) setTimeout(loop, nextDelay());
 })();

← 641bc93 ingest: re-entrancy guard on tick() — prevents overlapping a  ·  back to Slack To Steve  ·  auto-save: 2026-07-13T10:24:15 (1 files) — socket.mjs 9aae89c →