Files
qwen3-6-lora/data/raw/sanitized/plans/puedes-leer-el-plan-keen-gray.md
T

17 KiB

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 <agente>".

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 <agente>".

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 (<<SPARK_PASSWORD>>, <<BEARER_TOKEN_1>>, 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/<bucket>.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 <think>, 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, <tool_call><function=...> 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.