feat: import agents_and_robots platform as unibots (Matrix-out, unibus transport)

Reemplaza el scaffold del echobot por la plataforma completa de bots traida
desde ~/DataProyects/Github/agents_and_robots tras la operacion Matrix-out:
los bots ya no hablan por Matrix sino por el bus unibus (modelo todo-rooms +
E2E via shell/transportunibus sobre github.com/enmanuel/unibus/pkg/client).

- go.mod: replace de unibus -> ../unibus y de fn-registry -> ../../../.. (paths
  relativos reajustados a la nueva ubicacion dentro de fn_registry).
- app.md: bump a 0.2.0, descripcion + arquitectura + comandos + gotchas reales.
- modulo Go conservado como github.com/enmanuel/agents (sin reescribir imports).

agents_and_robots queda archivado como museo de la era Matrix.
This commit is contained in:
agent
2026-06-07 11:50:13 +02:00
parent bb5b0e09b1
commit fc644ecd6e
308 changed files with 38829 additions and 474 deletions
+236
View File
@@ -0,0 +1,236 @@
# Policy: Crear un nuevo agente o robot
Guia ejecutable para Claude. Seguir paso a paso sin desviarse.
## Robot vs Agent — decidir primero
| | Agent | Robot |
|---|---|---|
| **Cuando usar** | Necesita LLM, reglas, memoria, tools | Solo responde comandos (!xxx) |
| **Runtime** | `agents.New()` — completo | `agents.NewRobot()` — ligero |
| **Config type** | `type: agent` (default) | `type: robot` |
| **LLM** | Si | No |
| **Reglas** | Si (`agent.go` con `Rules()`) | No (sin `agent.go`) |
| **Memoria/Knowledge/Skills** | Si (opcionales) | No |
| **Tools** | Si (opcionales) | No |
| **System prompt** | Si (`prompts/system.md`) | No necesario |
| **Comandos built-in** | help, ping, tools, tool, status, info, clear, prompts, version | help, ping, status, info, version |
| **Comandos custom** | Si (`RegisterCommand`) | Si (`RegisterCommand`) |
| **Template** | `agents/_template/` | `agents/_template_robot/` |
| **Config ejemplo** | ~260 lineas | ~55 lineas |
**Regla**: si el bot necesita entender lenguaje natural, es un Agent. Si solo necesita comandos directos, es un Robot.
## Inputs — preguntar al usuario si no los da
| Input | Requerido | Default | Ejemplo |
|-------|-----------|---------|---------|
| `agent-id` | si | — | `monitor-bot` |
| `display-name` | si | — | `"Monitor Agent"` |
| `description` | si | — | `"Monitorea servicios y reporta estado"` |
| `type` | no | `agent` | `agent` o `robot` |
| `llm.provider` | no (N/A para robots) | `openai` | `openai` o `anthropic` |
| `llm.model` | no (N/A para robots) | `gpt-4o` | `gpt-4o`, `claude-sonnet-4-20250514` |
| `tool_use` | no (N/A para robots) | `false` | `true` si necesita herramientas |
| System prompt | si (N/A para robots) | — | Texto describiendo rol y capacidades |
Si el usuario da todos los inputs, ir directo a la Ruta Rapida. Si faltan, preguntar antes de empezar.
## Ruta rápida — script automatizado
```bash
./dev-scripts/agent/create-full.sh <agent-id> "Display Name"
```
Este script ejecuta en orden: scaffold → build → register Matrix → verify E2EE.
Crea todos los archivos, registra en el launcher, genera todas las env vars en `.env`.
Después del script, personalizar los 3 archivos del agente (ver sección siguiente).
## Archivos a personalizar después del scaffold
### 1. `agents/<agent-id>/agent.go` — Reglas puras
Template base (generado por el scaffold):
```go
package <pkgname> // sin guiones: "monitor-bot" → package monitor (strip hyphens, strip _bot)
import (
"github.com/enmanuel/agents/agents"
"github.com/enmanuel/agents/pkg/decision"
)
func init() {
agents.Register("<agent-id>", Rules)
}
func Rules() []decision.Rule {
return []decision.Rule{
// Any DM or mention → 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 estrictas:**
- **PURO**: solo imports de `pkg/decision` y `agents` (para Register), cero I/O, cero side effects
- **Auto-registro**: cada agente se registra via `init()` con `agents.Register("<agent-id>", Rules)`
- Package name = ID sin guiones ni `_bot` (e.g. `monitor-bot``package monitor`)
- **No usar reglas para comandos** (`!help`, `!ping`, etc.) — los comandos se gestionan via `RegisterCommand` (ver policy `create_command.md`)
- Las reglas solo aplican a mensajes normales (sin prefijo `!`)
Tipos de acción disponibles:
- `ActionKindReply` — respuesta estática (con `ReplyAction{Content: "..."}`)
- `ActionKindLLM` — pasa al LLM (con `LLMAction{}`)
### 2. `agents/<agent-id>/config.yaml` — Configuración
El scaffold genera un config completo con defaults sensatos. Solo personalizar estas secciones:
**Identidad** (siempre editar):
```yaml
agent:
description: "<la descripción del agente>"
```
**LLM** (si quieres cambiar provider/model):
```yaml
llm:
primary:
provider: anthropic # o openai (default)
model: claude-sonnet-4-20250514 # o gpt-4o (default)
api_key_env: ANTHROPIC_API_KEY # o OPENAI_API_KEY (default)
```
**Claude-code provider** (si usa `claude-code` como provider):
```yaml
llm:
primary:
provider: claude-code
claude_code:
working_dir: "/tmp/claude-agents/<agent-id>" # SIEMPRE configurar, nunca dejar vacío
permission_mode: "bypassPermissions"
```
**Importante**: `working_dir` debe apuntar fuera del repositorio para evitar que el subproceso `claude -p` acceda al código fuente. Si se deja vacío, se usará un directorio temporal (con WARN en logs).
**Tool use** (si el agente necesita herramientas):
```yaml
llm:
tool_use:
enabled: true # cambiar de false a true
max_iterations: 5
```
**Personalidad** (ajustar tono):
```yaml
personality:
tone: friendly # friendly | professional | casual | technical
language: es # es | en
prefix: "🤖" # emoji del bot
```
**Threads** (habilitado por defecto en el scaffold):
```yaml
matrix:
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
```
Referencia completa del schema: `internal/config/schema.go`
### 3. `agents/<agent-id>/prompts/system.md` — System prompt
Escribir el system prompt completo. Debe incluir:
- **Identidad**: quién es, cómo se llama
- **Rol**: qué hace, para qué sirve
- **Capacidades**: qué puede hacer (incluir tools si `tool_use.enabled: true`)
- **Estilo**: idioma, tono, formato de respuestas
- **Restricciones**: qué NO debe hacer
- **Seguridad** (obligatorio): copiar la seccion de `.claude/templates/security-prompt.md` al final del prompt. Esta seccion protege contra prompt injection.
Ejemplo de referencia: `agents/asistente-2/prompts/system.md`
## Registro en el launcher — `cmd/launcher/main.go`
El script `new-agent.sh` (ejecutado por `create-full.sh`) hace esto automáticamente.
Si falla, hacer manualmente:
**Blank import** (en la sección de blank imports de agentes):
```go
_ "github.com/enmanuel/agents/agents/<agent-id>"
```
Las reglas se registran automáticamente via `init()` en el paquete del agente.
No se necesita editar ningún map ni registry manualmente.
**El ID en `agents.Register()` DEBE coincidir exactamente con `agent.id` en config.yaml.**
## Convención de env vars — REGLA CRÍTICA
Normalización: `normalize_id()` → mayúsculas, guiones → underscores. **Sin eliminar sufijos.**
| Agent ID | Normalizado | Env vars |
|---|---|---|
| `assistant-bot` | `ASSISTANT_BOT` | `MATRIX_TOKEN_ASSISTANT_BOT`, `MATRIX_PASSWORD_ASSISTANT_BOT`, `PICKLE_KEY_ASSISTANT_BOT`, `SSSS_RECOVERY_KEY_ASSISTANT_BOT` |
| `mi-bot` | `MI_BOT` | `MATRIX_TOKEN_MI_BOT`, ... |
**NUNCA** aplicar transformaciones que eliminen partes del ID (no `sed 's/_BOT$//'`).
## Verificación post-creación
Checklist a verificar antes de considerar el agente listo:
- [ ] `go build -tags goolm ./...` compila sin errores
- [ ] `agents/<id>/agent.go` exporta `Rules()` y es puro (sin I/O)
- [ ] `agents/<id>/config.yaml` tiene `agent.id` = nombre del directorio
- [ ] `cmd/launcher/main.go` tiene blank import del paquete del agente
- [ ] `.env` contiene: `MATRIX_TOKEN_<NORM>`, `MATRIX_PASSWORD_<NORM>`, `PICKLE_KEY_<NORM>`, `SSSS_RECOVERY_KEY_<NORM>`
- [ ] `prompts/system.md` tiene contenido real (no el stub)
- [ ] `prompts/system.md` incluye la seccion de seguridad anti-injection (de `.claude/templates/security-prompt.md`)
- [ ] Si `tool_use.enabled: true`, el prompt menciona las tools disponibles
## Arranque y verificación
```bash
# Arrancar (reconstruye y lanza todos los agentes habilitados)
./dev-scripts/server/start.sh
# Verificar logs
tail -f run/launcher.log
# Logs esperados al arrancar correctamente:
# {"level":"INFO","msg":"e2ee ready"}
# {"level":"INFO","msg":"agent running"}
# {"level":"INFO","msg":"starting matrix sync"}
```
## Troubleshooting E2EE
| Problema | Solución |
|----------|----------|
| "device not verified by its owner" | `./dev-scripts/agent/verify.sh <id>` y reiniciar |
| "self-signing private key not in cache" | Recovery key incorrecta → re-ejecutar verify.sh |
| "received update for device with different signing key" | Recompilar launcher: `go build -tags goolm -o bin/launcher ./cmd/launcher` |
| Recovery key sin comillas en .env | Añadir comillas: `SSSS_RECOVERY_KEY_*="EsXX YYYY ..."` |
## Reglas generales
- **Nunca** side effects en `agent.go`
- **Siempre** compilar con `-tags goolm`
- **Siempre** que `agent.id` coincida entre config.yaml, `agents.Register()` y directorio
- **No** crear `data/` manualmente — se auto-genera
- **No** commitear tokens ni passwords
- **No** compartir crypto stores entre agentes
- Referencia de agente con tools: `agents/asistente-2/`
- Referencia de agente simple: `agents/assistant-bot/`
+149
View File
@@ -0,0 +1,149 @@
# Policy: Crear un comando para un agente
Los comandos (`!xxx`) son respuestas directas que no pasan por reglas ni por el LLM.
Siempre se resuelven primero en el flujo de eventos.
## Arquitectura del sistema de comandos
```
Usuario envía "!help"
→ listener.go parsea → msgCtx.Command = "help"
→ runtime.go handleEvent:
1. Busca en built-in commands (help, ping, tools, etc.) → match → responde → FIN
2. Si no match → "Comando desconocido" → FIN
(Nunca llega a reglas ni LLM)
Usuario envía "hola"
→ Command == "" → reglas → LLM → respuesta normal
```
## Tipos de comandos
### Built-in (todos los agentes)
Definidos en `pkg/command/builtins.go` (specs puras) y `agents/commands.go` (handlers).
Todos los agentes los tienen automáticamente: `!help`, `!ping`, `!tools`, `!tool`, `!status`, `!info`, `!clear`, `!version`.
**No modificar los built-in para agregar funcionalidad por agente.** Usar `RegisterCommand` en su lugar.
### Agent-specific (por agente)
Se registran con `agent.RegisterCommand(spec, handler)` en el launcher, después de `agents.New()` y antes de `agent.Run()`.
## Pasos para crear un comando de agente
### 1. Definir el handler en el paquete del agente
Crear un archivo `commands.go` en `agents/<agent-id>/`:
```go
package <pkgname>
import (
"context"
"fmt"
"github.com/enmanuel/agents/pkg/command"
"github.com/enmanuel/agents/pkg/decision"
)
// Commands returns the command specs and handlers for this agent.
// Handlers are functions, but must NOT do I/O directly — they receive
// dependencies via closure when registered in the launcher.
func Commands() []CommandEntry {
return []CommandEntry{
{
Spec: command.Spec{
Name: "deploy",
Aliases: []string{"d"},
Description: "Despliega al entorno indicado",
Usage: "!deploy <env>",
},
Handler: func(ctx context.Context, msgCtx decision.MessageContext) string {
if len(msgCtx.Args) < 2 {
return "Uso: !deploy <env>"
}
env := msgCtx.Args[1]
return fmt.Sprintf("Desplegando a %s...", env)
},
},
}
}
// CommandEntry pairs a spec with its handler.
type CommandEntry struct {
Spec command.Spec
Handler func(ctx context.Context, msgCtx decision.MessageContext) string
}
```
### 2. Registrar en el launcher (`cmd/launcher/main.go`)
Después de `agents.New()` y antes de `wg.Add(1)`:
```go
a, err := agents.New(cfg, rules, agentLogger)
if err != nil { ... }
// Register agent-specific commands
if cfg.Agent.ID == "<agent-id>" {
for _, cmd := range <pkg>agent.Commands() {
a.RegisterCommand(cmd.Spec, cmd.Handler)
}
}
```
### 3. Documentar en el system prompt
Si el agente tiene LLM, mencionar los comandos en `prompts/system.md` para que el LLM pueda informar al usuario:
```markdown
## Comandos disponibles
- `!deploy <env>` — Despliega al entorno indicado
- `!help` — Lista todos los comandos
```
## API de registro
```go
// En agents/runtime.go
func (a *Agent) RegisterCommand(spec command.Spec, handler CommandHandler)
```
- `spec.Name`: nombre del comando (lo que va después de `!`)
- `spec.Aliases`: nombres cortos alternativos (opcional)
- `spec.Description`: texto que aparece en `!help`
- `spec.Usage`: ejemplo de uso que aparece en `!help`
- `spec.Hidden`: si es `true`, no aparece en `!help`
- `handler`: recibe `(ctx, msgCtx)` y devuelve un string
El handler tiene acceso a:
- `msgCtx.Args` — argumentos parseados por `strings.Fields` (incluye el nombre del comando en `Args[0]` solo si viene de `!tool xxx`)
- `msgCtx.Command` — nombre del comando (ya sin `!`)
- `msgCtx.SenderID`, `msgCtx.RoomID`, `msgCtx.IsDirectMsg`, etc.
## Prioridad de resolución
```
1. Built-in commands (help, ping, tools, etc.) ← siempre ganan
2. Agent-specific commands (RegisterCommand) ← segundo
3. Si no hay match → "Comando desconocido" ← nunca llega al LLM
```
Un agent-specific command **no puede** sobrescribir un built-in. Si se registra un comando con el mismo nombre que un built-in, el built-in prevalece.
## Reglas
- **No usar reglas (`agent.go`) para comandos.** Las reglas son para lógica de decisión sobre mensajes normales.
- **Los handlers pueden ser impuros** (HTTP, SSH, etc.) — se ejecutan en el contexto del runtime.
- **Respuesta siempre es string** — el runtime lo envía por Matrix automáticamente.
- **Validar argumentos** al inicio del handler y devolver usage si faltan.
- **Logs automáticos** — el runtime loguea `command_received` y `command_executed` a nivel INFO.
- **`msgCtx.Args`** para `!deploy prod` contiene `["prod"]` (sin el nombre del comando).
## Verificación
- [ ] `go build -tags goolm ./...` compila
- [ ] `!help` muestra el comando nuevo en la sección "Comandos del agente"
- [ ] Enviar el comando por Matrix produce la respuesta esperada
- [ ] En logs aparece `command_received` y `command_executed`
+86
View File
@@ -0,0 +1,86 @@
# Regla: Crear un nuevo issue
Guia para crear issues de features, mejoras o bugs en `dev/issues/`.
## Inputs — preguntar al usuario si no los da
| Input | Requerido | Ejemplo |
|-------|-----------|---------|
| Titulo | si | "Hot reload de configuracion" |
| Descripcion/objetivo | si | "Recargar config sin reiniciar el agente" |
| Dependencias | no | "Requiere issue 0010" |
## Pasos
### 1. Determinar el numero del issue
Buscar el numero mas alto en `dev/issues/` (incluyendo `completed/`) y usar el siguiente. Formato: 4 digitos con ceros a la izquierda (`0019`, `0020`, etc.).
### 2. Crear el archivo desde el template
Copiar `.claude/templates/issue.md` a `dev/issues/<NNNN>-<slug>.md`.
El slug debe ser:
- Lowercase
- Palabras separadas por guiones
- Conciso (2-4 palabras)
- Ejemplo: `0019-hot-reload.md`
### 3. Rellenar el template
Completar todas las secciones del template:
- **Objetivo**: 1-3 frases claras de que se quiere lograr
- **Contexto**: que existe, que falta, dependencias
- **Arquitectura**: archivos afectados, marcar `NEW` los nuevos. Incluir como se respeta pure core / impure shell
- **Tareas**: desglosar en fases con tareas numeradas (`1.1`, `1.2`, etc.). Cada tarea debe ser concreta y verificable. Incluir siempre una fase de tests y una de cleanup/docs
- **Ejemplo de uso**: flujo concreto mostrando la feature funcionando
- **Decisiones de diseno**: justificar las decisiones clave
- **Prerequisitos**: que debe estar implementado antes
- **Riesgos**: problemas potenciales y mitigacion
### 4. Actualizar el indice
Agregar una fila al final de la tabla en `dev/issues/README.md`:
```markdown
| <N> | <Titulo> | [<NNNN>-<slug>.md](<NNNN>-<slug>.md) | pendiente |
```
## Features multi-issue (feature flags)
Si la feature es demasiado grande para completarse en una sola rama corta (horas), **desglosar en sub-issues** que se implementan y mergean por separado. Cada sub-issue debe:
1. Ser autocontenido: compilar, pasar tests, no romper master
2. Proteger el codigo parcial con un **feature flag** en `dev/feature_flags.json` (desactivado hasta que todo este listo)
3. Usar numeracion con sufijo letra: `0015a`, `0015b`, etc.
**Ejemplo:**
```
0015a-telegram-types → tipos puros en pkg/
0015b-telegram-client → cliente en shell/
0015c-telegram-listener → integracion en agents/
0015d-telegram-enable → activar flag, cleanup
```
Indicar en el issue principal que es multi-issue y listar los sub-issues planificados.
**Feature flag ≠ WIP.** Un flag protege codigo terminado y testeado; un WIP es codigo a medias. Nunca commitear codigo incompleto a master.
## Reglas
- **Patron pure core / impure shell**: toda feature debe explicar que va en `pkg/` (puro) vs `shell/` (impuro).
- **Tareas atomicas**: cada tarea debe ser implementable de forma independiente.
- **Numeracion continua**: nunca reusar numeros de issues eliminados.
- **Estado**: los issues nuevos siempre empiezan como `pendiente`.
- **Completados**: cuando se termine un issue, moverlo a `dev/issues/completed/` y actualizar el README.
- **Issues grandes**: si no cabe en una rama corta, desglosar en sub-issues con feature flags.
## Verificacion
- [ ] Archivo creado en `dev/issues/<NNNN>-<slug>.md`
- [ ] Todas las secciones del template rellenadas
- [ ] Fila agregada en `dev/issues/README.md`
- [ ] Numero de issue es consecutivo (no hay saltos ni duplicados)
- [ ] Si es multi-issue: sub-issues planificados y feature flag definido
+199
View File
@@ -0,0 +1,199 @@
# Regla: Crear nueva skill
Guia para crear una nueva skill en `skills/`.
## Prerequisitos
- Entender la diferencia entre **tools** (funciones atomicas) y **skills** (flujos multi-paso)
- Las skills son contenido declarativo (markdown + recursos), no codigo Go
- Una skill combina tools existentes, logica condicional y conocimiento de dominio
## Proceso
### 1. Determinar categoria
Elegir la categoria adecuada:
- `devops/` — operaciones y deploy
- `analysis/` — analisis de datos/logs
- `communication/` — comunicacion y notificaciones
- `coding/` — desarrollo y code review
- `system/` — administracion del sistema
Si ninguna aplica, crear nueva categoria.
### 2. Crear estructura de directorios
```bash
mkdir -p skills/<categoria>/<skill-name>/{scripts,references,templates,assets}
```
Solo crear las subcarpetas que vayas a usar.
### 3. Escribir SKILL.md
Template:
```markdown
---
name: skill-name
description: >
Descripcion clara de que hace la skill y cuando debe activarse.
Esta descripcion es el mecanismo principal de triggering.
Idealmente < 100 palabras.
---
# <Nombre Descriptivo>
Breve introduccion de la skill (1-2 parrafos).
## Casos de uso
- Caso 1
- Caso 2
- Caso 3
## Proceso de ejecucion
### 1. Paso inicial
Descripcion del paso, que tools usar, ejemplos de codigo.
```bash
# ejemplo de comando
ssh_command host="prod-01" command="systemctl status myapp"
```
### 2. Paso siguiente
Continuar con los pasos...
## Parametros requeridos
Lista de parametros que el usuario debe proporcionar:
- `param1`: descripcion
- `param2`: descripcion
Parametros opcionales:
- `opt1`: descripcion (default: valor)
## Ejemplo de uso
Usuario: "Haz X"
Agente:
1. skill_search("X")
2. skill_load("<skill-name>")
3. Ejecutar pasos...
4. Reportar resultado
## Seguridad
Consideraciones de seguridad especificas para esta skill.
```
### 4. Anadir recursos (opcional)
#### Scripts (`scripts/`)
Scripts ejecutables que la skill puede invocar:
```bash
#!/bin/bash
# scripts/deploy.sh
# Descripcion del script
set -euo pipefail
# Validar argumentos
if [ $# -lt 1 ]; then
echo "Usage: $0 <service-name>"
exit 1
fi
# Implementacion...
```
**Importante**:
- Usar shebang correcto (`#!/bin/bash`, `#!/usr/bin/env python3`, etc.)
- Validar argumentos
- Usar `set -euo pipefail` en bash
- Exit codes claros (0 = exito, != 0 = error)
#### Referencias (`references/`)
Documentacion extensa que el agente puede consultar bajo demanda:
```markdown
# API Reference
Documentacion detallada...
Si > 300 lineas, agregar TOC al inicio.
```
#### Templates (`templates/`)
Plantillas que la skill usa como base:
```yaml
# template-report.md
# Report: {{title}}
Generated: {{timestamp}}
## Summary
{{summary}}
...
```
### 5. Probar la skill
1. Habilitar skills en el config de un agente de prueba:
```yaml
skills:
enabled: true
path: "skills/"
categories: ["<categoria>"]
tools:
skills:
allowed_interpreters: ["bash", "sh"]
```
2. Reiniciar el agente
3. Probar buscando la skill: `skill_search("<query>")`
4. Cargar la skill: `skill_load("<skill-name>")`
5. Ejecutar el flujo completo siguiendo las instrucciones
### 6. Documentar
Actualizar `skills/README.md` si:
- Creas una nueva categoria
- La skill introduce un patron nuevo
- Hay consideraciones de seguridad especiales
## Reglas criticas
- **Skills != Tools**: Las skills usan tools, no son tools
- **SKILL.md < 500 lineas**: Si es mas largo, dividir en multiple skills o mover contenido a `references/`
- **Description precisa**: La description en el frontmatter es critica para el matching
- **Idempotencia**: Las skills deben ser seguras de ejecutar multiples veces si es posible
- **Error handling**: Las instrucciones deben incluir que hacer en caso de error
- **Rollback**: Si la skill hace cambios destructivos, incluir instrucciones de rollback
## Ejemplos de skills validas
Ver las skills existentes en `skills/`:
- `skills/devops/deploy-service/` — deploy completo con rollback
- `skills/analysis/log-analyzer/` — analisis de logs con metricas
- `skills/system/health-check/` — verificacion de salud multi-servicio
- `skills/communication/daily-report/` — generacion de reportes
## Anti-patrones
- Skill que solo ejecuta un comando SSH → usar tool `ssh_command` directamente
- Skill con logica de negocio compleja → crear tool Go con tests
- Skill que repite instrucciones del system prompt → innecesario
- Scripts que requieren interaccion humana → las skills son automaticas
+78
View File
@@ -0,0 +1,78 @@
# Cómo crear una nueva herramienta (tool)
Las herramientas viven en `tools/` y siguen el patrón **spec puro + función impura**.
## Pasos
### 1. Crear el archivo `tools/<nombre>.go`
```go
package tools
import (
"context"
"fmt"
)
// NewMiTool creates a mi_tool tool that does X.
// Accepts dependencies needed for execution (configs, clients, etc).
func NewMiTool(/* deps */) Tool {
return Tool{
Def: Def{
Name: "mi_tool",
Description: "Description clara de qué hace la herramienta para el LLM.",
Parameters: []Param{
{Name: "param1", Type: "string", Description: "What this param is", Required: true},
{Name: "param2", Type: "number", Description: "Optional param", Required: false},
},
},
Exec: func(ctx context.Context, args map[string]any) Result {
p1 := getString(args, "param1")
if p1 == "" {
return Result{Err: fmt.Errorf("mi_tool: param1 is required")}
}
// Execute the actual work here (impure)
output := doSomething(p1)
return Result{Output: output}
},
}
}
```
### 2. Registrar en `agents/runtime.go` → `buildToolRegistry()`
```go
if /* condición basada en config */ {
reg.Register(tools.NewMiTool(/* deps */))
logger.Debug("registered mi_tool")
}
```
### 3. Habilitar en el config del agente (`agents/<id>/config.yaml`)
Asegurarse de que `llm.tool_use.enabled: true` y la sección relevante de `tools:` esté habilitada.
## Reglas
- **Def es PURO**: solo datos (nombre, descripción, parámetros). Sin side effects.
- **Exec es IMPURO**: hace I/O real. Recibe `context.Context` y `map[string]any`.
- **Validar inputs**: siempre validar parámetros requeridos al inicio del Exec.
- **Validar permisos**: usar los campos del config (AllowedDomains, AllowedPaths, etc.) para restringir acceso.
- **Limitar output**: truncar a 64 KB máximo para no saturar el contexto del LLM.
- **Usar `getString()`**: helper del package para extraer strings de args de forma segura.
- **Param types válidos**: "string", "number", "integer", "boolean", "object", "array" (JSON Schema types).
- **Descripción clara**: el LLM decide cuándo usar la tool basándose en el Description del Def.
## Seguridad — requisitos obligatorios
Toda tool que haga I/O externo debe implementar protecciones:
- **Deny-by-default**: si la tool tiene una allowlist (AllowedPaths, AllowedDomains, AllowedCommands, etc.), un allowlist vacio debe denegar todo, no permitir todo.
- **Path traversal**: para tools que aceptan rutas de archivo, resolver symlinks con `filepath.EvalSymlinks` y validar que el path resuelto este dentro de los paths permitidos. Proteger contra `../` y prefix confusion.
- **SSRF protection**: para tools que hacen HTTP, resolver la IP del dominio antes de conectar y bloquear IPs privadas (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16).
- **Command injection**: para tools que ejecutan comandos, validar sintaxis shell (no permitir pipes `|`, subshells `$()`, redirects `>`, chains `&&`/`||`/`;`) a menos que esten explicitamente permitidos.
- **Rate limiting**: las tools estan sujetas a rate limiting por room via `security.tool_rate_limit` en el config. No se necesita implementar nada en la tool — el registry lo maneja automaticamente.
Referencia de implementaciones: `tools/file/`, `tools/ssh/`, `tools/http/`.
+156
View File
@@ -0,0 +1,156 @@
# Regla: Arreglar/implementar un issue existente
Guia para trabajar en un issue de `dev/issues/` y cerrarlo al terminar.
Usa trunk-based development: siempre trabajar en una rama, nunca en master.
## Inputs — preguntar al usuario si no los da
| Input | Requerido | Ejemplo |
|-------|-----------|---------|
| Numero o nombre del issue | si | `0010`, `0010-access-control` |
## Pasos
### 1. Leer el issue
Abrir `dev/issues/<NNNN>-<slug>.md` y entender:
- **Objetivo**: que se quiere lograr
- **Tareas**: lista de tareas a completar
- **Arquitectura**: archivos afectados, que es puro vs impuro
### 2. Crear rama de trabajo
Ejecutar `/git-branch` con el numero y slug del issue:
```
/git-branch
```
Esto crea la rama `issue/<NNNN>-<slug>` desde master actualizado.
**Nunca trabajar directamente en master.**
### 3. Planificar el trabajo
Crear un plan con `TodoWrite` basado en las tareas del issue. Respetar el orden de fases si el issue las define. **Incluir siempre una tarea de tests.**
### 4. Implementar
- Seguir las tareas del issue en orden
- Respetar **pure core / impure shell**: `pkg/` puro, `shell/` impuro
- Marcar cada tarea como completada en el TodoWrite conforme se avanza
- Compilar frecuentemente: `go build -tags goolm ./...`
- **Commits atómicos por bloque lógico** — no mezclar `feat:` + `test:` en un commit
- **No hacer commits WIP** — nada de "wip", "tmp", "fix fix". Si no hay un bloque lógico completo, no commitear todavía
- Cada commit lleva título corto con prefijo (`feat:`, `fix:`, `test:`, `docs:`, `refactor:`, `chore:`) y cuerpo largo en español explicando qué, por qué e impacto
### 5. Tests — OBLIGATORIO
Toda implementacion debe incluir tests. Antes de cerrar el issue:
- Escribir tests para el codigo nuevo o modificado
- Tests de `pkg/` (puro): tests unitarios directos, sin mocks de I/O
- Tests de `shell/` (impuro): pueden usar mocks/stubs para dependencias externas
- Tests de integracion si el issue lo requiere
Ejecutar:
```bash
go test -tags goolm ./...
```
**No cerrar el issue si los tests no pasan.**
### 6. Feature flags (solo si aplica)
En TBD no existen ramas largas. Para features que no caben en un solo issue/rama, se usan **feature flags**: código completo y testeado que se mergea a master pero desactivado.
**Feature flag ≠ WIP.** Un flag protege código terminado; un WIP es código a medias. Nunca commitear código incompleto.
**Cuándo usar feature flags:**
- Este issue es **parte de una feature multi-issue** (ej: issue 0015a, 0015b, 0015c)
- El cambio tiene **riesgo** y necesita poder desactivarse en producción
- Se quiere **despliegue gradual** (activar para un agente primero, después para todos)
**Cuándo NO usarlos:**
- Issue autocontenido que se completa en una rama → mergear directo, sin flag
- Bug fix, refactor, docs → no necesitan flag
**Al desglosar un issue en sub-issues**, documentar el desglose en el propio archivo del issue:
1. Añadir una seccion `## Desglose multi-issue` en el documento del issue (antes de `## Ejemplo de uso` o al final)
2. Incluir tabla con: sub-issue, rama, alcance, fases cubiertas, estado
3. Incluir checklist de progreso por tarea (marcar `[x]` las completadas, indicar en que sub-issue se hizo)
4. Actualizar el progreso cada vez que se completa una sub-issue
Si aplica, actualizar `dev/feature_flags.json`:
```json
{
"flags": {
"nombre-del-flag": {
"enabled": false,
"issue": "0020",
"description": "Descripcion breve de la feature",
"added": "2026-03-07"
}
}
}
```
Incluir el cambio en el commit correspondiente (no crear commit separado solo para flags).
**Flujo para features multi-issue:**
```
Feature grande (ej: 0015 Telegram)
├── issue/0015a-telegram-types → pkg/ types, flag OFF → merge
├── issue/0015b-telegram-client → shell/ client, flag OFF → merge
├── issue/0015c-telegram-listener → integration, flag OFF → merge
└── issue/0015d-telegram-enable → flag ON, cleanup → merge
```
Cada rama es corta, cada merge es seguro, master nunca se rompe.
### 7. Verificar
- [ ] `go build -tags goolm ./...` compila sin errores
- [ ] `go test -tags goolm ./...` pasa sin errores
- [ ] Todas las tareas del issue estan implementadas
- [ ] El codigo respeta pure core / impure shell
- [ ] Se escribieron tests para el codigo nuevo/modificado
### 8. Cerrar el issue — OBLIGATORIO al terminar
Al completar todas las tareas del issue, ejecutar estos pasos:
#### 8.1. Mover el archivo a completed
```bash
mv dev/issues/<NNNN>-<slug>.md dev/issues/completed/
```
#### 8.2. Actualizar el README
En `dev/issues/README.md`, cambiar la fila del issue:
- **Link**: de `[<NNNN>-<slug>.md](<NNNN>-<slug>.md)` a `[<NNNN>-<slug>.md](completed/<NNNN>-<slug>.md)`
- **Estado**: de `pendiente` a `completado`
### 9. Integrar y publicar
Ejecutar `/git-push` para:
1. Commitear los cambios restantes (cierre de issue, README)
2. Hacer merge --no-ff de la rama a master
3. Push a remoto
4. Eliminar la rama local
El commit de merge tendra formato: `merge: issue/<NNNN>-<slug> — <titulo>`
## Reglas
- **Leer antes de actuar**: siempre leer el issue completo antes de empezar a implementar.
- **Siempre en rama**: nunca trabajar en master. Usar `/git-branch` al inicio.
- **Siempre tests**: toda implementacion debe tener tests. No cerrar sin tests.
- **No saltear tareas**: implementar todas las tareas del issue, no solo las faciles.
- **Cerrar siempre**: nunca dejar un issue implementado sin moverlo a `completed/`.
- **Pure core / impure shell**: toda implementacion debe respetar el patron.
- **Compilar con goolm**: siempre usar `-tags goolm` en build y test.
- **Feature flags**: solo cuando el issue es parte de algo mayor. No es obligatorio en cada fix.
+70
View File
@@ -0,0 +1,70 @@
# Reglas del proyecto
Guias operativas para LLMs que trabajan en este codebase. Cada regla describe como ejecutar una tarea especifica respetando la arquitectura y convenciones del proyecto.
## Reglas disponibles
| Regla | Archivo | Cuando aplicarla |
|-------|---------|------------------|
| **Crear agente** | [create_agent.md](create_agent.md) | Al crear un nuevo bot/agente Matrix completo |
| **Crear herramienta** | [create_tool.md](create_tool.md) | Al añadir una nueva tool para LLM function calling |
| **Crear comando** | [create_command.md](create_command.md) | Al añadir un comando directo (!xxx) a un agente |
| **Crear skill** | [create_skill.md](create_skill.md) | Al crear una nueva skill (flujo multi-paso declarativo) |
| **Crear issue** | [create_issue.md](create_issue.md) | Al crear un nuevo issue/feature request en `dev/issues/` |
| **Arreglar issue** | [fix_issue.md](fix_issue.md) | Al implementar/arreglar un issue existente de `dev/issues/` |
## Cuando consultar las reglas
- **Crear agente**: cuando el usuario pida crear un nuevo bot, agente, o asistente. Incluye la estructura de archivos, reglas puras, config YAML, system prompt y registro en el launcher.
- **Crear herramienta**: cuando el usuario pida añadir una nueva herramienta/tool al sistema. Incluye el patron Def (puro) + Exec (impuro), registro en runtime.go y habilitacion en config.
- **Crear comando**: cuando el usuario pida añadir un comando directo (!xxx) a un agente. Los comandos se resuelven sin pasar por reglas ni LLM.
- **Crear skill**: cuando el usuario pida añadir una skill (flujo multi-paso declarativo). Las skills combinan tools, logica condicional y conocimiento de dominio en un SKILL.md con recursos opcionales.
- **Crear issue**: cuando el usuario pida crear un nuevo issue, feature request o task. Usa el template en `.claude/templates/issue.md`.
- **Arreglar issue**: cuando el usuario pida implementar, arreglar o trabajar en un issue existente. Incluye crear rama (`/git-branch`), implementar las tareas con tests, cerrar el issue, e integrar a master (`/git-push`).
## Flujo de desarrollo — Trunk-based development (TBD)
El proyecto usa TBD estricto. **master** es el unico branch estable y siempre deployable. **Nunca trabajar directamente en master.**
```
master (trunk) ← siempre deployable
└── issue/<NNNN>-<slug> ← rama efimera (horas, no dias)
├── commit: feat: ...
├── commit: test: ...
└── commit: docs: ...
merge --no-ff → master → push → delete branch
```
1. `/git-branch` — crea rama `issue/<NNNN>-<slug>` desde master actualizado
2. Implementar con commits atomicos por bloque logico (no WIP, no mezclar tipos)
3. `/git-push` — tests → merge `--no-ff` a master → push → eliminar rama
### Commits
- Cada commit es **atomico por bloque logico** con prefijo: `feat:`, `fix:`, `test:`, `docs:`, `refactor:`, `chore:`
- Titulo corto + cuerpo largo en español
- **No WIP**: nunca commitear "wip", "tmp", codigo a medias
- **No squash**: `--no-ff` preserva commits; `git log --first-parent` da vista limpia
- **No rebase -i**: commits limpios desde el inicio
### Feature flags (para features multi-issue)
Cuando una feature no cabe en una sola rama corta, desglosar en sub-issues. Cada sub-issue mergea codigo **completo y testeado** protegido por un feature flag (desactivado). **Feature flag ≠ WIP** — un flag protege codigo terminado, no codigo a medias.
Archivo: `dev/feature_flags.json`
### Comandos
- `/git-branch` — crear rama de trabajo (`.claude/commands/git-branch.md`)
- `/git-push` — integrar rama a master y publicar (`.claude/commands/git-push.md`)
Filosofia completa documentada en `CLAUDE.md` seccion "Trunk-based development".
## Principio general
Todas las reglas respetan el patron **pure core / impure shell**:
- `pkg/` es puro — nunca añadir side effects
- `shell/` es impuro — todo I/O va aqui
- `agents/` compone ambos — reglas puras + ensamblado con shell
- `tools/` sigue el mismo patron: `Def` (datos puros) + `Exec` (funcion impura)