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
|
||||
}
|
||||
@@ -3,10 +3,14 @@ package cognition
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/andydixon/noema/internal/beliefs"
|
||||
"github.com/andydixon/noema/internal/brain"
|
||||
"github.com/andydixon/noema/internal/memory"
|
||||
"github.com/andydixon/noema/internal/storage"
|
||||
"github.com/andydixon/noema/internal/workspace"
|
||||
"github.com/andydixon/noema/pkg/concepts"
|
||||
"github.com/andydixon/noema/pkg/events"
|
||||
@@ -15,13 +19,62 @@ import (
|
||||
|
||||
// Handlers are the executive actions for admitted thought kinds.
|
||||
type Handlers struct {
|
||||
Memory *memory.Store
|
||||
Memory *memory.Store
|
||||
Beliefs *beliefs.Store
|
||||
}
|
||||
|
||||
// Register installs the handlers on a registry.
|
||||
func (h Handlers) Register(r *brain.Registry) {
|
||||
r.Handle("attend", h.attend)
|
||||
r.Handle("retrieve", h.retrieve)
|
||||
r.Handle("hypothesis", h.hypothesis)
|
||||
}
|
||||
|
||||
// emitter adapts the cycle to the beliefs store's Emitter.
|
||||
func emitter(c *brain.Cycle) beliefs.Emitter {
|
||||
return func(ctx context.Context, _ storage.Querier, n events.New) (events.Event, error) {
|
||||
return c.EmitNow(ctx, n)
|
||||
}
|
||||
}
|
||||
|
||||
// hypothesis turns an admitted hypothesis candidate into a belief whose
|
||||
// confidence is computed from the cited observations. Temporal co-occurrence
|
||||
// is weak evidence, so weights are small and the result stays a hypothesis
|
||||
// until independent support arrives.
|
||||
func (h Handlers) hypothesis(ctx context.Context, c *brain.Cycle, t workspace.Thought) error {
|
||||
if h.Beliefs == nil {
|
||||
return nil
|
||||
}
|
||||
prop, _ := t.Content["proposition"].(string)
|
||||
if prop == "" {
|
||||
prop = strings.TrimPrefix(t.Summary, "hypothesis: ")
|
||||
}
|
||||
var ev []beliefs.EvidenceInput
|
||||
for _, ref := range t.Evidence {
|
||||
if ref.Kind != "event" {
|
||||
continue
|
||||
}
|
||||
cred := 0.6
|
||||
if e, err := c.Runtime.Events().Get(ctx, ids.ID(ref.ID)); err == nil {
|
||||
var p events.ObservationPayload
|
||||
if json.Unmarshal(e.Payload, &p) == nil && p.Trust.Valid() {
|
||||
cred = p.Trust.Credibility()
|
||||
}
|
||||
}
|
||||
ev = append(ev, beliefs.EvidenceInput{Direction: "for", Kind: "event", RefID: ref.ID, Source: "co-occurrence:" + ref.ID, Weight: 0.15, Credibility: cred, Note: "temporal co-occurrence (" + ref.Note + ")"})
|
||||
}
|
||||
res, err := h.Beliefs.Create(ctx, c.Tx, emitter(c), beliefs.New{MindID: c.State.Mind.ID, Proposition: prop, Epistemic: concepts.Hypothesis, Source: "brain.associate",
|
||||
Evidence: ev, Alternatives: []string{"coincidence: the two events are unrelated", "a third factor caused both"}, OriginEvent: t.EventID, Causation: t.EventID, Correlation: c.State.CycleID,
|
||||
Detail: map[string]any{"cause_event": t.Content["cause_event"], "effect_event": t.Content["effect_event"], "gap_seconds": t.Content["gap_seconds"]}})
|
||||
if err != nil {
|
||||
if errors.Is(err, beliefs.ErrBlocked) {
|
||||
c.Emit(events.New{Type: events.Metacognition, Source: "brain.executive", Summary: "hypothesis blocked by operator correction: " + prop, Salience: 0.3, Confidence: 1, Metadata: map[string]any{"check": "do_not_infer"}})
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
c.Runtime.Hold(workspace.Item{Key: "belief:" + string(res.Belief.ID), Content: "hypothesis (" + fmt.Sprintf("%.2f", res.Belief.Confidence) + "): " + prop, Kind: "thought", Ref: string(res.Belief.ID), SourceEvent: t.EventID})
|
||||
return nil
|
||||
}
|
||||
|
||||
// attend encodes the attended event as a memory and holds it in working
|
||||
|
||||
Reference in New Issue
Block a user