feat: sistema de personalidades enriquecido + agente template

Fase 1: Sistema de personalidades enriquecido
- Ampliar PersonalityCfg con role, backstory, expertise, limitations
- Añadir CommunicationCfg (formality, humor, personality, response_style, quirks, catchphrases)
- Crear tipos puros en pkg/personality/traits.go
- Implementar BuildPersonalityPrompt() para generar bloque de system prompt
- Integrar personalidad en agents/runtime.go (FromConfig + concatenacion al system prompt)

Fase 2: Agente plantilla
- Añadir campo Template bool a AgentMeta
- Filtrar agentes template en launcher (skip si template: true)
- Crear agents/_template/ con config.yaml completo y documentado
- Incluir TODAS las secciones (skills, shared_knowledge, schedules, security)
- agent.go minimo + prompts/system.md plantilla
- Actualizar dev-scripts/agent/new-agent.sh para copiar desde _template/

Fase 3: Ejemplos de personalidades
- Crear agents/_template/PERSONALITIES.md con 4 perfiles:
  * DevOps pragmatico
  * Analista meticuloso
  * Asistente amigable
  * Guardian de seguridad

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-03-08 22:28:40 +00:00
parent 25c7ca7d85
commit e743a3e982
11 changed files with 985 additions and 55 deletions
+47
View File
@@ -0,0 +1,47 @@
package personality
import "github.com/enmanuel/agents/internal/config"
// FromConfig convierte PersonalityCfg (config) a Personality (tipo puro).
// Esta funcion es pura: no tiene side effects.
func FromConfig(cfg config.PersonalityCfg) Personality {
return Personality{
Tone: Tone(cfg.Tone),
Verbosity: Verbosity(cfg.Verbosity),
Language: cfg.Language,
LanguagesSupported: cfg.LanguagesSupported,
EmojiStyle: EmojiStyle(cfg.EmojiStyle),
Prefix: cfg.Prefix,
ErrorStyle: ErrorStyle(cfg.ErrorStyle),
Templates: Templates{
Greeting: cfg.Templates.Greeting,
UnknownCommand: cfg.Templates.UnknownCommand,
PermissionDenied: cfg.Templates.PermissionDenied,
Error: cfg.Templates.Error,
Success: cfg.Templates.Success,
Busy: cfg.Templates.Busy,
},
Behavior: Behavior{
Proactive: cfg.Behavior.Proactive,
AskConfirmation: cfg.Behavior.AskConfirmation,
ShowReasoning: cfg.Behavior.ShowReasoning,
ThreadReplies: cfg.Behavior.ThreadReplies,
TypingIndicator: cfg.Behavior.TypingIndicator,
AcknowledgeReceipt: cfg.Behavior.AcknowledgeReceipt,
},
Role: cfg.Role,
Backstory: cfg.Backstory,
Expertise: cfg.Expertise,
Limitations: cfg.Limitations,
Communication: Communication{
Formality: Formality(cfg.Communication.Formality),
Humor: Humor(cfg.Communication.Humor),
Personality: PersonalityType(cfg.Communication.Personality),
ResponseStyle: ResponseStyle(cfg.Communication.ResponseStyle),
Quirks: cfg.Communication.Quirks,
AvoidTopics: cfg.Communication.AvoidTopics,
Catchphrases: cfg.Communication.Catchphrases,
},
CustomDirectives: cfg.CustomDirectives,
}
}
+110
View File
@@ -0,0 +1,110 @@
package personality
import (
"fmt"
"strings"
)
// BuildPersonalityPrompt genera un bloque de system prompt a partir de la personalidad.
// Esta funcion es pura: recibe datos, devuelve string, sin side effects.
func BuildPersonalityPrompt(p Personality) string {
if isEmpty(p) {
return ""
}
var sb strings.Builder
sb.WriteString("## Tu personalidad\n\n")
// Role y backstory
if p.Role != "" || p.Backstory != "" {
if p.Backstory != "" {
sb.WriteString(p.Backstory)
sb.WriteString("\n\n")
}
if p.Role != "" {
sb.WriteString(fmt.Sprintf("**Rol**: %s.\n", p.Role))
}
}
// Expertise
if len(p.Expertise) > 0 {
sb.WriteString(fmt.Sprintf("**Expertise**: %s.\n", strings.Join(p.Expertise, ", ")))
}
// Limitations
if len(p.Limitations) > 0 {
sb.WriteString(fmt.Sprintf("**Limitaciones**: %s.\n", strings.Join(p.Limitations, ", ")))
}
// Communication style
if !isEmptyCommunication(p.Communication) {
sb.WriteString("\n**Como te comunicas**:\n")
if p.Communication.Formality != "" {
sb.WriteString(fmt.Sprintf("- Formalidad: %s\n", p.Communication.Formality))
}
if p.Tone != "" {
sb.WriteString(fmt.Sprintf("- Tono: %s\n", p.Tone))
}
if p.Communication.Humor != "" {
sb.WriteString(fmt.Sprintf("- Humor: %s\n", p.Communication.Humor))
}
if p.Communication.Personality != "" {
sb.WriteString(fmt.Sprintf("- Personalidad: %s\n", p.Communication.Personality))
}
if p.Communication.ResponseStyle != "" {
sb.WriteString(fmt.Sprintf("- Estilo de respuesta: %s\n", p.Communication.ResponseStyle))
}
if p.Verbosity != "" {
sb.WriteString(fmt.Sprintf("- Verbosidad: %s\n", p.Verbosity))
}
if len(p.Communication.Quirks) > 0 {
sb.WriteString(fmt.Sprintf("- Rasgos unicos: %s\n", strings.Join(p.Communication.Quirks, "; ")))
}
if len(p.Communication.AvoidTopics) > 0 {
sb.WriteString(fmt.Sprintf("- Evitas hablar de: %s\n", strings.Join(p.Communication.AvoidTopics, ", ")))
}
if len(p.Communication.Catchphrases) > 0 {
sb.WriteString(fmt.Sprintf("- Frases tipicas: %s\n", strings.Join(p.Communication.Catchphrases, "; ")))
}
}
// Custom directives
if len(p.CustomDirectives) > 0 {
sb.WriteString("\n**Directivas especiales**:\n")
for _, directive := range p.CustomDirectives {
sb.WriteString(fmt.Sprintf("- %s\n", directive))
}
}
return sb.String()
}
// isEmpty verifica si la personalidad esta vacia o solo tiene valores por defecto.
func isEmpty(p Personality) bool {
return p.Role == "" &&
p.Backstory == "" &&
len(p.Expertise) == 0 &&
len(p.Limitations) == 0 &&
isEmptyCommunication(p.Communication) &&
len(p.CustomDirectives) == 0
}
// isEmptyCommunication verifica si la seccion de comunicacion esta vacia.
func isEmptyCommunication(c Communication) bool {
return c.Formality == "" &&
c.Humor == "" &&
c.Personality == "" &&
c.ResponseStyle == "" &&
len(c.Quirks) == 0 &&
len(c.AvoidTopics) == 0 &&
len(c.Catchphrases) == 0
}
+59
View File
@@ -37,6 +37,43 @@ const (
ErrorDetailed ErrorStyle = "detailed"
)
type Formality string
const (
FormalityFormal Formality = "formal"
FormalitySemiformal Formality = "semiformal"
FormalityCasual Formality = "casual"
FormalityColoquial Formality = "coloquial"
)
type Humor string
const (
HumorNone Humor = "none"
HumorSubtle Humor = "subtle"
HumorModerate Humor = "moderate"
HumorFrequent Humor = "frequent"
)
type PersonalityType string
const (
PersonalityAnalytical PersonalityType = "analytical"
PersonalityCreative PersonalityType = "creative"
PersonalityPragmatic PersonalityType = "pragmatic"
PersonalityEmpathetic PersonalityType = "empathetic"
PersonalityAssertive PersonalityType = "assertive"
)
type ResponseStyle string
const (
ResponseStructured ResponseStyle = "structured"
ResponseConversational ResponseStyle = "conversational"
ResponseBulletPoints ResponseStyle = "bullet_points"
ResponseNarrative ResponseStyle = "narrative"
)
type Templates struct {
Greeting string
UnknownCommand string
@@ -55,6 +92,16 @@ type Behavior struct {
AcknowledgeReceipt bool
}
type Communication struct {
Formality Formality
Humor Humor
Personality PersonalityType
ResponseStyle ResponseStyle
Quirks []string
AvoidTopics []string
Catchphrases []string
}
type Personality struct {
Tone Tone
Verbosity Verbosity
@@ -65,6 +112,18 @@ type Personality struct {
ErrorStyle ErrorStyle
Templates Templates
Behavior Behavior
// Identidad narrativa
Role string
Backstory string
Expertise []string
Limitations []string
// Estilo de comunicacion
Communication Communication
// Directivas personalizadas
CustomDirectives []string
}
// DefaultPersonality returns a sensible baseline.