# How Compact and Clear Context Triggers Use Two-Phase Timer Architecture in Munder Difflin

> Discover how Munder Difflin's two-phase timer architecture uses compact and clear context triggers to efficiently queue slash commands, saving resources by validating context usage.

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

---

**Compact and clear context triggers employ a two-phase "timer-gate" architecture that first waits for a configurable time interval, then validates context usage against a percentage threshold before queuing the slash command, ensuring housekeeping only occurs when necessary.**

Managing terminal context efficiently is critical for long-running AI agents in the `chaitanyagiri/munder-difflin` repository. The **context triggers** `compact` and `clear` automate this cleanup through a sophisticated two-phase timer system that balances periodic maintenance with performance optimization.

## The Two-Phase Timer-Gate Design

The architecture separates timing concerns from execution logic through distinct gates. According to the source code in [`src/renderer/src/hooks/useHive.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/hooks/useHive.ts), the `fire` function (around line 1094) orchestrates this dual-phase validation before enqueuing either `/compact` or `/clear` commands.

### Phase 1: Time-Based Gate (everyMs)

The first phase is controlled by a standard JavaScript timer mechanism. A `setTimeout` or `setInterval` is armed using the `everyMs` property defined in the `ContextRule` interface (located in [`src/shared/triggers.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/shared/triggers.ts) lines 77-88). 

When the interval elapses, the timer fires and enters the trigger logic. This ensures the system only evaluates context state at periodic intervals rather than continuously polling. The default configuration specifies a 2-hour interval (7,200,000ms) for both triggers, though this is customizable via user configuration.

### Phase 2: Context Fill Gate (minContextPct)

Immediately after the timer fires, the second gate validates whether action is actually necessary. The system checks the agent's current `contextFill` percentage against `minContextPct` (or `minContextPctLargeWindow` for very large windows).

If the context fill exceeds the threshold, the command is queued; otherwise, the trigger simply re-arms the timer without executing. This prevents needless compaction on idle agents or those with minimal context usage, while guaranteeing housekeeping occurs once the window becomes sufficiently full.

```ts
// src/renderer/src/hooks/useHive.ts (lines 1094-1101)
const fire = (action: 'compact' | 'clear', rule: ContextRule) => {
  // Phase 1 complete: timer has fired
  if (!rule.enabled) return;                     // Gate check: enabled?
  if (contextFill < rule.minContextPct) return; // Phase 2: context threshold
  enqueueCommand(action, rule.message);       // Execute: queue /compact or /clear
};

```

## Source Code Implementation

The two-phase architecture relies on three core components that handle configuration, defaults, and runtime execution.

### ContextRule Schema and Defaults

The trigger configuration structure is defined in [`src/shared/triggers.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/shared/triggers.ts) (lines 77-88), specifying the parameters for both gates:

```ts
interface ContextRule {
  enabled: boolean;
  everyMs: number;                    // Phase 1: timer interval
  minContextPct: number;              // Phase 2: threshold for normal windows
  minContextPctLargeWindow: number;   // Phase 2: threshold for large windows
  message: string;                    // Context preservation hint
}

```

Default values established in lines 133-141 set conservative baselines: `compact` triggers every 2 hours at 60% fill, while `clear` uses the same interval at 90% fill.

### Configuration Merging

To ensure the timer always operates with complete rule objects, [`src/main/config.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/config.ts) provides the `withTriggerDefaults` function (lines 11-14). This deep-merges persisted user configuration with `DEFAULT_CONTEXT_TRIGGER` constants, guaranteeing that optional partial configs still contain valid `everyMs` and `minContextPct` values for the two-phase logic.

## Configuring Context Triggers

Users customize the dual-gate behavior through the `contextTrigger` section of their configuration file. Adjusting `everyMs` modulates the timer frequency, while changing `minContextPct` tightens or loosens the execution condition.

```json
{
  "contextTrigger": {
    "compact": {
      "enabled": true,
      "everyMs": 3600000,               // Phase 1: fire every hour
      "minContextPct": 50,                // Phase 2: fire at 50% fill
      "minContextPctLargeWindow": 30,
      "message": "Keep the current task and next step"
    },
    "clear": {
      "enabled": false,
      "everyMs": 7200000,
      "minContextPct": 90,
      "minContextPctLargeWindow": 80,
      "message": ""
    }
  }
}

```

## Manual Execution and Debugging

For scenarios requiring immediate context management, the two-phase architecture can be bypassed entirely by manually queuing commands.

### Bypassing the Timer-Gate

The following pattern skips both the time-based and context-fill gates, executing the command immediately:

```ts
import { compactionCommandForProvider } from '@/shared/providerAutomation';

const cmd = compactionCommandForProvider(provider, {
  enabled: true,
  everyMs: 0,               // Irrelevant for manual invocation
  minContextPct: 0,         // Skip context check
  minContextPctLargeWindow: 0,
  message: 'Summarise the sprint goal'
});

store.enqueueMessage(agentId, cmd);

```

### Observing Timer Cadence

To verify the Phase 1 timer is firing correctly, insert diagnostic logging in [`src/renderer/src/hooks/useHive.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/hooks/useHive.ts):

```ts
const fire = (action, rule) => {
  console.log(`[trigger] ${action} timer fired; enabled=${rule.enabled}`);
  // ...existing gate logic
};

```

This log appears each time the time-based gate activates, independent of whether the context-fill gate permits execution.

## Summary

- **Compact and clear context triggers** use a two-phase architecture separating timing from execution logic.
- **Phase 1** uses `everyMs` to establish periodic evaluation cadence via `setTimeout`.
- **Phase 2** validates `contextFill` against `minContextPct` before queuing commands, preventing unnecessary operations.
- **Configuration** resides in [`src/shared/triggers.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/shared/triggers.ts) with runtime logic in [`src/renderer/src/hooks/useHive.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/hooks/useHive.ts).
- **Manual bypass** is possible by directly calling `compactionCommandForProvider` and enqueuing messages outside the `fire` function.

## Frequently Asked Questions

### What happens if context usage never exceeds the threshold?

If the agent's context fill remains below `minContextPct` when the timer fires, the trigger skips execution and re-arms the timer for the next interval. This cycle repeats indefinitely until either the context grows sufficiently or the timer is disabled, ensuring zero-overhead for idle agents.

### How do I adjust the timer interval for compact triggers?

Modify the `everyMs` property in your [`config.json`](https://github.com/chaitanyagiri/munder-difflin/blob/main/config.json) under `contextTrigger.compact`. This value represents milliseconds between Phase 1 evaluations. For example, setting `"everyMs": 3600000` creates an hourly check, while the default 7200000ms creates a 2-hour cycle.

### Can I disable the automatic triggers while keeping manual commands?

Yes. Set `"enabled": false` for either trigger in your configuration. This disables the two-phase timer logic in [`useHive.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/useHive.ts), but manual invocations via `compactionCommandForProvider` remain fully functional since they bypass the `fire` function entirely.

### Where does the timer state live in the application architecture?

The active timer references and `fire` callback logic reside in the renderer process within [`src/renderer/src/hooks/useHive.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/hooks/useHive.ts). However, the configuration defaults and merging logic exist in the main process at [`src/main/config.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/config.ts), with the trigger schema defined in the shared module at [`src/shared/triggers.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/shared/triggers.ts).