// Package loader loads persona definitions from YAML files using gopkg.in/yaml.v3. // A persona is the self-awareness/identity bound to a character and, per // DevDocs/design/01, it also carries the emotion state-machine parameters, the // affection curve, and a per-persona memory namespace (memory isolation). package loader import ( "git.yeij.top/AskaEth/Cyrene_v2/internal/persona/emotion" ) // Default affection bounds and step, applied when a persona defines no // affection curve. const ( defaultAffectionMin = 0 defaultAffectionMax = 100 defaultAffectionInit = 50 defaultAffectionDelta = 5 ) // defaultMemoryNS is the namespace prefix used when a persona omits a concrete // memory namespace; the full namespace becomes prefix + "." + persona id. const defaultMemoryNS = "persona" // Identity is the persona's self-awareness: the 人设 (who she is, e.g. a // character role) plus the system prompt that carries that identity at runtime. type Identity struct { // Role is the 人设 — the characterization the persona assumes. Role string `yaml:"role"` // SystemPrompt is the identity block injected as the system prompt. SystemPrompt string `yaml:"systemPrompt"` } // EmotionParams configures the persona's emotion state machine and its per- // persona memory isolation namespace. Transitions may be empty, in which case // the emotion package installs its default transition table. type EmotionParams struct { InitialState emotion.EmotionState `yaml:"initialState"` DecayRate float64 `yaml:"decayRate"` Transitions map[emotion.EmotionState][]emotion.EmotionState `yaml:"transitions"` // MemoryNamespace is the memory isolation namespace for this persona. MemoryNamespace string `yaml:"memoryNamespace"` } // AffectionParams defines this persona's affection curve bounds and the default // step applied to an Adjust call that carries no delta. type AffectionParams struct { Initial int `yaml:"initial"` Min int `yaml:"min"` Max int `yaml:"max"` Delta int `yaml:"delta"` } // Persona is a loaded persona definition. It describes who a character is (the // identity, personality, speech style, catchphrase, background, values) and // never restricts what that character can do. type Persona struct { ID string `yaml:"id"` Name string `yaml:"name"` CharacterRef string `yaml:"characterRef"` Identity Identity `yaml:"identity"` Personality string `yaml:"personality"` SpeechStyle string `yaml:"speechStyle"` Catchphrase string `yaml:"catchphrase"` Background string `yaml:"background"` Values []string `yaml:"values"` Emotion EmotionParams `yaml:"emotion"` Affection AffectionParams `yaml:"affection"` } // EmotionConfig converts this persona's own fields into the params consumed by // the emotion machine constructor. func (p *Persona) EmotionConfig() emotion.Params { return emotion.Params{ InitialState: p.Emotion.InitialState, DecayRate: p.Emotion.DecayRate, Transitions: p.Emotion.Transitions, } } // MemoryNamespace returns the effective memory isolation namespace for this // persona — the configured one, or a derived default of "persona." when none // is set. The derived default guarantees per-persona isolation. func (p *Persona) MemoryNamespace() string { if p.Emotion.MemoryNamespace != "" { return p.Emotion.MemoryNamespace } return defaultMemoryNS + "." + p.ID } // NewMachine builds the persona's emotion state machine from its config. func (p *Persona) NewMachine() (*emotion.Machine, error) { return emotion.NewMachine(p.EmotionConfig()) } // NewAffection builds the persona's affection tracker from its curve config, // applying default bounds when no curve was defined. func (p *Persona) NewAffection() (*emotion.Affection, error) { params := p.Affection if params.Min == 0 && params.Max == 0 && params.Initial == 0 && params.Delta == 0 { params = AffectionParams{ Initial: defaultAffectionInit, Min: defaultAffectionMin, Max: defaultAffectionMax, Delta: defaultAffectionDelta, } } return emotion.NewAffection(params.Initial, params.Min, params.Max) }