"""Agent config, all from env (the control container is configured at run).""" # Lazy annotations so the `from_env(cls) -> Config` self-reference is a string, # not evaluated at class-definition time — otherwise it NameErrors on the agent's # Python 3.10 (CI lints on 3.14, where PEP 649 hides this). from __future__ import annotations import os from dataclasses import dataclass def _bool_env(name: str, default: str = "") -> bool: """A boolean env var — present + truthy ('1'/'true'/'yes') → True.""" return os.environ.get(name, default).lower() in ("1", "true", "yes") @dataclass class Config: fc_url: str # base URL of the FabledCurator web service token: str # the bearer token from Settings → Tagging → GPU agent agent_id: str # identifies this agent's leases batch_size: int # jobs a worker leases per round concurrency: int # INITIAL parallel workers (tunable live from the UI) ccip_model: str # imgutils CCIP model name ("" → imgutils default) detector_level: str # imgutils person-detector level: n|s|m|x poll_idle_seconds: float # wait between empty leases embed_dtype: str # torch dtype for the crop embedder: float16|float32 embed_model_override: str # force a SigLIP-family model ("" → use the one # the server announces in the lease) auto_start: bool # start the worker pool on boot (so a container restart # resumes processing without anyone clicking Start) auto_scale: bool # autoscale the worker count (throughput hill-climb) # Crop PROPOSERS (extra YOLO detectors that say where to crop). Each weight # spec is an ultralytics name | http(s) URL | "hf_repo::file" ("" = off). person_weights: str # general COCO person detector (Western/realistic figs) person_conf: float anatomy_weights: str # booru_yolo anime/furry/NSFW components anatomy_conf: float panel_weights: str # comic-panel detector panel_conf: float max_components: int # cap anatomy component crops per frame max_panels: int # cap panel crops per frame max_figures: int # cap figure boxes per frame (each = a CCIP call + crop) max_regions: int # hard cap on total regions per JOB (submit-size backstop) dedupe_iou: float # crops overlapping >= this (same kind) are near-dupes, # dropped before the embed; >=1.0 disables it frame_dedupe_distance: int # video frames whose dHash differs by < this many # bits are near-duplicates, dropped before detect; # higher keeps more frames, 0 disables ffmpeg_timeout: float # hard ceiling (s) for ffmpeg-from-URL video sampling; # generous so a SLOW media link still completes bandwidth_limit_mb_s: float # aggregate download cap in MEGABYTES/s across # all downloaders + video streams (0 = unlimited); # tunable live from the agent UI @classmethod def from_env(cls) -> Config: return cls( fc_url=os.environ.get("FC_URL", "http://localhost:8000").rstrip("/"), token=os.environ.get("FC_TOKEN", ""), agent_id=os.environ.get("AGENT_ID", "desktop-agent"), batch_size=int(os.environ.get("BATCH_SIZE", "4")), concurrency=int(os.environ.get("CONCURRENCY", "1")), ccip_model=os.environ.get("CCIP_MODEL", ""), detector_level=os.environ.get("DETECTOR_LEVEL", "m"), poll_idle_seconds=float(os.environ.get("POLL_IDLE_SECONDS", "10")), embed_dtype=os.environ.get("SIGLIP_DTYPE", "float16"), embed_model_override=os.environ.get("EMBED_MODEL_NAME", ""), auto_start=_bool_env("AUTO_START"), auto_scale=_bool_env("AUTO_SCALE", "true"), person_weights=os.environ.get("PERSON_WEIGHTS", "yolo11n.pt"), person_conf=float(os.environ.get("PERSON_CONF", "0.35")), anatomy_weights=os.environ.get("ANATOMY_WEIGHTS", ""), anatomy_conf=float(os.environ.get("ANATOMY_CONF", "0.30")), panel_weights=os.environ.get("PANEL_WEIGHTS", ""), panel_conf=float(os.environ.get("PANEL_CONF", "0.30")), max_components=int(os.environ.get("MAX_COMPONENTS", "8")), max_panels=int(os.environ.get("MAX_PANELS", "8")), max_figures=int(os.environ.get("MAX_FIGURES", "8")), max_regions=int(os.environ.get("MAX_REGIONS", "128")), dedupe_iou=float(os.environ.get("DEDUPE_IOU", "0.85")), frame_dedupe_distance=int(os.environ.get("FRAME_DEDUPE_DISTANCE", "8")), ffmpeg_timeout=float(os.environ.get("FFMPEG_TIMEOUT", "1200")), # Default 8 MB/s (~64 Mbit/s): ~20% of the measured ~300 Mbit/s home # WiFi, so browsing stays snappy while the agent works — yet MORE # sweep throughput than the self-inflicted congestion collapse this # replaces (2026-07-02: 8 unthrottled downloaders bufferbloated the # link to ~1-1.5 MB/s per stream, browser included). Raise it (or 0) # from the agent UI on wired/faster networks. bandwidth_limit_mb_s=float(os.environ.get("BANDWIDTH_LIMIT_MB_S", "8")), )