How Scheduled Missions and Heartbeat Monitoring Operate in Munder Difflin
Munder Difflin runs a scheduler that drives two kinds of recurring work: fixed‑interval scheduled missions that dispatch Hive messages, and an adaptive heartbeat that monitors floor activity, adjusts its own cadence, and triggers circuit‑breaker logic when the office goes quiet.
The scheduled missions and adaptive heartbeat system forms the automation backbone of the Munder Difflin async office simulator. This article explains how the scheduler works, how the heartbeat adapts to floor conditions, and how you can create, monitor, and modify missions through the UI or code.
Understanding the Two Mission Types
Munder Difflin distinguishes between standard scheduled missions and the special heartbeat mission:
| Type | Behavior | Use Case |
|---|---|---|
| Fixed‑interval missions | Fire at a steady intervalMs cadence |
Daily stand‑ups, recurring reminders, custom automation |
| Adaptive heartbeat | Self‑rescheduling cadence that speeds up or slows down based on floor activity | Monitoring agent health, triggering circuit‑breakers, compacting idle sessions |
Both are defined by the same ScheduledMission interface, but the heartbeat receives special handling during scheduling.
The ScheduledMission Interface
Every recurring job in Munder Difflin conforms to the interface defined in src/main/config.ts:
export interface ScheduledMission {
id: string;
label: string;
intervalMs: number; // base cadence
to: string; // target agent (e.g. "god")
body: string;
enabled: boolean;
autoCompact?: boolean; // optional auto‑compact flag
lastFiredAt?: number;
kind?: 'dispatch' | 'heartbeat' | 'compact';
quietThresholdMs?: number; // only for heartbeat
}
Source: src/main/config.ts (lines 25‑48)
The kind field determines scheduling behavior, while quietThresholdMs enables the heartbeat's adaptive logic.
Built‑in Missions: Operations Stand‑up and Heartbeat
Munder Difflin ships with two predefined missions in src/main/config.ts:
export const OPS_STANDUP_MISSION = { /* … */ };
export const HEARTBEAT_MISSION = {
id: 'heartbeat',
label: 'Floor heartbeat',
intervalMs: 120_000, // 2 minute base interval
to: 'god',
body: 'Floor heartbeat …',
enabled: false, // disabled by default
kind: 'heartbeat',
quietThresholdMs: 300_000, // 5 minutes of quiet triggers digest
};
Source: src/main/config.ts (lines 81‑103)
The heartbeat mission starts disabled. When enabled, it ignores the fixed intervalMs and delegates to the adaptive armHeartbeat routine instead.
Scheduler Core: The syncMissions Function
The scheduler lives in src/main/index.ts. The syncMissions function runs whenever:
- The application starts
- Any mission is created, modified, enabled, or disabled
It performs a full reset of all timers and re‑arms each enabled mission from scratch:
// Pseudocode reflecting the actual implementation
function syncMissions() {
clearAllExistingTimers();
for (const m of enabledMissions) {
if (m.kind === 'heartbeat') {
armHeartbeat(m); // Adaptive scheduling
continue;
}
// Fixed‑interval scheduling
scheduleFixed(m);
}
}
Source: src/main/index.ts (lines 610‑617)
This clear‑and‑restart approach ensures clean state transitions when missions change mid‑flight.
How the Adaptive Heartbeat Works
The armHeartbeat function implements a self‑rescheduling loop rather than a fixed timer:
- Check floor quietness – Compare time since last activity against
quietThresholdMs - If quiet, dispatch a digest to God's inbox via
hive.send(..., 'heartbeat') - Evaluate circuit‑breaker – Trigger recovery logic if agents appear stuck
- Re‑arm with adjusted interval – Shorter when activity is high, longer when idle
if (m.kind === 'heartbeat') { armHeartbeat(m); continue; }
The heartbeat thus adapts its own cadence in real‑time, unlike fixed missions that respect only their intervalMs. This same mechanism optionally re‑engages God's PTY when he remains idle too long.
Managing Missions Through the UI
The renderer exposes mission control through the Realtime tools API in src/renderer/src/realtime/tools.ts:
// List all missions with metadata
const missions = await window.cth.listMissions();
Source: src/renderer/src/realtime/tools.ts (lines 239‑250)
The Triggers/Schedules tab displays:
- Last fired timestamp
- Next fired projection
- Enable/disable toggles
- Adaptive heartbeat status
This lets operators observe drift and tune intervals based on actual behavior.
Creating Custom Scheduled Missions
Use the create_schedule action exposed in src/renderer/src/realtime/actions.ts:
await window.cth.createSchedule({
missionId: 'daily-triage',
label: 'Daily GitHub triage',
intervalMs: 86_400_000, // 24 hours
to: 'god',
body: 'Please triage new GitHub issues.',
enabled: true,
kind: 'dispatch'
});
Source: src/renderer/src/realtime/actions.ts (lines 239‑246)
The backend persists missions via writeConfig({ missions }), and syncMissions picks up changes automatically.
Practical Code Examples
List all active missions
async function listMissions() {
const missions = await window.cth.listMissions();
missions.forEach(m => {
console.log(
`${m.label} → every ${m.intervalMs/1000}s, enabled: ${m.enabled}`
);
});
}
Enable the built‑in heartbeat
async function enableHeartbeat() {
const cfg = await window.cth.readConfig();
const heartbeat = cfg.missions?.find(m => m.id === 'heartbeat');
if (heartbeat) {
heartbeat.enabled = true;
await window.cth.saveMissions(cfg.missions);
}
}
Create a custom hourly mission
await window.cth.createSchedule({
label: 'Morning stand-up',
intervalMs: 3_600_000, // 1 hour
to: 'god',
body: 'Run the daily stand-up summary.',
enabled: true,
kind: 'dispatch'
});
Key Implementation Files
| File | Responsibility |
|---|---|
src/main/config.ts |
ScheduledMission interface, built‑in mission definitions |
src/main/index.ts |
syncMissions scheduler, armHeartbeat adaptive loop |
src/renderer/src/realtime/tools.ts |
UI‑side mission listing (listMissions) |
src/renderer/src/realtime/actions.ts |
Mission CRUD operations (create_schedule) |
src/main/breaker.ts |
Circuit‑breaker logic triggered by heartbeat |
Summary
-
Scheduled missions are stored as
ScheduledMissionobjects with fixedintervalMscadences, dispatched throughsyncMissionsusing standardsetIntervaltimers. -
The adaptive heartbeat uses the same interface but delegates to
armHeartbeat, which implements a self‑rescheduling loop that monitors floor quietness, posts digests to God's inbox, and adjusts its own frequency based on observed activity. -
Both mission types can be created, enabled, disabled, and monitored through the Triggers/Schedules UI or programmatically via the Realtime tools and actions APIs.
-
The scheduler's clear‑and‑restart approach in
syncMissionsensures consistent state across configuration changes.
Frequently Asked Questions
How does the heartbeat differ from a normal scheduled mission?
A normal mission fires at a fixed intervalMs using setInterval. The heartbeat mission, recognized by kind: 'heartbeat', is routed through armHeartbeat instead. This function implements an adaptive, self‑rescheduling loop that shortens its cadence when agents are active or stuck, and lengthens it when the floor stays quiet—making it responsive to runtime conditions rather than following a clock.
Why does syncMissions clear all timers instead of updating incrementally?
Clearing and re‑arming all timers ensures clean state transitions when missions are edited, disabled, or reordered. This prevents orphaned timers, drift accumulation, and race conditions between old and new configurations. The reset completes in milliseconds and only runs when missions actually change.
Can I create multiple heartbeat missions?
The interface permits any kind, but the scheduler in src/main/index.ts treats kind === 'heartbeat' as a special case calling armHeartbeat. While technically possible to define multiple heartbeat‑kinded missions, the system expects a single heartbeat instance. Additional heartbeats would compete for the same circuit‑breaker and quiet‑floor logic in src/main/breaker.ts.
How do I monitor when missions actually fire versus when they are scheduled?
The lastFiredAt field on each ScheduledMission records the timestamp of the last successful dispatch. The UI displays both last fired and next fired projections, letting you detect scheduling drift. For programmatic access, call window.cth.listMissions() and compare lastFiredAt against Date.now() and the mission's intervalMs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →