Built by Codex (gpt-6-luna) from android/API-CONTRACT.md and android/AGENTS.md; reviewed,
verified and fixed by Nyra.
The ten-item v1: settings with a Test-connection ping, the card gauge keyed to
pct_of_usable, per-pipeline state/health including the external-holder explanation,
switch/stop/restart with confirmation, the protected 409 -> explicit force path, model
pickers that post their selection, job following that shows the server's own steps,
free-the-card, 3s refresh plus resume and pull-to-refresh, and 401/tunnel-aware errors.
Verified, not taken on trust: ./gradlew --rerun-tasks testDebugUnitTest assembleDebug ->
BUILD SUCCESSFUL, test XML tests=1 skipped=0 failures=0 errors=0, and all 40 tasks
executed rather than reported up-to-date.
Fix applied on review: Job.steps was typed List<String> while the server appends
{"ts","text","ok"} objects, so Gson threw on any job that had taken a step and the UI
surfaced it as a tunnel failure. Now a JobStep type, and the panel shows the step history.
The contract said "plus a steps array" without naming the element type; it now does,
including that steps exist only on /api/jobs/<id>. Also gated the 3s poll on the
foreground so a backgrounded app stops hitting the tunnel.
gpu-cockpit
gpu-cockpit is a registry-driven console for one shared inference GPU. A single Python server
owns host probes, pipeline state, process/unit control, action jobs and the audit trail. The browser
GUI and terminal TUI are HTTP clients; service-specific details stay in TOML.
Run
Requires Python 3.11+ (stdlib only for the server and GUI). Copy
registry.example.toml to ~/.config/gpu-cockpit/registry.toml, then:
python3 -m cockpit --registry ~/.config/gpu-cockpit/registry.toml --check
python3 -m cockpit --registry ~/.config/gpu-cockpit/registry.toml --port 8770
The server binds to 127.0.0.1 by default. Set --host to bind elsewhere deliberately. State,
logs, selections and audit records go in ~/.local/state/gpu-cockpit by default. COCKPIT_REGISTRY
can provide the default registry path. --dry-run serves the API and performs read-only health
checks while recording intended commands without starting or stopping processes.
Open http://127.0.0.1:8770/ for the responsive web GUI. The TUI uses the same API:
python3 tui/cockpit.py --url http://127.0.0.1:8770
python3 tui/cockpit.py --print --url http://127.0.0.1:8770
Install textual to use the interactive TUI (pip install textual). --print needs no optional
dependency or terminal.
Add a pipeline
Add another [[pipeline]] block in the registry. No Python change is required. Each pipeline needs
one or more components. Components start in listed order and stop in reverse order. A proc component
is spawned and supervised by the server; a unit component is operated through systemctl.
health can use http, tcp, command or none.
For example, a CPU-only local service can run alongside a card owner:
[[pipeline]]
id = "cpu-llm"
label = "CPU language model"
description = "A local inference endpoint that does not claim the GPU"
requires_card = false
vram_mib = 0
serving = "Small instruct model"
[[pipeline.component]]
kind = "proc"
id = "server"
label = "Inference server"
argv = ["/opt/local-llm/bin/server", "--port", "8090", "{model}"]
cwd = "/opt/local-llm"
env = { OMP_NUM_THREADS = "8" }
ready_timeout_s = 30
stop_timeout_s = 10
health = { kind = "tcp", host = "127.0.0.1", port = 8090, timeout_s = 2 }
[[pipeline.option]]
id = "model"
label = "Model"
kind = "argv"
default = "small"
[[pipeline.option.choice]]
id = "small"
label = "Small model"
vars = { model = "/models/small.gguf" }
Placeholders are substituted within argv elements using values from argv options. An empty value
drops the entire argv element. Every placeholder must be declared by an option, and every choice
must provide each variable used by that option. Options can also be note kind for display-only
choices. Run --check before restarting the server; validation problems remain visible in the API
and GUI.
Safety and operation
- A pipeline with
requires_card = trueowns the exclusive card group. Starting another such pipeline stops active peers, in reverse component order, before reclaiming and starting it. protected = trueblocks implicit stops. A user must make a deliberate forced switch.- Starts count as successful only after their configured health probe passes. Timeouts fail the job and include the recent pipeline log tail. GPU waits are bounded and report unavailable probes or timeout conditions honestly.
- Only one action job runs globally at a time. Jobs expose progress in
/api/jobs/<id>and state. - Proc pidfiles include Linux process start identity; stale files and reused PIDs do not imply an owned process. An externally started healthy service is shown as external and cannot be killed.
- Executed commands and dry-run intent are recorded under the state directory. The API never edits
the registry; option selection is persisted in
selected.toml.
API
| Route | Purpose |
|---|---|
GET / |
Web GUI |
GET /static/<file> |
Allowlisted GUI assets |
GET /api/state |
Full card, component, pipeline, job and validation snapshot |
GET /api/events |
Server-sent state events and heartbeat (?once=1 for one event) |
POST /api/pipelines/<id>/action |
Start, stop or restart; returns a job id |
POST /api/card/free |
Run the registry reclaim recipe as a job |
GET /api/jobs/<id> |
Job state, progress steps and failure detail |
GET /api/logs/<id>?n=200 |
Tail proc log or unit journal |
GET /api/audit?n=100 |
Recent command audit records |
GET /api/registry |
Parsed registry and validation report |
POST /api/pipelines/<id>/options |
Validate and persist option selection |
Development checks
python3 -m compileall -q cockpit tui
python3 -m cockpit --registry registry.example.toml --check
bash scripts/selftest.sh