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.
77 lines
2.7 KiB
Go
77 lines
2.7 KiB
Go
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
|
|
}
|