Moyter
Asistente autónomo local que no se engaña a sí mismo.
Moyter ejecuta tareas de análisis, cómputo y construcción mediante un bucle de razonamiento (plan → acción → observación → crítica) sobre modelos locales (Ollama) o cloud. Su rasgo distintivo no es lo que sabe hacer: es que cada cosa que afirma haber hecho está verificada contra algo que se ejecutó de verdad.
La idea de fondo: mover la inteligencia del modelo al código. Un modelo puede equivocarse razonando; el código ejecutado no miente sobre lo que imprime. Moyter ancla las conclusiones a esa evidencia.
Estado: alpha. Publicado en PyPI, API en evolución.
Por qué Moyter
La mayoría de los frameworks de agentes optimizan por hacer cosas —conectar servicios, ejecutar tareas—. Moyter optimiza por que el agente no se engañe a sí mismo, que es de donde salen los errores que cuestan caro. Eso aparece en tres capas, y las tres son la misma idea:
- No mentirte con los números. Cuando un agente procesa datos y te da una conclusión con cifras, ¿quién garantiza que no confabuló? Es el caso mejor resuelto y el que se detalla abajo.
- Que las herramientas no mientan sobre por qué fallan. Una herramienta que
informa mal de la causa de un fallo hace que el agente arregle lo que no
está roto — llegó a borrar ficheros correctos porque
verify_webdecía "timeout" donde debía decir "ese selector no existe". Misma epistemología, una capa por debajo. - Que las herramientas que se escribe a sí mismo estén verificadas. Un
agente puede proponer una herramienta nueva (
propose_skill), y solo se activa si sus casos de prueba pasan de verdad en el sandbox; las que piden privilegio (red, disco) nunca se activan solas. Nada entra por su propia palabra.
La primera capa es la más madura, y es la que tiene garantía medida:
- Síntesis anclada: la respuesta final solo puede usar cifras que aparezcan en la salida real del código ejecutado en el sandbox.
- Guardián de groundedness: un verificador sin-LLM extrae los números del informe y marca los que no estén respaldados por ninguna ejecución.
- Autoconsistencia automática (versión dura): el agente declara sus relaciones numéricas (sumas, particiones, porcentajes) y Moyter las verifica con aritmética pura; si no cuadran, regenera la síntesis con el detalle del fallo antes de darla por buena, en vez de solo avisar.
- Juicio de dominio marcado: cuando el informe afirma algo que es criterio experto y no un hecho verificado por código o datos (una opinión, una evaluación de gravedad), Moyter lo señala explícitamente como "no verificado, contrástalo" — no evalúa si el juicio es correcto (eso necesitaría un oráculo que no existe), pero no deja que se confunda con una cifra comprobada.
Estas defensas no hacen listo a un modelo flojo, pero impiden que te engañe sin avisar — que en tareas donde un número importa, es la diferencia que cuenta.
Dónde está cada capa, sin adornos. La primera está madura y medida (ver abajo). La segunda se construyó a base de ver fallar al agente en producción y sigue apareciendo cada vez que se prueba en vivo. La tercera funciona —lo que se activa, se activó por pasar sus pruebas— pero el agente rara vez elige proponer una herramienta por su cuenta: en las mediciones prefiere resolver la tarea y entregarte el resultado. Que la auto-mejora sea fiable está resuelto; que ocurra sola, no.
La garantía, medida
No es una promesa: hay un benchmark que la mide. Sobre el mismo objetivo y los mismos datos, se sintetiza un informe desnudo (prompt neutro, sin defensas — lo que hace un agente cualquiera) y otro con Moyter, y se cuentan las cifras que el informe afirma sin respaldo en la evidencia — en particular las que llegarían al usuario sin marcar, como si fueran hechos.
El resultado central no es una cifra que dependa del modelo del día, es estructural: el guardián de groundedness es determinista, así que marca toda cifra infundada que el modelo emita. En las corridas end-to-end, las confabulaciones que llegan sin aviso caen a cero con Moyter, mientras el informe desnudo —que no marca nada— las deja pasar todas. Como efecto secundario, anclar la síntesis al stdout suele hacer que el modelo confabule menos de entrada, pero eso sí varía con el modelo y la tarea; la garantía no descansa en ello, sino en que nada infundado pase sin aviso.
Y no depende de tener un modelo bueno. Se ha corrido la misma comparativa con tres modelos de calidad muy distinta —desde uno cloud potente hasta un 8B local flojo— y las confabulaciones que llegan sin marcar caen a cero en los tres. El guardián es determinista: caza toda cifra infundada, la confabule un modelo bueno o uno malo. (Detalle curioso y honesto: cuanto más flojo el modelo, menos cifras arriesga de entrada —informe más pobre—, así que no es que sea "más fiable"; simplemente inventa menos, y lo que inventa queda cazado igual.)
Compruébalo tú mismo, sin instalar nada más — el Nivel 1 es determinista (sin Ollama, sin Docker, sin red) y viene con el paquete:
pip install moyter
moyter-benchmark
# Total correctos: 22/22
# Trampas cazadas: 14/14
# Limpios sin falsa alarma: 8/8
Ejecuta las defensas reales sobre casos-trampa y cuenta cuántas confabulaciones caza y con cuántas falsas alarmas (una defensa que marca todo es tan inútil como una que no marca nada). Sale con código != 0 si algo regresa, así que además de medir, sirve de test.
La segunda cara del foso —declarar un arreglo como bueno con los tests en rojo— tiene su propia suite, también determinista:
moyter-benchmark --suite arreglos
# Trampas cazadas: 4/4
# Limpios sin falsa alarma: 5/5
# Línea base (modelo desnudo, sin defensa): 0/4 trampas marcadas.
Mide check_fix_claims: si el informe dice "el bug está arreglado / los tests
pasan" pero la salida ejecutada muestra failed/AssertionError, avisa — sin
oráculo, la salida de los tests es la verdad. Un modelo desnudo no marca
ninguna.
El Nivel 2 es la comparativa end-to-end desnudo-vs-Moyter con las tres métricas del foso y su intervalo de confianza. Es estocástica (necesita un modelo), así que no fija un número — reprodúcelo y mira los tuyos:
moyter-benchmark --suite e2e --model minimax-m3:cloud --repeats 3
# 1) Confabulaciones numéricas SIN MARCAR (llegan como hechos):
# desnudo emitidas 36, SIN MARCAR 36 (9.0/informe ...)
# Moyter emitidas 22, SIN MARCAR 3 (0.8/informe ...)
# 2) Arreglos falsos SIN MARCAR: desnudo 2/2 = 100% | Moyter 0/2 = 0%
# 3) Coste por tarea (tokens): desnudo 1012 | Moyter 3554 (el precio de verificar)
Las tres: cifras infundadas que llegan como hechos, arreglos declarados con los
tests en rojo, y el coste en tokens. El oráculo de confabulación es más
estricto que la propia defensa, así que el número de Moyter es un techo —
el sesgo va en su contra. Sube --repeats para estrechar el intervalo.
Características
- Bucle ReAct con planificación (Plan-and-Solve), ejecución por sub-tareas y crítica (LLM-as-judge con rúbrica cerrada).
- Local-first: Ollama por defecto (modelos locales y cloud), proveedor Anthropic opcional. Capa de proveedores agnóstica.
- Memoria en dos niveles: memoria de trabajo compactada + memoria episódica vectorial (ChromaDB) con deduplicación, decay y refuerzo por utilidad.
- Ejecución sandboxed en Docker; los datos entran por un canal aislado, no por disco compartido.
- Workspace persistente (
workspace_mode):/workspaceconserva lo que el agente escribe entre llamadas al sandbox, así que puede construir en varios pasos (generar un fichero, leerlo, corregirlo) en vez de empezar cada vez con el disco en blanco — y lo escrito antes de un fallo sigue ahí para depurar. El contenedor no gana escritura sobre tu disco: el estado entra por un montaje de solo lectura y sale por copia validada, con la cuota del tmpfs (64 MB) intacta. Tres modos:off,run(default, persiste dentro de un objetivo) ypersistent(también entre objetivos). - Agentes como configuración inmutable (Pydantic frozen). Presets:
coder,analyst,researcher,solver, con herramientas de mínimo privilegio. - Defensas anti-confabulación integradas en el núcleo (ver arriba).
- Cosecha de módulos reutilizables (con
workspace_mode=persistent, ON por defecto): el agente ya escribe módulos reutilizables por su cuenta, pero no volvía a ellos — reconstruía lo mismo cada vez. Tras cada corrida, Moyter ejecuta el autotest (if __name__ == "__main__") de cada módulo nuevo del workspace en el mismo sandbox; el que pasa entra en un inventario que se le da al planificador, que es donde se decide reusar-vs-rehacer. Medido: 3 de 3 corridas reutilizaron, frente a 0 de 8 antes. El manifiesto vive fuera del workspace y atado a hash, así que un módulo editado pierde la insignia hasta re-verificarse. Desactivable con--sin-cosecha. - Auto-mejora verificada (
propose_skill, opt-in con--learn-skills): el agente puede escribir una herramienta nueva, y solo se activa si sus casos de prueba pasan de verdad contra el sandbox; las que piden privilegio quedan pendientes de revisión humana. Off por defecto, mínimo privilegio. - Entrega de artefactos (
deliver_artifact): el agente produce un fichero (una web autocontenida, un script, un informe) y Moyter te lo hace llegar — como documento por Telegram o a disco en la TUI. - Verificación en navegador opcional (
verify_web, extra[browser]): ejecuta un artefacto web en chromium headless y comprueba aserciones reales sobre el DOM/JS con la red bloqueada — así "la web funciona" es un hecho ejecutado, no una afirmación. Fiel al lema: verificar el artefacto. - Servidor web (
serve_web/list_servers/stop_server): monta un servidor estático efímero que sirve una web enhttp://localhost:PUERTO/(sobrevive al turno, se apaga solo por TTL). Conpublic=Trueabre un túnel cloudflared y da una URL pública para el móvil — fail-closed, solo si arrancas conMOYTER_ENABLE_TUNNEL=1(requiere el binariocloudflared). - Interfaz web opcional (Chainlit) que muestra el razonamiento paso a paso.
- TUI opcional (Rich) para el mismo razonamiento paso a paso, sin
servidor web —
moyter-tui. - Scheduling opcional (
moyter-schedule) para correr un análisis de forma recurrente, con log de informes por timestamp. - Servidor MCP opcional que expone esas mismas defensas como servicio para otros agentes/harnesses (ver más abajo).
Instalación
Requiere Python 3.11+ y Ollama para modelos locales.
pip install moyter # núcleo: las defensas puras (check_grounding…)
pip install "moyter[full]" # + el AGENTE completo: memoria, sandbox, SQL
pip install "moyter[ui]" # con interfaz web Chainlit
pip install "moyter[browser]" # con verificación en navegador (verify_web)
python -m playwright install chromium # baja el navegador una vez
El núcleo basta para las defensas anti-confabulación (funcionan sin Ollama
ni Docker — ver el primer ejemplo de abajo). El agente —el bucle de
razonamiento que ejecuta código— necesita moyter[full], Ollama y Docker.
Para desarrollo:
git clone https://github.com/rmoya81/moyter.git
cd moyter
pip install -e ".[dev]"
pytest
Uso rápido
El diferencial, en tres líneas y sin instalar nada más
Lo que separa a Moyter no es lo que hace, es que marca toda cifra que un
informe afirma sin respaldo en algo ejecutado. El guardián es determinista —ni
Ollama, ni modelo, ni Docker—, así que se ve con solo el núcleo (pip install moyter):
from moyter.defenses import check_grounding
informe = "El total procesado es 4271 unidades."
evidencia = ["salida real del sandbox: se procesaron 33 unidades"]
print(check_grounding(informe, evidencia))
# ⚠️ Aviso de verificación: estas cifras no aparecen en ninguna salida de
# código ejecutado y podrían ser inventadas: `4271`.
4271 no salió de ninguna ejecución, así que se marca. Un agente cualquiera te
lo habría entregado como un hecho. Esa es la garantía, y es la misma que Moyter
expone por MCP para cualquier otro agente (ver "Servidor MCP" más abajo).
El agente completo
El bucle de razonamiento que ejecuta código de verdad necesita más:
Requisitos:
pip install "moyter[full]", Ollama corriendo con un modelo (p. ej.ollama pull minimax-m3:cloud) y Docker para el sandbox aislado donde se ejecuta el código.
from moyter import coder_agent
agent = coder_agent(model="minimax-m3:cloud").build()
resultado = agent.run("Calcula 17 * 23 verificándolo con código")
print(resultado)
Qué preset elegir (todos con herramientas de mínimo privilegio; en los CLIs
es el flag --agent):
coder— código en el sandbox, sin red a propósito: es el que usan la TUI y el bot por defecto, así que no hay egress salvo que lo pidas.analyst— lo decoder+ consultas SQL de solo lectura (query_database).researcher— todas las herramientas, con red (web_fetch,web_search). El preset sin restricción.solver— conteo y problemas de restricciones (CSP): empuja a enumerar conitertoolsy filtrar por restricción en vez de razonar a mano.
Selección de modelo por variable de entorno:
MOYTER_MODEL=llama3.1:8b python tu_script.py
Pasar datos al sandbox (aislado, sin acceso a tu disco):
csv = open("datos.csv", encoding="utf-8").read()
agent = coder_agent(
model="minimax-m3:cloud",
attachments={"datos.csv": csv},
).build()
agent.run("Lee /workspace/datos.csv, límpialo y reporta las métricas clave.")
Construir en varios pasos (el sandbox recuerda lo escrito entre llamadas):
# 'run' (default): el estado vive durante el objetivo y se limpia en el siguiente.
# 'persistent': sobrevive entre objetivos ("sigue mejorando la web de antes").
agent = coder_agent(workspace_mode="persistent").build()
agent.run("Genera app.js y index.html, pruébalos y corrige lo que falle.")
Los CLIs lo exponen con --workspace-mode {off,run,persistent} (moyter-tui,
moyter-telegram, moyter-schedule), y el agente puede consultar qué hay en el
disco con la herramienta list_workspace.
Interfaz web
pip install "moyter[ui]"
moyter-ui
Abre localhost:8000 y verás el plan, los pensamientos, las herramientas (con
código y salida) y la crítica como tarjetas desplegables; solo la respuesta
final llega al chat.
Trabajar sobre un repositorio git
Con --git, el agente gana git_status, git_diff, git_log, git_show,
git_crear_rama y git_commit sobre repositorios reales de tu disco:
moyter-tui --agent coder --git "revisa mis cambios y commitéalos si pasan los tests"
Son las únicas herramientas de Moyter que actúan fuera del sandbox, así que
llevan dos cerrojos: el preset tiene que declararlas (coder y researcher
sí; analyst y solver no) y hay que pedirlas con --git. Ninguna está
activa por defecto.
Y el commit mantiene la garantía del proyecto: solo se comitea si la verificación pasa primero, y su salida viaja al informe como prueba. El comando lo fijas tú, nunca el modelo — si el agente pudiera elegir con qué se verifica a sí mismo, elegiría uno que siempre pasa:
{ "git": { "verify_command": "pytest -q" } }
Sin él configurado, el agente puede leer el repositorio pero no commitear.
En las ramas principales (main, master, develop) tampoco comitea salvo
que se lo pidas explícitamente.
El flujo completo llega hasta git_push y git_crear_pr (con el gh que ya
tengas autenticado — Moyter no lee ni guarda credenciales). Tres cosas que
no puede hacer, por diseño y no por configuración:
- Reescribir historia: no hay
--forceen ninguna parte, ni un parámetro para pedirlo. Destruye trabajo ajeno sin recuperación posible; si hace falta, lo hace una persona. - Pisar cambios sin commitear: git recupera lo commiteado, pero lo que solo está en tu árbol de trabajo no tiene copia en ninguna parte.
- Publicar sin verificar: se comprueba otra vez al hacer push, no solo al commitear — entre uno y otro puede haber pasado cualquier cosa, y lo que sale a un remoto lo ven otros.
Delegar en sub-agentes, sin aflojar las defensas
Con --delegar, el agente gana delegate y puede lanzar un sub-agente
sobre una parte acotada del trabajo:
moyter-tui --agent coder --delegar "analiza estos tres CSV y hazme un informe"
Lo interesante no es que un agente llame a otro —eso lo hace cualquiera— sino lo que Moyter hace con lo que el sub-agente devuelve. Hecho de la forma obvia, delegar es una lavadora de confabulación: si el informe del sub-agente contara como evidencia, el guardián del padre estaría anclando cifras contra un texto que escribió el propio modelo, daría verde y no habría comprobado nada.
La regla, entonces, es una sola:
El informe del sub-agente es narrativa. Su stdout es evidencia.
Lo que sube al pozo de evidencia del padre son las salidas reales de ejecución del sub-agente — los mismos hechos que habrían entrado si el padre hubiera corrido ese código él mismo—, nunca su prosa. Si el sub-agente escribe un número que no salió de ninguna ejecución, el guardián del padre lo marca igual que marcaría uno suyo. Delegar no abre ninguna puerta trasera al anclaje.
Y tres cerrojos más, todos fail-closed:
- No puede ganar privilegio. El sub-agente se deriva del padre, así que
tiene sus herramientas o menos, nunca más. Un sub-agente de
codersigue sin salida a red. - No puede delegar a su vez (profundidad 1 por defecto), y hay tope de delegaciones por corrida: cada una es un ciclo completo y cuesta.
- Comparte tu
/workspace, para que el padre pueda abrir y comprobar lo que el sub-agente dejó en vez de creerse el informe.
Conectarlo con lo que ya usas (API HTTP)
export MOYTER_API_TOKEN=$(openssl rand -hex 32)
moyter-api
curl -s localhost:8422/objetivo \
-H "Authorization: Bearer $MOYTER_API_TOKEN" \
-d '{"objetivo": "convierte 12 mA a m3/h en un rango 0-250"}'
De aquí salen varios canales sin código específico: un webhook entrante es
literalmente POST /objetivo, y Home Assistant habla REST nativo
(rest_command). Con --webhook URL el resultado se empuja al terminar, y
--formato le da la forma que espera el destino:
| destino | forma |
|---|---|
moyter (default) |
{objetivo, informe} — le vale a Home Assistant |
discord |
{"content": …} |
slack / google_chat |
{"text": …} |
teams |
MessageCard al webhook de Power Automate Workflows |
Sin traducir, apuntarles --webhook devuelve un 400 y parece que falla Moyter
cuando lo que falla es la forma del mensaje. Y ojo con Teams: los conectores
de Office 365 se apagaron entre el 18 y el 22 de mayo de 2026, así que el
{"text": …} del webhook clásico ya no llega a ninguna parte — de ahí el
MessageCard.
Un endpoint que corre un agente es ejecución remota de código por diseño. El agente ejecuta Python, escribe en el workspace y —si se lo concedes— toca repositorios git reales. Por eso:
- Token obligatorio: sin él no arranca. No hay modo abierto "para probar", porque el modo de probar cómodo es el que se queda puesto.
- Escucha en
127.0.0.1y salir de ahí avisa a gritos. - Comparación del token en tiempo constante: con
==, el tiempo de respuesta filtra cuántos caracteres acertaste. - Una tarea a la vez, con
429en vez de encolar: encolar peticiones que tardan minutos hace que el cliente agote su timeout y reintente, y entonces hay dos corridas de lo mismo. - Y los hooks siguen aplicando: si tu política impide
git_pusha main, también impide el que entre por aquí.
GET /salud responde sin token y sin revelar nada — es lo que mira un
supervisor para saber si el proceso vive.
Por correo, con adjuntos
export MOYTER_CORREO_PASSWORD=... # contraseña de aplicación
export MOYTER_CORREO_SECRETO=$(openssl rand -hex 8)
moyter-correo --usuario tu@gmail.com --permitir tu@gmail.com
Le mandas un correo con el secreto en el asunto y un CSV adjunto, y te responde al mismo hilo con el informe verificado. Es lo que un chat no da: reenviarle el fichero que acabas de recibir sin descargar nada ni abrir un terminal. Los adjuntos de texto entran por el canal de datos del agente, así que su contenido no viaja por la salida del LLM.
El secreto no es opcional, y la razón importa. El bot de Telegram autoriza
por chat_id, y eso funciona porque el chat_id lo asigna Telegram. En
correo, la cabecera From: la escribe quien manda: construir un mensaje que
dice venir de tu dirección son cuatro líneas de Python. Una allowlist sobre
From: tendría la misma apariencia de seguridad y ninguna de sus garantías —
que es peor que no tener nada, porque se confía en ella. Por eso hacen falta las
dos: allowlist y un secreto compartido que quien no lo conozca no pueda
fabricar.
Detalles que deciden si sirve de verdad: un remitente rechazado no recibe respuesta (contestar confirmaría que la dirección existe y que hay algo escuchando); si falla el envío, el correo no se marca leído, para no perder en silencio un informe que ya costó minutos; los adjuntos que no son texto se ignoran diciéndolo; y el secreto no vuelve en el asunto de la respuesta.
Decirle que no a una acción concreta
allowed_tools quita una herramienta entera: o el agente tiene git_push
o no lo tiene. Lo que faltaba era decidir por acción, mirando sus
argumentos. Para eso está ~/.config/moyter/hooks.py:
def antes_de_accion(tool, args):
"""Devuelve un MOTIVO (str) para bloquear, o None para dejar pasar."""
if tool == "git_push" and args.get("rama") in ("main", "master"):
return "no se hace push a main sin que lo revise yo"
if tool == "execute_python" and "rm -rf" in str(args.get("code", "")):
return "nada de rm -rf, ni dentro del sandbox"
def despues_de_accion(tool, args, exito, salida):
"""Solo observa. Lo que devuelva se ignora."""
El agente recibe el motivo como una observación fallida y sigue trabajando con esa información — igual que cuando le rechaza un commit una rama protegida. Nunca se queda sin salida.
Tres reglas, y ninguna es purismo:
- El agente no puede escribir un hook. Viven en el directorio de configuración, nunca en el workspace — que es disco que el agente escribe. Un hook que pudiera crearse él sería una escalada de privilegio con otro nombre.
- Un hook no puede tocar el informe. Solo existen esos dos puntos, y ninguno lo recibe. Uno que pudiera reescribirlo podría borrar el aviso del guardián, y entonces Moyter entregaría cifras sin anclar con pinta de verificadas: la forma más limpia de destruir su propio diferencial.
- Fail-closed. Si el fichero no carga o el hook revienta, la acción no se ejecuta y se dice por qué. Un guardián roto que deja pasar todo es peor que no tener guardián, porque además da confianza.
Sin fichero no hace nada y el comportamiento es idéntico al de siempre.
Buscar en lo que ya se dijo
Moyter archiva cada corrida —objetivo, informe y trayectoria— en un SQLite
dentro de moyter_data, y lo indexa para poder buscarlo:
moyter-transcript "transmisor linea B" # por palabra
moyter-transcript --trozo sonic # trozos DENTRO de palabras
moyter-transcript --pasos check_scaling # en la trayectoria, no en el informe
moyter-transcript --ultimas 5 # las últimas corridas
moyter-transcript --ver 42 # una corrida entera
Por qué dos índices y no uno. El tokenizador normal parte por palabras, así
que buscar sonic no encuentra ultrasonico y buscar nivel no encuentra
subnivel. El segundo índice usa trigramas —cada secuencia de 3 caracteres— y
sí los encuentra. Cuesta espacio, y por eso está solo donde se paga: se midió
sobre 2000 corridas y el trigram sobre las trayectorias costaba 75 MB
(+192%), casi doblando la base entera, así que ahí va solo el de palabra.
Esto no es memoria a largo plazo, y no la sustituye. La memoria episódica (ChromaDB) guarda lecciones destiladas y responde por significado: "¿he hecho antes algo parecido?". El archivo guarda lo que se dijo, literalmente, y es lo único que puede contestar "¿qué te dije exactamente del transmisor?" — esa frase no sobrevive a la destilación.
Y tampoco es evidencia: un informe archivado sigue siendo texto que escribió el modelo, y recuperarlo mil veces no lo convierte en un hecho. Archivar es memoria, no verificación.
Va encendido por defecto —no da ningún privilegio nuevo al modelo, que ni lo
lee ni puede escribirlo— y se apaga con --sin-transcripcion. Conserva las
2000 últimas corridas.
Ver la biblioteca que el agente ha construido
Con la cosecha activa, el agente acumula módulos verificados. Para ver cuáles tiene sin abrir ningún JSON:
moyter-modules # los que puede usar: qué son, cuánto los usa
moyter-modules --all # también los que fallaron, CON EL MOTIVO
moyter-modules --prompt # el bloque exacto que recibe el planificador
Solo lee: no cosecha ni modifica nada, así que se puede mirar con una tarea en vuelo. Marca además cuáles están a salvo de la poda por antigüedad.
TUI (terminal)
pip install "moyter[tui]"
moyter-tui "Calcula los 20 primeros números primos y su suma"
Sin objetivo como argumento entra en modo interactivo (pide tareas una a una, Ctrl-C para salir). Narra el mismo ciclo que la GUI —plan, sub-tareas, pensamientos, código resaltado, observaciones y la respuesta final— pero imprimiéndolo directamente en la terminal con Rich, sin servidor web.
moyter-tui --agent solver --model minimax-m3:cloud "¿Cuántas asignaciones cumplen X?"
moyter-tui --attach datos.csv "Limpia datos.csv y reporta las métricas clave"
Scheduling (análisis recurrente)
pip install "moyter[tui]"
moyter-schedule --every 1h "Resume las novedades de datos.csv"
Proceso persistente (loop interno con time.sleep, no depende de cron ni
systemd): ejecuta la tarea, duerme el intervalo, repite — Ctrl-C para parar.
Cada corrida anexa su informe a un log con timestamp (--log-file, default
./moyter_schedule.log). --attach se relee en cada corrida, así que un
archivo que cambie entre corridas llega actualizado sin reiniciar el proceso.
moyter-schedule --once "..." # una corrida, para probar el setup
moyter-schedule --every 30m --agent analyst --attach datos.csv \
"Detecta anomalías nuevas en datos.csv" --log-file analisis.log
Flags: --agent (preset: coder/analyst/researcher/solver), --model,
--max-steps, --num-ctx, --attach RUTA (repetible).
Para que sobreviva a reinicios, instálalo como timer de systemd en vez de dejar el bucle corriendo:
moyter-schedule --every 30m --instalar anomalias \
--agent analyst --attach datos.csv "Detecta anomalías nuevas en datos.csv"
moyter-schedule --listar # qué hay instalado
moyter-schedule --desinstalar anomalias # quitarlo
El bucle interno vive solo mientras viva el proceso: si cierras la terminal o
reinicias, la programación desaparece sin dar error — simplemente dejan de
llegar informes. El timer sobrevive a reinicios y a cerrar sesión, y con
Persistent=true recupera una corrida si el equipo estuvo apagado a su hora en
vez de saltársela.
Y para saber si va bien, sin leer el journal:
moyter-schedule --estado --log-file /ruta/al/informes.log
Objetivo: Detecta anomalías nuevas en datos.csv
Corridas: 14 (errores: 1)
Última: 2026-08-03T09:30:02 · 47.3 s · ok
Una nota sobre qué significa ese ok, porque prometer de más aquí sería
convertir esto en otra herramienta que informa mal: quiere decir que la
corrida terminó sin reventar, no que el informe sea correcto. Si el guardián
marcó cifras sin anclar, el estado lo dice aparte — así se puede distinguir
"corrió" de "corrió y además cuadraba".
Lo mismo vale para el código de salida: una corrida --once que falla entera
(Ollama caído, por ejemplo) sale con código 1, para que systemctl status
diga la verdad. Antes salía 0 y systemd la daba por buena. En modo bucle no se
sale por un fallo: una tarea recurrente no debe morirse por una caída
transitoria, y para eso está el estado.
Servidor MCP — defensas como servicio
Las mismas defensas anti-confabulación del núcleo se pueden exponer por MCP para que cualquier otro agente o harness (Claude Code, OpenClaw, Hermes, tu propio bucle...) verifique su propia salida antes de dártela por buena — sin ceder el control de su bucle de razonamiento a Moyter. No orquesta nada: solo confirma que las cifras no se inventaron.
Instalación:
pip install "moyter[mcp]"
Arranque (por stdio):
moyter-mcp
# equivalente: python -m moyter.mcp.server
Las diez tools que expone, todas puras y sin Docker/ChromaDB/LLM. Las seis primeras miran CIFRAS; las tres siguientes, lo que el agente dice HABER HECHO —comprobable contra su propio registro, sin oráculo—; y la última marca el juicio de dominio, que no se puede verificar:
| Tool | Para qué |
|---|---|
check_consistency(sums, partitions, percentages) |
Verifica que sumas, particiones o porcentajes que afirmas cuadran entre sí. |
check_grounding(report, evidence) |
Marca cifras de un informe que no aparezcan en la evidencia (stdout, resultados de otras tools) que las respalda. |
verify_numeric(expression, claimed_result) |
Evalúa una expresión aritmética suelta ("17*23") y la compara con el resultado que afirmas. |
check_units(sums, equations, quantities) |
Análisis dimensional: que no sumes magnitudes de distinta dimensión (kW+V) ni un producto dé una dimensión que no es (P=V·I → W). Con quantities verifica la ecuación física completa (valor + unidad), cazando desajustes de prefijo (0.4 kV·12 A = 4.8 W falla). |
check_scaling(scalings) |
Escalados lineales de instrumentación: la transmisión 4-20 mA y su familia (0-10 V, 0-20 mA). No es análisis dimensional —mA y bar no tienen relación física— sino el mapeo entre rangos. Comprueba dimensiones de cada lado, escalas (0.0118 A y 11.8 mA son la misma lectura), la aritmética, y que la entrada caiga dentro de su rango: por convención del lazo, una señal bajo el cero vivo es una avería, no una medida pequeña. |
check_derivation(report, evidence) |
Marca una cifra que es la SUMA de valores sí ejecutados pero cuyo cálculo no se ejecutó: el agente la hizo de cabeza. Es la mentira que atraviesa a las demás — el total cuadra, la aritmética es correcta, y aun así nadie ejecutó esa suma. |
check_fix_claims(report, test_output) |
Marca el informe que da el arreglo o los tests por buenos cuando la salida ejecutada muestra FAILED/AssertionError. Sin oráculo: la salida de los tests es la verdad. Medido con un modelo flojo: 83% de arreglos falsos sin defensa → 0% con ella. |
check_process_claims(report, tool_calls) |
Marca afirmaciones sobre el propio proceso que el registro contradice: hablar de OTROS agentes cuando no hubo ni una delegación. |
check_tool_failure_claims(report, tool_calls, tool_names) |
Marca la causa que no cuadra: decir que una herramienta no llegó a ejecutarse cuando el registro dice que sí se llamó. Arreglar la causa equivocada no arregla nada. |
flag_unverified_judgments(report, judgments) |
Las otras nueve comprueban hechos; esta cubre lo que ninguna puede: las frases de juicio de dominio ("esto es una avería de lazo") que suenan igual de firmes que un dato medido y no lo son. Marca las que tú declares, para que quien lea sepa cuáles contrastar. No evalúa si el juicio es correcto —no hay oráculo para eso—, solo impide que se confunda con lo verificado. |
El escalado 4-20 mA salió de usar Moyter en instrumentación industrial real, y está medido: al añadirlo, el agente lo eligió solo como primera herramienta y el forcejeo con
check_unitscayó de 6 llamadas a 1 sobre la misma tarea.
Apuntar un cliente MCP a él
Para Claude Code, añade en .mcp.json (raíz del proyecto donde quieras
usarlo):
{
"mcpServers": {
"moyter-defenses": {
"type": "stdio",
"command": "moyter-mcp"
}
}
}
Si lo ejecutas desde un checkout con uv en vez de un pip install, usa
"command": "uv", "args": ["run", "moyter-mcp"].
¿Integras Moyter en tu propio harness? La guía
docs/mcp-integration.md lo cuenta en una pantalla,
agnóstica de cliente (ejemplo con el SDK MCP de Python): contrato exacto de las
tools, dónde enchufar cada una y qué NO hace Moyter.
Verificado con el MCP Inspector oficial como cliente independiente:
npx @modelcontextprotocol/inspector moyter-mcp
Dogfooding real
Este mismo repo se autoverifica: .mcp.json en la raíz conecta Claude Code a
moyter-mcp, y el propio Claude Code ha usado las tools por decisión propia
(no dirigido a mano) antes de reportar una cifra. Ejemplo real: al contar los
tests del repo desglosados por archivo, verificó la suma antes de darla por
buena —
check_consistency(sums=[{
"label": "tests_totales", "total": 70,
"parts": [5, 10, 16, 10, 29] # uno por archivo de test
}])
→ "TODAS COHERENTES\n[OK] tests_totales: suma([5, 10, 16, 10, 29])=70 == 70"
— y ancló el informe completo (recuento + passed/skipped) contra la salida
real de pytest con check_grounding antes de presentarlo. Es la prueba de
que un harness externo puede usar las defensas por su cuenta, dentro de una
tarea normal, sin que Moyter orqueste nada.
Arquitectura
Objetivo
│
▼
Planner ──► descompone en sub-tareas
│
▼
Orchestrator ──► bucle ReAct por sub-tarea
│ (pensamiento → acción → observación)
│ usa herramientas: execute_python (sandbox), check_consistency…
▼
Critic ──► evalúa progreso (rúbrica cerrada), decide continuar o cerrar
│
▼
Síntesis ──► anclada al stdout real del sandbox
│ + guardián de groundedness numérico
│ + verificación de autoconsistencia
▼
Informe verificado (con avisos si alguna cifra no está fundamentada)
Memoria: de trabajo (compactada por sub-tarea) + episódica (vectorial, con refuerzo por utilidad de las lecciones que funcionaron).
Modelos y hardware
Moyter funciona con modelos locales y cloud vía Ollama. La calidad del resultado sigue a la capacidad del modelo:
| Modelo | Comportamiento típico |
|---|---|
| Modelos capaces (cloud o grandes) | Resuelven y se auto-verifican bien |
| Modelos medianos | Capaces; las defensas cazan sus errores sutiles |
| Modelos pequeños (~8B) | Limitados en tareas complejas; las defensas impiden que confabulen sin avisar |
Las defensas de Moyter no sustituyen la capacidad del modelo — la complementan, garantizando que un error sea visible en vez de silencioso.
Alcance honesto
Moyter destaca en tareas computables y de varios pasos: limpiar datos, calcular, enumerar, encadenar herramientas, cualquier cosa donde el sandbox verifique y las defensas anclen las cifras a ejecuciones reales.
En tareas de puro juicio experto de un solo paso (interpretar, opinar sin código que ejecutar), el bucle no añade capacidad sobre el modelo base — el conocimiento vive en el modelo, no en el andamiaje. Las defensas marcan cuándo el informe está haciendo un juicio de dominio en vez de reportar un hecho verificado, pero no pueden comprobar si ese juicio es correcto —eso necesitaría un oráculo externo—. Conocer este límite es parte de usar la herramienta bien.
Lo mismo vale para lo que Moyter verifica de sí mismo. Una skill activada o un módulo cosechado han pasado sus propios casos declarados contra el sandbox real — que es exactamente lo que demuestra cualquier suite de tests, ni más ni menos: que ESE comportamiento coincide con ESAS expectativas para ESOS inputs. No demuestra corrección general, no cubre lo no probado, y no garantiza que la especificación fuera la correcta. No es un límite que Moyter introduzca; es el de siempre, escrito aquí en vez de escondido.
Contribuir
Moyter es un proyecto joven y las contribuciones son bienvenidas. Abre un issue
para discutir cambios grandes antes de un PR. Todo cambio debe pasar
ruff check src/ y pytest.
Autor
Moyter fue creado por Rubén Moya Morata (moyter) — GitHub @rmoya81, moyter.com.
Licencia
MIT © Rubén Moya Morata (moyter)
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file moyter-0.1.62.tar.gz.
File metadata
- Download URL: moyter-0.1.62.tar.gz
- Upload date:
- Size: 387.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
27053aa7ef1b8bb1081ff189492bb1d2fec865cbcd08a502fff65c4bb67c28a2
|
|
| MD5 |
7e1144dbc182c7b8d8c920a5b1d11456
|
|
| BLAKE2b-256 |
2b0036f81123f4518a9206c4346435544b6da2bf85119f1d89ff5ef705812639
|
Provenance
The following attestation bundles were made for moyter-0.1.62.tar.gz:
Publisher:
publish.yml on rmoya81/moyter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moyter-0.1.62.tar.gz -
Subject digest:
27053aa7ef1b8bb1081ff189492bb1d2fec865cbcd08a502fff65c4bb67c28a2 - Sigstore transparency entry: 2405156459
- Sigstore integration time:
-
Permalink:
rmoya81/moyter@e49e7d28829c9db1628a2842044201465a8d0dec -
Branch / Tag:
refs/tags/v0.1.62 - Owner: https://github.com/rmoya81
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e49e7d28829c9db1628a2842044201465a8d0dec -
Trigger Event:
release
-
Statement type:
File details
Details for the file moyter-0.1.62-py3-none-any.whl.
File metadata
- Download URL: moyter-0.1.62-py3-none-any.whl
- Upload date:
- Size: 345.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5db3b367c65b5d4e39b2534e12d49ddb5a5766f1ffeec7c491be0e2afa7b9c3c
|
|
| MD5 |
e9951fb20fd3f88016c01ee14c5f37bd
|
|
| BLAKE2b-256 |
2e515df3e1f2b803b4e95d082cc3b92f90c11636298ae385569856f15f5a2b16
|
Provenance
The following attestation bundles were made for moyter-0.1.62-py3-none-any.whl:
Publisher:
publish.yml on rmoya81/moyter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moyter-0.1.62-py3-none-any.whl -
Subject digest:
5db3b367c65b5d4e39b2534e12d49ddb5a5766f1ffeec7c491be0e2afa7b9c3c - Sigstore transparency entry: 2405156845
- Sigstore integration time:
-
Permalink:
rmoya81/moyter@e49e7d28829c9db1628a2842044201465a8d0dec -
Branch / Tag:
refs/tags/v0.1.62 - Owner: https://github.com/rmoya81
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e49e7d28829c9db1628a2842044201465a8d0dec -
Trigger Event:
release
-
Statement type: