feat: implementar audit trail con AuditWriter y emision de eventos

Crea shell/audit/ con Writer que escribe eventos de auditoria a archivo
JSONL y opcionalmente a un room Matrix. Integra la emision de eventos
en los puntos clave del runtime:

- message_received: al recibir cualquier evento Matrix (handler.go)
- command_exec: al ejecutar un comando (handler.go)
- tool_exec: al ejecutar una tool (tools/registry.go via AuditFunc callback)
- llm_request / llm_error: al llamar al LLM (llm.go)

El Writer se inicializa en agents/runtime.go si security.audit.enabled=true.
Usa patron de inyeccion de dependencias (MatrixSender como funcion,
AuditFunc como callback) para evitar acoplamiento entre packages.

Incluye tests completos para el Writer: escritura JSONL, filtrado por
Include, modo solo-file, modo solo-room, auto-set de timestamp.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 20:13:21 +00:00
parent 892fe0cb19
commit fb96a79feb
7 changed files with 525 additions and 2 deletions
+14
View File
@@ -12,6 +12,7 @@ import (
"github.com/enmanuel/agents/pkg/decision"
coretypes "github.com/enmanuel/agents/pkg/llm"
"github.com/enmanuel/agents/pkg/personality"
"github.com/enmanuel/agents/shell/audit"
shelllm "github.com/enmanuel/agents/shell/llm"
)
@@ -75,6 +76,12 @@ func (a *Agent) runLLM(ctx context.Context, msgCtx decision.MessageContext, memK
resp, err := a.llm(ctx, req)
if err != nil {
a.logger.Error("LLM call failed", "model", req.Model, "err", err)
// Audit: llm_error
a.emitAudit(audit.Event{
AgentID: a.cfg.Agent.ID,
EventType: audit.EventLLMError,
Detail: fmt.Sprintf("provider=%s model=%s error=%s", a.cfg.LLM.Primary.Provider, req.Model, err),
})
return "", err
}
@@ -84,6 +91,13 @@ func (a *Agent) runLLM(ctx context.Context, msgCtx decision.MessageContext, memK
"finish_reason", resp.FinishReason,
)
// Audit: llm_request
a.emitAudit(audit.Event{
AgentID: a.cfg.Agent.ID,
EventType: audit.EventLLMRequest,
Detail: fmt.Sprintf("provider=%s model=%s content_len=%d tool_calls=%d", a.cfg.LLM.Primary.Provider, req.Model, len(resp.Content), len(resp.ToolCalls)),
})
// No tool calls — return the text response
if len(resp.ToolCalls) == 0 {
return resp.Content, nil