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
Copy
┌─────────────────────────────────────────────────────────────────────┐
│ 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:Copy
/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
Usesportable-pty crate for cross-platform PTY management:
- No node-pty (native module issues)
- Pure Rust implementation
- macOS, Linux, Windows support
Protocol
Control Socket Commands
| Command | Description |
|---|---|
CREATE | Create new PTY session |
LIST | List active sessions |
KILL | Terminate a session |
SHUTDOWN | Graceful daemon shutdown |
Session Socket Protocol
Binary protocol for data transfer:Copy
┌─────────────────────────────────────────────────────────────────────┐
│ 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
Copy
┌─────────────────────────────────────────────────────────────────────┐
│ 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
Copy
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
Copy
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
| Environment | Path |
|---|---|
| Development | src-tauri/binaries/pty-daemon-rust-{triple} |
| Production | MacOS/pty-daemon-rust |
Tauri Configuration
Copy
┌─────────────────────────────────────────────────────────────────┐
│ 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
| Platform | Suffix |
|---|---|
| macOS ARM | aarch64-apple-darwin |
| macOS Intel | x86_64-apple-darwin |
| Linux | x86_64-unknown-linux-gnu |
| Windows | x86_64-pc-windows-msvc.exe |
Logging
Copy
┌─────────────────────────────────────────────────────────────────┐
│ 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
Copy
┌─────────────────────────────────────────────────────────────────┐
│ 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
Copy
┌─────────────────────────────────────────────────────────────────┐
│ 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)
Copy
┌─────────────────────────────────────────────────────────────────┐
│ 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
| Metric | Value |
|---|---|
| Binary size | ~953KB |
| Startup | Fast |
| Memory per session | Low |
| Communication | Unix sockets |
SMPC/OFAC Applied
| Principle | Application |
|---|---|
| SMPC | Single responsibility: PTY management only |
| Simple protocol: length-prefixed binary | |
| One socket per session: clear isolation | |
| OFAC | Crash resilience emerged from separate process |
| Recovery pattern emerged from socket persistence |