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 hay02_dump_mcp_schemas.pycomo archivo separado, los schemas se volcaron directo desde la sesión). Estilo consistente: docstring inicial, constantesUPPER_CASEen vez deargparse, sinlogging(soloprintcon tags[...]), JSONL conensure_ascii=False+flush()inmediato, manejo de errores por contador sin abortar el batch completo.data/raw/replay.jsonltiene exactamente{"messages": [{"role":"user","content":...}, {"role":"assistant","content":...,"reasoning_content":...}], "meta": {"temperature":...,"finish_reason":...}}— 2 turnos, sintools/tool_calls. Los 5data/schemas/*.jsonson[{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-orchestrator719 líneas/~35 reglas duras,aleleba-pr261 líneas/~11 reglas en formato### NEVER/### ALWAYS,docmost-context164 líneas casi sin reglas duras,spark-ssh70 líneas/3 reglas,web-ui-test179 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 reglasNUNCA/SIEMPRE/OBLIGATORIOde cada una — este es el material crudo para los buckets de adherencia a skills (~450) y delegación a subagentes (~180).agent-orchestratordomina 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 repoqwen3-6-lora) tienepython3.10.12yjinja2 3.1.6, pero nogitleaksnidetect-secretsinstalados, 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.pyy05_build_dataset.pycorren localmente aquí (no necesitan GPU ni modelo — "Estado de vLLM: no aplica" según el plan principal);06_validate_dataset.pynecesitaapply_chat_template(..., return_assistant_tokens_mask=True)con el tokenizer real, así que debe correr dentro del contenedorqwen-lora-trainen spark (vía skillspark-ssh+docker exec), para usar exactamentetransformers==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.pyencuentra un problema de fondo (masking, longitud, chat template). La skill held-out (0 ejemplos de entrenamiento la mencionan, en ningún bucket) esspark-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/masteral 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)
AGENT_NAME = agente-fase2-dataset. Crear.worktrees/agente-fase2-dataset/+ branchagente-fase2-datasetenqwen3-6-lora/.- 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íadocmost-reporter), capturandopageId/spaceIdantes de lanzar tmux. - Configurar
.claude/settings.local.jsondel worktree con los permisos necesarios:Bash,Read,Edit,Write,Glob,Grep,Task,mcp__docmost,mcp__gitea(para el PR), y la skillspark-ssh(para el paso de validación en el contenedor). No necesitamcp__penpot/mcp__github-personal/mcp__atlassianen vivo — los schemas ya están volcados adata/schemas/*.jsonen el repo. - Escribir
TASK.mdcon 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 de04_sanitize.py, después de cada bucket de seeds, después de05_build_dataset.py, después de06_validate_dataset.py). - Lanzar la sesión tmux (modo interactivo, prompt corto +
TASK.mdcompleto), capturarSESSION_IDdel nombre del archivo.jsonlde transcript, actualizar el Docmost con el Session ID real, confirmar al usuario que arrancó sin prompts pendientes. - 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-secretslocalmente (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 parasshpassen 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-secretso 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 (insertChildnoappendChild, orden invertido dechildrenen flex, re-fijargrowTypetrasresize(), ausencia deimport_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(menosspark-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 invocarmailer/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 deagent-orchestratory 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.jsonlcomo ú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)
- 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 branchagente-fase2-dataset. 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.pr-shipper: abre el PR (título/cuerpo en inglés, vía skillaleleba-pr) desdeagente-fase2-datasethaciamaster. Nunca mergea.ci-developer: espera los checks (si el repo tiene CI configurado en Gitea Actions; si no hay ninguno configurado, lo reporta y continúa).docmost-reporter: checkpoint final en la subpágina propia del agente — resumen ejecutivo, conteos reales por bucket, resultado de06_validate_dataset.py(incluyendo cualquier ejemplo filtrado por longitud o secreto sobreviviente), decisiones tomadas (volumen v1 reducido, held-outspark-ssh), link al PR.mailer: envía el correo de finalización con ese mismo resumen.- 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 de04_sanitize.pytermina en exit 0 (no sobrevive ningún patrón conocido).data/raw/seeds/*.jsonlexisten para los 6 buckets, ninguno mencionaspark-ssh.data/train.jsonl/data/eval.jsonlexisten, aprox. 1500-1700 líneas combinadas (v1 nuevo + replay), JSON válido línea por línea.06_validate_dataset.pycorre dentro deqwen-lora-trainen spark y termina reportando 0 excepciones y 0 secretos sobrevivientes (los filtrados por longitud, si los hay, quedan documentados, no ocultos).vllm-qwen36sigueUp ... (healthy)— esta fase no lo toca en ningún momento.- Hay un PR abierto desde
agente-fase2-datasethaciamaster(nunca mergeado por el agente). - Llega el correo de
mailercon 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.