Skip to content

feat(windows): run dashboard-api natively as a windows-desktop profile - #7970

Open
IronicRayquaza wants to merge 3 commits into
Osmantic:mainfrom
IronicRayquaza:feat/windows-desktop-runtime-profile
Open

IronicRayquaza wants to merge 3 commits into
Osmantic:mainfrom
IronicRayquaza:feat/windows-desktop-runtime-profile

Conversation

@IronicRayquaza

@IronicRayquaza IronicRayquaza commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

First milestone of #7968: run the real dashboard-api as a native Windows process, with no Docker, no WSL and no host agent.

ODS_RUNTIME_PROFILE=windows-desktop is opt-in. Without it, nothing changes for the Compose stack.

On a native PC, the container defaults are wrong in ways that are easy to miss. This profile corrects each one:

On a native Windows PC, main today... With this profile
Writes a generated API key to Path("/data"), which is C:\data (the drive root) Refuses to start without an absolute ODS_DATA_DIR and DASHBOARD_API_KEY from the launcher
Falls back to host.docker.internal (or ODS_AGENT_HOST) for the host agent, which can reach a different Docker or WSL ODS on the same PC AGENT_URL is empty. host_agent_client raises AgentUnavailable before any socket exists, and callers already map that to "unavailable"
Starts nvidia-smi, curl and docker from its background loops; 8 child processes in 6 s in the probe below. A windowless app would flash a console for each No child processes: GPU probes go through gpu.run_command, which returns "not measured"; the curl cluster refresh, Token Spy poll and Docker preflight are skipped
Probes 26 Compose services by Docker hostname; each failed lookup stalls on Windows, so /api/status took 6.8 s Only services with a loopback host are loaded. /api/status takes 63–74 ms
Reports CPU and RAM as None kernel32 GetSystemTimes / GlobalMemoryStatusEx, the same shape as /proc

/api/status now includes runtimeProfile. /api/preflight/docker returns applicable: false in this profile.

A new windows-desktop.yml workflow runs on windows-latest, only when dashboard-api/** changes. It installs requirements.lock the way the Desktop installer will (--require-hashes --only-binary=:all:), so a lock without Windows wheels fails there. Then it runs the profile tests. The new tests also run in the existing Ubuntu api job.

AI Assistance

Claude Code drafted the code, tests and this description, and ran the validation below on my Windows 11 laptop. I chose the scope and design (see #7968), and I'm accountable for the diff and for responding to review.

Release Lane

  • Stable hotfix targeting release/2.6.x
  • Mainline change targeting main
  • Next-minor work targeting the next feature/minor release
  • Not sure; reviewer should help classify

Changed Surface

  • Docs only
  • Tests only
  • Dashboard UI
  • Dashboard API / host agent
  • Installer / bootstrap / lifecycle
  • Docker Compose / service manifests
  • Model routing / Hermes / capabilities
  • Network exposure / auth / proxy
  • Dependencies / runtime wiring

Risk And Validation

  • Risk level: Low for Compose. Every behavior change is gated on the new profile, except native-Windows CPU/RAM metrics, which were None before.
  • Validation run:
    • git diff --check
    • Focused tests listed below
    • Full dashboard-api suite on Windows, compared against main
    • Scoped hardware validation (live process on a consumer laptop)

Run on an ordinary laptop: Windows 11 Home 26200, i5-10500H, 16 GB RAM, Intel UHD + RTX 3050 Laptop, Windows PowerShell 5.1, OneDrive-redirected Desktop, Python 3.13.

pip install --require-hashes --only-binary=:all: -r requirements.lock      -> ok, 25 s, no litellm in the lock
pytest tests/test_runtime_profile.py tests/test_windows_metrics.py \
       tests/test_requirements_lock_policy.py tests/test_windows_desktop_startup.py   -> 34 passed

Desktop-profile probe (real app, real bundled manifests, lifespan held 6 s):
                          main @ 72e59cdc6            this branch
child processes started   8 (nvidia-smi x5, curl x2,  0
                             docker --version)
host agent                dialed 127.0.0.1:7710       never dialed (no client created)
/api/status runtimeProfile  missing                   windows-desktop

Live: uvicorn main:app --host 127.0.0.1 --port 3102, started hidden
  cold start to /health      4.2 s, 3.3 s, 3.3 s (idle)
  /api/status                63-74 ms over 10 calls (main: ~6.8 s)
  RAM                        13.0/15.8 GB 82.1%   (Windows: 82.0%)
  children over 120 s        0      memory 86 MB      listeners 127.0.0.1:3102 only
  C:\data / D:\data created  no

Full dashboard-api suite on Windows, one file per process, branch against main:

                 main     branch
failed           35       36
errors           24       24     (all test_macos_model_recovery_cli.py)

The extra branch failures, both in test_status_cloud_mode.py, fail identically on main when run on their own (State has no cloud_telemetry_client; it is only set when another test happened to run the lifespan first). These are pre-existing isolation failures on Windows, not regressions. CI runs this suite on Ubuntu.

Also pre-existing on Windows: test_storage_collection.py::test_settings_reads_collect_once_across_timeouts hangs (3/3 runs on main and 3/3 on this branch). I'll report it separately.

Operational Change Check

  • This is not an operational change.
  • This is an operational change and validation is recorded above.
  • This is an operational change and validation is intentionally deferred for:

Dashboard API control flow is touched. In the default Compose profile, behavior is unchanged except native-Windows CPU/RAM reporting. No Compose fleet run was done for this PR.

Notes For Reviewers

  • Not covered here: chat (PR2), serving the dashboard UI natively (PR3), the launcher and installer (PR5–PR7). This PR only makes the API safe and honest as a native process.
  • Not run:
    • PowerShell 7: this PR has no .ps1 changes.
    • A Compose fleet install.
    • A live llama-server: the native route comes in PR2.
  • The three remaining services in a Desktop /api/status (opencode, pixel-agent, tailscale) default to localhost and answer instantly as down or not deployed. PR4 hides them in the UI.
  • CI: powershell-lint (ubuntu-latest) fails in its Install PSScriptAnalyzer step, before any repository code runs. This PR has no .ps1 changes. The same step has failed intermittently on other PRs (e.g. feat(windows): native Portal app with CPU/CUDA chat and documents #7938's Windows job). Contributors can't rerun it (Must have admin rights). Every other check passes, including mypy and the new Windows dashboard-api job.

Add an opt-in ODS_RUNTIME_PROFILE=windows-desktop so dashboard-api can run
as a native Windows process with no Docker, no WSL and no host agent.

- Refuse to start without an absolute ODS_DATA_DIR and DASHBOARD_API_KEY;
  the generated-key fallback no longer writes to /data (C:\data on Windows).
- Never dial a host agent: AGENT_URL is empty and the shared client raises
  AgentUnavailable before any socket, so a Docker or WSL ODS on the same PC
  cannot answer.
- Start no child processes: GPU probes, the curl cluster refresh, Token Spy
  polling and the Docker preflight are skipped. A windowless process would
  otherwise flash a console every five seconds.
- Load only loopback services, since Compose service names resolve to
  nothing on a native PC and each failed lookup stalls /api/status.
- Report Windows CPU and RAM through kernel32 instead of None.
- Add a path-filtered windows-latest workflow that installs the hashed lock
  wheels-only and runs the profile tests.

Refs Osmantic#7968

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IronicRayquaza and others added 2 commits October 23, 2026 02:06
mypy runs on Linux, where ctypes has no windll or WinError, so assert the
Windows platform inside the two kernel32 readers. Count lock hashes in a
typed dict instead of a dict mixing names and counters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mypy 2.4 does not treat an assert on sys.platform as unreachable, so define
the two readers under a module-level sys.platform == "win32" branch, with
stubs that raise OSError elsewhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant