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:
@@ -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/`
|
||||
@@ -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`
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/`.
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user