diff --git a/agents/_template/agent.go b/agents/_template/agent.go index 5b5c706..1f6ee40 100644 --- a/agents/_template/agent.go +++ b/agents/_template/agent.go @@ -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 { diff --git a/agents/asistente-2/agent.go b/agents/asistente-2/agent.go index de69ea3..75547c8 100644 --- a/agents/asistente-2/agent.go +++ b/agents/asistente-2/agent.go @@ -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 { diff --git a/agents/assistant-bot/agent.go b/agents/assistant-bot/agent.go index 203dfd6..aff28d7 100644 --- a/agents/assistant-bot/agent.go +++ b/agents/assistant-bot/agent.go @@ -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 { diff --git a/agents/meteorologo/agent.go b/agents/meteorologo/agent.go index 7b7d649..d221ed6 100644 --- a/agents/meteorologo/agent.go +++ b/agents/meteorologo/agent.go @@ -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{ diff --git a/agents/registry.go b/agents/registry.go new file mode 100644 index 0000000..c941a58 --- /dev/null +++ b/agents/registry.go @@ -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) +} diff --git a/cmd/launcher/main.go b/cmd/launcher/main.go index f750b58..f080f17 100644 --- a/cmd/launcher/main.go +++ b/cmd/launcher/main.go @@ -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()