The Status pill hung on "stopping" forever (operator-flagged 2026-07-01). Root cause: the backend had no lifecycle state — status() only returned running/stopped — so the UI FABRICATED "stopping" in JS as `!running && active>0`. That pill only cleared when the backend's `active` counter hit 0, but stop() (a) blocked the HTTP handler on lease-release calls to curator and (b) left `active>0` whenever a consumer wedged mid-submit/release to an overloaded curator → "stopping" that never resolved. Give the backend a real, truthful state it drives itself: stopped → starting → running → stopping → stopped - start(): → starting; a downloader flips it to running on its FIRST successful lease (so "running" means curator is actually answering, not just "Start was clicked"). If curator's down it honestly stays "starting". - stop(): → stopping; returns immediately (no handler block). A background monitor waits for the worker threads to actually exit, releases leases, then → stopped — bounded by STOPPING_TIMEOUT (20s) so a wedged submit can NEVER hold the UI in "stopping" again. In-flight work is handed back safely. - Buttons follow the real state (Start only from stopped; both disabled through the transition), so you can't fight a transition. - Log every Start/Stop button press (routes) and every transition (worker), so the Logs panel shows exactly what each button did. Frontend now trusts s.state (drops the active>0 hack); VERSION → .8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FabledCurator GPU agent
A desktop-GPU worker that embeds characters (CCIP) + figure crops for FabledCurator. It talks to FC only over HTTP — it leases jobs, fetches image pixels, runs the models on your GPU, and posts results back. Your FC database and Redis stay private; the agent never touches them.
You run it when you want a burst and stop it to reclaim the card.
0. Host prerequisite — NVIDIA Container Toolkit
Docker needs the toolkit to hand the GPU to a container (else: "could not select device driver nvidia with capabilities gpu"). On Arch/CachyOS:
sudo pacman -S nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
# verify:
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi
1. Get a token
In FC: Settings → Tagging → GPU agent → Generate token (or Rotate). Copy it.
2. Pull (CI publishes it alongside the web/ml images)
docker pull git.fabledsword.com/bvandeusen/fabledcurator-agent:latest
Local build for development instead:
docker build -t fc-gpu-agent agent/
3. Run (on the machine with the GPU)
docker run --rm --gpus all -p 8770:8770 \
-e FC_URL=http://curator.traefik.internal \
-e FC_TOKEN=<paste-the-token> \
-v fc-agent-models:/models \
git.fabledsword.com/bvandeusen/fabledcurator-agent:latest
Then open http://localhost:8770 — the control page. Click Start to begin
draining the queue; Pause/Stop to yield the GPU. The -v fc-agent-models
volume caches the downloaded ONNX models so restarts are fast.
Kick off a backfill from FC (GPU agent card → Queue character embedding), then watch the queue counts on the control page (or FC's card) drain.
Config (env)
| var | default | meaning |
|---|---|---|
FC_URL |
http://localhost:8000 |
FC base URL |
FC_TOKEN |
— | the bearer token (required) |
AGENT_ID |
desktop-agent |
identifies this agent's leases |
BATCH_SIZE |
4 |
jobs leased per round (still processed one at a time) |
CCIP_MODEL |
imgutils default | CCIP model name |
DETECTOR_LEVEL |
m |
person-detector size: n < s < m < x |
POLL_IDLE_SECONDS |
10 |
wait between empty leases |
⚠️ Verify on first run
This part can't be CI-tested (no GPU/models in CI), so confirm against your
installed dghs-imgutils (pip show dghs-imgutils) — see fc_agent/models.py:
imgutils.detect.detect_person(image, level=...)returns[((x0,y0,x1,y1), label, score), ...].imgutils.metrics.ccip_extract_feature(image, model=...)returns a vector (768-d for caformer). If you want the F1-0.94 variant, setCCIP_MODEL=ccip-caformer_b36-24(verify the exact string in imgutils).
If FC's matcher under/over-fires, tune the cosine threshold in
backend/app/services/ml/ccip.py (DEFAULT_SIM_THRESHOLD) and use
GET /api/ccip/overview + /api/ccip/images/<id> to spot-check.
CPU fallback
Swap onnxruntime-gpu → onnxruntime in requirements.txt and drop --gpus all
to grind it slowly on the server instead. Same agent, no card.