# How Scheduled Missions and Heartbeat Monitoring Operate in Munder Difflin

> Discover how Munder Difflin uses scheduled missions for Hive messages and adaptive heartbeat monitoring to manage office activity and trigger circuit breakers when things go quiet.

- Repository: [Chaitanya Giri/munder-difflin](https://github.com/chaitanyagiri/munder-difflin)
- Tags: internals
- Published: 2026-08-20

---

**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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/config.ts):

```typescript
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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/config.ts):

```typescript
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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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:

```typescript
// 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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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:

1. **Check floor quietness** – Compare time since last activity against `quietThresholdMs`
2. **If quiet**, dispatch a digest to God's inbox via `hive.send(..., 'heartbeat')`
3. **Evaluate circuit‑breaker** – Trigger recovery logic if agents appear stuck
4. **Re‑arm with adjusted interval** – Shorter when activity is high, longer when idle

```typescript
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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/realtime/tools.ts):

```typescript
// List all missions with metadata
const missions = await window.cth.listMissions();

```

*Source: [`src/renderer/src/realtime/tools.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/realtime/actions.ts):

```typescript
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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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

```typescript
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

```typescript
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

```typescript
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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/config.ts) | `ScheduledMission` interface, built‑in mission definitions |
| [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts) | `syncMissions` scheduler, `armHeartbeat` adaptive loop |
| [`src/renderer/src/realtime/tools.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/realtime/tools.ts) | UI‑side mission listing (`listMissions`) |
| [`src/renderer/src/realtime/actions.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/realtime/actions.ts) | Mission CRUD operations (`create_schedule`) |
| [`src/main/breaker.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/breaker.ts) | Circuit‑breaker logic triggered by heartbeat |

---

## Summary

- **Scheduled missions** are stored as `ScheduledMission` objects with fixed `intervalMs` cadences, dispatched through `syncMissions` using standard `setInterval` timers.

- **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 `syncMissions` ensures 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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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`.