2026-09-27 16:04:02 -06:00
2026-09-27 16:04:02 -06:00
2026-09-27 16:04:02 -06:00
2026-09-27 16:04:02 -06:00
2026-09-27 16:04:02 -06:00
2026-09-27 16:04:02 -06:00
2026-09-27 16:04:02 -06:00
2026-09-27 21:47:56 +00:00
2026-09-27 16:04:02 -06:00
2026-09-27 16:04:02 -06:00
2026-09-27 16:04:02 -06:00

sysagent

Agente autónomo de administración de sistemas escrito en Go. Usa un modelo de lenguaje accesible mediante una API compatible con OpenAI y trabaja según las reglas del agent.md del directorio desde el que se ejecuta.

Estado: completo (fases 1–5 de la especificación). El agente ejecuta comandos y herramientas de archivos bajo una política declarada en agent.md, mantiene contexto.mem y memoria.md, comprime el contexto automáticamente, investiga en Internet, descarga con verificación de checksum, aplica y genera parches, pide contraseñas sin eco y documenta sus propias herramientas. Ver Limitaciones conocidas.

La especificación completa del proyecto está en Agent.md.

Descripción

sysagent administra, diagnostica y mantiene sistemas Linux bajo autorización del propietario. Se ejecuta desde un directorio de proyecto (PROJECT_ROOT) que contiene su configuración (init.conf), sus reglas y permisos (agent.md) y, opcionalmente, tareas (01-*.md, 02-*.md...). Puede:

  • procesar tareas de forma autónoma (sysagent) o conversar (sysagent --cli);
  • leer y modificar el sistema mediante herramientas controladas por la política: comandos, archivos (escritura atómica, backup, validación), parches;
  • investigar lo que no sabe (documentación, CVE, avisos de seguridad) y descargar con verificación de checksum;
  • recordar lo aprendido (memoria.md) y el histórico de lo hecho (contexto.mem), comprimiendo el contexto cuando se acerca al límite;
  • verificar sus acciones y distinguir PLAN / EXECUTED / VERIFIED.

Arquitectura

cmd/sysagent/        punto de entrada: flags, señales, código de salida
internal/config/     parser de init.conf, enmascarado de secretos
internal/policy/     política (bloque ```policy), clasificación de comandos, análisis de shell y Guard
internal/project/    PROJECT_ROOT: agent.md, descubrimiento de tareas, aislamiento, memoria.md, .agent/
internal/llm/        interfaz Client + cliente OpenAI-compatible (reintentos, descubrimiento de contexto)
internal/ctxmgr/     conversación, estimación calibrada de tokens, división segura y compresor de contexto
internal/memory/     memoria.md estructurada (remember/forget) y contexto.mem (estado persistente)
internal/fsutil/     escritura atómica y backups compartidos
internal/term/       lectura sin eco del terminal (ioctl, sin dependencias)
internal/patch/      aplicar y generar diffs unificados (Go puro, sin patch/git)
internal/tools/      herramientas: exec, shell, archivos, memoria, web, CVE, descargas, parches
internal/toolcall/   parser del protocolo de herramientas por texto (```tool, <tool_call>)
internal/audit/      logging por niveles y agent.log, con redacción de secretos
internal/agent/      arranque, prompt de sistema, ciclo del agente, modo tareas y modo CLI
examples/            agent.md de ejemplo, unidades systemd (service + timer)
Makefile             atajos opcionales: build, check, race, cross, install, live

El programa solo depende de la biblioteca estándar de Go (sin dependencias externas; go.sum no es necesario).

Orden de arranque (estricto): PROJECT_ROOT → init.conf → agent.md → política → memoria.md → contexto.mem → modo → tareas → contexto inicial → modelo → ciclo del agente.

Requisitos

  • Go 1.24 o superior (probado con 1.24.4 de Debian 13). En Debian/Ubuntu: sudo apt install golang-go.
  • Un servidor de inferencia con API estilo OpenAI (POST /chat/completions): llama.cpp llama-server, vLLM, Ollama, LM Studio, o un servicio remoto.
  • Linux (amd64 o arm64).
  • Opcional: make para usar el Makefile; gcc para go test -race.

Compilación

git clone <repository>
cd <repository>

go mod download     # no descarga nada: no hay dependencias externas
go test ./...
go build -o sysagent ./cmd/sysagent

Pruebas opcionales contra servicios reales (NVD, Debian, buscador, descargas), para comprobar que los formatos externos no han cambiado:

go test -tags live -run Live -v ./internal/tools

Con make (opcional): make check (gofmt + vet + tests), make build, make race, make cross, make install, make live. La versión se toma de git describe o de VERSION=....

Con versión inyectada:

go build -ldflags "-X main.version=0.1.0" -o sysagent ./cmd/sysagent

Compilación cruzada:

GOOS=linux GOARCH=amd64 go build -o sysagent-amd64 ./cmd/sysagent
GOOS=linux GOARCH=arm64 go build -o sysagent-arm64 ./cmd/sysagent

Instalación

sudo install -m 0755 sysagent /usr/local/bin/sysagent

Preparar un proyecto:

mkdir -p /root/admin-agent && cd /root/admin-agent
cp /ruta/al/repo/init.conf.example init.conf && chmod 600 init.conf
cp /ruta/al/repo/examples/agent.md.example agent.md
$EDITOR init.conf agent.md

init.conf

Una directiva KEY=VALUE por línea; se permiten comentarios # y líneas vacías. Las claves repetidas son un error. Las claves desconocidas se reportan y se ignoran.

Clave Obligatoria Descripción
API_URL sí URL base, p.ej. http://127.0.0.1:8080/v1
API_KEY no Clave; se envía como Authorization: Bearer. Se muestra enmascarada (sk-2f1...8a9)
MODEL sí Nombre exacto del modelo
CONTEXT_SIZE no Tokens máximos. 0/ausente: valor del servidor
REQUEST_TIMEOUT no Segundos por petición al modelo (300 por defecto)
TOOL_CALLING no auto (defecto), native o json. Ver Tool calling
SEARCH_URL no Plantilla del buscador con {query} (p.ej. SearXNG JSON). Vacío: DuckDuckGo HTML

Con CONTEXT_SIZE=0 el agente consulta GET {API_URL}/models (max_model_len, context_length, meta.n_ctx_train) y GET /props (n_ctx de llama.cpp). Si no obtiene nada, el tamaño queda como UNKNOWN: nunca se inventa.

agent.md

Autoridad primaria del agente: identidad, propósito, límites y política. Si no existe, el programa se niega a arrancar (ERROR: agent.md not found in PROJECT_ROOT). Se acepta una única variante que solo difiera en mayúsculas (p.ej. Agent.md), con una advertencia.

Bloque de política

Solo se interpretan las directivas dentro de un bloque cercado con info string policy. Así, los ejemplos KEY=VALUE del texto libre nunca conceden permisos por accidente:

```policy
EXECUTION_MODE=privileged
ALLOW_NETWORK=true
ALLOW_DELETE=false
```
Directiva Valores Por defecto
EXECUTION_MODE guarded, privileged, unrestricted guarded
ALLOW_NETWORK, ALLOW_WEB_RESEARCH, ALLOW_DOWNLOADS, ALLOW_PACKAGE_INSTALL, ALLOW_SERVICE_RESTART, ALLOW_OUTSIDE_PROJECT_READ, ALLOW_OUTSIDE_PROJECT_WRITE, ALLOW_DELETE, ALLOW_REBOOT, ALLOW_SELF_TOOL_CREATION true/false (también yes/no, 1/0, on/off) false
BACKUP_BEFORE_SYSTEM_EDIT booleano true
DEFAULT_COMMAND_TIMEOUT segundos (> 0) 60
MAX_STEPS 1–1000 llamadas al modelo por tarea o mensaje 40
MAX_DOWNLOAD_SIZE bytes (sufijos K, M, G) 1G
TASK_ORDER lista separada por comas orden lexicográfico

Reglas: sin bloque se aplican los valores por defecto; varios bloques, una directiva repetida o un valor inválido impiden arrancar; las directivas desconocidas se reportan y se ignoran. unrestricted solo se activa si se escribe literalmente, y no implica ninguna capacidad ALLOW_*.

Herramientas

Herramienta Qué hace
exec Ejecuta un programa con argumentos (sin shell; exec.CommandContext)
shell Ejecuta una expresión con bash -lc (tuberías, redirecciones, bucles)
read_file Lee un archivo de texto con números de línea (offset/limit)
write_file Crea o reemplaza un archivo de forma atómica, con backup y validación opcional
edit_file Reemplaza un fragmento exacto de texto (atómico, backup, validación)
append_file Añade texto al final (atómico, backup)
copy_file / move_file Copia (atómica) o mueve/renombra
remove_file Borra archivo o directorio (recursive); requiere ALLOW_DELETE
mkdir, list_dir, stat, chmod, chown Operaciones de sistema de archivos
system_info OS, kernel, CPU, RAM, uid/euid, sudo/doas, init, gestores de paquetes, montajes, interfaces
remember / forget Guardar/eliminar conocimiento persistente en memoria.md (ver abajo)
web_search Búsqueda web: títulos, URLs y fragmentos
web_fetch Descarga una página y la convierte a texto con enlaces numerados (o raw)
cve_lookup CVE en NVD (descripción, CVSS, rangos afectados, referencias) + Debian Security Tracker (versión corregida por release)
download Descarga HTTP(S) con límite de tamaño y verificación SHA256/SHA512 o checksum_url
apply_patch Aplica un diff unificado: todo o nada, archivado y con copias para rollback
diff_files Genera un diff unificado entre dos archivos (documentar cambios, crear parches)
register_tool Documenta en memoria.md una herramienta creada por el agente
find_program Comprueba qué programas existen y cómo encontrar el paquete que falta
ask_secret Pide una contraseña al operador sin eco (solo --cli); el modelo solo ve {{secret:nombre}}

Comandos (exec/shell):

  • Timeout obligatorio (DEFAULT_COMMAND_TIMEOUT o timeout_seconds, máx. 2 h). El proceso corre en su propio grupo y al vencer el timeout (o con Ctrl+C) se mata el grupo completo, incluidos los hijos.
  • stdin es /dev/null y se fuerzan DEBIAN_FRONTEND=noninteractive, PAGER=cat, SYSTEMD_PAGER=cat, etc., para que nada espere entrada.
  • cwd configurable sin cambiar el directorio del agente.
  • stdout y stderr se capturan por separado. Si superan 16 KiB, el modelo recibe los primeros 8 KiB y los últimos 4 KiB, y la salida completa se guarda en .agent/tmp/ para consultarla con read_file o grep.

Archivos:

  • Escritura atómica: temporal en el mismo directorio → fsync → permisos/propietario del original → validación → rename → fsync del directorio.
  • validate: comando que se ejecuta sobre el temporal antes de aplicarlo ({file} se sustituye por su ruta), p. ej. {"command":"sshd","args":["-t","-f","{file}"]}. Si falla, el original no se toca.
  • Con BACKUP_BEFORE_SYSTEM_EDIT=true, todo archivo existente fuera de PROJECT_ROOT se copia antes de modificarlo o borrarlo a archivo.sysagent.YYYYMMDD-HHMMSS.bak (mismos permisos y propietario). El resultado incluye la instrucción de rollback.
  • Idempotencia: si el contenido ya es el pedido, no se escribe nada.
  • Argumentos validados estrictamente: un campo desconocido o JSON inválido es un error.
  • Con reason, el motivo del cambio queda en agent.log.

Aplicación de la política

Todas las herramientas pasan por policy.Guard. Los comandos se clasifican por riesgo (desenvolviendo sudo, env, timeout, nice, xargs, find -exec, bash -c...); en shell se analiza el script completo, incluidas $(...), `...`, <(...), heredocs, redirecciones y cambios de directorio con cd.

Riesgo Ejemplos Requiere
read-only ls, cat, systemctl status, ip addr, dpkg -l, nft list, journalctl —
network ping, dig, curl, ssh ALLOW_NETWORK
download wget, curl -o ALLOW_DOWNLOADS (+ network)
write cp, mv, mkdir, sed -i, tar -x, make, go build privileged; en guarded solo dentro de PROJECT_ROOT
delete rm, rmdir, find -delete, journalctl --vacuum-* ALLOW_DELETE
package apt install, dpkg -i, dnf, pip install ALLOW_PACKAGE_INSTALL
service systemctl restart, service x reload, nginx -s reload ALLOW_SERVICE_RESTART
admin useradd, chmod, ip route add, nft add, mount, sysctl -w, kill privileged
destructive dd, mkfs, wipefs, fdisk, userdel, shred unrestricted
reboot reboot, shutdown, systemctl poweroff ALLOW_REBOOT (en cualquier modo)
unknown programas no catalogados, python, awk, $CMD privileged

Rutas:

Regla Detalle
Lectura fuera de PROJECT_ROOT ALLOW_OUTSIDE_PROJECT_READ
Escritura fuera de PROJECT_ROOT ALLOW_OUTSIDE_PROJECT_WRITE
Escritura en PROJECT_ROOT/tools/ ALLOW_SELF_TOOL_CREATION
Symlinks Se evalúa el destino real: un enlace del proyecto hacia /etc cuenta como /etc
Protegidos siempre agent.md, init.conf (tampoco se puede leer: contiene la API key), agent.log, contexto.mem, .context-history (tampoco se puede leer) y cualquier *.md de la raíz salvo memoria.md. El agente no puede editar sus instrucciones ni crearse tareas nuevas
Nunca borrables /, /etc, /usr, /var, /home, /boot... PROJECT_ROOT, .agent/, y comodines sobre ellos (rm -rf /etc/*)

Cuando algo se deniega, el modelo recibe denied by policy: <motivo> y se le indica que no intente rodearlo.

Importante: el control de política es una barrera contra errores del modelo, no un sandbox frente a un adversario. El análisis de shell es aproximado y los programas unknown (en privileged) pueden hacer cualquier cosa que permita el usuario del sistema. Para aislamiento real, ejecute el agente con un usuario sin privilegios, en un contenedor o una VM.

Tool calling

  • TOOL_CALLING=auto (defecto): se envían las herramientas con el campo tools de la API. Si el servidor responde con un error que indica que no lo soporta, el agente cambia al protocolo de texto para el resto de la sesión.

  • Protocolo de texto (json): el prompt de sistema describe las herramientas y el modelo responde con bloques:

    ```tool
    {"tool": "exec", "arguments": {"command": "systemctl", "args": ["status", "nginx"]}}
    ```
    

    También se aceptan <tool_call>...</tool_call> (plantillas Hermes/Qwen), incluso en modo nativo, porque muchos modelos locales escriben así la llamada. Un bloque mal formado se devuelve al modelo como error; nunca se ejecuta texto libre.

Ciclo del agente

Para cada tarea (o mensaje del CLI): el modelo razona y pide herramientas → se validan y ejecutan en orden → los resultados vuelven al modelo → se repite hasta que responde sin pedir herramientas.

  • Límite MAX_STEPS por tarea/mensaje.
  • Detección de bucles: la misma llamada con el mismo resultado 3 veces genera un aviso al modelo; 5 veces detiene la tarea como BLOCKED.
  • En modo tareas, la respuesta final debe terminar en TASK_STATUS: DONE|BLOCKED|FAILED (se pide una vez si falta). Una tarea BLOCKED/FAILED no detiene las siguientes; un error del modelo o Ctrl+C sí.
  • Contexto: ver Compresión de contexto.

Investigación en Internet

Requiere ALLOW_NETWORK=true y ALLOW_WEB_RESEARCH=true.

  • web_search usa por defecto el HTML de DuckDuckGo. Los buscadores públicos pueden limitar las consultas automáticas; para uso intensivo, configure una instancia SearXNG propia en init.conf: SEARCH_URL=https://searx.example.com/search?q={query}&format=json.
  • web_fetch elimina scripts, estilos y navegación, conserva párrafos, listas y tablas, y numera los enlaces (texto [3] + lista LINKS) para poder seguirlos. Máx. 5 MiB por página; el modelo recibe ~12 KB y el texto completo se guarda en .agent/tmp/.
  • cve_lookup consulta la API 2.0 de NVD (las entradas con rango de versiones aparecen primero) y la página del CVE en el Debian Security Tracker, que da la versión corregida por cada release de Debian. Siempre recuerda comprobar la versión instalada antes de concluir.
  • Todo el contenido web llega marcado como UNTRUSTED WEB CONTENT: el modelo lo trata como dato, nunca como instrucciones (§59).
  • El prompt de sistema indica el orden de fuentes (§38): documentación oficial, avisos del fabricante, repositorios oficiales, NVD, tracker de la distribución, código upstream, issues oficiales y, por último, fuentes secundarias.

Cliente HTTP único (§67): pool de conexiones, TLS ≥ 1.2, User-Agent: sysagent/<versión>, timeouts de conexión/TLS/cabeceras de 15–30 s, máx. 10 redirecciones solo a http(s), y respeta HTTPS_PROXY/NO_PROXY.

Descargas

Requiere ALLOW_NETWORK=true y ALLOW_DOWNLOADS=true.

  • Destino por defecto .agent/downloads/<nombre>; otro destino pasa por la política de rutas (fuera del proyecto requiere ALLOW_OUTSIDE_PROJECT_WRITE).
  • Límite MAX_DOWNLOAD_SIZE (agent.md, por defecto 1 GiB; admite 512M, 2G).
  • Verificación: sha256, sha512 o checksum_url (archivos SHA256SUMS, *.sha256, formato GNU o BSD). Se descarga a un temporal calculando el hash; solo si tamaño y checksum son correctos se mueve al destino. Un CHECKSUM MISMATCH descarta el archivo.
  • Sin checksum, la descarga se permite pero el resultado indica NOT VERIFIED y muestra el SHA-256 calculado.

Parches

internal/patch aplica y genera diffs unificados en Go, así que no necesita patch ni git (una instalación mínima de Debian no trae patch).

  • apply_patch acepta diff -u y git diff (creación y borrado de archivos, \ No newline at end of file, desplazamientos de posición). No hay "fuzz": el contexto debe coincidir exactamente.
  • Todo o nada: todos los hunks de todos los archivos se calculan en memoria; si uno falla, no se escribe nada. check_only=true solo comprueba.
  • Antes de escribir se guarda en .agent/patches/<fecha>/ el parche aplicado y las copias originales (orig/…); los archivos fuera del proyecto también reciben su backup .sysagent.<fecha>.bak. El resultado incluye los pasos de rollback.
  • Cada archivo pasa por la política (no se puede parchear agent.md, rutas con .. se rechazan, borrar requiere ALLOW_DELETE).
  • diff_files documenta exactamente un cambio (p.ej. backup frente a actual) y puede guardarlo como .agent/patches/<nombre>.patch.

Flujo de parche propio (§40), indicado al modelo en el prompt: código fuente legítimo → causa → parche mínimo → apply_patch (con check_only antes) → compilar → pruebas → comparar antes/después → conservar el original → documentar. Siempre se prefiere el parche oficial.

Herramientas propias

Con ALLOW_SELF_TOOL_CREATION=true el agente puede escribir en PROJECT_ROOT/tools/. Orden de preferencia (§42): herramienta instalada → paquete de la distribución → upstream confiable → script propio → programa propio. find_program comprueba qué existe antes de asumirlo; register_tool documenta la herramienta en memoria.md (sección Herramientas: ruta, descripción, uso, lenguaje) para reutilizarla. Las herramientas no se borran al terminar.

Contraseñas y secretos

Si una tarea necesita una contraseña que no está disponible (§80), en --cli el modelo llama a ask_secret. El operador la escribe sin eco; el valor queda solo en memoria durante la sesión y el modelo recibe únicamente el marcador {{secret:nombre}}, que usa en sus llamadas:

  • exec: el marcador se sustituye en los argumentos al ejecutar.
  • shell: el valor viaja como variable de entorno (SYSAGENT_SECRET_nombre) y el marcador se convierte en una referencia a ella adaptada al contexto de comillas (sin comillas, "...", '...', $(...), `...`). Un valor con comillas o ; nunca se interpreta como código.
  • write_file/edit_file/append_file: el valor real se escribe en el archivo.

El valor se enmascara (vuelve a aparecer como marcador) en todas las salidas, agent.log, contexto.mem, memoria.md y el historial; nunca se envía al modelo. En modo tareas no hay operador: ask_secret falla y el agente debe informar del bloqueo. /secrets lista los nombres.

Los argumentos de un comando son visibles para otros usuarios locales en la lista de procesos. Para contraseñas, es preferible shell (variable de entorno) o un archivo con permisos restrictivos.

La lectura sin eco usa ioctl(TCGETS/TCSETS) y siempre restaura el terminal, también con Ctrl+C. Si la entrada no es un terminal (tubería), se lee sin más; si es un terminal en el que no se puede desactivar el eco, se rechaza.

Tareas

En modo normal se cargan los *.md ubicados directamente en PROJECT_ROOT, excepto agent.md, memoria.md y README.md (sin distinguir mayúsculas), en orden lexicográfico (o TASK_ORDER). No hay búsqueda recursiva, y los symlinks que apuntan fuera del proyecto se ignoran con una advertencia: ningún archivo externo puede convertirse en instrucción.

Las tareas completadas (DONE) se registran en contexto.mem con el hash de su contenido; en la siguiente ejecución no se repiten si el archivo no cambió. Para repetirlas: sysagent --rerun. Las BLOCKED/FAILED se reintentan siempre.

memoria.md

Conocimiento persistente descubierto durante las tareas (§21): IPs y roles de hosts, puertos, rutas de configuración, forma correcta de reiniciar un servicio, distribución y kernel, dependencias, herramientas creadas, decisiones. No es un log (eso es contexto.mem).

  • Se crea con permisos 0600 si no existe; se reescribe siempre con 0600 de forma atómica. Si ya existía con permisos más abiertos se emite una advertencia.

  • El modelo la actualiza con la herramienta remember (section, entry, facts) y elimina datos obsoletos con forget:

    ## Hosts
    
    ### db01
    
    - IP: 10.10.10.15
    - Servicio: PostgreSQL
    - Puerto: 5432
    
  • Sin duplicados (§82): un hecho Etiqueta: valor actualiza la línea con la misma etiqueta (IP: ... cambia la IP existente); un hecho idéntico no se añade de nuevo. replace=true reescribe la entrada.

  • El contenido escrito a mano por el operador (texto libre, otras secciones, bloques de código) se conserva literalmente.

  • Credenciales (§23): un hecho que parece contraseña, token o clave se rechaza salvo sensitive=true; el modelo debe preferir referencias (Password: secret-tool lookup service postgres-admin). La API key nunca se guarda (se sustituye por su versión enmascarada).

  • Tras cada cambio, el prompt de sistema se regenera con la memoria nueva.

  • /memory en el CLI la muestra releyéndola del disco.

contexto.mem

Histórico operativo (§17), en JSON, con permisos 0600 y escritura atómica. Se guarda tras cada mensaje/tarea, en cada cambio de estado de tarea, tras cada compactación y al salir (también con Ctrl+C/SIGTERM).

Campo Contenido
summary Contexto histórico comprimido con las 14 secciones de §19
compactions Número de compactaciones realizadas
journal Diario registrado por el programa (no por el modelo): inicio/fin de sesión, herramientas ejecutadas con resultado, cambios de archivos con sus backups y motivo, denegaciones, errores, estados de tareas, respuestas finales, compactaciones. Máx. 400 eventos; se descartan primero los ya resumidos
recent Últimos mensajes aún no resumidos (máx. 60, recortados a 4 KB cada uno)
tasks Estado, hash y fecha de cada tarea

Al arrancar, el resumen se incluye en el prompt como CONTEXTO HISTÓRICO y los mensajes recientes como ÚLTIMOS MENSAJES DE LA SESIÓN ANTERIOR (en texto, para no depender del protocolo de herramientas de la sesión anterior). Si no caben en ~20 % del contexto, se compactan en la primera petición.

Si el archivo no es JSON (p.ej. notas escritas a mano) su texto se importa como resumen. Si es JSON corrupto se renombra a contexto.mem.corrupt-<fecha> y se empieza de cero con una advertencia; nunca se pierde en silencio.

Compresión de contexto

El uso de contexto se estima (~3 bytes/token, más el esquema de herramientas en modo nativo) y se calibra con los prompt_tokens que reporta el servidor tras cada petición. Nunca se presenta como exacto (§84).

  1. Al 80 % del contexto (§18) el historial se divide en una parte antigua y una reciente (~25 % del contexto). El corte nunca separa una llamada a herramienta de su resultado.
  2. La parte antigua, el resumen previo, el diario sin resumir y los mensajes de sesiones anteriores se envían al modelo con un prompt dedicado (§83) que exige conservar rutas, IPs, puertos, versiones, decisiones, errores sin resolver, cambios con sus backups y comandos exactos, y no inventar nada. Si no cabe en una petición, se resume por partes encadenando el resumen parcial.
  3. La salida se valida (deben estar las secciones OBJECTIVE, CURRENT_STATE, COMPLETED_TASKS, PENDING_TASKS, IMPORTANT_FACTS, SYSTEM_STATE, FILES_CHANGED, COMMANDS_OF_IMPORTANCE, ERRORS, DECISIONS, CREDENTIAL_REFERENCES, HOSTS, SERVICES, LESSONS_LEARNED; se toleran 3 ausentes) y sustituye a la parte antigua. Si la parte reciente no empieza por un mensaje del usuario, se antepone uno que repite la tarea en curso.
  4. Si la compresión falla o no basta (≥ 95 %), se sustituyen salidas antiguas de herramientas por un marcador.
  5. Si el servidor rechaza una petición por exceso de contexto (p.ej. con CONTEXT_SIZE desconocido), se compacta y se reintenta una vez.

En el CLI, /compact fuerza una compactación y /context summary muestra el resumen.

Modo --cli

$ cd /root/admin-agent
$ sysagent --cli
System Agent
Model: qwen3.5
Mode: privileged
Project: /root/admin-agent

agent> revisa por qué nginx no inicia

Carga init.conf, agent.md, memoria.md y contexto.mem, e ignora los archivos de tareas.

Comando Descripción
/help Ayuda
/status Modelo, API (clave enmascarada), modo, uso de contexto, root
/context Uso de contexto, resumen histórico, diario; /context summary imprime el resumen
/memory Muestra memoria.md
/policy Política efectiva
/history Conversación de la sesión
/journal [n] Últimos n eventos del diario de contexto.mem (20 por defecto)
/compact Compacta ya la conversación antigua en el resumen histórico
/secrets Nombres de los secretos introducidos en la sesión (nunca los valores)
/clear Vacía la conversación reciente (se conservan resumen, memoria y diario)
/quit, /exit, Ctrl+D Salir

Durante un mensaje, la consola muestra cada herramienta (→ exec: ...) y su resultado (← ok, ← error, ← denied). Ctrl+C cancela el mensaje en curso (petición al modelo y procesos hijos); en el prompt, sale. El uso de contexto se muestra como reported by server cuando el servidor devuelve usage, o estimated (estimación conservadora de ~3 bytes/token). No hay edición de línea ni historial con flechas; se puede usar rlwrap sysagent --cli.

Otras opciones

--version    muestra la versión
--verbose    muestra logs DEBUG/INFO en consola (y DEBUG en agent.log)
--rerun      repite tareas que contexto.mem registra como DONE
--dry-run    las herramientas que modifican el sistema solo describen lo que harían;
             los comandos clasificados como solo lectura/red sí se ejecutan (diagnóstico)

Ejemplos

# Modo normal: procesa 01-*.md, 02-*.md...
cd /root/admin-agent && sysagent

# Interactivo
cd /root/admin-agent && sysagent --cli

Con llama.cpp local:

llama-server -m qwen3-8b.gguf -c 32768 --port 8080
# init.conf: API_URL=http://127.0.0.1:8080/v1  MODEL=qwen3  CONTEXT_SIZE=0

Permisos y archivos generados

Ruta Permisos Contenido
agent.log 0600 Auditoría: timestamps RFC3339, acciones, resultados
memoria.md 0600 Conocimiento persistente
contexto.mem 0600 Resumen histórico, diario operativo, estado de tareas
.agent/{tmp,downloads,patches,logs} 0700 tmp/: salidas y páginas extensas; downloads/: descargas; patches/: parches aplicados, originales y diffs
*.sysagent.YYYYMMDD-HHMMSS.bak los del original Backups junto al archivo modificado
.context-history 0600 Entradas del operador en el CLI con fecha (§79); se omiten líneas que parecen credenciales y nunca incluye lo tecleado en ask_secret
.agent/lock 0600 Bloqueo de instancia única (PID)

Se recomienda chmod 600 init.conf. El programa detecta UID/EUID y lo informa (Running as root) pero no asume privilegios.

Seguridad

  • Aislamiento de instrucciones: solo agent.md, las tareas de PROJECT_ROOT y el operador del CLI dan instrucciones. Todo lo demás (archivos externos, salidas de comandos, páginas web) es dato no confiable y se marca como tal.
  • Jerarquía de autoridad incluida en el prompt de sistema: reglas internas > agent.md > operador CLI > tareas > memoria.md > contexto.mem > sistema > Internet.
  • Fail closed: capacidades no declaradas = denegadas; errores de configuración o de política impiden arrancar.
  • El agente no puede cambiar sus propias reglas: agent.md, init.conf, agent.log, contexto.mem, .context-history y los *.md de la raíz están protegidos frente a sus herramientas (ver Aplicación de la política).
  • Revisión al arrancar: se niega a arrancar si PROJECT_ROOT, agent.md, init.conf o un archivo de tarea son escribibles por cualquier usuario, o si PROJECT_ROOT es un directorio compartido tipo /tmp (cualquiera podría dictar lo que hace un agente que corre como root). Advierte si son escribibles por el grupo, si pertenecen a otro usuario mientras el agente corre como root, o si init.conf con API_KEY es legible por otros.
  • Instancia única por PROJECT_ROOT (flock en .agent/lock): dos agentes a la vez corromperían contexto.mem y duplicarían acciones.
  • Límites: agent.md y cada tarea ≤ 1 MiB; argumentos de herramienta ≤ 40 MiB; respuestas del modelo ≤ 64 MiB; páginas web ≤ 5 MiB; descargas ≤ MAX_DOWNLOAD_SIZE; escritura de archivos ≤ 32 MiB.
  • Secretos: la API key solo viaja en la cabecera HTTP; un redactor la sustituye por su versión enmascarada en logs, consola, contexto.mem, memoria.md y mensajes al modelo. Los secretos de ask_secret reciben el mismo trato (ver Contraseñas y secretos).
  • Robustez: un error interno de una herramienta se devuelve como error al modelo en lugar de terminar el proceso; SIGINT/SIGTERM cancelan procesos hijos y guardan el estado (un segundo Ctrl+C fuerza la salida).
  • Registro de cambios (§88): cada modificación de un archivo existente guarda en agent.log y en el diario de contexto.mem el diff exacto (valor anterior → nuevo), el motivo, la fecha y la ruta del backup.

El control de política es una barrera contra errores del modelo, no un sandbox frente a un adversario. Para aislamiento real, ejecute el agente con un usuario dedicado sin privilegios (con sudo restringido si hace falta), en un contenedor o en una VM.

Reintentos y privilegios

  • Errores transitorios que garantizan que el comando no hizo nada se reintentan con backoff (§54): bloqueo de apt/dpkg (3 reintentos: 5 s, 15 s, 30 s) y fallos temporales de DNS (2 reintentos: 2 s, 5 s). Los errores permanentes no se reintentan. El resultado indica cada reintento.
  • Si un comando falla por permisos (§46) y el agente no es root, el resultado incluye qué mecanismos de escalada hay (sudo/doas y si funcionan sin contraseña, comprobado con -n; su). Si ya es root, se sugieren otras causas (sistema de archivos de solo lectura, SELinux/AppArmor, atributo inmutable, contenedor).

Conflictos entre tareas

Las tareas se ejecutan en el orden de TASK_ORDER o lexicográfico (§61). El modelo conoce la lista de tareas de la ejecución y, si la actual contradice a una anterior, no deshace a ciegas: sigue el orden y lo indica con una línea CONFLICT: ..., que se registra en agent.log y en el diario.

Ejecución periódica con systemd

examples/sysagent.service y examples/sysagent.timer ejecutan las tareas de un proyecto cada hora. Como las tareas DONE sin cambios no se repiten, el agente solo actúa cuando hay tareas nuevas, modificadas o pendientes:

sudo cp examples/sysagent.service examples/sysagent.timer /etc/systemd/system/
sudo systemctl edit sysagent.service   # ajustar WorkingDirectory
sudo systemctl daemon-reload && sudo systemctl enable --now sysagent.timer
journalctl -u sysagent.service

Troubleshooting

Síntoma Causa / solución
ERROR: agent.md not found in PROJECT_ROOT Ejecutar desde el directorio del proyecto; crear agent.md
init.conf not found in PROJECT_ROOT Copiar init.conf.example a init.conf
API_URL must be an http(s) URL Incluir el esquema: http://host:puerto/v1
multiple policy blocks Dejar un solo bloque ```policy en agent.md
could not query model server Aviso: el servidor no expone /models; no impide arrancar
connection refused en cada petición El servidor de inferencia no está en marcha o la URL es incorrecta. Hay 3 reintentos con backoff (1s, 2s, 4s)
HTTP 404 en /chat/completions API_URL debe terminar en el prefijo de la API (normalmente /v1)
Uso de contexto UNKNOWN Definir CONTEXT_SIZE en init.conf
Respuestas con <think> Se eliminan automáticamente del contenido mostrado
denied by policy: ... La política de agent.md no lo permite; ajustar el bloque ```policy si procede
El modelo no usa herramientas o escribe JSON suelto Probar TOOL_CALLING=json; en llama.cpp, iniciar llama-server con --jinja para tool calling nativo
malformed tool call El modelo generó JSON inválido; se le devuelve el error y suele corregirse
loop detected El modelo repitió la misma acción; revisar la tarea o usar un modelo más capaz
MAX_STEPS=... reached Aumentar MAX_STEPS en agent.md o dividir la tarea
context compaction failed: ... missing sections El modelo no siguió el formato del resumen; se recortan salidas en su lugar. Un modelo más capaz o más contexto lo evita
Una tarea no se ejecuta (DONE, skipped) Ya se completó y no cambió; usar --rerun o editar el archivo
contexto.mem could not be parsed Se movió a contexto.mem.corrupt-*; revisarlo y, si procede, copiar su resumen a mano
insecure project: ... writable by any user Corregir permisos: chmod o-w agent.md init.conf *.md y del directorio
another sysagent instance is running Otra ejecución usa el proyecto (PID indicado); esperar o detenerla. El bloqueo se libera solo si el proceso muere
ask_secret ... no interactive operator Las contraseñas solo pueden pedirse en --cli; en modo tareas use un almacén (p.ej. secret-tool) y guarde en memoria.md dónde está
unknown secret(s) El modelo usó un marcador que no se pidió en esta sesión (los secretos no se persisten)
Reiniciar el histórico Borrar (o mover) contexto.mem; memoria.md es independiente
search provider returned no parseable results El buscador limita consultas automáticas; configurar SEARCH_URL (SearXNG)
NVD: HTTP 403/429 NVD limita clientes anónimos; reintentar más tarde (Debian tracker sigue funcionando)
CHECKSUM MISMATCH El archivo no coincide con el checksum: se descartó. Verificar la URL y la fuente del checksum
hunk N does not apply: context not found El archivo no coincide con el que se usó para crear el parche; revisar base_dir/strip o regenerar el parche

Estado de implementación

Fase Contenido Estado
1 init.conf, agent.md, política, tareas, cliente LLM, --cli hecho
2 ejecución de comandos, herramientas de archivos, tool calling, aplicación de política, --dry-run hecho
3 contexto.mem, memoria.md automática (remember/forget), compresión de contexto hecho
4 investigación web, CVE, descargas verificadas, parches, herramientas propias hecho
5 endurecimiento, secretos sin eco, reintentos, registro de cambios, conflictos, systemd, documentación hecho

Pruebas: go test ./... cubre, entre otros, el parser de init.conf, el de la política, el descubrimiento de tareas, la validación de rutas y comandos, la compresión de contexto, la memoria, el parser de tool calls, las herramientas (con servidores HTTP locales) y el ciclo completo del agente con un modelo simulado. Los comandos de prueba son inofensivos (printf, echo, true, false) y todo ocurre en directorios temporales (§73, §74).

Limitaciones conocidas

  • No es un sandbox. La clasificación de comandos es un catálogo y el análisis de shell es aproximado; los programas no catalogados (unknown, incluidos python, awk y las herramientas de tools/) se bloquean en guarded pero se permiten en privileged.
  • La calidad del resumen de contexto depende del modelo: se valida su estructura, no su exactitud. El diario del programa (datos exactos) se conserva aparte en contexto.mem hasta 400 eventos.
  • web_fetch no ejecuta JavaScript; los buscadores públicos pueden bloquear consultas automáticas (use SEARCH_URL con SearXNG); NVD limita clientes anónimos.
  • apply_patch no admite parches binarios ni renombrados de git.
  • Los secretos de ask_secret duran solo la sesión y solo existen en --cli.
  • El CLI no tiene edición de línea ni historial con flechas: use rlwrap sysagent --cli.
  • El recuento de tokens es siempre una estimación (calibrada con el servidor cuando este informa usage).
  • Solo Linux (amd64, arm64). Distribuciones priorizadas: Debian y Ubuntu; la detección de gestor de paquetes cubre apt, dnf/yum, pacman, zypper y apk.
S
Description
Agente de IA para administrar sistemas operativos
Readme GPL-2.0
6.2 MiB
Languages
Go 99.7%
Makefile 0.3%