3.6 KiB
ADR-001 — Shell / Terminal Boundary and Transport Model
Status: Accepted Date: 2026-04-22 Issue: #1695 — ATLAS Cockpit: operator inspector rail and session shell patterns
Context
The Nexus operator cockpit needs a real shell/terminal surface so the operator can run commands, inspect logs, and interact with the system without leaving the 3D world UI.
Three transport models were evaluated:
| Option | Description |
|---|---|
| A. Native local PTY | server.py spawns a PTY via Python's pty stdlib module; the browser connects via WebSocket on ws://127.0.0.1:8766/pty |
| B. Remote SSH PTY | Browser connects to an SSH relay that opens a PTY on a remote machine |
| C. Browser pseudo-terminal | Pure JavaScript terminal emulation (e.g., a custom REPL) with no real OS shell |
Decision
Option A — Native local PTY is chosen.
The Nexus is explicitly a local-first system (CLAUDE.md: "local-first training ground for Timmy"). The operator is the same person sitting at the machine. A local PTY bridged through server.py is:
- Architecturally consistent with the existing
server.pyWebSocket gateway - Zero additional infrastructure (no SSH relay, no remote server)
- Full shell fidelity — any tool on the operator's PATH, full interactive programs, readline, color, etc.
- Bounded: PTY_PORT (8766) binds to
127.0.0.1only; no external exposure
Transport Detail
Browser (xterm.js) ←→ ws://127.0.0.1:8766/pty ←→ server.py pty_handler ←→ OS PTY ($SHELL)
- Each WebSocket connection gets its own
pty.openpty()pair and subprocess. - Input from xterm.js
onData→_ptyWs.send(data)→os.write(master_fd, data) - Output from PTY →
os.read(master_fd)→websocket.send(text)→_term.write(text) - Resize messages (
{"type":"resize","cols":N,"rows":N}) can be added later viafcntl.ioctl(TIOCSWINSZ).
Why not Option B (Remote SSH PTY)?
Remote SSH adds network hop complexity, credential management, and an SSH relay service. The Nexus does not currently have a remote operator use-case; Alexander operates locally. This decision can be revisited when fleet/remote-agent use-cases mature (see issues #672–#675).
Why not Option C (Browser pseudo-terminal)?
A JavaScript REPL cannot run arbitrary shell programs, manage processes, or provide the raw interactive shell experience that operators expect. It would be a toy, not a tool.
Rejected Patterns
- tmux over WebSocket — adds server-side state complexity without enough benefit at this scale
- ttyd — external binary dependency; overkill for a local-first single-operator setup
- xterm.js + websocketd — external binary dependency;
server.pyalready owns the WebSocket gateway
Consequences
server.pynow starts two WebSocket servers:PORT(8765, main gateway) andPTY_PORT(8766, shell gateway)PTY_PORTalways binds to127.0.0.1regardless ofNEXUS_WS_HOSTcockpit-inspector.jsloads xterm.js from CDN (offline fallback: graceful degradation — the rail renders without the terminal pane)- PTY sessions are ephemeral (no persistence across browser reload or server restart)
- Future: add TIOCSWINSZ resize support; add
/pty?shell=zshquery param selection
Related
cockpit-inspector.js— implements the browser side (xterm.js + WebSocket)server.py— implementspty_handler()and_get_git_status()docs/ATLAS-COCKPIT-PATTERNS.md— documents source patterns adopted- Issues: #1695, #686, #687