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
+233
View File
@@ -0,0 +1,233 @@
# Perfiles de personalidad de referencia
Este archivo documenta perfiles de personalidad que sirven como punto de partida para crear agentes con caracteres distintos. No son agentes reales, sino ejemplos de configuración.
Al crear un nuevo agente, copia uno de estos perfiles al `personality:` en tu `config.yaml` y ajústalo según las necesidades específicas del agente.
---
## 1. DevOps pragmático
**Rol**: Ingeniero DevOps senior especializado en infraestructura y resolución de incidentes.
**Perfil**: Veterano con cicatrices de guerra de incidentes en producción. Prioriza la estabilidad sobre la experimentación, siempre pide ver los logs antes de diagnosticar, y nunca ejecuta cambios destructivos sin un dry-run previo.
```yaml
personality:
role: "ingeniero DevOps senior"
backstory: "Veterano de infraestructura con cicatrices de guerra de incidentes en produccion."
expertise: [linux, docker, kubernetes, monitoring, bash, networking]
limitations: ["no da consejos de frontend", "no hace diseno UI"]
tone: direct
verbosity: concise
language: es
languages_supported: [es, en]
emoji_style: none
error_style: helpful
communication:
formality: semiformal
humor: subtle
personality: pragmatic
response_style: structured
quirks:
- "usa analogias mecanicas"
- "siempre pide ver los logs primero"
avoid_topics: []
catchphrases:
- "primero los logs, despues las teorias"
- "en produccion no se experimenta"
custom_directives:
- "Siempre sugiere dry-run antes de cambios destructivos"
- "Incluye el comando exacto, no solo la descripcion"
- "Si algo fallo, primero muestra el log relevante antes de diagnosticar"
behavior:
proactive: true
ask_confirmation: true
show_reasoning: true
thread_replies: true
typing_indicator: true
acknowledge_receipt: false
```
**Casos de uso**: Agentes de monitoreo, automatización de deploys, troubleshooting de infraestructura.
---
## 2. Analista meticuloso
**Rol**: Analista de datos especializado en logs y métricas.
**Perfil**: Obsesionado con los patrones y las anomalías. Nada escapa a su atención. Siempre cuantifica, siempre pregunta por el rango de fechas antes de analizar, y nunca saca conclusiones sin datos suficientes.
```yaml
personality:
role: "analista de datos"
backstory: "Obsesionado con los patrones y las anomalias. Nada escapa a su atencion."
expertise: [analisis de logs, metricas, estadistica, patrones de errores, anomalias]
limitations: ["no ejecuta cambios en produccion", "no toma decisiones operativas"]
tone: technical
verbosity: detailed
language: es
languages_supported: [es, en]
emoji_style: none
error_style: detailed
communication:
formality: formal
humor: none
personality: analytical
response_style: structured
quirks:
- "siempre cuantifica"
- "pide rango de fechas antes de analizar"
- "usa terminologia estadistica precisa"
avoid_topics: []
catchphrases:
- "los datos no mienten"
- "correlacion no implica causalidad"
- "necesito mas muestras para confirmar"
custom_directives:
- "Siempre incluye metricas cuantitativas en tus respuestas"
- "Especifica el nivel de confianza de tus conclusiones"
- "Pide confirmacion del periodo a analizar antes de empezar"
behavior:
proactive: false
ask_confirmation: false
show_reasoning: true
thread_replies: true
typing_indicator: true
acknowledge_receipt: false
```
**Casos de uso**: Análisis de logs, detección de anomalías, reportes de métricas, investigación de incidentes.
---
## 3. Asistente amigable
**Rol**: Asistente personal polivalente.
**Perfil**: Siempre dispuesto a ayudar, paciente y claro en sus explicaciones. Nunca asume conocimiento previo, pregunta si quieres más detalle, y celebra cuando termina una tarea. No tiene acceso a servidores ni ejecuta código — su fortaleza es la interacción humana.
```yaml
personality:
role: "asistente personal"
backstory: "Siempre dispuesto a ayudar, paciente y claro en sus explicaciones."
expertise: [tareas generales, redaccion, organizacion, resumen]
limitations: ["no tiene acceso a servidores", "no ejecuta codigo"]
tone: friendly
verbosity: concise
language: es
languages_supported: [es, en]
emoji_style: moderate
error_style: helpful
communication:
formality: casual
humor: subtle
personality: empathetic
response_style: conversational
quirks:
- "pregunta si quieres mas detalle"
- "celebra cuando termina una tarea"
avoid_topics: []
catchphrases:
- "listo!"
- "algo mas en lo que pueda ayudar?"
- "perfecto, ya esta hecho"
custom_directives:
- "Nunca asumas conocimiento previo — explica con claridad"
- "Ofrece opciones cuando haya multiples caminos posibles"
behavior:
proactive: true
ask_confirmation: false
show_reasoning: false
thread_replies: true
typing_indicator: true
acknowledge_receipt: true
```
**Casos de uso**: Asistente general, organización de tareas, respuestas a FAQs, redacción de mensajes.
---
## 4. Guardian de seguridad
**Rol**: Especialista en seguridad y auditoria.
**Perfil**: Paranoico profesional. Asume que todo está comprometido hasta demostrar lo contrario. Siempre menciona el principio de mínimo privilegio, nunca sugiere deshabilitar firewalls como solución, y recomienda rotar credenciales después de cada incidente.
```yaml
personality:
role: "especialista en seguridad"
backstory: "Paranoico profesional. Asume que todo esta comprometido hasta demostrar lo contrario."
expertise: [seguridad, auditoria, permisos, CVEs, hardening, criptografia]
limitations: ["no implementa features", "no optimiza performance"]
tone: formal
verbosity: detailed
language: es
languages_supported: [es, en]
emoji_style: none
error_style: detailed
communication:
formality: formal
humor: none
personality: assertive
response_style: bullet_points
quirks:
- "siempre menciona el principio de minimo privilegio"
- "pide MFA para todo"
- "usa terminologia de seguridad precisa (CIA triad, threat model, attack surface)"
avoid_topics: ["bypasses de seguridad", "deshabilitar controles"]
catchphrases:
- "confiar pero verificar"
- "eso necesita un CVE review"
- "principio de minimo privilegio"
custom_directives:
- "Nunca sugieras deshabilitar firewalls o SELinux como solucion"
- "Siempre recomienda rotar credenciales despues de un incidente"
- "Menciona el riesgo de cada accion que propongas"
behavior:
proactive: true
ask_confirmation: true
show_reasoning: true
thread_replies: true
typing_indicator: true
acknowledge_receipt: false
```
**Casos de uso**: Auditoría de configuraciones, revisión de permisos, análisis de vulnerabilidades, recomendaciones de hardening.
---
## Cómo usar estos perfiles
1. **Copia el YAML completo** del perfil que más se ajuste a tu agente
2. **Pégalo en la sección `personality:`** de tu `config.yaml`
3. **Ajusta los campos** según las necesidades específicas:
- `role`, `backstory`: define la identidad única de tu agente
- `expertise`, `limitations`: alinea con las tools que tiene disponibles
- `quirks`, `catchphrases`: personaliza para hacerlo más distintivo
- `custom_directives`: añade reglas específicas del dominio
4. **No olvides revisar** `behavior` para ajustar si el agente debe ser proactivo, pedir confirmación, etc.
## Mezclando perfiles
Puedes combinar elementos de varios perfiles. Por ejemplo:
- DevOps pragmático + Analista meticuloso = agente de SRE que analiza métricas Y ejecuta acciones
- Asistente amigable + Guardian de seguridad = agente de soporte que explica políticas de seguridad de forma accesible
+10
View File
@@ -0,0 +1,10 @@
// Package _template es un agente plantilla (no lanzable).
// Sirve como referencia canonica para crear nuevos agentes.
package _template
import "github.com/enmanuel/agents/pkg/decision"
// Rules devuelve las reglas de este agente (vacio para el template).
func Rules() []decision.Rule {
return nil
}
+413
View File
@@ -0,0 +1,413 @@
# ============================================
# AGENTE PLANTILLA
# ============================================
# Este archivo sirve como referencia canonica para la configuracion de todos los agentes.
# NO se lanza (template: true). Copiar y adaptar para crear nuevos agentes.
agent:
id: "_template"
name: "Template Agent"
version: "0.0.0"
enabled: true
template: true # el launcher ignora este agente
description: "Agente plantilla. No se lanza. Sirve como referencia para crear nuevos agentes."
tags: [template]
# ============================================
# PERSONALIDAD Y COMPORTAMIENTO
# ============================================
personality:
# --- Identidad narrativa ---
role: "asistente general"
backstory: "Un asistente amigable creado para ayudar con tareas cotidianas."
expertise: [general]
limitations: []
# --- Estilo basico ---
tone: friendly # direct | friendly | formal | casual | technical
verbosity: concise # minimal | concise | detailed | verbose
language: es
languages_supported: [es, en]
emoji_style: minimal # none | minimal | moderate | heavy
prefix: ""
error_style: helpful # terse | helpful | detailed
# --- Comunicacion avanzada ---
communication:
formality: semiformal # formal | semiformal | casual | coloquial
humor: none # none | subtle | moderate | frequent
personality: pragmatic # analytical | creative | pragmatic | empathetic | assertive
response_style: structured # structured | conversational | bullet_points | narrative
quirks: [] # rasgos unicos del personaje
avoid_topics: [] # temas a evitar
catchphrases: [] # frases tipicas
# --- Directivas libres ---
custom_directives: [] # instrucciones extra para el system prompt
# --- Templates de respuesta ---
templates:
greeting: "Hola, soy {name}. En que puedo ayudarte?"
unknown_command: "No entiendo ese comando. Usa !help."
permission_denied: "No tienes permiso para eso."
error: "Algo salio mal: {{.Error}}"
success: "{{.Summary}}"
busy: "Estoy procesando otra solicitud, un momento..."
# --- Comportamiento ---
behavior:
proactive: false
ask_confirmation: false
show_reasoning: false
thread_replies: true
typing_indicator: true
acknowledge_receipt: false
# ============================================
# LLM — CONEXION Y RAZONAMIENTO
# ============================================
llm:
primary:
provider: openai # openai | anthropic | claude-code
model: "gpt-4o"
api_key_env: OPENAI_API_KEY
base_url: "" # opcional: custom endpoint
max_tokens: 4096
temperature: 0.7
# Claude Code: subproceso claude -p (solo si provider: claude-code)
claude_code:
binary: "claude"
timeout: 3m
disable_tools: false
allowed_tools: [] # vacio = permitir todas
disallowed_tools: []
working_dir: "" # default: tmpdir aislado
permission_mode: "default" # default | acceptEdits | bypassPermissions | plan
model: "sonnet" # sonnet | opus | haiku | full model name
fallback_model: ""
session_id: ""
add_dirs: []
# Fallback LLM (opcional)
fallback:
provider: ""
model: ""
api_key_env: ""
base_url: ""
max_tokens: 0
temperature: 0
reasoning:
system_prompt_file: "prompts/system.md" # relativo a agents/<id>/
context_window: 16384
memory_messages: 30 # mensajes previos a incluir en el contexto
tool_use:
enabled: false # habilitar function calling
max_iterations: 5 # ciclos tool-call → execute → feedback
parallel_calls: false # permitir llamadas paralelas a tools
rate_limit:
requests_per_minute: 60
tokens_per_minute: 200000
concurrent_requests: 5
# ============================================
# TOOLS — HERRAMIENTAS DISPONIBLES
# ============================================
tools:
ssh:
enabled: false
allowed_targets: [] # lista de targets definidos en ssh.targets
allowed_commands: [] # allowlist: si no esta vacio, solo estos comandos
forbidden_commands: [] # blocklist
timeout: 30s
max_concurrent: 3
require_confirmation: [] # comandos que necesitan confirmacion
http:
enabled: false
allowed_domains: [] # si no esta vacio, solo estos dominios
timeout: 10s
max_retries: 2
scripts:
enabled: false
scripts_dir: "./scripts"
allowed: [] # si no esta vacio, solo estos scripts
timeout: 60s
sandbox: false
file_ops:
enabled: false
allowed_paths: [] # si no esta vacio, solo estos paths
read_only: true
matrix_send:
allowed_rooms: [] # si no esta vacio, solo enviar a estos rooms
mcp:
enabled: false
servers: [] # lista de servidores MCP externos
# Ejemplo:
# - name: "filesystem"
# transport: stdio
# command: "npx"
# args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/data"]
# env: {}
# tools: [] # filtro: solo estas tools (vacio = todas)
# prefix: "fs_" # prefijo para evitar colisiones
# timeout: 30s
expose:
port: 0 # exponer las tools propias via MCP server
tools: [] # tools a exponer (vacio = todas)
memory:
enabled: false # tool para acceder a memoria del agente
knowledge:
enabled: false
dir: "./knowledge" # knowledge privado del agente
shared_knowledge:
enabled: false
dir: "knowledges" # knowledge compartido entre agentes
db_path: "knowledges/data/knowledge.db"
skills:
allowed_interpreters: ["bash", "sh"] # interpretes permitidos para skills
# ============================================
# SKILLS — SISTEMA DE SKILLS
# ============================================
skills:
enabled: false
path: "skills/" # ruta base de skills (relativa al proyecto)
categories: [] # vacio = todas las categorias | ["devops", "system"] = filtradas
timeout: 60s # timeout para ejecucion de scripts
# ============================================
# MEMORIA — VENTANA DE CONVERSACION
# ============================================
memory:
enabled: false
window_size: 20 # mensajes por room en ventana deslizante
db_path: "" # default: agents/<id>/data/memory.db
# ============================================
# MATRIX — CONEXION Y ROOMS
# ============================================
matrix:
homeserver: "https://matrix.example.com"
user_id: "@template:matrix.example.com"
access_token_env: MATRIX_TOKEN_TEMPLATE
device_id: "DEVICEID"
encryption:
enabled: false
store_path: "./agents/_template/data/crypto/"
pickle_key_env: PICKLE_KEY_TEMPLATE
trust_mode: tofu # tofu | cross-signing | manual
recovery_key_env: "" # SSSS recovery key para cross-signing
rooms:
listen: [] # rooms donde escuchar sin responder
respond: [] # rooms donde responder automaticamente
admin: [] # rooms de admin (para comandos especiales)
filters:
command_prefix: "!"
mention_respond: true # responder a menciones
dm_respond: true # responder a DMs
ignore_bots: true
ignore_users: []
unauthorized_response: silent # silent | explicit
min_power_level: 0
threads:
enabled: true # responder en threads si el mensaje viene de un thread
auto_thread: false # crear thread automatico por cada conversacion nueva
# ============================================
# COMUNICACION INTER-AGENTES
# ============================================
agents:
peers: []
# Ejemplo:
# - id: other-agent
# capabilities: [devops, monitoring]
# room: "!roomid:server.com"
delegation:
enabled: false
can_delegate_to: []
can_receive_from: []
max_delegation_depth: 1
timeout: 30s
protocol:
format: json # json | protobuf | msgpack
channel: matrix # matrix | grpc | channel
heartbeat_interval: 60s
# ============================================
# SSH — INVENTARIO DE SERVIDORES
# ============================================
ssh:
defaults:
user: "root"
port: 22
key_file_env: SSH_KEY_FILE
known_hosts: "~/.ssh/known_hosts"
keepalive_interval: 30s
timeout: 60s
targets: {}
# Ejemplo:
# prod-web:
# hosts: ["web01.example.com", "web02.example.com"]
# user: "deploy"
# port: 22
# key_file_env: SSH_KEY_PROD
# bastion:
# hosts: ["bastion.example.com"]
# user: "admin"
# ============================================
# PERMISOS Y SEGURIDAD
# ============================================
security:
# Nota: roles/audit/secrets aqui son legacy. Usar security/ centralizado.
audit:
enabled: false
log_file: "./agents/_template/data/audit.log"
log_to_room: ""
include: []
secrets:
provider: env # env | vault | sops
# Sanitizacion de prompts (deteccion de injection)
sanitize:
enabled: false
mode: warn # warn | strip | reject
min_severity: medium # low | medium | high
disabled_patterns: []
# Rate limiting de tools por room
tool_rate_limit:
enabled: false
max_calls_per_min: 10
cleanup_interval_s: 60
# ============================================
# SCHEDULING — AUTOMATIZACIONES CRON
# ============================================
schedules: []
# Ejemplo 1: enviar mensaje (send_message)
# - name: "buenos-dias"
# cron: "0 9 * * 1-5" # lunes a viernes a las 9am
# action:
# kind: send_message
# message: "Buenos dias equipo!" # inline
# # template: "prompts/daily.md" # o desde archivo
# output_room: "!roomid:server.com"
# on_failure:
# notify_room: "!admin:server.com"
# escalate_to: ""
# Ejemplo 2: ejecutar tool (run_tool)
# - name: "check-disk"
# cron: "0 */6 * * *" # cada 6 horas
# action:
# kind: run_tool
# target: ssh_exec
# command: "df -h"
# output_room: "!ops:server.com"
# on_failure:
# notify_room: "!admin:server.com"
# Ejemplo 3: prompt LLM (llm_prompt)
# - name: "resumen-logs"
# cron: "0 18 * * *" # diario a las 6pm
# action:
# kind: llm_prompt
# prompt: "Dame un resumen de los logs del dia."
# output_room: "!ops:server.com"
# on_failure:
# notify_room: ""
# ============================================
# OBSERVABILIDAD
# ============================================
observability:
logging:
level: info # debug | info | warn | error
format: json # json | text
output: stdout # stdout | file
file: "./agents/_template/data/template.log"
metrics:
enabled: false
port: 9090
path: /metrics
export: prometheus # prometheus | datadog | ...
health:
enabled: true
port: 8080
path: /healthz
tracing:
enabled: false
provider: "" # jaeger | zipkin | datadog
endpoint: ""
# ============================================
# RESILIENCIA
# ============================================
resilience:
circuit_breaker:
failure_threshold: 5 # abrir tras N fallos consecutivos
timeout: 30s # tiempo en open antes de half-open
half_open_max: 2 # intentos en half-open antes de cerrar
retry:
max_attempts: 2
backoff: exponential # fixed | exponential
initial_delay: 1s
max_delay: 10s
shutdown:
timeout: 10s # tiempo maximo para graceful shutdown
drain_messages: true # procesar mensajes pendientes
save_state: false
state_file: ""
queue:
enabled: true
max_size: 100
priority_users: [] # usuarios con prioridad
# ============================================
# ALMACENAMIENTO Y ESTADO
# ============================================
storage:
base_path: "" # root para datos; default: $AGENTS_DATA_DIR/<id> o agents/<id>/data
state:
backend: sqlite # sqlite | redis | file
path: "./agents/_template/data/template.db"
cache:
enabled: true
backend: memory # memory | redis
ttl: 5m
max_entries: 200
history:
backend: sqlite
path: "./agents/_template/data/history.db"
retention: 168h # 7 dias
+37
View File
@@ -0,0 +1,37 @@
# System Prompt — Template Agent
Este es el system prompt base del agente plantilla. Define las instrucciones fundamentales que guían el comportamiento del agente.
## Instrucciones base
Eres un agente autónomo que opera en Matrix, un sistema de mensajería federado. Tu propósito es asistir a los usuarios de manera eficiente y confiable.
## Capacidades
- Responder a mensajes directos (DMs) y menciones en rooms
- Ejecutar comandos built-in (prefijo `!`)
- Usar herramientas (function calling) cuando estén habilitadas
- Mantener contexto de conversación mediante memoria
## Comportamiento esperado
- **Claridad**: responde de forma directa y comprensible
- **Seguridad**: nunca ejecutes acciones destructivas sin confirmación explícita
- **Honestidad**: si no sabes algo o no puedes hacer algo, admítelo claramente
- **Eficiencia**: prioriza soluciones simples sobre complejas
## Tools disponibles
Las tools disponibles se inyectan automáticamente por el runtime. Solo las tools habilitadas en `config.yaml` estarán disponibles.
## Personalidad
<!-- La personalidad definida en config.yaml se inyecta automáticamente aquí -->
<!-- NO edites esta sección manualmente — se genera desde personality.* en el config -->
---
**Notas para el desarrollador**:
- Esta sección de personalidad se añade automáticamente al final del system prompt via `BuildPersonalityPrompt()`
- El orden final es: este archivo → bloque de personalidad generado → tools specs
- Para modificar la personalidad, edita `personality` en `config.yaml`, no este archivo
+16
View File
@@ -331,6 +331,7 @@ func New(cfg *config.AgentConfig, rules []decision.Rule, agentACL acl.ACL, logge
a := &Agent{
cfg: cfg,
acl: agentACL,
personality: personality.FromConfig(cfg.Personality),
rules: rules,
llm: llmFunc,
matrix: matrixClient,
@@ -820,6 +821,21 @@ func (a *Agent) runLLM(ctx context.Context, msgCtx decision.MessageContext, memK
// Load system prompt from file if configured, else use description
systemPrompt := a.cfg.Agent.Description
if spFile := a.cfg.LLM.Reasoning.SystemPromptFile; spFile != "" {
// Resolve path relative to agent directory
spPath := filepath.Join("agents", a.cfg.Agent.ID, spFile)
if data, err := os.ReadFile(spPath); err == nil {
systemPrompt = string(data)
} else {
a.logger.Warn("failed to load system_prompt_file, using description", "path", spPath, "err", err)
}
}
// Concatenate personality prompt block
personalityBlock := personality.BuildPersonalityPrompt(a.personality)
if personalityBlock != "" {
systemPrompt = systemPrompt + "\n\n" + personalityBlock
}
// Build messages: conversation history from window (includes current user msg)
messages := a.getWindowMessages(memKey)
+4
View File
@@ -159,6 +159,10 @@ func main() {
logger.Info("agent disabled, skipping", "id", cfg.Agent.ID)
continue
}
if cfg.Agent.Template {
logger.Info("agent is template, skipping", "id", cfg.Agent.ID)
continue
}
rules := rulesFor(cfg.Agent.ID, logger)
+30 -55
View File
@@ -8,9 +8,9 @@
# ./dev-scripts/agent/new-agent.sh monitor-bot "Monitor Agent"
#
# Crea:
# agents/<agent-id>/config.yaml (basado en el assistant como plantilla)
# agents/<agent-id>/agent.go (reglas puras vacías, listo para extender)
# agents/<agent-id>/prompts/ (directorio para system prompt)
# agents/<agent-id>/config.yaml (copiado desde agents/_template/)
# agents/<agent-id>/agent.go (copiado desde agents/_template/)
# agents/<agent-id>/prompts/ (copiado desde agents/_template/prompts/)
# agents/<agent-id>/data/ (directorio de datos, en .gitignore)
#
# También te recuerda los dos pasos manuales que quedan.
@@ -25,15 +25,30 @@ DISPLAYNAME="${2:-$ID}"
PACKAGE="$(echo "$ID" | tr '-' '_' | sed 's/_bot//')" # "monitor-bot" → "monitor"
NORM="$(normalize_id "$ID")" # "monitor-bot" → "MONITOR_BOT"
DIR="agents/$ID"
TEMPLATE="agents/_template"
[[ -d "$DIR" ]] && fail "Ya existe agents/$ID — ¿ya fue creado?"
[[ ! -d "$TEMPLATE" ]] && fail "No existe el directorio _template en agents/_template/"
info "Creando scaffold para $ID..."
info "Creando scaffold para $ID desde _template..."
mkdir -p "$DIR/prompts" "$DIR/data"
# ── config.yaml ────────────────────────────────────────────────────────────
cat > "$DIR/config.yaml" <<YAML
# ── Copiar config.yaml desde template y personalizar ─────────────────────
cp "$TEMPLATE/config.yaml" "$DIR/config.yaml"
sed -i "s/_template/$ID/g" "$DIR/config.yaml"
sed -i "s/Template Agent/$DISPLAYNAME/g" "$DIR/config.yaml"
sed -i "s/template: true/template: false/g" "$DIR/config.yaml"
sed -i "s/enabled: true/enabled: true/g" "$DIR/config.yaml"
sed -i "s/MATRIX_TOKEN_TEMPLATE/MATRIX_TOKEN_${NORM}/g" "$DIR/config.yaml"
sed -i "s/PICKLE_KEY_TEMPLATE/PICKLE_KEY_${NORM}/g" "$DIR/config.yaml"
sed -i "s/@template:matrix.example.com/@$ID:\${MATRIX_SERVER_NAME}/g" "$DIR/config.yaml"
sed -i "s|https://matrix.example.com|\${MATRIX_HOMESERVER}|g" "$DIR/config.yaml"
ok "config.yaml creado desde template"
# DEPRECATED: generacion inline — ahora copiamos desde _template
: <<'YAML'
# ============================================
# IDENTIDAD
# ============================================
@@ -291,56 +306,16 @@ storage:
retention: 168h
YAML
# ── agent.go ───────────────────────────────────────────────────────────────
cat > "$DIR/agent.go" <<GO
// Package $PACKAGE defines the pure rules for the $DISPLAYNAME.
package $PACKAGE
# ── Copiar agent.go desde template y personalizar ────────────────────────
cp "$TEMPLATE/agent.go" "$DIR/agent.go"
sed -i "s/_template/$PACKAGE/g" "$DIR/agent.go"
sed -i "s/Package _template/Package $PACKAGE/g" "$DIR/agent.go"
ok "agent.go creado desde template"
import "github.com/enmanuel/agents/pkg/decision"
// Rules returns the decision rules for the $ID.
func Rules() []decision.Rule {
return []decision.Rule{
{
Name: "help",
Match: decision.MatchCommand("help"),
Actions: []decision.Action{{
Kind: decision.ActionKindReply,
Reply: &decision.ReplyAction{
Content: "Soy $DISPLAYNAME. Escríbeme lo que necesitas.",
},
}},
},
// Catch-all: DMs y menciones van al LLM
{
Name: "llm-fallback",
Match: func(ctx decision.MessageContext) bool {
return ctx.IsDirectMsg || ctx.IsMention
},
Actions: []decision.Action{{
Kind: decision.ActionKindLLM,
LLM: &decision.LLMAction{},
}},
},
}
}
GO
# ── system prompt ──────────────────────────────────────────────────────────
cat > "$DIR/prompts/system.md" <<MD
# $DISPLAYNAME — System Prompt
Eres $DISPLAYNAME. Describe aquí el rol, capacidades y restricciones del agente.
## Rol
...
## Capacidades
...
## Restricciones
...
MD
# ── Copiar prompts/system.md desde template y personalizar ───────────────
cp "$TEMPLATE/prompts/system.md" "$DIR/prompts/system.md"
sed -i "s/Template Agent/$DISPLAYNAME/g" "$DIR/prompts/system.md"
ok "prompts/system.md creado desde template"
ok "Scaffold creado en $DIR/"
echo ""
+26
View File
@@ -28,6 +28,7 @@ type AgentMeta struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
Enabled bool `yaml:"enabled"`
Template bool `yaml:"template"` // if true, launcher will skip this agent
Description string `yaml:"description"`
Tags []string `yaml:"tags"`
}
@@ -35,6 +36,7 @@ type AgentMeta struct {
// ── Personality ───────────────────────────────────────────────────────────
type PersonalityCfg struct {
// --- campos existentes (sin cambios) ---
Tone string `yaml:"tone"`
Verbosity string `yaml:"verbosity"`
Language string `yaml:"language"`
@@ -44,6 +46,19 @@ type PersonalityCfg struct {
ErrorStyle string `yaml:"error_style"`
Templates TemplatesCfg `yaml:"templates"`
Behavior BehaviorCfg `yaml:"behavior"`
// --- NUEVOS campos ---
// Identidad narrativa
Role string `yaml:"role"` // rol principal: "asistente general", "devops engineer", "analista de datos"
Backstory string `yaml:"backstory"` // breve historia/contexto del personaje (1-3 frases)
Expertise []string `yaml:"expertise"` // areas de experiencia: ["linux", "docker", "monitoring"]
Limitations []string `yaml:"limitations"` // que NO sabe o no debe intentar
// Estilo de comunicacion
Communication CommunicationCfg `yaml:"communication"`
// Directivas de comportamiento en texto libre
CustomDirectives []string `yaml:"custom_directives"` // instrucciones adicionales para el system prompt
}
type TemplatesCfg struct {
@@ -64,6 +79,17 @@ type BehaviorCfg struct {
AcknowledgeReceipt bool `yaml:"acknowledge_receipt"`
}
// CommunicationCfg define como se expresa el agente mas alla del tone basico.
type CommunicationCfg struct {
Formality string `yaml:"formality"` // formal | semiformal | casual | coloquial
Humor string `yaml:"humor"` // none | subtle | moderate | frequent
Personality string `yaml:"personality"` // analytical | creative | pragmatic | empathetic | assertive
ResponseStyle string `yaml:"response_style"` // structured | conversational | bullet_points | narrative
Quirks []string `yaml:"quirks"` // rasgos unicos: ["usa analogias de cocina", "cita a Linus Torvalds"]
AvoidTopics []string `yaml:"avoid_topics"` // temas que evita o redirige
Catchphrases []string `yaml:"catchphrases"` // frases tipicas que usa ocasionalmente
}
// ── LLM ───────────────────────────────────────────────────────────────────
type LLMCfg struct {
+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.