src/gaia/ui/Overview
The Agent UI SDK is the Python backend that powers the GAIA Agent UI. It provides:- FastAPI REST server with session, chat, document, and memory endpoints
- SQLite database for persistent sessions, messages, and document metadata
- SSE streaming for real-time chat responses
- RAG integration for document Q&A
- Memory dashboard with knowledge browser, tool stats, and observability (Memory SDK)
- Pydantic models for request/response validation
4200 by default and serves both the Electron desktop app and browser-based clients.
Quick Start
Start the Server
Use the Database Directly
Core Classes
ChatDatabase
The persistence layer for all Agent UI data. Uses SQLite with WAL mode for concurrent read access.Session Methods
Message Methods
model_messages on the
assistant or autonomous row. These contain the current turn’s user message,
assistant tool calls, tool results and final answer, without duplicating prior
history or the system prompt. The nullable JSON column is added automatically to
existing databases; legacy rows retain their original text-only context.
Restoration budgets complete turns against the resolved model/device window
(including GAIA_CTX_SIZE and the NPU ceiling), reserving the current prompt, native tool schemas, output allowance and
2K estimated tokens of headroom. History uses at most half the resolved window.
Counts use GAIA’s shared token estimator, not a guarantee against the model’s
exact tokenizer. On overflow, old turns are removed in a block toward half the
history budget; the newest turn is retained if it fits. The full stored trace
remains in SQLite, and exclusions produce a warning in the backend log. When a
streamed turn hides more turns than the session’s last streamed turn did (a
longer chat, or a smaller model’s budget), the stream sends one status event
with status: "history_cut", turns and tokens (the turns no longer replayed
and their estimated size), which the chat shows as an inline note. A stable cut
is not reported again. A relayed sidecar agent receives only the last 5
exchanges; its stream sends the same event once, on the turn the sixth arrives.
get_messages and the ordinary transcript API do not expose the internal trace.
Deleting a transcript message also deletes its evidence.
Example:
Document Methods
Statistics
create_app()
Factory function that creates and configures the FastAPI application with all endpoints.app.state and is accessible in tests:
Pydantic Models
All request and response bodies use Pydantic models fromgaia.ui.models.
System
Sessions
Chat
Messages
Documents
REST API Endpoints
System
GET /api/system/status
GET /api/system/status
GET /api/health
GET /api/health
GET /api/system/model-server
GET /api/system/model-server
/health and /system-info. Always 200: an unreachable server comes back as reachable: false with a one-line error. A field Lemonade did not report is null.Response:PUT /api/system/model-server/backend
PUT /api/system/model-server/backend
llamacpp_backend in ~/.gaia/config.json. It applies the next time a chat model loads; the embedding model keeps its own backend.Request:backend is auto or one of the names in available_backends. Returns the refreshed GET /api/system/model-server body. 400 when the backend is not available on this machine, 503 when Lemonade is not answering.POST /api/system/load-model
POST /api/system/load-model
202 immediately; loading proceeds in the background. Poll GET /api/system/status to detect when loading completes.Request:llamacpp_backend is set in ~/.gaia/config.json and the model runs on llama.cpp, the load asks Lemonade for that backend.Response (202):POST /api/system/download-model
POST /api/system/download-model
202 immediately; the download proceeds in the background. Poll GET /api/system/status to detect when the model becomes available. Set force to true to re-download even if the file already exists (repairs corrupted or incomplete downloads).Request:POST /api/system/warm-up
POST /api/system/warm-up
GET /api/system/warm-up returns the same state without starting one, and GET /api/system/status carries it as warm_up.Response:state is one of idle, warming, ready, failed, or skipped.GET /api/settings
GET /api/settings
PUT /api/settings
PUT /api/settings
custom_model to a model ID to override the default, or to an empty string / null to clear the override and revert to the default model.Request:GET /api/settings.Sessions
POST /api/sessions
POST /api/sessions
SessionResponseFrom the Agent UI (X-Gaia-Client: agent-ui) a chat is always the GAIA
agent: an omitted agent_type becomes gaia and any other value is a
422. See Flagship only.GET /api/sessions
GET /api/sessions
limit (default 50), offset (default 0)Response: SessionListResponseGET /api/sessions/{session_id}
GET /api/sessions/{session_id}
SessionResponsePUT /api/sessions/{session_id}
PUT /api/sessions/{session_id}
agent_type, device, or mail_provider while a chat turn is running on the session returns 409; retry once the turn finishes. Renames are always accepted.DELETE /api/sessions/{session_id}
DELETE /api/sessions/{session_id}
GET /api/sessions/{session_id}/messages
GET /api/sessions/{session_id}/messages
limit (default 100), offset (default 0)Response: MessageListResponseGET /api/sessions/{session_id}/export
GET /api/sessions/{session_id}/export
format (“markdown” or “json”, default “markdown”)GET /api/sessions/events
GET /api/sessions/events
set_active_session) —
the frontend subscribes on mount.POST /api/sessions/{session_id}/activate
POST /api/sessions/{session_id}/activate
open_session_in_browser() in the MCP bridge) rather than a user
click. Emits the set_active_session event on /api/sessions/events.PATCH /api/sessions/{session_id}/private
PATCH /api/sessions/{session_id}/private
Chat
POST /api/chat/send
POST /api/chat/send
X-Gaia-Client: agent-ui) a non-gaia agent_type is
a 422, and sending into a chat stored under another agent is a 409
(“start a new chat”). See Flagship only.Request:stream: true, the server returns a text/event-stream response. Each line follows the SSE format data: <JSON>. The SSEOutputHandler (src/gaia/ui/sse_handler.py) bridges agent console events to the following typed events:Thinking and Progressresult_data variants in tool_result:- File list:
{"type": "file_list", "files": [...], "total": int}— up to 20 file entries - Search results:
{"type": "search_results", "count": int, "scores": float[], "previews": string[]}— top 5 chunk previews (200 chars each)
command_output shape in tool_result:success is false when the tool reports status: "error" or a non-zero
return_code. The exception is an exit the tool names in expected_exit,
such as grep’s exit 1 for “no match”: that result stays success: true.Response ContentGET /api/chat/active
GET /api/chat/active
GET /api/chat/attach
GET /api/chat/attach
?session_id=). Read-only — replays every event emitted so far, then
streams live events to completion; takes no session lock.POST /api/chat/cancel
POST /api/chat/cancel
POST /api/chat/user-input
POST /api/chat/user-input
needs_input question from the agent. Tries an
in-process agent first, then an email-relay run blocked on the sidecar’s
own query loop; a session with neither pending is a 404, never a silent
no-op accept.Documents
GET /api/documents
GET /api/documents
POST /api/documents/upload-path
POST /api/documents/upload-path
DocumentResponseDELETE /api/documents/{doc_id}
DELETE /api/documents/{doc_id}
POST /api/sessions/{session_id}/documents
POST /api/sessions/{session_id}/documents
DELETE /api/sessions/{session_id}/documents/{doc_id}
DELETE /api/sessions/{session_id}/documents/{doc_id}
Files
POST /api/files/open
POST /api/files/open
open, and on Linux it uses xdg-open. Symbolic links are rejected for security.Request:POST /api/files/upload
POST /api/files/upload
POST /api/documents/upload instead if you want the
file indexed for Q&A.GET /api/files/browse
GET /api/files/browse
GET /api/files/search
GET /api/files/search
~/Documents, ~/Downloads, ~/Desktop,
~/OneDrive, then the rest of the home directory. The walk is bounded: it
stops at max_results matches, after 20,000 directory entries, or after
about 10 seconds. truncated: true in the response means a bound cut the walk
short and more matches may exist.GET /api/files/preview
GET /api/files/preview
GET /api/files/image
GET /api/files/image
Agents
GET /api/agents
GET /api/agents
AgentRegistry — built-in agents plus any
custom agents discovered under ~/.gaia/agents/. See
plugin-registry for the registration format.gaia agent export and
gaia agent import CLI commands — see Custom Agent.
MCP
The MCP router is read-only status for external MCP servers, plus lifecycle for GAIA’s own MCP server (the one that exposes Agent UI tools to clients like Claude Code). Registering, enabling, and disabling an external MCP server connector lives under Connectors —mcp_server is one
of that router’s connector types.
GET /api/mcp/servers
GET /api/mcp/servers
~/.gaia/mcp_servers.json) and their
enabled state.GET /api/mcp/servers/{name}/tools
GET /api/mcp/servers/{name}/tools
GET /api/mcp/status
GET /api/mcp/status
GET /api/mcp/agent-server/status
GET /api/mcp/agent-server/status
POST /api/mcp/agent-server/start
POST /api/mcp/agent-server/start
{"port": <int>}; defaults to
AGENT_UI_MCP_PORT.POST /api/mcp/agent-server/stop
POST /api/mcp/agent-server/stop
Tunnel
Expose the local Agent UI over the internet via an ngrok/Cloudflare tunnel. Used to reach the UI from a phone.POST /api/tunnel/start
POST /api/tunnel/start
POST /api/tunnel/stop
POST /api/tunnel/stop
GET /api/tunnel/status
GET /api/tunnel/status
Chat (advanced)
POST /api/chat/confirm-tool
POST /api/chat/confirm-tool
confirmation_required_tools() set (the generic base set merged with the
agent’s own CONFIRMATION_REQUIRED_TOOLS) makes the agent pause, the
stream emits a permission_request event carrying confirm_id and, when
“always allow” can be offered, always_scope (what the grant would cover,
e.g. gh issue list). The UI answers here with allow once, always allow,
or deny. An unanswered prompt is denied after 600 seconds.Request:always (with approved: true) grants this exact invocation for the rest
of the chat — see gaia.agents.base.tool_grants. confirm_id echoes the
prompt being answered.Response: {"status": "ok", "approved": true, "granted": "gh issue list"}
— granted is the new grant’s label, or null.Errors: 404 when the chat has no running turn; 409 when
confirm_id no longer matches the live prompt, or always was requested
for a call with no scope narrow enough to grant.GET /api/chat/permissions
GET /api/chat/permissions
{"default_mode", "sessions": [{"session_id", "mode", "grants": [{"key", "label"}]}]}
— default_mode is the mode a new chat starts in: full_access when it is
set in ~/.gaia/config.json, otherwise ask.GET /api/chat/permissions/{session_id}
GET /api/chat/permissions/{session_id}
ask or full_access) and its grants. A chat
created in this backend run starts in ask unless full_access is set in
~/.gaia/config.json; a chat from an earlier run reopens in ask.
Permission state lives in memory: it ends when the chat is deleted or the
backend restarts. 404 when no such chat exists (also for PUT and
DELETE below).PUT /api/chat/permissions/{session_id}
PUT /api/chat/permissions/{session_id}
ask (every gated tool prompts) and full_access
(gated tools run unasked and shell guardrails are lifted, as the TUI’s
/full-access). Takes effect on the next gated tool, including mid-turn.Request: {"mode": "full_access"} — any value other than ask or
full_access is a 422.DELETE /api/chat/permissions/{session_id}/grants
DELETE /api/chat/permissions/{session_id}/grants
?key=<grant key>) or, without key,
all of them. Applies to a running turn too. 404 when key names no
grant in the chat. Returns the chat’s permission view.POST /api/eval/scripted-user
POST /api/eval/scripted-user
GAIA_EVAL_SCRIPTED_USER=1 (403 otherwise).
Scripts a user who declines the shell commands in decline_commands and
answers no questions, so gaia eval agent can score what the agent does
after a “no”. An empty list turns it off. The runner sets it from a
scenario’s setup.decline_commands and clears it afterwards.Documents (advanced)
POST /api/documents/upload
POST /api/documents/upload
GET /api/documents/monitor/status
GET /api/documents/monitor/status
document_monitor.py file-watcher.GET /api/documents/{doc_id}/status
GET /api/documents/{doc_id}/status
POST /api/documents/{doc_id}/cancel
POST /api/documents/{doc_id}/cancel
POST /api/documents/index-folder
POST /api/documents/index-folder
POST /api/documents/{doc_id}/reindex
POST /api/documents/{doc_id}/reindex
complete or failed (never 200 while
leaving the document in a bad state).Sessions (advanced)
DELETE /api/sessions/{session_id}/messages/{message_id}
DELETE /api/sessions/{session_id}/messages/{message_id}
DELETE /api/sessions/{session_id}/messages/{message_id}/and-below
DELETE /api/sessions/{session_id}/messages/{message_id}/and-below
System (advanced)
GET /api/system/tasks
GET /api/system/tasks
src/gaia/ui/dispatch.py.Memory
The Memory endpoints expose the agent’s persistent knowledge system. See the Memory SDK Reference for the underlyingMemoryStore and MemoryMixin APIs.
GET /api/memory/stats
GET /api/memory/stats
GET /api/memory/knowledge
GET /api/memory/knowledge
POST /api/memory/knowledge
POST /api/memory/knowledge
source='user' and confidence=0.8. The entry is embedded right away with the embedder that produced the stored vectors, so recall by meaning finds it without a rebuild.Request:"embedded": false plus an embed_error saying what to do (run Rebuild Embeddings; keyword recall still finds it).PUT /api/memory/knowledge/{knowledge_id}
PUT /api/memory/knowledge/{knowledge_id}
content change re-embeds the entry and the response includes embedded (and embed_error on failure), as for create; other edits keep the existing vector and omit the field.Request:DELETE /api/memory/knowledge/{knowledge_id}
DELETE /api/memory/knowledge/{knowledge_id}
GET /api/memory/upcoming
GET /api/memory/upcoming
days (default 7)Response:GET /api/memory/entities
GET /api/memory/entities
GET /api/memory/tools
GET /api/memory/tools
GET /api/memory/conversations
GET /api/memory/conversations
limit (default 20)GET /api/memory/conversations/{session_id}
GET /api/memory/conversations/{session_id}
GET /api/memory/activity
GET /api/memory/activity
days (default 30)Response:GET /api/memory/embedding-coverage
GET /api/memory/embedding-coverage
POST /api/memory/consolidate
POST /api/memory/consolidate
POST /api/memory/reconcile
POST /api/memory/reconcile
POST /api/memory/rebuild-embeddings
POST /api/memory/rebuild-embeddings
POST /api/memory/rebuild-fts
POST /api/memory/rebuild-fts
GET /api/memory/knowledge/{knowledge_id}
GET /api/memory/knowledge/{knowledge_id}
superseded_by).GET /api/memory/contexts
GET /api/memory/contexts
GET /api/memory/entities/{entity}
GET /api/memory/entities/{entity}
GET /api/memory/errors
GET /api/memory/errors
GET /api/memory/tools/{tool_name}/history
GET /api/memory/tools/{tool_name}/history
GET /api/memory/conversations/search
GET /api/memory/conversations/search
POST /api/memory/prune
POST /api/memory/prune
days (default 90). Returns per-table row counts.DELETE /api/memory/all
DELETE /api/memory/all
POST /api/memory/reinitialize
POST /api/memory/reinitialize
POST /api/memory/refresh-system-context
POST /api/memory/refresh-system-context
category="system" entries and stores fresh ones — no LLM call, runs in
a few seconds.GET /api/memory/stream-discovery
GET /api/memory/stream-discovery
GET /api/memory/stream-inference
GET /api/memory/stream-inference
insight event is a candidate fact awaiting approval. It runs on
the machine’s default local chat model (default_model in
~/.gaia/config.json), never the chat’s cloud model; a cloud
default_model ends the stream with an error event.POST /api/memory/commit-discovery
POST /api/memory/commit-discovery
POST /api/memory/commit-inference
POST /api/memory/commit-inference
source="inferred" rows are deleted first — a failure here leaves
the old profile in place rather than partially wiped).GET /api/memory/settings
GET /api/memory/settings
memory_enabled (default true), mcp_memory_enabled
(expose read tools to MCP clients) and system_discovery_consent (both
default false).PUT /api/memory/settings
PUT /api/memory/settings
system_discovery_consent on also
persists it to ~/.gaia/memory_settings.json and triggers an immediate
system-context refresh.POST /api/memory/admin/clear
POST /api/memory/admin/clear
GAIA_MEMORY_ADMIN=1 (403 otherwise). Wipes
memory for a scenario reset — scope is all / knowledge /
conversations.POST /api/memory/admin/seed
POST /api/memory/admin/seed
GAIA_MEMORY_ADMIN=1. Bulk-inserts knowledge
items verbatim, bypassing dedup, to plant a known state for stress
scenarios.Connectors
OAuth and MCP-server connections for the Settings page and per-agent grants (issue #915). Every mutating route requires theX-Gaia-UI: 1 header — see
Security.
GET /api/connectors
GET /api/connectors
GET /api/connectors/events
GET /api/connectors/events
GET /api/connectors/agent-mcps
GET /api/connectors/agent-mcps
GET /api/connectors/eval-mailbox
GET /api/connectors/eval-mailbox
GAIA_EVAL_MAILBOX (403 otherwise). Reports the
offline fixture mailbox the gaia_email scenarios read: its path, whether
the file exists, and whether it is attached.POST /api/connectors/eval-mailbox
POST /api/connectors/eval-mailbox
GAIA_EVAL_MAILBOX; requires X-Gaia-UI: 1.
{"attached": false} makes the mail tools answer as if no mailbox were
connected, for sessions that start reading mail afterwards; true restores
the fixture. The eval runner calls it from each scenario’s
setup.mailbox_fixture.GET /api/connectors/_debug
GET /api/connectors/_debug
GAIA_DEBUG=1 (404 otherwise).GET /api/connectors/{connector_id}
GET /api/connectors/{connector_id}
GET /api/connectors/{connector_id}/grants
GET /api/connectors/{connector_id}/grants
GET /api/connectors/{connector_id}/activations
GET /api/connectors/{connector_id}/activations
{} for OAuth connectors, which have no MCP tool surface to activate.POST /api/connectors/{connector_id}/configure
POST /api/connectors/{connector_id}/configure
mcp_server connector this
also writes ~/.gaia/mcp_servers.json.POST /api/connectors/{connector_id}/test
POST /api/connectors/{connector_id}/test
DELETE /api/connectors/{connector_id}
DELETE /api/connectors/{connector_id}
mcp_server,
its mcp_servers.json entry.POST /api/connectors/{connector_id}/enable
POST /api/connectors/{connector_id}/enable
mcp_server connector. Its tools
materialize live.POST /api/connectors/{connector_id}/disable
POST /api/connectors/{connector_id}/disable
mcp_server connector without clearing its
credentials.DELETE /api/connectors/_flows/{flow_id}
DELETE /api/connectors/_flows/{flow_id}
PUT /api/connectors/{connector_id}/grants/{agent_id}
PUT /api/connectors/{connector_id}/grants/{agent_id}
DELETE /api/connectors/{connector_id}/grants/{agent_id}
DELETE /api/connectors/{connector_id}/grants/{agent_id}
PUT /api/connectors/{connector_id}/activations/{agent_id}
PUT /api/connectors/{connector_id}/activations/{agent_id}
agent_id for connector_id (mcp_server only — OAuth
connectors 400). Auto-grants when no grant exists and the body supplies
scopes; 400 if it doesn’t.DELETE /api/connectors/{connector_id}/activations/{agent_id}
DELETE /api/connectors/{connector_id}/activations/{agent_id}
agent_id for connector_id. Idempotent, and non-destructive
— the grant is preserved so re-activating is one call; delete the grant
separately to wipe it./v1/connections/* is a separate, related surface — pre-authenticated
connections forwarded from another process (issue #1292). See the
Connectors infrastructure doc for it.Goals
Autonomous goal/task tracking (GoalStore) behind the Agent UI’s Goals panel.
GET /api/goals
GET /api/goals
status, source, and approved_only query
filters.POST /api/goals
POST /api/goals
GET /api/goals/stats
GET /api/goals/stats
GET /api/goals/pending-approval
GET /api/goals/pending-approval
GET /api/goals/{goal_id}
GET /api/goals/{goal_id}
DELETE /api/goals/{goal_id}
DELETE /api/goals/{goal_id}
PUT /api/goals/{goal_id}/approve
PUT /api/goals/{goal_id}/approve
PUT /api/goals/{goal_id}/reject
PUT /api/goals/{goal_id}/reject
PUT /api/goals/{goal_id}/cancel
PUT /api/goals/{goal_id}/cancel
PUT /api/goals/{goal_id}/status
PUT /api/goals/{goal_id}/status
POST /api/goals/{goal_id}/tasks
POST /api/goals/{goal_id}/tasks
PUT /api/goals/{goal_id}/tasks/{task_id}
PUT /api/goals/{goal_id}/tasks/{task_id}
Schedules
Recurring prompts run on a cron-like interval — seegaia schedule for the CLI equivalent. The scheduler runs prompts only; it cannot yet run a skill directly (gaia schedule add --skill is rejected — #1019).
GET /api/schedules
GET /api/schedules
POST /api/schedules
POST /api/schedules
timezone (IANA name, e.g. America/Los_Angeles) sets the zone its times
are in; it defaults to the server PC’s zone. An unknown zone returns 400.POST /api/schedules/parse
POST /api/schedules/parse
timezone and echoes the zone it used.GET /api/schedules/{name}
GET /api/schedules/{name}
PUT /api/schedules/{name}
PUT /api/schedules/{name}
{"status": ...}.DELETE /api/schedules/{name}
DELETE /api/schedules/{name}
GET /api/schedules/{name}/results
GET /api/schedules/{name}/results
Onboarding
First-run hardware scan (issues #1726, #1727).GET /api/onboarding/preflight
GET /api/onboarding/preflight
/system-info, RAM) and report
whether this machine can run the first-run model, with a coarse tier
recommendation. recommended_model is the model chat will load on this
machine (the default_model gaia init chose, else Gemma-4-E4B), and
required_disk_gb is sized for that model’s download.Setup
First-run setup. The Agent UI asks and runs the samegaia init the TUI does,
so the two can never disagree about whether GAIA is set up.
GET /api/setup/check
GET /api/setup/check
gaia init --check --profile gaia --json and returns its result plus
the step list: {"ready": bool, "stage": ..., "reasons": [...], "steps": [...]}.Query params: skip_chat_model (the chat runs on a cloud provider, so
only the embedding model is needed locally), load (also check that the
models load).Errors: 403 without the X-Gaia-UI: 1 header — the check can start
the model server, so it is guarded like a mutating route. 503 when the
check could not be answered — a timeout or an unexpected exit. That is
never reported as “not set up”.POST /api/setup/run
POST /api/setup/run
gaia init --profile gaia --yes in the background; follow it with
/api/setup/status. Body: {"skip_chat_model": false}. 409 when setup
is already running.GET /api/setup/status
GET /api/setup/status
state (running, verifying, ready,
failed, cancelled, or idle when none has run), the three steps with
their status, progress text and percent, error, the equivalent
command, and the last 40 output lines.POST /api/setup/cancel
POST /api/setup/cancel
{"cancelled": bool}.Lemonade server
Which Lemonade Server GAIA uses: its own, or one the user already runs. Same choice and checks asgaia lemonade connect and gaia config set lemonade_mode.
All three routes need the X-Gaia-UI: 1 header (403 without it).
GET /api/lemonade/target
GET /api/lemonade/target
mode (gaia or external),
saved_mode (null until someone chooses), source, base_url,
saved_base_url, api_key_stored, env_override (a warning when
LEMONADE_BASE_URL overrides the setting), gaia_installed, offer (no
choice saved and the environment isn’t choosing, so the UI should offer the
switch), and what_gaia_does. 409 when the config is unreadable or the
saved choice is unusable; detail names the fix.GET /api/lemonade/detect
GET /api/lemonade/detect
{"found": {"url", "status", "version", "minimum", "message"}}, or
{"found": null} when none answers. status is ok, needs_key,
bad_key, too_old, not_lemonade, or unreachable.POST /api/lemonade/target
POST /api/lemonade/target
{"mode": "gaia" | "external", "base_url": "...", "api_key": "..."}; the URL and key are only for external. Nothing is
saved unless the user’s server checks out. A running daemon is told to
stop (or start) GAIA’s own server, and cached agents are dropped. Returns
the GET shape plus daemon. 422 when the server can’t be used
(detail.message, detail.status), including status: "not_installed"
when switching from the user’s server to GAIA’s own before it is
installed; nothing is saved. 409 for an unreadable config, 403 over
mobile access, 502 when the daemon did not switch. A failed switch to
GAIA’s own puts the previous mode back; a failed switch to the user’s
server stays saved.Providers
Where the chat model runs: Local, Fireworks AI, or the AMD LLM Gateway, all served through the local Lemonade — the TUI’s/provider.
Keys go to the local Lemonade only and, once they discover models, into the OS
credential store (the slot the TUI uses). A key is never echoed back or logged.
GET /api/providers
GET /api/providers
registered, key_source (environment,
lemonade, stored, or null), models_discovered — and the active
model. A stored key is handed back to a Lemonade that lost it.
lemonade_error is set when Lemonade could not be reached. Requires
X-Gaia-UI: 1 (403 without it).GET /api/providers/active
GET /api/providers/active
last_model from ~/.gaia/config.json; restore.status is
restored, failed (with the reason — another model is never switched in
quietly), none, or pending while the local model server is still
starting (not cached; ask again). A saved key is never sent to a Lemonade
that is not on this PC. Requires X-Gaia-UI: 1 (403 without it).POST /api/providers/{provider}/connect
POST /api/providers/{provider}/connect
fireworks or amd) in Lemonade and give it
the key. Body: {"api_key", "base_url", "auth_header_name", "auth_header_prefix"}; the last three apply to amd only. A blank
api_key keeps the current key.While GAIA uses the user’s own Lemonade (lemonade_mode external) it
sends that server nothing: it succeeds only when the server already has
the provider working, and a pasted key is not sent anywhere.Errors: 401 when the provider rejects the key or discovers no
models (a key that discovers nothing is removed from Lemonade again);
403 when a key is sent through the mobile tunnel; 409 when an
environment key takes precedence, Lemonade is not on this PC, or the
user’s own server has no working provider.DELETE /api/providers/{provider}/key
DELETE /api/providers/{provider}/key
{"removed": bool}. On the user’s own Lemonade only GAIA’s stored copy is
removed; the server keeps its key.GET /api/providers/{provider}/models
GET /api/providers/{provider}/models
local lists downloaded models plus the default model;
cloud providers list their own. Recommended models come first with a
rank, note, and the measured evidence behind the pick.POST /api/providers/select
POST /api/providers/select
last_model /
last_provider in ~/.gaia/config.json (other keys are kept). Body:
{"model": "fireworks.glm-5p3-flash"}. 409 when the model cannot run
now; 503 while the local model server is still starting. A successful
pick clears this run’s restore notice.Skills
GET /api/skills
GET /api/skills
{"skills": [{"name", "description", "version", "tier", "tools", "path"}], "invalid": {...}}. 503 when the GAIA agent package
is not installed.Hub
Install and uninstall agents from the Agent Hub catalog — the backend for the desktop app’sgaia://hub/install/<id> deep link.
GET /api/agents/catalog
GET /api/agents/catalog
POST /api/agents/install
POST /api/agents/install
install-status for progress.GET /api/agents/{agent_id}/install-status
GET /api/agents/{agent_id}/install-status
DELETE /api/agents/{agent_id}
DELETE /api/agents/{agent_id}
Security
src/gaia/ui/security.py
for the enforcing code.- CSRF header. Every mutating (non-GET/HEAD/OPTIONS) request to
/api/*or/v1/*must carryX-Gaia-UI: 1. A cross-origin page cannot set a custom header without triggering a CORS preflight, and the app’s CORS policy only approves preflights from its own first-party dev origins. Missing the header is a403. Three reads with side effects carry the same requirement:GET /api/setup/check,GET /api/providersandGET /api/providers/active. - Origin allowlist. A mutating request whose
Originnames anything other than this server, loopback, or the live tunnel is refused, so a CORS misconfiguration alone isn’t enough to get through. - Host allowlist. DNS rebinding needs a hostname that resolves to
loopback, so a
Hostthat is neither an IP literal, loopback, nor the live tunnel is refused.
Origin: null and a missing Origin are both accepted: the packaged
Electron shell serves the SPA from file:// (an opaque origin), and
non-browser callers send no Origin at all. Neither can be produced by a
cross-site page that also carries X-Gaia-UI, so layer 1 already stops
those.
GAIA_UI_ALLOWED_ORIGINS and GAIA_UI_ALLOWED_HOSTS (comma-separated) opt a
reverse-proxied or named-host deployment in; neither is populated by
default.
Flagship only
The Agent UI frontend sendsX-Gaia-Client: agent-ui and runs the GAIA agent
only. With that header, POST /api/sessions and POST /api/chat/send reject
any other agent_type (422), an omitted one becomes gaia, and sending
into a chat stored under another agent is a 409. Requests without the
header — the eval harness, the MCP bridge — may still pick any registered
agent.
Database Schema
The Agent UI uses SQLite with four tables:indexing_status, file_mtime, agent_steps, and inference_stats
columns (plus the settings table) are added via migrations for databases
created before these existed. New databases include them in the initial schema.Testing
Unit Testing with In-Memory Database
Database Testing
Integration with the Agent
The Agent UI server delegates to the GAIA Agent for LLM communication and tool execution:npm Package
GAIA Agent UI is also available as an npm package for quick installation:gaia-ui CLI command:
gaia-ui automatically installs the Python backend (uv, Python 3.12, amd-gaia) if not already present. On subsequent runs, it auto-updates if the version doesn’t match.
Package Contents
The npm package includes:Release Management
The package version is sourced fromsrc/gaia/version.py (single source of truth for all of GAIA):
v* trigger the automated npm publish workflow.
Related
- User Guide — Desktop app usage, features, and troubleshooting
- Agent SDK — Core Agent SDK with memory and RAG
- RAG SDK — Document indexing and retrieval
- Desktop App Installation — Download and install instructions