Web UI¶
The optional browser UI turns any lovia Agent into a local chat application. It includes streaming text and Tool activity, conversation history with edit-and-resend and regenerate, titles, follow-up suggestions, approvals, schedules, a memory editor, image and file attachments, and a read-only Workspace file panel. All browser assets are bundled, so the page works without a CDN or external font requests.
Start in one command¶
Open http://127.0.0.1:8000. On first launch the CLI asks for whatever model
configuration is missing, checks it against the endpoint, and can save it
(owner-only, git-ignored) to ~/.lovia/config.env — one setup works from any
directory — so it is never retyped. Change it later with lovia web --setup:
the wizard revisits every value (Enter keeps the current one) and asks whether
to save user-wide or to the project-local ./.lovia/config.env. To see what
configuration would win and whether the endpoint answers, run
lovia web --check.
The default Agent includes Todo, optional Skills from ./.agents/skills
(the cross-agent convention, shared with e.g. Claude Code and Codex), Memory in
./.lovia/memory, time and HTTP Tools, web search (Tavily when
TAVILY_API_KEY is set, else optional DuckDuckGo), scheduling, and a
coding-mode Workspace rooted at the current directory. If AGENTS.md exists,
its content becomes the Agent's instructions.
Local by default; token-guarded beyond that
The default 127.0.0.1 binding needs no credentials. Binding any other
host requires an API token — --token / LOVIA_WEB_TOKEN, or one is
generated and printed with a ready /?token=... link (the UI stores it
and asks for it on 401). The token then protects file edits and shell,
so treat it like a password, and prefer --readonly off-loopback. For
real multi-user exposure see Deployment.
Serve your own Agent¶
Create app.py:
from lovia import Agent
assistant = Agent(
name="assistant",
instructions="Answer clearly and use tools when they improve accuracy.",
model="<model>",
)
Then point the CLI at the object:
--app MODULE:ATTR accepts one Agent or a {name: agent} mapping. For Python
deployment and ASGI integration, see Web server.
The UI renders GitHub-flavored Markdown — tables, highlighted code, mermaid
diagrams, inline images. The default agent is told so in its system prompt; a
custom agent is not, and a model that doesn't know its surface assumes plain
text and answers image requests with "I cannot display images here". Append
SURFACE_NOTE to your instructions to declare the surface:
from lovia.web import SURFACE_NOTE
assistant = Agent(
name="assistant",
instructions="Answer clearly.\n\n" + SURFACE_NOTE,
model="<model>",
)
Images and files¶
Attachments (the composer's +, or drag-drop, or paste) are uploaded into the
Workspace's uploads/ directory and referenced from your message by path, so
the Agent can open them with its file tools whatever model you run —
--no-workspace therefore hides the button.
Images additionally go inline to models that can see them:
- Vision-capable main model. Official
api.openai.com/api.anthropic.comhosts are assumed multimodal. Declare any other endpoint (a Qwen-VL / DashScope or vLLM deployment, or a gateway that may front a text-only model) withLOVIA_VISION=1. - Text-only main model.
LOVIA_VISION_MODEL=<vendor>:<model>(e.g.openai:qwen3.7-plus) registers asee_imageTool: the main model delegates "look at this image" and gets back a text answer, so the image bytes never enter the main transcript. Thevendor:prefix picks the API dialect exactly as inLOVIA_MODEL, and the endpoint and key default to theOPENAI_*/ANTHROPIC_*pair it routes to — override withLOVIA_VISION_BASE_URL/LOVIA_VISION_API_KEYwhen the vision model lives elsewhere.
Uploads are capped at 25 MiB (LOVIA_MAX_UPLOAD_MB) and limited to a built-in
allowlist of common image, document, data, and code extensions
(LOVIA_UPLOAD_ALLOWED_EXT, comma/space-separated, or * for any).
Useful CLI options¶
Every option resolves in this order: command-line flag, environment variable,
./.lovia/config.env, ~/.lovia/config.env (or the --env-file files),
then the default. The startup summary names each value's source; --check
prints the same and probes the endpoint without serving.
| Flag | Environment | Default |
|---|---|---|
--host / --port |
LOVIA_HOST / LOVIA_PORT |
127.0.0.1 / 8000 |
--token |
LOVIA_WEB_TOKEN |
None on loopback; generated + printed otherwise |
--db |
LOVIA_DB |
./.lovia/<agent>.db |
--model |
LOVIA_MODEL |
Asked on first run |
--app MODULE:ATTR |
LOVIA_APP |
Build the default Agent |
--skills-dir |
LOVIA_SKILLS_DIR |
./.agents/skills when present |
--memory-dir / --no-memory |
LOVIA_MEMORY_DIR |
./.lovia/memory |
--workspace, --readonly / --trusted / --no-workspace |
LOVIA_WORKSPACE, LOVIA_WORKSPACE_MODE |
. in coding mode |
--instructions-file |
LOVIA_INSTRUCTIONS_FILE |
AGENTS.md when present |
--max-retries / --max-turns |
LOVIA_MAX_RETRIES / LOVIA_MAX_TURNS |
4 / 50 |
--no-followups |
LOVIA_FOLLOWUPS, LOVIA_FOLLOWUP_MODEL |
Suggestions on, using the Agent's model |
--setup / --check |
— | Rerun the wizard / probe and exit |
--env-file |
— | ./.lovia/config.env, then ~/.lovia/config.env |
lovia web --help lists them all in four groups: model (including the context
window), agent, server, and advanced — output tokens, Provider timeout and
retries, proxy handling, and log level.
Follow-up suggestions¶
After a reply lands, the UI offers a few questions you might want to ask next as chips below the answer; clicking one sends it. They come from a separate small model call, so they never enter the conversation itself, and they appear only after a Run that actually answered — not after a stop or an error. Sending anything clears them.
The model is told it may decline: a chat that reached a natural end gets no
chips rather than three invented ones. Settings → "Suggest follow-up
questions after a reply" turns them off for you alone, without a server
restart — the check runs before the request, so opting out spends nothing.
Server-wide, use --no-followups, and point the call at a cheaper model with
LOVIA_FOLLOWUP_MODEL.
For a custom Agent served through create_app(), this is opt-in — see
Web server.
Background subagents¶
lovia web ships with background subagents
on by default (--no-subagents disables): the assistant can delegate
self-contained work to a sub-assistant that keeps the basic capabilities
(Skills, Todo, tools, workspace) while shedding Scheduling, Memory, and
recursive spawning. For a custom agent, attach the Subagents plugin
yourself — create_app automatically adapts it to the serving context
(opt out with create_app(..., wire_subagents=False); apps that mount
build_api_router directly call wire_subagents(app) once instead).
Every spawn is a real session. A wired child runs as a supervised run in
its own task session, surfaced by a Tasks button in the chat's top
bar — visible only when the open chat has background tasks, pulsing while
any run and amber when an approval waits. Its popover lists this chat's
tasks (t1, t2, … with live elapsed or outcome); click one to watch the
child work token by
token (the ordinary session view: streaming, reconnect, stop button, delete
all work), including any tool approval it is waiting on — you can approve or
deny right there; unanswered approvals auto-deny after the server's
approval_timeout (lovia web sets 10 minutes) so a task can never hold a
run slot forever. Each task leaves a run record (status, token usage) under
source subagent:<session>.
Reports deliver into the parent chat. When a child finishes, its report lands in the spawning conversation — injected into the live run if one is going, otherwise a clientless run starts with the report as its input so the model reacts to it and the exchange persists. At the concurrency cap, delivery retries with backoff before giving up with a warning.
Wired children keep running after the parent's Stop button (stop them from
their task session, or let the model cancel_subagent); a server restart
does not resume in-flight tasks.
The whole lifecycle is legible in the server log: task started/ended,
report injected vs. delivered via a new run, and a run parking on an
approval. lovia web additionally tags every line emitted inside a
supervised run with its source — [user], [schedule:<id>],
[subagent:<session>] for a child task run, [subagent-report:<task>] for
the run that delivers its report — so parallel background work reads apart
from the main chat. Custom setups get the same tagging by including %(run_source)s
in their log format and attaching
lovia.web.supervisor.run_source_log_filter() to their handler.
Closing or refreshing the page¶
Runs are managed by the server, so closing or refreshing the page does not stop them. Reopen the conversation to catch up and resume live updates. Only the Stop button cancels a Run; completed turns remain in the Session. Running sessions are marked in the sidebar and can also be stopped there.
See also¶
- Web server — Python API, lifecycle, and scheduling
- HTTP API — build a different front end
- Tools: approval — how approval dialogs resolve
- Example:
26_web_serve.py