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.
207 lines
8.7 KiB
Bash
Executable file
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"
|