2026-09-21 — MVP: enrich que completa nodos (doctrina con LLM local)

Repo: ~/nef/second-brain, rama hermes-experiments. Sin commit todavía.

Qué es

npm run enrich deja de solo pegar el extracto más largo (el modo de falla conocido) y ahora completa el nodo a doctrina: línea curador + Mecanismo + Límite, escrita por LLM local desde las fuentes, con gates de calidad, provenance y resume. El draft queda en _enrich/<slug>.md + .meta.json; la adopción a content/nodes/ sigue siendo manual (regla del repo).

Cómo se corre

npm run enrich -- --report          # backlog: sin sources 186 · sources sin doctrina 140
npm run enrich -- --limit 15        # tanda (default: nodos sin sources)
npm run enrich -- --doctrine        # nodos con sources pero sin doctrina
npm run enrich -- --all --limit 20  # ambos backlogs
npm run enrich -- <slug> ...        # nodos explícitos
npm run enrich -- --dry --limit 15  # lista sin ejecutar
npm run enrich -- --no-llm          # fallback extract-only (sin GPU)
npm run enrich -- --selftest        # valida parse/gates sin Ollama
npm run enrich -- --model gemma4:latest

Pipeline por nodo

  1. Fuentes (existente, cacheado en data/enrich-cache/): Wikipedia (búsqueda + summary con hint por kind), arXiv (solo paper/experiment), docs oficiales curados.
  2. Doctrina: prompts/doctrine.md pide JSON {curator, mechanism, pitfall} a Ollama (format: json, think: false, num_ctx: 32768, temperature 0.3). El script compone el cuerpo con las etiquetas correctas según el idioma del nodo — el modelo ya no formatea, solo escribe.
  3. Gates (si falla, reintenta 1 vez y marca ok:false con razones): estructura (curador + Mecanismo/Límite), verbatim (ningún 12-grama compartido con las fuentes), longitud 80–350 palabras, idioma (coincide con el nodo), fuentes presentes.
  4. Salida: _enrich/<slug>.md (frontmatter original verbatim + sources sin duplicar) y _enrich/<slug>.meta.json (modelo, hash del prompt, checks, sources + hashes, palabras, intentos, duración). Log por corrida en _enrich/_run.jsonl. Los wikilinks los inserta el pipeline (nunca el modelo), así que no puede inventar links.

Verificado hoy (sin GPU / sin cola)

  • --selftest verde: parse JSON, rechazo de basura, gate acepta doctrina limpia, gate detecta copia verbatim.
  • --report, --dry (default/—doctrine/—all), --no-llm <slug> con nodo con sources (frontmatter verbatim, sin duplicar sources) y sin sources.
  • Llamada real al LLM ejecutada: el modelo ignoraba las etiquetas → se cambió a JSON estructurado (fix de diseño, no de prompt).

Fixes de paso

  • Caché de fuentes envenenada: un fallo transitorio de Wikipedia se guardaba como {empty:true} para siempre (56 entradas así). Ahora las vacías se reintentan después de 24 h.
  • Duplicación en modo extract: si el cuerpo del nodo ya contenía el extracto (nodos adoptados sin doctrina), se pegaba dos veces. Guard por n-grama.
  • {{ONE_LINER}} y detección de idioma ahora usan el primer párrafo, no el cuerpo completo (los nodos con extracto pegado contaminaban ambos).

Bloqueo conocido (2026-09-21)

La cola de extracción de second-brain-twitter está corriendo (chunks de chatgpt, 72) y satura Ollama: ~5–9 min por nodo. Máquina: 16 GB RAM, así que qwen3.6:latest (23 GB) no cabe; el default es qwen3.5:9b. Cuando la cola termine, la validación real es:

npm run enrich -- --limit 15        # ~1–2 min/nodo sin contención
# revisar 3 drafts: _enrich/<slug>.md + meta
# si la calidad convence: correr el backlog por tandas

Validación real (misma noche)

Tanda de 13 nodos con qwen3.5:9b: 12 ok, 1 fallo honesto, 1 skip.

  • ok: binary-search-tree, adex-neuron, apptainer, statistics, adversarial-attacks, agent-memory, api-endpoints, fairness-ml-book, batch-processing, datasheets-paper, model-cards-paper, surrogate-gradient-paper.
  • fallo honesto: eprop-paper — el paper (Bellec et al. 2020) no está en arXiv; el buscador por título falla y se prefirió dejarlo pendiente antes que colgar una fuente equivocada. Requiere fuente manual.
  • skip: red-teaming ya tenía doctrina.
  • Velocidad: 13–30 s/nodo con Ollama libre; 250–360 s/nodo con la cola de extracción corriendo (se reinició a media tanda). La cola manda.

Lectura de calidad (muestra): agent-memory y model-cards-paper sólidos y concretos; statistics algo genérico (describe el flujo, no el mecanismo) — el gate no juzga profundidad, para eso está la revisión humana. Contrato cumplido: son drafts, no adopciones.

Fix de paso: mapa curado ARXIV_ID para papers cuyo título no encuentra arXiv (mismo patrón que DOCS), con IDs verificados a mano: datasheets 1803.09010 · model-cards 1810.03993 · surrogate-gradient 1901.09948. Verificación real: un ID que di por bueno (1907.13286 para eprop) resultó ser otro paper — de ahí la regla de verificar siempre.

Fix de reporte: pendingTargets() ahora excluye nodos con meta ok:true (el draft está listo, falta adopción) → el backlog mostrado es real (174 sin sources, no 186).

npm run build verde tras los cambios (381 nodos · 75 proyectos).

Próximo

  • Adoptar los 12 drafts (revisar y mover a content/nodes/).
  • Correr el backlog por frente de estudio (no alfabeto), como dice study/queue-enrich.md; el default son 174 nodos sin sources.
  • Decidir si el paso de adopción se automatiza (mover drafts ok:true a content/nodes/) o se queda manual — hoy es manual a propósito.
  • Evaluar --doctrine sobre los 140 nodos con sources sin doctrina.

Archivos tocados

  • scripts/enrich.mjs (reescrito: pipeline + gates + provenance + flags + ARXIV_ID + fixes de caché/duplicación/reporte)
  • prompts/doctrine.md (nuevo: contrato de generación)
  • AGENTS.md y README.md (comando documentado)
  • _enrich/*.meta.json (12 drafts ok + fallos honestos de prueba)