JARVIS/config/noche.sh
sito 8e4bc8ad94 JARVIS: asistente de voz local para Linux
Nucleo propio: oye con whisper.cpp, piensa con un modelo de Ollama, habla
con Piper, y hace RAG sobre los apuntes del usuario. 100% local, sin
cuentas ni claves.

Escrito bajo una restriccion dura, 4 GB de VRAM: el cerebro y whisper
comparten tarjeta y solo caben porque estan dimensionados para ello. El
RAG usa embeddings estaticos con busqueda hibrida; la voz clonada se sirve
de una cache de frases.

Incluye instalador (install.sh), requisitos, y documentacion del stack,
del manejo de root y de las acciones. Los apuntes indexados y el diario NO
se incluyen: son privados y el .gitignore los bloquea.
2026-08-16 15:34:37 +02:00

207 lines
8.7 KiB
Bash
Executable file

#!/usr/bin/env bash
# Pipeline nocturno: JARVIS se mejora solo mientras nadie mira.
#
# Cada ciclo hace lo mismo: mira los datos que ha dejado el uso real, propone
# un cambio, lo aplica, y lo somete a config/puerta.sh. Si la puerta dice que
# no, se revierte ENTERO con git y se pasa al siguiente. Nada se queda sin
# pasar por ahi.
#
# Por que git y no una copia: el arbol ya esta versionado, `git checkout` es
# atomico y deja rastro de lo que se intento. Al terminar la noche se puede ver
# commit a commit que se cambio y por que.
#
# noche.sh corre hasta que se le diga que pare
# noche.sh --ciclos 5 solo cinco vueltas
# noche.sh --seco propone y valida, pero NO commitea (para mirar)
#
# Para pararlo: touch ~/COFRE/CODERS/JARVIS/PARAR
#
# ── Que puede mejorar solo, y que no ────────────────────────────────────────
#
# Se mejora solo lo que se puede VALIDAR solo. Eso es:
# · el vocabulario: alias nuevos para ordenes que no se entendieron
# · las frases del catalogo: variantes de las que fallan
# · la voz: regenerar en la voz buena las frases que cayeron a la de reserva
# · el prompt: mantenerlo al dia con el catalogo
# · acciones nuevas a partir de lo que se pidio y no existia
#
# NO se toca solo el motor, el panel ni la logica de la aplicacion. No porque
# sea imposible generarlo, sino porque no hay forma automatica de comprobar que
# un cambio ahi es bueno: la suite dice si algo se rompio, no si algo mejoro.
# Un bucle que reescribe su propio validador acaba pasando siempre.
set -uo pipefail
# ── DESACTIVADO EL 15 DE AGOSTO DE 2026 ────────────────────────────────────
#
# Este bucle modifica el arbol de `newelle/` y COMMITEA solo. Ese arbol esta
# congelado en la etiqueta jarvis-newelle-final desde que el desarrollo se
# mudo a nucleo/: dejarlo correr escribiria commits automaticos sobre un
# repositorio que por definicion ya no cambia.
#
# Se ha quitado tambien su temporizador de systemd —estaba armado para las
# 03:30 y habria saltado solo—, movido a ~/.config/systemd/user/desactivados/.
#
# Esto de aqui es la segunda cerradura: aunque alguien lo lance a mano, se
# niega. Para retomarlo hay que apuntarlo primero al nucleo, y eso no es
# cambiar una ruta: la puerta (config/puerta.sh) valida el catalogo, el
# generador y la suite VIEJOS, y el nucleo tiene los suyos.
if [ "${JARVIS_NOCHE_A_SABIENDAS:-}" != "si" ]; then
cat >&2 <<'AVISO'
noche.sh esta desactivado.
Modifica y commitea sobre newelle/, que esta congelado en jarvis-newelle-final
desde que el desarrollo se mudo a nucleo/. Su temporizador de systemd tambien
se retiro.
Para retomarlo hay que reescribirlo contra el nucleo: la puerta valida el
catalogo, el generador y la suite viejos.
Si de verdad quieres correrlo tal cual sobre el arbol congelado:
JARVIS_NOCHE_A_SABIENDAS=si config/noche.sh
AVISO
exit 78
fi
RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)
cd "$RAIZ"
PARADA=$RAIZ/PARAR
DIARIO=$RAIZ/noche.log
CICLOS=0; SECO=no
while [ $# -gt 0 ]; do
case "$1" in
--ciclos) CICLOS=${2:-0}; shift 2;;
--seco) SECO=si; shift;;
*) shift;;
esac
done
log() { printf '%s %s\n' "$(date +%H:%M:%S)" "$*" | tee -a "$DIARIO"; }
# ── salvaguarda ────────────────────────────────────────────────────────────
cd "$RAIZ/newelle"
if [ -n "$(git status --porcelain)" ]; then
log "ABORTO: el fork tiene cambios sin commitear. Commitealos o descartalos."
exit 1
fi
ANCLA=$(git rev-parse HEAD)
git tag -f "noche-$(date +%Y%m%d-%H%M)" >/dev/null 2>&1
log "ancla: $ANCLA (para volver: git reset --hard $ANCLA)"
cd "$RAIZ"
# ── las mejoras, cada una en su funcion ────────────────────────────────────
# Cada una devuelve 0 si propuso algo, 1 si no habia nada que hacer.
mejora_vocabulario() {
# Lo que el usuario dijo y no se entendio. Es la fuente mas valiosa porque
# son fallos REALES, no inventados.
local registro=$HOME/.var/app/io.github.qwersyk.Newelle/config/jarvis-sin-accion.jsonl
[ -f "$registro" ] || return 1
python3 "$RAIZ/config/proponer_alias.py" --aplicar 2>&1 | tail -3
return ${PIPESTATUS[0]}
}
mejora_frases() {
# Variantes de las frases del catalogo, generadas con el modelo local y
# filtradas duro. Sube la cobertura sin tocar comandos.
python3 "$RAIZ/config/proponer_frases.py" --aplicar 2>&1 | tail -3
return ${PIPESTATUS[0]}
}
mejora_voz() {
# Las frases que cayeron a la voz de reserva se regeneran con la clonada.
[ -s "$RAIZ/voz/misses.log" ] || return 1
"$RAIZ/voz/clonador-venv/bin/python" "$RAIZ/voz/calienta_cache.py" cpu 2>&1 | tail -2
return 0
}
mejora_prompt() {
# El prompt lleva la lista de capacidades: si cambio el catalogo, hay que
# regenerarlo o el modelo sigue creyendo que sabe hacer otra cosa.
flatpak run --command=python3 io.github.qwersyk.Newelle//master \
"$RAIZ/config/set_persona.py" 2>&1 | grep -v WARNING | tail -1
return 0
}
mejora_reconocimiento() {
# La unica que genera sus PROPIOS datos: dice las frases del catalogo con
# varias voces, velocidades y ruidos, las transcribe con el mismo Whisper
# del asistente, y propone alias para lo que no llega a su accion.
#
# Es la que hace productiva la noche aunque nadie hable con JARVIS. Ya trae
# su propio guardarrail —solo aplica si el acierto sube Y la charla sigue
# yendo al modelo— y ademas pasa por la puerta como todo lo demas.
timeout 3000 python3 "$RAIZ/config/mejora_nocturna.py" --ronda --muestras 2 2>&1 | tail -4
return ${PIPESTATUS[0]}
}
MEJORAS=(mejora_reconocimiento mejora_vocabulario mejora_frases mejora_prompt mejora_voz)
# ── el bucle ───────────────────────────────────────────────────────────────
vuelta=0
aceptados=0; rechazados=0; sin_nada=0
log "=== empieza la noche ==="
while true; do
[ -f "$PARADA" ] && { log "parada pedida (existe $PARADA)"; break; }
[ "$CICLOS" -gt 0 ] && [ "$vuelta" -ge "$CICLOS" ] && { log "hechas $CICLOS vueltas"; break; }
vuelta=$((vuelta+1))
for mejora in "${MEJORAS[@]}"; do
[ -f "$PARADA" ] && break
log "── vuelta $vuelta · $mejora"
salida=$($mejora 2>&1); resultado=$?
[ -n "$salida" ] && echo "$salida" | sed 's/^/ /' | tee -a "$DIARIO" >/dev/null
cd "$RAIZ/newelle"
cambios=$(git status --porcelain)
cd "$RAIZ"
if [ -z "$cambios" ] && [ "$resultado" -ne 0 ]; then
log " nada que proponer"
sin_nada=$((sin_nada+1))
continue
fi
if [ -z "$cambios" ]; then
log " aplicado fuera del fork (voz o ajustes), sin commit"
aceptados=$((aceptados+1))
continue
fi
# ── LA PUERTA ──
if timeout 2400 "$RAIZ/config/puerta.sh" completa > "$RAIZ/.puerta.txt" 2>&1; then
if [ "$SECO" = si ]; then
log " [seco] habria pasado la puerta; se revierte igual"
(cd "$RAIZ/newelle" && git checkout -- . && git clean -fd >/dev/null 2>&1)
else
(cd "$RAIZ/newelle" && git add -A && \
git commit -q -m "auto($mejora): mejora nocturna
Propuesto y validado por config/noche.sh sin intervencion. Paso la
puerta completa: catalogo valido, generador reproducible, emparejador
sin perdidas, 150 acciones respondiendo y la suite en verde.
$(echo "$salida" | head -3)")
log " ACEPTADO y commiteado"
aceptados=$((aceptados+1))
fi
else
log " RECHAZADO: $(tail -1 "$RAIZ/.puerta.txt")"
(cd "$RAIZ/newelle" && git checkout -- . && git clean -fd >/dev/null 2>&1)
rechazados=$((rechazados+1))
fi
done
# Si una vuelta entera no propuso nada, no tiene sentido seguir dando
# vueltas en vacio: se espera a que el uso genere datos nuevos.
if [ "$sin_nada" -ge $(( ${#MEJORAS[@]} )) ]; then
log "una vuelta entera sin nada que hacer; esperando 30 min"
for _ in $(seq 180); do [ -f "$PARADA" ] && break; sleep 10; done
sin_nada=0
fi
done
log "=== fin: $aceptados aceptados, $rechazados rechazados, $vuelta vueltas ==="
cd "$RAIZ/newelle"
log "commits de esta noche:"
git log --oneline "$ANCLA"..HEAD 2>/dev/null | sed 's/^/ /' | tee -a "$DIARIO"