Documentation Index
Fetch the complete documentation index at: https://docs.monolex.ai/llms.txt
Use this file to discover all available pages before exploring further.
SessionActor Pattern
The SessionActor pattern is the core of Monolex’s terminal architecture. It eliminates all locks by ensuring a single owner for all session state.
The Problem: Lock Contention
Traditional terminal architectures suffer from lock contention:
╔═════════════════════════════════════════════════════════════════╗
║ TRADITIONAL ARCHITECTURE (Multiple Owners) ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ Thread A (PTY Read) Thread B (Resize) Thread C (UI) ║
║ │ │ │ ║
║ ▼ ▼ ▼ ║
║ ┌─────────────────────────────────────────────────────────┐ ║
║ │ Mutex<SessionState> │ ║
║ │ ↓ Lock contention ↓ │ ║
║ │ Potential deadlocks │ ║
║ └─────────────────────────────────────────────────────────┘ ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Problems:
- Lock contention under high load
- Potential for deadlocks
- Complex reasoning about concurrent access
- Performance degradation
The Solution: Single Owner
SessionActor owns all state. No locks required.
╔═════════════════════════════════════════════════════════════════╗
║ SESSIONACTOR ARCHITECTURE (Single Owner) ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ IPC Handler PTY Socket Resize Event ║
║ │ │ │ ║
║ │ MPSC │ MPSC │ ║
║ └───────────────┼─────────────────┘ ║
║ ▼ ║
║ ┌────────────────────────────────────────────┐ ║
║ │ SessionActor ← Single owner │ ║
║ │ │ ║
║ │ sessions: HashMap<String, SessionState> │ ║
║ │ grid_workers: HashMap<String, Sender> │ ║
║ │ pty_client: PtyClient │ ║
║ │ │ ║
║ │ NO LOCKS! │ ║
║ └────────────────────────────────────────────┘ ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Implementation
Core Structure
╔═════════════════════════════════════════════════════════════════╗
║ SESSIONACTOR STRUCTURE ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ SessionActor owns: ║
║ ║
║ ┌───────────────────────────────────────────────────────────┐ ║
║ │ SESSION STATE (all sessions) │ ║
║ │ ├── Session states (per session) │ ║
║ │ ├── Grid workers (per session) │ ║
║ │ └── Log workers (per session) │ ║
║ └───────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌───────────────────────────────────────────────────────────┐ ║
║ │ COMMUNICATION │ ║
║ │ ├── Command receiver (MPSC) │ ║
║ │ └── Command sender (for external use) │ ║
║ └───────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌───────────────────────────────────────────────────────────┐ ║
║ │ CONNECTIONS │ ║
║ │ ├── PTY Client (daemon communication) │ ║
║ │ └── App Handle (Tauri events) │ ║
║ └───────────────────────────────────────────────────────────┘ ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Command Pattern
All operations are sent as commands through MPSC channels:
╔═════════════════════════════════════════════════════════════════╗
║ SESSION COMMANDS ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ Command Parameters Returns ║
║ ───────────────── ──────────────────────── ───────── ║
║ Create cols, rows session_id ║
║ Write session_id, data - ║
║ Resize session_id, cols, rows - ║
║ Close session_id - ║
║ Pause session_id - ║
║ Resume session_id - ║
║ GridAck session_id - ║
║ ║
║ All commands flow through a single MPSC channel ║
║ → Guaranteed ordering, no race conditions ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Event Loop
╔═════════════════════════════════════════════════════════════════╗
║ SESSIONACTOR EVENT LOOP ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ ┌──────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ ┌─────────────────┐ │ ║
║ │ │ Receive command │ ◀─── MPSC Channel │ ║
║ │ └────────┬────────┘ │ ║
║ │ │ │ ║
║ │ ▼ │ ║
║ │ ┌─────────────────┐ │ ║
║ │ │ Match command │ │ ║
║ │ └────────┬────────┘ │ ║
║ │ │ │ ║
║ │ ┌────────┴────────┬────────┬────────┬─────────┐ │ ║
║ │ ▼ ▼ ▼ ▼ ▼ │ ║
║ │ Create Write Resize Close Pause │ ║
║ │ │ ║
║ │ └─────────────────────────────────────────────┘ │ ║
║ │ │ │ ║
║ │ └──────────────────── (loop) ─────────────┘ ║
║ │ ║
║ └──────────────────────────────────────────────────────────┘
║ ║
║ One command at a time → No locks needed → Simple reasoning ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Benefits
| Aspect | With Locks | SessionActor |
|---|
| Deadlocks | Possible | Impossible |
| Lock contention | Under load | None |
| Reasoning | Complex | Simple |
| Performance | Degrades | Consistent |
| Code complexity | High | Low |
GridWorker: Per-Session Processing
Each session has its own GridWorker running in a separate task:
╔═════════════════════════════════════════════════════════════════╗
║ GRIDWORKER PER SESSION ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ SessionActor ║
║ │ ║
║ ├── GridWorker (session-1) ──▶ emit("pty-grid-session-1") ║
║ │ └── Terminal Parser ║
║ │ └── ACK state ║
║ │ ║
║ ├── GridWorker (session-2) ──▶ emit("pty-grid-session-2") ║
║ │ └── Terminal Parser ║
║ │ └── ACK state ║
║ │ ║
║ └── GridWorker (session-N) ──▶ emit("pty-grid-session-N") ║
║ └── Terminal Parser ║
║ └── ACK state ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Message Flow Example
User types 'ls' in terminal:
1. Frontend: invoke("write_to_pty", { sessionId, data: "ls\n" })
2. Tauri: Sends SessionCommand::Write to SessionActor
3. SessionActor: Forwards to PtyClient via Unix socket
4. PTY Daemon: Writes to PTY, reads output
5. PTY Daemon: Sends output back via Unix socket
6. SessionActor: Routes to GridWorker
7. GridWorker: Parses terminal data, converts cells
8. GridWorker: emit("pty-grid-{sessionId}", GridUpdate)
9. Frontend: Receives event, injects to xterm buffer
10. Frontend: invoke("grid_ack", { sessionId })
11. Repeat from step 6 if more data pending
Why Not Other Patterns?
Why not RwLock?
- Still has contention between readers and writers
- Deadlock potential with multiple locks
- Complex upgrade/downgrade semantics
Why not Actor per Operation?
- Too fine-grained, message passing overhead
- Loses locality of related state
- Harder to reason about ordering
Why SessionActor?
- Natural boundary: one actor per “thing” (terminal sessions)
- All related state co-located
- Simple event loop, easy to understand
- Guaranteed ordering within actor
SMPC/OFAC Applied
| Principle | Application |
|---|
| SMPC | Single owner = simple reasoning |
| MPSC channels = simple communication |
| No locks = no complexity |
| OFAC | Actor pattern emerged from need |
| Per-session workers = natural parallelism |
| Commands = organized chaos |