feat(ui): wants, intentions, commitments, plans, agency timeline, why, research

- Wants: active wants by effective strength with origin, means, drives,
  values, goal, reinforcement, suppressions and each conflict with its
  resolution rule; closed wants with their resolution.
- Intentions grouped by status with blocked reasons and plans, plus the
  wants that did not become intentions and why. An intention page shows
  sources, subordinate intentions, formation terms, every plan revision as
  a dependency graph, decisions and the correlated timeline; operators can
  abandon it.
- Commitments with polarity, exception, matcher, weight and due time,
  live conflicts between commitments, a creation form and close controls.
- Plans with estimate against budget; a plan page with the dependency
  graph, per-step expectations, observations and linked approvals,
  requests, executions and subordinate intentions, resources, completion
  and abort conditions, assumptions, alternatives, revisions and
  validation; operators can cancel a plan or release a blocked step.
- Agency timeline labelling each event as wanted, intended, committed,
  planned, requested, approved, acted, observed or reconsidered.
- "Why did this happen?" pages built from agency.Explain, reachable from
  events, executions in the approval centre, plan steps and intentions.
- A research dashboard per mind (volition and agency rates, introspection
  accuracy, action claims, self-model accuracy, calibration by confidence
  band, experiments on the mind) and a global research page grouping
  experiments by protocol; every figure sits next to its operational
  definition and the disclaimer. Experiment pages show the protocol,
  reproducibility, build, per-variant research measures, divergence
  extras and counterfactual findings.
- The mind tabs gain Volition; the navigation gains Research.
This commit is contained in:
Andy Dixon
2026-09-24 21:16:48 +01:00
parent a43fc31643
commit 2ecdbc39b5
23 changed files with 975 additions and 5 deletions
+7 -1
View File
@@ -116,7 +116,13 @@ func (u *UI) approvalsPage(w http.ResponseWriter, r *http.Request) {
for _, c := range u.deps.Capabilities.Registry.List() {
caps[c.Name] = c
}
u.render(w, r, "approvals", "Approval centre", map[string]any{"Pending": pending, "Decided": decided, "Executions": execs, "MindNames": names, "Batches": batches, "Grants": grants, "Caps": caps})
slugs := map[ids.ID]string{}
if all, err := u.deps.Minds.List(r.Context(), true); err == nil {
for _, m := range all {
slugs[m.ID] = m.Slug
}
}
u.render(w, r, "approvals", "Approval centre", map[string]any{"Pending": pending, "Decided": decided, "Executions": execs, "MindNames": names, "Batches": batches, "Grants": grants, "Caps": caps, "MindSlugs": slugs})
}
// requestBatch groups requests raised for one intention (or one mind in one
+46
View File
@@ -0,0 +1,46 @@
package ui
import (
"bytes"
"encoding/json"
"log/slog"
"strings"
"testing"
"github.com/andydixon/noema/internal/app"
"github.com/andydixon/noema/internal/auth"
"github.com/andydixon/noema/internal/config"
"github.com/andydixon/noema/internal/experiments"
"github.com/andydixon/noema/internal/minds"
"github.com/andydixon/noema/internal/snapshots"
"github.com/andydixon/noema/pkg/ids"
)
// The experiment page renders protocol, reproducibility, research measures
// and counterfactual findings without template errors.
func TestExperimentTemplateRendersResearchResults(t *testing.T) {
u, err := New(app.Deps{Config: config.Defaults(), Log: slog.New(slog.DiscardHandler)}, Actions{})
if err != nil {
t.Fatal(err)
}
acc := 0.5
res := experiments.Results{Variants: []experiments.VariantResult{
{Name: "admitted", Wants: []string{"a"}, Intentions: []string{}, Downstream: map[string]int{"memory": 2}, IntrospectionAccuracy: &acc, SelfModelAccuracy: &acc, CorrectionLatency: 1, LLMCalls: map[string]int{}, EventsByType: map[string]int{}},
{Name: "excluded", Ablations: []string{"metacognition"}, CorrectionLatency: -1, LLMCalls: map[string]int{}, EventsByType: map[string]int{}},
}, Comparison: &experiments.Comparison{FirstDivergentStimulus: 0, DownstreamDelta: map[string]int{"memory": 2}, ArchitectureShare: 0.4},
Reproducibility: experiments.Reproducibility{Status: "single run", Digest: "abc"},
Counterfactual: &experiments.CounterfactualReport{OmittedSummaries: []string{"disk errors"}, Findings: []string{"the belief X was never formed"}}}
raw, _ := json.Marshal(res)
e := experiments.Experiment{ID: ids.New(), Name: "gw", Hypothesis: "admission matters", Status: "done", Results: raw,
Definition: experiments.Definition{Protocol: &experiments.Protocol{Kind: "global_workspace", IndependentVariable: "admission", ControlledVariables: []string{"stimulus"}, Measures: []string{"workspace.downstream_events"}}}}
var buf bytes.Buffer
err = u.pages["experiment"].ExecuteTemplate(&buf, "layout", View{Principal: &auth.Principal{User: auth.User{Username: "watcher", Role: auth.RoleObserver}}, Data: map[string]any{"E": e, "Results": experiments.ResultsOf(e), "Snapshot": snapshots.Snapshot{}, "Base": minds.Mind{}, "Subject": nil, "DefinitionJSON": "{}"}})
if err != nil {
t.Fatal(err)
}
for _, want := range []string{"Independent variable", "single run", "Counterfactual", "the belief X was never formed", "Architecture share", "metacognition"} {
if !strings.Contains(buf.String(), want) {
t.Errorf("missing %q", want)
}
}
}
+1
View File
@@ -396,6 +396,7 @@ func (u *UI) Register(m *http.ServeMux) {
u.dreamRoutes(m)
u.studyRoutes(m)
u.privacyRoutes(m)
u.volitionRoutes(m)
for _, p := range []struct{ path, title, note string }{} {
m.Handle("GET "+p.path, u.require(auth.RoleObserver, func(w http.ResponseWriter, r *http.Request) { u.render(w, r, "placeholder", p.title, p.note) }))
+548
View File
@@ -0,0 +1,548 @@
package ui
import (
"encoding/json"
"net/http"
"sort"
"strconv"
"strings"
"time"
"github.com/andydixon/noema/internal/agency"
"github.com/andydixon/noema/internal/auth"
evstore "github.com/andydixon/noema/internal/events"
"github.com/andydixon/noema/internal/experiments"
"github.com/andydixon/noema/internal/planning"
"github.com/andydixon/noema/internal/research"
"github.com/andydixon/noema/internal/volition"
"github.com/andydixon/noema/pkg/events"
"github.com/andydixon/noema/pkg/ids"
)
func (u *UI) volitionRoutes(m *http.ServeMux) {
m.Handle("GET /minds/{mind}/wants", u.require(auth.RoleObserver, u.wantsPage))
m.Handle("GET /minds/{mind}/intentions", u.require(auth.RoleObserver, u.intentionsPage))
m.Handle("GET /minds/{mind}/intentions/{id}", u.require(auth.RoleObserver, u.intentionPage))
m.Handle("POST /minds/{mind}/intentions/{id}/abandon", u.require(auth.RoleOperator, u.intentionAbandon))
m.Handle("GET /intentions/{id}", u.require(auth.RoleObserver, u.intentionRedirect))
m.Handle("GET /minds/{mind}/commitments", u.require(auth.RoleObserver, u.commitmentsPage))
m.Handle("POST /minds/{mind}/commitments", u.require(auth.RoleOperator, u.commitmentCreate))
m.Handle("POST /minds/{mind}/commitments/{id}/close", u.require(auth.RoleOperator, u.commitmentClose))
m.Handle("GET /minds/{mind}/plans", u.require(auth.RoleObserver, u.plansPage))
m.Handle("GET /minds/{mind}/plans/{id}", u.require(auth.RoleObserver, u.planPage))
m.Handle("POST /minds/{mind}/plans/{id}/cancel", u.require(auth.RoleOperator, u.planCancel))
m.Handle("POST /minds/{mind}/plans/{id}/steps/{step}/retry", u.require(auth.RoleOperator, u.planStepRetry))
m.Handle("GET /minds/{mind}/agency", u.require(auth.RoleObserver, u.agencyPage))
m.Handle("GET /minds/{mind}/why", u.require(auth.RoleObserver, u.whyPage))
m.Handle("GET /minds/{mind}/research", u.require(auth.RoleObserver, u.researchPage))
m.Handle("GET /research", u.require(auth.RoleObserver, u.researchIndex))
}
type wantRow struct {
volition.Want
Eff float64
Conflicts []conflictView
}
type conflictView struct {
Other, Kind, Outcome, Rule string
Preferred bool
At time.Time
}
func (u *UI) wantsPage(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
ctx := r.Context()
all, _ := u.deps.Volition.Wants(ctx, nil, m.ID, nil, 500)
byID := map[ids.ID]volition.Want{}
for _, x := range all {
byID[x.ID] = x
}
conflicts, _ := u.deps.Volition.Conflicts(ctx, m.ID, "", 500)
now := time.Now()
var active, closed []wantRow
for _, x := range all {
row := wantRow{Want: x, Eff: x.Effective(now)}
for _, c := range conflicts {
other := ids.Nil
switch x.ID {
case c.WantA:
other = c.WantB
case c.WantB:
other = c.WantA
default:
continue
}
rule, _ := c.Rationale["rule"].(string)
row.Conflicts = append(row.Conflicts, conflictView{Other: byID[other].Description, Kind: c.Kind, Outcome: c.Resolution + " (" + c.Status + ")", Rule: rule, Preferred: c.Preferred == x.ID, At: c.CreatedAt})
}
if x.Status == volition.WantActive {
active = append(active, row)
} else {
closed = append(closed, row)
}
}
sort.SliceStable(active, func(i, j int) bool { return active[i].Eff > active[j].Eff })
goalTitles := map[ids.ID]string{}
if gs, err := u.deps.Goals.List(ctx, m.ID, nil, 300); err == nil {
for _, g := range gs {
goalTitles[g.ID] = g.Title
}
}
u.render(w, r, "wants", m.DisplayName+" · Wants", map[string]any{"Mind": m, "Active": active, "Closed": closed, "Goals": goalTitles, "HalfLife": volition.WantHalfLife.String(), "Threshold": volition.WantThreshold})
}
func (u *UI) intentionsPage(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
all, _ := u.deps.Volition.Intentions(r.Context(), nil, m.ID, nil, 500)
groups := map[string][]volition.Intention{}
for _, it := range all {
groups[string(it.Status)] = append(groups[string(it.Status)], it)
}
order := []string{"active", "blocked", "suspended", "proposed", "fulfilled", "abandoned", "superseded"}
notFormed, _ := u.deps.Volition.Decisions(r.Context(), m.ID, volition.DecisionFilter{Kind: "non_formation", Limit: 30})
u.render(w, r, "intentions", m.DisplayName+" · Intentions", map[string]any{"Mind": m, "Groups": groups, "Order": order, "NotFormed": notFormed})
}
func (u *UI) intentionRedirect(w http.ResponseWriter, r *http.Request) {
id, err := ids.Parse(r.PathValue("id"))
if err != nil {
u.errorPage(w, r, http.StatusNotFound, "No such intention.")
return
}
it, err := u.deps.Volition.GetIntentionAny(r.Context(), id)
if err != nil {
u.errorPage(w, r, http.StatusNotFound, "No such intention.")
return
}
m, err := u.deps.Minds.Get(r.Context(), it.MindID)
if err != nil {
u.errorPage(w, r, http.StatusNotFound, "No such mind.")
return
}
http.Redirect(w, r, "/minds/"+m.Slug+"/intentions/"+string(id), http.StatusSeeOther)
}
// dagLevel is one column of a plan's dependency graph.
type dagLevel struct {
Level int
Steps []planning.Step
}
func planLevels(p planning.Plan) []dagLevel {
level := map[string]int{}
byKey := map[string]planning.Step{}
for _, s := range p.Steps {
byKey[s.Key] = s
}
var depth func(k string, seen map[string]bool) int
depth = func(k string, seen map[string]bool) int {
if l, ok := level[k]; ok {
return l
}
if seen[k] {
return 0
}
seen[k] = true
l := 0
for _, d := range byKey[k].DependsOn {
if x := depth(d, seen) + 1; x > l {
l = x
}
}
level[k] = l
return l
}
maxL := 0
for _, s := range p.Steps {
if l := depth(s.Key, map[string]bool{}); l > maxL {
maxL = l
}
}
out := make([]dagLevel, maxL+1)
for i := range out {
out[i].Level = i
}
for _, s := range p.Steps {
out[level[s.Key]].Steps = append(out[level[s.Key]].Steps, s)
}
return out
}
func (u *UI) intentionPage(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
id, err := ids.Parse(r.PathValue("id"))
if err != nil {
u.errorPage(w, r, http.StatusNotFound, "No such intention.")
return
}
d, err := u.studyF().IntentionDetail(r, m.ID, id)
if err != nil {
u.errorPage(w, r, http.StatusNotFound, "No such intention.")
return
}
formation, _ := json.MarshalIndent(d.Intention.Formation, "", " ")
var levels [][]dagLevel
for _, p := range d.Plans {
levels = append(levels, planLevels(p))
}
timeline, _ := u.studyF().AgencyTimeline(r, m.ID, 200, id)
u.render(w, r, "intention", m.DisplayName+" · Intention", map[string]any{"Mind": m, "D": d, "Formation": string(formation), "Levels": levels, "Timeline": timeline})
}
func (u *UI) intentionAbandon(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
id, _ := ids.Parse(r.PathValue("id"))
r.ParseForm() //nolint:errcheck
to := "/minds/" + m.Slug + "/intentions/" + string(id)
if _, err := u.studyF().AbandonIntention(r, m.ID, id, r.PostFormValue("reason")); err != nil {
u.flash(w, r, "error", err.Error(), to)
return
}
u.flash(w, r, "ok", "Intention abandoned; its plan was cancelled and open requests withdrawn.", to)
}
func (u *UI) commitmentsPage(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
all, _ := u.deps.Volition.Commitments(r.Context(), nil, m.ID, nil, 500)
var active, closed []volition.Commitment
for _, c := range all {
if c.Status == "active" {
active = append(active, c)
} else {
closed = append(closed, c)
}
}
conflicts := volition.CommitmentConflicts(active, time.Now())
u.render(w, r, "commitments", m.DisplayName+" · Commitments", map[string]any{"Mind": m, "Active": active, "Closed": closed, "Conflicts": conflicts,
"Kinds": []string{"operator_instruction", "promise", "responsibility", "value_rule", "relationship", "prior_decision", "policy", "scheduled"}})
}
func (u *UI) commitmentCreate(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
r.ParseForm() //nolint:errcheck
to := "/minds/" + m.Slug + "/commitments"
split := func(k string) []string {
var out []string
for _, x := range strings.Split(r.PostFormValue(k), ",") {
if x = strings.TrimSpace(x); x != "" {
out = append(out, x)
}
}
return out
}
n := volition.NewCommitment{MindID: m.ID, Description: r.PostFormValue("description"), Kind: volition.CommitmentKind(r.PostFormValue("kind")), Polarity: r.PostFormValue("polarity"), Unless: r.PostFormValue("unless"),
Beneficiary: r.PostFormValue("beneficiary"), Matcher: volition.Matcher{Capabilities: split("capabilities"), Targets: split("targets"), Access: r.PostFormValue("access"), MinRisk: r.PostFormValue("min_risk")}}
n.Strength, _ = strconv.ParseFloat(r.PostFormValue("strength"), 64)
n.Priority, _ = strconv.ParseFloat(r.PostFormValue("priority"), 64)
if v := r.PostFormValue("due_at"); v != "" {
if t, err := time.ParseInLocation("2006-01-02T15:04", v, time.Local); err == nil {
n.DueAt = &t
}
}
if _, err := u.studyF().CreateCommitment(r, n); err != nil {
u.flash(w, r, "error", err.Error(), to)
return
}
u.flash(w, r, "ok", "Commitment recorded. It shapes what the mind intends; it never grants authority.", to)
}
func (u *UI) commitmentClose(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
id, _ := ids.Parse(r.PathValue("id"))
r.ParseForm() //nolint:errcheck
to := "/minds/" + m.Slug + "/commitments"
if _, err := u.studyF().CloseCommitment(r, m.ID, id, r.PostFormValue("status"), r.PostFormValue("reason")); err != nil {
u.flash(w, r, "error", err.Error(), to)
return
}
u.flash(w, r, "ok", "Commitment closed.", to)
}
func (u *UI) plansPage(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
list, _ := u.deps.Plans.List(r.Context(), m.ID, nil, 200)
u.render(w, r, "plans", m.DisplayName+" · Plans", map[string]any{"Mind": m, "Plans": list})
}
func (u *UI) planPage(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
id, err := ids.Parse(r.PathValue("id"))
if err != nil {
u.errorPage(w, r, http.StatusNotFound, "No such plan.")
return
}
p, err := u.deps.Plans.Get(r.Context(), nil, id)
if err != nil || p.MindID != m.ID {
u.errorPage(w, r, http.StatusNotFound, "No such plan.")
return
}
it, _ := u.deps.Volition.GetIntention(r.Context(), nil, m.ID, p.IntentionID)
revisions, _ := u.deps.Plans.ForIntention(r.Context(), nil, p.IntentionID)
validation, _ := json.MarshalIndent(p.Validation, "", " ")
u.render(w, r, "plan", m.DisplayName+" · Plan", map[string]any{"Mind": m, "Plan": p, "Intention": it, "Levels": planLevels(p), "Revisions": revisions, "Validation": string(validation)})
}
func (u *UI) planCancel(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
id, _ := ids.Parse(r.PathValue("id"))
to := "/minds/" + m.Slug + "/plans/" + string(id)
p, err := u.deps.Plans.Get(r.Context(), nil, id)
if err != nil || p.MindID != m.ID {
u.flash(w, r, "error", "No such plan.", to)
return
}
r.ParseForm() //nolint:errcheck
if err := u.deps.Executor.Cancel(r.Context(), id, "cancelled by operator "+username(r)+": "+r.PostFormValue("reason")); err != nil {
u.flash(w, r, "error", err.Error(), to)
return
}
u.deps.Audit.Log(r.Context(), "plan.cancel", "plan", id.String(), map[string]any{"mind": m.ID})
u.flash(w, r, "ok", "Plan cancelled.", to)
}
func (u *UI) planStepRetry(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
id, _ := ids.Parse(r.PathValue("id"))
to := "/minds/" + m.Slug + "/plans/" + string(id)
p, err := u.deps.Plans.Get(r.Context(), nil, id)
if err != nil || p.MindID != m.ID {
u.flash(w, r, "error", "No such plan.", to)
return
}
if err := u.studyF().RetryStep(r, p, r.PathValue("step")); err != nil {
u.flash(w, r, "error", err.Error(), to)
return
}
u.flash(w, r, "ok", "Step released; the executor will pick it up.", to)
}
// stageOf names the agency stage an event belongs to.
func stageOf(t events.Type) string {
switch t {
case events.WantCreated, events.WantSuppressed, events.WantConflictDetected:
return "wanted"
case events.IntentionFormed:
return "intended"
case events.CommitmentCreated, events.CommitmentDue, events.CommitmentConflict, events.CommitmentFulfilled, events.CommitmentBroken, events.CommitmentRevoked:
return "committed"
case events.PlanCreated, events.PlanReplanned:
return "planned"
case events.ToolRequested:
return "requested"
case events.ToolAuthorised, events.ToolDenied:
return "approved"
case events.PlanStepStarted, events.PlanStepCompleted, events.PlanStepFailed, events.ToolExecuted, events.ToolFailed:
return "acted"
case events.PredictionCreated, events.PredictionResolved, events.PredictionError:
return "observed"
}
return "reconsidered"
}
type timelineRow struct {
Event events.Event
Stage string
Why string
}
func (u *UI) agencyPage(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
corr, _ := ids.Parse(r.URL.Query().Get("intention"))
evs, _ := u.studyF().AgencyTimeline(r, m.ID, 300, corr)
var rows []timelineRow
for _, ev := range evs {
row := timelineRow{Event: ev, Stage: stageOf(ev.Type)}
for _, k := range []string{"execution_id", "approval_id", "request_id", "intention_id"} {
if v, _ := ev.Metadata[k].(string); v != "" {
row.Why = "/minds/" + m.Slug + "/why?kind=event&id=" + string(ev.ID)
break
}
}
rows = append(rows, row)
}
u.render(w, r, "agency", m.DisplayName+" · Agency", map[string]any{"Mind": m, "Rows": rows, "Intention": corr})
}
func (u *UI) whyPage(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
id, err := ids.Parse(r.URL.Query().Get("id"))
if err != nil {
u.errorPage(w, r, http.StatusBadRequest, "Choose an action, approval, request, plan step, intention or event to explain.")
return
}
kind := r.URL.Query().Get("kind")
if kind == "" {
kind = "event"
}
x, err := u.deps.Agency.Explain(r.Context(), m.ID, agency.Ref{Kind: kind, ID: id})
if err != nil {
u.errorPage(w, r, http.StatusNotFound, err.Error())
return
}
var attribution *agency.Attribution
if kind == "event" {
if a, err := u.deps.Agency.Attribute(r.Context(), m.ID, id); err == nil {
attribution = &a
}
}
u.render(w, r, "why", m.DisplayName+" · Why did this happen?", map[string]any{"Mind": m, "X": x, "Attribution": attribution})
}
func (u *UI) researchPage(w http.ResponseWriter, r *http.Request) {
m, ok := u.mind(w, r)
if !ok {
return
}
ctx := r.Context()
since := time.Now().Add(-30 * 24 * time.Hour)
metrics, _ := u.deps.Research.Agency(ctx, m.ID, since)
calibration, _ := u.deps.Research.MetacognitiveCalibration(ctx, m.ID, time.Now().Add(-90*24*time.Hour))
introspection, _ := u.deps.Research.List(ctx, m.ID, "introspection_accuracy", since, 50)
claims, _ := u.deps.Research.List(ctx, m.ID, "action_claim_accuracy", since, 50)
labels := map[string]int{}
for _, x := range introspection {
labels[x.Label]++
}
// Self-model accuracy now.
var selfAcc *float64
var selfErr []string
if sm, err := u.deps.Minds.GetSelfModel(ctx, m.ID); err == nil && len(sm.Capabilities) > 0 {
if inv, err := u.deps.Capabilities.Inventory(ctx, m.ID); err == nil {
eff, stated := map[string]string{}, map[string]string{}
for _, i := range inv {
eff[i.Capability.Name] = string(i.Effect)
if i.Capability.Locked {
eff[i.Capability.Name] = "locked"
}
}
for _, c := range sm.Capabilities {
stated[c.Name] = c.Policy
}
a, wrong := research.SelfModelAccuracy(stated, eff)
selfAcc, selfErr = &a, wrong
}
}
// Attribution of recent outcomes (resolved expectations).
attr := map[string]int{}
if outs, err := u.deps.Events.Query(ctx, eventsFilterTypes(m.ID, events.PredictionResolved, events.PredictionError)); err == nil {
for i, ev := range outs {
if i >= 50 {
break
}
if a, err := u.deps.Agency.Attribute(ctx, m.ID, ev.ID); err == nil {
attr[a.Class]++
}
}
}
metrics.Attribution = attr
var exps []experiments.Experiment
if u.deps.Experiments != nil {
exps, _ = u.deps.Experiments.List(ctx, 200)
}
var related []experimentRow
for _, e := range exps {
sn, err := u.deps.Snapshots.Get(ctx, e.SnapshotID)
if err != nil || sn.MindID != m.ID {
continue
}
related = append(related, experimentRowOf(e))
}
bySection := map[string][]research.Measure{}
var sections []string
for _, x := range research.Measures {
if _, ok := bySection[x.Section]; !ok {
sections = append(sections, x.Section)
}
bySection[x.Section] = append(bySection[x.Section], x)
}
u.render(w, r, "research", m.DisplayName+" · Research", map[string]any{"Mind": m, "Metrics": metrics, "Calibration": calibration, "Introspection": introspection, "IntrospectionLabels": labels, "Claims": claims,
"SelfAccuracy": selfAcc, "SelfErrors": selfErr, "Experiments": related, "Sections": sections, "Measures": bySection, "Disclaimer": research.Disclaimer})
}
type experimentRow struct {
E experiments.Experiment
Protocol string
Repro string
Variants int
First int
Share float64
}
func experimentRowOf(e experiments.Experiment) experimentRow {
res := experiments.ResultsOf(e)
row := experimentRow{E: e, Protocol: "custom", Repro: res.Reproducibility.Status, Variants: len(res.Variants), First: -1}
if e.Definition.Protocol != nil && e.Definition.Protocol.Kind != "" {
row.Protocol = e.Definition.Protocol.Kind
}
if res.Comparison != nil {
row.First, row.Share = res.Comparison.FirstDivergentStimulus, res.Comparison.ArchitectureShare
}
return row
}
func (u *UI) researchIndex(w http.ResponseWriter, r *http.Request) {
var exps []experiments.Experiment
if u.deps.Experiments != nil {
exps, _ = u.deps.Experiments.List(r.Context(), 200)
}
byProtocol := map[string][]experimentRow{}
for _, e := range exps {
row := experimentRowOf(e)
byProtocol[row.Protocol] = append(byProtocol[row.Protocol], row)
}
list, _ := u.deps.Minds.List(r.Context(), false)
bySection := map[string][]research.Measure{}
var sections []string
for _, x := range research.Measures {
if _, ok := bySection[x.Section]; !ok {
sections = append(sections, x.Section)
}
bySection[x.Section] = append(bySection[x.Section], x)
}
u.render(w, r, "research_index", "Cognitive research", map[string]any{"ByProtocol": byProtocol, "Protocols": []string{"global_workspace", "ablation", "self_model", "counterfactual", "model_comparison", "conflict", "custom"},
"Minds": list, "Sections": sections, "Measures": bySection, "Disclaimer": research.Disclaimer})
}
func eventsFilterTypes(mind ids.ID, types ...events.Type) evstore.Filter {
return evstore.Filter{MindID: mind, Types: types, Limit: 100}
}