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>
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
AuditCfglleva 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.goya 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.goconAuditWriterstruct:- Constructor
New(cfg AuditCfg, matrixSender func(roomID, msg string), logger *slog.Logger) *AuditWriter - Método
Emit(event AuditEvent)que escribe aLogFile(JSONL append) y opcionalmente envía aLogToRoom(room Matrix) AuditEventstruct:{ 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
- Constructor
-
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
LogFilevacío (no escribe a archivo, solo room) - Test con
LogToRoomvacío (solo escribe a archivo)
-
1.3 Integrar
AuditWriterenagents/runtime.go:- En
New(): sicfg.Security.Audit.Enabled, crearAuditWritery guardarlo en el structAgent - Pasar
matrixSendercomo closure que usa el cliente Matrix del agente - Si audit no está habilitado,
AuditWriteres nil (los call sites hacen nil-check)
- En
-
1.4 Emitir eventos en los puntos clave:
agents/handler.go→message_received(sender, room, is_dm)agents/handler.go→command_exec(command name, sender)tools/registry.go→tool_exec(tool name, duration, success/error)shell/llm/→llm_request(provider, model, tokens) yllm_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
cmdMetricsenagents/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 |
- Leer logs del día actual con
-
2.3 El handler necesita acceso al
logDirdel agente — pasar via config o campo en Agent struct (ya existea.cfgcon 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.mdla 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
-
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).
-
Sin SQLite para métricas: el comando
!metricscalcula 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. -
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. -
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:
AuditCfgeninternal/config/schema.goshell/logger/query.gopara leer JSONLpkg/command/builtins.gopara registrar!metricsagents/commands.gopara 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 |