Skip to main content

PTY Daemon Architecture

The PTY Daemon (pty-daemon-rust) is a standalone Rust binary that manages PTY sessions. It survives app crashes and enables session recovery.

Overview

┌─────────────────────────────────────────────────────────────────────┐
│                    ARCHITECTURE OVERVIEW                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌───────────────────────────────────────────────────────────────┐ │
│   │                    Tauri App Process                          │ │
│   │                                                               │ │
│   │   ┌─────────────┐    MPSC     ┌─────────────────────────────┐ │ │
│   │   │ IPC Handler │───Channel──→│      SessionActor           │ │ │
│   │   │ (invoke)    │             │                             │ │ │
│   │   └─────────────┘             └─────────────────────────────┘ │ │
│   │                                                               │ │
│   └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│   ┌───────────────────────────────────────────────────────────────┐ │
│   │              PTY Daemon (Detached Process)                    │ │
│   │              pty-daemon-rust (953KB)                          │ │
│   │                                                               │ │
│   │   ┌──────────────────┐      ┌──────────────────────────────┐  │ │
│   │   │ Control Socket   │      │ portable-pty                 │  │ │
│   │   │ daemon-control-  │      │ (Pure Rust PTY)              │  │ │
│   │   │ {PID}.sock       │      └──────────────────────────────┘  │ │
│   │   └──────────────────┘                                        │ │
│   │   ┌──────────────────┐                                        │ │
│   │   │ Session Sockets  │  ← One per terminal session            │ │
│   │   │ session-{ID}.sock│                                        │ │
│   │   └──────────────────┘                                        │ │
│   │                                                               │ │
│   │   App Crash → Daemon Survives → Sessions Recoverable          │ │
│   └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Key Features

Crash Resilience

The daemon runs as a separate process from the main app:
  • App crashes don’t kill the daemon
  • Terminal sessions continue running
  • Sessions can be recovered on app restart

Unix Socket Communication

Each session has its own Unix socket:
/tmp/monolex-pty/
├── daemon-control-{PID}.sock    # Control channel
├── session-abc123.sock          # Session 1
├── session-def456.sock          # Session 2
└── session-ghi789.sock          # Session 3

Pure Rust PTY

Uses portable-pty crate for cross-platform PTY management:
  • No node-pty (native module issues)
  • Pure Rust implementation
  • macOS, Linux, Windows support

Protocol

Control Socket Commands

CommandDescription
CREATECreate new PTY session
LISTList active sessions
KILLTerminate a session
SHUTDOWNGraceful daemon shutdown

Session Socket Protocol

Binary protocol for data transfer:
┌─────────────────────────────────────────────────────────────────────┐
│  SESSION SOCKET PROTOCOL                                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Client → Daemon (Write to PTY):                                    │
│  ┌────────────┬─────────────────────────────────────────────────┐   │
│  │ Length (4B)│ Data (variable)                                 │   │
│  └────────────┴─────────────────────────────────────────────────┘   │
│                                                                     │
│  Daemon → Client (PTY Output):                                      │
│  ┌────────────┬─────────────────────────────────────────────────┐   │
│  │ Length (4B)│ Raw PTY output (binary + ANSI)                  │   │
│  └────────────┴─────────────────────────────────────────────────┘   │
│                                                                     │
│  Note: 4KB chunk size for Unix socket efficiency                    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Lifecycle

Startup

┌─────────────────────────────────────────────────────────────────────┐
│  DAEMON STARTUP SEQUENCE                                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   1. Check daemon status                                            │
│      └── Already running? → Skip (reuse existing)                   │
│                                                                     │
│   2. Spawn daemon as sidecar                                        │
│      └── Tauri Shell API spawns pty-daemon-rust                     │
│                                                                     │
│   3. Wait for control socket                                        │
│      └── Poll until socket file exists                              │
│                                                                     │
│   4. Connect client                                                 │
│      └── PtyClient connects to control socket                       │
│                                                                     │
│   Result: Daemon ready, accepting session requests                  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Session Creation

1. Frontend: invoke("create_session", { cols: 120, rows: 40 })
2. SessionActor: Sends CREATE to daemon control socket
3. Daemon: Spawns PTY, creates session socket
4. Daemon: Returns session ID
5. SessionActor: Connects to session socket
6. SessionActor: Spawns GridWorker for this session
7. Frontend: Receives session ID, ready to use

Crash Recovery

1. App crashes
2. Daemon continues running (separate process)
3. PTY sessions stay alive
4. App restarts
5. Frontend: invoke("list_sessions")
6. Daemon: Returns list of active session IDs
7. Frontend: Reconnects to existing sessions
8. User sees their terminal sessions restored

Configuration

Binary Location

EnvironmentPath
Developmentsrc-tauri/binaries/pty-daemon-rust-{triple}
ProductionMacOS/pty-daemon-rust

Tauri Configuration

┌─────────────────────────────────────────────────────────────────┐
│  SIDECAR CONFIGURATION                                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Tauri bundles the daemon as an external binary:               │
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  externalBin: pty-daemon-rust                           │   │
│   │                                                         │   │
│   │  • Automatically bundled with app                       │   │
│   │  • Target triple suffix added automatically             │   │
│   │  • Spawned via Tauri Shell API                          │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Target Triple Suffix

PlatformSuffix
macOS ARMaarch64-apple-darwin
macOS Intelx86_64-apple-darwin
Linuxx86_64-unknown-linux-gnu
Windowsx86_64-pc-windows-msvc.exe

Logging

┌─────────────────────────────────────────────────────────────────┐
│  DAEMON LOG FLOW                                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Daemon starts                                                 │
│        │                                                        │
│        ▼                                                        │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  INFO   Starting PTY daemon                             │   │
│   │  INFO   Control socket created                          │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   Session requested                                             │
│        │                                                        │
│        ▼                                                        │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  INFO   Session created: {session_id}                   │   │
│   │  INFO   Session socket ready                            │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   Log location: system temp directory                           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Troubleshooting

Check Daemon Status

┌─────────────────────────────────────────────────────────────────┐
│  STATUS CHECK FLOW                                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Step 1: Verify daemon process                                  │
│  ─────────────────────────────                                  │
│  □ Check if pty-daemon-rust is running                          │
│  □ Note the process ID                                          │
│                                                                 │
│  Step 2: Check socket files                                     │
│  ─────────────────────────────                                  │
│  □ Verify control socket exists                                 │
│  □ Count session sockets (one per terminal)                     │
│                                                                 │
│  Step 3: Review logs                                            │
│  ─────────────────────────────                                  │
│  □ Check for startup errors                                     │
│  □ Look for session creation failures                           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Kill Stale Daemon

┌─────────────────────────────────────────────────────────────────┐
│  CLEANUP PROCEDURE                                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  If daemon becomes unresponsive:                                │
│                                                                 │
│  Step 1: Terminate daemon process                               │
│          └── Kill the pty-daemon-rust process                   │
│                                                                 │
│  Step 2: Clean up socket files                                  │
│          └── Remove leftover socket files                       │
│                                                                 │
│  Step 3: Restart app                                            │
│          └── Daemon will spawn fresh on next launch             │
│                                                                 │
│  ⚠️  Note: Active terminal sessions will be lost                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Code Signing (macOS)

┌─────────────────────────────────────────────────────────────────┐
│  CODE SIGNING REQUIREMENT                                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ⚠️  CRITICAL: Daemon MUST be signed on macOS                    │
│                                                                 │
│  Without signature:                                             │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  macOS → SIGKILL → Daemon dies immediately              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
│  With signature:                                                │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  macOS → ✓ Verified → Daemon runs normally              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
│  Signing checklist:                                             │
│  □ Sign with Developer ID Application certificate               │
│  □ Verify signature before distribution                         │
│  □ Notarize for macOS Gatekeeper                                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Characteristics

MetricValue
Binary size~953KB
StartupFast
Memory per sessionLow
CommunicationUnix sockets

SMPC/OFAC Applied

PrincipleApplication
SMPCSingle responsibility: PTY management only
Simple protocol: length-prefixed binary
One socket per session: clear isolation
OFACCrash resilience emerged from separate process
Recovery pattern emerged from socket persistence