nyra 2f4614f353 android: Compose client for the cockpit, over WireGuard only
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.
2026-09-27 16:39:37 +00:00

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 = true owns the exclusive card group. Starting another such pipeline stops active peers, in reverse component order, before reclaiming and starting it.
  • protected = true blocks 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
S
Description
GPU + pipeline cockpit: registry-driven VRAM/state console with TUI and web GUI
Readme
957 KiB