8.6 KiB
Cómo crear un nuevo agente
Guía para LLMs que asisten en la creación de agentes en este proyecto.
Flujo completo automatizado
# 1. Scaffold — crea config.yaml (E2EE habilitado), agent.go, prompts/, data/
./dev-scripts/new-agent.sh <agent-id> "Display Name"
# 2. Registrar en Matrix — genera y guarda en .env:
# MATRIX_TOKEN_<NORM>, MATRIX_PASSWORD_<NORM>, PICKLE_KEY_<NORM>
./dev-scripts/register.sh <agent-id> "Display Name"
# 3. Verificar E2EE — genera cross-signing keys, firma el device,
# guarda SSSS_RECOVERY_KEY_<NORM> en .env
./dev-scripts/verify.sh <agent-id>
# 4. Arrancar — ya verificado desde el primer arranque
./dev-scripts/start.sh <agent-id>
Los scripts automatizan todo. Solo intervenir manualmente si un paso falla.
Convención de nombres de env vars — REGLA CRÍTICA
Normalización: agent ID → mayúsculas, guiones → underscores. Sin eliminar sufijos.
Función canónica en dev-scripts/_common.sh:
normalize_id() { echo "$1" | tr '[:lower:]-' '[:upper:]_'; }
| Agent ID | Sufijo normalizado | Env vars |
|---|---|---|
assistant-bot |
ASSISTANT_BOT |
MATRIX_TOKEN_ASSISTANT_BOT, MATRIX_PASSWORD_ASSISTANT_BOT, PICKLE_KEY_ASSISTANT_BOT, SSSS_RECOVERY_KEY_ASSISTANT_BOT |
asistente-2 |
ASISTENTE_2 |
MATRIX_TOKEN_ASISTENTE_2, MATRIX_PASSWORD_ASISTENTE_2, PICKLE_KEY_ASISTENTE_2, SSSS_RECOVERY_KEY_ASISTENTE_2 |
monitor-bot |
MONITOR_BOT |
MATRIX_TOKEN_MONITOR_BOT, ... |
NUNCA usar sed 's/_BOT$//' ni transformaciones que eliminen partes del ID.
Estructura requerida
Cada agente vive en agents/<agent-id>/ con esta estructura:
agents/<agent-id>/
├── agent.go # Package propio, exporta Rules() []decision.Rule
├── config.yaml # Configuración completa (ver schema en internal/config/schema.go)
├── data/ # Runtime data (crypto, logs) — en .gitignore
│ └── crypto/ # Crypto store E2EE — NUNCA compartir entre agentes
└── prompts/
└── system.md # System prompt del LLM
Archivos a crear
1. agents/<agent-id>/agent.go — Reglas puras
package <agentpkg>
import "github.com/enmanuel/agents/pkg/decision"
func Rules() []decision.Rule {
return []decision.Rule{
// Regla help explícita
{
Name: "help",
Match: decision.MatchCommand("help"),
Actions: []decision.Action{{
Kind: decision.ActionKindReply,
Reply: &decision.ReplyAction{Content: "Descripción de capacidades del bot."},
}},
},
// Catch-all → LLM
{
Name: "llm-all",
Match: func(ctx decision.MessageContext) bool {
return ctx.IsDirectMsg || ctx.IsMention
},
Actions: []decision.Action{{
Kind: decision.ActionKindLLM,
LLM: &decision.LLMAction{},
}},
},
}
}
Reglas del archivo de reglas:
- PURO: sin imports de I/O, sin side effects, solo
pkg/decision - El package name debe ser Go-valid (sin guiones):
agents/mi-bot/→package mibot - Las reglas se evalúan en orden — poner las específicas antes del catch-all
- El catch-all debe cubrir
ctx.IsDirectMsg || ctx.IsMentioncomo mínimo ActionKindReplypara respuestas estáticas,ActionKindLLMpara respuestas dinámicas
2. agents/<agent-id>/config.yaml — Configuración
new-agent.sh genera esto automáticamente. Campos que hay que personalizar:
agent:
id: <agent-id> # DEBE coincidir con el directorio y rulesRegistry
name: "Display Name"
description: "Qué hace este agente"
llm:
primary:
provider: openai # o anthropic
model: gpt-4o # o claude-sonnet-4-20250514
api_key_env: OPENAI_API_KEY # o ANTHROPIC_API_KEY
tool_use:
enabled: true/false # true si el agente usa herramientas
matrix:
user_id: "@<agent-id>:matrix-af2f3d.organic-machine.com"
access_token_env: MATRIX_TOKEN_<NORM>
device_id: "" # se resuelve automáticamente via whoami
Sección E2EE en config.yaml (generada por new-agent.sh)
encryption:
enabled: true # SIEMPRE true para agentes nuevos
store_path: "./agents/<agent-id>/data/crypto/" # SIEMPRE por agente, nunca compartida
pickle_key_env: PICKLE_KEY_<NORM> # generada por register.sh
trust_mode: tofu
recovery_key_env: SSSS_RECOVERY_KEY_<NORM> # generada por verify.sh
3. agents/<agent-id>/prompts/system.md — System prompt
Debe incluir:
- Identidad del bot (quién es, qué hace)
- Capacidades y limitaciones
- Herramientas disponibles (si
tool_use.enabled: true) - Estilo de respuesta (idioma, tono, formato)
- Instrucciones de uso de herramientas (cuándo y cómo usarlas)
Archivos a modificar
4. cmd/launcher/main.go — Registro en el launcher
new-agent.sh hace esto automáticamente. Dos cambios:
Import:
<agentpkg>agent "github.com/enmanuel/agents/agents/<agent-id>"
rulesRegistry:
var rulesRegistry = map[string]func() []decision.Rule{
// ... agentes existentes ...
"<agent-id>": <agentpkg>agent.Rules, // ← nuevo
}
El ID en rulesRegistry DEBE coincidir exactamente con agent.id del config.yaml.
5. agents/runtime.go — Registro de herramientas (solo si hay tools nuevas)
Si el agente necesita una herramienta nueva (no existente), ver la policy create_tool.md.
Las herramientas "siempre disponibles" (current_time, matrix_send) ya están registradas para todos los agentes.
E2EE — Cómo funciona la verificación
Flujo al arrancar (agents/runtime.go)
InitCrypto → crea/carga el device y Olm account del crypto store
FetchCrossSigningKeys → obtiene private keys de SSSS usando recovery key
SignOwnDevice → fetch device keys del servidor + firma con self-signing key
Qué hace cada script
| Script | Qué genera | Dónde se guarda |
|---|---|---|
register.sh |
Token, password, pickle key (32 bytes hex) | .env |
verify.sh |
Cross-signing keys (master, self-signing, user-signing) + recovery key | Server (keys) + .env (recovery key) |
Por qué cada credential importa
| Credential | Para qué | Si falta |
|---|---|---|
MATRIX_TOKEN_* |
Autenticación del bot con el homeserver | Bot no arranca |
MATRIX_PASSWORD_* |
UIA al subir cross-signing keys (verify.sh) | verify.sh intenta dummy auth (MSC3967) |
PICKLE_KEY_* |
Cifrar el crypto store en disco | Usa sha256(token) como fallback — inseguro |
SSSS_RECOVERY_KEY_* |
Recuperar private keys de cross-signing al arrancar | Device no se firma → "not verified by its owner" |
Problemas comunes y soluciones
"Encrypted by a device not verified by its owner"
→ Ejecutar ./dev-scripts/verify.sh <agent-id> y reiniciar
"self-signing private key not in cache"
→ La recovery key en .env no corresponde a las cross-signing keys actuales. Re-ejecutar verify.sh.
"received update for device with different signing key"
→ Bug resuelto: SignOwnDevice ahora hace FetchKeys antes de firmar. Si reaparece, recompilar el launcher: go build -tags goolm -o bin/launcher ./cmd/launcher
"data is not encrypted for given key ID"
→ Las cross-signing keys fueron regeneradas pero la recovery key en .env es la vieja. Re-ejecutar verify.sh (actualiza .env automáticamente).
Recovery key sin comillas en .env
→ Causan command not found al hacer source .env. Las recovery keys tienen espacios y DEBEN ir entre comillas: SSSS_RECOVERY_KEY_*="EsXX YYYY ZZZZ ..."
Después de crear los archivos
Verificar compilación:
go build -tags goolm ./...
Luego seguir con registro, verificación y arranque usando los dev-scripts.
Reglas generales
- Nunca poner side effects en
agent.go— es código puro - Siempre verificar que
agent.idcoincide entre config.yaml, rulesRegistry y el directorio - Siempre compilar con
-tags goolmpara soporte E2EE - Siempre usar
normalize_id()de_common.shpara nombres de env vars - Idioma: español en configs, prompts y descripciones de dominio; inglés en código Go
- No crear archivos
data/— se generan automáticamente al arrancar - No commitear tokens ni passwords — solo van en
.env - No compartir crypto stores entre agentes — cada uno tiene su
store_path - Si el agente usa tool_use, asegurarse de que
llm.tool_use.enabled: trueen el config - Usar
agents/asistente2/como referencia completa de un agente con tools habilitadas