Architecture#

Nixi is a Go application with a clean separation between the agent core, LLM client, tool system, and UI frontends.

Data Flow#

User Input --> TUI/Web UI --> Agent --> LLM Client --> Ollama/OpenAI API
                                |
                                +--> Tool Registry --> Executor --> System

Packages#

  • cmd/nixi/ – entry point, CLI flags, launches TUI or web daemon
  • internal/agent/ – agent loop with tool dispatch, system prompt
  • internal/llm/ – dual-protocol streaming client (Ollama + OpenAI)
  • internal/memory/ – SQLite persistent memory with FTS5 full-text search
  • internal/tools/ – tool interface, registry, executor abstraction (13 tools)
  • internal/tui/ – Bubble Tea terminal UI
  • internal/web/ – web UI daemon (HTTP + WebSocket server, nixi serve)
  • internal/config/ – configuration loading

Agent Loop#

The agent runs up to 10 iterations per user message:

  1. Send conversation history + tool schemas to the LLM
  2. Stream the response tokens to the UI
  3. If the LLM requests tool calls, execute them
  4. Feed tool results back into the conversation
  5. Repeat until the LLM responds with text (no tool calls)

Streaming#

All communication uses Go channels:

  • llm.Client.StreamChat() returns <-chan StreamEvent
  • The agent collects events, executes tools, and re-emits agent.StreamEvent
  • The TUI reads from the agent channel via Bubble Tea commands
  • The web UI reads from the agent channel and writes JSON to a WebSocket
  • Context cancellation propagates immediately through all layers

Executor Abstraction#

Tools never call os/exec directly. Instead, they use the Executor interface:

  • LocalExecutor – runs commands via os/exec on the controller node
  • SSHExecutor – runs commands on remote nodes via ssh (key-based auth, BatchMode=yes)
  • MonitoredExecutor – wraps another executor and refuses mutating commands (for read-only nodes)
  • ResolveExecutor(node, nodes) – routes to the correct executor based on the node parameter and configured nodes

This means the same tool code works for both local and remote execution without modification.

The SSHExecutor automatically prepends sudo for commands that need elevated privileges on remote nodes (e.g. nixos-rebuild, mkdir /srv/...), while read-only commands run without sudo.

Exception: the memory tool operates on a local SQLite database directly, not via the executor.

Memory#

Nixi has persistent memory backed by SQLite with FTS5 full-text search. The database lives at ~/.local/share/nixi/memory.db.

  • The LLM saves facts via the memory tool (system info, services, user preferences)
  • On each user message, relevant memories are automatically recalled via full-text search and injected into the system prompt
  • Memories survive /clear and app restarts
  • The /memories slash command lists all saved memories without going through the LLM

For details on the confirmation system, file system boundaries, and sudo access, see Security.

Multi-Node#

Nixi supports managing multiple NixOS machines from a single controller:

  • Nodes are configured in config.toml with [[nodes]] sections or via the NixOS module
  • Each node has a name, host, user, and mode (managed or monitored)
  • The /adopt TUI command handles SSH key setup securely (passwords never touch the LLM)
  • The nodes tool lets the LLM list and remove nodes
  • All tools accept a node parameter – the registry injects known node names into the schema so the LLM can target specific nodes
  • Node names are fuzzy-matched to catch typos and hallucinations

See Multi-Node for the full guide.

Networking#

  • All containers join a shared nixi network for container-to-container DNS (works with both Podman and Docker)
  • Same-node containers can reference each other by name
  • Cross-node references use the node’s IP address (container DNS is local-only)

Web UI#

The web UI (nixi serve) provides browser-based access with full feature parity to the TUI.

  • Daemon: Runs as a persistent HTTP server on port 6494 (configurable)
  • WebSocket: Each browser connection gets its own agent instance and WebSocket for real-time streaming
  • Sessions: Independent per-connection (like separate terminal sessions)
  • Frontend: Vanilla JS single-page app embedded in the binary via go:embed
  • NixOS: The NixOS module can run the web daemon as a systemd service (services.nixi.web.enable = true)

The WebSocket protocol mirrors the agent’s StreamEvent channel:

  • Server sends token, tool_start, tool_result, done, error, and connected events
  • Client sends message, cancel, and command events