Files
synapse/internal/agent/group/protocol.go
2026-06-28 03:24:26 +08:00

795 lines
27 KiB
Go

package group
import (
"context"
"encoding/json"
"fmt"
"log"
"sync"
"time"
"synapse/pkg/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
}