DEV Community

bigrivi
bigrivi

Posted on

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

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.

One shared runtime

All four surfaces use the same pieces:

  • The same agent loop decides what to do next, calls tools, and streams back text, reasoning blocks, tool calls, and tool results.
  • The same tool registry provides 21 built-in tools, plus whatever your connected MCP servers add at runtime.
  • The same SQLite store keeps sessions, messages, agents, and memories under ~/.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.

Four interfaces, same agent

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
Enter fullscreen mode Exit fullscreen mode

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 empty chat screen with a message composer, Workspace selector, and model selector

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
Enter fullscreen mode Exit fullscreen mode

Vite proxies /api/* to the backend, overridable with NOVA_FRONTEND_PROXY_TARGET or VITE_NOVA_API_BASE_URL. The quickstart 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.

What stays shared when you switch surfaces

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.

Bring your own model, including local models

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 } }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

Tools, MCP, and a workspace you control

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 chat showing multi-round research, tool-call counts, progress updates, and source verification

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

Three workflow patterns that show the architecture

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.

Nova terminal TUI editing a file with an inline diff

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.

Sessions, memory, skills, and sub-agents

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.

Where to extend it without forking it

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.

What keeps longer runs manageable

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.

A starting point you can shape

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.

Local quickstart

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
Enter fullscreen mode Exit fullscreen mode

If nova is not found afterward, add your environment's bin/ directory to PATH. The full walkthrough is in the installation guide.

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
Enter fullscreen mode Exit fullscreen mode

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.

How the source tree is organized

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

If you want to contribute

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

pip install -e ".[dev]"
Enter fullscreen mode Exit fullscreen mode

Frontend and TUI dependencies are separate:

cd frontend && npm ci
Enter fullscreen mode Exit fullscreen mode
cd tui && bun install
Enter fullscreen mode Exit fullscreen mode

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

pytest
Enter fullscreen mode Exit fullscreen mode

Run a subset while iterating:

pytest tests/test_server.py -q
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Who this is for

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.

Clone it and run it

Nova is Apache-2.0 licensed, and the code, docs, and issue tracker all live at bigrivi/nova.

If a single local runtime for providers, tools, sessions, memory, MCP, and sub-agents sounds useful, start with the README, clone the repo, run it locally with Ollama or your own key, and star it if it proves useful.

Top comments (0)