# 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        │   │
│  │  orchestrator    │  │  routes/messages.py        │   │
│  │  stop endpoint   │  │  routes/agents.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  │  │  (PostgreSQL)  │
│(Ollama)│  │  + user tools│  │                        │
└────────┘  └──────────────┘  └────────────────────────┘
                                        │
                              ┌─────────┴──────────┐
                              │   MemoryStore       │
                              │   (PostgreSQL + pgvector)    │
                              └────────────────────┘
```

## Request lifecycle (streaming)

1. Client sends `{type: "message", content: "..."}` over WebSocket.
2. `AgentSessionOrchestrator` creates a `SessionRun`, subscribes a queue, and launches the agent task.
3. The agent task 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 via `ToolContext`, 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 `SessionRun` to all subscriber queues.
6. The WebSocket handler drains its queue → sends JSON to the client.

## Tool context

Defined in `navi/tools/_internal/base.py`.

**Legacy path**: `ContextVar`s (`current_session_id`, `current_event_sink`, `current_stop_event`, `current_model`, `current_user_id`, `current_user_role`, `current_user_info`) are still present for backward compatibility.

**Current path**: `Agent` builds a `ToolContext` dataclass explicitly and passes it into every tool's `execute()` call. This removes hidden dependencies and makes tool execution deterministic and testable:

| Field | Type | Purpose |
|---|---|---|
| `session_id` | `str \| None` | Session ID for per-session state (SSH pool, scratchpad, todo) |
| `event_sink` | `Queue \| None` | Queue where subagent events are written; parent drains it in real time |
| `stop_event` | `Event \| None` | Cooperative stop signal checked before each LLM call |
| `model` | `list[str] \| str \| None` | Current profile model — tools that call the LLM read this |
| `user_id` | `str \| None` | Authenticated user ID |
| `user_role` | `str` | Role (`user` or `admin`) — admins bypass sandbox restrictions |
| `user_info` | `dict \| None` | Full user profile for context injection |

## 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`](sessions.md) for details.
