What Is the Two‑Plane Architecture of Munder Difflin? A Deep‑Dive into Terminal and Event Planes
Munder Difflin uses a two‑plane architecture that splits terminal I/O handling from high‑level event coordination, enabling real CLI processes via node‑pty while keeping UI logic decoupled through a hook‑based event system.
This design pattern powers Munder Difflin’s ability to run authentic terminal agents alongside rich, React‑driven visualizations. Rather than simulating shells in JavaScript, the system spawns genuine pseudo‑terminals (the terminal plane) and layers a structured communication bus (the event plane) on top for orchestration and UI updates.
The Two Planes Explained
The architecture deliberately separates concerns into two distinct data planes that converge on a single renderer.
Terminal Plane: Real PTY Processes
The terminal plane creates and manages actual operating‑level pseudo‑terminals for every agent. This guarantees byte‑perfect compatibility with any CLI tool, shell, or TUI application.
- Core implementation:
src/main/pty.tswraps node‑pty to spawn processes - Pool management:
src/renderer/src/components/terminalPool.tsmaintains terminal instances - Output handling: Raw byte streams travel over Electron IPC to xterm.js or Pixi.js renderers
Because the terminal plane deals only in raw PTY data, agents behave exactly as they would in a standalone terminal—colors, ANSI sequences, cursor positioning, and interactive prompts all work without emulation bugs.
Event Plane: Structured Lifecycle Hooks
The event plane intercepts and broadcasts high‑level events without interfering with byte streams. It provides a publish/subscribe API that components use to react to agent state changes.
- Core implementation:
src/main/hive.tshosts the hook‑based event emitter (the "hive") - Launch coordination:
src/main/workerLaunch.tsorchestrates agent startup through the event plane - Consumption pattern: Renderer components subscribe to events like
agent:start,agent:exit, or custom prompt signals
This plane enables cross‑cutting concerns—budget enforcement, circuit breakers, human‑approval gates, logging—without polluting the raw PTY layer.
How the Planes Converge on One Renderer
Both planes feed into a unified React + Pixi.js frontend. The renderer composes three responsibilities:
- Draw terminal output – Subscribes to IPC messages from the terminal plane and writes bytes to xterm.js or canvas textures
- Render UI chrome – Uses event plane hooks to display status badges, progress indicators, and control panels
- Coordinate multi‑agent views – Manages layout and focus across many concurrent terminal sessions
The separation means UI engineers never touch PTY internals, while systems engineers can extend orchestration logic without breaking terminal rendering.
Code Walkthrough: Each Plane in Practice
Spawning a Terminal (Terminal Plane)
Located in src/main/pty.ts, the wrapper forwards raw PTY data across the Electron main‑renderer boundary:
// src/main/pty.ts
import * as pty from 'node-pty';
export function launchAgent(agentId: string, cmd: string, args: string[]) {
const term = pty.spawn(cmd, args, {
name: 'xterm-color',
cols: 80,
rows: 24,
cwd: process.cwd(),
env: process.env,
});
// Terminal plane: raw bytes → renderer
term.onData(data => sendIpc('pty-data', { agentId, data }));
term.onExit(info => sendIpc('pty-exit', { agentId, ...info }));
return term;
}
Broadcasting Events (Event Plane)
The hive in src/main/hive.ts offers a typed event bus for lifecycle coordination:
// src/main/hive.ts
import { EventEmitter } from 'events';
export const hive = new EventEmitter();
export function announceStart(agentId: string) {
hive.emit('agent:start', { agentId, timestamp: Date.now() });
}
// Subscription elsewhere
hive.on('agent:start', ({ agentId }) => {
console.log(`🟢 Agent ${agentId} is now running`);
});
Consuming Both Planes in the Renderer
src/renderer/src/components/TerminalView.tsx demonstrates dual consumption—raw terminal data for display, events for state management:
// src/renderer/src/components/TerminalView.tsx
import { useEffect } from 'react';
import { ipcRenderer } from 'electron';
export function TerminalView({ agentId }: { agentId: string }) {
useEffect(() => {
const handleData = (_: any, { agentId: id, data }: any) => {
if (id === agentId) termRef.current?.write(data);
};
ipcRenderer.on('pty-data', handleData);
return () => ipcRenderer.removeListener('pty-data', handleData);
}, [agentId]);
// Pixi.js canvas receives terminal texture
return <PixiTerminalCanvas agentId={agentId} />;
}
Why Two Planes?
| Benefit | How the two‑plane design delivers |
|---|---|
| Reliability | node‑pty provides real terminals, eliminating string‑simulation edge cases |
| Scalability | Lightweight hooks in the event plane coordinate hundreds of agents without PTY overhead |
| Extensibility | New UI features subscribe to events; new orchestration logic ignores rendering |
| Testability | Event plane can be mocked; terminal plane can be replaced with fixtures |
Summary
- Terminal plane (
src/main/pty.ts,terminalPool.ts) handles raw PTY I/O via node‑pty - Event plane (
src/main/hive.ts,workerLaunch.ts) provides hook‑based lifecycle coordination - Single renderer combines React and Pixi.js to present both raw terminal output and structured UI state
- The split enables authentic CLI compatibility alongside rich, event‑driven orchestration
Frequently Asked Questions
What problem does the two‑plane architecture solve?
The two‑plane architecture solves the tension between authentic terminal behavior and rich application orchestration. Simulating terminals in JavaScript introduces bugs with complex TUIs, while exposing raw PTY internals to UI code creates fragile coupling. The split lets Munder Difflin run real shells and still coordinate them through clean, testable hooks.
Can I use the event plane without the terminal plane?
Yes. The event plane in src/main/hive.ts is an independent EventEmitter. You can emit and subscribe to events for mock agents, background jobs, or external integrations without spawning any PTY processes. This is useful for testing orchestration logic or integrating non‑terminal workloads.
How does node‑pty differ from child_process.spawn?
node‑pty creates a pseudo‑terminal (PTY), allocating a TTY device that programs detect as an interactive terminal. This enables color output, readline behavior, and TUI rendering. child_process.spawn creates a standard pipe, which many CLI tools run in non‑interactive mode, suppressing colors and prompts. Munder Difflin’s terminal plane requires PTYs for faithful agent reproduction.
Where is the authoritative design documentation?
The original architecture rationale lives in blog/src/posts/architecture-two-planes-one-renderer.md (source). This file preceded the implementation and remains the reference for design decisions around plane separation and renderer unification.
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 →