package group import ( "context" "encoding/json" "fmt" "log" "sync" "time" "synapse/types" ) // ============================================================================= // Group Protocol Handler // ============================================================================= // GroupTaskHandler is the interface for handling group task execution type GroupTaskHandler interface { // ExecuteSubtask executes a subtask and returns the result ExecuteSubtask(ctx context.Context, subtaskID string, payload map[string]interface{}) (any, error) // MergeResults merges multiple subtask results MergeResults(results []types.SubtaskResult, strategy types.MergeStrategy) (any, error) // ValidateTaskSplit validates if a task can be split ValidateTaskSplit(taskType string, requirements map[string]interface{}) ([]types.Subtask, bool) } // ============================================================================= // GroupProtocol represents the group protocol handler // ============================================================================= type GroupProtocol struct { mu sync.RWMutex // Agent identity agentEndpoint types.AgentEndpoint // Group state groups map[string]*types.AgentGroup // Membership memberships map[string]string // groupID -> member's role in group // Task handler taskHandler GroupTaskHandler // Configuration config *GroupConfig } // GroupConfig holds group protocol configuration type GroupConfig struct { MaxGroupSize int MaxSubtasks int ConsensusTimeout time.Duration TaskTimeout time.Duration } // DefaultGroupConfig returns default group configuration func DefaultGroupConfig() *GroupConfig { return &GroupConfig{ MaxGroupSize: 20, MaxSubtasks: 50, ConsensusTimeout: 5 * time.Minute, TaskTimeout: 10 * time.Minute, } } // ============================================================================= // NewGroupProtocol creates a new group protocol handler // ============================================================================= func NewGroupProtocol(agentEndpoint types.AgentEndpoint, config *GroupConfig) *GroupProtocol { if config == nil { config = DefaultGroupConfig() } return &GroupProtocol{ agentEndpoint: agentEndpoint, groups: make(map[string]*types.AgentGroup), memberships: make(map[string]string), config: config, } } // SetTaskHandler sets the group task handler func (g *GroupProtocol) SetTaskHandler(h GroupTaskHandler) { g.taskHandler = h } // ============================================================================= // HandleMessage processes an incoming group message // ============================================================================= func (g *GroupProtocol) HandleMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { g.mu.Lock() defer g.mu.Unlock() if err := g.validateMessage(msg); err != nil { a2aErr, ok := err.(*types.SynapseError) if !ok { a2aErr = types.NewSynapseError(types.ErrMessageFormatInvalid, err.Error(), nil) } return g.newErrorResponse(msg, a2aErr) } switch types.GroupMessageType(msg.Type) { // Group management case types.GroupCreate: return g.handleGroupCreate(ctx, msg) case types.GroupDisband: return g.handleGroupDisband(ctx, msg) case types.GroupJoin: return g.handleGroupJoin(ctx, msg) case types.GroupLeave: return g.handleGroupLeave(ctx, msg) case types.GroupInvite: return g.handleGroupInvite(ctx, msg) case types.GroupAcceptInvite: return g.handleGroupAcceptInvite(ctx, msg) case types.GroupRejectInvite: return g.handleGroupRejectInvite(ctx, msg) // Group communication case types.GroupBroadcast: return g.handleGroupBroadcast(ctx, msg) case types.GroupMulticast: return g.handleGroupMulticast(ctx, msg) case types.GroupDirect: return g.handleGroupDirect(ctx, msg) // Task collaboration case types.GroupTaskSplit: return g.handleGroupTaskSplit(ctx, msg) case types.GroupTaskAssign: return g.handleGroupTaskAssign(ctx, msg) case types.GroupTaskMerge: return g.handleGroupTaskMerge(ctx, msg) case types.GroupTaskProgress: return g.handleGroupTaskProgress(ctx, msg) // Consensus case types.GroupVote: return g.handleGroupVote(ctx, msg) case types.GroupVoteSubmit: return g.handleGroupVoteSubmit(ctx, msg) // Human-in-the-Loop case types.GroupConsensusEscalate: return g.handleGroupConsensusEscalate(ctx, msg) case types.GroupSubtaskEscalate: return g.handleGroupSubtaskEscalate(ctx, msg) case types.GroupHumanDecision: return g.handleGroupHumanDecision(ctx, msg) case types.InterGroupEscalate: return g.handleInterGroupEscalate(ctx, msg) default: return g.newErrorResponse(msg, types.NewSynapseError( types.ErrMessageFormatInvalid, fmt.Sprintf("Unknown group message type: %s", msg.Type), nil, )) } } // ============================================================================= // Group Management Handlers // ============================================================================= func (g *GroupProtocol) handleGroupCreate(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload types.GroupCreatePayload if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid create payload", err.Error())) } // Check permissions (only coordinator can create) // For now, allow any agent to create group := &types.AgentGroup{ ID: fmt.Sprintf("group-%d", time.Now().UnixNano()), Name: payload.Group.Name, Topology: payload.Group.Topology, CreatedBy: g.agentEndpoint.AgentID, CreatedAt: types.NowTimestamp(), Config: payload.Group.Config, Members: []types.GroupMember{}, Roles: payload.Group.Roles, Status: types.GroupStatusActive, } // Add creator as first member creator := types.GroupMember{ AgentID: g.agentEndpoint.AgentID, AgentName: g.agentEndpoint.AgentName, Role: "coordinator", JoinedAt: types.NowTimestamp(), Status: types.GroupMemberActive, Capabilities: []string{g.agentEndpoint.Version}, LoadFactor: 0.0, } group.Members = append(group.Members, creator) g.groups[group.ID] = group g.memberships[group.ID] = "coordinator" log.Printf("[Group] Group created: %s (topology: %s)", group.ID, group.Topology) return g.newSuccessResponse(msg, map[string]interface{}{ "group_id": group.ID, "status": "created", }) } func (g *GroupProtocol) handleGroupDisband(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload struct { GroupID string `json:"group_id"` } if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid disband payload", err.Error())) } group, ok := g.groups[payload.GroupID] if !ok { return g.newErrorResponse(msg, types.ErrGroupNotFoundError) } // Check if sender is coordinator senderRole := g.getMemberRole(payload.GroupID, msg.Sender.AgentID) if senderRole != "coordinator" { return g.newErrorResponse(msg, types.ErrPermissionDeniedError) } group.Status = types.GroupStatusDisbanded delete(g.groups, payload.GroupID) delete(g.memberships, payload.GroupID) log.Printf("[Group] Group disbanded: %s", payload.GroupID) return g.newSuccessResponse(msg, map[string]interface{}{ "group_id": payload.GroupID, "status": "disbanded", }) } func (g *GroupProtocol) handleGroupJoin(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload struct { GroupID string `json:"group_id"` } if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid join payload", err.Error())) } group, ok := g.groups[payload.GroupID] if !ok { return g.newErrorResponse(msg, types.ErrGroupNotFoundError) } if group.Status != types.GroupStatusActive { return g.newErrorResponse(msg, types.ErrGroupNotActiveError) } // Check if already a member for _, m := range group.Members { if m.AgentID == g.agentEndpoint.AgentID { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrAlreadyMember, "Already a member", nil)) } } // Check group size if len(group.Members) >= g.config.MaxGroupSize { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrGroupFull, "Group is full", nil)) } // Add as member member := types.GroupMember{ AgentID: g.agentEndpoint.AgentID, AgentName: g.agentEndpoint.AgentName, Role: "worker", JoinedAt: types.NowTimestamp(), Status: types.GroupMemberActive, LoadFactor: 0.0, } group.Members = append(group.Members, member) g.memberships[payload.GroupID] = "worker" log.Printf("[Group] Agent %s joined group %s", g.agentEndpoint.AgentID, payload.GroupID) return g.newSuccessResponse(msg, map[string]interface{}{ "group_id": payload.GroupID, "role": "worker", "member_id": member.AgentID, }) } func (g *GroupProtocol) handleGroupLeave(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload struct { GroupID string `json:"group_id"` } if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid leave payload", err.Error())) } group, ok := g.groups[payload.GroupID] if !ok { return g.newErrorResponse(msg, types.ErrGroupNotFoundError) } // Check if member found := false for i, m := range group.Members { if m.AgentID == g.agentEndpoint.AgentID { group.Members = append(group.Members[:i], group.Members[i+1:]...) found = true break } } if !found { return g.newErrorResponse(msg, types.ErrNotGroupMemberError) } // Check if can self-leave if !group.Config.AllowSelfLeave && g.getMemberRole(payload.GroupID, g.agentEndpoint.AgentID) == "coordinator" { return g.newErrorResponse(msg, types.ErrPermissionDeniedError) } delete(g.memberships, payload.GroupID) log.Printf("[Group] Agent %s left group %s", g.agentEndpoint.AgentID, payload.GroupID) return g.newSuccessResponse(msg, map[string]interface{}{ "group_id": payload.GroupID, "status": "left", }) } func (g *GroupProtocol) handleGroupInvite(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload types.GroupInvitePayload if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid invite payload", err.Error())) } _, ok := g.groups[payload.GroupID] if !ok { return g.newErrorResponse(msg, types.ErrGroupNotFoundError) } // Check if inviter is coordinator or has invite permission inviterRole := g.getMemberRole(payload.GroupID, msg.Sender.AgentID) if inviterRole != "coordinator" && inviterRole != "" { return g.newErrorResponse(msg, types.ErrPermissionDeniedError) } log.Printf("[Group] Invitation sent to %s for group %s", payload.Invitee.AgentID, payload.GroupID) return g.newSuccessResponse(msg, map[string]interface{}{ "group_id": payload.GroupID, "invitee": payload.Invitee.AgentID, "status": "invited", }) } func (g *GroupProtocol) handleGroupAcceptInvite(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload struct { GroupID string `json:"group_id"` } if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid accept payload", err.Error())) } // Join the group return g.handleGroupJoin(ctx, msg) } func (g *GroupProtocol) handleGroupRejectInvite(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload struct { GroupID string `json:"group_id"` Reason string `json:"reason"` } if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid reject payload", err.Error())) } log.Printf("[Group] Invitation rejected for group %s: %s", payload.GroupID, payload.Reason) return g.newSuccessResponse(msg, map[string]interface{}{ "group_id": payload.GroupID, "status": "rejected", "reason": payload.Reason, }) } // ============================================================================= // Group Communication Handlers // ============================================================================= func (g *GroupProtocol) handleGroupBroadcast(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { // Broadcast is handled by the transport layer // This just validates the message log.Printf("[Group] Broadcast received in group %s from %s", msg.GroupID, msg.Sender.AgentID) return g.newSuccessResponse(msg, map[string]interface{}{ "status": "broadcast_received", }) } func (g *GroupProtocol) handleGroupMulticast(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { _, ok := g.groups[msg.GroupID] if !ok { return g.newErrorResponse(msg, types.ErrGroupNotFoundError) } // Validate targets exist targets := msg.TargetIDs for _, t := range targets { if !g.isMember(msg.GroupID, t) { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMemberNotFound, fmt.Sprintf("Member not found: %s", t), nil)) } } log.Printf("[Group] Multicast in group %s to %d targets", msg.GroupID, len(targets)) return g.newSuccessResponse(msg, map[string]interface{}{ "status": "multicast_scheduled", "target_count": len(targets), }) } func (g *GroupProtocol) handleGroupDirect(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { if len(msg.TargetIDs) != 1 { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrInvalidParams, "Direct message requires exactly one target", nil)) } target := msg.TargetIDs[0] if !g.isMember(msg.GroupID, target) { return g.newErrorResponse(msg, types.ErrMemberNotFoundError) } log.Printf("[Group] Direct message in group %s to %s", msg.GroupID, target) return g.newSuccessResponse(msg, map[string]interface{}{ "status": "direct_scheduled", "target": target, }) } // ============================================================================= // Task Collaboration Handlers // ============================================================================= func (g *GroupProtocol) handleGroupTaskSplit(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload types.GroupTaskSplitPayload if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid task split payload", err.Error())) } _, ok := g.groups[msg.GroupID] if !ok { return g.newErrorResponse(msg, types.ErrGroupNotFoundError) } // Check if sender is coordinator if g.getMemberRole(msg.GroupID, msg.Sender.AgentID) != "coordinator" { return g.newErrorResponse(msg, types.ErrPermissionDeniedError) } // Validate task split if g.taskHandler != nil { subtasks, valid := g.taskHandler.ValidateTaskSplit(payload.OriginalTask.Type, payload.OriginalTask.Requirements) if !valid { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrInvalidParams, "Task cannot be split", nil)) } payload.Subtasks = subtasks } // Check subtask count if len(payload.Subtasks) > g.config.MaxSubtasks { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrInvalidParams, fmt.Sprintf("Too many subtasks: %d", len(payload.Subtasks)), nil)) } // Assign subtasks to members for i := range payload.Subtasks { assignee := payload.Subtasks[i].AssignedTo if !g.isMember(msg.GroupID, assignee) { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMemberNotFound, fmt.Sprintf("Assignee not found: %s", assignee), nil)) } } log.Printf("[Group] Task split in group %s: %d subtasks", msg.GroupID, len(payload.Subtasks)) // Execute subtasks asynchronously go g.executeSubtasks(context.Background(), msg.GroupID, payload.OriginalTask.ID, payload.Subtasks, payload.MergeStrategy) return g.newSuccessResponse(msg, map[string]interface{}{ "status": "task_split_initiated", "original_task_id": payload.OriginalTask.ID, "subtask_count": len(payload.Subtasks), }) } func (g *GroupProtocol) executeSubtasks(ctx context.Context, groupID, originalTaskID string, subtasks []types.Subtask, strategy types.MergeStrategy) { if g.taskHandler == nil { log.Printf("[Group] No task handler configured, skipping subtask execution") return } results := make([]types.SubtaskResult, 0, len(subtasks)) for _, st := range subtasks { result, err := g.taskHandler.ExecuteSubtask(ctx, st.ID, map[string]interface{}{ "description": st.Description, "dependencies": st.Dependencies, }) subtaskResult := types.SubtaskResult{ SubtaskID: st.ID, AssignedTo: st.AssignedTo, } if err != nil { subtaskResult.Status = "failed" subtaskResult.Error = err.Error() } else { subtaskResult.Status = "success" subtaskResult.Result = result now := types.NowTimestamp() subtaskResult.CompletedAt = &now } results = append(results, subtaskResult) } // Merge results _, err := g.taskHandler.MergeResults(results, strategy) if err != nil { log.Printf("[Group] Failed to merge results: %v", err) } log.Printf("[Group] All subtasks completed, merged result ready") // In a real implementation, this would send a GROUP_TASK_MERGE message } func (g *GroupProtocol) handleGroupTaskAssign(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload struct { SubtaskID string `json:"subtask_id"` AssignTo string `json:"assign_to"` } if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid task assign payload", err.Error())) } if !g.isMember(msg.GroupID, payload.AssignTo) { return g.newErrorResponse(msg, types.ErrMemberNotFoundError) } log.Printf("[Group] Subtask %s assigned to %s in group %s", payload.SubtaskID, payload.AssignTo, msg.GroupID) return g.newSuccessResponse(msg, map[string]interface{}{ "status": "assigned", "subtask_id": payload.SubtaskID, "assignee": payload.AssignTo, }) } func (g *GroupProtocol) handleGroupTaskMerge(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload types.GroupTaskMergePayload if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid task merge payload", err.Error())) } log.Printf("[Group] Task merge for %s: %d results, consensus: %v", payload.OriginalTaskID, len(payload.SubtaskResults), payload.ConsensusAchieved) return g.newSuccessResponse(msg, map[string]interface{}{ "status": "merged", "original_task_id": payload.OriginalTaskID, "result_ready": true, }) } func (g *GroupProtocol) handleGroupTaskProgress(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload struct { SubtaskID string `json:"subtask_id"` Progress int `json:"progress"` Message string `json:"message"` } if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid progress payload", err.Error())) } log.Printf("[Group] Progress update: subtask %s - %d%% (%s)", payload.SubtaskID, payload.Progress, payload.Message) return g.newSuccessResponse(msg, map[string]interface{}{ "status": "progress_recorded", "subtask_id": payload.SubtaskID, }) } // ============================================================================= // Consensus Handlers // ============================================================================= func (g *GroupProtocol) handleGroupVote(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload types.GroupVotePayload if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid vote payload", err.Error())) } // Initialize vote payload.Status = "open" payload.Votes = []types.GroupVoteOption{} _, ok := g.groups[msg.GroupID] if !ok { return g.newErrorResponse(msg, types.ErrGroupNotFoundError) } // Check if sender is allowed to initiate vote senderRole := g.getMemberRole(msg.GroupID, msg.Sender.AgentID) if senderRole != "coordinator" && senderRole != "reviewer" { return g.newErrorResponse(msg, types.ErrPermissionDeniedError) } log.Printf("[Group] Vote initiated in group %s: %s (threshold: %.0f%%)", msg.GroupID, payload.Question, payload.Threshold*100) // In a real implementation, this would set up a vote session and notify voters return g.newSuccessResponse(msg, map[string]interface{}{ "status": "vote_initiated", "vote_id": payload.VoteID, "voters": len(payload.Voters), }) } func (g *GroupProtocol) handleGroupVoteSubmit(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload struct { VoteID string `json:"vote_id"` Option string `json:"option"` Reason string `json:"reason,omitempty"` } if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid vote submit payload", err.Error())) } log.Printf("[Group] Vote submitted: %s by %s (option: %s)", payload.VoteID, msg.Sender.AgentID, payload.Option) return g.newSuccessResponse(msg, map[string]interface{}{ "status": "vote_recorded", "vote_id": payload.VoteID, }) } // ============================================================================= // Human-in-the-Loop Handlers // ============================================================================= func (g *GroupProtocol) handleGroupConsensusEscalate(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload types.GroupConsensusEscalatePayload if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid consensus escalate payload", err.Error())) } log.Printf("[Group] Consensus escalation in group %s (level: %d): %s", msg.GroupID, payload.EscalationLevel, payload.EscalateReason.Message) // Store escalation and notify human decision makers return g.newSuccessResponse(msg, map[string]interface{}{ "status": "escalation_recorded", "escalation_id": payload.EscalationID, "human_notified": true, }) } func (g *GroupProtocol) handleGroupSubtaskEscalate(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload types.GroupSubtaskEscalatePayload if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid subtask escalate payload", err.Error())) } log.Printf("[Group] Subtask escalation: %s in task %s (level: %d)", payload.Subtask.ID, payload.OriginalTask.ID, payload.EscalationLevel) return g.newSuccessResponse(msg, map[string]interface{}{ "status": "escalation_recorded", "subtask_id": payload.Subtask.ID, "human_notified": true, }) } func (g *GroupProtocol) handleGroupHumanDecision(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload types.HumanDecisionPayload if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid human decision payload", err.Error())) } log.Printf("[Group] Human decision for task %s: %s (by %s)", payload.TaskID, payload.Decision.Action, payload.DecidedBy.UserName) // Process the decision and resume task execution return g.newSuccessResponse(msg, map[string]interface{}{ "status": "decision_recorded", "task_id": payload.TaskID, "action": payload.Decision.Action, }) } func (g *GroupProtocol) handleInterGroupEscalate(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) { var payload struct { EscalationID string `json:"escalation_id"` EscalationType string `json:"escalation_type"` // ... other fields } if err := json.Unmarshal(msg.Payload, &payload); err != nil { return g.newErrorResponse(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid inter-group escalate payload", err.Error())) } log.Printf("[Group] Inter-group escalation: %s (type: %s)", payload.EscalationID, payload.EscalationType) return g.newSuccessResponse(msg, map[string]interface{}{ "status": "escalation_recorded", "escalation_id": payload.EscalationID, }) } // ============================================================================= // Helper Methods // ============================================================================= func (g *GroupProtocol) validateMessage(msg *types.UnifiedSynapseMessage) error { if msg.GroupID == "" { return types.NewSynapseError(types.ErrMessageFormatInvalid, "Missing group_id", nil) } if msg.Sender.AgentID == "" { return types.NewSynapseError(types.ErrMessageFormatInvalid, "Missing sender agent_id", nil) } return nil } func (g *GroupProtocol) getMemberRole(groupID, agentID string) string { group, ok := g.groups[groupID] if !ok { return "" } for _, m := range group.Members { if m.AgentID == agentID { return m.Role } } return "" } func (g *GroupProtocol) isMember(groupID, agentID string) bool { return g.getMemberRole(groupID, agentID) != "" } func (g *GroupProtocol) newSuccessResponse(msg *types.UnifiedSynapseMessage, data map[string]interface{}) (*types.UnifiedSynapseMessage, error) { resp := &types.UnifiedSynapseMessage{ ID: types.GenerateMessageID(), Version: "1.0.0", Type: string(types.P2PAck), Sender: g.agentEndpoint, CorrelationID: msg.CorrelationID, ReplyTo: msg.ID, Timestamp: types.NowTimestamp(), } return resp, nil } func (g *GroupProtocol) newErrorResponse(msg *types.UnifiedSynapseMessage, err *types.SynapseError) (*types.UnifiedSynapseMessage, error) { return nil, err } // GetGroups returns all groups the agent is a member of func (g *GroupProtocol) GetGroups() []*types.AgentGroup { g.mu.RLock() defer g.mu.RUnlock() result := make([]*types.AgentGroup, 0, len(g.groups)) for _, group := range g.groups { if g.isMember(group.ID, g.agentEndpoint.AgentID) { result = append(result, group) } } return result } // GetGroup returns a specific group func (g *GroupProtocol) GetGroup(groupID string) (*types.AgentGroup, bool) { g.mu.RLock() defer g.mu.RUnlock() group, ok := g.groups[groupID] return group, ok }