# How Context Trigger Persistence Works with CONTEXT_LAST_RUN_KV_KEY in the Durable KV Store

> Learn how context trigger persistence uses CONTEXT_LAST_RUN_KV_KEY and the durable KV store for reliable operations. Ensure consistent restarts and config changes with this powerful technique.

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

---

**Context trigger persistence uses the durable KV store to record last-run timestamps for compact and clear operations, ensuring consistent cadence across restarts and config changes.**

The `munder-difflin` repository implements an intelligent context management system that automatically compacts or clears agent context windows based on configurable thresholds. To maintain reliable scheduling across process lifecycles, the system persists execution timestamps using `CONTEXT_LAST_RUN_KV_KEY` in a durable key-value store.

## Understanding the Context Trigger Mechanism

The context trigger (also called "auto-compact / auto-clear") operates on a dual-cadence schedule. It fires at regular intervals **only when** an agent's live context window exceeds a configurable pressure threshold. Because Node.js processes restart during config edits, system sleep/wake cycles, or deployments, the trigger cannot rely solely on in-memory timers.

According to the source code in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts), the solution stores the last execution time for each trigger half (`compact` and `clear`) in a durable KV entry. This allows `syncContextTriggers()` to calculate remaining time accurately across restarts rather than resetting the interval on every boot.

## How CONTEXT_LAST_RUN_KV_KEY Stores Execution State

The persistence mechanism centers on a single constant that identifies the storage location for context trigger metadata.

### The KV Key Definition

In [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts), the system defines the storage key as a constant string:

```ts
const CONTEXT_LAST_RUN_KV_KEY = 'triggers.context.lastRun';

```

This key holds a JSON map with the structure `{ compact: number, clear: number }`, where each value represents a Unix timestamp in milliseconds.

### Lazy Loading from the Durable Store

The `contextRunMap()` function implements a caching layer over the durable store. It checks an in-memory cache first, then falls back to the KV store via `persist.getKv` only when necessary. Error handling ensures that missing or corrupted entries default to an empty object rather than crashing the process.

```ts
function contextRunMap(): Record<string, number> {
  if (!contextLastRun) {
    try { contextLastRun = persist.getKv<Record<string, number>>(CONTEXT_LAST_RUN_KV_KEY) ?? {}; }
    catch { contextLastRun = {}; }
  }
  return contextLastRun;
}

```

This pattern minimizes I/O operations while guaranteeing that the system can always recover from storage failures.

## Reading and Writing Last-Run Timestamps

The system provides specialized functions for updating and querying the persisted state, both wrapped in try-catch blocks because persistence is treated as "best-effort" rather than critical path.

### Stamping a New Execution Time

When a trigger fires successfully, `stampContextRun(action)` writes the current timestamp to the map and immediately persists it back to the KV store using `persist.setKv`:

```ts
function stampContextRun(action: 'compact' | 'clear'): number {
  const map = contextRunMap();
  const at = Date.now();
  map[action] = at;
  try { persist.setKv(CONTEXT_LAST_RUN_KV_KEY, map); } catch { /* DB best-effort */ }
  return at;
}

```

This function returns the timestamp for immediate use in timer calculations while asynchronously updating the durable store.

### Retrieving Persisted Timestamps

The `contextLastRunAt(action)` function retrieves the stored timestamp for a specific action. If the entry does not exist—common during first launch or after data corruption—it automatically stamps a fresh timestamp to prevent the trigger from firing immediately on boot:

```ts
function contextLastRunAt(action: 'compact' | 'clear'): number {
  const map = contextRunMap();
  const v = map[action];
  if (typeof v === 'number' && Number.isFinite(v)) return v;
  return stampContextRun(action);
}

```

This defensive programming ensures that new installations or repaired databases do not experience unexpected immediate compaction cycles.

## Timer Calculation and Cross-Process Consistency

The `syncContextTriggers()` function in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts) uses the persisted timestamps to compute accurate remaining intervals. By subtracting the stored last-run time from the configured cadence (`rule.everyMs`), the system honors partial intervals across restarts:

```ts
const remaining = Math.max(0, rule.everyMs - (Date.now() - contextLastRunAt(action)));

```

This calculation guarantees that an operator who edits the context trigger configuration twice a day will still see the trigger fire at the intended interval, rather than resetting to a fresh interval on every edit. The durable KV store bridges the gap between process lifecycles, providing stateful cadence management that respects user expectations.

## Practical Code Examples

You can manually query the last run timestamp to build monitoring dashboards or debug trigger behavior:

```ts
// Example: manually query the last run timestamp for the 'compact' half
import { persist } from './persist';   // assume persistence helper is exported
const key = 'triggers.context.lastRun';
const map = await persist.getKv<Record<string, number>>(key);
const lastCompact = map?.compact ?? 0;
console.log('Compact last ran at:', new Date(lastCompact));

```

For maintenance operations such as migrations or resetting stuck triggers, you can overwrite the timestamps directly:

```ts
// Example: force-reset the timestamps (e.g., after a migration)
async function resetContextTimestamps() {
  const emptyMap = { compact: 0, clear: 0 };
  await persist.setKv('triggers.context.lastRun', emptyMap);
}
resetContextTimestamps();

```

## Summary

- **CONTEXT_LAST_RUN_KV_KEY** (`'triggers.context.lastRun'`) stores a JSON map of last-run timestamps for compact and clear operations in the durable KV store.
- **Lazy loading** via `contextRunMap()` minimizes I/O while providing resilient fallback to empty state on corruption.
- **Best-effort persistence** means updates use try-catch blocks to prevent storage failures from crashing the trigger system.
- **Accurate timer calculation** uses persisted timestamps to compute `remaining` time, ensuring consistent cadence across process restarts and configuration changes.
- **Key files** include [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts) (core logic), [`src/shared/triggers.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/shared/triggers.ts) (default configuration), [`src/main/config.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/config.ts) (user config merging), and [`src/main/persist.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/persist.ts) (KV store implementations).

## Frequently Asked Questions

### What happens if the KV store entry is corrupted or missing?

If `persist.getKv` throws an error or returns null, the `contextRunMap()` function catches the exception and initializes an empty object `{}`. This allows the system to continue operating, and the next trigger execution will stamp a fresh timestamp via `contextLastRunAt()`, effectively resetting the schedule.

### Why does the system use Date.now() rather than a sequence number or counter?

The system uses Unix timestamps (`Date.now()`) because they support deterministic interval calculation using simple arithmetic subtraction. This approach enables `syncContextTriggers()` to compute remaining time accurately even if the process was offline for hours or days, whereas a sequence-based system would lose temporal context during downtime.

### Can I safely delete the CONTEXT_LAST_RUN_KV_KEY entry to force immediate trigger execution?

Yes, deleting the key or calling `resetContextTimestamps()` as shown in the examples will cause `contextLastRunAt()` to return a fresh timestamp on the next check. However, because `syncContextTriggers()` calculates `remaining` time based on `rule.everyMs`, a fresh timestamp means the trigger will wait the full interval before firing unless the context pressure threshold is also exceeded.

### How does this persistence interact with the DEFAULT_CONTEXT_TRIGGER configuration?

The `DEFAULT_CONTEXT_TRIGGER` defined in [`src/shared/triggers.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/shared/triggers.ts) provides the base cadence (`everyMs`) and pressure thresholds. When [`src/main/config.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/config.ts) loads user configuration, it merges custom settings with defaults. The persisted timestamps in `CONTEXT_LAST_RUN_KV_KEY` are then compared against the merged configuration's `everyMs` value, ensuring that operator changes to the cadence take effect while still respecting when the trigger last ran under previous settings.