feat(experiments): cognitive forks per model with per-mind routing and comparison
A mind's configuration may override model routing per role (llm_roles); the router honours it for that mind only and fails closed when the named model is missing or disabled, so a comparison between providers can never silently fall back to the default. Experiments may declare variants: each becomes its own fork of the same snapshot with its own configuration, personality or routing, receives identical stimuli, and is measured. The results carry one entry per variant and a deterministic comparison: first divergent stimulus, belief and attention overlap, mean score difference on shared propositions, and the ranges of memory counts, goal counts, cost and capability requests. Single-subject definitions are unchanged.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/andydixon/noema/internal/minds"
|
||||
"github.com/andydixon/noema/internal/replay/replaytest"
|
||||
"github.com/andydixon/noema/pkg/ids"
|
||||
)
|
||||
|
||||
func TestExperimentRunsInSandbox(t *testing.T) {
|
||||
@@ -62,3 +63,62 @@ func TestExperimentRunsInSandbox(t *testing.T) {
|
||||
t.Fatalf("bad override: %+v", bad)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVariantsShareStateAndCompare: two forks of one snapshot receive the
|
||||
// same stimuli under different configurations; each gets its own subject,
|
||||
// the comparison is computed deterministically, and the first variant
|
||||
// remains the experiment's subject for the existing views.
|
||||
func TestVariantsShareStateAndCompare(t *testing.T) {
|
||||
h := replaytest.NewHarness(t)
|
||||
ctx := context.Background()
|
||||
base, _ := h.Minds.Create(ctx, minds.CreateParams{Slug: "vbase", DisplayName: "Base"})
|
||||
sn, err := h.Snaps.Create(ctx, base, "baseline", "", "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
svc := &Service{DB: h.Pool, Minds: h.Minds, Snapshots: h.Snaps, Brain: h.Brain, Identity: h.Identity, Log: slog.New(slog.DiscardHandler)}
|
||||
def := Definition{
|
||||
Stimuli: []Stimulus{{Text: "Deployment of the payments service finished at 09:10"}, {Text: "Latency alert on the payments service at 09:12"}},
|
||||
Variants: []Variant{
|
||||
{Name: "wide", Config: map[string]any{"workspace_capacity": 7}},
|
||||
{Name: "narrow", Config: map[string]any{"workspace_capacity": 2}, LLMRoles: map[string]string{"respond": string(ids.New())}},
|
||||
},
|
||||
}
|
||||
e, err := svc.Create(ctx, "capacity comparison", "a narrow workspace attends to fewer recollections", sn.ID, def, "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
e, err = svc.Run(ctx, e.ID, "test")
|
||||
if err != nil || e.Status != "done" {
|
||||
t.Fatalf("run: %v %+v", err, e)
|
||||
}
|
||||
res := ResultsOf(e)
|
||||
if len(res.Variants) != 2 || res.Variants[0].Name != "wide" || res.Variants[1].Name != "narrow" {
|
||||
t.Fatalf("variants: %+v", res.Variants)
|
||||
}
|
||||
if res.Variants[0].Subject == res.Variants[1].Subject || res.Subject != res.Variants[0].Subject {
|
||||
t.Fatalf("subjects: %+v", res.Variants)
|
||||
}
|
||||
a, _ := h.Minds.Get(ctx, res.Variants[0].Subject)
|
||||
b, _ := h.Minds.Get(ctx, res.Variants[1].Subject)
|
||||
if a.Config.WorkspaceCapacity != 7 || b.Config.WorkspaceCapacity != 2 || len(b.Config.LLMRoles) != 1 || len(a.Config.LLMRoles) != 0 {
|
||||
t.Fatalf("overrides not applied per fork: %+v %+v", a.Config, b.Config)
|
||||
}
|
||||
if res.Comparison == nil || res.Comparison.BeliefOverlap < 0 || res.Comparison.BeliefOverlap > 1 || len(res.Comparison.Notes) == 0 {
|
||||
t.Fatalf("comparison: %+v", res.Comparison)
|
||||
}
|
||||
if len(res.Variants[0].Steps) != 2 || len(res.Variants[1].Steps) != 2 {
|
||||
t.Fatalf("each variant should have seen both stimuli: %+v", res.Variants)
|
||||
}
|
||||
// The comparison is a pure function of the results and therefore repeatable.
|
||||
again := Compare(res.Variants)
|
||||
if again.FirstDivergentStimulus != res.Comparison.FirstDivergentStimulus || again.AttentionOverlap != res.Comparison.AttentionOverlap {
|
||||
t.Fatal("comparison not deterministic")
|
||||
}
|
||||
// A single-subject definition still works and records no comparison.
|
||||
single, _ := svc.Create(ctx, "single", "", sn.ID, Definition{Stimuli: def.Stimuli}, "test")
|
||||
single, _ = svc.Run(ctx, single.ID, "test")
|
||||
if sr := ResultsOf(single); len(sr.Variants) != 1 || sr.Comparison != nil {
|
||||
t.Fatalf("single subject: %+v", sr)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user