JARVIS/verde/NOTAS.md
sito 1e3e5ca20d carril verde (Hermes Agent): documentacion, panel, voz local y RAG
Rama JARVIS-GREEN, independiente de master (el nucleo naranja queda intacto).

- README propio del carril verde con diagrama de arquitectura y capturas del panel.
- verde/: arranque, panel web, puente de voz (escucha local con faster-whisper +
  habla con la voz del naranja), pruebas (07 voz, 08 escucha), config y notas.
- Integracion RAG: nucleo/saber/busca_cli.py + skill buscar-en-apuntes, para que
  el agente consulte el mismo indice que el nucleo.
- Todo local (127.0.0.1); sin datos personales (rutas y modelo de GPU scrubeados).
2026-08-18 11:26:42 +02:00

38 KiB

Notas del carril verde

Lo medido, lo resuelto y lo que queda abierto.

Fechas: 15 de agosto de 2026 (instalación, en frío) y 16 de agosto (primer arranque real). Lo de la primera fecha salía de leer código y mirar el disco; lo de la segunda está ejecutado.

EL SUELO DE 64k: el tope que invalidó la medida anterior (16 ago)

(Esto va primero porque cambia la conclusión de la sección siguiente.)

arrancar.sh midió bien: 24576 era el mayor contexto que cabía en la GPU con las 32 capas en la tarjeta, 3301 MiB, y con eso el modelo emitía tool_calls válidos. Todo correcto y todo inútil, porque al abrir Hermes:

Model qwen3.5:4b-verde has a context window of 24,576 tokens, which is below
the minimum 64,000 required by Hermes Agent.

Es un tope duro del arnés, no un aviso: agent/model_metadata.py:405, MINIMUM_CONTEXT_LENGTH = 64_000, comprobado en agent/agent_init.py:2656. No se ve en frío — hermes prompt-size no lo menciona — y solo salta al construir el agente.

Las dos salidas que se descartaron, y por qué

  1. La puerta de atrás de lmstudio. El mismo if se salta si provider == "lmstudio" y context_length está puesto a mano. No se usa: ese proveedor llama a {server}/api/v1/models, la API nativa de LM Studio, que ollama no tiene, y además intenta precargar el modelo por su cuenta (ensure_lmstudio_model_loaded). Fingir ser otro servidor rompería más adelante y de forma más confusa.
  2. Mentir en el config.yaml poniendo 64000 con el modelo a 24576. Es la peor de las tres: Hermes creería tener 64k, no comprimiría hasta acercarse, y ollama iría tirando los tokens más viejos — que son el prompt de sistema y los esquemas de las 19 herramientas. El síntoma sería que el agente deja de llamar herramientas a mitad de sesión, sin un solo error en el log.

Lo que se hizo: subir el contexto de verdad y pagar capas

Con num_ctx 65536 fijo, medido el 16 de agosto:

num_gpu Resultado En tarjeta Total
32 cudaMalloc failed
24 cudaMalloc failed
16 carga 2795 MiB 5784 MiB
8 carga 1772 MiB 5784 MiB
0 carga 0 MiB 5368 MiB

O sea que 64k sí cabe en esta máquina, pero con la mitad de la red en la CPU y unos 3 GB en RAM. Lo que eso cuesta en tiempo está más abajo, en "Cuánto tarda de verdad".

La palanca para recuperar capas —OLLAMA_FLASH_ATTENTION=1 + OLLAMA_KV_CACHE_TYPE=q8_0, que parte el KV caché por la mitad— sigue sin aplicarse: es del servicio de ollama, no del modelo, y afectaría también al naranja. Esa decisión es del usuario.

Lección de método, que es la misma que ya aparece dos veces en este fichero: se midió a fondo el límite de la máquina y no se comprobó el límite del software que iba encima. Los dos son topes; solo uno se estaba mirando.

Cuánto tarda de verdad (16 ago)

Medido con hermes -z, que es una pregunta y a callar, contra el modelo a num_ctx 65536 con 16 de 32 capas en GPU.

Escalón Vueltas Tiempo
1 · "hola, ¿quién eres?" — en frío 0 herramientas 2 min 38 s
2 · "¿cuánto espacio libre queda?" 1 herramienta 48,6 s
3 · "lee verde/README.md y resúmelo" 4 (dos fallidas + recuperación) 3 min 29 s
3 bis · igual, con terminal.cwd arreglado 1 1 min 58 s
3 ter · igual, ya con AGENTS.md 2 (una fallida + recuperación) 4 min 54 s
4 · "cuál de los .md de verde/ es el más largo" 4 encadenadas 3 min 54 s
5 · "un cartel en arte ascii que ponga JARVIS" 3 (una salió a la red) 4 min 58 s
6 · "di en voz alta…" 1 (text_to_speech) 3 min 37 s
(pwd suelto, caché caliente, una llamada) 1 13,2 s

Esa diferencia de tres veces no es ruido, y es lo más útil que se midió. El primer turno paga el prefill entero de los ~20.500 tokens del prompt fijo con media red en la CPU. A partir de ahí, ollama cachea el prefijo y las vueltas siguientes solo procesan lo nuevo — y eso que la 2 hace dos llamadas al modelo (pedir la herramienta y redactar con su resultado) y aun así tarda un tercio.

O sea que la cifra que importa para decidir si esto se usa no es la del primer arranque. Es la de régimen. Igual que la primera transcripción de whisper tarda ~4 s por arrancar el servidor y luego se queda en ~930 ms.

El escalón 2 acertó, además: 663G libres de 1,9T al 63 %, que es exactamente lo que dice df -h /.

Y el 3 enseñó algo que no se estaba buscando. Falló dos veces seguidas — partió mal la frase y pidió cat ~/lee-verde/README.md, que no existe— y en vez de inventarse una respuesta cambió de estrategia: search_files para localizar el fichero, y read_file con la ruta absoluta ya correcta. El resumen final es fiel al README.

Eso es exactamente lo que se venía a probar de este carril: recuperarse de una herramienta que devuelve error. El núcleo naranja, con sus acciones rápidas, o acierta a la primera o no hay segunda. Las dos vueltas de más son las que explican los 3 min 29 s.

El punto flaco del 4B: las rutas relativas

Con terminal.cwd ya arreglado se repitió el escalón 3, y salió peor: 1 min 58 s y una sola llamada, read_file sobre COFRE/CODERS/JARVIS/README.md. Se comió el verde/. Resumió el README de la raíz —el del proyecto entero— con un "JARVES" de propina y la frase cortada a medias.

La comparación entre las dos pasadas es lo interesante:

Ruta base Qué hizo Resultado
Con cwd en $HOME mal falló, buscó, se corrigió acertó
Con cwd en el repo bien fue directo a una ruta plausible falló

O sea: cuando la herramienta le devuelve un error, se recupera; cuando le devuelve un fichero que existe pero no es el pedido, no tiene forma de notarlo y lo resume tan campante. Es el mismo patrón del error de ~/README.md, y la razón de que estos fallos no se vean: no hay error que mirar.

Lo que apunta a la mejora obvia si este carril sigue adelante: un AGENTS.md en la raíz del repo describiendo la disposición de carpetas. Hermes ya carga ficheros de contexto, así que el sitio está.

Escrito el AGENTS.md, el escalón 3 pasa — pero no por lo que parece

Se escribió (<raíz>/AGENTS.md: los tres carriles, el árbol de carpetas y la regla de que las rutas relativas salen de la raíz del repo). Cuesta 2.490 B del prompt fijo, que sube a 84.740 B ≈ 21.185 tokens de los 65.536: quedan 44.351 para conversar.

Tercera pasada del escalón 3, 4 min 54 s, y el resumen es del fichero correcto por fin. Recoge incluso el aviso de la fuga a internet que se añadió hoy al README.

Pero el modelo volvió a alucinar la ruta. Su primera llamada fue

read_file ~/COFRE/CODERS/JARVIS-verde/zapier-connection.py

que no existe y no se parece a nada de lo pedido. Recibió File not found y entonces acertó con JARVIS/verde/README.md.

O sea que el AGENTS.md no evitó el disparate; le dio con qué recuperarse. Es la tercera vez que sale el mismo patrón en esta sesión y ya no es anécdota:

Lo que recibe la herramienta Qué hace el 4B
Un error cambia de estrategia y suele acertar
Un fichero que existe pero no es lo resume tan contento

La lección práctica para este carril: más vale una ruta que falle que una que acierte por accidente. Y en la prosa sigue descuidado — aquí se inventó un "98 % local", llamó API de terceros a pyfiglet (que es una librería local; la API era asciified.thelicato.io) y le dio la vuelta a la frase del objetivo.

El escalón 4, que era el que decidía: pasa

"dime cuál de los .md de la carpeta verde/ es el más largo"3 min 54 s, cuatro llamadas encadenadas, respuesta correcta:

# Llamada Qué pasó
1 search_files con pattern: "*.md" rg: regex parse error
2 search_files con pattern: "\\.md$" corrigió el patrón él solo
3 terminal: find … -name "*.md" los cinco ficheros
4 terminal: bucle con wc -l sobre cada uno las cinco cifras

Y concluyó: verde/NOTAS.md con 625 líneas. Es el correcto.

Esto es lo que justificaba el carril entero. No hay una herramienta que conteste esa pregunta: hay que listar, medir y razonar sobre el resultado de la vuelta anterior, y saber cuándo parar. El núcleo naranja no puede hacerlo — sus 150 acciones rápidas o aciertan a la primera o no hay segunda.

La letra pequeña, que no cambia el veredicto pero conviene tener escrita: al enumerar los ficheros los ordenó mal (puso PLAN-ARRANQUE con 181 por encima de ESCALERA con 208) y etiquetó el README de verde/ como "categoría raíz". O sea que el razonamiento de la cadena es bueno y la prosa de alrededor es descuidada. Para una orden hablada da igual; para un informe, no.

EL BLOQUEO DEL ARRANQUE: no cabe la pregunta

(15 ago, tarde. Esto es lo más importante del fichero.)

Se preparó el primer arranque y apareció un tope que no era el que se anticipaba. No es que qwen3.5:4b no sepa llamar herramientas: es que no le cabe la pregunta. Medido con hermes prompt-size:

Prompt de sistema de Hermes 26.281 B
Esquemas de las 19 herramientas 55.978 B
Fijo, antes de decir nada 82.259 B ≈ 20.500 tokens
num_ctx de qwen3.5:4b-jarvis 4.096

Cinco veces por encima. Y hay un segundo tope escondido: num_predict 300, que el naranja hereda del modelo BASE (no lo pone su Modelfile). 300 tokens cortan el JSON de una llamada a herramienta por la mitad.

Los dos están bien puestos para el naranja —respuestas habladas cortas, VRAM para whisper— y son justo lo contrario de lo que pide un arnés agéntico.

Y encima, la trampa que avisaba la documentación: /api/show reporta qwen35.context_length = 262144, el máximo del modelo, no los 4096 configurados. Hermes se habría creído que tenía 262k. El síntoma habría sido respuestas truncadas y llamadas rotas, y la conclusión natural —"el 4B no vale para esto"— habría sido falsa.

La variante verde, ya creada

verde/qwen3.5-4b-verde.Modelfileollama create qwen3.5:4b-verde. qwen3.5:4b-jarvis no se tocó.

Parámetro Valor Por qué
num_gpu 32 todas las capas, con el naranja cerrado
num_ctx 24576 32768 no cabe (ver abajo)
num_predict 4096 300 cortaría una tool call
temperature 0.2 emitir JSON no es escribir prosa

Trampa del Modelfile: ollama no admite comentarios en la misma línea que un PARAMETER. Esto falla:

PARAMETER num_gpu 32          # todas las capas
→ Error: invalid int value [32          # todas las capas]

Los comentarios van en su propia línea.

Lo medido de VRAM

Configuración Resultado
num_ctx 32768, 32 capas cudaMalloc failed: out of memory
num_ctx 24576, 32 capas pendiente: hay que medirlo con la tarjeta libre

Escalera si 24576 tampoco entra, en orden de menos a más invasivo: num_ctx 16384 (el suelo: por debajo no cabe el prompt fijo con holgura) → num_gpu 24OLLAMA_FLASH_ATTENTION=1 + OLLAMA_KV_CACHE_TYPE=q8_0.

Cuidado con el último: OLLAMA_KV_CACHE_TYPE es del servicio, no del modelo. Se pone en el systemd de ollama y afectaría también al naranja la próxima vez que arranque. Si se usa, hay que anotarlo aquí y en el Modelfile del naranja.

Desde dónde se lanza importa, y mucho

Directorio de arranque Prompt de sistema
~/COFRE/CODERS/JARVIS 26.281 B
~/.hermes/hermes-agent 55.261 B

La diferencia son 30 KB: el AGENTS.md de 81 KB del propio repo de Hermes, que se cuela como fichero de contexto y encima sale truncado con un aviso. A mano es fácil olvidarlo y sacar medidas que no comparan nada.

Y el cd NO basta (16 ago)

Esto se descubrió subiendo la escalera y es peor que lo anterior, porque no deja ni un error. verde-launcher.sh hacía cd "$RAIZ" antes de llamar a Hermes, que parece suficiente y no lo es: Hermes restaura el directorio de trabajo apuntado en la sesión y se salta aquel desde el que lo lanzas.

El síntoma: se le pide "lee verde/README.md y resúmelo en una línea" y devuelve

Gitleaks es una herramienta SAST diseñada para detectar y prevenir secretos codificados a mano en repositorios git.

Parece el modelo alucinando de manera espectacular. No lo es. En la base de sesiones se ve la llamada real:

terminal  {"command": "cat ~/README.md | head -50"}

Resolvió verde/README.md contra ~, ahí hay un README.md que es el de Gitleaks, lo leyó entero y lo resumió bien. La herramienta funcionó, el modelo entendió, el resumen es correcto — y la respuesta es inútil.

Lo que NO lo arregla, probado

--in DIR no vale en modo -z. Parece la bandera exacta —entra en el directorio y se salta la restauración, mientras que --no-restore-cwd solo hace lo segundo— y con ella el fallo se repitió igual. El motivo está en hermes_cli/main.py:11324: el modo de una sola pregunta sale por _run_and_exit_oneshot(...) y termina el proceso ahí, sin llegar nunca a cmd_chat, que es la única función donde se atiende --in. Se ignora en silencio. En el TUI sí funciona, y por eso verde-launcher.sh la pasa.

Un cd delante tampoco. Comprobado del modo más directo posible:

pwd del shell : ~/COFRE/CODERS/JARVIS
pwd del agente : ~

Y no es que el modelo se lo invente: el ls -d */ que ejecutó por su cuenta devolvió BIKEPARK/ COFRE/ ctf/ Downloads/…, o sea el $HOME de verdad.

Lo que sí lo arregla

terminal.cwd en ~/.hermes/config.yaml, que de fábrica viene como "." y que su propia documentación describe como "el directorio actual donde ejecutas hermes". No lo es.

terminal:
  cwd: "~/COFRE/CODERS/JARVIS"

Verificado: con eso el prompt de sistema pasa a decir working directory: ~/COFRE/CODERS/JARVIS. Está replicado en verde/config.yaml y lo comprueba pruebas/03_config.sh.

Ojo, que esto ATA el carril a este repositorio. Es lo correcto para un banco de pruebas que se compara contra el naranja, y es una limitación real si algún día se quiere que el verde mire ~/BIKEPARK o ~/oasis. Con la raíz en $HOME además se ahoga: un search_files de *.md devolvía sobre todo site-packages/ de los venv de entrena/.

Cómo se diagnosticó, que es reutilizable: -z imprime solo el texto final, sin las llamadas a herramienta, así que desde fuera no se ve nada. Todo queda en ~/.hermes/state.db, tabla messages, columnas tool_calls y content:

python3 -c "
import sqlite3
c = sqlite3.connect('file:$HOME/.hermes/state.db?mode=ro', uri=True)
for r in c.execute('SELECT role, tool_name, tool_calls, content FROM messages ORDER BY rowid DESC LIMIT 10'):
    print(r)
"

Sin mirar ahí, este fallo se atribuye al modelo y se acaba cambiando de modelo para nada.

Por qué se paró: dataset.py

El arranque en GPU quedó aplazado, no descartado. La gráfica la tiene un trabajo tuyo: JARVIS/entrena/dataset.py, que genera pares pregunta-respuesta de COFRE con qwen3.5:4b-jarvis. Son 7-10 horas (tu propio README lo dice) y va por 100/2261 fragmentos.

Detalle que confunde y conviene tener escrito: mientras ese script corra, ollama stop qwen3.5:4b-jarvis parece no funcionar. Sí funciona — descarga el modelo — pero la siguiente petición del script lo vuelve a cargar en segundos. No es un fallo de ollama.

Comprobado que los ollama stop no rompieron la generación: siguió de 199 a 224 ejemplos en 75 s.

Retomar cuando termine

# 1. confirmar que dataset.py acabó y la tarjeta está libre
pgrep -f 'entrena/dataset.py' || echo "terminado"
nvidia-smi --query-gpu=memory.used --format=csv,noheader     # ~140 MiB

# 2. medir si 24576 cabe
curl -s http://127.0.0.1:11434/v1/chat/completions -H 'Content-Type: application/json' \
  -d '{"model":"qwen3.5:4b-verde","messages":[{"role":"user","content":"ok"}],"max_tokens":5}'
curl -s http://127.0.0.1:11434/api/ps | python3 -m json.tool | grep -E 'size_vram|context_length'

# 3. decirle a Hermes el contexto REAL (si no, cree que son 262144)
#    en ~/.hermes/config.yaml, bloque model:, en las lineas canonicas (~82/~95)
#      default: "qwen3.5:4b-verde"
#      context_length: 24576

# 4. LA PRUEBA DECISIVA, antes de abrir el TUI: ¿emite tool_calls?
curl -s http://127.0.0.1:11434/v1/chat/completions -H 'Content-Type: application/json' -d '{
  "model":"qwen3.5:4b-verde",
  "messages":[{"role":"user","content":"¿Cuánto espacio libre queda en el disco?"}],
  "tools":[{"type":"function","function":{"name":"ejecuta_comando",
    "description":"Ejecuta un comando de shell",
    "parameters":{"type":"object","properties":{"comando":{"type":"string"}},"required":["comando"]}}}]}' \
  | python3 -m json.tool

El paso 4 separa dos preguntas que juntas se contestan mal: ¿sabe el modelo emitir una llamada? y ¿funciona Hermes?. Si sale tool_calls con JSON válido, lo que falle después es de Hermes. Si contesta en prosa, abrir el TUI solo daría un fallo más confuso.

Después, la escalera del plan: "hola" → una herramienta → ficheros → varias vueltas encadenadas (el escalón que separa un chatbot de un agente) → skills. La voz al final, y la escucha la última de todas: los 839 MiB de pico de whisper contra una tarjeta que tendrá el modelo casi entero.

Estado de la gráfica

VRAM Quién
Antes de instalar 878 MiB de 4096 tres whisper-server tuyos, del naranja
Después de instalar 127 MiB de 4096 nadie; tus whisper acabaron solos

Instalar no cargó nada en la tarjeta. Comprobado con nvidia-smi --query-compute-apps y con /api/ps de Ollama, que devolvió {"models": []} en todo momento.

De paso, un dato que puede interesarte del naranja: llegaste a tener tres whisper-server a la vez, ~880 MiB cada uno, 2648 MiB en total sobre una tarjeta de 4096. Es el escenario exacto del CUDA error: out of memory que documenta el README. No hice nada al respecto —es tu desarrollo en marcha— pero queda anotado.

Verificación en frío, ejecutada

Comprobación Resultado
hermes en PATH ~/.local/bin/hermes
Tamaño de ~/.hermes 2,2 GB
Caché de Playwright 622 MB
Credenciales con valor en .env 0
Tokens de mensajería 0 (por eso el gateway nunca arrancó)
URLs de nube activas en config.yaml 0
Puertos fuera de loopback ninguno
Modelos cargados en Ollama ninguno
Puente de voz funciona: 0,34 s para 3,2 s de audio, 22050 Hz mono

Las 11 líneas con contenido del .env no son claves: son TERMINAL_TIMEOUT, BROWSER_SESSION_TIMEOUT, banderas de depuración y similares.

Trampa: claves YAML duplicadas en config.yaml

Esto casi cuela y merece quedar escrito. El config.yaml que genera el instalador declara provider y base_url dos veces dentro del bloque model:: una arriba, junto a default, y otra ~45 líneas más abajo. En YAML la última repetición gana.

Al configurar Ollama arriba, el fichero parecía correcto leyéndolo, y al parsearlo salía esto:

model.provider: auto
model.base_url: https://openrouter.ai/api/v1

Es decir: el carril habría salido por OpenRouter creyendo yo que iba a localhost. Se arregló dejando los valores solo en las líneas de abajo, las canónicas.

La lección, que vale para todo este fichero: con 1890 líneas y 121 activas, no basta con leerlo. Hay que parsearlo:

python3 -c "import yaml;d=yaml.safe_load(open('$HOME/.hermes/config.yaml'));print(d['model'])"

Trampa: la instalación de Chromium se cuelga

Le pasa a más gente (issue #35166 del repo). El zip se descarga entero y la extracción se queda parada: 50 minutos de reloj con 1 segundo de CPU y cero crecimiento en ~/.cache/ms-playwright/. El timeout del instalador mata al padre y deja procesos huérfanos reteniendo un __dirlock, así que el reintento también falla.

Se resolvió a mano, y si vuelve a pasar tras un hermes update, esta es la receta:

pkill -f oopDownloadBrowserMain; pkill -f 'playwright install'
rm -rf ~/.cache/ms-playwright/chromium-1208 ~/.cache/ms-playwright/__dirlock
mkdir -p ~/.cache/ms-playwright/chromium-1208
unzip -q /tmp/playwright-download-*/playwright-download-chromium-*.zip \
      -d ~/.cache/ms-playwright/chromium-1208/
touch ~/.cache/ms-playwright/chromium-1208/INSTALLATION_COMPLETE

El INSTALLATION_COMPLETE es lo que Playwright mira para dar el navegador por bueno (lib/server/registry/index.js:1345). Sin él lo reinstala cada vez.

Quedaron los tres componentes, verificados con playwright install --dry-run:

Tamaño
chromium-1208 364 MB
chromium_headless_shell-1208 254 MB
ffmpeg-1011 4,9 MB

El binario responde: Google Chrome for Testing 145.0.7632.6.

Resuelto: tu voz afinada SÍ se puede reutilizar

Era la pregunta abierta del plan, y salió el mejor de los tres desenlaces.

Hermes tiene un proveedor de TTS por comando que no está documentado en su web. Está en tools/tts_tool.py, bloque "Custom command providers":

tts:
  provider: mi-voz
  providers:
    mi-voz:
      type: command
      command: "... {output_path} {input_path}"
      output_format: wav

Marcadores: {input_path}, {text_path}, {output_path}, {format}, {voice}, {model}, {speed}. Ejecuta con shell=True.

Eso permite llamar al mismo config/jarvis-piper.sh que usa el naranja, así que el verde suena exactamente igual: mismo binario Piper 1.2.0, mismo .onnx, misma cadena de audio. Sin esto la comparación de voz entre carriles no habría valido nada.

Dos detalles que obligaron a escribir voz-verde.sh en vez de meter el comando directo en el YAML:

  1. El entorno se limpia. _run_command_tts llama a hermes_subprocess_env(inherit_credentials=False), así que JARVIS_PIPER_VOZ y JARVIS_PIPER_TONO no llegarían. Se ponen dentro del adaptador.
  2. Las interfaces no encajan. jarvis-piper.sh quiere el texto como argumento; Hermes entrega un fichero. El adaptador hace de puente.

Qué voz, y ojo con el README

voz-verde.sh usa davefx a tono 1.0, que es lo que tiene el núcleo hoy (nucleo/boca/voz.py: POR_DEFECTO = "davefx", JARVIS_PIPER_TONO = "1.0").

El README.md del repo describe sharvard + 0,86 = 106 Hz como "la puesta". Eso era el JARVIS sobre Newelle. El verde copia al núcleo, que es contra quien se va a comparar, no al README. Si algún día el núcleo vuelve a sharvard, hay que cambiarlo aquí también.

De paso: el Piper de Hermes es el GPL, no el tuyo

El proveedor piper de serie usa OHF-Voice/piper1-gpl (GPL-3.0). El tuyo es el original rhasspy/piper 1.2.0, MIT, archivado en octubre de 2025. Los dos son software libre; no son el mismo paquete. Como usamos el comando y no el proveedor de serie, el verde habla con tu Piper MIT.

El cerebro: lo que hay que vigilar

Configurado como proveedor custom contra http://127.0.0.1:11434/v1 con qwen3.5:4b-jarvis. Ollama ya habla OpenAI, no hace falta puente.

Pero la propia guía de Hermes avisa de algo que hay que probar. Su tabla de modelos locales dice, literalmente, que para trabajo agéntico completo hace falta gemma4:31b, y marca sin tool calling a todo lo de 9B para abajo:

Modelo (su tabla) Tool calling
gemma4:31b
gemma2:27b No
gemma2:9b No
llama3.2:3b No

Su tabla no lista qwen3.5, que hace tool calling — el commit "nucleo: el cerebro con tool calling nativo" lo demuestra. Pero lo demuestra en tu arnés, no en el de Hermes, que inyecta muchas más definiciones de herramientas por vuelta. Así que:

  • Es lo primero que hay que comprobar al arrancar: si el 4B llama herramientas bien dentro de Hermes o se atraganta.
  • Si se atraganta, esta máquina tiene 62 GB de RAM: un modelo grande cabe, aunque iría por CPU (~2-5 tok/s según su guía, 30-120 s por respuesta). Para eso está HERMES_API_TIMEOUT=1800 documentado en env.ejemplo.
  • qwen3.5:9b (6,14 GB) es el escalón intermedio, y solo con el naranja cerrado.

Aviso de contexto: si se define num_ctx propio en Ollama, hay que poner el mismo en Hermes. Su /api/show reporta el contexto máximo del modelo, no el configurado, así que Hermes puede creer que tiene más sitio del que hay.

Antes de comparar los dos carriles: no midas peras y manzanas

El naranja tiene 150 acciones rápidas que se saltan el modelo: "¿cuánto espacio queda?" es un df, no una inferencia. Hermes no tiene ese atajo — allí toda orden pasa por el cerebro, con las herramientas, las skills y la memoria en contexto en cada vuelta.

El verde va a medir más lento, y no será culpa del modelo. Para comparar de verdad hay que usar solo las frases que hoy no encajan en el catálogo y acaban en el cerebro: son las únicas donde los dos hacen lo mismo. Las hay anotadas en config/jarvis-sin-accion.jsonl.

Pendiente: dependencias de sistema de Playwright

El instalador pidió sudo para las librerías de sistema de Chromium, se declinó (no hay sudo sin contraseña) y degradó solo: instaló el binario en la caché del usuario. Chromium arrancará si las librerías ya están; si se queja, esto es lo que hay que correr una vez:

cd ~/.hermes/hermes-agent && sudo npx playwright install-deps chromium

Qué se descargará el primer día que arranques

Nada de esto ha pasado todavía. Que no sorprenda:

Cuándo Cuánto
Modelo de whisper small primera transcripción ~500 MB en disco, 839 MiB de pico en GPU
Modelo de Ollama ninguno — 4b-jarvis ya está 0
Voces de Piper ninguna — usamos las tuyas 0

stt.local.unload_after_idle_seconds: 300 está puesto justo por los 839 MiB: suelta whisper tras cinco minutos de silencio y le devuelve el hueco al modelo. Cuesta una recarga en la frase siguiente.

Se instaló también el driver de Computer Use

Va en la instalación completa. Es el que permitiría a un agente controlar ratón y teclado del escritorio, no solo el navegador. Instalado no es lo mismo que activo — hoy no hay nada que lo invoque — pero conviene saber que está antes de dar permisos a algo. Si se quiere fuera: --skip-computer-use.

Dato que sí importa en esta máquina: ese driver no viene de Nous. El instalador lo trae con un curl | bash contra terceros:

https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh

O sea que el envoltorio instalar.sh audita el instalador de Hermes, pero ese instalador se descarga y ejecuta otro script de otro repo sin que nadie lo mire. Con COFRE en esta máquina, merece una lectura antes del próximo hermes update. Está en ~/.hermes/hermes-agent, y el binario resultante también.

Las 104 pruebas del naranja: NO se ejecutaron

El plan decía correr pruebas/ejecutar.sh para comprobar que el naranja seguía dando el mismo número. No se hizo, y a propósito: esa suite abre la aplicación y carga el modelo, que es exactamente lo que se pidió no hacer mientras el naranja estaba en desarrollo.

Queda pendiente para cuando pares de desarrollar:

cd ~/COFRE/CODERS/JARVIS/pruebas && ./ejecutar.sh

En su lugar se comprobó lo que sí se podía en frío: que la gráfica quedó libre y que el verde no escribió fuera de su sitio. Todo lo suyo vive en verde/, en ~/.hermes/, más tres ficheros de escritorio (~/.local/share/icons/jarvis-verde.png, ~/.local/share/applications/jarvis-verde.desktop) y la caché de Playwright.

Los 13 ficheros que git status da por modificados son todos anteriores a esta sesión: el más reciente es config/set_persona.py a las 13:22 y esto empezó a las 16:09. Y los ficheros nuevos que aparecen en nucleo/ después de esa hora —arranca.sh, estado.py, ventana.sh, pruebas/prueba_cara.py…— son tu desarrollo del naranja de esta tarde: no existían cuando se listó esa carpeta al empezar.

La duda del 4B: resuelta, y a favor (16 ago)

Era la pregunta abierta del carril — la guía de Hermes marca sin tool calling todo lo de 9B para abajo y recomienda gemma4:31b para trabajo agéntico.

qwen3.5:4b llama herramientas bien dentro de Hermes. Comprobado primero contra el endpoint pelado (pruebas/04_tool_calling.sh, tres casos) y después en el arnés completo, con las 19 herramientas en contexto:

Caso Resultado
Pide herramienta cuando hace falta terminal {"command": "df -h ."}
Acierta la herramienta y sus dos argumentos escribe_fichero {"ruta": …, "contenido": …}
No llama cuando no hace falta ("¿cuánto son dos más dos?") contesta 4 en prosa

El tercero importa tanto como los otros: un modelo que llama siempre es tan inútil como uno que no llama nunca, y con una sola pregunta no se distinguen.

El escalón 6: habla, y con la voz del naranja (16 ago)

text_to_speech → proveedor jarvis-pipervoz-verde.shconfig/jarvis-piper.sh. Comparado el WAV que salió de Hermes con el que genera la prueba 07_voz.sh llamando al adaptador a pelo:

Hermes Directo
Frecuencia 22050 Hz 22050 Hz
Canales 1 1
Bits 16 16

Idénticos, que era el objetivo: mismo binario Piper 1.2.0 MIT, mismo .onnx, mismo tono. La comparación de voz entre los dos carriles vale, que es para lo que se montó el adaptador.

De propina, sin que nadie se lo pidiera contestó "Perfecto, señor" y "Todos mis sistemas están listos para sus órdenes". El registro de JARVIS sale del SOUL.md de Hermes, no de la persona del naranja, y aun así coincide.

Queda el juicio que no es medible: si suena bien. El fichero está en ~/.hermes/cache/audio/.

EL ESCALÓN 5 ROMPIÓ LA PREMISA: el agente se fue a internet solo (16 ago)

Esto es lo más serio de la sesión de estreno y hay que leerlo antes que nada.

Se le pidió "hazme un cartel en arte ascii que ponga JARVIS" — a propósito sin nombrar ninguna skill, que es lo que medía el escalón. Lo que hizo:

# Llamada Resultado
1 skill_view {"name": "ascii-art"} eligió la skill sola
2 python3 -m pyfiglet "JARVIS" --font slant No module named pyfiglet
3 curl https://asciified.thelicato.io/api/v2/ascii?text=JARVIS salió a internet

Nadie le dijo que podía. La skill menciona pyfiglet, pyfiglet no está instalado, y el modelo resolvió el hueco buscando el servicio equivalente en la web y mandándole el texto a un tercero.

Por qué no lo cazaba nada

pruebas/06_privacidad.sh pasa, y seguirá pasando: comprueba configuración —cero credenciales, cero URLs de nube en el YAML, nada escuchando fuera de loopback— y esto no es configuración, es conducta en marcha. El agente tiene una terminal y la terminal tiene curl. No hay ajuste que lo impidiera.

Es una diferencia de fondo con el naranja, y conviene que quede escrita: el naranja ejecuta un catálogo cerrado de 150 acciones. El verde decide qué ejecutar, y ese es justo su valor y justo su riesgo. No se puede tener lo uno sin lo otro.

Lo que se puede hacer, por orden de menos a más invasivo

  1. Instalar lo que las skills dan por hecho (pip install pyfiglet). Quita el motivo, no la capacidad. Es parche, no solución.
  2. Apagar las skills que tiran de red: hermes skills disable <nombre>.
  3. Recortar herramientas en platform_toolsets del config.yaml, que ya estaba apuntado como palanca para el contexto y sirve para esto también.
  4. Cortar por debajo: regla de cortafuegos para el proceso, o terminal en contenedor sin red. hermes egress no vale para esto — es iron-proxy, un proxy que INYECTA credenciales en las salidas, no un bloqueo.

No he aplicado ninguna. Cualquiera de ellas cambia lo que el carril sabe hacer, y eso es una decisión del usuario, no una corrección técnica.

Y el cartel salió mal

Aparte de todo lo anterior: el banner que curl devolvió era correcto, y al copiarlo a su respuesta final el modelo lo destrozó. Mismo patrón que la lista de ficheros del escalón 4: el 4B razona bien la cadena y es descuidado reproduciendo texto literal largo. 4 min 58 s en total.

El escalón 7 (la escucha) cabe, y por un motivo que sorprende (16 ago)

El plan lo dejaba para el final temiendo un CUDA error: out of memory: whisper small pide 839 MiB en el pico de la transcripción y la tarjeta son 4096. Medido con el verde cargado y trabajando:

MiB
Tarjeta 4096
Ocupado por el modelo verde (65536 ctx, 16 capas) 2902
Libre 1062
Pico de whisper small 839
Margen 223

Cabe. Y lo que sorprende es que el verde deja más sitio que el naranja: 2902 MiB contra los 2941 del reparto automático de ollama para qwen3.5:4b-jarvis. Bajar 16 capas a la CPU libera tarjeta, así que el precio que se paga en velocidad se cobra en VRAM.

Sigue siendo justo —223 MiB— y falta la prueba de verdad, que necesita micrófono. Pero la aritmética que hacía temer el escalón ya no dice lo que decía.

Para probar cuando arranques

  • Tema del panel web. dashboard.theme acepta default, midnight, ember, mono, cyberpunk, rose. No he podido ver sus paletas (el panel va empaquetado), pero cyberpunk o mono son los candidatos a pegar con el verde. El TUI no tiene tema: ahí el color lo pone verde-launcher.sh con secuencias OSC.
  • Los escalones 5, 6 y 7 de ESCALERA.md: una skill, la voz dentro de Hermes y la escucha por micrófono. Los tres piden criterio u oído, o el micrófono, así que no se automatizan.
  • Si merece la pena la palanca del KV caché. OLLAMA_FLASH_ATTENTION=1 + OLLAMA_KV_CACHE_TYPE=q8_0 partiría el caché por la mitad y devolvería capas a la tarjeta, que es justo lo que hoy cuesta el tiempo de respuesta. No se ha aplicado: es del servicio de ollama y afectaría también al naranja.

Noche del 16-17 ago: voz de entrada, RAG y frontal a nivel del naranja

Encargo: subir el verde al nivel del naranja en voz, RAG y frontal. Todo local, nada a gitea (pendiente de revisión).

La escucha, resuelta y en local (escalón 7 cerrado sin micro)

El TUI de Hermes no tiene micrófono, y su STT nativo (stt: del config) es para notas de voz de mensajería — saldrían de la máquina. Así que se puentea:

  • verde/escucha_transcribe.py — WAV → texto con faster-whisper small (el mismo que declara el config), en CPU a propósito: el 4B ocupa la tarjeta y el margen es de 223 MiB; whisper en CPU tarda ~12 s por frase y no arriesga OOM. No llama a ninguna API.
  • verde/escucha-verde.sh — pulsar-para-hablar: graba (pw-record/arecord/parec) → transcribe → hermes chat --reasoning none -q → responde; --habla lo dice con la voz del verde, --seguido encadena turnos.
  • verde/pruebas/08_escucha.sh8/8. Bucle TTS→STT sin micro: genera voz con el 07, la transcribe y comprueba que vuelven las palabras clave. Mide y verifica que el transcriptor no referencia APIs remotas.

Medido: "hola jarvis verde cuanto espacio libre queda en el disco" → "¿Cuánto espacio libre queda en el disco?" (solo pierde "jarvis"→"harvis", nombre raro).

Latencia honesta: la ESCUCHA va bien (~12 s CPU). El cuello es el 4B (minutos por turno). El puente sirve para PROBAR que el verde oye, no como diario.

RAG del naranja, disponible en el verde

El agente ya puede consultar el mismo índice que el núcleo (14.949 entradas):

  • nucleo/saber/busca_cli.py — CLI sobre saber.busca (texto o --json).
  • Skill local ~/.hermes/skills/research/buscar-en-apuntes/ — le dice al agente cuándo y cómo buscar (herramienta de terminal, local). hermes skills list: buscar-en-apuntes · research · local · enabled.

Skills de ECC (el ganador del hackathon de Claude)

Instaladas 4 por hermes skills install (escaneadas SAFE): security-review, git-workflow, python-testing, coding-standards. Total 82 skills. El prompt fijo apenas sube (29 KB, lejos de 64k). Probada security-review con el 4B: checklist correcto, 4 min 30 s en frío.

Noche: expansión del RAG + reentrenamiento (auto mode, local, sin gitea)

Contenido añadido al índice PERSONAL (nunca al público sin revisar licencias):

  • Strix (usestrix/strix, agente IA de pentesting) clonado en CODERS; sus docs (README, docs/, AGENTS, benchmarks) indexados → 49 fragmentos.
  • 6 awesome lists más: sysadmin, cli-apps, security, pentest, shell, linux (catálogo pasa de 385 a 740 fragmentos). En awesome.py, publico=False.
  • Apps instaladas de esta máquina: nucleo/saber/instalado.py lee los .desktop (146 apps) + apt-mark showmanual (22 fragmentos). Personal por definición.
  • Índice personal: ~49 MB. Reindex raw hecho; contenido recuperable ya.

Reentrenamiento nocturno: nucleo/saber/enriquece/reentrena_noche.sh encadena síntesis de ganchos del contenido nuevo → reindex → eval → refresco COFRE (cosecha+sintetiza) → reindex final. Corre detached, log en datos/reentrena_noche.log.

Pendiente de revisar por la mañana: licencias de las 6 awesome nuevas antes de subir nada; y decidir qué de todo esto va al índice público / gitea.

Cierre de la noche (07:52) — números y decisiones

Dos rondas de reentrenamiento. Índice final: 64 MB, 19.578 entradas (12.260 preguntas-gancho, 1.383 catálogo, 168 apps, 459 metodología incl. strix).

Retrieval (eval_set ampliado a 57 casos = 45 pentest + 12 amplitud):

  • Ronda 1: hit@1 77%, hit@5 96%
  • Ronda 2 (+osint/devops/docker/go/python): hit@1 75%, hit@5 96%
  • Amplitud sola (apps/self-hosted/cli/linux/strix): hit@1 75%, hit@5 100%

Intercambio medido: pentest puro pasó de 82% (antes de la noche) a ~75% de hit@1; hit@5 se mantiene 95-96%. Es el precio de la amplitud. Las listas gigantes (awesome-go, 3197 items → 322 fragmentos; osint con algún artefacto de TOC) son las que más ruido meten y menos aporta a consultas por voz.

Decisiones para la mañana:

  1. Licencias de las 12 awesome lists antes de publicar nada (ahora todas publico=False = solo índice personal).
  2. Si se prioriza precisión pentest: quitar awesome-go (y quizá awesome-python) recuperaría 1-2 pts de hit@1 sin perder casi cobertura útil.
  3. Qué del contenido nuevo va al índice público / gitea (hoy: nada subido).