## Context Currently, terminal sessions are ephemeral. Each WebSocket connection to `/ws/tool-instances/{instance_id}/terminal` spawns a new `docker exec` process via `TerminalManager.create_session()`. When the WebSocket disconnects, the session is cleaned up and the `docker exec` process is killed. This means users lose their shell state, running processes, and command history every time they disconnect. The current architecture: - `TerminalManager` tracks sessions by `session_id` (UUID) in a dictionary - Each session creates a new PTY and `docker exec` process - WebSocket I/O loops are tied to the session lifecycle - No concept of reconnection or session persistence ## Goals / Non-Goals **Goals:** - Terminal sessions persist across WebSocket disconnections/reconnections - Users reconnect to the same shell process, maintaining state and history - Add explicit "Reset Terminal" functionality to kill and restart the session - Graceful handling of idle timeouts to clean up abandoned sessions - Buffer recent output for replay on reconnect **Non-Goals:** - Multi-user shared terminal sessions (one session per instance, but only one active WebSocket at a time) - Session persistence across container restarts or instance stops - Full terminal scrollback history persistence (only recent buffer) - Automatic session restoration after instance restart ## Decisions **1. Session tracking by instance_id** - Rationale: One persistent session per tool instance is the simplest model - Alternative: Track by user_id + instance_id — rejected because it's overkill; users don't need multiple terminals to the same container - Trade-off: Only one user can have an active terminal at a time per instance **2. Detach WebSocket from session on disconnect** - Rationale: Keep the `docker exec` process alive, just remove the WebSocket reference - Implementation: Session stores a list of WebSocket connections (initially just one) - On disconnect: remove WebSocket from session, don't kill process - On reconnect: attach new WebSocket to existing session **3. Circular buffer for output replay** - Rationale: Users should see what happened while disconnected - Size: 10KB buffer (configurable) — enough for ~100 lines of typical output - Implementation: Buffer stores raw bytes, replayed on WebSocket attach **4. Reset terminal via WebSocket message** - Rationale: Users need a way to kill a stuck or corrupted session - Implementation: JSON control message `{"type": "reset"}` kills process and starts fresh - Alternative: HTTP endpoint — rejected because it's more complex and less intuitive **5. Idle timeout cleanup** - Rationale: Prevent resource leaks from abandoned sessions - Timeout: 30 minutes of no WebSocket connections - Implementation: Background task checks last_activity timestamp ## Risks / Trade-offs - **[Risk] Zombie sessions**: Users disconnect and never reconnect, leaving `docker exec` processes running - Mitigation: Idle timeout of 30 minutes cleans up abandoned sessions - **[Risk] Session corruption**: If the shell process crashes, the session is dead but still tracked - Mitigation: Health check on `docker exec` process; auto-reset on next connect if dead - **[Risk] Concurrent connections**: Multiple tabs trying to connect to the same instance - Mitigation: Only allow one active WebSocket per session; new connection closes old one with a message ## Migration Plan 1. Deploy updated backend services (TerminalManager, TerminalSession, terminal endpoint) 2. Deploy frontend changes (reset button, reconnection handling) 3. No database migration needed 4. Rollback: Revert to previous code; existing sessions will be killed on disconnect as before ## Open Questions - Should we show a "session resumed" indicator in the UI? - Should we persist the last N commands for command history?