fix(cognition): decisions and cycle output no longer depend on map order

Formation summed its terms by ranging over a map, so the score could
differ in the last bit between runs of the same state; the volition hook
assessed commitments, metacognition raised repeated-failure candidates and
the forensic explanation listed commitments in map iteration order, so the
events a cycle emits and an explanation's order varied between runs. Each
now iterates in sorted order, as replay determinism (PLAN §1 R3) requires.
This commit is contained in:
Andy Dixon
2026-10-06 20:24:54 +01:00
parent 581e9009cd
commit c3308106bb
4 changed files with 36 additions and 10 deletions
+15 -4
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"sort"
"strings"
"time"
@@ -93,8 +94,13 @@ func (Metacognition) Observe(ctx context.Context, s *brain.State) ([]brain.Candi
bySubject[b.Subject]++
}
}
for subj, n := range bySubject {
if n >= 3 {
subjects := make([]string, 0, len(bySubject))
for subj := range bySubject {
subjects = append(subjects, subj)
}
sort.Strings(subjects)
for _, subj := range subjects {
if n := bySubject[subj]; n >= 3 {
add("repeated_failure", subj, fmt.Sprintf("%d hypotheses about %q were rejected this month", n, subj), 0.6, nil, map[string]any{"subject": subj, "count": n})
}
}
@@ -260,8 +266,13 @@ func volitionChecks(ctx context.Context, s *brain.State, add func(check, ref, su
byAction[capName]++
}
}
for capName, n := range byAction {
if n >= 3 {
names := make([]string, 0, len(byAction))
for capName := range byAction {
names = append(names, capName)
}
sort.Strings(names) // candidates in a stable order, for replay
for _, capName := range names {
if n := byAction[capName]; n >= 3 {
add("repeated_execution_failure", capName, fmt.Sprintf("%s failed %d times in plans today", capName, n), 0.6, nil, map[string]any{"capability": capName, "count": n})
}
}