feat: add immutable cognitive event store, live streams and event explorer

Phase 3. Events are the append-only record of everything a mind does
(ADR-0002). pkg/events defines the closed type vocabulary, categories,
evidence references and the observation payload with explicit trust
levels. The store validates and appends inside the caller's transaction,
publishes to a bounded fan-out bus only after commit, queries by seq
cursor with type/category/source/salience/confidence/time/search
filters, and walks provenance: cause, parents, children, cited evidence,
derived events and correlation groups. Durable per-consumer cursors never
move backwards.

Server-Sent Events streams (per mind and global) subscribe before
replaying history after Last-Event-ID so nothing is missed, apply the
same filter vocabulary live, and heartbeat. Operators can inject
observations tagged with operator trust for demos and tests.

UI: live thought stream with filters and paging, forensic event page
with causal chain and evidence navigation, dashboard important events.
Personality and status changes now leave operator_action events in the
mind's own history. CLI: event inspect/list/tail, observe.
This commit is contained in:
Andy Dixon
2026-06-17 20:02:22 +01:00
parent fb34f08f40
commit 85d769b245
29 changed files with 1866 additions and 25 deletions
+7
View File
@@ -2,6 +2,7 @@ package api
import (
"errors"
"fmt"
"net/http"
"strconv"
@@ -119,6 +120,9 @@ func (s *Server) mindPatch(w http.ResponseWriter, r *http.Request) {
return
}
s.deps.Audit.Log(r.Context(), "mind.update", "mind", m.ID.String(), in)
if in.Status != nil {
s.deps.Events.OperatorAction(r.Context(), m.ID, actor(r), "mind.status", "Operator "+actor(r)+" set status to "+string(*in.Status), map[string]any{"status": *in.Status})
}
writeJSON(w, http.StatusOK, out)
}
@@ -195,6 +199,7 @@ func (s *Server) personalityPut(w http.ResponseWriter, r *http.Request) {
return
}
s.deps.Audit.Log(r.Context(), "personality.update", "mind", m.ID.String(), map[string]any{"version": v.Version, "note": in.ChangeNote, "changes": minds.Diff(m.Personality, v.Personality)})
s.deps.Events.OperatorAction(r.Context(), m.ID, actor(r), "personality.update", fmt.Sprintf("Operator %s updated personality to version %d: %s", actor(r), v.Version, in.ChangeNote), map[string]any{"version": v.Version, "changes": minds.Diff(m.Personality, v.Personality)})
writeJSON(w, http.StatusOK, v)
}
@@ -245,6 +250,7 @@ func (s *Server) personalityRestore(w http.ResponseWriter, r *http.Request) {
return
}
s.deps.Audit.Log(r.Context(), "personality.restore", "mind", m.ID.String(), map[string]any{"restored": n, "version": v.Version})
s.deps.Events.OperatorAction(r.Context(), m.ID, actor(r), "personality.restore", fmt.Sprintf("Operator %s restored personality version %d as version %d", actor(r), n, v.Version), map[string]any{"restored": n, "version": v.Version})
writeJSON(w, http.StatusOK, v)
}
@@ -290,6 +296,7 @@ func (s *Server) personalityTemplate(w http.ResponseWriter, r *http.Request) {
return
}
s.deps.Audit.Log(r.Context(), "personality.template", "mind", m.ID.String(), map[string]any{"template_id": in.TemplateID, "version": v.Version})
s.deps.Events.OperatorAction(r.Context(), m.ID, actor(r), "personality.template", "Operator "+actor(r)+": "+v.ChangeNote, map[string]any{"version": v.Version})
writeJSON(w, http.StatusOK, v)
}