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
+91
View File
@@ -0,0 +1,91 @@
// Package orchestration defines pure types for multi-bot coordination.
// Zero side effects — only data structures and helpers.
package orchestration
import "encoding/json"
// TaskEvent is sent by the orchestrator to a bot via the bus.
// It tells the bot: "answer this question in this room with this context."
type TaskEvent struct {
TaskID string `json:"task_id"`
TargetBotID string `json:"target_bot_id"`
TargetRoomID string `json:"target_room_id"`
OriginalSender string `json:"original_sender"`
OriginalQuestion string `json:"original_question"`
Iteration int `json:"iteration"`
PreviousResponses []BotResponse `json:"previous_responses,omitempty"`
RoomContext []ContextMessage `json:"room_context,omitempty"`
}
// BotResponse is a bot's reply to a TaskEvent.
type BotResponse struct {
BotID string `json:"bot_id"`
Text string `json:"text"`
}
// TaskResult is sent by a bot back to the orchestrator via the bus.
type TaskResult struct {
TaskID string `json:"task_id"`
BotID string `json:"bot_id"`
Text string `json:"text"`
Error string `json:"error,omitempty"`
}
// QualityScore is the LLM's evaluation of a bot's response.
type QualityScore struct {
Score float64 `json:"score"` // 0.01.0
Continue bool `json:"continue"` // should the pipeline continue?
Reason string `json:"reason"`
}
// RoutingDecision is the LLM's choice of which bot should respond.
type RoutingDecision struct {
TargetBotID string `json:"bot_id"`
Confidence float64 `json:"confidence"`
Reason string `json:"reason"`
}
// ContextMessage is a single message from the room's recent history.
type ContextMessage struct {
SenderID string `json:"sender_id"`
Content string `json:"content"`
}
// ParticipantInfo describes a bot available for routing.
type ParticipantInfo struct {
ID string `json:"id"`
Description string `json:"description"`
Capabilities []string `json:"capabilities,omitempty"`
}
// MarshalTaskEvent serializes a TaskEvent to JSON for bus transport.
func MarshalTaskEvent(t TaskEvent) (string, error) {
b, err := json.Marshal(t)
if err != nil {
return "", err
}
return string(b), nil
}
// UnmarshalTaskEvent deserializes a TaskEvent from JSON.
func UnmarshalTaskEvent(data string) (TaskEvent, error) {
var t TaskEvent
err := json.Unmarshal([]byte(data), &t)
return t, err
}
// MarshalTaskResult serializes a TaskResult to JSON for bus transport.
func MarshalTaskResult(r TaskResult) (string, error) {
b, err := json.Marshal(r)
if err != nil {
return "", err
}
return string(b), nil
}
// UnmarshalTaskResult deserializes a TaskResult from JSON.
func UnmarshalTaskResult(data string) (TaskResult, error) {
var r TaskResult
err := json.Unmarshal([]byte(data), &r)
return r, err
}