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

# Agent UI Server

> Technical specification for the GAIA Agent UI FastAPI backend, SQLite database, and REST API

<Note>
  **You are viewing:** API Specification - Complete technical reference

  **See also:** [User Guide](/guides/agent-ui) | [SDK Reference](/sdk/sdks/agent-ui)
</Note>

<Note>
  * **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/`](https://github.com/amd/gaia/blob/main/src/gaia/ui/)
</Note>

***

## 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. Every route
is registered under the `/api` prefix (e.g. the `chat` router serves
`POST /api/chat/send`); the **Mount** column below names the surface, not the
literal path prefix. Additional routers exist beyond the core set below
(`connectors`, `goals`, `hub`, `memory`, `schedules`).

| Module         | Mount                   | Purpose                                                                           |
| -------------- | ----------------------- | --------------------------------------------------------------------------------- |
| `agents.py`    | `/agents`               | List + inspect registered agents (from [`AgentRegistry`](/spec/plugin-registry)). |
| `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

```python theme={null}
def create_app(db_path: str = None, webui_dist: 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:

1. A background thread calls `AgentSDK.send_stream()` synchronously
2. Chunks are placed into a `queue.Queue`
3. The async generator polls the queue and yields SSE events
4. On completion, the full response is saved to the database

```python theme={null}
# 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`)

### Schema

Four tables with foreign key relationships:

```sql theme={null}
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

```python theme={null}
@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](https://github.com/amd/gaia/issues/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

***

<small style="color: #666;">
  Copyright(C) 2024-2026 Advanced Micro Devices, Inc. All rights reserved.

  SPDX-License-Identifier: MIT
</small>
