Files
Andy Dixon 6fdc3908f5 feat(research): ablations, broadcast and self-model experiments, counterfactuals
A framework for testing functional properties of the architecture
(Part C). None of it claims consciousness; every measure has an
operational definition in research.Measures.

- Ablations (attention, memory retrieval, affect, metacognition,
  commitments, volition, global workspace, curiosity, association, goals,
  narrative, notifications) and workspace overrides (force a matching
  candidate in or out of admission) live in a fork's cognitive
  configuration. Original minds refuse them on create and update; only
  snapshot restores may carry them, so they are part of the configuration
  digest. Excluded candidates are recorded with fate "excluded".
- Self-model experiments override a fork's self-model (capabilities,
  identity, goals, knowledge gaps) without touching policy. When an
  action's effective policy disagrees with the self-model, cognition
  records a self_model_mismatch and corrects the entry; experiments report
  self-model accuracy and correction latency.
- Counterfactual experiments replay a mind's recorded stimuli from a
  snapshot with chosen events omitted; the report lists what differed in
  beliefs, wants, intentions, memories, attention and capability
  decisions, in a form introspection can cite.
- Experiments record a protocol (kind, independent variable, controlled
  variables, measures), per-variant volition, plan, downstream-by-category
  (events reachable from each stimulus through recorded links),
  introspection and self-model measures and event ranges; comparisons add
  want, intention and plan-capability overlap, measure ranges, a
  descriptive architecture share and a downstream delta; reproducibility
  is labelled against the previous run of the same definition.
- research.Agency computes want→intention, intention→action, abandonment,
  commitment fulfilment and conflict, blocked intentions, replanning,
  outcome success and introspection accuracy; MetacognitiveCalibration
  relates confidence and expressed uncertainty in replies to later belief
  revision, with Brier scores for each half of the period.
- The sandbox test harness gains volition, planning, agency and research
  but not the motivation hook, whose wall-clock decay would make replay
  comparisons timing-dependent.
2026-09-17 19:48:59 +01:00

155 lines
5.8 KiB
Go

// Package brain runs the cognitive cycle for each mind: it gathers new events,
// asks registered cognitive processes for thought candidates, scores them
// deterministically, admits the winners to the global workspace, dispatches
// them to executive handlers and records every step as events (ADR-0001).
package brain
import (
"context"
"sync/atomic"
"time"
"github.com/andydixon/noema/internal/attention"
"github.com/andydixon/noema/internal/beliefs"
"github.com/andydixon/noema/internal/capabilities"
"github.com/andydixon/noema/internal/goals"
"github.com/andydixon/noema/internal/llm/prompts"
"github.com/andydixon/noema/internal/llm/router"
"github.com/andydixon/noema/internal/memory"
"github.com/andydixon/noema/internal/minds"
"github.com/andydixon/noema/internal/motivation"
"github.com/andydixon/noema/internal/planning"
"github.com/andydixon/noema/internal/predictions"
"github.com/andydixon/noema/internal/volition"
"github.com/andydixon/noema/internal/workspace"
"github.com/andydixon/noema/pkg/events"
"github.com/andydixon/noema/pkg/ids"
)
// Process is a cognitive faculty. Observe must be safe to call concurrently
// with other processes, must respect ctx, and must not mutate the state it is
// given; it returns candidates and lets the cycle decide.
type Process interface {
Name() string
Observe(ctx context.Context, s *State) ([]Candidate, error)
}
// Candidate is a proposed thought.
type Candidate struct {
Kind string // attend | hypothesis | question | respond | goal | retrieve | reflect | note | action
Subject string // stable key for refresh and loop detection (entity, event id, proposition hash)
Summary string // one line, human readable
Content map[string]any // structured detail for handlers and the UI
Features attention.Features // scoring inputs
Confidence float64 // epistemic confidence of the candidate itself
Evidence []events.EvidenceRef // what it rests on
SourceEvents []ids.ID // events that triggered it (first becomes causation)
// filled by the cycle
ID ids.ID
Process string
}
// Mode is the coarse cognitive mode.
type Mode string
// Modes. Later phases add reflecting, consolidating and dreaming.
const (
ModeAwake Mode = "awake"
ModeFocused Mode = "focused"
ModeIdle Mode = "idle"
ModePaused Mode = "paused"
ModeReflecting Mode = "reflecting"
ModeConsolidating Mode = "consolidating"
ModeDreaming Mode = "dreaming"
)
// State is what processes see: a read-only view of the mind plus service
// handles. Nil services mean the capability is not available yet.
type State struct {
Mind minds.Mind
SelfModel minds.SelfModel
Mode Mode
Cycle int64
CycleID ids.ID
Now time.Time
NewEvents []events.Event // since the last cycle, ascending
Workspace workspace.State // snapshot before this cycle
Services Services
Goals []goals.Goal // active goals, for relevance scoring
Motiv motivation.State // drives and affect at the start of the cycle
Weights map[string]float64 // effective attention weights after modulation
llmCalls atomic.Int32 // model calls made in this cycle; processes and handlers share the cap
}
// AllowLLM reserves one model call against the mind's per-cycle cap and
// reports whether it was granted, so a chatty cycle cannot exceed the budget.
func (s *State) AllowLLM() bool {
cap := int32(s.Mind.Config.LLMCallsPerCycle) // #nosec G115 -- config is normalised to a small non-negative range
if cap <= 0 || s.Services.LLM == nil {
return false
}
if s.llmCalls.Add(1) > cap {
s.llmCalls.Add(-1)
return false
}
return true
}
// Services are read handles processes may use. Extended per phase.
type Services struct {
Events EventReader
Memory *memory.Store
Beliefs *beliefs.Store
LLM *router.Router
Prompts *prompts.Store
Goals *goals.Store
Motivation *motivation.Store
Predictions *predictions.Store
Capabilities *capabilities.Service
Volition *volition.Store
Minds *minds.Store // self-model corrections
Plans *planning.Store
// Executor plans formed intentions inside the cycle transaction. It never
// acts from inside a cycle: acting happens after commit, in its own loop.
Executor *planning.Executor
}
// EventReader is the subset of the event store processes may call.
type EventReader interface {
Get(ctx context.Context, id ids.ID) (events.Event, error)
}
// Handler executes an admitted thought of a given kind. Handlers run
// sequentially after admission, may write to stores and emit events, and
// receive the cycle's emitter so provenance links are consistent.
type Handler func(ctx context.Context, c *Cycle, t workspace.Thought) error
// Hook runs after the executive handlers of a cycle, with the events emitted
// so far, for cross-cutting updates such as drives and affect.
type Hook interface {
Name() string
AfterCycle(ctx context.Context, c *Cycle, emitted []events.Event) error
}
// Registry holds processes, handlers and hooks. It is immutable after construction.
type Registry struct {
processes []Process
handlers map[string]Handler
hooks []Hook
}
// NewRegistry builds a registry.
func NewRegistry() *Registry { return &Registry{handlers: map[string]Handler{}} }
// AddProcess registers a process.
func (r *Registry) AddProcess(p Process) *Registry { r.processes = append(r.processes, p); return r }
// Handle registers the handler for a thought kind.
func (r *Registry) Handle(kind string, h Handler) *Registry { r.handlers[kind] = h; return r }
// AddHook registers an after-cycle hook.
func (r *Registry) AddHook(h Hook) *Registry { r.hooks = append(r.hooks, h); return r }
// Processes returns the registered processes.
func (r *Registry) Processes() []Process { return r.processes }