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:
@@ -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}
|
||||
}
|
||||
Reference in New Issue
Block a user