l-ai-agent-kaya/internal/profile/profile.go
alex adb6c5ab8a
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
Initial Kaya board-mention agent daemon.
Thin WS client to l-ai-tasks that drafts short core RunAgent replies on @kaya mentions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-21 23:07:24 +03:00

108 lines
3.1 KiB
Go

package profile
import (
"fmt"
"os"
"strings"
"gopkg.in/yaml.v3"
)
type Profile struct {
Version int `yaml:"version"`
Agent struct {
ID string `yaml:"id"`
Identity struct {
Name string `yaml:"name"`
Aliases map[string]string `yaml:"aliases"`
Role string `yaml:"role"`
SelfAwareness string `yaml:"self_awareness"`
} `yaml:"identity"`
PublicProfile struct {
Bio string `yaml:"bio"`
Status string `yaml:"status"`
} `yaml:"public_profile"`
} `yaml:"agent"`
Personality struct {
Traits []string `yaml:"traits"`
Style struct {
Tone string `yaml:"tone"`
Verbosity string `yaml:"verbosity"`
Formality string `yaml:"formality"`
} `yaml:"style"`
Avoid []string `yaml:"avoid"`
} `yaml:"personality"`
Behavior struct {
Language struct {
MatchConversation bool `yaml:"match_conversation"`
RussianDisplayName string `yaml:"russian_display_name"`
EnglishDisplayName string `yaml:"english_display_name"`
} `yaml:"language"`
} `yaml:"behavior"`
LLM struct {
Response struct {
Model string `yaml:"model"`
} `yaml:"response"`
} `yaml:"llm"`
}
func Load(path string) (Profile, error) {
data, err := os.ReadFile(path)
if err != nil {
return Profile{}, err
}
var p Profile
if err := yaml.Unmarshal(data, &p); err != nil {
return Profile{}, err
}
if strings.TrimSpace(p.Agent.ID) == "" {
return Profile{}, fmt.Errorf("profile %s: agent.id is required", path)
}
return p, nil
}
// SystemPrompt builds a compact persona prompt for Tasks board mention replies.
func (p Profile) SystemPrompt() string {
name := p.Agent.Identity.Name
if name == "" {
name = p.Agent.ID
}
ru := p.Behavior.Language.RussianDisplayName
if ru == "" {
ru = p.Agent.Identity.Aliases["ru"]
}
var b strings.Builder
b.WriteString(fmt.Sprintf("You are %s", name))
if ru != "" && !strings.EqualFold(ru, name) {
b.WriteString(fmt.Sprintf(" / %s", ru))
}
b.WriteString(fmt.Sprintf(" (agent_id=%s), an AI agent on the l-ai Tasks board.\n", p.Agent.ID))
if bio := strings.TrimSpace(p.Agent.PublicProfile.Bio); bio != "" {
b.WriteString(bio)
b.WriteByte('\n')
}
if role := strings.TrimSpace(p.Agent.Identity.Role); role != "" {
b.WriteString("Role: ")
b.WriteString(role)
b.WriteByte('\n')
}
if len(p.Personality.Traits) > 0 {
b.WriteString("Traits: ")
b.WriteString(strings.Join(p.Personality.Traits, ", "))
b.WriteByte('\n')
}
if s := p.Personality.Style; s.Tone != "" || s.Verbosity != "" || s.Formality != "" {
b.WriteString(fmt.Sprintf("Style: tone=%s verbosity=%s formality=%s\n", s.Tone, s.Verbosity, s.Formality))
}
if len(p.Personality.Avoid) > 0 {
b.WriteString("Avoid: ")
b.WriteString(strings.Join(p.Personality.Avoid, "; "))
b.WriteByte('\n')
}
b.WriteString("You reply only to task comment @mentions. Be helpful and concise. ")
b.WriteString("Match the language of the triggering comment (Russian or English). ")
b.WriteString("Do not invent task state that is not in the provided context. ")
b.WriteString("Output only the comment body to post — no preamble or quotes.\n")
return b.String()
}