#!/usr/bin/env bash # new-agent.sh — genera el scaffold de un nuevo agente # # Uso: # ./dev-scripts/agent/new-agent.sh [displayname] # # Ejemplo: # ./dev-scripts/agent/new-agent.sh monitor-bot "Monitor Agent" # # Crea: # agents//config.yaml (copiado desde agents/_template/) # agents//agent.go (copiado desde agents/_template/) # agents//prompts/ (copiado desde agents/_template/prompts/) # agents//data/ (directorio de datos, en .gitignore) # # También te recuerda los dos pasos manuales que quedan. source "$(dirname "$0")/../_common.sh" load_env 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" 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 desde _template..." mkdir -p "$DIR/prompts" "$DIR/data" # ── 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/SSSS_RECOVERY_KEY_TEMPLATE/SSSS_RECOVERY_KEY_${NORM}/g" "$DIR/config.yaml" sed -i "s/SSSS_RECOVERY_KEY_ROBOT/SSSS_RECOVERY_KEY_${NORM}/g" "$DIR/config.yaml" sed -i "s/MATRIX_TOKEN_ROBOT/MATRIX_TOKEN_${NORM}/g" "$DIR/config.yaml" sed -i "s/PICKLE_KEY_ROBOT/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 # ============================================ agent: id: $ID name: "$DISPLAYNAME" version: "1.0.0" enabled: true description: "Descripción del agente $DISPLAYNAME" tags: [$(echo "$ID" | tr '-' ',')] # ============================================ # PERSONALIDAD Y COMPORTAMIENTO # ============================================ personality: tone: friendly verbosity: concise language: es languages_supported: [es, en] emoji_style: minimal prefix: "🤖" error_style: helpful templates: greeting: "Hola, soy $DISPLAYNAME. ¿En qué puedo ayudarte?" unknown_command: "No reconozco ese comando. Escríbeme directamente." permission_denied: "No tengo permiso para hacer eso." error: "Algo salió mal: {{.Error}}" success: "{{.Summary}}" busy: "Procesando, dame un momento..." behavior: proactive: false ask_confirmation: false show_reasoning: false thread_replies: true typing_indicator: true acknowledge_receipt: false # ============================================ # LLM # ============================================ llm: primary: provider: openai model: gpt-4o api_key_env: OPENAI_API_KEY base_url: "" max_tokens: 4096 temperature: 0.7 fallback: provider: "" model: "" api_key_env: "" base_url: "" max_tokens: 0 temperature: 0 reasoning: system_prompt_file: "prompts/system.md" context_window: 16384 memory_messages: 20 tool_use: enabled: false max_iterations: 3 parallel_calls: false rate_limit: requests_per_minute: 30 tokens_per_minute: 100000 concurrent_requests: 3 # ============================================ # TOOLS — ajustar según necesidades del agente # ============================================ tools: ssh: enabled: false allowed_targets: [] forbidden_commands: [] timeout: 0s max_concurrent: 0 require_confirmation: [] http: enabled: false allowed_domains: [] timeout: 0s max_retries: 0 scripts: enabled: false scripts_dir: "" allowed: [] timeout: 0s sandbox: false file_ops: enabled: false allowed_paths: [] read_only: true mcp: enabled: false servers: [] expose: port: 0 tools: [] # ============================================ # MATRIX # ============================================ matrix: homeserver: "${MATRIX_HOMESERVER}" user_id: "@$ID:${MATRIX_SERVER_NAME}" access_token_env: MATRIX_TOKEN_${NORM} device_id: "" encryption: enabled: true store_path: "./agents/${ID}/data/crypto/" pickle_key_env: PICKLE_KEY_${NORM} trust_mode: tofu recovery_key_env: SSSS_RECOVERY_KEY_${NORM} rooms: listen: [] respond: [] admin: [] filters: command_prefix: "!" mention_respond: true dm_respond: true ignore_bots: true ignore_users: [] min_power_level: 0 threads: enabled: true # responder en threads cuando el mensaje viene de un thread auto_thread: false # true para crear thread automático por cada conversación nueva # ============================================ # INTER-AGENTES # ============================================ agents: peers: [] delegation: enabled: false can_delegate_to: [] can_receive_from: [] max_delegation_depth: 1 timeout: 30s protocol: format: json channel: matrix heartbeat_interval: 60s # ============================================ # SSH # ============================================ ssh: defaults: user: "" port: 22 key_file_env: "" known_hosts: "" keepalive_interval: 0s timeout: 0s targets: {} # ============================================ # SEGURIDAD # ============================================ security: roles: admin: users: ["@admin:\${MATRIX_SERVER_NAME}"] actions: ["*"] user: users: ["*"] actions: ["help"] audit: enabled: false log_file: "./data/audit.log" log_to_room: "" include: [] secrets: provider: env # ============================================ # SCHEDULING # ============================================ schedules: [] # ============================================ # OBSERVABILIDAD # ============================================ observability: logging: level: info format: json output: stdout file: "./data/$ID.log" metrics: enabled: false port: 0 path: /metrics export: prometheus health: enabled: true port: 0 path: /healthz tracing: enabled: false provider: "" endpoint: "" # ============================================ # RESILIENCIA # ============================================ resilience: circuit_breaker: failure_threshold: 5 timeout: 30s half_open_max: 2 retry: max_attempts: 2 backoff: exponential initial_delay: 1s max_delay: 10s shutdown: timeout: 10s drain_messages: true save_state: false state_file: "" queue: enabled: true max_size: 50 priority_users: ["@admin:\${MATRIX_SERVER_NAME}"] # ============================================ # ALMACENAMIENTO # ============================================ storage: state: backend: sqlite path: "./data/$ID.db" cache: enabled: true backend: memory ttl: 5m max_entries: 200 history: backend: sqlite path: "./data/history.db" retention: 168h YAML # ── 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" sed -i "s/AGENT_ID_PLACEHOLDER/$ID/g" "$DIR/agent.go" ok "agent.go creado desde template" # ── 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 "" # ── Actualizar cmd/launcher/main.go — añadir blank import ──────────────── LAUNCHER="cmd/launcher/main.go" BLANK_IMPORT="_ \"github.com/enmanuel/agents/agents/$ID\"" if grep -q "agents/$ID\"" "$LAUNCHER" 2>/dev/null; then warn "$ID ya tiene blank import en $LAUNCHER — saltando" else TAB=$'\t' IMPORT_LINE="${TAB}${BLANK_IMPORT}" # Insertar blank import después del último blank import de agents/ if awk -v new_import="$IMPORT_LINE" ' { lines[NR] = $0 if ($0 ~ /_ "github\.com\/enmanuel\/agents\/agents\//) last_import = NR } END { if (!last_import) { for (i=1;i<=NR;i++) print lines[i]; exit 1 } for (i = 1; i <= NR; i++) { print lines[i] if (i == last_import) print new_import } } ' "$LAUNCHER" > /tmp/_launcher_tmp; then mv /tmp/_launcher_tmp "$LAUNCHER" ok "Blank import añadido en $LAUNCHER" else warn "No se pudo insertar el blank import — añádelo manualmente:" echo -e " ${GRN}${IMPORT_LINE}${RST}" fi fi echo "" echo -e "${YLW}Quedan 3 pasos:${RST}" echo "" echo -e " ${DIM}1. ./dev-scripts/agent/register.sh $ID \"$DISPLAYNAME\"${RST} # registra en Matrix + genera token, password, pickle key" echo -e " ${DIM}2. ./dev-scripts/agent/verify.sh $ID${RST} # genera cross-signing keys + verifica device" echo -e " ${DIM}3. ./dev-scripts/server/start.sh $ID${RST} # arranca el agente" echo ""