Documentation Index
Fetch the complete documentation index at: https://amd-gaia.ai/docs/llms.txt
Use this file to discover all available pages before exploring further.
- Component: Agent UI Server - FastAPI backend for the desktop chat application
- Module:
gaia.ui
- Import:
from gaia.ui.server import create_app
- Source:
src/gaia/ui/
Overview
The Agent UI Server is a FastAPI-based backend that powers the GAIA Chat desktop application. It provides REST API endpoints for session management, real-time chat with SSE streaming, document library management, and system health monitoring.
Key Design Decisions:
- SQLite for zero-configuration persistence (WAL mode for concurrent reads)
- FastAPI for automatic OpenAPI docs and Pydantic validation
- SSE (Server-Sent Events) for streaming chat responses
- Hybrid document model โ global library with per-session attachment
- Shared database between CLI and desktop app
Module Structure
src/gaia/ui/
__init__.py # Package init
server.py # FastAPI app factory and global router wiring
database.py # ChatDatabase class (SQLite)
models.py # Pydantic request/response models
sse_handler.py # SSE output handler for streaming agent events
tunnel.py # Tunnel manager for mobile access via ngrok
dispatch.py # Background task dispatcher (agent jobs, session GC, etc.)
document_monitor.py # Watches the library for new/changed files
dependencies.py # FastAPI dependency-injection helpers (shared registry/db)
build.py # Embedded-frontend build bootstrap for prebuilt dist/
utils.py # Cross-router helpers
_chat_helpers.py # Agent factory / session mapping used by the chat router
routers/ # One module per API surface (see below)
Routers (src/gaia/ui/routers/)
As of v0.17.x the HTTP surface is split into focused router modules:
| Module | Mount | Purpose |
|---|
agents.py | /agents | List + inspect registered agents (from AgentRegistry). |
chat.py | /chat, /chat/stream | Send messages, stream agent events via SSE. |
sessions.py | /sessions | Create/list/delete chat sessions. |
documents.py | /documents | Document library CRUD, metadata, per-session attachment. |
files.py | /files | Raw file uploads and attachments for chat. |
mcp.py | /mcp | Configure and inspect MCP servers used by agents. |
system.py | /system | Health, version, model status, diagnostics. |
tunnel.py | /tunnel | Ngrok/Cloudflared tunnel lifecycle. |
Server (server.py)
App Factory
def create_app(db_path: str = None) -> FastAPI:
Creates a configured FastAPI application with:
- CORS middleware (allows all origins for local use)
- Database initialization and shared
AgentRegistry
- Router registration (
include_router for each module above)
- Static file serving (for web frontend, when
dist/ build exists)
- Dispatch-queue + document-monitor startup tasks
Default port: 4200
Endpoint Registration
Each router under src/gaia/ui/routers/ is mounted via app.include_router(...).
Routers receive the shared ChatDatabase and AgentRegistry through the
dependency helpers in dependencies.py. Database objects remain accessible via
app.state.db for tests.
Streaming Implementation
Chat streaming uses a thread-based producer/consumer pattern:
- A background thread calls
AgentSDK.send_stream() synchronously
- Chunks are placed into a
queue.Queue
- The async generator polls the queue and yields SSE events
- On completion, the full response is saved to the database
# SSE event format
data: {"type": "chunk", "content": "partial text"}
data: {"type": "done", "message_id": 42, "content": "full response"}
data: {"type": "error", "content": "error description"}
Helper Functions
| Function | Description |
|---|
_session_to_response(session) | Convert DB dict to SessionResponse |
_message_to_response(msg) | Convert DB dict to MessageResponse (parses JSON rag_sources) |
_doc_to_response(doc) | Convert DB dict to DocumentResponse |
_sanitize_document_path(user_path) | Validate and sanitize user-provided file paths (null bytes, extension allowlist, path resolution) |
_sanitize_static_path(base_dir, user_path) | Validate URL paths for static file serving stay within base directory |
_validate_file_path(filepath) | Check path is absolute, no null bytes, and has allowed extension |
_compute_file_hash(filepath) | SHA-256 hash for document deduplication |
_index_document(filepath) | Index file via RAG SDK, returns chunk count |
_get_chat_response(db, session, request) | Non-streaming LLM response |
_stream_chat_response(db, session, request) | SSE streaming generator |
Database (database.py)
Four tables with foreign key relationships:
documents -- Global document library
sessions -- Chat conversations
session_documents -- Many-to-many attachment relationship
messages -- Conversation messages with optional RAG metadata
Configuration
- Foreign keys: Enabled (
PRAGMA foreign_keys = ON)
- Journal mode: WAL (
PRAGMA journal_mode = WAL)
- Thread safety:
check_same_thread=False (FastAPI runs across threads)
- Row factory:
sqlite3.Row for dict-like access
Transaction Management
@contextmanager
def _transaction(self):
"""Execute operations atomically."""
try:
yield
self._conn.commit()
except Exception:
self._conn.rollback()
raise
Document Deduplication
Documents are deduplicated by SHA-256 file hash. If a document with the same hash already exists, the existing record is returned with an updated last_accessed_at timestamp.
Document Upload Paths
The server exposes two document upload endpoints, each for a different source of truth:
| Endpoint | Input | Used By | Storage |
|---|
POST /api/documents/upload-path | JSON {filepath} โ absolute server-side path | File browser (user picks an existing file) | Indexes in place; server does NOT own the file |
POST /api/documents/upload | multipart/form-data file blob | Drag-and-drop in browser mode / modern Electron | File is persisted under ~/.gaia/documents/<uuid>.<ext> and owned by the server |
The blob endpoint exists because browser File objects never expose a filesystem path, and since Electron 32 File.path has been removed as well (see issue #728). It streams the upload with a running size check (capped at 20 MB), computes a SHA-256 hash, short-circuits on dedup by hash, and only then promotes the file into the managed directory via an atomic os.replace. On failure, partial and orphan files are cleaned up.
When a server-owned document is deleted (DELETE /api/documents/{id}), the on-disk file under ~/.gaia/documents/ is also unlinked as a best-effort cleanup. User-owned files from upload-path are left untouched.
Cascade Deletes
- Deleting a session cascades to its messages and session_documents entries
- Deleting a document cascades to session_documents entries
Models (models.py)
All 14 Pydantic models:
| Model | Type | Used By |
|---|
SystemStatus | Response | GET /api/system/status |
CreateSessionRequest | Request | POST /api/sessions |
UpdateSessionRequest | Request | PUT /api/sessions/{session_id} |
SessionResponse | Response | Session endpoints |
SessionListResponse | Response | GET /api/sessions |
ChatRequest | Request | POST /api/chat/send |
ChatResponse | Response | POST /api/chat/send (non-streaming) |
SourceInfo | Nested | RAG source citations |
MessageResponse | Response | GET /api/sessions/{session_id}/messages |
MessageListResponse | Response | GET /api/sessions/{session_id}/messages |
DocumentResponse | Response | Document endpoints |
DocumentListResponse | Response | GET /api/documents |
DocumentUploadRequest | Request | POST /api/documents/upload-path |
UploadFile (multipart) | Request | POST /api/documents/upload (blob) |
AttachDocumentRequest | Request | POST /api/sessions/{session_id}/documents |
Dependencies
Required
| Package | Purpose |
|---|
fastapi | Web framework |
uvicorn | ASGI server |
pydantic | Request/response validation |
httpx | Async HTTP client (Lemonade health checks) |
Optional
| Package | Purpose |
|---|
psutil | Memory availability check in system status |
gaia.chat.sdk | LLM communication (AgentSDK) |
gaia.rag.sdk | Document indexing (RAGSDK) |
Error Handling
| Error | HTTP Code | Behavior |
|---|
| Session not found | 404 | "Session not found" |
| Document not found | 404 | "Document not found" or "File not found: {path}" |
| Path is not a file | 400 | "Path is not a file" |
| Unsupported export format | 400 | "Unsupported format: {format}" |
| LLM error (non-streaming) | 200 | Error message in response text |
| LLM error (streaming) | 200 | SSE error event: {"type": "error", "content": "..."} |
| Database locked | 500 | SQLite WAL mode minimizes this |
Security Considerations
- CORS: Uses an allowlist of localhost origins plus ngrok regex. The standalone server (
python -m gaia.ui.server) binds to localhost by default. Note: gaia chat --ui binds to 0.0.0.0 for Electron/browser access
- No authentication: Designed for single-user local use. Do not expose to untrusted networks
- File path validation: The
upload-path endpoint sanitizes all user-provided paths:
- Null byte rejection (prevents path injection)
- Path resolution to absolute canonical paths (eliminates
.. traversal)
- File extension allowlist (only document/code file types accepted)
- See
_sanitize_document_path() in server.py
- Static file serving: URL paths are validated to stay within the
dist/ directory via _sanitize_static_path(), preventing directory traversal
- SQL injection: Prevented by parameterized queries throughout
- No telemetry: No data is sent externally