Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 16 additions & 22 deletions src/rhythms/scheduler.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> {
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<boolean> {
const { dateStr, hour, weekday } = localParts(HEARTBEAT_TZ, now);
if (!isWeekday(weekday) || hour < HEARTBEAT_HOUR) return false;
Expand All @@ -47,10 +48,6 @@ async function pushDailyData(now: Date): Promise<void> {

/** 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;

Expand All @@ -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
Expand Down Expand Up @@ -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)`,
);

Expand Down