SparkDeck Agent
Remote AI Command Center agent for NVIDIA DGX Spark.
A small FastAPI service that runs on the Spark and gives the SparkDeck iOS app GPU telemetry, Docker container control (start, stop, restart) and discovery of local LLM endpoints.
Install
From the SparkDeck app
Open the device in SparkDeck and use Agent Management > Install Agent. The app connects over SSH and:
- creates a virtualenv at
~/.sparkdeck-agent-venv, - installs
sparkdeck-agentfrom PyPI into it, - enables lingering for your user and starts the agent as a systemd user
service (
sparkdeck-agent start --daemon), - reads the auth token with
sparkdeck-agent token.
Update Agent upgrades the package and restarts the service, Restart Agent restarts it, and Uninstall Agent removes the service and the virtualenv.
Manually
DGX OS does not allow system-wide pip install, so use a virtualenv. Create it
at ~/.sparkdeck-agent-venv, the location the SparkDeck app looks in:
python3 -m venv ~/.sparkdeck-agent-venv
source ~/.sparkdeck-agent-venv/bin/activate
pip install sparkdeck-agent
# Keep user services running after you log out (once per machine)
loginctl enable-linger "$USER"
# Install and start the systemd user service
sparkdeck-agent start --daemon
Without --daemon, sparkdeck-agent start runs in the foreground. Use
--port to override the port.
The app reads the auth token over SSH by running sparkdeck-agent token. You
can print it yourself at any time:
sparkdeck-agent token
Command line
| Command | What it does |
|---|---|
sparkdeck-agent start [--port N] [--daemon] |
Run in the foreground, or install and start the systemd user service |
sparkdeck-agent stop |
Stop the service |
sparkdeck-agent status |
Show version, configuration and service state |
sparkdeck-agent token [--reset] |
Print the auth token, or generate a new one |
sparkdeck-agent config |
Show the configuration values in effect |
python -m sparkdeck_agent works as well.
Configuration
Settings are read from ~/.config/sparkdeck/config.toml. The file is parsed
with Python's built-in tomllib, which needs Python 3.11 or newer; on older
versions the defaults are used. A file that cannot be parsed, an unknown key
and a value of the wrong type are each logged as a warning that names the file
(for the service: journalctl --user -u sparkdeck-agent). An unknown key or a
wrong value is ignored and the rest of the file still applies; a file that
cannot be parsed means all the defaults, so the agent listens on every
interface.
| Key | Default | Meaning |
|---|---|---|
host |
"0.0.0.0" |
Interface to listen on |
port |
8420 |
Port to listen on |
log_level |
"info" |
uvicorn log level |
gpu_poll_interval |
1.0 |
Seconds between GPU samples on the WebSocket stream |
discovery_ports |
[11434, 8000, 5000, 8080, 8081] |
Local ports scanned for LLM endpoints |
Restart the service after editing the file:
sparkdeck-agent stop && sparkdeck-agent start --daemon.
API
Every /api route requires Authorization: Bearer <token>. The WebSocket
also accepts a legacy ?token= query parameter (see the security notes).
| Route | Description |
|---|---|
GET /api/status |
Hostname, agent version, uptime, GPU, running containers, LLM endpoints |
GET /api/gpu/metrics |
Current GPU metrics |
WS /api/gpu/stream |
One JSON frame of GPU metrics per gpu_poll_interval; a bad token is rejected at the handshake |
GET /api/containers |
All Docker containers |
POST /api/containers/{id} |
Body {"action": "start"}, {"action": "stop"} or {"action": "restart"} |
GET /api/models |
Discovered LLM endpoints |
POST /api/chat/proxy |
OpenAI-style chat request forwarded to a local LLM |
POST /api/notify/register |
Push notification registration (stub) |
GPU metrics
The numeric fields (utilization, memory_used, memory_total,
temperature, power_draw, fan_speed, memory_utilization) are never
null. A value the GPU cannot report, such as fan speed on some systems, is
sent as 0 and its field name is listed in unavailable, so clients can show
"N/A" instead of a misleading zero. Memory is the exception: an unavailable
memory reading is sent as memory_used = 0 and memory_total = 1 (the total
stays positive), with "memory" in unavailable.
memory_kind is "unified" when NVML reports no GPU memory and the figures
come from the system memory in /proc/meminfo (the case on a GB10), and
"dedicated" otherwise.
Token and security notes
- Treat the token like a password. Anyone who has it can start, stop and
restart the Docker containers the agent can reach and use the agent as a
proxy to your local LLMs. It is stored in
~/.config/sparkdeck/token(mode0600); do not paste it into chats, tickets or logs. - Reset a token with
sparkdeck-agent token --reset. The running agent re-reads the token file whenever it changes, so the old token is rejected from the next request on and no restart is needed. WebSocket streams opened with the old token are closed (code 4001) within one poll interval. The SparkDeck app (1.0.1 and later) refreshes the token automatically over SSH: when the agent rejects its saved token (HTTP 401), the dashboard runssparkdeck-agent tokenover SSH, saves the new token and retries once. Agent Management shows Token rejected with a Refresh Token button that does the same. Other clients that cached the old token need the new one. - The agent speaks plain HTTP and listens on all interfaces by default.
Use it on a trusted network such as your LAN or Tailscale, and never expose
port 8420 to the internet. To narrow it, set
hostinconfig.toml, for example your Tailscale address (host = "100.101.102.103").127.0.0.1makes the agent reachable only from the Spark itself, so the app cannot connect.sparkdeck-agent configshows the value in effect. - WebSocket authentication. Send the token as
Authorization: Bearer <token>. The older?token=<token>query parameter is still accepted for existing app versions, but prefer the header: URLs end up in logs and proxies. The agent maskstoken=...andBearer ...values in uvicorn's log output. - No API docs.
/docs,/redocand/openapi.jsonare disabled, so the API surface is not published to anyone who reaches the port.
Development
pip install -e ".[dev]"
pytest
Metadata
Release files for sparkdeck-agent 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| sparkdeck_agent-0.1.2.tar.gz | 31.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sparkdeck_agent-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 55.2 kB
Release files / sparkdeck_agent-0.1.2.tar.gz
| Download URL | sparkdeck_agent-0.1.2.tar.gz |
|---|---|
| Size | 31.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e9dec7982c8de669e455a85ea1a6e45d137106cb05eb310232fb28b366f7ce81
|
|
BLAKE2b-256 checksum How to use checksums |
867a4319085375be004e19227d0e794b4396c48c059f1835a1f957a47c50d96d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / sparkdeck_agent-0.1.2-py3-none-any.whl
| Download URL | sparkdeck_agent-0.1.2-py3-none-any.whl |
|---|---|
| Size | 23.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
699df918c3f68695cae89eaa632d3ee8b58d25c7b38db412840bad37245f20d7
|
|
BLAKE2b-256 checksum How to use checksums |
2241f37cbe05d3e2608daf257735d4f09d02ce8d5fc66f28f8c37cc715eed02c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Security for sparkdeck-agent 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.