Cerberus is a service plus a desktop client. The service is the cerberus daemon. The desktop base
is Atrium’s LLM chat (llm-chat.py with llm_chat_engine.py and llm_models.py), which already
speaks the OpenAI API. Phase 1 connects the two without changing Atrium’s code. Phase 5 grows that
chat into the Cerberus desktop client.
cerberus install-service # ~/.local/bin/cerberus + systemd user unit, enabled and started
systemctl --user status cerberus
The unit starts with the user session, restarts on failure and stops gracefully. On stop, Cerberus
ends its sessions and shuts down any llama-server it spawned.
Agent binaries (claude, aider, opencode…) must be on the service’s PATH. The systemd user
manager starts with a bare PATH, so the unit sets ~/.local/bin:~/bin:/usr/local/bin:/usr/bin:/bin
and cerberus serve also adds ~/.local/bin and ~/bin when they exist. An agent installed
elsewhere needs a full path in its profile’s command, or a systemctl --user edit cerberus
override that extends Environment=PATH=….
~/.config/hypr/scripts/llm_models.py set server_url http://127.0.0.1:7340/v1
Settings › AI & models sets the same key in ~/.local/state/hypr/llm.json. Leave server_key
empty: with server.local_no_auth (the default), /v1/* from the same machine needs no token. From
another machine, set server_key to the Cerberus token and use an address from extra_listen.
What this gives the chat:
server:<id> entries: local models from llama.cpp,
LM Studio and Ollama, plus <provider>/<model> for every enabled cloud provider. default maps to
engines.default_model.stream_options.include_usage.Atrium’s own local kind still talks to LM Studio directly. Cerberus sees models loaded that way as
loaded by someone else and leaves their unloading to LM Studio’s TTL; it can still evict them when it
needs room. Using only server: models gives Cerberus the full picture.
Optional: a client may send X-Cerberus-Client: <name> (and X-Cerberus-Session: <id>) so its
requests are attributed in the usage log. Atrium does not send it today; its requests are recorded
with an empty client.
When ~/.config/hypr/scripts/notify exists, Cerberus uses it for session events (waiting for
approval, passed, failed, escalated, error), so they get Atrium’s styling and appear in the dynamic
island. Without it, Cerberus falls back to notify-send. notify.ntfy_url adds phone push through
ntfy. Sends are rate-limited to one per session and state every 10 s.
The CLI is the desktop entry point until the Phase 5 client exists:
cd ~/Github/some-repo && cerberus task --agent cerberus --model default "Fix the failing test"
cerberus url # open the session on a phone: live terminal, approvals, diff, accept
The desktop client grows out of Atrium’s LLM chat. It stays a native client of the same HTTP API the web UI uses, with no embedded browser runtime and no second copy of any logic.
| Piece | Source of data | Behaviour |
|---|---|---|
| Chat | /v1 (unchanged) |
Model picker grouped by local, loaded and cloud; per-reply tok/s and energy from the usage log. |
| Island agent status | GET /api/events (SSE) |
Shows running and waiting sessions; a waiting approval opens a small y / a / n prompt that posts to /api/sessions/{id}/input. |
| Launcher “new task” | GET /api/repos, GET /api/status (agents), POST /api/sessions |
Repo picker (recent first), task text, agent and model; opens the session view or the web UI. |
| Bar readout | GET /api/status (gpu, models) |
GPU power and the current tok/s while a model is generating; hidden when idle. |
| Session view | GET /api/sessions/{id}/stream, /diff |
Terminal and diff review with accept and discard, for when a phone isn’t at hand. |
Energy rules carry over from the service: one SSE connection for events while visible, nothing while
hidden, no timers when no model is loaded. The client authenticates with the bearer token read from
~/.config/cerberus/config.json; it never needs the cookie or the CSRF header.
The service already cross-compiles for both. Platform paths are in place (~/Library/Application
Support/Cerberus, %LOCALAPPDATA%\Cerberus), as are process-group signalling behind build tags and
stubs where something isn’t supported yet. What remains:
platform.StartPTY, and process-tree
termination through Job Objects instead of signals. The built-in agent’s run_command needs a
shell other than bash, or Git Bash.cerberus install-service.nvidia-smi works on Windows as on Linux. Apple Silicon needs a power source that
does not require root (or reports energy as unknown, which the gateway already handles).llama-server binaries exist for both.