# Implementar Fase 2 del plan LoRA de Qwen3.6 (construcción del dataset) — v1 a volumen reducido, vía agente en background ## Decisión de ejecución El usuario pidió explícitamente que esta fase la ejecute **un agente autónomo en background usando la skill `agent-orchestrator`** (mismo patrón que la Fase 1: tmux + git worktree + branch propia + Docmost + PR, nunca merge directo). A diferencia de la Fase 1 (una tarea puramente operativa: correr un script y esperar), esta es una tarea de **autoría de contenido** — por eso el agente, dentro de su propio worktree, puede spawnear sus propios subagentes `developer` en paralelo (uno por bucket, con listas de archivos disjuntas) para escribir los seeds, y cierra con el flujo estándar de la skill: commit propio → `qa-validator` omitido (tarea backend/data, no toca UI web) → `pr-shipper` abre PR (nunca mergea) → `ci-developer` espera CI → `docmost-reporter` documenta en su propia subpágina → `mailer` notifica por correo → se queda en turno esperando `"apruebo "`. Lo que hace **la sesión madre (yo, ahora)** en este plan es solo el **lanzamiento**: crear el worktree/branch, la subpágina de Docmost del agente, el `TASK.md` con todo el diseño de contenido de abajo, y arrancar la sesión tmux. La ejecución del contenido (sanitize → autoría de seeds → build → validate → commit → PR) la hace el agente de forma autónoma; la integración final a la página principal de Docmost, el merge (con validación explícita del usuario) y el archivado del agente quedan para un turno posterior, cuando el usuario diga `"apruebo "`. ## Contexto Con la Fase 0 (infraestructura) y la Fase 1 (schemas de los 5 MCPs + `data/raw/replay.jsonl` con 720 ejemplos) completadas y ya commiteadas en `master`, el usuario pidió avanzar con la Fase 2: construir el dataset de entrenamiento (`scripts/04_sanitize.py`, `05_build_dataset.py`, `06_validate_dataset.py`). A diferencia de las Fases 0/1 (operativas: instalar, verificar, volcar schemas, pedirle prompts al modelo de producción), la Fase 2 es fundamentalmente un trabajo de **autoría de contenido**: hay que enseñarle al modelo aprox. 1800-2300 ejemplos nuevos (Penpot, los otros 4 MCPs, adherencia a skills, delegación a subagentes, negativos/no-tool, manejo de errores) sobre reglas que **el modelo base todavía no sigue** (esa es la razón de ser del LoRA). Por eso estos ejemplos se autoran directamente (yo + subagentes en paralelo, grounded en los schemas/reglas reales ya documentados en `data/schemas/*.json`, la subpágina de Penpot, y los 5 `SKILL.md`/7 agentes `~/.claude/agents/*.md` reales) en vez de muestrear al modelo de producción, que es lo que sí tenía sentido para el replay (Fase 1) pero no aquí. Se investigó antes de planificar: - **Inventario de código/convenciones existentes**: repo limpio, 5 scripts previos (`00`, `01`, `03` — no hay `02_dump_mcp_schemas.py` como archivo separado, los schemas se volcaron directo desde la sesión). Estilo consistente: docstring inicial, constantes `UPPER_CASE` en vez de `argparse`, sin `logging` (solo `print` con tags `[...]`), JSONL con `ensure_ascii=False` + `flush()` inmediato, manejo de errores por contador sin abortar el batch completo. `data/raw/replay.jsonl` tiene exactamente `{"messages": [{"role":"user","content":...}, {"role":"assistant","content":...,"reasoning_content":...}], "meta": {"temperature":...,"finish_reason":...}}` — 2 turnos, sin `tools`/`tool_calls`. Los 5 `data/schemas/*.json` son `[{name, description, parameters}, ...]` (154 tools totales: Penpot 4, Gitea 53, GitHub-personal 43, Docmost 17, Atlassian 37). - **Inventario de skills/agentes reales**: confirmado que son exactamente 5 skills (`agent-orchestrator` 719 líneas/~35 reglas duras, `aleleba-pr` 261 líneas/~11 reglas en formato `### NEVER`/`### ALWAYS`, `docmost-context` 164 líneas casi sin reglas duras, `spark-ssh` 70 líneas/3 reglas, `web-ui-test` 179 líneas/reglas de headless) y 7 agentes (`developer`, `code-reviewer`, `qa-validator`, `pr-shipper`, `ci-developer`, `docmost-reporter`, `mailer`) bajo `~/.claude/agents/`. Se extrajeron verbatim las reglas `NUNCA/SIEMPRE/OBLIGATORIO` de cada una — este es el material crudo para los buckets de adherencia a skills (~450) y delegación a subagentes (~180). `agent-orchestrator` domina el corpus (~65% de los bytes de skills) y es la fuente más rica para ambos buckets. - **Entorno de ejecución local**: este entorno de desarrollo (donde vive `~/.claude/skills`, `~/.claude/agents`, `~/.claude/plans`, y el propio repo `qwen3-6-lora`) tiene `python3.10.12` y `jinja2 3.1.6`, pero **no** `gitleaks` ni `detect-secrets` instalados, y **no** tiene el checkpoint del modelo (vive solo en disco local de spark, fuera del NFS, por diseño — ver Fase 0). Esto determina dónde corre cada script: `04_sanitize.py` y `05_build_dataset.py` corren **localmente aquí** (no necesitan GPU ni modelo — "Estado de vLLM: no aplica" según el plan principal); `06_validate_dataset.py` necesita `apply_chat_template(..., return_assistant_tokens_mask=True)` con el tokenizer real, así que debe correr **dentro del contenedor `qwen-lora-train` en spark** (vía skill `spark-ssh` + `docker exec`), para usar exactamente `transformers==5.14.1` (la misma versión ya verificada en Fase 0), no una versión distinta instalada localmente. - **Decisión de volumen y skill held-out ya confirmadas con el usuario**: se arranca con un **v1 a volumen reducido** (aprox. 800-1000 ejemplos nuevos, proporcional entre buckets) para validar el pipeline completo (sanitize→build→validate) antes de invertir en el volumen completo (aprox. 2500-3000 del plan original) — evita rehacer trabajo si `06_validate_dataset.py` encuentra un problema de fondo (masking, longitud, chat template). La skill held-out (0 ejemplos de entrenamiento la mencionan, en ningún bucket) es **`spark-ssh`** — la más autocontenida y menos cruzada con delegación a subagentes. - **Todo lo generado se versiona en el repo**, igual que en Fase 0/1: seeds, copias sanitizadas de skills/agentes/plans, y los JSONL finales se commitean y pushean a `origin/master` al terminar. **Resultado esperado de este plan**: `data/train.jsonl` y `data/eval.jsonl` (v1, aprox. 800-1000 ejemplos nuevos + los 700 de replay), validados de punta a punta por `06_validate_dataset.py` dentro del contenedor de training, commiteados y pusheados, con Fase 2 marcada "En proceso" en Docmost (no "Completada" — falta la pasada de volumen completo, que se hace en un plan/turno posterior una vez revisado el v1 con el usuario). --- ## Plan de ejecución ### 0. Lanzamiento del agente (lo hace la sesión madre, en este turno) 1. `AGENT_NAME = agente-fase2-dataset`. Crear `.worktrees/agente-fase2-dataset/` + branch `agente-fase2-dataset` en `qwen3-6-lora/`. 2. Crear la fila del agente en la tabla "Agentes Activos" de la página Docmost `019fa739-ae2b-7be3-9bd0-e5363a1b047d`, y su subpágina dedicada (checkpoints de progreso vía `docmost-reporter`), capturando `pageId`/`spaceId` **antes** de lanzar tmux. 3. Configurar `.claude/settings.local.json` del worktree con los permisos necesarios: `Bash`, `Read`, `Edit`, `Write`, `Glob`, `Grep`, `Task`, `mcp__docmost`, `mcp__gitea` (para el PR), y la skill `spark-ssh` (para el paso de validación en el contenedor). No necesita `mcp__penpot`/`mcp__github-personal`/`mcp__atlassian` en vivo — los schemas ya están volcados a `data/schemas/*.json` en el repo. 4. Escribir `TASK.md` con el contenido completo de las secciones 1-5 de abajo (el diseño de sanitización, autoría de seeds, ensamblado, validación y cierre), más el recordatorio explícito de que el agente **nunca mergea** y que la subpágina de Docmost se actualiza en cada checkpoint importante (después de `04_sanitize.py`, después de cada bucket de seeds, después de `05_build_dataset.py`, después de `06_validate_dataset.py`). 5. Lanzar la sesión tmux (modo interactivo, prompt corto + `TASK.md` completo), capturar `SESSION_ID` del nombre del archivo `.jsonl` de transcript, actualizar el Docmost con el Session ID real, confirmar al usuario que arrancó sin prompts pendientes. 6. Monitoreo pasivo posterior (no cada 30s): revisar cuando haya una razón concreta (el usuario pregunta, o llega el correo de `mailer`). ### Contenido de `TASK.md` — lo que ejecuta el agente de forma autónoma, dentro de su propio worktree/branch ### 1. `scripts/04_sanitize.py` — scrubbing de secretos (corre localmente, dentro del worktree del agente) - Fuentes: los 5 `SKILL.md`, los 7 `~/.claude/agents/*.md`, y los 66 `~/.claude/plans/*.md` (628K) — **no** transcripts (`~/.claude/projects/*.jsonl`), ya descartados como fuente en el hallazgo de Fase 0 ("casi vacíos de uso de MCPs"). - `pip install detect-secrets` localmente (paquete puro Python, sin binario/daemon) como red de seguridad genérica (entropía alta, formatos conocidos de AWS/JWT/etc.) — se declara explícitamente aquí para que quede cubierto por la aprobación de este plan, igual que se pidió permiso para `sshpass` en la Fase 0. - Además, lista de regex explícitas para patrones ya conocidos de este proyecto: `SPARK_PASSWORD`, `GMAIL_APP_PASSWORD`, el bearer token reusado (documentado como hallazgo de seguridad en el plan principal), emails reales, IPs/hostnames internos de spark. - Sustitución **estable** (no borrado): mismo secreto real → mismo placeholder semántico (`<>`, `<>`, etc.) vía un diccionario hash persistido *localmente y gitignoreado* (nunca commiteado, solo para debug). - Salida: `data/raw/sanitized/{skills,agents,plans}/...` (misma estructura relativa que el original). - **Gate**: el script termina con exit code ≠ 0 si `detect-secrets` o las regex explícitas encuentran algo en la salida *ya sanitizada* — falla cerrado, no adivina reemplazos para patrones no reconocidos. ### 2. Autoría de seeds por bucket (corre localmente, dentro del worktree, contenido nuevo) El agente escribe esto — puede spawnear sus propios subagentes `developer` en paralelo (uno por bucket, cada uno con una lista de archivos disjunta: solo su `data/raw/seeds/.jsonl`) para paralelizar la autoría, siempre grounded en el schema/reglas reales ya extraídas (no inventar). El commit de cada bucket lo hace el agente mismo (los `developer` nunca commitean, por regla general de `agent-orchestrator`). Formato final de entrenamiento (`messages` + `tools` cuando aplique + `tool_calls` estructurados como dict de `arguments`, nunca XML escrito a mano — según la Decisión de diseño #4 del plan principal): - **Penpot** (`data/schemas/penpot.json`, 4 tools + reglas no-obvias de la subpágina dedicada): seeds cubriendo cada regla documentada (`insertChild` no `appendChild`, orden invertido de `children` en flex, re-fijar `growType` tras `resize()`, ausencia de `import_image`/`filePath`, etc.), variando shapes/valores. - **Otros 4 MCPs** (gitea/github-personal/docmost/atlassian, 150 tools combinadas): seeds por tool/familia de tools, incluyendo explícitamente la regla no-obvia de formato de tablas de Docmost (separador GFM + una fila por línea) ya documentada en el plan principal. - **Adherencia a skills**: seeds basados en las reglas verbatim extraídas de las 5 `SKILL.md` (menos `spark-ssh`, held-out) — checklist inicial en el ``, auto-corrección a mitad de trayectoria, fidelidad a plantillas de reporte. - **Delegación a subagentes**: seeds basados en `agent-orchestrator` (los 7 pasos obligatorios antes de terminar, protocolo de concurrencia, cuándo invocar `mailer`/`docmost-reporter`) y los otros 6 agentes. - **Negativos/no-tool**: frases cercanas a triggers estrictos (ej. las frases exactas de activación de `agent-orchestrator`) que NO deben disparar ninguna skill/tool — el modelo debe responder en texto plano. - **Manejo de errores**: trayectorias donde una tool call falla (`"Tool execution failed: ..."`) y el modelo debe leerlo como texto y autocorregirse — basado en la tabla de 11 incidentes reales de `agent-orchestrator` y los bugs de Docmost (colapso de tablas, corrupción de tildes) ya documentados. ### 3. `scripts/05_build_dataset.py` — ensamblado (corre localmente) - Carga todos los `data/raw/seeds/*.jsonl` + `data/raw/replay.jsonl`. - Aplica variación determinística (`random.seed(42)` para reproducibilidad) — sustitución de valores desde los schemas reales + un set chico de parafraseos escritos a mano por intención — para llegar al volumen v1 (aprox. 800-1000 nuevos, repartidos proporcionalmente a los buckets del plan original: Penpot ~110, otros MCPs ~290, skills ~160, delegación ~65, negativos ~70, errores ~55). - Verifica que ningún seed mencione `spark-ssh` (assert del held-out). - Tagea cada ejemplo con `meta.bucket`, mezcla, separa 90/10 train/eval **estratificado por bucket** (para que eval no quede dominado por un solo bucket). - Escribe `data/train.jsonl` / `data/eval.jsonl`. ### 4. `scripts/06_validate_dataset.py` — validación (corre en el contenedor `qwen-lora-train` en spark, vía skill `spark-ssh`) - Por cada ejemplo: `tokenizer.apply_chat_template(messages, tools=..., tokenize=True, return_assistant_tokens_mask=True, return_dict=True)` — assert sin excepción, `` bien formado cuando corresponde, máscara de assistant no vacía y no cubre system/user/tool. - Filtra (no trunca) ejemplos que excedan 8192 tokens — reporta cuántos y de qué bucket (nunca silencioso). - Re-corre el gate de secretos sobre `train.jsonl`/`eval.jsonl` como última línea de defensa. - Imprime/reporta un resumen final por bucket (conteo, filtrados, excepciones). ### 5. Cierre del agente (flujo estándar de `agent-orchestrator`, nunca merge) 1. Commit final propio (nunca de un `developer`) de: `scripts/04_sanitize.py`, `05_build_dataset.py`, `06_validate_dataset.py`, `data/raw/sanitized/`, `data/raw/seeds/*.jsonl`, `data/train.jsonl`, `data/eval.jsonl` — en su propia branch `agente-fase2-dataset`. 2. `qa-validator`: **omitido explícitamente** — tarea backend/data pura, no toca ninguna interfaz web. Dejarlo dicho en el checkpoint de Docmost, no simplemente saltarlo en silencio. 3. `pr-shipper`: abre el PR (título/cuerpo en inglés, vía skill `aleleba-pr`) desde `agente-fase2-dataset` hacia `master`. **Nunca mergea.** 4. `ci-developer`: espera los checks (si el repo tiene CI configurado en Gitea Actions; si no hay ninguno configurado, lo reporta y continúa). 5. `docmost-reporter`: checkpoint final en la subpágina propia del agente — resumen ejecutivo, conteos reales por bucket, resultado de `06_validate_dataset.py` (incluyendo cualquier ejemplo filtrado por longitud o secreto sobreviviente), decisiones tomadas (volumen v1 reducido, held-out `spark-ssh`), link al PR. 6. `mailer`: envía el correo de finalización con ese mismo resumen. 7. El agente **no cierra su sesión** — se queda en turno esperando `"apruebo agente-fase2-dataset"` o una corrección. **Fuera del alcance de este plan** (se hace en un turno posterior, cuando el usuario apruebe): revisar el PR, mergear con validación explícita del usuario, integrar el resumen de la subpágina del agente a la página principal del plan (Fase 2 → "En proceso", con el resultado del v1 documentado y la decisión de volumen completo pendiente), y archivar el agente (mover fila a "Historial", borrar worktree/branch/subpágina) — exactamente el mismo procedimiento de FASE 4 de `agent-orchestrator` ya ejecutado al cerrar la Fase 1. --- ## Verificación **Del lanzamiento (esta sesión, ahora):** - `.worktrees/agente-fase2-dataset/` y la branch existen; la sesión tmux arrancó sin prompts de permisos pendientes. - La fila del agente en "Agentes Activos" tiene el Session ID real (no "pendiente") y su subpágina de Docmost existe. **Del trabajo del agente (verificable más tarde, sin polling activo):** - `data/raw/sanitized/` existe y el gate de `04_sanitize.py` termina en exit 0 (no sobrevive ningún patrón conocido). - `data/raw/seeds/*.jsonl` existen para los 6 buckets, ninguno menciona `spark-ssh`. - `data/train.jsonl`/`data/eval.jsonl` existen, aprox. 1500-1700 líneas combinadas (v1 nuevo + replay), JSON válido línea por línea. - `06_validate_dataset.py` corre dentro de `qwen-lora-train` en spark y termina reportando 0 excepciones y 0 secretos sobrevivientes (los filtrados por longitud, si los hay, quedan documentados, no ocultos). - `vllm-qwen36` sigue `Up ... (healthy)` — esta fase no lo toca en ningún momento. - Hay un PR abierto desde `agente-fase2-dataset` hacia `master` (nunca mergeado por el agente). - Llega el correo de `mailer` con el resumen final. - La subpágina de Docmost del agente documenta conteos reales por bucket y el resultado de la validación. - El agente sigue en turno, esperando `"apruebo agente-fase2-dataset"` — no cierra su sesión solo.