Files
FabledCurator/agent/README.md
T
bvandeusenandClaude Opus 5 2677ce020c
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 3s
extension / lint (push) Successful in 18s
CI / frontend-build (push) Successful in 24s
CI / backend-lint-and-test (push) Successful in 32s
Build images / sign-extension (push) Successful in 2s
Build images / build-agent (push) Successful in 5s
Build images / build-web (push) Successful in 5s
Build images / smoke-web (push) Successful in 26s
Build images / promote (push) Skipped
CI / integration (push) Successful in 2m11s
fix: the ml artifact was in a second list I never grepped for (4311)
ac70f2a removed the `ml` image but CI went red on six tests:
`tests/test_artifact_identity.py` parametrises over its own
`ARTIFACTS = ("web", "ml", "agent", "extension")`, and every case now hits
the dispatch guard that same commit added.

My miss, and a specific one. I grepped for `fabledcurator-ml` and `ML_PATHS`
and called the survey done — but the artifact is also named as a bare `"ml"`,
which neither pattern finds. Rule 90 (grep pinned tests when changing a
shared symbol) was surfaced to me while I was making the change and I ran a
narrower sweep than it asks for. Lesson #4275 names the shape exactly: an
absence claim is only as good as the search behind it, and a grep that
matched nothing looks identical to a grep that asked the wrong question.

The re-run was done by value, not by name: every occurrence of a bare `ml` in
the repo, then filtering. That distinguishes the two things the token means —
the celery LANE `ml` and the `backend/app/services/ml` package both stay and
account for nearly every hit; only the IMAGE name went. Worth stating in the
test, since the next person to grep will hit the same ambiguity.

Also swept the prose the first pass left describing the old pipeline: "Four
artifacts" (README, ci-requirements), "leaves web and ml alone" (×5 in
build.yml, plus both docs), "CI publishes it alongside the web/ml images"
(agent/README). The run 4896 build-time measurements keep their `ml` number —
that was measured when ml was a real build — with a note saying the name has
since gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
2026-09-23 08:32:49 -04:00

72 lines
3.0 KiB
Markdown

# 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:
```sh
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 image)
```sh
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)
```sh
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, set
`CCIP_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.