bd0c8c0dd3
agents/ ahora solo contiene carpetas de agentes (config, reglas, prompts). El runtime (Agent, Robot, Runner, registry, handler, commands, llm, memory) vive en devagents/ como package devagents. Cambios: - git mv agents/*.go → devagents/*.go - package agents → package devagents en todos los archivos movidos - Actualizar imports en agents/*/agent.go, cmd/launcher/, dev-scripts/ - Actualizar docs: CLAUDE.md, rules/, docs/e2ee.md, issues pendientes Build y tests pasan sin errores. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
168 lines
5.8 KiB
Markdown
168 lines
5.8 KiB
Markdown
# 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 `devagents.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 `devagents.New()` y antes de `wg.Add(1)`:
|
|
|
|
```go
|
|
a, err := devagents.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.
|
|
|
|
## Comandos sin prefijo (robots)
|
|
|
|
Los robots (`agent.type: robot`) pueden configurarse con `command_prefix: ""` para aceptar comandos sin el prefijo `!`. En este modo:
|
|
|
|
- Todo mensaje se trata como un posible comando (el primer token es el nombre del comando)
|
|
- `!help` sigue funcionando por retrocompatibilidad (el `!` se quita automaticamente)
|
|
- Si el comando no existe, se responde "Comando desconocido" con sugerencia de usar `help`
|
|
- El `!help` del robot muestra los comandos sin prefijo cuando `command_prefix` es vacio
|
|
|
|
```yaml
|
|
# config.yaml del robot
|
|
matrix:
|
|
filters:
|
|
command_prefix: "" # sin prefijo — todo mensaje es potencial comando
|
|
```
|
|
|
|
**Solo para robots.** Los agentes con LLM necesitan `command_prefix: "!"` para distinguir comandos de mensajes naturales.
|
|
|
|
## 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`
|