From f0a9c0c60e39af44955b5f36c50444d3479083a9 Mon Sep 17 00:00:00 2001 From: Navid Shad Date: Mon, 3 Aug 2026 08:18:52 +0000 Subject: [PATCH] Retire the legacy 5h PM-check clock, keep Navid's 10:00/16:00 job The scheduler ran its own fixed-interval PM check (PM_CHECK_INTERVAL_MS, default 5h) independently of the agent-owned "pm-check" scheduled job Navid set up on a 10:00/16:00 cron. The two fired on separate clocks and produced several duplicate PM-check-shaped runs a day, flagged repeatedly in chat over the past two weeks. Removing the built-in clock leaves the generic-scheduler job as the only source of scheduled PM checks; manual triggers (CLI, chat "run pm-check") are untouched. Co-Authored-By: Aso Dara --- src/rhythms/scheduler.ts | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/rhythms/scheduler.ts b/src/rhythms/scheduler.ts index 2ec787b..b56c2c0 100644 --- a/src/rhythms/scheduler.ts +++ b/src/rhythms/scheduler.ts @@ -1,29 +1,30 @@ /** - * The rhythm scheduler: a 60s tick that fires the PM check (every 5h) and the - * heartbeat (weekdays at HEARTBEAT_HOUR in HEARTBEAT_TZ). State persists in - * data/schedule.json so a restart doesn't double-fire and a missed heartbeat - * (downtime past 08:00) runs once when the box returns. All runs go through the - * cross-process rhythm lock so nothing overlaps. + * The rhythm scheduler: a 60s tick that fires the heartbeat (weekdays at + * HEARTBEAT_HOUR in HEARTBEAT_TZ). State persists in data/schedule.json so a + * restart doesn't double-fire and a missed heartbeat (downtime past 08:00) runs + * once when the box returns. All runs go through the cross-process rhythm lock + * so nothing overlaps. + * + * The PM check itself is no longer fired from here — it used to run on its own + * fixed 5h clock (PM_CHECK_INTERVAL_MS), which ran independently of the + * agent-owned "pm-check" job in the generic scheduler below (Navid's 10:00 + + * 16:00 cron). The two overlapped and produced several PM-check-shaped runs a + * day. The generic-scheduler job is the one Navid actually configured, so it's + * now the only source of scheduled PM checks; rhythms/pmCheck.ts (runPmCheck) + * stays as-is for manual triggers (CLI, chat/task "run pm-check" command). */ -import { DATA_PUSH_HOUR, HEARTBEAT_HOUR, HEARTBEAT_TZ, PM_CHECK_INTERVAL_MS } from "../config.js"; +import { DATA_PUSH_HOUR, HEARTBEAT_HOUR, HEARTBEAT_TZ } from "../config.js"; import { commitHistory } from "../history/commit.js"; import { runHeartbeat } from "./heartbeat.js"; import { runExclusive } from "./lock.js"; -import { runPmCheck } from "./pmCheck.js"; -import { readState, setLastDataPush, setLastPmCheck } from "./state.js"; +import { readState, setLastDataPush } from "./state.js"; import { isWeekday, localParts } from "./time.js"; import { continuingJobs, dueJobs, runScheduledJob } from "./schedules.js"; import { dueWorkflows, runWorkflowGenerate } from "../workflows/registry.js"; const TICK_MS = 60_000; -async function pmCheckDue(now: Date): Promise { - const { lastPmCheckAt } = await readState(); - if (!lastPmCheckAt) return false; // baselined on first start - return now.getTime() - new Date(lastPmCheckAt).getTime() >= PM_CHECK_INTERVAL_MS; -} - async function heartbeatDue(now: Date): Promise { const { dateStr, hour, weekday } = localParts(HEARTBEAT_TZ, now); if (!isWeekday(weekday) || hour < HEARTBEAT_HOUR) return false; @@ -47,10 +48,6 @@ async function pushDailyData(now: Date): Promise { /** Start the scheduler loop. Returns a stop function. */ export async function startScheduler(): Promise<() => void> { - // Baseline the PM clock on first ever start so it doesn't fire immediately. - const s = await readState(); - if (!s.lastPmCheckAt) await setLastPmCheck(new Date().toISOString()); - let stopped = false; let ticking = false; @@ -61,8 +58,6 @@ export async function startScheduler(): Promise<() => void> { const now = new Date(); if (await heartbeatDue(now)) { await runExclusive("heartbeat", runHeartbeat); - } else if (await pmCheckDue(now)) { - await runExclusive("pm-check", runPmCheck); } // Resume any job that checkpointed mid-flight last time, BEFORE starting fresh // cron-due ones — so a long, chunked task keeps making progress each tick. dueJobs @@ -94,8 +89,7 @@ export async function startScheduler(): Promise<() => void> { void tick(); const hb = localParts(HEARTBEAT_TZ); console.log( - `[scheduler] started — PM check every ${Math.round(PM_CHECK_INTERVAL_MS / 3_600_000)}h, ` + - `heartbeat weekdays ${HEARTBEAT_HOUR}:00 ${HEARTBEAT_TZ}, ` + + `[scheduler] started — heartbeat weekdays ${HEARTBEAT_HOUR}:00 ${HEARTBEAT_TZ}, ` + `daily data push ${DATA_PUSH_HOUR}:00 ${HEARTBEAT_TZ} (now ${hb.dateStr} ${hb.hour}:00 local)`, );