--- 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/)