merge: issue/0028-decouple-launcher — auto-discovery de agentes via init()
# Conflicts: # dev/issues/README.md
This commit is contained in:
@@ -36,7 +36,14 @@ Template base (generado por el scaffold):
|
||||
```go
|
||||
package <pkgname> // sin guiones: "monitor-bot" → package monitor (strip hyphens, strip _bot)
|
||||
|
||||
import "github.com/enmanuel/agents/pkg/decision"
|
||||
import (
|
||||
"github.com/enmanuel/agents/agents"
|
||||
"github.com/enmanuel/agents/pkg/decision"
|
||||
)
|
||||
|
||||
func init() {
|
||||
agents.Register("<agent-id>", Rules)
|
||||
}
|
||||
|
||||
func Rules() []decision.Rule {
|
||||
return []decision.Rule{
|
||||
@@ -56,7 +63,8 @@ func Rules() []decision.Rule {
|
||||
```
|
||||
|
||||
**Reglas estrictas:**
|
||||
- **PURO**: solo imports de `pkg/decision`, cero I/O, cero side effects
|
||||
- **PURO**: solo imports de `pkg/decision` y `agents` (para Register), cero I/O, cero side effects
|
||||
- **Auto-registro**: cada agente se registra via `init()` con `agents.Register("<agent-id>", Rules)`
|
||||
- Package name = ID sin guiones ni `_bot` (e.g. `monitor-bot` → `package monitor`)
|
||||
- **No usar reglas para comandos** (`!help`, `!ping`, etc.) — los comandos se gestionan via `RegisterCommand` (ver policy `create_command.md`)
|
||||
- Las reglas solo aplican a mensajes normales (sin prefijo `!`)
|
||||
@@ -139,18 +147,14 @@ Ejemplo de referencia: `agents/asistente-2/prompts/system.md`
|
||||
El script `new-agent.sh` (ejecutado por `create-full.sh`) hace esto automáticamente.
|
||||
Si falla, hacer manualmente:
|
||||
|
||||
**Import** (después de los imports de agentes existentes):
|
||||
**Blank import** (en la sección de blank imports de agentes):
|
||||
```go
|
||||
<pkg>agent "github.com/enmanuel/agents/agents/<agent-id>"
|
||||
_ "github.com/enmanuel/agents/agents/<agent-id>"
|
||||
```
|
||||
|
||||
**rulesRegistry** (dentro del map):
|
||||
```go
|
||||
"<agent-id>": <pkg>agent.Rules,
|
||||
```
|
||||
|
||||
El `<pkg>` es el package name del agent.go (sin guiones, sin `_bot`).
|
||||
**El ID en rulesRegistry DEBE coincidir exactamente con `agent.id` en config.yaml.**
|
||||
Las reglas se registran automáticamente via `init()` en el paquete del agente.
|
||||
No se necesita editar ningún map ni registry manualmente.
|
||||
**El ID en `agents.Register()` DEBE coincidir exactamente con `agent.id` en config.yaml.**
|
||||
|
||||
## Convención de env vars — REGLA CRÍTICA
|
||||
|
||||
@@ -170,7 +174,7 @@ Checklist a verificar antes de considerar el agente listo:
|
||||
- [ ] `go build -tags goolm ./...` compila sin errores
|
||||
- [ ] `agents/<id>/agent.go` exporta `Rules()` y es puro (sin I/O)
|
||||
- [ ] `agents/<id>/config.yaml` tiene `agent.id` = nombre del directorio
|
||||
- [ ] `cmd/launcher/main.go` tiene import + entry en rulesRegistry con el mismo ID
|
||||
- [ ] `cmd/launcher/main.go` tiene blank import del paquete del agente
|
||||
- [ ] `.env` contiene: `MATRIX_TOKEN_<NORM>`, `MATRIX_PASSWORD_<NORM>`, `PICKLE_KEY_<NORM>`, `SSSS_RECOVERY_KEY_<NORM>`
|
||||
- [ ] `prompts/system.md` tiene contenido real (no el stub)
|
||||
- [ ] `prompts/system.md` incluye la seccion de seguridad anti-injection (de `.claude/templates/security-prompt.md`)
|
||||
@@ -204,7 +208,7 @@ tail -f run/launcher.log
|
||||
|
||||
- **Nunca** side effects en `agent.go`
|
||||
- **Siempre** compilar con `-tags goolm`
|
||||
- **Siempre** que `agent.id` coincida entre config.yaml, rulesRegistry y directorio
|
||||
- **Siempre** que `agent.id` coincida entre config.yaml, `agents.Register()` y directorio
|
||||
- **No** crear `data/` manualmente — se auto-genera
|
||||
- **No** commitear tokens ni passwords
|
||||
- **No** compartir crypto stores entre agentes
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
// Package _template es un agente plantilla (no lanzable).
|
||||
// Sirve como referencia canonica para crear nuevos agentes.
|
||||
// Al crear un nuevo agente, new-agent.sh reemplaza _template y AGENT_ID_PLACEHOLDER.
|
||||
package _template
|
||||
|
||||
import "github.com/enmanuel/agents/pkg/decision"
|
||||
import (
|
||||
"github.com/enmanuel/agents/agents"
|
||||
"github.com/enmanuel/agents/pkg/decision"
|
||||
)
|
||||
|
||||
func init() {
|
||||
agents.Register("AGENT_ID_PLACEHOLDER", Rules)
|
||||
}
|
||||
|
||||
// Rules devuelve las reglas de este agente (vacio para el template).
|
||||
func Rules() []decision.Rule {
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
package asistente2
|
||||
|
||||
import (
|
||||
"github.com/enmanuel/agents/agents"
|
||||
"github.com/enmanuel/agents/pkg/decision"
|
||||
)
|
||||
|
||||
func init() {
|
||||
agents.Register("asistente-2", Rules)
|
||||
}
|
||||
|
||||
// Rules returns the decision rules for the asistente-2 bot.
|
||||
// Note: !help is now handled by the built-in command system.
|
||||
func Rules() []decision.Rule {
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
package assistant
|
||||
|
||||
import (
|
||||
"github.com/enmanuel/agents/agents"
|
||||
"github.com/enmanuel/agents/pkg/decision"
|
||||
)
|
||||
|
||||
func init() {
|
||||
agents.Register("assistant-bot", Rules)
|
||||
}
|
||||
|
||||
// Rules returns the decision rules for the assistant bot.
|
||||
// Note: !help is now handled by the built-in command system.
|
||||
func Rules() []decision.Rule {
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
package meteorologo
|
||||
|
||||
import (
|
||||
"github.com/enmanuel/agents/agents"
|
||||
"github.com/enmanuel/agents/pkg/decision"
|
||||
)
|
||||
|
||||
func init() {
|
||||
agents.Register("meteorologo", Rules)
|
||||
}
|
||||
|
||||
// Rules returns the decision rules for the meteorologo bot.
|
||||
func Rules() []decision.Rule {
|
||||
return []decision.Rule{
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Package agents provides a global registry for agent rule factories.
|
||||
//
|
||||
// Each agent package self-registers via init() using Register.
|
||||
// The launcher retrieves rules via GetRules without importing agent
|
||||
// packages explicitly (only blank imports are needed).
|
||||
package agents
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/enmanuel/agents/pkg/decision"
|
||||
)
|
||||
|
||||
// RulesFunc is a factory that returns the decision rules for an agent.
|
||||
type RulesFunc func() []decision.Rule
|
||||
|
||||
var (
|
||||
registryMu sync.RWMutex
|
||||
registry = make(map[string]RulesFunc)
|
||||
)
|
||||
|
||||
// Register adds a rule factory for the given agent ID.
|
||||
// Intended to be called from init() in each agent package.
|
||||
// Panics if the same ID is registered twice (catches copy-paste errors early).
|
||||
func Register(id string, fn RulesFunc) {
|
||||
registryMu.Lock()
|
||||
defer registryMu.Unlock()
|
||||
|
||||
if _, exists := registry[id]; exists {
|
||||
panic("agents.Register: duplicate agent id: " + id)
|
||||
}
|
||||
registry[id] = fn
|
||||
}
|
||||
|
||||
// GetRules returns the rule factory for the given agent ID.
|
||||
// Returns nil if no rules are registered (the agent is command-only).
|
||||
func GetRules(id string) RulesFunc {
|
||||
registryMu.RLock()
|
||||
defer registryMu.RUnlock()
|
||||
return registry[id]
|
||||
}
|
||||
|
||||
// RegisteredIDs returns a sorted list of all registered agent IDs.
|
||||
// Useful for debugging and diagnostics.
|
||||
func RegisteredIDs() []string {
|
||||
registryMu.RLock()
|
||||
defer registryMu.RUnlock()
|
||||
|
||||
ids := make([]string, 0, len(registry))
|
||||
for id := range registry {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// resetRegistry clears all registrations (for testing only).
|
||||
func resetRegistry() {
|
||||
registryMu.Lock()
|
||||
defer registryMu.Unlock()
|
||||
registry = make(map[string]RulesFunc)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package agents
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/enmanuel/agents/pkg/decision"
|
||||
)
|
||||
|
||||
func TestRegisterAndGetRules(t *testing.T) {
|
||||
resetRegistry()
|
||||
|
||||
called := false
|
||||
fn := func() []decision.Rule {
|
||||
called = true
|
||||
return []decision.Rule{{Name: "test-rule"}}
|
||||
}
|
||||
|
||||
Register("test-agent", fn)
|
||||
|
||||
got := GetRules("test-agent")
|
||||
if got == nil {
|
||||
t.Fatal("GetRules returned nil for registered agent")
|
||||
}
|
||||
|
||||
rules := got()
|
||||
if !called {
|
||||
t.Error("rule factory was not called")
|
||||
}
|
||||
if len(rules) != 1 || rules[0].Name != "test-rule" {
|
||||
t.Errorf("unexpected rules: %+v", rules)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRulesMissing(t *testing.T) {
|
||||
resetRegistry()
|
||||
|
||||
got := GetRules("nonexistent")
|
||||
if got != nil {
|
||||
t.Errorf("expected nil for unregistered agent, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterDuplicatePanics(t *testing.T) {
|
||||
resetRegistry()
|
||||
|
||||
fn := func() []decision.Rule { return nil }
|
||||
Register("dup-agent", fn)
|
||||
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
t.Fatal("expected panic on duplicate registration, got none")
|
||||
}
|
||||
msg, ok := r.(string)
|
||||
if !ok {
|
||||
t.Fatalf("expected string panic, got %T: %v", r, r)
|
||||
}
|
||||
if msg != "agents.Register: duplicate agent id: dup-agent" {
|
||||
t.Errorf("unexpected panic message: %s", msg)
|
||||
}
|
||||
}()
|
||||
|
||||
Register("dup-agent", fn)
|
||||
}
|
||||
|
||||
func TestRegisteredIDs(t *testing.T) {
|
||||
resetRegistry()
|
||||
|
||||
Register("charlie", func() []decision.Rule { return nil })
|
||||
Register("alpha", func() []decision.Rule { return nil })
|
||||
Register("bravo", func() []decision.Rule { return nil })
|
||||
|
||||
ids := RegisteredIDs()
|
||||
sort.Strings(ids)
|
||||
|
||||
expected := []string{"alpha", "bravo", "charlie"}
|
||||
if len(ids) != len(expected) {
|
||||
t.Fatalf("expected %d ids, got %d: %v", len(expected), len(ids), ids)
|
||||
}
|
||||
for i, id := range ids {
|
||||
if id != expected[i] {
|
||||
t.Errorf("id[%d] = %q, want %q", i, id, expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetRegistry(t *testing.T) {
|
||||
resetRegistry()
|
||||
|
||||
Register("temp", func() []decision.Rule { return nil })
|
||||
if GetRules("temp") == nil {
|
||||
t.Fatal("expected registered agent")
|
||||
}
|
||||
|
||||
resetRegistry()
|
||||
|
||||
if GetRules("temp") != nil {
|
||||
t.Error("expected nil after reset")
|
||||
}
|
||||
if len(RegisteredIDs()) != 0 {
|
||||
t.Error("expected empty registry after reset")
|
||||
}
|
||||
}
|
||||
+11
-14
@@ -20,9 +20,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/enmanuel/agents/agents"
|
||||
assistantagent "github.com/enmanuel/agents/agents/assistant-bot"
|
||||
asistente2agent "github.com/enmanuel/agents/agents/asistente-2"
|
||||
meteorologoagent "github.com/enmanuel/agents/agents/meteorologo"
|
||||
"github.com/enmanuel/agents/internal/config"
|
||||
"github.com/enmanuel/agents/pkg/decision"
|
||||
"github.com/enmanuel/agents/pkg/orchestration"
|
||||
@@ -31,15 +28,12 @@ import (
|
||||
agentlog "github.com/enmanuel/agents/shell/logger"
|
||||
orchshell "github.com/enmanuel/agents/shell/orchestration"
|
||||
shellsecurity "github.com/enmanuel/agents/shell/security"
|
||||
)
|
||||
|
||||
// rulesRegistry maps agent IDs to their rule factories.
|
||||
// Add a new entry here when you create a new agent package.
|
||||
var rulesRegistry = map[string]func() []decision.Rule{
|
||||
"assistant-bot": assistantagent.Rules,
|
||||
"asistente-2": asistente2agent.Rules,
|
||||
"meteorologo": meteorologoagent.Rules,
|
||||
}
|
||||
// Blank imports: each agent self-registers its rules via init().
|
||||
_ "github.com/enmanuel/agents/agents/assistant-bot"
|
||||
_ "github.com/enmanuel/agents/agents/asistente-2"
|
||||
_ "github.com/enmanuel/agents/agents/meteorologo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
@@ -289,10 +283,13 @@ func startOrchestrator(agentBus *bus.Bus, logger *slog.Logger) (*orchHandle, err
|
||||
return &orchHandle{orchestrator: orch, cfg: cfg}, nil
|
||||
}
|
||||
|
||||
// rulesFor retrieves the rule factory for the given agent ID from the
|
||||
// global registry (populated by init() in each agent package).
|
||||
// Returns nil if no rules are registered (command-only bot).
|
||||
func rulesFor(agentID string, logger *slog.Logger) []decision.Rule {
|
||||
factory, ok := rulesRegistry[agentID]
|
||||
if !ok {
|
||||
logger.Warn("no rules registered for agent, using empty ruleset", "id", agentID)
|
||||
factory := agents.GetRules(agentID)
|
||||
if factory == nil {
|
||||
logger.Warn("no rules registered for agent, using empty ruleset (command-only)", "id", agentID)
|
||||
return nil
|
||||
}
|
||||
return factory()
|
||||
|
||||
@@ -310,6 +310,7 @@ YAML
|
||||
cp "$TEMPLATE/agent.go" "$DIR/agent.go"
|
||||
sed -i "s/_template/$PACKAGE/g" "$DIR/agent.go"
|
||||
sed -i "s/Package _template/Package $PACKAGE/g" "$DIR/agent.go"
|
||||
sed -i "s/AGENT_ID_PLACEHOLDER/$ID/g" "$DIR/agent.go"
|
||||
ok "agent.go creado desde template"
|
||||
|
||||
# ── Copiar prompts/system.md desde template y personalizar ───────────────
|
||||
@@ -320,21 +321,21 @@ ok "prompts/system.md creado desde template"
|
||||
ok "Scaffold creado en $DIR/"
|
||||
echo ""
|
||||
|
||||
# ── Actualizar cmd/launcher/main.go ───────────────────────────────────────
|
||||
# ── Actualizar cmd/launcher/main.go — añadir blank import ────────────────
|
||||
LAUNCHER="cmd/launcher/main.go"
|
||||
BLANK_IMPORT="_ \"github.com/enmanuel/agents/agents/$ID\""
|
||||
|
||||
if grep -q "\"$ID\":" "$LAUNCHER" 2>/dev/null; then
|
||||
warn "$ID ya está en rulesRegistry de $LAUNCHER — saltando"
|
||||
if grep -q "agents/$ID\"" "$LAUNCHER" 2>/dev/null; then
|
||||
warn "$ID ya tiene blank import en $LAUNCHER — saltando"
|
||||
else
|
||||
TAB=$'\t'
|
||||
IMPORT_LINE="${TAB}${PACKAGE}agent \"github.com/enmanuel/agents/agents/$ID\""
|
||||
REGISTRY_LINE="${TAB}\"$ID\": ${PACKAGE}agent.Rules,"
|
||||
IMPORT_LINE="${TAB}${BLANK_IMPORT}"
|
||||
|
||||
# Insertar import después del último import agents/agents/*
|
||||
# Insertar blank import después del último blank import de agents/
|
||||
if awk -v new_import="$IMPORT_LINE" '
|
||||
{
|
||||
lines[NR] = $0
|
||||
if ($0 ~ /[a-z_]+agent "github\.com\/enmanuel\/agents\/agents\/[^"]+"/)
|
||||
if ($0 ~ /_ "github\.com\/enmanuel\/agents\/agents\//)
|
||||
last_import = NR
|
||||
}
|
||||
END {
|
||||
@@ -346,24 +347,11 @@ else
|
||||
}
|
||||
' "$LAUNCHER" > /tmp/_launcher_tmp; then
|
||||
mv /tmp/_launcher_tmp "$LAUNCHER"
|
||||
ok "Import añadido en $LAUNCHER"
|
||||
ok "Blank import añadido en $LAUNCHER"
|
||||
else
|
||||
warn "No se pudo insertar el import automáticamente — añádelo manualmente:"
|
||||
warn "No se pudo insertar el blank import — añádelo manualmente:"
|
||||
echo -e " ${GRN}${IMPORT_LINE}${RST}"
|
||||
fi
|
||||
|
||||
# Insertar entry en rulesRegistry antes del cierre }
|
||||
if awk -v new_entry="$REGISTRY_LINE" '
|
||||
/^var rulesRegistry/ { in_reg = 1 }
|
||||
in_reg && /^\}/ { print new_entry; in_reg = 0 }
|
||||
{ print }
|
||||
' "$LAUNCHER" > /tmp/_launcher_tmp; then
|
||||
mv /tmp/_launcher_tmp "$LAUNCHER"
|
||||
ok "Registry entry añadida en $LAUNCHER"
|
||||
else
|
||||
warn "No se pudo insertar el registry entry — añádelo manualmente:"
|
||||
echo -e " ${GRN}${REGISTRY_LINE}${RST}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
@@ -38,7 +38,7 @@ afectados y notas de implementacion.
|
||||
| 25 | Catálogo cron + scaffolder | [0025-cron-scaffolder.md](completed/0025-cron-scaffolder.md) | completado |
|
||||
| 26 | Refactorizar runtime.go | [0026-split-runtime.md](0026-split-runtime.md) | pendiente |
|
||||
| 27 | Limpiar config schema | [0027-prune-config-schema.md](completed/0027-prune-config-schema.md) | completado |
|
||||
| 28 | Desacoplar launcher | [0028-decouple-launcher.md](0028-decouple-launcher.md) | pendiente |
|
||||
| 28 | Desacoplar launcher | [0028-decouple-launcher.md](completed/0028-decouple-launcher.md) | completado |
|
||||
| 29 | Tests para runtime y config | [0029-core-tests.md](0029-core-tests.md) | pendiente |
|
||||
| 30 | Separacion Robot vs Agente | [0030-robot-vs-agent.md](0030-robot-vs-agent.md) | pendiente |
|
||||
| 31 | Expandir tools/file/ | [0031-expand-file-tools.md](0031-expand-file-tools.md) | pendiente |
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# 0028 — Desacoplar launcher del registro estatico de agentes
|
||||
|
||||
## Objetivo
|
||||
|
||||
Eliminar la necesidad de editar `cmd/launcher/main.go` cada vez que se añade un agente. Reemplazar el `rulesRegistry` hard-coded con auto-discovery basado en la convencion de directorios.
|
||||
|
||||
## Contexto
|
||||
|
||||
- Actualmente `cmd/launcher/main.go` importa cada paquete de agente explicitamente:
|
||||
```go
|
||||
import (
|
||||
assistantagent "github.com/enmanuel/agents/agents/assistant-bot"
|
||||
asistente2agent "github.com/enmanuel/agents/agents/asistente-2"
|
||||
)
|
||||
var rulesRegistry = map[string]func() []decision.Rule{...}
|
||||
```
|
||||
- Cada agente nuevo requiere: añadir import + añadir entrada al map + recompilar
|
||||
- El script `dev-scripts/agent/new-agent.sh` ya modifica el launcher automaticamente, pero es fragil (sed sobre codigo Go)
|
||||
- Contradiccion: el launcher hace glob de `agents/*/config.yaml` para descubrir configs, pero luego necesita imports estaticos para las reglas
|
||||
|
||||
## Arquitectura
|
||||
|
||||
```
|
||||
agents/registry.go NEW → registro global de reglas (init-based)
|
||||
agents/<id>/agent.go → cada agente se auto-registra via init()
|
||||
cmd/launcher/main.go → eliminar rulesRegistry, usar agents.GetRules(id)
|
||||
```
|
||||
|
||||
### Patron pure core / impure shell
|
||||
|
||||
- `pkg/` — sin cambios
|
||||
- `shell/` — sin cambios
|
||||
- `agents/` — nuevo registry global + init() en cada agente
|
||||
- `cmd/launcher/` — simplificacion
|
||||
|
||||
## Tareas
|
||||
|
||||
### Fase 1: Crear registry de reglas
|
||||
|
||||
- [ ] **1.1** Crear `agents/registry.go` con `Register(id, rulesFn)` y `GetRules(id)`
|
||||
- [ ] **1.2** Usar sync.Mutex o sync.Map para seguridad en init()
|
||||
|
||||
### Fase 2: Migrar agentes a auto-registro
|
||||
|
||||
- [ ] **2.1** En `agents/assistant-bot/agent.go` añadir `func init() { agents.Register("assistant-bot", Rules) }`
|
||||
- [ ] **2.2** Repetir para `asistente-2` y `meteorologo`
|
||||
- [ ] **2.3** Actualizar `agents/_template/agent.go` con el patron init()
|
||||
|
||||
### Fase 3: Simplificar launcher
|
||||
|
||||
- [ ] **3.1** Eliminar imports explicitos de agentes en `cmd/launcher/main.go`
|
||||
- [ ] **3.2** Añadir blank import: `_ "github.com/enmanuel/agents/agents/assistant-bot"` (etc.)
|
||||
- [ ] **3.3** Reemplazar `rulesRegistry[id]` con `agents.GetRules(id)`
|
||||
- [ ] **3.4** Si no hay reglas registradas para un agent id, log warning y usar reglas vacias (command-only bot)
|
||||
|
||||
### Fase 4: Actualizar scripts
|
||||
|
||||
- [ ] **4.1** Simplificar `dev-scripts/agent/new-agent.sh` — ya no necesita editar el map, solo añadir blank import
|
||||
- [ ] **4.2** Actualizar `.claude/rules/create_agent.md` con el nuevo patron
|
||||
|
||||
### Fase 5: Tests
|
||||
|
||||
- [ ] **5.1** Test para `agents/registry.go` (register, get, get-missing)
|
||||
- [ ] **5.2** `go build -tags goolm ./...` compila
|
||||
- [ ] **5.3** `go test -tags goolm ./...` pasa
|
||||
|
||||
### Fase 6: Cleanup
|
||||
|
||||
- [ ] **6.1** Actualizar `CLAUDE.md` seccion sobre registro en launcher
|
||||
- [ ] **6.2** Eliminar codigo muerto del launcher
|
||||
|
||||
---
|
||||
|
||||
## Ejemplo de uso
|
||||
|
||||
Antes (crear agente):
|
||||
```go
|
||||
// cmd/launcher/main.go — editar manualmente
|
||||
import newagent "github.com/enmanuel/agents/agents/new-bot"
|
||||
var rulesRegistry = map[string]func() []decision.Rule{
|
||||
"new-bot": newagent.Rules, // añadir esta linea
|
||||
}
|
||||
```
|
||||
|
||||
Despues:
|
||||
```go
|
||||
// agents/new-bot/agent.go — auto-registro
|
||||
func init() {
|
||||
agents.Register("new-bot", Rules)
|
||||
}
|
||||
|
||||
// cmd/launcher/main.go — solo blank import
|
||||
import _ "github.com/enmanuel/agents/agents/new-bot"
|
||||
```
|
||||
|
||||
## Decisiones de diseno
|
||||
|
||||
- **init() + blank import**: patron estandar en Go (database/sql drivers, image codecs). Simple y familiar
|
||||
- **Blank imports en launcher**: siguen siendo estaticos en el codigo, pero son una linea trivial sin logica. El script de scaffolding puede añadirla sin riesgo de romper sintaxis Go
|
||||
- **No plugin system dinamico**: Go no tiene plugins portables. init() es el mecanismo idomatic
|
||||
|
||||
## Prerequisitos
|
||||
|
||||
- Ninguno (puede hacerse independiente de otros issues)
|
||||
|
||||
## Riesgos
|
||||
|
||||
- **Orden de init()**: Go garantiza init() dentro de un paquete, pero no entre paquetes. Mitigacion: el registro es un map simple, el orden no importa
|
||||
- **Olvidar blank import**: si no se añade el blank import, el agente no se registra y el launcher lo trata como command-only. Mitigacion: el script de scaffolding lo añade automaticamente
|
||||
Reference in New Issue
Block a user