l-ai-agent-kaya/internal/profile/profile.go
alex 3a2a0c1c34
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Enable kaya-board tools on Tasks mention replies.
Match Telegram chat tool surface (journal, tasks, steam/discord/node) so diary requests hit write_journal instead of a board-only comment.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 16:45:03 +03:00

110 lines
3.4 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("Tools match Telegram chat (journal, tasks, steam/faceit, discord, node) — use them when needed. ")
b.WriteString("When asked to journal / diary a merged change: call write_journal (persona Postgres diary), then a short board reply noting what shipped and any PR URL. ")
b.WriteString("Do not invent task state that is not in the provided context or tool results. ")
b.WriteString("Output only the comment body to post — no preamble or quotes.\n")
return b.String()
}