feat: launcher descubre agentes en _specials/ con validacion de tipo

El launcher ahora escanea agents/_specials/*/config.yaml ademas de
agents/*/config.yaml para descubrir agentes del sistema con identidad
Matrix (ej: father-bot). Los SpecialConfig ya cargados (orchestrator)
se detectan y saltan via isSpecialConfig() para evitar errores de
validacion.

Tambien actualiza dev-scripts/_common.sh para que config_path_for()
y list_agents_raw() incluyan _specials/ en la busqueda.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 22:05:13 +00:00
parent cd22c1c861
commit 2f3f30c86b
2 changed files with 37 additions and 2 deletions
+33
View File
@@ -34,6 +34,7 @@ import (
_ "github.com/enmanuel/agents/agents/asistente-2"
_ "github.com/enmanuel/agents/agents/meteorologo"
_ "github.com/enmanuel/agents/agents/test-personality"
_ "github.com/enmanuel/agents/agents/_specials/father-bot"
testbot "github.com/enmanuel/agents/agents/test-bot"
)
@@ -51,6 +52,10 @@ func main() {
if len(configPaths) == 0 {
matches, _ := filepath.Glob("agents/*/config.yaml")
configPaths = matches
// Also discover agent-type specials (e.g. father-bot).
// SpecialConfig middleware (orchestrator) is handled separately.
specials, _ := filepath.Glob("agents/_specials/*/config.yaml")
configPaths = append(configPaths, specials...)
}
return nil
},
@@ -143,9 +148,22 @@ func main() {
}()
// ── Start normal agents ──
// Build a set of special IDs already loaded (e.g. orchestrator)
// so the discovery loop skips them instead of failing on validation.
loadedSpecials := make(map[string]bool)
if orch != nil {
loadedSpecials[orch.cfg.Special.ID] = true
}
var scannerOnce scanOnce
for _, path := range configPaths {
path := path
// Skip configs that belong to already-loaded specials.
if isSpecialConfig(path, loadedSpecials) {
continue
}
cfg, err := config.Load(path)
if err != nil {
logger.Error("failed to load config", "path", path, "err", err)
@@ -337,3 +355,18 @@ func parseLogLevel(level string) slog.Level {
func newLogger(level string) *slog.Logger {
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: parseLogLevel(level)}))
}
// isSpecialConfig checks whether a config path belongs to a special agent
// that was already loaded (e.g. orchestrator). It reads the YAML to detect
// a "special:" top-level key. This avoids config.Load() failing with
// validation errors for SpecialConfig files.
func isSpecialConfig(path string, loadedSpecials map[string]bool) bool {
if len(loadedSpecials) == 0 {
return false
}
cfg, err := config.LoadSpecial(path)
if err != nil {
return false // not a valid special config → let Load() handle it
}
return loadedSpecials[cfg.Special.ID]
}
+4 -2
View File
@@ -51,9 +51,10 @@ read_pid() {
}
# Map agent ID to its config path by scanning agent directories.
# Also scans agents/_specials/ for privileged system agents (e.g. father-bot).
config_path_for() {
local target_id="$1"
for cfg in agents/*/config.yaml; do
for cfg in agents/*/config.yaml agents/_specials/*/config.yaml; do
[[ -f "$cfg" ]] || continue
local id
id=$(grep -m1 '^ id:' "$cfg" | awk '{print $2}')
@@ -150,8 +151,9 @@ is_launcher_running() {
# ── Agent discovery ────────────────────────────────────────────────────────
# Prints: id|version|enabled|description (one line per agent)
# Also scans agents/_specials/ for privileged system agents.
list_agents_raw() {
for cfg in agents/*/config.yaml; do
for cfg in agents/*/config.yaml agents/_specials/*/config.yaml; do
[[ -f "$cfg" ]] || continue
local id version enabled desc
id=$(grep -m1 '^ id:' "$cfg" | awk '{print $2}')