feat: implement multi-bot orchestration system with LLM routing

Implementa el sistema de orquestación para salas Matrix con múltiples bots.
El orquestador es un "special agent" sin identidad Matrix que coordina qué bot
responde y cuándo, usando LLM (Claude) para routing y evaluación de calidad.

Cambios principales:
- pkg/orchestration/task.go: tipos puros (TaskEvent, BotResponse, QualityScore, RoutingDecision)
- shell/orchestration/: runtime del orquestador (orchestrator.go, router.go, evaluator.go)
- agents/specials/orchestrator/: config + prompts (routing, quality, refinement)
- internal/config/: SpecialConfig, OrchestrationCfg, LoadSpecial()
- shell/bus/bus.go: protocolo request-reply (SendAndWait, Reply) para delegación
- shell/matrix/listener.go: InterceptFunc para interceptar eventos en salas orquestadas
- agents/runtime.go: SetBus, listenBus, handleTaskEvent para recibir tareas del orquestador
- cmd/launcher/main.go: creación de bus compartido, arranque del orquestador antes de bots

Incluye deduplicación para evitar que múltiples listeners en la misma sala
disparen el orquestador más de una vez por mensaje.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 09:05:42 +00:00
parent 6bef4283c6
commit 2667af52cc
14 changed files with 1001 additions and 7 deletions
+26 -6
View File
@@ -18,14 +18,20 @@ import (
// EventHandler is called for each incoming Matrix message that passes filters.
type EventHandler func(ctx context.Context, msgCtx decision.MessageContext, evt *event.Event)
// InterceptFunc is called for events in orchestrated rooms.
// It receives the parsed MessageContext. If it returns true, the event is not
// delivered to the bot's normal handler (the orchestrator handles it instead).
type InterceptFunc func(ctx context.Context, msgCtx decision.MessageContext) bool
// Listener attaches to a mautrix syncer and dispatches events to an EventHandler.
type Listener struct {
client *Client
cfg config.MatrixCfg
handler EventHandler
logger *slog.Logger
dmCache map[id.RoomID]bool
mu sync.RWMutex
client *Client
cfg config.MatrixCfg
handler EventHandler
logger *slog.Logger
dmCache map[id.RoomID]bool
mu sync.RWMutex
interceptor InterceptFunc // if set and returns true, event is forwarded to orchestrator
}
// NewListener creates a Listener for the given client.
@@ -39,6 +45,13 @@ func NewListener(client *Client, cfg config.MatrixCfg, handler EventHandler, log
}
}
// SetInterceptor registers a function that can intercept event delivery.
// If the function returns true, the event is handled by the orchestrator
// and not delivered to the bot's normal handler.
func (l *Listener) SetInterceptor(fn InterceptFunc) {
l.interceptor = fn
}
// Run starts the Matrix sync loop. Blocks until ctx is cancelled.
func (l *Listener) Run(ctx context.Context) error {
syncer := l.client.raw.Syncer.(*mautrix.DefaultSyncer)
@@ -112,6 +125,13 @@ func (l *Listener) Run(ctx context.Context) error {
"content_preview", truncate(msgCtx.Content, 80),
)
// Orchestrator intercept: if this room is managed, the orchestrator
// handles routing instead of the bot's normal handler.
if l.interceptor != nil && l.interceptor(ctx, msgCtx) {
l.logger.Debug("event intercepted by orchestrator", "room", evt.RoomID)
return
}
go l.handler(ctx, msgCtx, evt)
})