# 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`
