Architecture
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 daemoninternal/agent/– agent loop with tool dispatch, system promptinternal/llm/– dual-protocol streaming client (Ollama + OpenAI)internal/memory/– SQLite persistent memory with FTS5 full-text searchinternal/tools/– tool interface, registry, executor abstraction (13 tools)internal/tui/– Bubble Tea terminal UIinternal/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:
- Send conversation history + tool schemas to the LLM
- Stream the response tokens to the UI
- If the LLM requests tool calls, execute them
- Feed tool results back into the conversation
- 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 viaos/execon the controller nodeSSHExecutor– runs commands on remote nodes viassh(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 thenodeparameter 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
memorytool (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
/clearand app restarts - The
/memoriesslash 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.tomlwith[[nodes]]sections or via the NixOS module - Each node has a name, host, user, and mode (
managedormonitored) - The
/adoptTUI command handles SSH key setup securely (passwords never touch the LLM) - The
nodestool lets the LLM list and remove nodes - All tools accept a
nodeparameter – 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
nixinetwork 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, andconnectedevents - Client sends
message,cancel, andcommandevents