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:
@@ -0,0 +1,236 @@
|
||||
// Package events defines the cognitive event vocabulary shared by every part
|
||||
// of Noema. Events are the append-only record of what a mind perceived,
|
||||
// thought, believed, predicted and did, and the provenance links between
|
||||
// those things (ADR-0002).
|
||||
package events
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/andydixon/noema/pkg/ids"
|
||||
)
|
||||
|
||||
// Type identifies what kind of thing happened.
|
||||
type Type string
|
||||
|
||||
// Event types. The list is closed: adding one means adding it here so that
|
||||
// filters, metrics and the UI vocabulary stay in step.
|
||||
const (
|
||||
Observation Type = "observation"
|
||||
Perception Type = "perception"
|
||||
ThoughtCandidate Type = "thought_candidate"
|
||||
ThoughtSelected Type = "thought_selected"
|
||||
WorkspaceBroadcast Type = "workspace_broadcast"
|
||||
AttentionShift Type = "attention_shift"
|
||||
MemoryCreated Type = "memory_created"
|
||||
MemoryRetrieved Type = "memory_retrieved"
|
||||
MemoryReinforced Type = "memory_reinforced"
|
||||
MemoryDecayed Type = "memory_decayed"
|
||||
MemoryConsolidated Type = "memory_consolidated"
|
||||
BeliefCreated Type = "belief_created"
|
||||
BeliefUpdated Type = "belief_updated"
|
||||
BeliefChallenged Type = "belief_challenged"
|
||||
BeliefRetracted Type = "belief_retracted"
|
||||
HypothesisCreated Type = "hypothesis_created"
|
||||
HypothesisSupported Type = "hypothesis_supported"
|
||||
HypothesisWeakened Type = "hypothesis_weakened"
|
||||
PredictionCreated Type = "prediction_created"
|
||||
PredictionResolved Type = "prediction_resolved"
|
||||
PredictionError Type = "prediction_error"
|
||||
GoalCreated Type = "goal_created"
|
||||
GoalUpdated Type = "goal_updated"
|
||||
GoalCompleted Type = "goal_completed"
|
||||
GoalAbandoned Type = "goal_abandoned"
|
||||
DriveChanged Type = "drive_changed"
|
||||
EmotionChanged Type = "emotion_changed"
|
||||
Reflection Type = "reflection"
|
||||
ContradictionDetected Type = "contradiction_detected"
|
||||
ToolRequested Type = "tool_requested"
|
||||
ToolAuthorised Type = "tool_authorised"
|
||||
ToolDenied Type = "tool_denied"
|
||||
ToolExecuted Type = "tool_executed"
|
||||
ToolFailed Type = "tool_failed"
|
||||
NotificationCreated Type = "notification_created"
|
||||
NotificationSuppressed Type = "notification_suppressed"
|
||||
LLMRequest Type = "llm_request"
|
||||
LLMResponse Type = "llm_response"
|
||||
PolicyDecision Type = "policy_decision"
|
||||
SelfModelUpdated Type = "self_model_updated"
|
||||
DreamAssociation Type = "dream_association"
|
||||
OperatorAction Type = "operator_action"
|
||||
Metacognition Type = "metacognition"
|
||||
NarrativeUpdated Type = "narrative_updated"
|
||||
ModeChanged Type = "mode_changed"
|
||||
ResponseGenerated Type = "response_generated"
|
||||
CycleCompleted Type = "cycle_completed"
|
||||
)
|
||||
|
||||
// Category groups types for filtering and colouring in the UI.
|
||||
type Category string
|
||||
|
||||
// Categories.
|
||||
const (
|
||||
CatPerception Category = "perception"
|
||||
CatCognition Category = "cognition"
|
||||
CatMemory Category = "memory"
|
||||
CatBelief Category = "belief"
|
||||
CatPrediction Category = "prediction"
|
||||
CatGoal Category = "goal"
|
||||
CatAffect Category = "affect"
|
||||
CatAction Category = "action"
|
||||
CatLLM Category = "llm"
|
||||
CatOperator Category = "operator"
|
||||
CatSystem Category = "system"
|
||||
)
|
||||
|
||||
var categories = map[Type]Category{
|
||||
Observation: CatPerception, Perception: CatPerception,
|
||||
ThoughtCandidate: CatCognition, ThoughtSelected: CatCognition, WorkspaceBroadcast: CatCognition, AttentionShift: CatCognition,
|
||||
Reflection: CatCognition, ContradictionDetected: CatCognition, Metacognition: CatCognition, DreamAssociation: CatCognition,
|
||||
NarrativeUpdated: CatCognition, ResponseGenerated: CatCognition, CycleCompleted: CatSystem, ModeChanged: CatSystem,
|
||||
MemoryCreated: CatMemory, MemoryRetrieved: CatMemory, MemoryReinforced: CatMemory, MemoryDecayed: CatMemory, MemoryConsolidated: CatMemory,
|
||||
BeliefCreated: CatBelief, BeliefUpdated: CatBelief, BeliefChallenged: CatBelief, BeliefRetracted: CatBelief,
|
||||
HypothesisCreated: CatBelief, HypothesisSupported: CatBelief, HypothesisWeakened: CatBelief,
|
||||
PredictionCreated: CatPrediction, PredictionResolved: CatPrediction, PredictionError: CatPrediction,
|
||||
GoalCreated: CatGoal, GoalUpdated: CatGoal, GoalCompleted: CatGoal, GoalAbandoned: CatGoal,
|
||||
DriveChanged: CatAffect, EmotionChanged: CatAffect,
|
||||
ToolRequested: CatAction, ToolAuthorised: CatAction, ToolDenied: CatAction, ToolExecuted: CatAction, ToolFailed: CatAction,
|
||||
NotificationCreated: CatAction, NotificationSuppressed: CatAction, PolicyDecision: CatAction,
|
||||
LLMRequest: CatLLM, LLMResponse: CatLLM,
|
||||
SelfModelUpdated: CatSystem, OperatorAction: CatOperator,
|
||||
}
|
||||
|
||||
// CategoryOf returns the category of a type (CatSystem for unknown types).
|
||||
func CategoryOf(t Type) Category {
|
||||
if c, ok := categories[t]; ok {
|
||||
return c
|
||||
}
|
||||
return CatSystem
|
||||
}
|
||||
|
||||
// Valid reports whether t is a known event type.
|
||||
func (t Type) Valid() bool { _, ok := categories[t]; return ok }
|
||||
|
||||
// AllTypes lists every known type in a stable order.
|
||||
func AllTypes() []Type {
|
||||
out := make([]Type, 0, len(categories))
|
||||
for t := range categories {
|
||||
out = append(out, t)
|
||||
}
|
||||
sortTypes(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func sortTypes(ts []Type) {
|
||||
for i := 1; i < len(ts); i++ {
|
||||
for j := i; j > 0 && ts[j] < ts[j-1]; j-- {
|
||||
ts[j], ts[j-1] = ts[j-1], ts[j]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EvidenceRef points at something that supports or explains an event: a
|
||||
// memory, belief, another event, an entity, a document, an LLM call, a
|
||||
// prediction, a message.
|
||||
type EvidenceRef struct {
|
||||
Kind string `json:"kind"` // event | memory | belief | entity | document | llm_call | prediction | goal | message | approval
|
||||
ID string `json:"id"`
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
// Event is one immutable record.
|
||||
type Event struct {
|
||||
ID ids.ID `json:"id"`
|
||||
Seq int64 `json:"seq"`
|
||||
MindID ids.ID `json:"mind_id"`
|
||||
Type Type `json:"type"`
|
||||
Category Category `json:"category"`
|
||||
Source string `json:"source"` // process, integration or actor that produced it
|
||||
OccurredAt time.Time `json:"occurred_at"`
|
||||
RecordedAt time.Time `json:"recorded_at"`
|
||||
ParentIDs []ids.ID `json:"parent_ids"`
|
||||
CausationID ids.ID `json:"causation_id,omitempty"` // the event that directly caused this one
|
||||
CorrelationID ids.ID `json:"correlation_id,omitempty"` // cycle, conversation or job this belongs to
|
||||
Salience float64 `json:"salience"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
Valence *float64 `json:"valence,omitempty"`
|
||||
Arousal *float64 `json:"arousal,omitempty"`
|
||||
Evidence []EvidenceRef `json:"evidence"`
|
||||
Summary string `json:"summary"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
}
|
||||
|
||||
// New describes an event to append. Zero OccurredAt means now.
|
||||
type New struct {
|
||||
MindID ids.ID
|
||||
Type Type
|
||||
Source string
|
||||
OccurredAt time.Time
|
||||
ParentIDs []ids.ID
|
||||
CausationID ids.ID
|
||||
CorrelationID ids.ID
|
||||
Salience float64
|
||||
Confidence float64
|
||||
Valence *float64
|
||||
Arousal *float64
|
||||
Evidence []EvidenceRef
|
||||
Summary string
|
||||
Metadata map[string]any
|
||||
Payload any // marshalled to JSON; nil → {}
|
||||
}
|
||||
|
||||
// ObservationPayload is the payload contract for Observation events. Every
|
||||
// piece of external data carries its provenance and trust level (spec §25).
|
||||
type ObservationPayload struct {
|
||||
Text string `json:"text"`
|
||||
ContentType string `json:"content_type,omitempty"` // text/plain, text/markdown, application/json ...
|
||||
Source string `json:"source"` // conversation, operator, research_folder, git, prometheus, webhook ...
|
||||
Trust Trust `json:"trust"`
|
||||
Method string `json:"method,omitempty"` // how it was ingested: chat, api, watch, poll, push
|
||||
Reference string `json:"reference,omitempty"` // original locator: message id, file path, URL, commit
|
||||
Channel string `json:"channel,omitempty"` // conversation or integration identifier
|
||||
Actor string `json:"actor,omitempty"` // who said or produced it, if known
|
||||
}
|
||||
|
||||
// Trust is the credibility class assigned to an external source. It never
|
||||
// exceeds the trust of the channel the data arrived through.
|
||||
type Trust string
|
||||
|
||||
// Trust levels, from most to least trusted.
|
||||
const (
|
||||
TrustOperator Trust = "operator" // an authenticated operator speaking as themselves
|
||||
TrustUser Trust = "user" // an authenticated user in conversation
|
||||
TrustSystem Trust = "system" // Noema's own instrumentation
|
||||
TrustTool Trust = "tool" // output of a capability invocation
|
||||
TrustSource Trust = "source" // a configured integration (metrics, repo, feed)
|
||||
TrustLow Trust = "low" // arbitrary documents, web pages, unknown senders
|
||||
)
|
||||
|
||||
// Valid reports whether t is a known trust level.
|
||||
func (t Trust) Valid() bool {
|
||||
switch t {
|
||||
case TrustOperator, TrustUser, TrustSystem, TrustTool, TrustSource, TrustLow:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Credibility maps trust to a base weight used by belief and memory scoring.
|
||||
func (t Trust) Credibility() float64 {
|
||||
switch t {
|
||||
case TrustOperator:
|
||||
return 0.95
|
||||
case TrustUser:
|
||||
return 0.85
|
||||
case TrustSystem:
|
||||
return 0.9
|
||||
case TrustTool:
|
||||
return 0.8
|
||||
case TrustSource:
|
||||
return 0.7
|
||||
default:
|
||||
return 0.4
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user