Create: aipa-plans/docs/ssh-tui-architecture-spec.md

This commit is contained in:
Atlas
2026-04-14 22:41:53 -07:00
parent 8af10ee356
commit 042b73e80b
@@ -0,0 +1,427 @@
---
created: '2026-04-15T05:41:53+00:00'
id: e8450555-eab3-4d48-a654-b5f1708f0517
keywords:
- SSH-TUI
- AIPA
- architecture
- Textual
- asyncssh
- MCP
- Linus
- TUI
- terminal
- AI copilot
- Pydantic
- aipa-ssh-tui
- graceful degradation
- command detection
- PTY
links: []
modified: '2026-04-15T05:41:53+00:00'
summary: 'Full architecture specification for the AIPA SSH-TUI application: a Textual-based
terminal SSH client with integrated AI copilot, covering component design, data
flows, project structure, configuration, error handling, testing, and security model.'
tags:
- aipa-plans
- ssh-tui
- architecture
- specification
- textual
- asyncssh
- linus
title: AIPA SSH-TUI — Architecture Specification
---
# AIPA SSH-TUI — Architecture Specification
**Status:** Draft — Pending review
**Project:** AIPA SSH-TUI
**Version:** 0.1
**Last updated:** 2025-07-10
**Authors:** Linus (Systems Architect), Cara (UI/UX Design)
## 1. Overview
AIPA SSH-TUI is a terminal-based SSH client with an integrated AI copilot. It wraps SSH sessions in a Textual TUI and connects to the AIPA MCP server to provide real-time AI assistance for system administration tasks. The AI (Linus) monitors terminal output, maintains a plan of action, suggests commands, and responds to natural language queries — but never executes commands without explicit user approval.
### Goals
- **SSH client first** — fully functional SSH terminal even without AI. Graceful degradation if AI is unavailable.
- **AI copilot second** — enhances the SSH experience with context-aware suggestions, intent tracking, and natural language interaction.
- **Safety** — the user is always in control. Every AI-suggested command requires explicit approval. Commands are editable before execution.
- **Auditability** — AI-suggested commands are visually distinct in the terminal so the user always knows who issued what.
### Non-Goals (v1)
- Multiple concurrent SSH sessions
- Remote file inspection via AI
- AI-initiated push notifications
- Native SSH key management
- Port forwarding / tunneling UI
- SSH jump host / proxy support (v1 uses local SSH config or `ProxyCommand`)
## 2. Technology Stack
| Component | Technology | Rationale |
|---|---|---|
| TUI Framework | **Textual** (Python) | Async-native, rich widget system, mature, active development |
| SSH Library | **asyncssh** | Async, native coroutines, plays well with Textual's event loop |
| MCP Client | **mcp** Python SDK | Official MCP client library |
| Configuration | **TOML** | Standard Python config format, human-readable |
| Data Models | **Pydantic** | Validation, serialization, type safety |
| Python | **>=3.11** | Required for asyncssh and modern type syntax |
## 3. TUI Layout
Designed by Cara (UI/UX). The layout follows sysadmin conventions — minimal chrome, single-line borders, dense information display.
```
┌─────────────────────────────── Status Bar ────────────────────────────────────┐
│ host: prod-web-03 user: deploy ● connected AI: ● online │
├──────────────────────────────────┬────────────────────────────────────────────┤
│ │ ┌── Intent ──────────────────────────┐ │
│ │ │ ✓ Check overall disk usage │ │
│ SSH Session │ │ ✓ Identify largest directories │ │
│ (main interaction surface) │ │ ▸ List largest files in /var/log │ │
│ │ │ ○ Determine cleanup strategy │ │
│ │ │ ○ Free disk space safely │ │
│ root@prod:~# df -h │ │ │ │
│ Filesystem Size Used ... │ │ ── Confidence: 85% │ │
│ │ └────────────────────────────────────┘ │
│ │ │
│ │ ┌── Suggested Commands ────────────┐ │
│ │ │ 1. [low] ls -lhS /var/log |... │ │
│ │ │ 2. [low] journalctl --disk-use │ │
│ │ │ 3. [med] apt-get clean && ... │ │
│ │ │ │ │
│ │ │ [Enter] Send [e] Edit [d] Del│ │
│ │ └────────────────────────────────────┘ │
├──────────────────────────────────┴────────────────────────────────────────────┤
│ ┌── Chat to AI ──────────────────────────────────────────────────────────┐ │
│ │ > /var is full, what's eating the space? │ │
│ │ AI: Looks like /var/log is the culprit at 4.2G. Queued commands to │ │
│ │ identify the largest files. │ │
│ │ > go ahead with #1 │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ > type here... │
└──────────────────────────────────────────────────────────────────────────────┘
```
### Layout Proportions
- **Top row:** 75% of terminal height
- SSH Session: ~60% width (left)
- Intent + Suggested Commands: ~40% width (right), stacked vertically with Intent taking ~55% and Commands taking ~45%
- **Bottom row:** 25% of terminal height (minimum 6 lines, resizable)
- Chat to AI: full width
### Key UX Rules (from Cara)
1. **Escape always returns focus to SSH** — panic button for when something goes wrong. The sysadmin needs `Ctrl+C` in the shell immediately.
2. **Focus cycling:** `Tab` cycles SSH → Commands → Chat (skips Intent, it's read-only). `Ctrl+J`/`Ctrl+K` for up/down cycling.
3. **Intent is a living checklist** — uses ✓ (done), ▸ (current), ○ (pending). Not overwritten each turn; steps are added/updated incrementally.
4. **Suggested commands are editable** — press `e` to inline-edit before sending. `Enter` sends the selected command.
5. **AI-sent commands are visually distinct** — in the SSH panel, commands from the suggestions are prefixed with `→` and dimmed.
6. **Scroll isolation** — each panel scrolls only when focused. Unfocused panels show a `▼ N new lines` indicator.
7. **Graceful degradation** — if AI drops, Intent shows "AI offline", Commands show "(commands may be stale)", Chat queues messages, but SSH stays fully functional.
8. **Minimal chrome** — single-line borders, single-character labels. This lives next to `vim` and `htop` — it should look like it belongs.
### Key Bindings
| Key | Context | Action |
|---|---|---|
| `Escape` | Anywhere | Return focus to SSH session |
| `Tab` | Anywhere | Cycle focus: SSH → Commands → Chat → SSH |
| `Ctrl+J` | Anywhere | Focus previous panel |
| `Ctrl+K` | Anywhere | Focus next panel |
| `Enter` | SSH Session | Send input (passthrough) |
| `Enter` | Commands Panel | Send selected command |
| `Enter` | Chat Panel | Send message to AI |
| `e` | Commands Panel | Inline-edit selected command |
| `d` | Commands Panel | Delete selected command |
| `↑`/`↓` | Commands Panel | Select command |
| `Ctrl+L` | Anywhere | Force redraw |
## 4. System Architecture
### 4.1 Component Diagram
```
┌─────────────────────────────────────────────────────────────────┐
│ SSH-TUI Application │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ TUI │ │ Session │ │ MCP Client │ │
│ │ Layer │◄──┤ Manager │ │ │ │
│ │ │ │ │ │ ┌───────────────────┐ │ │
│ │ ┌──────┐ │ │ ┌────────┐ │ │ │ Connection Mgmt │ │ │
│ │ │SSH │ │ │ │ asyncssh│ │ │ │ Message Queue │ │ │
│ │ │Panel │ │ │ │ session │ │ │ │ Debounce Logic │ │ │
│ │ └──────┘ │ │ └────────┘ │ │ └───────────────────┘ │ │
│ │ ┌──────┐ │ │ ┌────────┐ │ │ │ │
│ │ │Intent│ │ │ │Terminal│ │ └───────────┬─────────────┘ │
│ │ │Panel │ │ │ │Buffer │ │ │ │
│ │ └──────┘ │ │ └────────┘ │ │ │
│ │ ┌──────┐ │ │ ┌────────┐ │ │ │
│ │ │Sugg. │ │ │ │Command │ │ │ │
│ │ │Cmds │ │ │ │Log │ │ │ │
│ │ └──────┘ │ │ └────────┘ │ │ │
│ │ ┌──────┐ │ └──────────────┘ │ │
│ │ │Chat │ │ │ │
│ │ │Panel │ │ │ │
│ │ └──────┘ │ │ │
│ └──────────┘ │ │
└─────────────────────────────────────────────────┼─────────────────┘
│ MCP Protocol
│
┌─────────────────────────────────────────────────┼─────────────────┐
│ AIPA MCP Server │ │
│ ▼ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ Agent │ │ Tool │ │ Linus Agent │ │
│ │ Router │──┤ Registry │──┤ ┌───────────────────┐ │ │
│ │ │ │ │ │ │ linus_chat │ │ │
│ └──────────┘ └──────────────┘ │ │ linus_term_update │ │ │
│ │ │ linus_cmd_exec │ │ │
│ │ │ linus_session_st │ │ │
│ │ └───────────────────┘ │ │
│ └────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
```
### 4.2 Component Responsibilities
#### TUI Layer (`app.py`, `panels/`)
- Manages the Textual application lifecycle
- Renders all four panels and the status bar
- Handles keyboard input and focus management
- Dispatches user actions to the Session Manager or MCP Client
#### Session Manager (`ssh_session.py`)
- Manages the asyncssh connection lifecycle (connect, authenticate, disconnect)
- Creates and manages the PTY (pseudo-terminal) on the remote host
- Captures all terminal output into a ring buffer (configurable, default 2000 lines)
- Tracks the current working directory (by parsing prompts, best-effort)
- Provides a terminal tail snapshot (last N lines) for MCP context
- Tracks command execution: command text, output, exit code, timestamp
- Injects commands into the PTY (from user typing or from sending suggestions)
#### MCP Client (`mcp_client.py`)
- Connects to the AIPA MCP server (configurable URL)
- Manages the message queue for outgoing messages
- Implements debouncing for `terminal_update` messages (1 second)
- Queues `chat` messages when offline, sends on reconnect
- Discards `terminal_update` messages when offline (ephemeral)
- Parses Linus's structured responses and dispatches updates to the appropriate TUI panels
- Handles connection errors and implements retry with exponential backoff
#### Data Models (`models.py`)
- Pydantic models for all message types (see MCP Interface Contract)
- Session state tracking
- Command log entries
### 4.3 Data Flow
#### Flow 1: User sends a chat message
```
User types in Chat Panel → TUI dispatches to MCP Client
→ MCP Client calls linus_chat(message, context)
→ MCP Server routes to Linus
→ Linus processes and returns { chat_response, intent_update, suggested_commands }
→ MCP Client receives response
→ Updates Chat Panel with chat_response.message
→ Updates Intent Panel with intent_update.steps
→ Updates Commands Panel with suggested_commands.commands
```
#### Flow 2: User types in SSH session
```
User types in SSH Panel → TUI passes keystrokes to Session Manager
→ Session Manager writes to asyncssh PTY
→ Remote host processes and produces output
→ Session Manager captures output into ring buffer
→ Session Manager signals MCP Client (terminal output changed)
→ MCP Client debounces (1 second)
→ MCP Client calls linus_terminal_update(new_output, terminal_tail, context)
→ Linus may return { intent_update, suggested_commands } or null
→ If updates: TUI panels refreshed
```
#### Flow 3: User sends a suggested command
```
User selects command + presses Enter → TUI reads selected command
→ TUI sends command to Session Manager
→ Session Manager writes command + newline to asyncssh PTY
→ Session Manager logs: { command, source: "suggested", suggestion_id, was_edited }
→ Output flows back (see Flow 2)
→ When command completes (prompt detected):
→ Session Manager sends to MCP Client
→ MCP Client calls linus_command_executed(command, output, exit_code, context)
→ Linus returns updated { chat_response, intent_update, suggested_commands, clear_suggestions }
```
### 4.4 Terminal Output Detection
Detecting when a command has finished executing (to capture exit code and complete output) is non-trivial in a PTY. Strategy:
1. **Prompt detection (primary):** Parse the shell prompt to detect when a new prompt appears after a command. Configurable prompt pattern (default: `$` or `#` at line start).
2. **Exit code capture:** Before each user/suggested command, inject `\n echo "EXIT:$?"` after the command to capture the exit code. Parse this from the output stream.
3. **Timeout fallback:** If no prompt is detected within a configurable timeout (default: 30 seconds), consider the command "still running" and send what output is available.
## 5. Project Structure
```
aipa-ssh-tui/
├── pyproject.toml # Project metadata, dependencies, entry point
├── README.md # Quick start, usage, screenshots
├── config/
│ └── default.toml # Default configuration
├── src/
│ └── aipa_ssh_tui/
│ ├── __init__.py
│ ├── app.py # Textual App class, layout, CSS
│ ├── ssh_session.py # asyncssh connection, PTY, output capture
│ ├── mcp_client.py # MCP client, message queue, debouncing
│ ├── command_detector.py # Prompt detection, exit code parsing
│ ├── models.py # Pydantic models for all message types
│ ├── config.py # Configuration loading (TOML)
│ └── panels/
│ ├── __init__.py
│ ├── session.py # SSH session panel (RichLog or similar)
│ ├── intent.py # Intent checklist panel
│ ├── suggestions.py # Suggested commands panel + send/edit
│ ├── chat.py # Chat to AI panel
│ └── status_bar.py # Top status bar
└── tests/
├── conftest.py
├── test_models.py
├── test_ssh_session.py
├── test_mcp_client.py
├── test_command_detector.py
└── test_app.py
```
## 6. Configuration
```toml
# config/default.toml
[ssh]
# Default SSH settings (can be overridden via CLI args)
host = ""
port = 22
user = ""
# Path to SSH config file (uses ~/.ssh/config by default)
config_file = ""
[mcp]
# AIPA MCP server connection
server_url = "http://localhost:8000"
# Retry settings
retry_base_delay = 1.0 # seconds
retry_max_delay = 30.0 # seconds
retry_max_attempts = 5
[terminal]
# Terminal buffer settings
ring_buffer_size = 2000 # lines to keep in memory
tail_lines = 100 # lines sent to AI as context
# Prompt detection (regex)
prompt_pattern = "^[^@]*@[^:]*:[^#]*[#$] "
[mcp_debounce]
# Debounce settings for terminal updates
terminal_update_interval = 1.0 # seconds
command_complete_timeout = 30.0 # seconds before considering a command "still running"
[ui]
# TUI appearance
theme = "nord" # Textual theme
ai_command_prefix = "→ " # prefix for AI-sent commands in SSH panel
show_line_numbers = false # line numbers in SSH panel
```
## 7. CLI Interface
```
aipa-ssh-tui [user@]hostname [-p port] [-i identity_file] [--config config.toml]
Examples:
aipa-ssh-tui root@10.250.50.70
aipa-ssh-tui deploy@prod-web-03 -p 2222 -i ~/.ssh/id_prod
aipa-ssh-tui web-01 --config ~/my-ssh-tui.toml
```
If no MCP server is configured or reachable, the TUI operates as a plain SSH client with the Intent and Commands panels hidden or showing "AI not configured".
## 8. Error Handling
| Scenario | Behavior |
|---|---|
| SSH connection fails | Show error in SSH panel. Offer retry. Chat/Intent/Commands non-functional. |
| SSH connection drops mid-session | Show disconnection in status bar and SSH panel. Attempt reconnect (configurable). Queue chat messages. |
| MCP server unreachable | SSH works normally. Intent shows "AI offline". Commands show "(stale)". Chat queues messages. Auto-retry MCP connection. |
| MCP server returns malformed response | Log error. Skip update for malformed component. Show warning in status bar. |
| asyncssh authentication fails | Show error. Do not retry automatically. Prompt for different credentials or key. |
| Terminal resize | Textual handles natively. SSH PTY resized via window-change request. |
## 9. Dependencies
```toml
# pyproject.toml (core dependencies)
[project]
dependencies = [
"textual>=0.47.0",
"asyncssh>=2.14.0",
"pydantic>=2.0.0",
"tomli>=2.0.0; python_version < '3.11'",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.23.0",
"ruff>=0.4.0",
"mypy>=1.10.0",
]
```
## 10. Testing Strategy
| Layer | What to test | Tools |
|---|---|---|
| Data models | Pydantic validation, serialization, defaults | `pytest` |
| Command detector | Prompt regex matching, exit code parsing | `pytest` |
| SSH session | Connect/disconnect, PTY I/O, output capture | `pytest-asyncio` + mock SSH server |
| MCP client | Message queueing, debouncing, offline handling | `pytest` + mock MCP server |
| TUI panels | Rendering, focus management, key bindings | `pytest` + Textual's test driver |
## 11. Security Model
1. **No credential exposure** — SSH passwords and keys never appear in MCP messages. The MCP client only sends `ssh_host` and `ssh_user` in the context object.
2. **User approval required** — every AI-suggested command requires explicit `Enter` to send. No auto-execution.
3. **Command editing** — the user can modify any suggested command before sending.
4. **Risk labels** — every command carries a `risk` level (`low`, `medium`, `high`). High-risk commands are visually highlighted.
5. **Audit trail** — AI-suggested commands are visually distinct in the SSH panel (prefixed with `→`).
6. **Command logging** — all executed commands are logged locally with source, timestamp, and whether they were edited.
## 12. Future Scope (v2+)
- Multiple concurrent SSH sessions (tabs)
- SSH jump host / ProxyCommand support
- AI-driven file inspection (Linus requests file contents from remote host)
- Push notifications from Linus (e.g., "I detected a kernel OOM in dmesg")
- Bash history integration for smarter suggestions
- Saved session profiles (host + common tasks)
- Shared intent/state between sessions
## References
- [SSH-TUI MCP Interface Contract](aipa-plans/docs/ssh-tui-mcp-interface-contract.md) — detailed message format specification
- [Textual Documentation](https://textual.textualize.io/)
- [asyncssh Documentation](https://asyncssh.readthedocs.io/)
- [MCP Specification](https://modelcontextprotocol.io/)