# How the HookServer Feeds Control Signals to Agent Sessions via the ControlRegistry

> Discover how the HookServer uses the ControlRegistry to feed control signals like paused or halted to agent sessions, ensuring real-time command execution.

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

---

**The HookServer queries the ControlRegistry on every hook payload to determine whether to block, redirect, or allow agent actions based on real-time control flags like `paused`, `gated`, or `halted`.**

In the `chaitanyagiri/munder-difflin` repository, the HookServer acts as the central bridge between operator commands and running agent sessions. By leveraging the ControlRegistry—a per-agent state container defined in [`src/main/control.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/control.ts)—it dynamically feeds control signals that determine execution flow without modifying the core agent logic.

## ControlRegistry: The Per-Agent State Store

The **ControlRegistry** maintains a dedicated state snapshot for each active session. Located in [`src/main/control.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/control.ts), this class stores boolean flags that represent the operator's current control intent.

Key flags tracked per session include:

- **`paused`** – Blocks all hook delivery until explicitly cleared
- **`gated`** – Requires human-in-the-loop (HITL) approval before proceeding
- **`steering`** – Redirects or modifies payload content
- **`halted`** – Terminates the session entirely

The registry exposes methods like `getSessionState(sessionId)` to retrieve the current flag map and `setPause(sessionId, boolean)` to update control states. This centralization ensures that control logic remains decoupled from the HookServer's delivery mechanism.

## HookServer Initialization and Dependency Injection

The HookServer receives its ControlRegistry reference during instantiation in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts). This dependency injection pattern ensures that both components share a singleton state instance across the application lifecycle.

```typescript
// src/main/index.ts
const control = new ControlRegistry();          // Per-agent control state
const hookServer = new HookServer({            // Receives registry reference
  control,
  // …other dependencies
});

```

By passing the registry through the constructor, the HookServer gains read access to all session control flags without directly managing state mutations.

## Runtime Signal Processing in the HookServer

When the HookServer receives a hook payload, it inspects the ControlRegistry before delivering the event to the agent. In [`src/main/hooks.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/hooks.ts), the `handleHookPayload` method implements this evaluation logic:

```typescript
// src/main/hooks.ts (excerpt)
private handleHookPayload(payload: HookPayload) {
  const ctrl = this.control?.getSessionState(payload.sessionId);
  
  if (ctrl?.paused) {
    // Block delivery until unpaused
    return this.blockDelivery(payload);
  }
  
  if (ctrl?.gated) {
    // Apply gating logic
    return this.applyGate(payload);
  }
  
  // Normal processing continues...
}

```

This check occurs synchronously on every incoming hook, ensuring that control signals take effect immediately when the operator toggles a flag in the registry.

## Feeding Control Signals to Agent Sessions

Once the HookServer detects an active control flag, it feeds the signal to the target session through one of three pathways:

**1. Direct Delivery Blocking**
When `ctrl.paused` returns true, the server invokes `blockDelivery(payload)`, queuing the hook until the operator clears the pause state. The session receives no data during this interval, effectively freezing agent execution.

**2. Human-in-the-Loop Gating**
For gated sessions, the HookServer calls `applyGate(payload)`, which redirects the request to the UI layer via `showPermissionPrompt(payload)`. The operator approves or rejects the action through the interface, and the registry updates accordingly before the HookServer resumes normal delivery.

**3. Session Control API Integration**
When the registry state changes (e.g., an operator calls `control.setPause("agent-42", true)`), the next hook evaluation cycle picks up the new flag. The HookServer translates this state change into concrete session behavior without requiring the agent to implement pause logic internally.

## Summary

- The **ControlRegistry** in [`src/main/control.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/control.ts) stores per-session flags (`paused`, `gated`, `steering`, `halted`) that represent operator intent.
- **HookServer** instantiation in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts) injects the registry as a shared dependency.
- At runtime, `handleHookPayload` in [`src/main/hooks.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/hooks.ts) queries `getSessionState()` to read current control flags before delivering hooks.
- Control signals feed into agent sessions through delivery blocking, HITL prompts, or direct state evaluation on each hook cycle.

## Frequently Asked Questions

### What is the ControlRegistry responsible for in munder-difflin?

The ControlRegistry is a state management class defined in [`src/main/control.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/control.ts) that maintains a map of control flags for each active agent session. It tracks whether a session is paused, gated, steering, or halted, providing the single source of truth that the HookServer consults before processing lifecycle hooks.

### How does the HookServer detect when an agent should pause?

The HookServer checks the `paused` flag by calling `this.control?.getSessionState(payload.sessionId)` inside the `handleHookPayload` method. If the flag is true, it immediately invokes `blockDelivery(payload)`, preventing the hook from reaching the agent until the operator clears the pause state in the registry.

### Where does the ControlRegistry get initialized in the codebase?

The registry initializes in [`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts) as a singleton instance: `const control = new ControlRegistry()`. This instance gets passed into the HookServer constructor, ensuring both components share the same state reference throughout the application lifecycle.

### How does gating work with human approval?

When the ControlRegistry indicates `gated: true` for a session, the HookServer's `applyGate` method redirects the payload to `showPermissionPrompt(payload)`, which renders a UI prompt for operator approval. The registry updates after the human decision, allowing the HookServer to either proceed with delivery or discard the hook based on the response.