Newer
Older
navi-1 / docs / architecture.md

Architecture

High-level component map and data flow for the Navi backend.

Component diagram

┌─────────────────────────────────────────────────────────┐
│  Client (browser)                                       │
│  WebSocket /ws/sessions/{id}    REST /sessions/*        │
└──────────────┬──────────────────────────┬───────────────┘
               │ WS frames               │ HTTP
               ▼                         ▼
┌─────────────────────────────────────────────────────────┐
│  FastAPI (navi/main.py)                                 │
│  ┌──────────────────┐  ┌───────────────────────────┐   │
│  │  websocket.py    │  │  routes/sessions.py        │   │
│  │  websocket.py    │  │  routes/messages.py        │   │
│  │  _AgentRun       │  │  routes/agents.py          │   │
│  │  stop endpoint   │  │  routes/health.py          │   │
│  └────────┬─────────┘  └────────────┬──────────────┘   │
└───────────┼─────────────────────────┼───────────────────┘
            │                         │
            ▼                         ▼
┌──────────────────────────────────────────────────────────┐
│  Agent (navi/core/agent.py)                              │
│  run_stream() → AsyncGenerator[AgentEvent]               │
│  run()        → str  (non-streaming)                     │
│  run_ephemeral() → str  (subagent, no DB)                │
│                                                          │
│  ┌──────────────┐  ┌───────────────┐  ┌──────────────┐  │
│  │ Planning     │  │ Tool-calling  │  │  Workers     │  │
│  │ _run_planning│  │ loop          │  │ (compression)│  │
│  └──────────────┘  └───────────────┘  └──────────────┘  │
└───┬──────────────┬────────────────┬─────────────────────┘
    │              │                │
    ▼              ▼                ▼
┌────────┐  ┌──────────────┐  ┌────────────────────────┐
│ LLM    │  │  ToolRegistry│  │  SessionStore          │
│Backend │  │  (built-ins  │  │  (SQLite / in-memory)  │
│(Ollama)│  │  + user tools│  │                        │
└────────┘  └──────────────┘  └────────────────────────┘
                                        │
                              ┌─────────┴──────────┐
                              │   MemoryStore       │
                              │   (SQLite facts)    │
                              └────────────────────┘

Request lifecycle (streaming)

  1. Client sends {type: "message", content: "..."} over WebSocket.
  2. websocket_session() creates _AgentRun, subscribes a queue, launches _run_agent() as a task.
  3. _run_agent() calls agent.run_stream(session_id, content).
  4. run_stream(): a. Loads session + profile from store. b. Pre-turn: checks if context needs compression; compresses if threshold exceeded. c. Planning phase (if profile.planning_enabled): calls LLM once (non-streaming, no tools) to produce a step plan; injects plan as assistant message. d. Tool-calling loop (up to max_iterations):
    • Calls llm.stream_complete() → yields ThinkingDelta, TextDelta, tool call requests.
    • If tool calls: executes each tool, yields ToolStarted → sub-agent events → ToolEvent.
    • If finish_reason == stop: yields StreamEnd, runs post-turn workers. e. Saves session to DB.
  5. Events are broadcast from _AgentRun to all subscriber queues.
  6. _stream_to_client() drains the queue → sends JSON to WebSocket.

Context vars (thread-safe, async-safe)

Defined in navi/tools/base.py. Set by Agent before each tool call; tools read them.

ContextVar Type Purpose
current_session_id `str \ None` Session ID for tools needing per-session state (SSH pool, scratchpad)
current_event_sink `Queue \ None` Queue where subagent events are written; parent drains it in real time
current_stop_event `Event \ None` Set by POST /sessions/{id}/stop; agent checks before each LLM call

Registry wiring (navi/core/registry.py)

build_default_registries() is the composition root. It:

  1. Creates ToolRegistry, registers all built-in tools.
  2. Loads user tools from tools/ directory.
  3. Creates ProfileRegistry, registers all profiles from navi/profiles/.
  4. Creates BackendRegistry, registers LLM backend. If OLLAMA_BACKENDS_FILE is set, uses FallbackOllamaBackend (multi-server with blacklisting); otherwise uses the single OllamaBackend.
  5. Creates SpawnAgentTool and SwitchProfileTool (need references to other registries).
  6. Patches spawn_tool._backend_registry after backends are built (avoids circular dep).

Called once at startup from navi/api/deps.py.

Two-buffer session design

Each Session has two message lists:

  • messages — full display history, never modified by compression. Used for UI history.
  • context — what the LLM sees. May be replaced with a summary by the compressor.

See sessions.md for details.