desktop-assistant
A local text assistant daemon. Questions arrive on a Unix socket, the model already loaded in a local llama-server answers, calling read-only tools when a question needs them, and the reply streams back token by token. Every conversation is kept in a local SQLite history with full-text search. The model and the history stay on the machine; only tools you enable (weather, web search) reach the network.
The front end is the assistant canvas of the quickshell desktop
(desktop/assistant/ in the quickshell repo, on SUPER+Home). Anything
that speaks JSON lines on a Unix socket can drive it.
The voice version (push-to-talk, whisper, Kokoro) is the tag voice-v1.
Requirements
- a running
llama-serverwith an OpenAI-compatible API, router mode - Python 3 (stdlib only; SQLite with FTS5)
- poppler (
pdftotext,pdftoppm) for PDF attachments curlandjqfor the weather and search example tools,python3-dateutilfor the calendar one
Usage
python3 assistant.py # run
python3 assistant.py --test # self-check
./assistant.sh start # detached
./assistant.sh stop
./assistant.sh restart
./assistant.sh status
The daemon uses whichever model the router has loaded, so it never swaps a model out under another client.
Only one daemon serves the socket: a second one refuses to start while the first answers.
Protocol
JSON lines both ways on $XDG_RUNTIME_DIR/desktop-assistant.sock (mode
0600). Every connected client sees every event.
In: ask {text, files}, write {files, prompt, dir, name}, stop, new, list, open {id}, search {q},
delete {id}.
Out: hello on connect (state, model, context use, the current
conversation and any reply in flight), then user, think, token,
tool, done, state, ctx, error, and the answers conversations,
conversation and results. The full table is in
docs/superpowers/specs/2026-10-16-text-daemon-design.md.
Ids are integers; a malformed command or argument answers error and the
connection stays open.
write is a one-off request whose reply goes to a file instead of the
conversation (the canvas's To .md): no history, no thinking, no tools; it
streams progress {chars} and ends with written {path}, the first free
name of name in dir. A stop leaves no file.
files is a list of absolute paths, at most 8. Text and code files and
PDFs are read and inlined, sharing half the free context (a PDF page with
no text goes as an image, at most 4, rendered at 150 dpi, at most 1600 px);
images go as image parts to a model that reports seeing them. History
keeps the text and, for images, the path and hash: a file that changed or
moved is reported as no longer available when the conversation is
reopened, and a model that cannot see images gets a note instead.
Try it by hand:
socat - UNIX-CONNECT:$XDG_RUNTIME_DIR/desktop-assistant.sock
{"type":"ask","text":"What time is it?"}
History
~/.local/share/desktop-assistant/history.db ($XDG_DATA_HOME
honoured), directory 0700, file 0600. The model is sent the last 10 turns
of the open conversation. Tool results are stored as a placeholder, not
their output. Reasoning is shown live and not stored.
Configuration and tools
Everything is optional. Settings and tools live in
~/.config/desktop-assistant/ (or $XDG_CONFIG_HOME/desktop-assistant/):
mkdir -p ~/.config/desktop-assistant
cp examples/config.toml ~/.config/desktop-assistant/
cp -r examples/tools ~/.config/desktop-assistant/
config.toml: every key is optional, the example lists the defaults.[env]is added to every tool's environment; put instance URLs and key file paths there.tools/<name>.toml: one tool each, enabled by being there. A tool is a command run without a shell, its stdout goes back to the model:
description = "What the tool does, for the model"
command = ["script.sh", "{param}"] # looked up in tools/ first, then PATH
timeout = 10
[params.param]
type = "string"
description = "What the model should pass"
The example tools only read:
now: date and timecalendar:agenda.pyover a vdirsyncer folderweather: OpenWeatherMap, key file in the conky-theme-udt formatsearch: a SearXNG instance with the JSON format enabledcalc:qalc(arithmetic, units, currencies, date math)mail: unread threads throughmailctlsearches, no tagging or syncplaying: the current track fromplayerctl, ormpcfinance: balances, budgets and spending from Firefly III'sfireflylist commandsbluetooth: power, connected devices (battery when reported) and paired ones, throughbluetoothctl show/devices/infovms: libvirt VMs and their state, or one VM's details, throughvirshread commandsupdates: Slackware -current ChangeLog entries newer than the installed packages: counts, security fixes, kernel bump, one package's notes, or a full markdown recap opened inMD_EDITOR(defaultxdg-open);CHANGELOGandPKGDIRin[env]override the mirror path and package database
License
Copyright (C) 2026 Danilo M. danix@danix.xyz
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. See LICENSE.
Development Approach
This project is developed using AI-assisted tools. Code is generated with the help of AI based on human-provided specifications, design decisions, and iterative feedback.
All contributions are reviewed, tested, and curated by the maintainer before being included in the codebase. AI is used as a productivity and exploration tool, while human oversight remains central to all decisions.
The goal is to combine the flexibility of AI-assisted development with standard open-source practices such as transparency, review, and accountability.
