AI Assistance in the Qubes OS dom0 Terminal

This guide and software was made using AI: Claude Opus 5.5 / DeepSeek Flash

Overview

This guide shows how to add AI assistance to the dom0 terminal in Qubes OS. The AI runs in a dedicated AppVM, and dom0 talks to it with qvm-run. dom0 itself never gets network access.

The setup uses qai, which has two parts:

  • A PTY wrapper that buffers the terminal’s input and output.
  • A command-line tool, qai, that sends your question to the AI.

When you run qai, it automatically includes your last command and its output. The AI sees exactly what went wrong without you copying and pasting anything.

You don’t have to use this code. It is small and simple, so you can also hand it to your own AI and have it write a version that fits your setup.

Example

Here the user tries to start a qube that is already running, then asks why it failed:

user@dom0:~$ qvm-start sys-net
Error: domain sys-net is already running
user@dom0:~$ qai why didnt the command work?
qai: sending 174 bytes to ai-llm ...
── answer from ai-llm ──────────────────────────────────────
The command didn’t β€œwork” because sys-net was already running. qvm-start only starts a qube that is currently stopped; if it’s already running, Qubes returns an error and exit status 1:

Error: domain sys-net is already running

This is not a failure of Qubes or of sys-netβ€”it just means there was nothing to start.

To check its state:
qvm-ls sys-net

If you need to restart it, shut it down first and start it again:
qvm-shutdown sys-net && qvm-start sys-net

Or, depending on your Qubes version/tools:
qvm-restart sys-net

────────────────────────────────────────────────────────
user@dom0:~$

The user only typed the question. qai added the failed command and the error message on its own.

Before you start: security
dom0 is the most trusted part of Qubes, so think through what this setup changes:

  • Your terminal output leaves dom0. Every qai call sends the last command and its output to the AppVM, and from there to the AI provider (DeepSeek in this guide). Don’t use it on output that contains secrets.
  • The AppVM’s answer is printed in dom0. Treat the AI’s suggestions as advice, not commands. Read every command before you run it in dom0.
  • Code you install in dom0 must be trusted. Read the qai scripts before installing them, or write your own version.

If you want nothing to leave your machine, you can run a local model in the AppVM instead. The dom0 side stays the same.

Step 1: Create the template

Start from a minimal Debian template so the AI qube contains as little as possible. In dom0, clone it:

qvm-clone debian-13-minimal debian-13-llm

Step 2: Configure the template

The minimal template lacks networking and passwordless sudo. Open a root terminal in the template from dom0:

qvm-run --user root debian-13-llm xterm

In that terminal, install the two packages:

apt install qubes-core-agent-passwordless-root 
apt install qubes-core-agent-networking

Then shut the template down, so the changes are saved. Run this in dom0:

qvm-shutdown debian-13-llm

Step 3: Create the AppVM

Create an AppVM named ai-llm based on the new template. In dom0:

qvm-create --class AppVM --template debian-13-llm --label red ai-llm

–label sets the window color; pick any color you like. The AppVM gets your default NetVM, which it needs to reach the AI provider.

Step 4: Configure the AppVM

The AppVM runs Simon Willison’s llm command-line tool with a DeepSeek plugin. It is installed in a Python virtual environment in the home directory, so it survives reboots.

Open a terminal in the AppVM from dom0:

qvm-run ai-llm xterm

Install pip and venv:

sudo apt install python3-pip python3-venv

Create the virtual environment and install the DeepSeek plugin:

python3 -m venv llm
./llm/bin/pip install llm-deepseek-ya

Save your DeepSeek API key. The command prompts you to paste it:

./llm/bin/llm keys set deepseek

Test that the AppVM can reach the model:

./llm/bin/llm 'What is the capital of Germany?' -m deepseek-flash

If you get an answer, restart the AppVM before moving on.

Step 5: Verify from dom0

Check that dom0 can send a question to the AppVM and read the answer. In a dom0 terminal:

echo "What is the capital of Germany?" | qvm-run -p ai-llm '~/llm/bin/llm -m deepseek-flash -o show_reasoning false'

qvm-run -p pipes standard input into the command in the AppVM and prints its output in dom0. This is the same mechanism qai uses. If the answer appears, the AI side is ready.

Step 6: Install qai in dom0

dom0 has no network, so download the repo in a qube first and copy it over. For example, clone it in a disposable or work qube:

git clone https://github.com/renehoj/qubes-ai
tar czf qubes-ai.tar.gz qubes-ai

Then pull the archive into dom0 (replace work with the qube you used):

qvm-run -p work 'cat ~/qubes-ai.tar.gz' > qubes-ai.tar.gz
tar xzf qubes-ai.tar.gz

Read through the scripts, then run the install script from the repo. Finally, close and reopen your dom0 terminal so the PTY wrapper starts.

Run a command that fails, then type qai followed by your question. Use qai --new to start a fresh conversation.

1 Like