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>
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!helpspec.Usage: ejemplo de uso que aparece en!helpspec.Hidden: si estrue, no aparece en!helphandler: recibe(ctx, msgCtx)y devuelve un string
El handler tiene acceso a:
msgCtx.Args— argumentos parseados porstrings.Fields(incluye el nombre del comando enArgs[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)
!helpsigue funcionando por retrocompatibilidad (el!se quita automaticamente)- Si el comando no existe, se responde "Comando desconocido" con sugerencia de usar
help - El
!helpdel robot muestra los comandos sin prefijo cuandocommand_prefixes 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_receivedycommand_executeda nivel INFO. msgCtx.Argspara!deploy prodcontiene["prod"](sin el nombre del comando).
Verificación
go build -tags goolm ./...compila!helpmuestra el comando nuevo en la sección "Comandos del agente"- Enviar el comando por Matrix produce la respuesta esperada
- En logs aparece
command_receivedycommand_executed