High-level component map and data flow for the Navi backend.
┌─────────────────────────────────────────────────────────┐
│ 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) │
└────────────────────┘
{type: "message", content: "..."} over WebSocket.websocket_session() creates _AgentRun, subscribes a queue, launches _run_agent() as a task._run_agent() calls agent.run_stream(session_id, content).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):
llm.stream_complete() → yields ThinkingDelta, TextDelta, tool call requests.ToolStarted → sub-agent events → ToolEvent.finish_reason == stop: yields StreamEnd, runs post-turn workers. e. Saves session to DB._AgentRun to all subscriber queues._stream_to_client() drains the queue → sends JSON to WebSocket.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 |
navi/core/registry.py)build_default_registries() is the composition root. It:
ToolRegistry, registers all built-in tools.tools/ directory.ProfileRegistry, registers all profiles from navi/profiles/.BackendRegistry, registers LLM backend. If OLLAMA_BACKENDS_FILE is set, uses FallbackOllamaBackend (multi-server with blacklisting); otherwise uses the single OllamaBackend.SpawnAgentTool and SwitchProfileTool (need references to other registries).spawn_tool._backend_registry after backends are built (avoids circular dep).Called once at startup from navi/api/deps.py.
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.