l-ai-agent-kaya/internal/profile/profile.go
alex 8c88d4ca8a
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Prompt Kaya to journal merged delivery-pipeline tasks.
Board mention replies treat Mira journal asks as short diary notes with PR context.
2026-09-22 11:20:16 +03:00

109 lines
3.2 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("When asked to journal / diary a merged change: write a short factual note of what shipped and any PR URL from the thread. ")
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()
}