# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What this is The **workpc compute half** of a manga→narrated-video pipeline. This repo holds stateless GPU/CPU workers only. State, job scheduling, and stage orchestration live in a **separate homesrv orchestrator repo** (`/mnt/server/home/kami/apps/Maven/` region) — not here. The two communicate over a fixed HTTP contract; never add sqlite or durable state to a worker. Machine split (memorize): workers run on **workpc** (RX 7900 GRE, ROCm). MinIO + orchestrator run on **homesrv** (`192.168.1.104`, CPU-only). Data dir `/mnt/server/home/kami/` is an SSHFS mount of homesrv. ## Run / test ```bash ./start_workers.sh # dev: session_manager + 9 workers, each a uvicorn in a tmux window tmux attach -t manga-workers # watch logs; per-worker window sudo systemd/install.sh # production: one systemd unit per process (User=kami) python session_manager.py # each module has a __main__ self-check (assert-based, no framework) python transport.py # run these to verify a file after editing it python test_vision_parse.py # the one standalone pytest-free test ``` There is no lint/build step. `.venv` is the ROCm torch env; workers import `transport` by module name. ## Architecture **Workers are stateless HTTP stages.** Each `worker_*.py` is a FastAPI app on a fixed port. It pulls inputs from MinIO by URI to local disk (`/dev/shm`), does one stage, pushes outputs back, returns URIs. No cross-request memory. Ports: crop 8000, vision 8002, identity 8003, scene 8004, script 8005, tts 8006, layers 8007, render 8008, **session_manager 8095**. **`transport.py`** — shared MinIO client (`get`/`put`/`put_bytes`/`exists`) + `install_logging(app, name)` (one log line per request with panel id). URIs are `s3://bucket/key`. Import it in every worker. **`session_manager.py`** — the **GPU mutex**. Only one warm model at a time on the single local GPU. Two model kinds: - *subprocess* (`gemma4`): it spawns/health-waits/terminates `llama-server`, and a supervisor respawns it in-place if it crashes mid-session (keeps the same session_id + port). - *in-process* (`siglip2`, `dots`): returns `port=None`; the **worker** loads the transformers model itself and must expose `/unload` so `/session/close` can free the ~5GB VRAM (the mutex alone can't reclaim it). Leases have a TTL + heartbeat; a reaper force-closes stale ones. A GPU worker's flow: `/session/open {model}` → (409 if busy) → do work against the returned port or its own resident model → `/session/close`. CPU workers (crop) take no session. ## Conventions - **`ponytail:` comments** mark deliberate simplifications and name the upgrade path — respect them, don't "fix" them without reason. - Any non-trivial logic gets ONE runnable check in `__main__` (assert-based `demo`/self-check), not a test suite. Follow that pattern; run the file to verify. - The HTTP contract with the orchestrator is load-bearing and shared across repos — changing a worker's request/response shape means reconciling the orchestrator too (see commit history: "reconcile worker contracts"). ## Specs & docs - `spec-v3.md` — current quality/look work (narration voice, panel curation, render overhaul), marked DONE/TODO per item. `legacy/` holds the old single-repo workers. - `AGENTS.md` — use `scripts/analyze_video_frames.sh` (not manual seeking) to diagnose render output; its frames are `/tmp` artifacts, never commit them. - `collage.py` (behind `COLLAGE` flag) and the render worker carry the animated-layout planners.