Back to Documentation
Product Guide

Terminal Sessions

Persistent PTY sessions, agent attention detection, and recovery behaviour in the Implementation Plans terminal.

6 min read

Terminal sessions are managed by terminal_manager.rs (41KB), which provides PTY-based terminal emulation using the portable-pty crate. Each session maintains dual output channels for local consumption and remote WebSocket clients, with a 32MB in-memory buffer per session and automatic SQLite persistence every 10 seconds.

Terminal session architecture

PTY process lifecycle, output routing, and persistence layers.

Terminal session architecture showing PTY, channels, and persistence
Click to expand
Placeholder for terminal session architecture diagram.

PTY Management with portable-pty

The terminal manager uses the portable-pty crate to spawn pseudo-terminal processes. Each PTY is configured with the user's default shell, working directory, and environment variables. The manager maintains a HashMap of active sessions keyed by session_id.

PTY Operations

  • PTY spawning: CommandBuilder with shell path, args, cwd, and env vars passed to PtySystem::default().openpty()
  • Terminal sizing: PtySize struct with rows, cols, pixel_width, pixel_height sent via master.resize()
  • Output reading: Blocking read loop on master.try_clone_reader() with configurable buffer size
  • Input writing: master.take_writer() for stdin, supporting raw bytes and escape sequences

Dual Output Routing

Each session maintains two output destinations: a local Channel for UI consumption and WebSocket connections for mobile clients. Output is broadcast to all connected receivers without blocking the PTY read loop.

Local Channel

tokio::sync::mpsc::channel with 1024-message buffer for UI updates. The React TerminalView component consumes this channel and renders output in real-time.

Remote WebSocket

Binary frames sent to connected mobile clients via device link. Uses PTC1 framing protocol for efficient multiplexed streaming.

Broadcast pattern: Output is cloned to each receiver; slow consumers are dropped to prevent backpressure from affecting the PTY read loop.

32MB In-Memory Buffer

Each session maintains a 32MB circular buffer for output history. This allows clients to reconnect and receive recent output without querying the database. The buffer automatically evicts oldest content when full.

// Buffer configuration
const BUFFER_SIZE: usize = 32 * 1024 * 1024; // 32MB per session

struct SessionBuffer {
    data: VecDeque<u8>,
    write_offset: usize,
}

impl SessionBuffer {
    fn push(&mut self, bytes: &[u8]) {
        // Evict from front if buffer full
        while self.data.len() + bytes.len() > BUFFER_SIZE {
            self.data.pop_front();
        }
        self.data.extend(bytes);
        self.write_offset += bytes.len();
    }

    fn get_since(&self, offset: usize) -> &[u8] {
        // Return bytes from offset to current end
    }
}

PTC1 Binary Framing for Mobile

Mobile clients receive terminal output using the PTC1 binary framing protocol. This compact format includes message type, session ID, and payload length to support multiplexed session streams over a single WebSocket connection.

// PTC1 Frame Structure
┌─────────────────────────────────────────────────────┐
│  Type   │     Session UUID      │  Payload Length   │
│ (1 byte)│     (16 bytes)        │   (4 bytes LE)    │
├─────────────────────────────────────────────────────┤
│                   Payload Data                      │
│                 (variable length)                   │
└─────────────────────────────────────────────────────┘

Message Types:
  0x01 = Terminal output
  0x02 = Resize notification
  0x03 = Input acknowledgment
  0x04 = State change event

The compact header (21 bytes) minimizes overhead for real-time streaming to bandwidth-constrained mobile clients.

Session State Machine

Sessions transition through defined states that reflect the PTY process lifecycle and recovery status.

InitializingPTY spawn in progress, not yet ready for input
RunningPTY active, accepting input and producing output
SuspendedPTY paused (SIGSTOP), can be resumed
ExitedPTY process terminated with exit code
KilledPTY forcibly terminated (SIGKILL)
ErrorPTY encountered unrecoverable error
RestoredSession recovered from SQLite after app restart

SQLite Persistence with 10-Second Flush

Session state and output logs are flushed to SQLite every 10 seconds. This provides durability without impacting PTY performance. On app restart, sessions in Running or Suspended state are restored from the database.

-- terminal_sessions table schema
CREATE TABLE terminal_sessions (
    session_id      TEXT PRIMARY KEY,
    created_at      TEXT NOT NULL,
    updated_at      TEXT NOT NULL,
    status          TEXT NOT NULL,  -- Initializing|Running|Suspended|Exited|...
    exit_code       INTEGER,
    working_dir     TEXT NOT NULL,
    env_vars        TEXT,           -- JSON object
    output_log      BLOB            -- Full session output for restoration
);

-- Restoration query on startup
SELECT * FROM terminal_sessions
WHERE status IN ('Running', 'Suspended');

Persistence Details

  • Flush interval: 10 seconds via tokio::time::interval
  • Output log: BLOB column containing full session output history
  • Restoration: Replay output_log to buffer, resume PTY monitoring

Health Checks: PTY + Database

Health checks combine PTY process status with database records to determine session liveness. This dual-source approach handles edge cases where the PTY dies without updating the database.

Health Check Process

  • PTY check: try_wait() on child process to detect exit without blocking
  • Database check: Query terminal_sessions for status and updated_at timestamp
  • Reconciliation: If PTY dead but DB shows Running, update status to Exited with detected exit code

Session Lifecycle

Sessions follow a predictable lifecycle from creation through termination or restoration. The UI component initiates sessions, the terminal manager handles PTY operations, and the persistence layer ensures durability.

1

Create

UI calls create_terminal_session command with working_dir and env overrides

2

Stream

Output flows through mpsc channel to TerminalView component for rendering

3

Input

Keystrokes sent via write_to_terminal command, supporting raw mode and escape sequences

4

Terminate

close_terminal_session sends SIGTERM, waits 5s, then SIGKILL if needed

Agent Attention Detection

The terminal monitors agent activity through a two-level inactivity detection system. When an agent stops producing output, the system progressively alerts you to check what has happened:

Level 1 (30 seconds)

Agent idle - may have completed task. Yellow indicator displayed.

Level 2 (2 minutes)

Agent requires attention - check terminal. Red indicator and desktop notification.

Attention indicators clear automatically when new output is received. This helps track when agents have finished tasks or need guidance without guessing why they stopped.

Dependency and Shell Detection

Before launching commands, the terminal checks for configured CLI tools and reports the default shell. This ensures users know which environment will run and whether required tools are available.

  • Shell detection: SHELL env var on Unix, ComSpec on Windows, fallback to /bin/sh or cmd.exe
  • Tool checks: which/where command for configured CLI binaries (claude, aider, cursor, etc.)
  • Reporting: Missing tools surfaced in UI before session creation to prevent failed executions

Voice Transcription and Recovery

Voice Input

Voice transcription can capture speech and paste it into the terminal input area. The recording hook looks up project-level transcription settings, tracks recording state, and streams recognized text into the active plan session.

Session Recovery

If a PTY session disconnects, the terminal surface displays recovery controls and retries with exponential backoff. Health checks continue monitoring session state and provide automatic recovery actions when connection issues are detected.

Connect terminals to execution

See how copy buttons hand off plan content to terminal sessions for agent execution.