# One Python Agent Core, Four Ways to Run It: Nova on Server, Web, TUI, and Desktop

> Source: <https://dev.to/_340a11d0e3d75cd9d691d/one-python-agent-core-four-ways-to-run-it-nova-on-server-web-tui-and-desktop-m9f>
> Published: 2026-09-12 13:29:11+00:00

Nova is an open-source personal AI agent runtime for developers. One Python core is available through terminal, web, desktop, and API, so you get a single local workspace for model providers, tools, sessions, memory, MCP, and sub-agents.

The practical problem it addresses is familiar: you start an agent in the terminal to fix a bug, then you want the same setup for a longer task you check from a browser, then you want an API you can script against, then something clickable on the desktop. Without a shared runtime, that becomes four tools, four configs, and four ways for behavior to drift.

Nova's answer, in its README's words, is "Your open source AI agent on desktop, terminal, web, and API." The same agent core in `nova/` drives the TUI, server, frontend, and desktop. What changes is the surface. What stays the same is the agent loop, the tool registry, and the SQLite store.

All four surfaces use the same pieces:

`~/.nova/nova.db`.
The runtime home lives at `~/.nova/`, with `config.json`, `nova.db`, `logs/nova.log`, `skills/`, `workspace/`, and `agents/` alongside it. You can point it elsewhere with `NOVA_HOME` if you keep dotfiles or checkouts isolated.

That shared store is what makes switching surfaces uneventful. A session you start in the terminal is stored in the same SQLite file the web UI and desktop read from. Pick a model with `/models` in the TUI or with the model selector in the web UI. Add persona files like `IDENTITY.md`, `SOUL.md`, `USER.md`, or `MEMORY.md` and they land in the system prompt no matter which surface you open.

Nova exposes the core in four ways:

```
nova serve                      # HTTP server on http://127.0.0.1:8765
nova web                        # built web UI in the browser
nova tui                        # OpenTUI terminal client from any directory
./nova-tui                      # equivalent source-checkout launcher
nova desktop                    # desktop window
```

`nova serve` runs a FastAPI backend with an SSE stream at `POST /api/chat/stream`. `nova web` serves the built frontend from the same backend address and opens your browser. `nova tui` is a Bun plus React plus OpenTUI client that streams text, reasoning, tool calls, and inline diffs for `edit` and `write`. `nova desktop` hosts the built frontend in a PyWebView window with the backend on a background thread. Use `nova desktop --dev` when working against the Vite dev server.

*Nova's web interface starts with a focused chat composer, a Workspace selector, and a model picker.*

For frontend work with live reload, run the two halves separately:

```
nova serve
cd frontend && npm install && npm run dev
```

Vite proxies `/api/*` to the backend, overridable with `NOVA_FRONTEND_PROXY_TARGET` or `VITE_NOVA_API_BASE_URL`. The [quickstart](https://github.com/bigrivi/nova/blob/main/docs/getting-started/quickstart.md) has the full mapping.

Pick the surface that fits the moment and keep the same agent underneath. Short fix in the TUI, long-running task in the web UI, scripted call over the API, casual use on desktop.

It helps to be precise about what "same agent" means here, because the four surfaces do not look or feel the same, and that is intentional.

What stays shared is the state and the loop. Sessions, messages, agents, and memories live in the same SQLite file, under the same runtime home with its config, logs, skills, workspace, and agents folders. The agent loop, the tool registry including whatever MCP servers you connected, the per-session workspace semantics, and the persona files injected into the system prompt all behave the same no matter where you open Nova. That is why stopping a task in one place and continuing it in another needs no export step. The history is already there.

What stays different is everything about interaction. The TUI is keyboard-driven with slash commands, Escape to interrupt, inline diffs for file changes, and tree-sitter highlighting. The web UI leans on a thread list, composer, workspace folder picker, memory manager, approval dialog, and language switcher. The desktop hosts that same built frontend in a PyWebView window with the backend on a background thread. The API has no UI at all and instead streams text, reasoning blocks, tool calls, and tool results over SSE for you to render however you like.

The tradeoff is straightforward. You get continuity of state without uniformity of interface. Each surface keeps the controls that make sense for its setting, so there is still a small adjustment when you move. The benefit is that the adjustment is only about controls, not about reconfiguring providers, tools, or memory from scratch.

Model access lives in `~/.nova/config.json`. Only `providers` and optionally `mcp_servers` sit at the top level. Aliases under `providers` are yours to name.

Nova supports four provider `type` values: `ollama`, `openai-compatible`, `openai-response`, and `anthropic`. Ollama runs locally with no API key, `openai-response` targets the Responses API, and Anthropic supports extended thinking.

At the current checkout, `openai-response` is configured through the config file or API rather than the frontend provider dropdown.

If you already run Ollama locally, this minimal config from the documented quickstart is enough to start:

```
{
  "providers": {
    "ollama": {
      "type": "ollama",
      "options": { "base_url": "http://localhost:11434" },
      "models": { "qwen2.5:7b": { "name": "qwen2.5:7b", "tools": true } }
    }
  }
}
```

Swap in whatever model you have pulled. The key part is `"tools": true` so the agent can actually call tools.

The built-in set is fixed and documented: `read`, `write`, `edit`, `shell`, `code_run`, `glob`, `grep`, `web_search`, `web_fetch`, `browser_use`, `read_image`, `todo_write`, `ask_user`, memory tools (` save_memory`, `search_memory`, `list_memories`, `delete_memory`), `delegate_to_agent`, and skill tools (` list_skills`, `load_skill`, `install_skill`).

Any MCP server you connect over stdio or SSE/HTTP shows up as extra tools too. `code_run` executes inline Python with dependencies auto-installed to `~/.nova/site-packages/`. Web fetch returns Markdown with a 5MB cap, alongside web search. `browser_use` registers when Playwright imports. Image and document attachments ride on `POST /api/chat`, with `read_image` returning base64 plus extracted text.

Set a per-session workspace folder and `shell`, `code_run`, `glob`, and `grep` respect it. Shell commands pass a three-tier approval gate of blocked, needs approval, and auto-run, with dangerous ones asking over SSE and an optional allowlist. For longer sessions, two-layer compaction trims old tool output to disk and summarizes older turns.

The web interface keeps multi-step work visible rather than collapsing it into a single loading state. In the example below, Nova searches for recent open-source agent developments, opens first-party sources, cross-checks claims, and reports progress between rounds. It also catches a misdated OpenHands item before producing the final briefing.

*Nova reports progress between research rounds, tracks tool calls, and surfaces corrections made during source verification.*

The shared runtime matters most once you see how different tasks pull on it.

**Terminal code work with reviewable diffs.** Set a per-session workspace folder so file search and shell execution start from the checkout you mean, then work through reading, searching, and editing from the TUI. Successful file changes render as inline diffs you can read before moving on, and Escape interrupts a run that heads the wrong way. The workspace keeps everyday commands scoped to the task, while an explicit working directory still wins when you pass one, so treat it as a scoping aid that reduces mistakes rather than a boundary. This pattern fits tight fix loops where you stay in one repo and want quick review cycles.

*The TUI takes an instruction, reads a file, applies an edit shown as an inline diff, with model and context status visible.*

**Multi-round web research with visible progress.** In the web UI, a research task becomes a series of search and fetch rounds with progress reported between them instead of one long silent wait. Fetched pages arrive as Markdown, the thread keeps tool-call counts and intermediate findings, and corrections surface in the open when a source does not check out. The implication is that verification work stays inspectable. You can follow which sources were opened, what was cross-checked, and where the final briefing diverged from an early lead.

**Scripted use through the HTTP and SSE endpoint.** The same loop is available over HTTP for scripts, with chat streaming text, reasoning, tool calls, and results over SSE and attachments accepted alongside chat requests. Approval prompts for sensitive commands arrive over that stream and are answered through a dedicated approval endpoint, with an allowlist to remember routine approvals. The tradeoff here is control versus convenience. A script gets the full agent behavior including tools and memory, but it also takes on rendering progress, handling approvals, and deciding when to stop and retry.

Persistent sessions are the default. Threads, messages, agents, and memories live in SQLite, so you can stop a task in one surface and pick it up in another without exporting state.

Memory covers `fact`, `preference`, `decision`, and `context` types across `user`, `project`, and `session` scopes, with search and optional AI reranking. The frontend includes a memory manager next to the thread list, composer, model selector, workspace folder picker, and approval dialog. The TUI covers `/new`, `/sessions`, `/clear`, `/models`, `/install-skill`, and `/quit`, with Escape to interrupt and inline diffs for `edit` and `write`.

Skills live as `~/.nova/skills/<name>/SKILL.md` files, scanned at startup and loaded on demand through `list_skills` and `load_skill`. `install_skill` pulls from ClawHub only when you ask.

When a task splits cleanly, `delegate_to_agent` spawns a sub-agent with the hierarchy persisted in SQLite and surfaced over the API. Sub-agents run without MCP tools or further delegation, which keeps delegated work bounded.

Nova leaves a few deliberate seams where your own setup slots in, and each one answers a different kind of change.

Provider aliases are the first. The top-level config holds only providers and optionally MCP servers, and the names under providers are yours to choose. That means pointing Nova at a new account, endpoint, or local model is a config edit rather than a code change, and you can keep several named setups side by side for different tasks.

The database layer is another extension seam. Nova's agent, session, memory, and configuration services depend on the `NovaRepository` protocol instead of directly depending on SQLite. The current provider factory ships with `aiosqlite` and in-memory implementations, but it can register another provider by name. A MySQL-backed deployment would implement the repository protocol, create it through a `DataSourceProvider`, and register that provider with the factory. That is still real adapter work—the protocol covers sessions, messages, agents, compaction, and memory—but it keeps database-specific code behind one boundary instead of spreading SQL changes through the agent runtime.

MCP servers are the second. Any server reachable over stdio or SSE and HTTP becomes extra tools at runtime, initialized in parallel with a per-server timeout. The practical effect is that new capabilities arrive as processes Nova talks to, not patches to the agent itself. If a server is slow or missing, only its tools are affected.

Local skills are the third. A skill is a folder with a `SKILL.md` file under the runtime skills directory, scanned at startup and loaded only when the task calls for it. Fetching from ClawHub happens only when you ask for it. This suits repeatable procedures you want written down once and reused, like a review checklist or a repo-specific workflow, without baking them into every prompt.

Persona files are the lightest touch. Short markdown files describing identity, background, user context, and retained notes are injected into the system prompt on every surface. They shape tone and defaults without touching tool wiring.

Sub-agents are the most structured seam, and also the most bounded. Delegation persists the parent-child relationship and exposes it over the API, but the child runs without MCP tools and cannot delegate further. That bound is worth understanding before you lean on it. It keeps delegated work predictable and easy to trace, at the cost of ruling out recursive fan-out. Use it for cleanly separable chunks, not for open-ended chains.

Longer tasks fail in familiar ways. They stall waiting on a risky command, loop on the same call, outgrow context, or forget a decision from an earlier session. Nova addresses each with a separate mechanism, and each asks something of you.

Approval tiers handle the risky-command case. Shell input falls into blocked, needs approval, or auto-run by pattern, with sensitive prompts delivered over the stream and answered through an approval endpoint. A rememberable allowlist smooths repeated runs of commands you trust. The tradeoff is interruption. Tighter patterns mean more pauses, while a generous allowlist means fewer pauses and more responsibility for what you pre-approved.

Repeated-call guardrails handle loops. The run halts after several identical calls or identical failures in a row, and warns after a run of read-only calls. This catches the agent re-reading the same files or retrying the same failing command instead of reconsidering. When you hit one, the fix is usually in the task framing rather than the limit.

Two-layer compaction handles context growth. Older tool output is snipped to per-session files on disk while older turns are summarized, with tuning available for how aggressive each layer is. Snipped output stays retrievable rather than vanishing, which matters when you need to audit what the agent actually saw three rounds back.

Persistent memory handles cross-session recall. Stored items carry a type like fact, preference, decision, or context, and a scope of user, project, or session, with search and optional reranking when you look something up. There is no per-turn prefetch, so memory does not silently steer every reply. The implication is direct. What you explicitly save and search for carries forward, and what you never write down does not. For longer projects that means building a small habit of saving decisions and preferences as they settle.

If building an agent from scratch sounds like too much plumbing, and taking a ready-made product as-is feels too rigid, Nova sits in the middle. The shared runtime, provider wiring, tool registry, SQLite persistence, four interfaces, MCP loading, skills, and sub-agent handling are already wired together, so you start from working code rather than an empty repo. As Apache-2.0 open source, you can inspect each part in `nova/`, modify or replace what you need, and build your own setup on top while keeping the rest.

Nova needs Python 3.12 or newer:

```
git clone https://github.com/bigrivi/nova.git && cd nova
pip install -e .                # Python 3.12+
playwright install chromium     # only if you want the browser tools
```

If `nova` is not found afterward, add your environment's `bin/` directory to `PATH`. The full walkthrough is in the [installation guide](https://github.com/bigrivi/nova/blob/main/docs/getting-started/installation.md).

Then add a provider to `~/.nova/config.json` as shown above, and start where you want to work:

```
nova serve                      # HTTP server on http://127.0.0.1:8765
nova web                        # built web UI in the browser
nova tui                        # OpenTUI terminal client from any directory
nova desktop                    # desktop window
```

Pick a model via `/models` in the TUI or the model selector in the web UI, and you are running the same core everywhere.

One practical note: `shell` and `code_run` execute locally as your user and are not sandboxed, so point Nova at repos and machines you can afford to change.

The shared runtime lives in `nova/`, with the terminal client in `tui/`, the web UI in `frontend/`, tests in `tests/`, and guides in `docs/`. Inside `nova/`, each package owns one concern:

`agent/` runs the agent loop that plans the next step, calls tools, and streams results.`app/` wires the runtime pieces together at startup.`config/` handles runtime configuration under `~/.nova/`.` db/` implements persistence behind the repository protocol, backed by SQLite.`desktop/` hosts the desktop window around the built frontend.`llm/` holds the model provider implementations.`mcp/` loads connected MCP servers as extra tools.`memory/` stores and searches memory records across scopes.`prompt/` assembles the system prompt, including persona files.`server/` serves the HTTP backend and the chat stream.`session/` manages session and thread state.`skills/` scans and loads local skills on demand.`tools/` registers the built-in tool set.`utils/` holds shared helpers.`__main__.py` is the `nova` console entry point for `serve`, `web`, `tui`, and `desktop`.` settings.py` parses `~/.nova/config.json`.
As a rough map: models go in `llm/`, persistence in `db/`, streaming and API behavior in `server/`, prompt assembly in `prompt/`, tools in `tools/`, the terminal client in `tui/`, and the web frontend in `frontend/`.

Setup follows the contributing guide. Install the Python package with dev tooling:

```
pip install -e ".[dev]"
```

Frontend and TUI dependencies are separate:

```
cd frontend && npm ci
cd tui && bun install
```

Run the suite from the repo root. No `PYTHONPATH` setup is needed:

```
pytest
```

Run a subset while iterating:

```
pytest tests/test_server.py -q
```

The contributing guide currently reports 392 passing and 6 skipped. The skipped tests are the live Ollama end-to-end suite, which is opt-in:

```
RUN_LIVE_OLLAMA_SERVER_E2E=1 pytest tests/e2e -q
```

Tests use an internal `faker` provider, so CI runs without real API keys. Browser tooling is optional: `playwright install chromium` adds it, and no test requires it.

For bigger changes, open an issue first to discuss the approach. Report vulnerabilities through the repo Security tab, not a public issue.

Nova fits developers who want a local-first agent they can read end to end, who want to start with Ollama and no API key, who like sessions kept in one inspectable SQLite file, and who want to move between terminal, browser, desktop, and HTTP without switching agent implementations.

Nova is Apache-2.0 licensed, and the code, docs, and issue tracker all live at [bigrivi/nova](https://github.com/bigrivi/nova).

If a single local runtime for providers, tools, sessions, memory, MCP, and sub-agents sounds useful, start with the [README](https://github.com/bigrivi/nova/blob/main/README.md), clone the repo, run it locally with Ollama or your own key, and star it if it proves useful.
