feat: add beliefs with evidence-derived confidence, contradictions and corrections
Phase 6. Beliefs are propositions with a prior set by epistemic kind and a confidence computed from typed evidence: sigmoid(logit(prior) + slope × (support × diversity − opposition)), where support from a single source is discounted so repetition cannot manufacture certainty, and bounds keep the mind away from absolute certainty. Every evaluation stores its breakdown and appends a version row protected by the append-only trigger; the belief page shows the arithmetic, evidence for and against, alternatives, contradictions and history. Contradiction detection is deterministic first (negation plus token overlap): a hit emits contradiction_detected, links both beliefs and makes each counter-evidence for the other so both trend to contested. Operators can assert beliefs (their statement is the evidence), add evidence, mark a belief wrong (pinned rejected, history kept) and forbid an inference; do-not-infer corrections block matching hypotheses. Retracting a memory retracts the evidence that cited it and re-evaluates the beliefs concerned. The associate process proposes low-confidence causal hypotheses from co-occurring notable observations, and the hypothesis handler turns admitted ones into beliefs with automatic alternatives, closing demo scenarios 1 and 2 without any model call.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package cognition
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/andydixon/noema/internal/attention"
|
||||
"github.com/andydixon/noema/internal/brain"
|
||||
"github.com/andydixon/noema/internal/workspace"
|
||||
"github.com/andydixon/noema/pkg/events"
|
||||
"github.com/andydixon/noema/pkg/ids"
|
||||
)
|
||||
|
||||
// Associate proposes low-confidence causal hypotheses when two notable
|
||||
// observations arrive close together ("deployment at 09:10, latency at
|
||||
// 09:12"). It is deliberately weak: confidence comes from the belief layer's
|
||||
// arithmetic over the two observations, never from the wording.
|
||||
type Associate struct {
|
||||
Window time.Duration // how far back an earlier observation may lie
|
||||
}
|
||||
|
||||
// Name implements brain.Process.
|
||||
func (Associate) Name() string { return "associate" }
|
||||
|
||||
// Observe implements brain.Process.
|
||||
func (a Associate) Observe(_ context.Context, s *brain.State) ([]brain.Candidate, error) {
|
||||
if s.Services.Beliefs == nil {
|
||||
return nil, nil
|
||||
}
|
||||
window := a.Window
|
||||
if window <= 0 {
|
||||
window = 15 * time.Minute
|
||||
}
|
||||
var out []brain.Candidate
|
||||
for _, ev := range s.NewEvents {
|
||||
if strings.HasPrefix(ev.Source, "brain") || ev.Type != events.Observation {
|
||||
continue
|
||||
}
|
||||
laterToks := workspace.Tokenise(ev.Summary)
|
||||
laterU := urgency(laterToks)
|
||||
if laterU < 0.4 {
|
||||
continue
|
||||
}
|
||||
for _, it := range s.Workspace.WorkingMemory {
|
||||
if it.Kind != "observation" || it.SourceEvent.IsZero() || it.SourceEvent == ev.ID {
|
||||
continue
|
||||
}
|
||||
if ev.OccurredAt.Sub(it.TouchedAt) > window || it.TouchedAt.After(ev.OccurredAt) {
|
||||
continue
|
||||
}
|
||||
earlierToks := workspace.Tokenise(it.Content)
|
||||
if urgency(earlierToks) < 0.3 && workspace.Overlap(earlierToks, toSet(laterToks)) < 0.2 {
|
||||
continue
|
||||
}
|
||||
pair := []string{string(it.SourceEvent), string(ev.ID)}
|
||||
sort.Strings(pair)
|
||||
prop := truncate(it.Content, 100) + " may have caused: " + truncate(ev.Summary, 100)
|
||||
out = append(out, brain.Candidate{Kind: "hypothesis", Subject: strings.Join(pair, "+"), Summary: "hypothesis: " + prop,
|
||||
Content: map[string]any{"proposition": prop, "cause_event": string(it.SourceEvent), "effect_event": string(ev.ID), "gap_seconds": int(ev.OccurredAt.Sub(it.TouchedAt).Seconds()), "source_event": string(ev.ID)},
|
||||
Features: attention.Features{Salience: 0.5*ev.Salience + 0.2, Novelty: 0.6, Consequence: 0.6, Urgency: laterU * 0.5, Confidence: 0.3, ContextRelevance: 0.5},
|
||||
Confidence: 0.3, SourceEvents: []ids.ID{ev.ID, it.SourceEvent},
|
||||
Evidence: []events.EvidenceRef{{Kind: "event", ID: string(it.SourceEvent), Note: "earlier"}, {Kind: "event", ID: string(ev.ID), Note: "later"}}})
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func toSet(ws []string) map[string]struct{} {
|
||||
m := make(map[string]struct{}, len(ws))
|
||||
for _, w := range ws {
|
||||
m[w] = struct{}{}
|
||||
}
|
||||
return m
|
||||
}
|
||||
Reference in New Issue
Block a user