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