> ## Documentation Index
> Fetch the complete documentation index at: https://gladlabs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Game mode

# Game mode — claiming the GPU for the operator

`poindexter game on` parks the GPU-heavy services and pauses pipeline GPU work
for a bounded window, so the box can be used for something other than
inference. `poindexter game off` ends it early; the window also expires on its
own.

## Why this exists

The operator PC is simultaneously the gaming rig and the AI inference server,
and the self-healing machinery is *designed* to keep the inference side up. A
hand-run `docker stop` therefore does not hold: `poindexter/brain/compose_drift_probe`
sees the service missing and runs `docker compose up -d` on its next cycle.

That is not a bug in the drift probe — it is the probe doing its job. Game mode
is the sanctioned way to tell it "these are down on purpose".

The incident that motivated this: a gaming session found the RTX 5090 pegged at
99% by `poindexter-speaches` (a Whisper model that had grown to 5 GB) while the
game itself failed to launch. Stopping the container by hand worked for about
60 seconds before the drift probe brought it back.

## Usage

```bash theme={null}
poindexter game on                # default window (game_mode_default_hours, 4h)
poindexter game on --hours 2
poindexter game status
poindexter game off
```

`--json` on any subcommand for machine-readable output.

From a phone, via MCP: `set_game_mode(action="on", hours=4)`.

## What it does

| Effect | Mechanism |
| - | - |
| Stops the GPU containers | CLI runs `docker stop` immediately; brain keeps them down |
| Keeps them down | `compose_drift_probe` folds them into its on-demand set |
| Frees resident VRAM | Evicts Ollama models on **every** configured host (confirmed, not fire-and-forget) |
| Pauses pipeline GPU work | `gpu_scheduler` raises `GpuBusyError("game_mode")` on every new acquire |
| Prevents a retry cascade | The Prefect flow stops claiming new tasks for the window |

CPU-only work continues: taps, research, SEO sweeps, scheduled publishing, and
anything else that never takes the GPU lock.

## Design notes

**It is a TTL, not a toggle.** `app_settings.game_mode_until` holds an absolute
ISO-8601 UTC timestamp and every consumer compares it against `now()`. A
forgotten game mode therefore expires on its own rather than silently starving
the content pipeline for days — the failure mode of a plain boolean. Nothing
has to run to clean up; an elapsed timestamp simply reads as inactive.

**Intent is central, enforcement is distributed.** `enable()` writes one key.
Three independent consumers read it, so no consumer depends on another being
reachable. This is also why triggering from a phone works: the worker container
has no docker socket, so container stopping cannot live in the service layer
and work everywhere. The CLI (which runs on the host) stops containers
immediately as an optimisation; the brain enforces it regardless.

**Restore reuses the self-healer.** `disable()` does *not* start anything. The
drift probe already owns "a service that should be up is down" and restores the
parked services on its next cycle (≤5 min). Exactly one piece of code starts
containers.

**It does not touch `gpu_external_workload_wait_enabled`.** That flag drives the
*utilization-inference* path in `gpu_scheduler._wait_for_gaming_clear`, which is
off by default because it mislabels the stack's own GPU bursts as gaming and
once produced a 407-second phantom pipeline stall (validation finding 4a). Game
mode is an *explicit* signal, so it needs no heuristic and re-introduces none of
those false positives. The two are independent on purpose.

**Failure direction is deliberate.** A corrupt `game_mode_until` reads as OFF
everywhere. Failing *open* would pause the pipeline indefinitely with nothing to
notice it; in the brain it would additionally leave real outages unrecovered and
unpaged. Both are worse than a game session seeing its containers restart.

## Settings

| Key | Default | Meaning |
| - | - | - |
| `game_mode_until` | `''` | Expiry (ISO-8601 UTC). `''` = off. Written by the adapters. |
| `game_mode_parked_services` | `speaches,chatterbox,stable-audio-server,image-gen-server,wan-server,comfyui` | Compose **service** names |
| `game_mode_default_hours` | `4` | Window when `--hours` is omitted |
| `game_mode_evict_ollama` | `true` | Evict resident models on enable |
| `game_mode_container_prefix` | `poindexter-` | Prefix applied to derive container names |

`game_mode_parked_services` holds compose *service* names because that is the
vocabulary `compose_drift_probe` speaks; docker container names are derived by
prefixing `game_mode_container_prefix` — except for five services whose
compose `container_name` isn't prefix + service name (`stable-audio-server`
→ `poindexter-stable-audio` is the one that bit us; see `CONTAINER_SUFFIX_OVERRIDES`
in `services/game_mode.py`), where that map's suffix is used instead. A unit
test checks the map against `docker-compose.local.yml`, so a compose rename
fails CI rather than silently parking nothing.

## Verifying

`poindexter game status` prints intent **and** reality:

```
game mode ON until 2026-08-10T08:17:00+00:00 (238m left)
  parked   speaches, chatterbox, stable-audio-server, image-gen-server, wan-server, comfyui
  STILL UP poindexter-speaches — GPU is not fully free
```

A service listed as parked but still running means the stop did not land (no
docker socket, or a manual restart). Showing both stops "game mode is on" from
implying "the GPU is actually free".

Direct check:

```bash theme={null}
nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.free --format=csv
```

## What it deliberately does NOT free

The vision-pinned Ollama instance (`:11435`, `ollama-vision.service`) runs its
model with `keep_alive=-1`. `unload_loaded_ollama_models` honours that pin and
skips it, so game mode leaves \~21 GB resident on the second card by design —
that model is held there as eviction insurance for the pipeline.

This does not affect gaming: the display GPU is card 0, and that is the one
game mode clears. If you genuinely need the second card too, stop
`ollama-vision.service` by hand; game mode will not override a deliberate pin.

## Probes that know about game mode

A parked service is unreachable on purpose, so any probe that live-checks one
has to read `game_mode_until` or it will page on the operator's own action.

* `compose_drift_probe` folds the parked list into its on-demand set while
  the window is open, **actively stops** a parked service that is still
  running (the MCP path has no docker socket, so the brain does the stop on
  its next cycle), and `docker start`s each parked container once the window
  lapses — **including services behind a compose profile that is not in
  `compose_drift_active_profiles`**. Being on `game_mode_parked_services` is
  the operator's statement that the service normally runs; a profile that was
  never brought up has no container to start, so nothing is created. (Before
  2026-09-21 the profile check won: chatterbox — `profiles: [tts-hq]` — stayed
  `exited` after the game while the narration probe paged critical hourly.)
* `probe_narration_failure` skips its live TTS health check while the engine's
  service is parked, recording `tts_healthy=skipped` so the window can never
  count toward its consecutive-unhealthy streak.

If you add a probe that reaches a GPU sidecar, check `services.game_mode`
first (`is_active` + `parked_services`) and return "skipped", not "down".

## Fixed gaps (2026-09-21/22)

Two bugs shipped together, both closed by stack#3923:

* `game_mode_parked_services` listed `stable-audio`, not the real compose
  service `stable-audio-server`. A name that matches no service parks
  nothing, silently — `poindexter game status` reports only the names it was
  given, so the mismatch was invisible from there. Game mode never parked
  stable-audio until this was fixed.
* Fixing that name exposed a second bug: `container_names()` built
  `poindexter-<service>`, but `stable-audio-server`'s container is
  `poindexter-stable-audio`, not `poindexter-stable-audio-server`. Five
  services break the prefix rule; see `CONTAINER_SUFFIX_OVERRIDES` above.
  Without it, `poindexter game on` would have stopped nothing for those
  services even with the name corrected.

`comfyui` was also added to the default parked list in the same PR — it holds
VRAM like the other GPU servers and had been the one left running during
games. (A related fix landed alongside in stack#3924: `compose_drift_probe`'s
active-profiles list now comes from the stack's own `COMPOSE_PROFILES` launch
env instead of a hand-kept setting, so a profile drifting out of sync no
longer leaves a crashed service undetected.)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.