docs: añadir política create_command y actualizar docs

Nueva política create_command.md que documenta cómo añadir comandos
directos (!xxx) a un agente usando RegisterCommand.

Actualiza create_agent.md para no usar reglas en comandos (ahora se
gestionan via RegisterCommand). Actualiza index.md y CLAUDE.md para
referenciar la nueva política.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 01:40:01 +00:00
parent 62783f3655
commit e2f14e7abb
4 changed files with 157 additions and 12 deletions
+1
View File
@@ -68,6 +68,7 @@ Ver índice completo en `.claude/policies/index.md`.
|----------|------------------|
| `.claude/policies/create_agent.md` | Al crear un nuevo bot/agente Matrix |
| `.claude/policies/create_tool.md` | Al añadir una nueva tool para function calling |
| `.claude/policies/create_command.md` | Al añadir un comando directo (!xxx) a un agente |
Documentación detallada para humanos en `docs/creating-agents.md`.
+5 -12
View File
@@ -40,16 +40,9 @@ import "github.com/enmanuel/agents/pkg/decision"
func Rules() []decision.Rule {
return []decision.Rule{
// Any DM or mention → LLM
{
Name: "help",
Match: decision.MatchCommand("help"),
Actions: []decision.Action{{
Kind: decision.ActionKindReply,
Reply: &decision.ReplyAction{Content: "<descripción de capacidades>"},
}},
},
{
Name: "llm-fallback",
Name: "llm-all",
Match: func(ctx decision.MessageContext) bool {
return ctx.IsDirectMsg || ctx.IsMention
},
@@ -65,10 +58,10 @@ func Rules() []decision.Rule {
**Reglas estrictas:**
- **PURO**: solo imports de `pkg/decision`, cero I/O, cero side effects
- Package name = ID sin guiones ni `_bot` (e.g. `monitor-bot``package monitor`)
- Reglas se evalúan en orden — específicas arriba, catch-all al final
- El catch-all DEBE cubrir `ctx.IsDirectMsg || ctx.IsMention`
- **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 `!`)
Para añadir reglas extra, insertar antes del catch-all. Tipos de acción disponibles:
Tipos de acción disponibles:
- `ActionKindReply` — respuesta estática (con `ReplyAction{Content: "..."}`)
- `ActionKindLLM` — pasa al LLM (con `LLMAction{}`)
+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`
+2
View File
@@ -8,11 +8,13 @@ Guías operativas para LLMs que trabajan en este codebase. Cada política descri
|----------|---------|------------------|
| **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 |
## Cuándo consultar las políticas
- **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 patrón Def (puro) + Exec (impuro), registro en runtime.go y habilitación 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.
## Principio general