Request and data flow¶
A request through the backend¶
Every API route is under /dgxmm/api (dgxmm/app.py mounts the routers with the base path). The browser
sends the session cookie dgxmm_session and, on every request other than GET, the session's CSRF token.
A change such as Sleep on a deployment goes through these steps:
sequenceDiagram
autonumber
actor B as Browser (React SPA)
participant P as Reverse proxy (optional)
participant U as Uvicorn :8090
participant M as BodyLimit middleware
participant A as current_principal / require_admin
participant R as Router (dgxmm/api/)
participant S as Service (Deployer, BudgetService, ...)
participant DB as SQLite
participant EV as EventBroker
B->>P: POST /dgxmm/api/deployments/{id}/sleep<br/>cookie dgxmm_session, X-CSRF-Token
P->>U: HTTP, X-Forwarded-For
Note over U: X-Forwarded-For / -Proto applied only from trusted_proxies,<br/>never from the decider's network (proxy_headers.py)
U->>M: request
Note over M: body caps on the decider, insights and schedule routes<br/>(413 request_too_large)
M->>A: route dependency
A->>DB: session by SHA-256 of the token
alt no valid session
A-->>B: 401 not_authenticated
else bad or missing CSRF token
A-->>B: 403 csrf_failed
else password must be changed first
A-->>B: 403 password_change_required
else viewer on an admin route
A-->>B: 403 admin_required
end
A->>R: Principal (user, session)
R->>S: call the service
S->>DB: write state, events, audit
S->>EV: publish(topic, data)
R-->>B: JSON (errors: {"detail": {"code", "message"}})
EV-->>B: server-sent event on GET /dgxmm/api/stream<br/>(topics filtered by role)
- Authentication (
dgxmm/auth/deps.py):current_principalresolves the session, checks the CSRF header on non-GET requests and refuses everything but the password change whilemust_change_pwis set.require_adminadds the role check. Admin CLI requests arrive on the admin unix socket with a short-lived token instead (see application security model). - Services are wired once into an
AppContext(dgxmm/context.py); routers only translate HTTP to service calls. Docker is only ever called fromdgxmm/docker_ctl.py. - Live updates (
dgxmm/events.py): services publish to an in-processEventBroker; each openGET /streamsubscribes, receives the topics its role may see (dgxmm/api/stream.py) and drops its oldest events when it is too slow. The UI refreshes the matching queries (see frontend).
Data flow¶
- Health: metrics scraper → live samples and rollups →
health_check(summary, rules, model for ambiguous windows) →insights(+decisions) → SSEinsights→ Insights page / deployment card → Reconfigure dialog →POST /deployments/{id}/reconfigure→POST /insights/{id}/applied. - Schedules: rollups →
traffic_profiles(nightly) → candidates → model → proposal insight → admin creates schedules →deployment_schedules→ runner (every minute) → deployer sleep / stop / wake / start → deployment events and audit. - Readings: benchmark runner / calibration recorder → listener triggers
reading_labels→ label andqualityon the row → calibration and advice use plausible rows only. - Routing: deployer state hooks and LiteLLM sync listeners → settled trigger →
routing_advice→ latest advice (LiteLLM page) and an insight with the snippet. Nothing is written to LiteLLM. - Tuning: feedback on
decisions→ weeklythreshold_tuning→ proposal insight →PUT /api/decider/settings.
The same flows as a diagram: rules and the model only produce advice and insights; changes still go through the normal endpoints (bold arrows), and only the schedule runner acts on its own.
flowchart LR
scraper["metrics scraper"] --> rollups[("metric_rollups")]
rollups --> health["health_check<br/>(every 300 s)"]
rollups --> profiles["traffic_profiles<br/>(daily 03:10)"]
profiles --> proposals["schedule_proposals<br/>(daily 03:40)"]
readings["benchmark runner /<br/>calibration recorder"] --> rl["reading_labels"]
hooks["deployer state hooks,<br/>LiteLLM sync listeners"] --> routing["routing_advice"]
feedback["feedback on decisions"] --> tuning["threshold_tuning<br/>(weekly)"]
health --> insights[("insights")]
proposals --> insights
routing --> insights
tuning --> insights
rl --> rows[("benchmarks /<br/>memory_calibration<br/>reading, quality")]
health -. "ambiguous windows" .-> decider["decision model<br/>(decisions log)"]
proposals -. "candidate windows" .-> decider
routing -. "per traffic class" .-> decider
rl -. "unclear readings" .-> decider
insights -- "SSE insights" --> ui["Insights page,<br/>deployment card"]
ui == "Reconfigure dialog" ==> reconf["POST /deployments/{id}/reconfigure"]
ui == "admin creates schedules" ==> sched[("deployment_schedules")]
ui == "Apply threshold" ==> settings["PUT /decider/settings"]
sched --> runner["schedule_runner<br/>(every 60 s)"]
runner == "sleep / stop / wake / start" ==> deployer["deployer"]
HTTP API¶
Every route is under /dgxmm/api (dgxmm/api/). All of them except login need a session; user means any
signed-in user (viewer or admin). With a session, every request other than GET must also send the
session's CSRF token in X-CSRF-Token. The UI uses exactly
these routes, so this is also the API for scripts.
| Area | Routes | Role |
|---|---|---|
| Auth | POST /auth/login, POST /auth/logout, GET /auth/me, POST /auth/change-password |
login: none; others: user |
| Host | GET /host (budget, GPU, disk), GET /host/processes?scope=all\|external\|managed&limit=50 |
user |
| Deployments | GET /deployments, GET /deployments/{id}, GET /deployments/{id}/litellm |
user |
POST /deployments/preview, POST /deployments (with mode and idle_timeout_s), PATCH /deployments/{id} ({"alias": …, "rewrite_keys": true}), PATCH /deployments/{id}/mode ({"mode": "on_demand"\|"always_on", "idle_timeout_s": …}), POST /deployments/{id}/start\|stop\|sleep\|wake\|switch\|reconfigure, DELETE /deployments/{id}, GET /deployments/{id}/logs |
admin | |
| Metrics and advice | GET /deployments/{id}/metrics, GET /host/metrics, GET /deployments/{id}/advice, GET /deployments/{id}/benchmarks |
user |
POST /deployments/{id}/benchmark |
admin | |
| Inventory | GET /inventory |
user |
POST /inventory/refresh, GET /inventory/delete-preview?ref=, DELETE /inventory?ref=, GET /inventory/cleanup-incomplete/preview, POST /inventory/cleanup-incomplete |
admin | |
| Catalog and downloads | GET /catalog/search, GET /catalog/details, GET /downloads |
user |
POST /downloads, POST /downloads/{id}/cancel |
admin | |
| Planner | GET /planner |
user |
PUT /planner/favourites/{ref}, DELETE /planner/favourites/{ref} |
admin | |
| LiteLLM | GET /integrations/litellm (?probe=false answers at once with the cached status), GET /integrations/litellm/status, GET /integrations/litellm/models (API bases only for admins) |
user |
PUT /integrations/litellm, POST /integrations/litellm/test, POST /integrations/litellm/sync, GET\|POST /integrations/litellm/keys, PATCH\|DELETE /integrations/litellm/keys/{token} |
admin | |
| Decision model | GET /decider/status, GET /decider/image, GET /deployments/{id}/diagnosis, GET /deployments/{id}/workload |
user |
PUT /decider/settings, POST /decider/enable ({"actions": […]}), POST /decider/disable ({"remove": true}), POST /decider/models/download, POST /decider/image/build, POST /decider/rotate-key, GET /decider/decisions?feature=&limit=, POST /decider/decisions/{id}/feedback, POST /decider/suggest-config |
admin | |
| Engines | GET /engines |
user |
| Containers | GET /containers/adoptable, POST /containers/{container}/adopt |
admin |
| Users and audit | GET\|POST /users, PATCH\|DELETE /users/{id}, GET /audit |
admin |
| Insights | GET /insights?status=&subject=&kind= (viewers: viewer-visible features, no actions) |
user |
POST /insights/{id}/dismiss, POST /insights/{id}/snooze ({"hours": 1..168}), POST /insights/{id}/applied |
admin | |
| Deploy-time advice | GET /catalog/compat?repo= |
user |
POST /decider/config-advice, POST /decider/draft-suggest, POST /decider/engine-suggest, POST /decider/remote-code-check, GET /decider/routing-advice |
admin | |
| Schedules | GET /deployments/{id}/schedules, GET /schedules/settings |
user |
POST /deployments/{id}/schedules ({"schedules": [{action, days, at_local, timezone}]}), PUT /schedules/{id}, DELETE /schedules/{id}, PUT /schedules/settings ({"paused_all": …}) |
admin | |
| Commands | POST /decider/command ({"text": …}, ≤ 300 characters; viewers get navigation only) |
user |
GET /decider/free-plans?gib=, POST /decider/free-memory |
admin | |
| Events | GET /stream (server-sent events) |
user |
The on-demand gateway (/dgxmm/ondemand/{id}/v1/…) is not part of this API: it
takes no session, only LiteLLM's bearer key from the Docker network, and answers OpenAI-style errors.
GET /host also returns the global on-demand defaults (ondemand: {idle_timeout_s, queue_timeout_s}), and
deployment views carry mode, idle_timeout_s and ondemand (inflight, queued, last_request_at,
phase; null for always-on deployments).
Errors are JSON, {"detail": {"code": …, "message": …}}, with a stable code (for example
model_name_taken, model_in_use, insufficient_memory, litellm_no_db, image_missing,
rate_limited) and sometimes extra fields such as the deployment that holds a model name. Some
routes are rate-limited and decider request bodies are capped (see
API limits).
Responses that changed for the decision model: plan options carry recommended, recommendation
(source, confidence, reason) and unsafe_reason; inventory artifacts kind_source and
kind_confidence; advice items relevance and workload_fit; deployment views system and
startup_queue (position and reason while queued). POST /deployments/{id}/wake answers 202 when
the wake-up continues in the background. GET /decider/status gained feature_specs (the registry) and,
for admins, jobs (the background jobs).