diff --git a/docs/motion-pipeline-map.md b/docs/motion-pipeline-map.md index f79792b..c7ec160 100644 --- a/docs/motion-pipeline-map.md +++ b/docs/motion-pipeline-map.md @@ -7,8 +7,33 @@ cuts not yet mapped. The target's rationale is `docs/motion-pipeline-target.md`. ## Body facts +### Raven tray-toggle probes, 2026-10-22 23:30 to 23:36 UTC + +Run read-only from Starfire over `ssh raven` (session 0, elevated), one +PowerShell 5.1 script copied to `C:\Users\Public` and deleted in the same +command. Nothing on Raven was changed: no task edits, no restarts, no GPU jobs. +They inform cut spec `motion-pipeline:cut_spec:cut-raven-gpu-tenancy-gate.r2`, +which replaces detection with the household's toggle +(`motion-pipeline:ruling:raven-gpu-tray-toggle`). + +| Probe | Result | +|---|---| +| Who the household is on Raven | One account. `query user`: `madman's lullaby`, console, session 1, Active since 2026-10-18. The `\GameCult\Vili` task runs as that same user (`LogonType Interactive`, `RunLevel Limited`), so a tray process at that user's logon is in the same session and the same user as the daemon, with no admin rights. | +| Daemon listen address | `node.exe ... vili-daemon.mjs --host 0.0.0.0 --port 8824 --state-root E:\Projects\Vili\.vili`, pid 33776, session 1, at detached head `a2b9983`. Listens on `0.0.0.0:8824`, so the WireGuard peers can reach every route; a write route must check the caller itself. `GET http://127.0.0.1:8824/operator-state` from Raven answers in 3 ms. | +| Live task action versus the installer | The live `\GameCult\Vili` action passes `-IdunnRudpHealth 10.77.0.2:17870 -IdunnDaemon vili -IdunnHealthContract ... -IdunnHealthIntervalSeconds 15` to `start-vili-daemon.ps1`; `scripts/install-vili-task.ps1` does not. Rerunning that installer would rewrite the live task, so the tray gets its own installer. | +| Tray substrate | `powershell.exe` 5.1.26100, apartment `STA` by default; `System.Windows.Forms` and `System.Drawing` load (`v4.0.30319`, .NET Framework release 534296). Execution policy is Undefined at every scope (the task launcher passes `-ExecutionPolicy Bypass`). `scripts/run-hidden-powershell.vbs` already starts a hidden PowerShell with no console flash. No new dependency is needed. | +| Tray overflow | `HKCU:\Control Panel\NotifyIconSettings` exists with 47 entries: Windows 11 records each notify icon there and puts new ones in the overflow until the user promotes it. A fresh tray icon will not be on the taskbar until the household drags it there once (or turns it on under Settings, Personalization, Taskbar, Other system tray icons). | +| Startup folder | `C:\Users\Madman's Lullaby\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup`. Not used: from SSH (session 0) nothing in it can be started in session 1, while `Start-ScheduledTask` on an Interactive task can. | +| Scheduled tasks under `\GameCult\` | `Backup-Yggdrasil-Pull`, `ComfyUI`, `Vili`, all Ready. ComfyUI is a second possible heavy GPU consumer on Raven; the toggle governs Vili only. | +| Host free VRAM | `C:\WINDOWS\system32\nvidia-smi.exe --query-gpu=memory.free --format=csv,noheader,nounits`: 159 ms from a cold PowerShell, exit 0, free 5001 MiB at 23:3x UTC (the household or another consumer held about 11 GiB). No `nvidia-smi.exe` process was left running. | +| Vili's store | `E:\Projects\Vili\.vili\vili.service.cc`, 11810 bytes. Newest job file `vili-1792710792764.json`, written 23:20:12 UTC. The live gate (`a2b9983`) read `inUse true, probeOk true`. | +| cultcache-ts on Raven | `E:\Projects\CultLib` at `b9497d35`, package `0.14.1`; its build carries `pullAllBackingStores`. `SingleFileMessagePackBackingStore.push` reads the file, replaces one `(type, key)` entry and writes all, inside a write queue that belongs to one store instance. | + ### Raven GPU tenancy probes, 2026-10-22 20:15 to 20:23 UTC +These informed r1's detection gate, which r2 deletes. The session, principal and +`nvidia-smi` rows still hold. + Run read-only from Starfire over `ssh -o HostName=192.168.178.165 raven` (alias in `~/.ssh/config`; LAN address per `gamecult-ops/inventory.md`), as `powershell -NoProfile -NonInteractive -EncodedCommand`. Nothing on Raven was @@ -21,56 +46,83 @@ category (compositor, WSL family, or "other"). | Session of the Vili daemon | `node.exe ... vili-daemon.mjs --host 0.0.0.0 --port 8824` runs in **session 1**, the console session of `madman's lullaby` (`query user`: console, ID 1, Active). It is the interactive desktop session, not session 0. | | Vili task principal | `\GameCult\Vili`: `LogonType Interactive`, `RunLevel Limited`, boot trigger (`MSFT_TaskBootTrigger`), last run 2026-10-22 21:18:55 local, result 0. Children the daemon spawns inherit session 1. | | SSH context | Windows `sshd` runs commands in **session 0**, elevated (`IsInRole(Administrator)` true). `gamecult-ops/runbooks/idunn-host-raven.md` already records that sshd cannot start anything in the user's session without a scheduled-task hop. | -| `SHQueryUserNotificationState` from session 0 | `hr=0`, state `2` (`QUNS_BUSY`) on every call, with the GPU idle (3D engine total 0.01 to 0.02 %). Session 0 has no shell, so this value says nothing about the user's desktop. The call must run in session 1, which is where the daemon and its children are. It was **not** observed from session 1: doing that without a task edit is impossible from SSH. | -| `nvidia-smi.exe` | Present at `C:\WINDOWS\system32\nvidia-smi.exe`. `--query-gpu=memory.total,memory.used,memory.free,utilization.gpu`: `16380, 1592, 14516, 2` MiB/%. 63 ms cold, about 49 ms per call (10 calls 491 ms). Per-process `--query-compute-apps` returns `[N/A]` memory under WDDM. | -| `\GPU Engine(*engtype_3D)\Utilization Percentage` | Readable from session 0 as admin; 115 instances. `Get-Counter -SampleInterval 1 -MaxSamples 1` takes 1.08 s wall, 16 ms CPU warm, 328 ms CPU on first use in a process. Summed with `Measure-Object` it needs no instance names. Not tried as a Limited token in session 1. | -| `\GPU Adapter Memory(*)\Dedicated Usage` | Readable; 1.0 s wall; 1597 MiB on the RTX 4060 Ti, 0 on the Parsec virtual adapter. | -| `\GPU Process Memory(*)\Dedicated Usage` | Readable; 1.0 s wall. Two instances above 50 MiB: the desktop compositor (1643 MiB, session 1) and one session-0 process outside the WSL family (1405 MiB). Per-process figures overlap: they sum above the adapter total. | -| WSL attribution | The Kimodo worker container was resident (`/health` ok, uptime 3454 s, 6 generations) while `vmmemWSL` (and `vmwp`) had **no** `GPU Process Memory` instance. Host counters do not attribute WSL CUDA memory to a nameable host process. WSL `nvidia-smi` reported the same `1592 MiB used, 14516 MiB free` as the host. | -| PowerShell cost | `powershell.exe` 5.1 cold start 266 ms CPU, `Add-Type` for the shell32 call 78 ms CPU, first counter read 328 ms CPU: about 0.7 s CPU per cold spawn. A warm loop iteration (QUNS + `nvidia-smi` + one 3D counter read) is about 1.1 s wall and 16 ms of its own CPU. | +| `SHQueryUserNotificationState` from session 0 | `hr=0`, state `2` (`QUNS_BUSY`) on every call, with the GPU idle. Session 0 has no shell, so this value says nothing about the user's desktop. | +| `nvidia-smi.exe` | Present at `C:\WINDOWS\system32\nvidia-smi.exe`. `--query-gpu=memory.total,memory.used,memory.free,utilization.gpu`: `16380, 1592, 14516, 2` MiB/%. 63 ms cold, about 49 ms per call. Per-process `--query-compute-apps` returns `[N/A]` memory under WDDM. | +| `\GPU Engine(*engtype_3D)\Utilization Percentage` | Readable from session 0 as admin; 115 instances; `Get-Counter` 1.08 s wall. | +| `\GPU Process Memory(*)\Dedicated Usage` | Per-process figures overlap and sum above the adapter total. | +| WSL attribution | Host counters do not attribute WSL CUDA memory to a nameable host process. Host `nvidia-smi.exe` does see a WSL container's allocation: cut report h1 measured free falling 14292 to 1866 MiB under a 12 GiB holder. | | Adapters | `NVIDIA GeForce RTX 4060 Ti` driver 32.0.16.1088, 16 GiB; `Parsec Virtual Display Adapter`. | -What follows from these, and what does not: +### What the detection gate taught (r1, h1, h2, s1, s2) -- The daemon's own session sees the user's desktop, so the tenancy probe runs as - a child of the daemon. No fork about where it runs: the scheduled task already - puts it in the right session. -- Free VRAM must come from the host (`nvidia-smi.exe`), not from torch in WSL: - Soul's s1 probe on cut `vili-encoder-placement` showed `mem_get_info` inside a - container blind to another container's 12 GiB. Whether host `nvidia-smi` sees - a WSL container's allocation is **unverified** (the resident worker's share - could not be separated from the compositor's); the gate cut checks it first. -- The 3D-engine signal cannot be used to tell Vili's own work from the - household's by process, and need not be: Kimodo runs on CUDA (compute - engines), games on the 3D engine. Summing only `engtype_3D` leaves Vili out. -- A long-lived probe child costs about 1 ms of CPU per second at a 15 s period; - a fresh PowerShell per sample would cost about 45 ms per second. Neither - loads the GPU. +- Host free VRAM cannot tell Vili's encoder child from a household load: the + child sizes itself to the VRAM that was free (s1 `self-tenancy-refusal`). + Discounting Vili's own VRAM during a job blinds the gate to a household load + that starts mid-job (s2 `hold-blinds-household-vram`). +- Live, the probe child went silent during 2 of 5 of Vili's own encoder jobs and + the gate stayed stale after the job (s2 `own-load-starves-probe`; Raven had + 770 MB of host RAM available at one stall). Every repair added a watchdog, a + drain or a baseline, and each brought its own finding. +- After a job ends, host free VRAM is back at 14.6 to 15.2 GiB at the next + 2-second sample (s2), so one `nvidia-smi` read before a job, with no job + holding the slot, sees the card as the household left it. ## Model page | Kind | Named by | Over time | Decided by | |---|---|---|---| -| GPU tenancy state (`gpu` in `gamecult.vili.operator_state.v1`) | the daemon, one per host | rewritten every 5 s with the operator state; holds `inUse`, `since`, `checkedAt`, `probeOk` and nothing else | the daemon's tenancy gate, from host samples; never persisted as truth, rebuilt from samples at start | +| GPU permission (`gamecult.vili.gpu_permission.v1` in `.vili/vili.service.cc`, key `raven`) | the daemon, one per host | written only when the household changes it, before the change is acknowledged; read once at daemon start; absent means not allowed | the household, through the tray, through the daemon's loopback-only `POST /gpu-permission`; the daemon is its one writer | +| GPU status (`gpu` in `gamecult.vili.operator_state.v1`) | the daemon | rewritten every 5 s with the operator state; `{ allowed, changedAt, jobRunning, lastRefusal }` | derived from the permission, the slot and the last refusal; display only, never read back | | Motion job (`.vili/jobs/.json`) | `vili-` | written once when a job ends | the daemon; a job refused at admission writes none | | Text embedding (`.vili/embeddings/text-embeddings.cc`) | `sha256(identity, text)` | append on miss | the worker's cache | ## Rationale -- **Refuse, do not queue (recommended, pending the operator).** `/motion/generate` - is synchronous today. Holding a request open for as long as a game runs, or - adding a persisted job queue, are bigger machines than the one consumer needs: - RPS clip generation is a script an agent runs, and it can retry a typed - `gpu-busy`. A queue belongs with the authoring UX's takes, when that target - exists (`motion-pipeline:follow_up:vili-authoring-ux-target-2`). -- **Fail closed.** If the probe child dies or stops answering, the gate reports - in use. A wrongly deferred clip costs minutes; a wrongly admitted encoder child - pages a game's VRAM and thrashes Raven. +- **The household decides; nothing detects.** The operator replaced detection + with a tray toggle (`motion-pipeline:ruling:raven-gpu-tray-toggle`). The + detection gate's findings were the cost of guessing intent from counters; a + yes from the person at the desk needs no guess, no hysteresis and no probe. +- **The daemon is the one owner of the permission; the tray is a lowering.** + The tray reads and sets the state only through the daemon's HTTP route, so + admission and the stored value cannot disagree and the tray holds no state of + its own. The alternative, the tray writing a CultCache document the daemon + reads, would give the document two writers on two clocks and make the daemon + poll a file to learn its own admission rule. +- **Off on a fresh install, and off when the document is missing.** The safe + default for the household: Vili does nothing on Raven's GPU until someone at + the desk says yes. A stored yes survives restarts and reboots. +- **Loopback only for the write.** The daemon listens on `0.0.0.0`; the + household's switch must not be flippable from a WireGuard peer. A request + from a non-loopback address is refused 403. Agents read the state through + `GET /gpu-permission` or `/operator-state` and never set it. +- **One cache instance for the daemon.** The 5-second surface writer built a + new store per call. Two store instances writing one file each read, modify and + write in their own queue, so a permission write could be lost under a + concurrent surface write. One module-level cache serializes every write to + `vili.service.cc`. +- **One slot, one fresh VRAM read, fail closed.** h2's slot stays: one job at a + time, the rest refused at once with source `slot`. The slot is taken + synchronously before the VRAM read, so two requests cannot both pass it. A + failed or unparseable read refuses with source `vram`, so a job never starts + onto a full card on a guess. The permission is checked again after the read. +- **Refuse, do not queue** (`motion-pipeline:ruling:gpu-defer-refuse-typed`). + `/motion/generate` is synchronous; the consumer retries a typed `gpu-busy` + after `retry-after`. A queue belongs with the authoring UX's takes + (`motion-pipeline:follow_up:vili-authoring-ux-target-2`). +- **A logon task, not the Startup folder.** `Start-ScheduledTask` on an + Interactive task puts the tray in the household's session from SSH at deploy + time; a Startup folder entry would wait for the next logon. The task pattern + matches `\GameCult\Vili`. +- **The privacy rule is moot.** "Never record program names" protected the + household from a probe that watched its programs. With no probe, nothing in + Vili observes the household's programs; the only household fact Vili holds is + the yes or no the household chose. - **The child's own VRAM check is placement, not tenancy.** `plan_placement` - still sizes accelerate's `max_memory` from torch's view and can still refuse; - it answers "can the encoder be placed", not "may heavy work start". + still sizes accelerate's `max_memory` from torch's view and can still refuse + (source `placement`); it answers "can the encoder be placed", not "may heavy + work start". ## Cuts not yet mapped - RPS beats through Vili (`motion-pipeline:follow_up:rps-clips-from-vili`), after - the gate and `vili-fbx-export`. + the gate and `vili-fbx-export`. They wait on the household's yes on Raven.