feat: guard Agent UI against unsupported devices#593
Merged
kovtcharov-amd merged 12 commits intomainfrom Mar 19, 2026
Merged
Conversation
itomek
approved these changes
Mar 19, 2026
2604363 to
5f9612b
Compare
95ec6df to
5ff6124
Compare
Add a device compatibility check before starting the Agent UI that allows only AMD Ryzen AI Max (Strix Halo) CPUs and AMD Radeon GPUs with >= 24 GB VRAM — the minimum required to run Qwen3-Coder-30B. - CLI: new _get_processor_name() reads the CPU name from the Windows registry (instant, no subprocess). _get_gpu_info() reads GPU name and accurate VRAM via the HardwareInformation.qwMemorySize QWORD registry key (bypasses the Win32_VideoController 4 GB cap). _check_device_supported() returns (bool, device_name) and is called inside _launch_agent_ui() before the server starts. - Bypass options: --skip-device-check flag (top-level + chat subcommand), --base-url (remote Lemonade — inference on remote machine), or GAIA_SKIP_DEVICE_CHECK=1 env var. - API: SystemStatus model gains processor_name and device_supported fields populated by the system/status endpoint. - Frontend: ConnectionBanner shows a dismissible info banner on unsupported devices with a pre-filled GitHub feature-request link. SettingsModal shows the detected processor in the System Status grid. - Tests: 27 unit tests cover processor detection, GPU VRAM gating, edge cases (unknown device, multi-GPU, NVIDIA rejection, etc.).
- Extract device detection logic (_get_processor_name, _get_gpu_info, _check_device_supported) into lightweight gaia.device module to eliminate circular import risk from gaia.ui.routers.system → gaia.cli - Fix winreg key handle leak: use context-manager (with) in all registry reads so keys are closed even if QueryValueEx raises - Fix CR1: add --base-url to the top-level parser so gaia --ui --base-url works and the printed bypass instructions are correct; forward base_url to _launch_agent_ui at both top-level call sites - Fix copyright year in system.py (2024-2025 → 2024-2026) - Respect GAIA_SKIP_DEVICE_CHECK env var in system/status endpoint so the UI banner is consistent with the CLI bypass decision - Simplify ConnectionBanner dismissed-state logic (remove dead early return) - Update unit tests to import from gaia.device and patch at the correct module path
- Update agent-ui.mdx Warning block to list both supported hardware configurations (Ryzen AI Max and Radeon >= 24 GB VRAM) - Add troubleshooting accordion for unsupported device error with three bypass options (--base-url, --skip-device-check, env var) - Add --skip-device-check and --base-url to cli.mdx top-level flags table and gaia chat flags table - Add Note clarifying GAIA_SKIP_DEVICE_CHECK env var bypass Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
5ff6124 to
2c1cf93
Compare
PR #566 (Fix Agent UI Round 5) accidentally stripped the extended Lemonade info fields and Settings endpoints from the Agent UI: - Restore SystemStatus fields: lemonade_version, model_size_gb, model_device, model_context_size, model_labels, gpu_name, gpu_vram_gb, tokens_per_second, time_to_first_token - Restore ModelStatus, SettingsResponse, SettingsUpdateRequest models - Restore _check_model_status(), /api/settings GET and PUT endpoints in system.py, and the 10s Lemonade timeout - Restore extended status rows in SettingsModal.tsx - Restore extended fields in types/index.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move `from gaia.device import ...` from mid-file (line 686, after function definitions) to the top of cli.py to satisfy Pylint C0413 (wrong-import-position) - Remove unused `_get_gpu_info` and `_get_processor_name` aliases from cli.py — only `check_device_supported` and the URL constant are used directly in cli.py; the gpu/processor helpers are called internally by check_device_supported inside gaia.device - Remove unused `_get_gpu_info` import from test_device_check.py - Auto-fix Black formatting and isort order Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tempfile creates files in /tmp on Linux CI, which is outside the user's home directory. The safe_open_document() security check correctly rejects paths outside home with 403, causing the tests to fail in CI while passing locally on Windows (where temp files land under the home directory). Fix: pass dir=Path.home() to NamedTemporaryFile and TemporaryDirectory in the three affected tests so they always land inside home on all platforms. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ask about "my pet" explicitly (not "Max") so tiny models connect the question to the established dog context. Broaden assertion keywords to include terms small models commonly use (canine, companion, puppy) when describing pet type. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sers - ConnectionBanner: add Case 4 for Lemonade running but no model loaded; Case 3 now shows `gaia init --profile chat` for first-time users and `lemonade-server serve` for users who are already initialized - WelcomeScreen: surface first-run setup hints from system status — `gaia init --profile chat` when not initialized, model hint when no model is loaded - SettingsModal: show inline fix hints on failing status rows (Lemonade not running, model not loaded, low disk space when model missing) - cli.py: print prerequisites checklist on Agent UI startup; add specific OSError handler for port conflicts with `--ui-port` suggestion - docs/guides/agent-ui.mdx: add "Before You Start" steps section with explicit `gaia init --profile chat` command, disk space note, and expanded troubleshooting for device bypass and model download All setup guidance consistently uses `gaia init --profile chat` (the profile that downloads LLM + embedding + VLM models needed by the UI).
- device.py: detect AMD Ryzen AI Max (Strix Halo) and AMD Radeon GPUs with >= 24 GB VRAM via Windows registry; fail-open on unknown hardware - routers/system.py: populate processor_name and device_supported in /api/system/status; respect GAIA_SKIP_DEVICE_CHECK and remote base-url - cli.mdx, agent-ui.mdx (sdk): document bypass flags and system status fields; restore ModelStatus/SettingsResponse API classes - test_device_check.py: 27 unit tests for CPU/GPU detection and VRAM boundary conditions - test_server.py: system status endpoint tests for device fields
- Patch sys.platform to "win32" in all TestCheckDeviceSupported hardware tests so they run correctly on Linux CI (OS check fires before mocks) - Remove f-strings without interpolation in cli.py (Pylint W1309) - Auto-fix Black formatting in cli.py and test_server.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Suppress local prerequisites hint when --base-url (remote server) is used
- Fix GAIA_SKIP_DEVICE_CHECK to require explicit truthy value ("1"/"true"/"yes")
instead of any non-empty string (avoids GAIA_SKIP_DEVICE_CHECK=0 being truthy)
- Add 0.0.0.0 to local-address list so LEMONADE_BASE_URL=http://0.0.0.0:8000
is not misclassified as a remote server
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix is_remote URL check to use urlparse hostname comparison instead of fragile substring matching (e.g. 'not-localhost.example.com' no longer matches 'localhost') - Reduce timeout for optional /stats and /system-info calls to 3s so system status endpoint stays fast when these endpoints are slow - Fix copyright year in _chat_helpers.py (2025 -> 2026) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
itomek
added a commit
that referenced
this pull request
Mar 23, 2026
PR #566 squash-merged a stale branch that had resolved merge conflicts by keeping older file versions, reverting 3 previously-merged PRs from main: - PR #564: TOCTOU upload locking security fix - PR #565: Tool execution guardrails with confirmation popup - PR #568: Agent UI overhaul (CSS design system, animations, UX polish) Follow-up PRs #593/#604/#605 partially restored functionality. This PR restores all remaining missing changes while preserving those follow-ups. Changes: - 24 files: clean restore from pre-revert commit (CSS, components, utils) - Security: restore per-file asyncio.Lock upload guard (dependencies.py, documents.py, server.py) - SSE handler: restore <think> block state machine, UUID-scoped confirms, timeout parameter, friendly error messages - Frontend: restore AnimatedPresence, session hash badge, smooth streaming exit, custom model override UI, terminal typing animation, inference stats - Backend: restore custom_model DB override, Lemonade stats fetching, friendlier user-facing error messages - Tests: 497 passing, TypeScript build clean (1845 modules) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 tasks
itomek
added a commit
that referenced
this pull request
Mar 23, 2026
PR #566 squash-merged a stale branch that had resolved merge conflicts by keeping older file versions, reverting 3 previously-merged PRs from main: - PR #564: TOCTOU upload locking security fix - PR #565: Tool execution guardrails with confirmation popup - PR #568: Agent UI overhaul (CSS design system, animations, UX polish) Follow-up PRs #593/#604/#605 partially restored functionality. This PR restores all remaining missing changes while preserving those follow-ups. Changes: - 24 files: clean restore from pre-revert commit (CSS, components, utils) - Security: restore per-file asyncio.Lock upload guard (dependencies.py, documents.py, server.py) - SSE handler: restore <think> block state machine, UUID-scoped confirms, timeout parameter, friendly error messages - Frontend: restore AnimatedPresence, session hash badge, smooth streaming exit, custom model override UI, terminal typing animation, inference stats - Backend: restore custom_model DB override, Lemonade stats fetching, friendlier user-facing error messages - Tests: 497 passing, TypeScript build clean (1845 modules) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
github-merge-queue bot
pushed a commit
that referenced
this pull request
Mar 23, 2026
… (#608) ## Summary PR #566 was accidentally merged with stale conflict resolutions that reverted 3 previously-merged PRs. Follow-up PRs #593/#604/#605 partially restored functionality. This PR restores all remaining missing changes. **Root cause:** During a `git merge origin/main` into the branch (commit `f07b932`), conflict resolution kept the branch's older file versions, discarding work from 3 PRs. The squash merge then propagated this to main. **Reverted PRs restored by this PR:** - **#564** — TOCTOU race condition fix: per-file `asyncio.Lock` for document uploads (`dependencies.py`, `routers/documents.py`, `server.py`) - **#565** — Tool execution guardrails: `<think>` block state machine, UUID-scoped confirms, inference stats, custom model override, friendly error messages (`sse_handler.py`, `_chat_helpers.py`, `models.py`) - **#568** — Agent UI overhaul: CSS design system (glassmorphism, animations), AnimatedPresence, session hash badge, smooth streaming exit, terminal typing animation, custom model override UI, `appendThinkingContent`, `format.ts` utilities (`App.tsx`, `ChatView.tsx`, `AgentActivity.tsx`, `SettingsModal.tsx/css`, `WelcomeScreen.tsx/css`, `Sidebar.tsx/css`, `MessageBubble.tsx/css`, `chatStore.ts`, 12 other CSS files, `shell_tools.py`, `database.py`) **Preserved follow-up PR additions:** - #593: Device support banners, processor name display, Lemonade hints - #604: `permission_request` events, `confirmTool` API, `fileList` pass-through, PermissionPrompt - #605: RAG indexing guards ## Test plan - [x] `python -m pytest tests/unit/chat/ui/ --tb=short` — 497 passed - [x] `python util/lint.py --black --isort` — all checks pass - [x] `npm run build` in `src/gaia/apps/webui/` — 1,845 modules, no TypeScript errors - [ ] Smoke test: `gaia chat --ui` — verify UI loads, settings modal shows custom model override, welcome screen has typing animation, chat streams correctly - [ ] Verify concurrent document uploads use per-file locking 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
gaia --ui/gaia chat --uiagainst devices that lack the memory to run large local LLMs--base-url(remote Lemonade server) orGAIA_SKIP_DEVICE_CHECK=1Changes
New module:
src/gaia/device.pyget_processor_name()— reads CPU name from Windows registry (instant, no subprocess); falls back toplatform.processor()on non-Windowsget_gpu_info()— reads GPU name + VRAM via theHardwareInformation.qwMemorySizeQWORD registry key (accurate; bypasses the Win32_VideoController 4 GB cap)check_device_supported()— checks CPU first (Ryzen AI Max), then AMD Radeon GPU ≥ 24 GB VRAM; fail-open on unknown hardwareCLI (
src/gaia/cli.py)_launch_agent_ui()— prints a prerequisites checklist on startup (gaia init --profile chat+lemonade-server serve)OSErrorhandler for port conflicts: friendly message with--ui-port 8080suggestion--base-urlflag on top-level parser (bypasses device check — inference runs remotely)Backend API (
src/gaia/ui/)models.py:SystemStatusgainsprocessor_name,device_supported,initialized, and extended Lemonade fields (lemonade_version,model_size_gb,model_device,model_context_size,model_labels,gpu_name,gpu_vram_gb,tokens_per_second,time_to_first_token); also restoresModelStatus/SettingsResponse/SettingsUpdateRequest(accidentally stripped by Fix Agent UI Round 5: hide post-tool thinking, FileListView, text spacing #566)routers/system.py:/api/system/statuspopulates all fields;GAIA_SKIP_DEVICE_CHECKenv var and remote--base-urlboth bypass the device check for UI banner consistencyFrontend (
src/gaia/apps/webui/)ConnectionBanner.tsx: four-case banner system (in priority order):gaia chat --uigaia init --profile chat(first time) orlemonade-server serve(already set up)gaia init --profile chat+ disk space warning if < 30 GB freeWelcomeScreen.tsx: readssystemStatusfrom store; shows an amber hint box wheninitialized === falseor no model is loaded, with the exact command to runSettingsModal.tsx: inline fix hints on failing status rows — Lemonade not running, model not loaded, disk space warning; all pointing togaia init --profile chatConnectionBanner.css,WelcomeScreen.css,SettingsModal.css: styles for new UI elements (light + dark mode)types/index.ts:SystemStatusinterface updated with all new and restored fieldsDocs
docs/guides/agent-ui.mdx:gaia init --profile chat→lemonade-server serve→gaia chat --uigaia init --profile chat, "Unsupported device" documents--base-urlandGAIA_SKIP_DEVICE_CHECKdocs/reference/cli.mdx:--base-urldocumented in top-level flag tableTests
tests/unit/test_device_check.py: 27 unit tests covering CPU detection, GPU VRAM boundary (23.9 / 24.0 / 24.1 GB), multi-GPU, unknown device, NVIDIA rejection, registry fallback, and non-Windows platformtests/unit/chat/ui/test_server.py: system status endpoint tests includinginitializedanddevice_supportedfieldsCommand Consistency
All user-facing surfaces consistently use
gaia init --profile chat(the profile that downloads the LLM + embedding + VLM models the Agent UI needs):!initialized && !lemonade_runninggaia init --profile chatinitialized && !lemonade_runninglemonade-server servelemonade_running && !model_loadedgaia init --profile chat!initializedgaia init --profile chat!model_loadedgaia init --profile chatgaia init --profile chatlemonade-server servegaia init --profile chatTest plan
gaia --uion a Strix Halo machine → launches normally, no bannergaia --uion an unsupported machine → blue device banner with processor name + GitHub linkgaia --ui --base-url http://remote:8000/api/v1→ bypasses device check, launchesset GAIA_SKIP_DEVICE_CHECK=1 && gaia --ui→ bypasses device checklemonade-server servegaia init --profile chatinsteadgaia init --profile chatinitialized=false→ amber hint box withgaia init --profile chat--ui-port 8080suggestionpytest tests/unit/test_device_check.py→ 27 passedpytest tests/unit/chat/ui/test_server.py→ passed