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, mantienecontexto.memymemoria.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.cppllama-server, vLLM, Ollama, LM Studio, o un servicio remoto. - Linux (amd64 o arm64).
- Opcional:
makepara usar elMakefile;gccparago 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_TIMEOUTotimeout_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. stdines/dev/nully se fuerzanDEBIAN_FRONTEND=noninteractive,PAGER=cat,SYSTEMD_PAGER=cat, etc., para que nada espere entrada.cwdconfigurable 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 conread_fileogrep.
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 aarchivo.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 enagent.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(enprivileged) 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 campotoolsde 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_STEPSpor 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 tareaBLOCKED/FAILEDno 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_searchusa por defecto el HTML de DuckDuckGo. Los buscadores públicos pueden limitar las consultas automáticas; para uso intensivo, configure una instancia SearXNG propia eninit.conf:SEARCH_URL=https://searx.example.com/search?q={query}&format=json.web_fetchelimina scripts, estilos y navegación, conserva párrafos, listas y tablas, y numera los enlaces (texto [3]+ listaLINKS) 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_lookupconsulta 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 requiereALLOW_OUTSIDE_PROJECT_WRITE). - Límite
MAX_DOWNLOAD_SIZE(agent.md, por defecto 1 GiB; admite512M,2G). - Verificación:
sha256,sha512ochecksum_url(archivosSHA256SUMS,*.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. UnCHECKSUM MISMATCHdescarta el archivo. - Sin checksum, la descarga se permite pero el resultado indica
NOT VERIFIEDy 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_patchaceptadiff -uygit 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=truesolo 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 requiereALLOW_DELETE). diff_filesdocumenta 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
0600si no existe; se reescribe siempre con0600de 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 conforget:## Hosts ### db01 - IP: 10.10.10.15 - Servicio: PostgreSQL - Puerto: 5432 -
Sin duplicados (§82): un hecho
Etiqueta: valoractualiza la línea con la misma etiqueta (IP: ...cambia la IP existente); un hecho idéntico no se añade de nuevo.replace=truereescribe 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.
-
/memoryen 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).
- 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.
- 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.
- 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. - Si la compresión falla o no basta (≥ 95 %), se sustituyen salidas antiguas de herramientas por un marcador.
- Si el servidor rechaza una petición por exceso de contexto (p.ej. con
CONTEXT_SIZEdesconocido), 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-historyy los*.mdde 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.confo 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 siinit.confconAPI_KEYes legible por otros. - Instancia única por PROJECT_ROOT (
flocken.agent/lock): dos agentes a la vez corromperíancontexto.memy duplicarían acciones. - Límites:
agent.mdy 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.mdy mensajes al modelo. Los secretos deask_secretreciben 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.logy en el diario decontexto.memel 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
sudorestringido 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/doasy 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, incluidospython,awky las herramientas detools/) se bloquean enguardedpero se permiten enprivileged. - 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.memhasta 400 eventos. web_fetchno ejecuta JavaScript; los buscadores públicos pueden bloquear consultas automáticas (useSEARCH_URLcon SearXNG); NVD limita clientes anónimos.apply_patchno admite parches binarios ni renombrados degit.- Los secretos de
ask_secretduran 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.