Files
agents_and_robots/.claude/rules/create_command.md
T
egutierrez bd0c8c0dd3 refactor: mover runtime Go de agents/ a devagents/
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>
2026-04-09 21:19:25 +00:00

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

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

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:

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

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