# El RAG JARVIS responde desde los apuntes del usuario, no desde lo que el modelo recuerde. Para 668 herramientas de pentesting, recordar sería inventar. Este documento explica el método y los resultados. El índice **personal** (los apuntes de cada quien) no se publica; sí se publica un índice **público pre-construido** —glosario de Linux más el pack de metodología y manuales, con los vectores ya calculados (`nucleo/datos/saber-publico.jsonl`)— para que un clon recién hecho consulte al instante, sin repetir el trabajo de indexar. El código que lo construye está entero en `nucleo/saber/`. ## Cómo funciona Embeddings **estáticos** (`model2vec`, `potion-multilingual-128M`) sobre los fragmentos de los apuntes, con una **búsqueda híbrida**: similitud coseno más un bono por palabras exactas y por nombrar la herramienta. Los estáticos son rápidos —un vector por token, precalculado, sin red neuronal en la consulta— a cambio de ser más flojos en lo semántico; el bono por palabras compensa esa debilidad. No hace falta una base de datos vectorial: con unos miles de vectores, un producto escalar en `numpy` arranca antes y no añade dependencias. El RAG entra de **dos formas**: - **Pasivo**: en cada pregunta se le ponen delante al modelo los fragmentos más parecidos. Siempre dispara. - **Agéntico**: `buscar_en_apuntes` es una herramienta que el modelo invoca cuando lo decide, reformulando la consulta con sus palabras y buscando varias veces. Salva los casos donde el usuario dice una cosa y el apunte está escrito de otra. ## El pipeline de enriquecimiento En `nucleo/saber/enriquece/`, cuatro fases, reanudable y no destructivo: ``` apuntes ──cosecha──> fragmentos ──sintetiza──> preguntas │ │ └───── reindexa ──────────┘ │ índice + vectores │ evalúa (hit@k) ──promociona SOLO si mejora ``` 1. **Cosecha**: rescata la metodología que un indexado ingenuo se deja. En el caso real, de 72 notas de metodología escritas a mano, **0 estaban en el índice**: el filtro por nombre y por profundidad las descartaba. Un tope por herramienta evita que un repo a granel (una base de exploits con decenas de miles de ficheros) ahogue lo bueno. 2. **Síntesis** — *indexación multi-representación*: por cada fragmento se generan las preguntas que responde, y **se embebe la pregunta, se devuelve el fragmento**. Así una consulta coloquial ("cómo saco una shell reversa") se compara pregunta-contra-pregunta, no contra un comando técnico en otro idioma. El modelo solo escribe preguntas, nunca reescribe comandos: si inventa una pregunta rara, esa entrada recupera peor y ya está. 3. **Reindexa**: une todo, deduplica y calcula los vectores. 4. **Evalúa**: mide la recuperación con un conjunto de preguntas de prueba (`enriquece/eval_set.jsonl`). Determinista, sin juez-LLM. ## Resultados, medidos Rescatar la metodología y añadir los ganchos-pregunta, sobre el mismo conjunto de evaluación: | | antes | después | |---|---|---| | hit@1 | 48 % | **82 %** | | hit@5 | 77 % | **95 %** | | similitud media | 0.689 | **0.802** | ## Un resultado negativo, útil ¿Y si se cambia el embedder estático por un transformer de verdad (e5, bge)? Medido: **no compensa**. `intfloat/multilingual-e5-base` empató con el estático (hit@1 86 %, hit@5 95 %), arreglando dos consultas y rompiendo otras dos, a cambio de 50-150 ms por consulta y una dependencia de `torch` en cada búsqueda. El trabajo de recuperación lo hace la búsqueda híbrida, no el embedder. Se reproduce con `enriquece/experimento_embedder.py`. ## Por qué el índice no está en el repositorio `saber.jsonl` contiene el TEXTO de los apuntes indexados, con rutas y conocimiento personal; `diario.jsonl` es lo que se le dice al asistente. Son del usuario. El `.gitignore` los bloquea. Lo reproducible —el método, el código y la forma de medirlo— sí está; el corpus lo pone cada quien con sus notas. ## Qué se publica y qué no, en concreto | | En el repo | |---|---| | El código: `indexa`, `busca`, `glosario`, `sistema`, `manuales`, `enriquece/` | **sí** | | El eval (`enriquece/eval_set.jsonl`) | **sí** | | El glosario de tareas de Linux en castellano (`glosario.py`) | **sí** (genérico) | | **El pack de metodología y manuales** (`saber/conocimiento/`) | **sí** — limpio y genérico | | **El catálogo de *awesome lists*** (`saber/awesome.py`) | **sí** — solo lo redistribuible (CC0, CC BY-SA), ver [ATRIBUCION](../ATRIBUCION.md) | | **El índice público pre-construido** (`datos/saber-publico.jsonl`) | **sí** — glosario + pack + catálogo + preguntas-gancho, con vectores | | El índice personal (`saber.jsonl`) y sus copias | **no** — los apuntes indexados | | El diario (`diario.jsonl`) | **no** — las conversaciones | | Lo derivado (`cosecha.jsonl`, `sintesis.jsonl`) | **no** — salen de los apuntes | Así que un equipo recién clonado consulta **al instante**, sin dato privado y sin reindexar: el `saber-publico.jsonl` ya trae el glosario y el pack de metodología —enumeración, explotación web, shells, privesc, Active Directory, cheatsheets— más los **manuales** de las herramientas que no traen man page (`saber/conocimiento/09 - Manuales/`, cosechados con `manuales.py` de su `--help`). Cuando quiera sumar sus propias notas y las *man pages* de su máquina, corre `indexa.py` y pasa a usar su índice personal. Los apuntes privados del usuario (su `saber.jsonl`, su diario) siguen fuera; el pack los complementa. ## El catálogo: awesome lists Para el "¿qué existe para hacer X?" —no cómo, sino qué herramienta o software hay— `awesome.py` indexa *awesome lists* curadas: parsea sus listas (markdown y reStructuredText), las limpia de enlaces y badges, y agrupa cada categoría en un fragmento ("Analytics: GoatCounter — …, Plausible — …"). Cada fuente lleva su licencia: al índice **público** solo van las redistribuibles —`awesome` (sindresorhus, CC0) y `awesome-selfhosted` (CC BY-SA 3.0, con atribución en [ATRIBUCION.md](../ATRIBUCION.md))—. `awesome-hacking`, sin licencia explícita, se indexa solo en el índice **personal** si se clona en `~/COFRE/CODERS/`. Para ampliar el catálogo, se clona otra lista ahí y se reindexa. ## Las preguntas-gancho, ya horneadas El salto de recall (48→82 % hit@1) viene de la *indexación multi-representación*: por cada fragmento, preguntas en castellano que lo encuentran. Generarlas cuesta horas de modelo local, así que `enriquece/sintetiza_catalogo.py` las produce para el pack y el catálogo, y el índice público **las trae ya calculadas**. Un clon recién hecho consulta con esa calidad sin correr ni un minuto de síntesis: sobre el `saber-publico.jsonl` hay ~1.900 preguntas-gancho además de los fragmentos en crudo. Las de fragmentos no públicos (p. ej. `awesome-hacking`) se quedan fuera, como el resto de su contenido.