# ComfyUI Skill — Recetas versionadas de generación reutilizables Tag: `comfyui-skill`. Grupo para tratar una configuración de generación de ComfyUI como una **skill**: una receta versionada en disco (checkpoint + LoRAs + params + scaffold de prompt + bloques de post-proceso) que se guarda una vez y se reproduce a un workflow concreto cambiando solo el *subject*. Es la doctrina del issue 0087 aplicada a la generación de imágenes: el registry crece **promoviendo configuraciones que funcionan a recetas reutilizables**, no reescribiendo el grafo de nodos cada vez. Construye sobre el grupo [`comfyui`](comfyui.md) (los builders puros de workflow y el ciclo submit/wait). Una skill no es un workflow: es la *receta* que compila a uno. Filtro MCP: `mcp__registry__fn_search query="" tag="comfyui-skill"`. ## Qué es una skill Una receta vive en `~/ComfyUI/skills_library//` y la manipulan las funciones de este grupo: ``` ~/ComfyUI/skills_library/ INDEX.md # índice regenerado de todas las skills / recipe.json # la receta actual versions/vN.json # snapshot inmutable de cada save (N incremental) growth_log.jsonl # bitácora append-only de cada save exports/ # plantillas de workflow exportadas samples/ # imágenes de muestra ``` ### Schema de `recipe.json` (canónico) ```json { "schema_version": 1, "slug": "portrait_cinematic_sdxl", "version": "1.0.0", "title": "Retrato cinematográfico SDXL", "base_workflow": "txt2img", "checkpoint": "juggernaut_xl_v11.safetensors", "loras": [{"name": "add_detail.safetensors", "strength_model": 0.6, "strength_clip": 0.6}], "params": {"steps": 30, "cfg": 5.5, "sampler_name": "dpmpp_2m", "scheduler": "karras", "width": 832, "height": 1216, "denoise": 1.0}, "prompt_scaffold": {"positive": "cinematic portrait of {subject}, sharp focus", "negative": "blurry, lowres", "trigger_words": []}, "blocks": [{"type": "facedetailer", "params": {"denoise": 0.45}}, {"type": "hires_fix", "params": {"upscale_by": 1.5, "denoise": 0.4}}], "score_mean": 0.0, "score_n": 0, "provenance": {"source": "manual", "nsfw": false}, "export_template_path": "exports/portrait_cinematic_sdxl.template.json" } ``` `base_workflow` ∈ {`txt2img`, `flux`, `sdxl_refiner`} (las bases que se generan desde un *subject* de texto). `blocks[].type` ∈ {`facedetailer`, `hires_fix`}. ## Funciones del grupo | ID | Firma corta | Qué hace | Purity | |---|---|---|---| | [comfyui_build_skill_workflow_py_ml](../../python/functions/ml/comfyui_build_skill_workflow.md) | `build_skill_workflow(recipe, subject, *, seed=0) -> dict` | Compila una receta a un workflow en API format: despacha al builder base, sustituye `{subject}` + trigger_words, encadena LoRAs y aplica los blocks en orden. `SkillWorkflowError` si la base es desconocida o requiere imagen. | **pura** | | [comfyui_inject_hires_fix_py_ml](../../python/functions/ml/comfyui_inject_hires_fix.md) | `comfyui_inject_hires_fix(workflow, *, upscale_by=1.5, denoise=0.4, steps=20, ...) -> dict` | Inyecta una 2ª pasada hires-fix (UpscaleModelLoader + UltimateSDUpscale) sobre un workflow ya construido, repuntando el SaveImage. Versión encadenable-sobre-dict del builder hermano. | **pura** | | [comfyui_save_skill_py_ml](../../python/functions/ml/comfyui_save_skill.md) | `comfyui_save_skill(recipe, *, library_dir=None) -> dict` | Valida el schema mínimo y escribe `recipe.json` + snapshot `versions/vN.json` + growth_log + INDEX.md. No muta la receta (round-trip con load). | impura | | [comfyui_load_skill_py_ml](../../python/functions/ml/comfyui_load_skill.md) | `comfyui_load_skill(slug, *, version=None, library_dir=None) -> dict` | Lee `recipe.json` (actual) o un snapshot `versions/vN.json`. Slug/versión inexistente → `{ok:False}` sin lanzar. | impura | | [comfyui_list_skills_py_ml](../../python/functions/ml/comfyui_list_skills.md) | `comfyui_list_skills(*, library_dir=None, include_nsfw=False) -> dict` | Lista las skills con slug/title/base_workflow/version/score/nsfw/n_versions. Oculta NSFW por defecto. | impura | | [ask_llm_vision_py_core](../../python/functions/core/ask_llm_vision.md) | `ask_llm_vision(prompt, image_path='', *, image_b64='', media_type='', model='claude-opus-4-8', ...) -> dict` | Pregunta multimodal (imagen + texto) al modelo via API directa de Anthropic (grupo `claude-direct`). Útil para **puntuar** el PNG de una skill y alimentar `score_mean`. | impura | | [comfyui_generate_with_skill_oneshot_py_pipelines](../../python/functions/pipelines/comfyui_generate_with_skill_oneshot.md) | `generate_with_skill_oneshot(slug, subject, *, server='127.0.0.1:8188', dest=None, seed=0, judge=True, recipe_patch=None, ...) -> dict` | One-shot del bucle: carga la skill, la compila para el `subject`, encola, espera, descarga el PNG y (si `judge`) lo puntúa con el panel `comfyui-judge`, acumulando el score en la media. `recipe_patch` prueba una variante en memoria sin guardar. | pipeline (impura) | | [comfyui_update_skill_score_py_ml](../../python/functions/ml/comfyui_update_skill_score.md) | `comfyui_update_skill_score(slug, new_score, *, library_dir=None) -> dict` | Acumula el score de un juicio en `score_mean`/`score_n` por media incremental, reescribiendo `recipe.json` en sitio (sin snapshot ni growth_log). | impura | | [comfyui_bump_skill_version_py_ml](../../python/functions/ml/comfyui_bump_skill_version.md) | `comfyui_bump_skill_version(slug, change, *, score_before, score_after, judge_run_id=None, recipe_patch=None, force=False, ...) -> dict` | Promueve una versión nueva **solo si el score sube** (gate objetivo): snapshot `versions/vN.json` + aplica `recipe_patch` + sube el semver + línea en `growth_log`. Gate bloquea si no mejora. | impura | `build_skill_workflow` compone los builders del grupo [`comfyui`](comfyui.md): `comfyui_build_txt2img_workflow`, `comfyui_build_flux_workflow`, `comfyui_build_sdxl_refiner_workflow`, `comfyui_inject_lora`, `comfyui_build_facedetailer_workflow` y `comfyui_inject_hires_fix`. ## Ejemplo canónico end-to-end (receta → workflow → PNG → score) Guardar una skill, cargarla, compilarla a un workflow para un sujeto, encolarla y puntuar el resultado con visión. Requiere el server ComfyUI en `127.0.0.1:8188` y los modelos de la receta instalados. ```python import sys, os sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions")) from ml.comfyui_save_skill import comfyui_save_skill from ml.comfyui_load_skill import comfyui_load_skill from ml.comfyui_build_skill_workflow import build_skill_workflow from ml.comfyui_submit_workflow import comfyui_submit_workflow from ml.comfyui_wait_result import comfyui_wait_result from ml.comfyui_fetch_output_image import comfyui_fetch_output_image from core.ask_llm_vision import ask_llm_vision # 1. Definir y guardar la skill (una vez). recipe = { "schema_version": 1, "slug": "portrait_cinematic_sdxl", "version": "1.0.0", "title": "Retrato cinematográfico SDXL", "base_workflow": "txt2img", "checkpoint": "dreamshaper_8.safetensors", "loras": [{"name": "add_detail.safetensors", "strength_model": 0.6, "strength_clip": 0.6}], "params": {"steps": 28, "cfg": 6.0, "sampler_name": "dpmpp_2m", "scheduler": "karras"}, "prompt_scaffold": {"positive": "cinematic portrait of {subject}, sharp focus", "negative": "blurry, lowres", "trigger_words": []}, "blocks": [{"type": "facedetailer", "params": {"denoise": 0.45}}], "score_mean": 0.0, "score_n": 0, "provenance": {"source": "manual", "nsfw": False}, } comfyui_save_skill(recipe) # ~/ComfyUI/skills_library/portrait_cinematic_sdxl/ # 2. Cargar + compilar a un workflow para un sujeto concreto. recipe = comfyui_load_skill("portrait_cinematic_sdxl")["recipe"] wf = build_skill_workflow(recipe, "a woman with red hair", seed=42) # 3. Encolar y esperar el PNG (camino headless del grupo comfyui). pid = comfyui_submit_workflow(wf)["prompt_id"] outputs = comfyui_wait_result(pid)["outputs"] img = comfyui_fetch_output_image(outputs[0]["filename"], dest_dir="/tmp")["path"] # 4. Puntuar el resultado con visión (alimenta el bucle de scoring de la skill). verdict = ask_llm_vision( "Puntúa de 0 a 10 el realismo de este retrato. Responde solo el número.", image_path=img, model="claude-opus-4-8", ) print(verdict["text"]) ``` El paso "guardar la receta" se hace una sola vez; a partir de ahí cada generación es `load → build → submit`, cambiando solo el `subject` y la `seed`. ## Bucle de mejora (skill → genera → juzga → bump) La doctrina del issue 0087 cerrada en un lazo: una skill **no crece inflando la receta a ciegas, crece registrando mejoras medibles**. El juez (no el humano) decide qué se promueve. ``` ┌─────────────────────────────────────────────────────────────────┐ │ generate_with_skill_oneshot(slug, subject, judge=True) │ │ load → build → submit → wait → fetch → judge → score_mean │ ← canónica └─────────────────────────────────────────────────────────────────┘ │ score_before = score de la receta vigente ▼ ┌─────────────────────────────────────────────────────────────────┐ │ generate_with_skill_oneshot(..., recipe_patch={params:{...}}) │ ← variante (no guarda score) │ misma seed, un cambio plausible → judge.score = score_after │ └─────────────────────────────────────────────────────────────────┘ │ ▼ GATE objetivo comfyui_bump_skill_version(slug, change, score_before, score_after, judge_run_id=...) │ score_after > score_before ? ├── sí → promueve: versions/vN.json (snapshot) + recipe_patch + semver↑ + growth_log └── no → {ok:False} — NO se promueve (la variante se descarta) ``` Pasos concretos: 1. **Genera la canónica** con `judge=True`. El panel `comfyui-judge` emite un `score` y el pipeline lo acumula en `score_mean`/`score_n` de la skill (vía `comfyui_update_skill_score`). Ese score es el `score_before`. 2. **Genera una variante** con `recipe_patch` (p.ej. `{"params": {"steps": 32}}`) y la **misma seed**. El patch se aplica en memoria, NO se guarda, y su score NO contamina la media. Su `judge.score` es el `score_after`, y su `judge_run_id` es la evidencia. 3. **Promueve con el gate**: `comfyui_bump_skill_version` aplica el patch a `recipe.json`, sube el semver y deja una línea en `growth_log.jsonl` **solo si `score_after > score_before`**. Si no mejora, devuelve `{ok:False}` y la receta se queda como estaba. El gate es objetivo: lo decide el número del juez, no quien lanza la generación. Así `versions/` y `growth_log` reflejan **versiones de receta con mejora demostrada**, mientras `score_mean` es la telemetría de calidad media de la versión vigente. ## Fronteras - **No genera ni descarga modelos**: una skill referencia checkpoints/LoRAs por nombre; deben estar ya instalados en ComfyUI (`comfyui_download_model`, otro flujo). `build_skill_workflow` es puro y no valida contra el servidor — usa `comfyui_validate_workflow` antes de encolar si dudas. - **`base_workflow` solo de texto**: `txt2img`, `flux`, `sdxl_refiner`. Las bases que parten de una imagen (`img2img`, `inpaint`, `controlnet`) lanzan `SkillWorkflowError`; para esas, monta el workflow con los builders del grupo `comfyui` directamente. - **`blocks` soportados**: `facedetailer` y `hires_fix`. Otros post-procesos (IPAdapter, multi-ControlNet) se añaden creando su función-inyector hermana y registrándola en el dispatcher de `build_skill_workflow`. - **El juicio (`comfyui-judge`) vive en su grupo**: este grupo lo *consume* (vía `generate_with_skill_oneshot` con `judge=True`), pero el panel multi-juez —estético + CLIP + LLM-vision— se documenta en [`comfyui-judge`](comfyui-judge.md). Aquí solo se acumula su `score` en `score_mean` (`comfyui_update_skill_score`) y se usa como gate del bump. - **El bump solo sube versiones, no genera ni juzga**: `comfyui_bump_skill_version` aplica el patch y registra la mejora; generar la imagen y puntuarla es trabajo del pipeline + el panel-juez. Una variante que no supera a la vigente se descarta sola (el gate la rechaza). - **La librería es metadata local**: vive bajo `~/ComfyUI/skills_library` (no toca el venv ni los modelos en disco). No tiene repo propio ni se indexa — es estado vivo, como un `operations.db`. - **Las funciones impuras del grupo** (save/load/list, ask_llm_vision) no llevan unit tests por diseño (I/O de disco / red); `build_skill_workflow` e `inject_hires_fix` son puras y sí tienen tests de estructura offline (`python/functions/ml/tests/test_comfyui_build_skill_workflow.py`, `test_comfyui_inject_hires_fix.py`).