No toolkit.
No cgo. Just Go and X11.
Most desktop toys drag in a GUI framework measured in hundreds of megabytes. This one doesn't. It speaks the X11 wire protocol directly, and every pixel of Onidia is drawn by code at runtime.
UI thread never blocks
Ollama / OpenRouter
/tmp/desktop-pet--0.say
matching expression
Sixty-four bits, end to end.
Onidia and onidia-chat are pure Go, so a build is a single static arm64
binary — nothing to cross-compile on the Pi, nothing to install beside it. The
release artifact is exactly that: one arm64 .deb that carries both
halves. A Raspberry Pi 5 is what they were written against,
designed around and tested on.
The more interesting half is what that Pi can be as the machine. On a
AI HAT+ 2 — the Hailo-10H board, 40 TOPS of INT4 inference and 8 GB of RAM
living on the card itself — Hailo's hailo-ollama speaks the very same
ollama /api/chat dialect onidia-chat already speaks to the network.
Point the provider at localhost and the model stops being someone else's computer.
# Hailo's ollama-compatible server. Listens on :8000. hailo-ollama # 8000 is already the default - this is the whole switch: ./onidia-chat -provider ollama -model qwen2:1.5b # Qwen2 1.5B, Llama 3.2 1B, Qwen2.5-Coder, DeepSeek-R1 curl localhost:8000/api/tags
tools field, so onidia-chat retires
tool calling after the first reply and carries on in plain text.How a frame is made
A tiny state machine — idle, walk, hop,
drag, throw — runs a few physics steps a frame and produces a pose: bob, leg swing, foot
lift, blink, arm flags, expression. The pose is rendered procedurally onto a supersampled canvas and
downsampled for smooth edges, the speech bubble is composed above it, and the whole image is uploaded
with a single PutImage. The window itself just moves to follow her.
Because the character is code and not a
sprite sheet, adding a personality means writing a Go package: its own palette, drawing functions and
phrase list, registered at startup and picked with -character. Two characters can never
collide, because they share nothing but the pose contract.
XShape is deliberately not used: under XWayland, shrinking a shaped window's region can stop updates from being presented, so the plain transparent window composites correctly everywhere instead.
Worth knowing
flock per display, so a second launch exits and names the pid holding it.Questions, ideas,
or a new character?
Found a bug, want a feature, or fancy writing your own personality? The repositories are the fastest way in — and issues are genuinely read. If you'd rather just say hello, the form works too.