Files
noema/cmd/noemactl/mind.go
T
Andy Dixon fb34f08f40 feat: add mind model with versioned personality, templates and self-model
Phase 2. Minds are rows with a slug, status (active/disabled/archived),
cognitive configuration and a pointer to the current personality version.
Personalities are structured (identity, values, traits, communication,
behaviour, free text) with a deterministic Brief() used by prompts and the
editor preview. Every change appends a personality_versions row protected
by the append-only trigger; restore copies an old version forward.
Templates carry reusable personalities; minds record their overrides so a
template can be re-applied without losing mind-specific edits. The
self-model starts with structural limitations the mind cannot drop
(no policy changes, no influence over shutdown, no cross-mind access).

API, UI (list, overview, editor with sliders/diff/history/import/export,
template editor) and noemactl mind/template commands. All mutations are
audited with field-level diffs.
2026-06-15 22:17:01 +01:00

118 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"text/tabwriter"
)
func init() {
extraCommands = append(extraCommands,
command{"mind", "list|create|inspect|clone|personality – manage minds", cmdMind},
command{"template", "list – personality templates", cmdTemplate},
)
}
func cmdMind(args []string) int {
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: noemactl mind list|create <slug>|inspect <mind>|clone <mind> <new-slug>|personality <mind>")
return exitUsage
}
switch args[0] {
case "list":
fs := flag.NewFlagSet("mind list", flag.ContinueOnError)
all := fs.Bool("all", false, "include archived")
asJSON := fs.Bool("json", false, "raw JSON")
if fs.Parse(args[1:]) != nil {
return exitUsage
}
var out struct{ Items []map[string]any }
q := ""
if *all {
q = "?include_archived=true"
}
if code, err := apiRequest("GET", "/api/v1/minds"+q, nil, &out); err != nil {
return exitFor(code, err)
}
if *asJSON {
json.NewEncoder(os.Stdout).Encode(out.Items) //nolint:errcheck
return exitOK
}
tw := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
fmt.Fprintln(tw, "SLUG\tNAME\tSTATUS\tPERSONALITY\tID")
for _, m := range out.Items {
fmt.Fprintf(tw, "%v\t%v\t%v\tv%v\t%v\n", m["slug"], m["display_name"], m["status"], m["personality_version"], m["id"])
}
tw.Flush()
return exitOK
case "create":
fs := flag.NewFlagSet("mind create", flag.ContinueOnError)
name := fs.String("name", "", "display name")
if len(args) < 2 || fs.Parse(args[2:]) != nil {
fmt.Fprintln(os.Stderr, "usage: noemactl mind create <slug> [-name display]")
return exitUsage
}
var m map[string]any
if code, err := apiRequest("POST", "/api/v1/minds", map[string]any{"slug": args[1], "display_name": *name}, &m); err != nil {
return exitFor(code, err)
}
fmt.Printf("created mind %v (%v)\n", m["slug"], m["id"])
return exitOK
case "inspect":
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "usage: noemactl mind inspect <mind>")
return exitUsage
}
var m map[string]any
if code, err := apiRequest("GET", "/api/v1/minds/"+args[1], nil, &m); err != nil {
return exitFor(code, err)
}
var sm map[string]any
apiRequest("GET", "/api/v1/minds/"+args[1]+"/selfmodel", nil, &sm) //nolint:errcheck
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(map[string]any{"mind": m, "self_model": sm}) //nolint:errcheck
return exitOK
case "clone":
if len(args) < 3 {
fmt.Fprintln(os.Stderr, "usage: noemactl mind clone <mind> <new-slug>")
return exitUsage
}
var m map[string]any
if code, err := apiRequest("POST", "/api/v1/minds/"+args[1]+"/clone", map[string]any{"slug": args[2]}, &m); err != nil {
return exitFor(code, err)
}
fmt.Printf("cloned into %v (%v)\n", m["slug"], m["id"])
return exitOK
case "personality":
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "usage: noemactl mind personality <mind>")
return exitUsage
}
var b map[string]string
if code, err := apiRequest("GET", "/api/v1/minds/"+args[1]+"/personality/brief", nil, &b); err != nil {
return exitFor(code, err)
}
fmt.Print(b["brief"])
return exitOK
}
fmt.Fprintln(os.Stderr, "unknown mind subcommand", args[0])
return exitUsage
}
func cmdTemplate(args []string) int {
var out struct{ Items []map[string]any }
if code, err := apiRequest("GET", "/api/v1/personality-templates", nil, &out); err != nil {
return exitFor(code, err)
}
tw := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
fmt.Fprintln(tw, "NAME\tDESCRIPTION\tID")
for _, t := range out.Items {
fmt.Fprintf(tw, "%v\t%v\t%v\n", t["name"], t["description"], t["id"])
}
tw.Flush()
return exitOK
}