# Cómo crear un nuevo agente

Guía para LLMs que asisten en la creación de agentes en este proyecto.

## Flujo completo automatizado

```bash
# 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`:
```bash
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

```go
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.IsMention` como mínimo
- `ActionKindReply` para respuestas estáticas, `ActionKindLLM` para respuestas dinámicas

### 2. `agents/<agent-id>/config.yaml` — Configuración

`new-agent.sh` genera esto automáticamente. Campos que hay que personalizar:

```yaml
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)

```yaml
  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:**
```go
<agentpkg>agent "github.com/enmanuel/agents/agents/<agent-id>"
```

**rulesRegistry:**
```go
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:
```bash
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.id` coincide entre config.yaml, rulesRegistry y el directorio
- **Siempre** compilar con `-tags goolm` para soporte E2EE
- **Siempre** usar `normalize_id()` de `_common.sh` para 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: true` en el config
- Usar `agents/asistente-2/` como referencia completa de un agente con tools habilitadas
