// Command launcher starts one or more agents from their config files. package main import ( "context" "log/slog" "os" "os/signal" "path/filepath" "sync" "syscall" "github.com/spf13/cobra" "github.com/enmanuel/agents/agents" "github.com/enmanuel/agents/internal/config" "github.com/enmanuel/agents/pkg/decision" ) func main() { var configPaths []string var logLevel string root := &cobra.Command{ Use: "launcher", Short: "Start Matrix agents from config files", RunE: func(cmd *cobra.Command, args []string) error { level := slog.LevelInfo if logLevel == "debug" { level = slog.LevelDebug } logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level})) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() var wg sync.WaitGroup for _, path := range configPaths { path := path // capture cfg, err := config.Load(path) if err != nil { logger.Error("failed to load config", "path", path, "err", err) continue } if !cfg.Agent.Enabled { logger.Info("agent disabled, skipping", "id", cfg.Agent.ID) continue } // Load agent-specific rules (extend here with your own rule builders) rules := loadRulesForAgent(cfg) agent, err := agents.New(cfg, rules, logger.With("agent", cfg.Agent.ID)) if err != nil { logger.Error("failed to create agent", "id", cfg.Agent.ID, "err", err) continue } wg.Add(1) go func() { defer wg.Done() if err := agent.Run(ctx); err != nil { logger.Error("agent stopped", "id", cfg.Agent.ID, "err", err) } }() } wg.Wait() return nil }, } root.Flags().StringSliceVarP(&configPaths, "config", "c", nil, "Agent config files (comma-separated or repeated flag)") root.Flags().StringVar(&logLevel, "log-level", "info", "Log level: debug|info|warn|error") // Default: discover all config.yaml files under agents/ root.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { if len(configPaths) == 0 { matches, _ := filepath.Glob("agents/*/config.yaml") configPaths = matches } return nil } if err := root.Execute(); err != nil { os.Exit(1) } } // loadRulesForAgent returns the decision rules for a given agent config. // Extend this function (or use a registry) to wire up agent-specific rules. func loadRulesForAgent(cfg *config.AgentConfig) []decision.Rule { return []decision.Rule{ { Name: "help", Match: decision.MatchCommand("help"), Actions: []decision.Action{{ Kind: decision.ActionKindReply, Reply: &decision.ReplyAction{ Content: "I'm " + cfg.Agent.Name + ". " + cfg.Agent.Description, }, }}, }, } }