Files
agents_and_robots/.claude/rules/create_command.md
T
egutierrez 9deffc12c9 refactor: migrar policies/ a rules/ y añadir create_issue
Se renombra .claude/policies/ a .claude/rules/ para usar terminología
más clara y consistente. Se añade la nueva regla create_issue.md con
guía completa para crear issues en dev/issues/, incluyendo el template
en .claude/templates/issue.md. El índice (index.md) se actualiza con
la nueva regla.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:41:08 +00:00

5.0 KiB

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>/:

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):

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:

## Comandos disponibles
- `!deploy <env>` — Despliega al entorno indicado
- `!help` — Lista todos los comandos

API de registro

// 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