Files
agents_and_robots/dev/issues/completed/0035-audit-trail-metrics.md
T
egutierrez 056dd3c73a docs: cerrar issue 0035
Mueve el issue a dev/issues/completed/ tras implementar todas las tareas:
audit trail con AuditWriter y comando !metrics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 20:22:36 +00:00

7.6 KiB

0035 — Observabilidad activa: audit trail + comando !metrics

Estado: pendiente

Objetivo

Activar la infraestructura de auditoría (AuditCfg) que ya está definida en internal/config/schema.go pero nunca implementada, y añadir un comando !metrics que agregue datos del log del día actual. Ambas features usan infraestructura existente (JSONL logs, config schema, command system) sin dependencias nuevas.

Contexto

  • AuditCfg lleva definida desde el schema original pero el código nunca la consume: no hay writer, no hay emisión de eventos, no hay integración con el runtime.
  • Los logs JSONL ya contienen métricas útiles (duration_ms, tokens_used, tool_exec_*, command_received) pero no hay forma de consultarlas sin parsear archivos manualmente.
  • shell/logger/query.go ya tiene helpers para leer y filtrar logs por fecha/campo.
  • El command system (!status, !info) ya existe y es extensible via built-in.

Arquitectura

Fase 1: Audit trail

shell/audit/           NEW — audit event writer (archivo JSONL + opcionalmente room Matrix)
shell/audit/writer.go  NEW — AuditWriter: escribe eventos a archivo y/o room

Integración en el runtime existente:

agents/handler.go      MOD — emitir eventos audit en puntos clave
agents/runtime.go      MOD — inicializar AuditWriter si cfg.Security.Audit.Enabled
tools/registry.go      MOD — emitir evento audit en tool_exec

Pure core / impure shell:

  • No se añade nada a pkg/ — los eventos audit son side effects puros (escritura a archivo/Matrix)
  • shell/audit/ es 100% impuro: escribe a disco y opcionalmente envía mensajes Matrix

Fase 2: Comando !metrics

agents/commands.go     MOD — añadir cmdMetrics como built-in
pkg/command/builtins.go MOD — añadir spec de !metrics

El comando lee los JSONL del día actual usando shell/logger/query.go (ya existente) y calcula agregados en memoria. No persiste nada, no crea tablas, no necesita SQLite.

Tareas

Fase 1 — Audit trail

  • 1.1 Crear shell/audit/writer.go con AuditWriter struct:

    • Constructor New(cfg AuditCfg, matrixSender func(roomID, msg string), logger *slog.Logger) *AuditWriter
    • Método Emit(event AuditEvent) que escribe a LogFile (JSONL append) y opcionalmente envía a LogToRoom (room Matrix)
    • AuditEvent struct: { Time, AgentID, EventType, SenderID, RoomID, Detail string }
    • Filtrado por Include (lista de event types a auditar; vacío = todos)
    • Event types iniciales: command_exec, tool_exec, llm_request, llm_error, message_received
  • 1.2 Crear shell/audit/writer_test.go:

    • Test de escritura a archivo (verificar formato JSONL)
    • Test de filtrado por Include (solo emite los tipos configurados)
    • Test con LogFile vacío (no escribe a archivo, solo room)
    • Test con LogToRoom vacío (solo escribe a archivo)
  • 1.3 Integrar AuditWriter en agents/runtime.go:

    • En New(): si cfg.Security.Audit.Enabled, crear AuditWriter y guardarlo en el struct Agent
    • Pasar matrixSender como closure que usa el cliente Matrix del agente
    • Si audit no está habilitado, AuditWriter es nil (los call sites hacen nil-check)
  • 1.4 Emitir eventos en los puntos clave:

    • agents/handler.gomessage_received (sender, room, is_dm)
    • agents/handler.gocommand_exec (command name, sender)
    • tools/registry.gotool_exec (tool name, duration, success/error)
    • shell/llm/llm_request (provider, model, tokens) y llm_error (provider, error)

Fase 2 — Comando !metrics

  • 2.1 Añadir spec en pkg/command/builtins.go:

    {Name: "metrics", Description: "Métricas agregadas del día actual", Usage: "!metrics"}
    
  • 2.2 Implementar cmdMetrics en agents/commands.go:

    • Leer logs del día actual con logger.ReadDayLogs(logDir, agentID, time.Now())
    • Calcular: total mensajes recibidos, comandos ejecutados, llamadas LLM (count + tokens totales + latencia media), llamadas a tools (count + errores), errores totales
    • Formatear como markdown table para Matrix
    • Ejemplo de output:
      **Métricas de hoy (2026-04-09):**
      
      | Métrica | Valor |
      |---------|-------|
      | Mensajes recibidos | 42 |
      | Comandos ejecutados | 15 |
      | Llamadas LLM | 27 |
      | Tokens totales | 45,230 |
      | Latencia LLM media | 1,250 ms |
      | Tool calls | 8 |
      | Tool errors | 1 |
      | Errores totales | 2 |
      | Uptime | 6h 30m |
      
  • 2.3 El handler necesita acceso al logDir del agente — pasar via config o campo en Agent struct (ya existe a.cfg con el agent ID, solo falta saber el baseDir de logs)

Fase 3 — Tests y cleanup

  • 3.1 Tests para cmdMetrics: crear logs JSONL de ejemplo en tmpdir, verificar que los agregados son correctos
  • 3.2 Test de integración: AuditWriter + handler emite eventos reales a archivo temporal
  • 3.3 Documentar en docs/security.md la sección de audit trail (config YAML de ejemplo)

Ejemplo de uso

Audit trail

Config en agents/asistente-2/config.yaml:

security:
  audit:
    enabled: true
    log_file: "logs/asistente-2/audit.jsonl"
    log_to_room: "!audit-room:matrix-af2f3d.organic-machine.com"
    include:
      - command_exec
      - tool_exec
      - llm_error

Resultado en audit.jsonl:

{"time":"2026-04-09T10:30:00Z","agent_id":"asistente-2","event":"command_exec","sender":"@user:matrix","room":"!abc:matrix","detail":"!status"}
{"time":"2026-04-09T10:30:05Z","agent_id":"asistente-2","event":"tool_exec","sender":"@user:matrix","room":"!abc:matrix","detail":"http_get duration=350ms ok"}

Comando !metrics

Usuario: !metrics
Bot:
**Métricas de hoy (2026-04-09):**

| Métrica | Valor |
|---------|-------|
| Mensajes recibidos | 42 |
| Comandos ejecutados | 15 |
| Llamadas LLM | 27 |
| Tokens totales | 45,230 |
| Latencia LLM media | 1,250 ms |
| Tool calls | 8 |
| Tool errors | 1 |
| Errores totales | 2 |
| Uptime | 6h 30m |

Decisiones de diseño

  1. Audit separado de logs normales: los logs de runtime son para debugging (alto volumen, retención corta). El audit trail es para compliance/revisión (eventos selectivos, retención configurable independiente).

  2. Sin SQLite para métricas: el comando !metrics calcula en memoria leyendo JSONL del día. Con los volúmenes actuales (~cientos de eventos/día por agente), esto es instantáneo. Si escala, se puede cachear o migrar a SQLite en un issue futuro.

  3. AuditWriter acepta matrixSender como función: evita acoplar shell/audit/ con el cliente Matrix directamente. Sigue el patrón de inyección de dependencias del proyecto.

  4. Include como allowlist: lista vacía = auditar todo. Esto es deny-by-default invertido (opt-in por tipo de evento) para evitar audit logs gigantes.

Prerequisitos

Ninguno. Todo usa infraestructura existente:

  • AuditCfg en internal/config/schema.go
  • shell/logger/query.go para leer JSONL
  • pkg/command/builtins.go para registrar !metrics
  • agents/commands.go para implementar el handler

Riesgos

Riesgo Mitigación
Audit file crece sin límite Usar el mismo DailyRotatingWriter de shell/logger/ o rotación externa (logrotate)
LogToRoom falla (room no existe) Log warning y continuar — audit a archivo no debe fallar por Matrix
!metrics lento con logs muy grandes Los JSONL se rotan a 50MB max. Un día normal tiene KB-pocos MB. Aceptable.
Audit de message_received loguea contenido sensible El Detail solo incluye metadata (sender, room, is_dm), nunca el body del mensaje