Skip to content

ADR-0009: Hermes Agent con contexto persistente de Ramon (memoria, HA, git log)

Campo Valor
Status Proposed
Date 2026-05-17
Decision by ramon
Reviewers claude (Opus 4.7 1M)
Supersedes
Builds on ADR-0003 (sesion 2026-05-16, OpenClaw retirado), memorias hermes_setup.md + iarq_oviedo_stack.md

Contexto

Hermes Agent (LXC 101 hermesbot, 192.168.0.138 en pmx-50) es el unico bot conversacional vivo desde el retiro de OpenClaw (2026-05-16, LXC 100 destruido). Hoy responde stateless: cada interaccion Telegram empieza con el system-prompt + tools schema (~12-14K tokens overhead) y ningun contexto operativo del homelab. Resultado:

  • No sabe que paso en el cluster en las ultimas 24h (commits, alerts firing).
  • No conoce las 23 memorias curadas que Ramon mantiene en ~/.claude/memory/ (passwords, IPs, gotchas, decisiones tomadas).
  • No conoce los ADRs (0001-0003 hasta hoy) que documentan el "porque" de la arquitectura.
  • No tiene acceso a HA state (presencia, sensores) ni a metricas de Healthchecks/Loki/Prometheus.
  • No conoce la agenda Google Calendar (aunque la skill google-workspace esta autenticada — ver hermes_setup.md L200-221).

Como minimo el 60% de las queries reales de Ramon a Hermes son "que paso con X" o "esta sano Y" — preguntas que requieren contexto que el LLM upstream no tiene porque Hermes nunca lo inyecta.

Backend LLM: OAuth ChatGPT Plus (provider openai-codex, modelo gpt-5.5). Cuota compartida con cualquier otro proceso del mismo usuario. Inyectar contexto naive (todas las memorias en cada turn) quemaria la cuota en 1-2 dias.

Riesgo critico: las memorias contienen secretos en plano (passwords Alfagann007/alfagann, tokens RAG/Locator/Validator/dump, IPs internas, OAuth tokens en auth.json). Mandarlos a ChatGPT via OAuth = leak permanente a OpenAI logs sin posibilidad de retraccion.

Decisiones

D1. RAG local sobre memorias + ADRs (no contexto bruto en prompt)

Decision: montar un servicio RAG local en LXC 101 que indexe (a) las 23 memorias de ~/.claude/memory/ sincronizadas desde la Mac y (b) los ADRs del repo monxas/stacks. Hermes lo consume como tool search_memory(query, k=5), no como contexto pre-pegado.

Stack: - Embeddings: e5-large ya corriendo en Mac mini :9101 (servicio asturias-rag, ver iarq_oviedo_stack.md). Reutilizamos endpoint via LAN, no duplicamos modelo. - Vector store: LanceDB local en LXC 101 (/root/.hermes/rag/lance.db), ~80 KB de memorias → indice trivial (<5 MB). - API: FastAPI minimo en 127.0.0.1:9110 (/search?q=...&k=5) gestionado por systemd-user. - Reindex: cron cada 15 min si mtime de ~/.hermes/memories-mirror/ cambio (ver D2).

Justificacion: - Las 23 memorias son ~80 KB (~25K tokens). Pegarlas en cada turn quema cuota en horas. - Top-K=5 chunks relevantes (~2-3K tokens) cubre 90% de queries y deja headroom para conversacion. - e5-large ya validado en produccion (asturias-rag: 80% queries con cita directa, p50 1.7s) — no introducimos riesgo nuevo.

Alternativa descartada: pegar resumen estatico de memorias en system-prompt. Razon: estatico = stale en horas, no escala a 50+ memorias, y sigue mandando el dump al LLM sin filtro.

D2. Sync memorias Mac → LXC 101 via repo git privado

Decision: crear repo privado monxas/claude-memory-mirror (Github private). Cron en la Mac (launchd com.monxas.memory-sync, cada 30 min) hace rsync ~/.claude/memory/ → repo/ && git commit && git push. Cron en LXC 101 hace git pull cada 15 min a /root/.hermes/memories-mirror/.

Justificacion: - Repo git da historial, diffs, rollback trivial. rsync directo Mac→LXC requiere Mac siempre online y SSH inbound a LXC (mas superficie). - 30 min Mac-push + 15 min LXC-pull = lag worst-case 45 min. Aceptable para contexto (no es realtime monitoring). - Reutiliza healthcheck mirror-homelab-repo (ya existe en stack) extendido a un segundo repo.

Alternativa descartada: Syncthing. Razon: mas dependencias, sin historial git, conflictos resolverlos manualmente. NFS desde NAS: introduce dependencia NAS para que Hermes arranque (ADR-0003 D1 nos dejo el cache NVMe en writethrough; el NAS es critico, no queremos otra cosa colgando).

D3. Privacy filter en DOS capas: redaccion local + repo cifrado

Decision: secretos NUNCA salen del LAN, garantizado por dos capas independientes:

Capa 1 — repo cifrado en origen (git-crypt): - El repo claude-memory-mirror usa git-crypt con clave simetrica almacenada en LXC 101 + Mac. - En Github los blobs estan AES-cifrados. Aunque el repo se hiciera publico por error, contenido ilegible. - En LXC 101 se descifra al git pull (clave en /root/.git-crypt-key, perms 0400, root-only).

Capa 2 — regex redaction antes de inferencia: - Pre-procesador Python en Hermes (skill local privacy-filter) intercepta TODO contenido recuperado del RAG antes de inyectarlo al prompt. - Reglas regex aplicadas: (?i)alfagann\\w*[REDACTED_PWD], tokens base64-ish > 20 chars ([A-Za-z0-9_-]{20,}) → [REDACTED_TOKEN], IPs 192.168.x.x → [INTERNAL_IP], OAuth bearer headers → [REDACTED_AUTH]. - Lista patrones versionada en /root/.hermes/privacy-patterns.yaml. Test suite (pytest tests/test_privacy.py) con 30+ casos conocidos (todos los secretos actualmente en memorias).

Honestidad sobre los limites:

El regex SOLO NO ES SUFICIENTE. Razones: 1. Secretos nuevos que no matcheen patrones existentes (token con formato distinto) pasan limpios. 2. Secretos parafraseados ("la contrasena del NAS empieza por Alfa") regex no los pilla. 3. Contexto que implique secretos ("user monxas, password normal del homelab") permite a un atacante adivinar.

Por eso git-crypt es la defensa primaria, no el regex. El flujo es: - git-crypt protege contra leak accidental del repo (Github breach, push a publico, laptop robado). - Regex protege contra leak al LLM upstream (ChatGPT logs). - Si UNO falla, el otro contiene el dano. Defense-in-depth.

Defensa adicional (D3b): NO indexar archivos marcados con frontmatter privacy: secret en las memorias. Convencion: proxmox_host.md (contiene IPs/specs) → indexable. hermes_setup.md con tokens bot Telegram en plano → marcar privacy: secret, excluir del RAG, mantener solo en local Mac. Trabajo manual: revisar las 23 memorias, etiquetar.

Alternativa considerada y descartada: usar LLM local (Ollama) para queries que tocan secretos. No viable en este hardware: el experimento fallback FOSS de mayo 2026 (hermes_setup.md L128-147) confirmo que prompt processing con toolset Hermes supera 16 min en 8B-CPU. Volver a la idea solo cuando haya GPU dedicada.

D4. Tools adicionales para contexto on-demand (no pre-cargado)

Decision: anadir 4 tools a Hermes (en lugar de stuff context en el system-prompt). El LLM decide cuando invocarlos:

  • search_memory(query, k=5): RAG local (D1). Devuelve top-K chunks de memorias+ADRs con citas.
  • get_recent_git_log(repo=stacks, days=7): git log --since=Xd --oneline del repo homelab via SSH a media-208.
  • get_ha_state(entity_id?): REST /api/states a HA. Si entity_id vacio, devuelve resumen (presencia + alertas activas).
  • get_healthchecks_status(): consulta API Healthchecks, devuelve solo checks RED/grace.

Justificacion: - Tools on-demand cuestan tokens solo cuando se usan. Stuff-in-prompt cuesta en CADA turn. - LLM gpt-5.5 elige cuando llamar — para "hola" no llama nada (0 cost extra), para "que esta roto?" llama healthchecks + loki. - Tools devuelven JSON estructurado pequeno (<1 KB), no dumps gigantes.

No incluido aun: get_loki_events() y get_calendar_today(). Razon: prioridad fase 2 (ver Implementacion). El calendar requiere ya esta autenticado pero la skill google-workspace no esta integrada como Hermes tool, requiere wrapper.

D5. Adaptive behavior por reglas, NO por LLM extra

Decision: ajustes de tono/verbosidad en funcion de contexto se hacen con reglas hardcoded en el wrapper Telegram de Hermes, no llamando a otro LLM.

Reglas iniciales (en /root/.hermes/rules/adaptive.yaml): - Si ha.input_boolean.ramon_focus == on (entity manual o auto via Pomodoro) → injectar al system-prompt: "Ramon esta en focus. NO interrumpas con alertas no-criticas. Respuestas concisas, sin small-talk." - Si healthchecks tiene >=1 RED → injectar: "ALERTA: hay checks rojos. Si la pregunta es ambigua, prioriza mencionarlos." - Si hora local entre 23:00-07:00 → injectar: "Es horario de descanso de Ramon. Mensajes solo si critico." - Si git log --since=24h contiene commits que toquen path mencionado en la query → invocar search_memory automatico sobre ese path.

Justificacion: - Rule-based es deterministico, debuggable, sin coste LLM. - LLM-based "adaptive personality" requeriria llamada extra por turn → 2x quota = inviable con OAuth. - Empezamos simple, evolucionamos si reglas saturan.

Alternativa descartada: meta-LLM que decida tono. Razon: ver hermes_setup.md L62-67, latencia Hermes ya es 5-11s primary; anadir meta-call la dobla.

D6. Quota guard y degradacion graceful

Decision: el HermesContextPipeline tiene un budget de tokens por turn:

  • Cap duro: 8000 tokens de contexto inyectado (memorias+rules+tools-results) por turn. Si excede, degradar K del RAG (5→3→1) hasta caber.
  • Si Codex devuelve 429 (rate limit, ver hermes_setup.md L146): cortar TODO contexto extra, dejar solo system-prompt minimo y el mensaje. Mejor responder corto que no responder.
  • Telemetria: por cada turn loggear context_tokens_injected, rag_hits, tools_called, degraded_reason a Loki. Dashboard Grafana en folder homelab/hermes.

Justificacion: la 429 de OAuth Codex no avisa, pega de golpe. Sin degradacion graceful Hermes pasa de "responde con contexto" a "muerto 2h" sin paso intermedio.

Implementacion

Fase 1 — Foundation (1-2 sesiones, sin riesgo): 1. Repo claude-memory-mirror privado + git-crypt setup en Mac y LXC 101. 2. Cron Mac push + cron LXC pull. Verificar lag <45 min. 3. Marcar memorias privacy: secret (manual, ~30 min revision). 4. FastAPI RAG local en LXC 101 contra e5-large de Mac mini. 5. Tool search_memory registrado en Hermes config.

Fase 2 — Privacy + tools (1 sesion): 6. Privacy filter regex + pytest con 30+ casos. NO desplegar a prod hasta tests green. 7. Tools get_recent_git_log, get_ha_state, get_healthchecks_status (cada uno ~50 LOC). 8. Smoke-test E2E: query Telegram → verificar redaccion en logs Hermes antes de envio LLM.

Fase 3 — Adaptive + observability (1 sesion): 9. Reglas adaptive (D5) + entity HA input_boolean.ramon_focus. 10. Telemetria Loki + Grafana dashboard hermes. 11. Quota guard (D6).

Fase 4 — Extension (opcional, futuro): 12. Tool get_loki_events, integracion calendar via wrapper de skill google-workspace.

Consecuencias

Positivas: - Hermes pasa de stateless a contextual sin re-arquitectura del agente. - Reutiliza infra existente (e5-large, healthchecks, HA REST, git mirror). - Coste OAuth controlado por tools on-demand + quota guard.

Negativas / Riesgos: - Privacy es el riesgo dominante. Defense-in-depth (git-crypt + regex + privacy: secret opt-out) es lo mejor disponible sin LLM local viable. Asumimos riesgo residual: secretos nuevos sin patron regex pueden filtrarse. Mitigacion: auditoria trimestral de privacy-patterns vs memorias actuales. - Lag 45 min en sync memorias. Aceptable; no es realtime monitoring. - Complejidad +500 LOC en LXC 101 (RAG + filter + tools). Mantenibilidad ok si esta versionado en monxas/stacks/hermes-context/. - Si la Mac esta apagada >horas, memorias no se actualizan. Hermes sigue funcional con snapshot anterior — graceful degradation natural.

Trade-off explicito aceptado: NO usar LLM local para queries con secretos. Razon: hardware insuficiente (validado mayo 2026). Esto significa que confiamos en defense-in-depth para no filtrar a ChatGPT. Si en algun momento esta confianza se rompe (ej. leak detectado en logs OpenAI), revisar D3 y considerar bloquear queries que tocan archivos privacy: secret por completo, aceptando UX peor.

Referencias

  • ~/.claude/memory/hermes_setup.md — Hermes LXC 101 baseline, OAuth Codex, fallback FOSS desmontado
  • ~/.claude/memory/iarq_oviedo_stack.md — asturias-rag (e5-large reutilizable)
  • ADR-0003 — sesion 2026-05-16, contexto cluster reciente
  • Hermes Agent docs v0.13.0 — mcp_servers, fallback_providers, skills config