Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -2,6 +2,43 @@
|
||||
|
||||
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:
|
||||
@@ -10,6 +47,8 @@ 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
|
||||
```
|
||||
@@ -58,9 +97,7 @@ func Rules() []decision.Rule {
|
||||
|
||||
### 2. `agents/<agent-id>/config.yaml` — Configuración
|
||||
|
||||
Usar como plantilla `agents/assistant/config.yaml` o `agents/asistente2/config.yaml`.
|
||||
|
||||
**Campos que SIEMPRE hay que personalizar:**
|
||||
`new-agent.sh` genera esto automáticamente. Campos que hay que personalizar:
|
||||
|
||||
```yaml
|
||||
agent:
|
||||
@@ -78,49 +115,21 @@ llm:
|
||||
|
||||
matrix:
|
||||
user_id: "@<agent-id>:matrix-af2f3d.organic-machine.com"
|
||||
access_token_env: MATRIX_TOKEN_<AGENT_UPPER>
|
||||
device_id: "<se actualiza después del registro>"
|
||||
access_token_env: MATRIX_TOKEN_<NORM>
|
||||
device_id: "" # se resuelve automáticamente via whoami
|
||||
```
|
||||
|
||||
**Convención de nombres de env vars:**
|
||||
- Token: `MATRIX_TOKEN_<ID_UPPER>` donde ID se convierte a mayúsculas y guiones a underscores
|
||||
- Ejemplo: `asistente-2` → `MATRIX_TOKEN_ASISTENTE2`
|
||||
- Password: `MATRIX_PASSWORD_<ID_UPPER>` con la misma convención
|
||||
- Pickle key E2EE: `PICKLE_KEY_<ID_UPPER>` — clave fija hex de 32 bytes
|
||||
### Sección E2EE en config.yaml (generada por new-agent.sh)
|
||||
|
||||
**Sección encryption en config.yaml:**
|
||||
```yaml
|
||||
encryption:
|
||||
enabled: true
|
||||
enabled: true # SIEMPRE true para agentes nuevos
|
||||
store_path: "./agents/<agent-id>/data/crypto/" # SIEMPRE por agente, nunca compartida
|
||||
pickle_key_env: PICKLE_KEY_<ID_UPPER> # env var con clave hex
|
||||
pickle_key_env: PICKLE_KEY_<NORM> # generada por register.sh
|
||||
trust_mode: tofu
|
||||
recovery_key_env: SSSS_RECOVERY_KEY_<ID_UPPER> # env var con base58 recovery key
|
||||
recovery_key_env: SSSS_RECOVERY_KEY_<NORM> # generada por verify.sh
|
||||
```
|
||||
|
||||
**Al crear un nuevo agente con E2EE:**
|
||||
1. Generar pickle key: `openssl rand -hex 32`
|
||||
2. Añadir a `.env`: `PICKLE_KEY_<ID_UPPER>=<hex>`
|
||||
3. Añadir a `.env.example`: `PICKLE_KEY_<ID_UPPER>=`
|
||||
4. Usar `store_path` propio del agente (no compartir entre agentes)
|
||||
5. Ejecutar `cmd/verify` con `--store` y `--pickle-key` del agente:
|
||||
```bash
|
||||
./bin/verify --homeserver "$MATRIX_HOMESERVER" --username "<id>" \
|
||||
--password "$MATRIX_PASSWORD_<AGENT>" --token "$MATRIX_TOKEN_<AGENT>" \
|
||||
--store "./agents/<id>/data/crypto/" --pickle-key "$PICKLE_KEY_<AGENT>"
|
||||
```
|
||||
6. Guardar el recovery key en `.env` (con comillas por los espacios):
|
||||
```bash
|
||||
SSSS_RECOVERY_KEY_<ID_UPPER>="EsXX YYYY ZZZZ ..."
|
||||
```
|
||||
7. Añadir `recovery_key_env` al config.yaml:
|
||||
```yaml
|
||||
encryption:
|
||||
recovery_key_env: SSSS_RECOVERY_KEY_<ID_UPPER>
|
||||
```
|
||||
|
||||
**Sin el recovery key**, el agente arranca pero los mensajes muestran "Encrypted by a device not verified by its owner".
|
||||
|
||||
### 3. `agents/<agent-id>/prompts/system.md` — System prompt
|
||||
|
||||
Debe incluir:
|
||||
@@ -134,7 +143,7 @@ Debe incluir:
|
||||
|
||||
### 4. `cmd/launcher/main.go` — Registro en el launcher
|
||||
|
||||
Dos cambios:
|
||||
`new-agent.sh` hace esto automáticamente. Dos cambios:
|
||||
|
||||
**Import:**
|
||||
```go
|
||||
@@ -157,6 +166,49 @@ Si el agente necesita una herramienta nueva (no existente), ver la policy `creat
|
||||
|
||||
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:
|
||||
@@ -164,15 +216,17 @@ Verificar compilación:
|
||||
go build -tags goolm ./...
|
||||
```
|
||||
|
||||
Luego seguir con registro, avatar, verificación y arranque (ver `docs/creating-agents.md`).
|
||||
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/asistente2/` como referencia completa de un agente con tools habilitadas
|
||||
|
||||
+22
-4
@@ -12,6 +12,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -80,11 +81,17 @@ Example:
|
||||
}
|
||||
fmt.Printf("✓ Logged in, device ID: %s\n", deviceID)
|
||||
|
||||
// Step 3: Print results
|
||||
// Step 3: Generate pickle key for E2EE crypto store
|
||||
pickleKey := generatePickleKey()
|
||||
|
||||
// Derive env var prefix from envVar (e.g. MATRIX_TOKEN_FOO → FOO)
|
||||
norm := strings.TrimPrefix(envVar, "MATRIX_TOKEN_")
|
||||
|
||||
// Step 4: Print results — parseable lines for register.sh
|
||||
fmt.Println("\n─── Add to your .env ───────────────────────────────")
|
||||
fmt.Printf("%s=%s\n", envVar, token)
|
||||
fmt.Printf("MATRIX_HOMESERVER=%s\n", homeserver)
|
||||
fmt.Printf("MATRIX_SERVER_NAME=%s\n", serverName)
|
||||
fmt.Printf("MATRIX_PASSWORD_%s=%s\n", norm, password)
|
||||
fmt.Printf("PICKLE_KEY_%s=%s\n", norm, pickleKey)
|
||||
fmt.Println("────────────────────────────────────────────────────")
|
||||
fmt.Printf("\nUser ID: %s\n", userID)
|
||||
fmt.Printf("Device ID: %s\n", deviceID)
|
||||
@@ -176,7 +183,6 @@ func loginAs(homeserver, username, password string) (token, deviceID string, err
|
||||
|
||||
// generatePassword creates a random-enough password for the bot account.
|
||||
func generatePassword() string {
|
||||
// Simple: use os.ReadFile on /dev/urandom, encode hex
|
||||
f, err := os.Open("/dev/urandom")
|
||||
if err != nil {
|
||||
return "agent-bot-default-please-change"
|
||||
@@ -186,3 +192,15 @@ func generatePassword() string {
|
||||
_, _ = io.ReadFull(f, buf)
|
||||
return fmt.Sprintf("%x", buf)
|
||||
}
|
||||
|
||||
// generatePickleKey creates a 32-byte hex-encoded key for E2EE crypto store encryption.
|
||||
func generatePickleKey() string {
|
||||
f, err := os.Open("/dev/urandom")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer f.Close()
|
||||
buf := make([]byte, 32)
|
||||
_, _ = io.ReadFull(f, buf)
|
||||
return hex.EncodeToString(buf)
|
||||
}
|
||||
|
||||
@@ -135,6 +135,16 @@ list_agents_raw() {
|
||||
done
|
||||
}
|
||||
|
||||
# ── Naming convention ─────────────────────────────────────────────────────
|
||||
# Normalizes an agent ID to an env-var suffix.
|
||||
# Convention: uppercase, hyphens → underscores. No stripping of suffixes.
|
||||
# "assistant-bot" → "ASSISTANT_BOT"
|
||||
# "asistente-2" → "ASISTENTE_2"
|
||||
# "devops-bot" → "DEVOPS_BOT"
|
||||
normalize_id() {
|
||||
echo "$1" | tr '[:lower:]-' '[:upper:]_'
|
||||
}
|
||||
|
||||
# ── Usage helper ──────────────────────────────────────────────────────────
|
||||
need_arg() {
|
||||
[[ -n "${1:-}" ]] || { echo "Usage: $0 <agent-id>"; exit 1; }
|
||||
|
||||
@@ -23,6 +23,7 @@ need_arg "${1:-}"
|
||||
ID="$1"
|
||||
DISPLAYNAME="${2:-$ID}"
|
||||
PACKAGE="$(echo "$ID" | tr '-' '_' | sed 's/_bot//')" # "monitor-bot" → "monitor"
|
||||
NORM="$(normalize_id "$ID")" # "monitor-bot" → "MONITOR_BOT"
|
||||
DIR="agents/$ID"
|
||||
|
||||
[[ -d "$DIR" ]] && fail "Ya existe agents/$ID — ¿ya fue creado?"
|
||||
@@ -146,14 +147,15 @@ tools:
|
||||
matrix:
|
||||
homeserver: "${MATRIX_HOMESERVER}"
|
||||
user_id: "@$ID:${MATRIX_SERVER_NAME}"
|
||||
access_token_env: MATRIX_TOKEN_$(echo "$ID" | tr '[:lower:]-' '[:upper:]_' | sed 's/_BOT$//')
|
||||
device_id: "$(echo "$ID" | tr '[:lower:]-' '[:upper:]_')01"
|
||||
access_token_env: MATRIX_TOKEN_${NORM}
|
||||
device_id: ""
|
||||
|
||||
encryption:
|
||||
enabled: false
|
||||
enabled: true
|
||||
store_path: "./agents/${ID}/data/crypto/"
|
||||
pickle_key_env: PICKLE_KEY_$(echo "$ID" | tr '[:lower:]-' '[:upper:]_')
|
||||
pickle_key_env: PICKLE_KEY_${NORM}
|
||||
trust_mode: tofu
|
||||
recovery_key_env: SSSS_RECOVERY_KEY_${NORM}
|
||||
|
||||
rooms:
|
||||
listen: []
|
||||
@@ -386,7 +388,9 @@ else
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${YLW}Queda 1 paso:${RST} registrar el bot en Matrix y añadir su token a .env:"
|
||||
echo -e "${YLW}Quedan 3 pasos:${RST}"
|
||||
echo ""
|
||||
echo -e " ${DIM}./dev-scripts/register.sh $ID \"$DISPLAYNAME\"${RST}"
|
||||
echo -e " ${DIM}1. ./dev-scripts/register.sh $ID \"$DISPLAYNAME\"${RST} # registra en Matrix + genera token, password, pickle key"
|
||||
echo -e " ${DIM}2. ./dev-scripts/verify.sh $ID${RST} # genera cross-signing keys + verifica device"
|
||||
echo -e " ${DIM}3. ./dev-scripts/start.sh $ID${RST} # arranca el agente"
|
||||
echo ""
|
||||
|
||||
+46
-20
@@ -2,11 +2,16 @@
|
||||
# register.sh — registra un nuevo bot en el servidor Matrix via Synapse admin API
|
||||
#
|
||||
# Uso:
|
||||
# ./dev-scripts/register.sh <username> [displayname] [env-var-name]
|
||||
# ./dev-scripts/register.sh <username> [displayname]
|
||||
#
|
||||
# Ejemplos:
|
||||
# ./dev-scripts/register.sh assistant-bot "Assistant" MATRIX_TOKEN_ASSISTANT
|
||||
# ./dev-scripts/register.sh devops-bot "DevOps Agent" MATRIX_TOKEN_DEVOPS
|
||||
# ./dev-scripts/register.sh assistant-bot "Assistant"
|
||||
# ./dev-scripts/register.sh devops-bot "DevOps Agent"
|
||||
#
|
||||
# Genera y guarda en .env:
|
||||
# MATRIX_TOKEN_<NORM>=... (access token)
|
||||
# MATRIX_PASSWORD_<NORM>=... (password para UIA)
|
||||
# PICKLE_KEY_<NORM>=... (E2EE crypto store key)
|
||||
#
|
||||
# Requiere en .env:
|
||||
# MATRIX_ADMIN_TOKEN=syt_...
|
||||
@@ -19,12 +24,14 @@ need_arg "${1:-}"
|
||||
|
||||
USERNAME="$1"
|
||||
DISPLAYNAME="${2:-$USERNAME}"
|
||||
ENV_VAR="${3:-MATRIX_TOKEN_$(echo "$USERNAME" | tr '[:lower:]-' '[:upper:]_' | sed 's/_BOT$//')}"
|
||||
NORM="$(normalize_id "$USERNAME")"
|
||||
ENV_VAR="MATRIX_TOKEN_${NORM}"
|
||||
|
||||
[[ -n "${MATRIX_ADMIN_TOKEN:-}" ]] || fail "MATRIX_ADMIN_TOKEN no está en .env"
|
||||
[[ -n "${MATRIX_HOMESERVER:-}" ]] || fail "MATRIX_HOMESERVER no está en .env"
|
||||
|
||||
info "Registrando @${USERNAME}:${MATRIX_SERVER_NAME:-$MATRIX_HOMESERVER}..."
|
||||
dim " Env var prefix: ${NORM}"
|
||||
echo ""
|
||||
|
||||
# Ejecutar cmd/register y capturar su output completo
|
||||
@@ -37,23 +44,42 @@ OUTPUT=$("$GO" run ./cmd/register \
|
||||
echo "$OUTPUT"
|
||||
echo ""
|
||||
|
||||
# Extraer la línea ENV_VAR=token del output
|
||||
TOKEN_LINE=$(echo "$OUTPUT" | grep "^${ENV_VAR}=")
|
||||
[[ -n "$TOKEN_LINE" ]] || fail "No se encontró '${ENV_VAR}=' en el output de cmd/register"
|
||||
# ── Parsear y guardar cada variable en .env ──────────────────────────────
|
||||
|
||||
TOKEN=$(echo "$TOKEN_LINE" | cut -d= -f2-)
|
||||
[[ -n "$TOKEN" ]] || fail "Token vacío para $ENV_VAR"
|
||||
save_env_var() {
|
||||
local key="$1" value="$2"
|
||||
[[ -n "$value" ]] || return
|
||||
|
||||
# Actualizar .env — reemplazar si ya existe, añadir si no
|
||||
if grep -q "^${ENV_VAR}=" .env; then
|
||||
awk -v key="$ENV_VAR" -v val="$TOKEN" \
|
||||
'index($0, key "=") == 1 { print key "=" val; next } { print }' \
|
||||
.env > /tmp/_env_tmp && mv /tmp/_env_tmp .env
|
||||
ok "$ENV_VAR actualizado en .env"
|
||||
else
|
||||
printf '\n%s=%s\n' "$ENV_VAR" "$TOKEN" >> .env
|
||||
ok "$ENV_VAR añadido a .env"
|
||||
fi
|
||||
# Quote values with spaces
|
||||
if [[ "$value" == *" "* ]]; then
|
||||
value="\"${value}\""
|
||||
fi
|
||||
|
||||
if grep -q "^${key}=" .env; then
|
||||
awk -v key="$key" -v val="$value" \
|
||||
'index($0, key "=") == 1 { print key "=" val; next } { print }' \
|
||||
.env > /tmp/_env_tmp && mv /tmp/_env_tmp .env
|
||||
ok "$key actualizado en .env"
|
||||
else
|
||||
printf '%s=%s\n' "$key" "$value" >> .env
|
||||
ok "$key añadido a .env"
|
||||
fi
|
||||
}
|
||||
|
||||
# Extract parseable lines from output
|
||||
TOKEN=$(echo "$OUTPUT" | grep "^${ENV_VAR}=" | cut -d= -f2-)
|
||||
PASSWORD=$(echo "$OUTPUT" | grep "^MATRIX_PASSWORD_${NORM}=" | cut -d= -f2-)
|
||||
PICKLE_KEY=$(echo "$OUTPUT" | grep "^PICKLE_KEY_${NORM}=" | cut -d= -f2-)
|
||||
|
||||
[[ -n "$TOKEN" ]] || fail "No se encontró '${ENV_VAR}=' en el output"
|
||||
|
||||
save_env_var "$ENV_VAR" "$TOKEN"
|
||||
save_env_var "MATRIX_PASSWORD_${NORM}" "$PASSWORD"
|
||||
save_env_var "PICKLE_KEY_${NORM}" "$PICKLE_KEY"
|
||||
|
||||
echo ""
|
||||
dim " Arranca el bot con: ./dev-scripts/start.sh $USERNAME"
|
||||
echo -e "${YLW}Siguientes pasos:${RST}"
|
||||
echo ""
|
||||
echo -e " ${DIM}1. ./dev-scripts/verify.sh $USERNAME${RST} # genera cross-signing keys E2EE"
|
||||
echo -e " ${DIM}2. ./dev-scripts/start.sh $USERNAME${RST} # arranca el agente"
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user