[object Object]

← back to Wallco Ai

drunk-animals: replace launchd plist with pm2 daemon for precise cadence

530832434f508762e2955e74f34934348e8e1bd4 · 2026-05-12 20:59:42 -0700 · SteveStudio2

macOS launchd was coalescing the StartInterval=240s firings into
~20-min intervals (system idle → power-saving deferred wake). pm2
daemon uses setInterval(4*60*1000) so cadence is exact regardless
of system idle state.

- scripts/drunk_animals_daemon.js: long-lived process, fires
  scripts/drunk_animals_tick.js every 240s; skips if previous tick
  is still running; bails outside 18:00→06:00 PT window
- launchctl unloaded com.steve.wallco-drunk-animals.plist
- pm2 started as 'wallco-drunk-animals' with PATH including
  /opt/homebrew/bin so PIL chain works
- pm2 save dumped so daemon survives reboot
- Manual tick fired: design #1838 generated cleanly, 55 total

Files touched

Diff

commit 530832434f508762e2955e74f34934348e8e1bd4
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Tue May 12 20:59:42 2026 -0700

    drunk-animals: replace launchd plist with pm2 daemon for precise cadence
    
    macOS launchd was coalescing the StartInterval=240s firings into
    ~20-min intervals (system idle → power-saving deferred wake). pm2
    daemon uses setInterval(4*60*1000) so cadence is exact regardless
    of system idle state.
    
    - scripts/drunk_animals_daemon.js: long-lived process, fires
      scripts/drunk_animals_tick.js every 240s; skips if previous tick
      is still running; bails outside 18:00→06:00 PT window
    - launchctl unloaded com.steve.wallco-drunk-animals.plist
    - pm2 started as 'wallco-drunk-animals' with PATH including
      /opt/homebrew/bin so PIL chain works
    - pm2 save dumped so daemon survives reboot
    - Manual tick fired: design #1838 generated cleanly, 55 total
---
 scripts/drunk_animals_daemon.js | 61 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 61 insertions(+)

diff --git a/scripts/drunk_animals_daemon.js b/scripts/drunk_animals_daemon.js
new file mode 100644
index 0000000..0044838
--- /dev/null
+++ b/scripts/drunk_animals_daemon.js
@@ -0,0 +1,61 @@
+#!/usr/bin/env node
+/**
+ * Drunk-animals daemon — runs as a pm2-managed long-lived process and fires
+ * scripts/drunk_animals_tick.js exactly every TICK_MS milliseconds. Replaces
+ * the launchd plist which got deferred from 4-min to 20-min cadence by
+ * macOS power-coalescing on Apple Silicon (idle system + StartInterval is
+ * "best effort", not guaranteed).
+ *
+ * Stops itself past STOP_HOUR PT (default 6 AM) — does not exit, but
+ * suppresses tick firings. Resumes the next evening if the daemon is still
+ * up (the tick script also has its own outside-window guard, redundant).
+ */
+
+const { spawn } = require('child_process');
+const path = require('path');
+
+const TICK_MS    = parseInt(process.env.TICK_MS || '240000', 10);   // 4 min
+const STOP_HOUR  = parseInt(process.env.STOP_HOUR || '6', 10);
+const ROOT       = path.join(__dirname, '..');
+const TICK_SCRIPT = path.join(__dirname, 'drunk_animals_tick.js');
+
+function nowPT() {
+  return new Date(new Date().toLocaleString('en-US', { timeZone: 'America/Los_Angeles' }));
+}
+
+function inWindow() {
+  const t = nowPT();
+  const h = t.getHours();
+  // Active window: 18:00 → 05:59 PT next day. Quiet 06:00 → 17:59.
+  return h >= 18 || h < STOP_HOUR;
+}
+
+let running = false;
+
+function fire() {
+  if (running) {
+    console.log(`[${new Date().toISOString()}] previous tick still running, skipping`);
+    return;
+  }
+  if (!inWindow()) {
+    const h = nowPT().getHours();
+    if (h % 4 === 0) console.log(`[${new Date().toISOString()}] outside window (PT hour ${h}), idle`);
+    return;
+  }
+  running = true;
+  const t0 = Date.now();
+  const child = spawn('node', [TICK_SCRIPT], { stdio: 'inherit', cwd: ROOT });
+  child.on('exit', code => {
+    running = false;
+    console.log(`[${new Date().toISOString()}] tick done in ${((Date.now()-t0)/1000).toFixed(1)}s (exit ${code})`);
+  });
+  child.on('error', err => {
+    running = false;
+    console.error(`[${new Date().toISOString()}] tick spawn error:`, err.message);
+  });
+}
+
+console.log(`[${new Date().toISOString()}] drunk-animals daemon up · TICK_MS=${TICK_MS} STOP_HOUR=${STOP_HOUR}PT`);
+console.log(`[${new Date().toISOString()}] first tick in ${TICK_MS/1000}s`);
+setInterval(fire, TICK_MS);
+// Don't fire on boot — give pm2 a moment, then ride the setInterval cadence

← 73f6770 /compare: ETag + 304 keyed on (catalog lastmod, view, ordere  ·  back to Wallco Ai  ·  drunk-animals: cross-tick prompt dedup against last 5 PG row 83c5b98 →