Files
headquarter/openspec/changes/persistent-terminal-sessions/design.md
T
alex a919ff8611 feat: add persistent terminal sessions spec
- Add proposal, design, specs, and tasks for persistent terminal sessions
- Support reconnection, output buffer replay, reset, and idle timeout

Refs: persistent-terminal-sessions
2026-05-24 12:29:29 +00:00

74 lines
3.8 KiB
Markdown

## 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?