灵隙(synapse) v1.0.0 版本

This commit is contained in:
lychang
2026-06-27 14:08:30 +08:00
parent a73ff857bb
commit 713292489f
44 changed files with 21884 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.idea

245
MIGRATION.md Normal file
View File

@@ -0,0 +1,245 @@
# A2A 协议迁移说明
## 概述
本文档记录了从 `ai-video-factory` 项目中抽离 A2A(Agent-to-Agent)协议和 Group(群组)相关代码到独立模块的过程。
## 源项目信息
- **源项目**: `ai-video-factory`
- **源路径**: `ai-video-factory/a2aprotocol/`
- **目标项目**: `dp-headless`
- **目标路径**: `dp-headless/a2a/`
## 模块路径变更
| 项目 | 模块路径 |
|------|----------|
| 源 | `ai-video-factory/a2aprotocol/` |
| 目标 | `dp-headless/a2a/` |
所有 Go 文件中的 import 路径已从 `ai-video-factory/a2aprotocol/xxx` 更新为 `dp-headless/a2a/xxx`。
## 目录结构
```
a2a/
├── go.mod # Go 模块定义
├── proto/
│ └── a2a.proto # gRPC 协议定义
├── types/ # 核心类型定义
│ ├── types.go # 基础类型、消息类型、任务状态
│ ├── content.go # 内容类型(文本、图片、视频、音频等)
│ └── errors.go # 错误定义
├── p2p/ # P2P 协议
│ └── protocol.go # 点对点任务处理协议
├── group/ # 群组协议
│ └── protocol.go # 群组协作协议(创建、解散、任务拆分合并)
├── routing/ # 消息路由
│ └── router.go # 消息路由器(P2P/群组分发)
├── transport/ # 传输层
│ ├── transport.go # HTTP 传输实现
│ └── multipart_polling.go # 多部分文件传输和轮询客户端
├── capabilities/ # 能力发现
│ └── provider.go # Agent 能力提供者
├── externalapi/ # 外部 REST API
│ └── handler.go # 简化的 RESTful API 处理器
├── sdk/ # SDK
│ └── client.go # 外部调用者 SDK 客户端
├── grpcapi/ # gRPC API
│ ├── a2a.pb.go # gRPC 消息生成代码
│ ├── a2a_grpc.pb.go # gRPC 服务生成代码
│ ├── client.go # gRPC 客户端实现
│ └── server.go # gRPC 服务端实现
├── hil/ # 人机协同(HiL)
│ └── protocol.go # 人工介入任务处理协议
└── examples/ # 示例代码
└── main.go # 完整使用示例
```
## 迁移的文件清单
### types 包(3 个文件)
- `types/types.go` - 核心数据类型:AgentEndpoint、A2AMessage、TaskPayload、TaskStatus 等
- `types/content.go` - 内容类型:TextContent、ImageContent、VideoContent、AudioContent、FileContent
- `types/errors.go` - 错误定义:TaskError、错误码常量
### p2p 包(1 个文件)
- `p2p/protocol.go` - P2P 协议实现:任务提交、状态更新、结果回调、取消任务
### group 包(1 个文件)
- `group/protocol.go` - 群组协议实现:群组创建/解散、成员管理、任务拆分/合并、投票机制
### routing 包(1 个文件)
- `routing/router.go` - 消息路由器:根据消息类型分发到 P2P 或群组处理器
### transport 包(2 个文件)
- `transport/transport.go` - HTTP 传输:发送消息、处理回调、签名验证
- `transport/multipart_polling.go` - 多部分文件传输和轮询客户端
### capabilities 包(1 个文件)
- `capabilities/provider.go` - Agent 能力提供者:能力声明、能力查询
### externalapi 包(1 个文件)
- `externalapi/handler.go` - 外部 REST API:任务提交/查询/取消、能力查询
### sdk 包(1 个文件)
- `sdk/client.go` - SDK 客户端:封装 A2A 协议调用,简化外部集成
### grpcapi 包(4 个文件)
- `grpcapi/a2a.pb.go` - Protobuf 生成的消息类型
- `grpcapi/a2a_grpc.pb.go` - Protobuf 生成的 gRPC 服务接口
- `grpcapi/client.go` - gRPC 客户端实现
- `grpcapi/server.go` - gRPC 服务端实现
### hil 包(1 个文件)
- `hil/protocol.go` - 人机协同协议:人工决策请求/响应、任务挂起/恢复
### proto(1 个文件)
- `proto/a2a.proto` - gRPC 协议定义文件
### examples(1 个文件)
- `examples/main.go` - 完整使用示例
**总计:17 个 Go 源文件 + 1 个 proto 文件 + 1 个 go.mod**
## 核心功能
### 1. P2P 任务协议
- 任务提交与响应
- 任务状态查询与更新
- 任务取消
- 进度回调
- 信息缺失通知
### 2. 群组协作协议
- 群组创建与解散
- 成员加入与离开
- 任务拆分与分发
- 子任务结果收集
- 结果合并
- 投票机制(发起投票、提交投票、公布结果)
### 3. 传输层
- HTTP 回调模式
- 轮询模式
- 同步模式
- 混合模式
- HMAC 签名验证
- 多部分文件上传
### 4. 能力发现
- Agent 能力声明
- 能力查询接口
- 支持的任务类型
- 支持的传输模式
- 并发能力限制
### 5. 人机协同(HiL)
- 人工决策请求
- 决策响应
- 任务挂起与恢复
- 超时处理
### 6. gRPC 支持
- TaskService - 任务服务
- A2AMessageService - 消息服务
- CapabilitiesService - 能力服务
- GroupService - 群组服务
- HealthService - 健康检查
## 使用示例
### 快速开始
```go
package main
import (
"dp-headless/a2a/capabilities"
"dp-headless/a2a/externalapi"
"dp-headless/a2a/group"
"dp-headless/a2a/p2p"
"dp-headless/a2a/routing"
"dp-headless/a2a/transport"
"dp-headless/a2a/types"
)
func main() {
// 1. 定义 Agent 端点
agentEndpoint := types.AgentEndpoint{
AgentID: "my-agent",
AgentName: "我的Agent",
SystemID: "my-system",
Endpoint: "http://localhost:8080/api/v1/a2a/callback",
Version: "1.0.0",
}
// 2. 配置能力提供者
caps := &capabilities.AgentCapabilityProvider{
AgentID: agentEndpoint.AgentID,
AgentName: agentEndpoint.AgentName,
SystemID: agentEndpoint.SystemID,
SupportedTaskTypes: []capabilities.SupportedTaskType{
{Type: "text_generation", Name: "文本生成"},
},
SupportGroups: true,
SupportHiL: true,
}
// 3. 创建 P2P 协议处理器
p2pProtocol := p2p.NewP2PProtocol(agentEndpoint, caps,
func(taskID string, taskType string, payload map[string]interface{}) (map[string]interface{}, error) {
// 处理任务逻辑
return map[string]interface{}{"result": "success"}, nil
})
// 4. 创建群组协议处理器
groupConfig := &group.GroupConfig{MaxGroupSize: 10}
groupProtocol := group.NewGroupProtocol(agentEndpoint, nil, groupConfig)
// 5. 配置消息路由
router := routing.NewMessageRouter(agentEndpoint)
router.RegisterP2PHandler(p2pProtocol)
router.RegisterGroupHandler(groupProtocol)
// 6. 启动 HTTP 服务
httpTransport := transport.NewHTTPTransport(agentEndpoint, nil, router)
extHandler := externalapi.NewExternalAPIHandler(p2pProtocol, caps, httpTransport)
mux := http.NewServeMux()
extHandler.RegisterRoutes(mux, "/api/v1/a2a")
http.ListenAndServe(":8080", mux)
}
```
完整示例请参考 `examples/main.go`。
## 依赖项
```
- github.com/golang/protobuf v1.5.4
- github.com/gorilla/websocket v1.5.3
- golang.org/x/image v0.42.0
- google.golang.org/grpc v1.68.0
- google.golang.org/protobuf v1.35.2
- modernc.org/sqlite v1.52.0
```
## 未迁移的内容
以下内容因与 ai-video-factory 业务逻辑强耦合,未包含在本次迁移中:
1. **旧版 A2A 实现**(`a2a.go` 和 `a2a_api.go`)- 基于 SQLite 的简单实现,与视频生成工作流深度绑定
2. **数据库集成** - 旧版的 SQLite 数据库层
3. **视频工作流集成** - 与 VideoCreationWorkflow 的集成代码
如需数据库持久化,建议基于现有的协议层自行实现存储适配层。
## 后续可扩展方向
1. 添加持久化存储接口(Memory/Redis/PostgreSQL 等)
2. 实现完整的 gRPC 网关
3. 添加更多传输协议支持(WebSocket、MQTT 等)
4. 增加单元测试和集成测试
5. 完善文档和示例

546
capabilities/provider.go Normal file
View File

@@ -0,0 +1,546 @@
package capabilities
import (
"context"
"encoding/json"
"net/http"
"synapse/types"
)
// =============================================================================
// Capabilities Provider Interface
// =============================================================================
// CapabilitiesProvider 定义Agent暴露自身能力的接口
// 所有Agent都应该实现此接口以便其他Agent查询其能力
type CapabilitiesProvider interface {
// GetCapabilities 返回Agent的完整能力描述
GetCapabilities(ctx context.Context) (*types.AgentCapabilities, error)
// GetSupportedInterfaces 返回Agent实现的接口列表
GetSupportedInterfaces() []ImplementedInterface
// GetTaskTypeCapabilities 返回特定任务类型的详细能力
GetTaskTypeCapabilities(taskType string) (*types.SupportedTaskType, error)
}
// ImplementedInterface 定义Agent实现的接口
type ImplementedInterface struct {
// InterfaceName 接口名称
Name string `json:"name"`
// Category 接口分类
Category InterfaceCategory `json:"category"`
// Version 接口版本
Version string `json:"version"`
// ComplianceLevel 合规级别 (basic, complete, advanced)
ComplianceLevel string `json:"compliance_level"`
// Description 接口描述
Description string `json:"description"`
// Methods 接口支持的方法
Methods []InterfaceMethod `json:"methods,omitempty"`
// Status 接口状态 (implemented, partial, planned)
Status string `json:"status"`
}
// InterfaceCategory 接口分类
type InterfaceCategory string
const (
CategoryCore InterfaceCategory = "core" // 核心接口
CategoryP2P InterfaceCategory = "p2p" // P2P接口
CategoryGroup InterfaceCategory = "group" // 群组接口
CategoryHiL InterfaceCategory = "hil" // 人工介入接口
CategoryTransport InterfaceCategory = "transport" // 传输接口
CategorySecurity InterfaceCategory = "security" // 安全接口
CategoryManagement InterfaceCategory = "management" // 管理接口
)
// InterfaceMethod 接口方法
type InterfaceMethod struct {
Name string `json:"name"`
Description string `json:"description"`
InputType string `json:"input_type,omitempty"`
OutputType string `json:"output_type,omitempty"`
Required bool `json:"required"`
}
// =============================================================================
// Standard Interfaces Registry
// =============================================================================
// StandardInterfaces 定义Synapse协议的标准接口
var StandardInterfaces = map[string]ImplementedInterface{
// === Core Interfaces ===
"agent_identity": {
Name: "AgentIdentity",
Category: CategoryCore,
Version: "1.0.0",
ComplianceLevel: "basic",
Description: "提供Agent身份信息接口",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "GetAgentID", Description: "返回Agent唯一标识", OutputType: "string", Required: true},
{Name: "GetSystemID", Description: "返回Agent所属系统ID", OutputType: "string", Required: true},
{Name: "GetEndpoint", Description: "返回Agent回调端点", OutputType: "string", Required: true},
{Name: "GetName", Description: "返回Agent名称", OutputType: "string", Required: false},
{Name: "GetVersion", Description: "返回Agent版本", OutputType: "string", Required: false},
},
},
"message_handler": {
Name: "MessageHandler",
Category: CategoryCore,
Version: "1.0.0",
ComplianceLevel: "basic",
Description: "处理接收到的Synapse消息",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "HandleMessage", Description: "处理消息并返回响应", InputType: "UnifiedSynapseMessage", OutputType: "UnifiedSynapseMessage", Required: true},
{Name: "GetProtocolVersion", Description: "返回支持的协议版本", OutputType: "string", Required: true},
},
},
"capabilities_provider": {
Name: "CapabilitiesProvider",
Category: CategoryCore,
Version: "1.0.0",
ComplianceLevel: "basic",
Description: "暴露Agent能力信息",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "GetCapabilities", Description: "返回完整能力描述", OutputType: "AgentCapabilities", Required: true},
{Name: "GetSupportedInterfaces", Description: "返回实现的接口列表", OutputType: "[]ImplementedInterface", Required: true},
},
},
// === P2P Interfaces ===
"task_handler": {
Name: "P2PTaskHandler",
Category: CategoryP2P,
Version: "1.0.0",
ComplianceLevel: "basic",
Description: "处理P2P任务请求",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "ExecuteTask", Description: "执行任务并返回结果", InputType: "taskType, payload", OutputType: "any", Required: true},
{Name: "ValidateTask", Description: "验证任务是否可执行", InputType: "taskType, payload", OutputType: "[]MissingField, bool", Required: true},
{Name: "GetSupportedTaskTypes", Description: "返回支持的任务类型列表", OutputType: "[]string", Required: true},
{Name: "GetEstimatedDuration", Description: "预估任务执行时间", InputType: "taskType, payload", OutputType: "int", Required: false},
},
},
"callback_sender": {
Name: "CallbackSender",
Category: CategoryP2P,
Version: "1.0.0",
ComplianceLevel: "complete",
Description: "发送回调消息",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "SendCallback", Description: "发送回调消息", InputType: "endpoint, message", OutputType: "error", Required: true},
{Name: "SendCallbackWithRetry", Description: "带重试的回调发送", InputType: "endpoint, message, maxRetries", OutputType: "error", Required: false},
},
},
"callback_receiver": {
Name: "CallbackReceiver",
Category: CategoryP2P,
Version: "1.0.0",
ComplianceLevel: "basic",
Description: "接收回调消息的HTTP端点",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "POST /synapse/callback", Description: "接收回调消息的HTTP端点", InputType: "UnifiedSynapseMessage", OutputType: "HTTP 200", Required: true},
},
},
// === Polling Interfaces ===
"task_status_provider": {
Name: "TaskStatusProvider",
Category: CategoryP2P,
Version: "1.0.0",
ComplianceLevel: "complete",
Description: "提供任务状态查询接口",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "GET /synapse/task/{id}", Description: "查询单个任务状态", OutputType: "TaskStatusResponse", Required: true},
{Name: "GET /synapse/tasks", Description: "查询任务列表", OutputType: "TaskListResponse", Required: true},
{Name: "GET /synapse/task/{id}/events", Description: "查询任务事件流", OutputType: "TaskEventsResponse", Required: false},
},
},
"polling_client": {
Name: "PollingClient",
Category: CategoryTransport,
Version: "1.0.0",
ComplianceLevel: "complete",
Description: "轮询客户端",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "GetTaskStatus", Description: "获取任务状态", InputType: "taskID", OutputType: "TaskInfo", Required: true},
{Name: "StartPolling", Description: "开始持续轮询", InputType: "taskID, callback", OutputType: "error", Required: true},
{Name: "StopPolling", Description: "停止轮询", InputType: "taskID", Required: true},
},
},
// === Group Interfaces ===
"group_handler": {
Name: "GroupTaskHandler",
Category: CategoryGroup,
Version: "1.0.0",
ComplianceLevel: "advanced",
Description: "处理群组任务协作",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "ExecuteSubtask", Description: "执行子任务", InputType: "subtaskID, payload", OutputType: "any", Required: true},
{Name: "MergeResults", Description: "合并子任务结果", InputType: "results, strategy", OutputType: "any", Required: true},
{Name: "ValidateTaskSplit", Description: "验证任务分解方案", InputType: "taskType, requirements", OutputType: "[]Subtask, bool", Required: true},
},
},
"group_manager": {
Name: "GroupManager",
Category: CategoryGroup,
Version: "1.0.0",
ComplianceLevel: "advanced",
Description: "管理群组",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "CreateGroup", Description: "创建群组", InputType: "GroupConfig", OutputType: "Group", Required: true},
{Name: "JoinGroup", Description: "加入群组", InputType: "groupID", OutputType: "error", Required: true},
{Name: "LeaveGroup", Description: "离开群组", InputType: "groupID", OutputType: "error", Required: true},
{Name: "DisbandGroup", Description: "解散群组", InputType: "groupID", OutputType: "error", Required: true},
},
},
"voting_handler": {
Name: "VotingHandler",
Category: CategoryGroup,
Version: "1.0.0",
ComplianceLevel: "advanced",
Description: "处理投票共识",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "InitiateVote", Description: "发起投票", InputType: "VoteRequest", OutputType: "Vote", Required: true},
{Name: "SubmitVote", Description: "提交投票", InputType: "voteID, option", OutputType: "error", Required: true},
{Name: "GetVoteStatus", Description: "获取投票状态", InputType: "voteID", OutputType: "VoteStatus", Required: true},
},
},
// === HiL Interfaces ===
"hil_handler": {
Name: "HILHandler",
Category: CategoryHiL,
Version: "1.0.0",
ComplianceLevel: "complete",
Description: "处理人工介入请求",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "RequestHumanDecision", Description: "请求人工决策", InputType: "HILRequest", OutputType: "HILResponse", Required: true},
{Name: "ProcessHumanDecision", Description: "处理人工决策响应", InputType: "HILResponse", OutputType: "error", Required: true},
{Name: "GetHILStatus", Description: "获取HiL请求状态", InputType: "hilID", OutputType: "HILStatus", Required: true},
},
},
"escalation_handler": {
Name: "EscalationHandler",
Category: CategoryHiL,
Version: "1.0.0",
ComplianceLevel: "advanced",
Description: "处理升级请求",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "EscalateToHuman", Description: "升级到人工", InputType: "taskID, reason, level", OutputType: "HILRequest", Required: true},
{Name: "EscalateToGroup", Description: "升级到群组", InputType: "taskID, groupID", OutputType: "error", Required: true},
},
},
// === Transport Interfaces ===
"http_transport": {
Name: "HTTPTransport",
Category: CategoryTransport,
Version: "1.0.0",
ComplianceLevel: "basic",
Description: "HTTP传输",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "Send", Description: "发送HTTP消息", InputType: "endpoint, message", OutputType: "error", Required: true},
{Name: "Start", Description: "启动服务", Required: true},
{Name: "Stop", Description: "停止服务", Required: true},
},
},
"multipart_transport": {
Name: "MultipartTransport",
Category: CategoryTransport,
Version: "1.0.0",
ComplianceLevel: "complete",
Description: "Multipart文件传输",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "SendTaskWithFiles", Description: "发送带文件的任务", InputType: "endpoint, message, files", OutputType: "error", Required: true},
{Name: "ParseMultipartRequest", Description: "解析multipart请求", InputType: "HTTPRequest", OutputType: "Message, Files", Required: true},
},
},
"hybrid_transport": {
Name: "HybridTransport",
Category: CategoryTransport,
Version: "1.0.0",
ComplianceLevel: "complete",
Description: "混合传输(回调+轮询)",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "SendTaskWithHybridFallback", Description: "发送任务并自动切换", InputType: "endpoint, message, callback", OutputType: "error", Required: true},
{Name: "NotifyCallbackReceived", Description: "通知回调已收到", InputType: "taskID", Required: true},
},
},
"streaming_transport": {
Name: "StreamingTransport",
Category: CategoryTransport,
Version: "1.0.0",
ComplianceLevel: "advanced",
Description: "流式传输",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "WebSocket /synapse/stream", Description: "WebSocket流式端点", Required: true},
{Name: "GET /synapse/stream (SSE)", Description: "SSE流式端点", Required: false},
},
},
// === Security Interfaces ===
"authenticator": {
Name: "Authenticator",
Category: CategorySecurity,
Version: "1.0.0",
ComplianceLevel: "basic",
Description: "认证和签名",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "Authenticate", Description: "验证消息签名", InputType: "message, signature", OutputType: "bool", Required: true},
{Name: "Sign", Description: "签名消息", InputType: "message", OutputType: "string", Required: true},
},
},
"idempotency_handler": {
Name: "IdempotencyHandler",
Category: CategorySecurity,
Version: "1.0.0",
ComplianceLevel: "basic",
Description: "幂等性处理",
Status: "implemented",
Methods: []InterfaceMethod{
{Name: "CheckIdempotency", Description: "检查消息幂等性", InputType: "messageID", OutputType: "bool", Required: true},
{Name: "MarkProcessed", Description: "标记消息已处理", InputType: "messageID", Required: true},
},
},
}
// =============================================================================
// Extended AgentCapabilities
// =============================================================================
// ExtendedAgentCapabilities 扩展的Agent能力描述
type ExtendedAgentCapabilities struct {
// 基础能力
types.AgentCapabilities
// 实现的接口列表
ImplementedInterfaces []ImplementedInterface `json:"implemented_interfaces"`
// 合规级别 summary
ComplianceSummary ComplianceSummary `json:"compliance_summary"`
// 自定义能力
CustomCapabilities map[string]any `json:"custom_capabilities,omitempty"`
// 依赖的其他Agent
Dependencies []AgentDependency `json:"dependencies,omitempty"`
// 服务健康信息
HealthInfo HealthInfo `json:"health_info"`
}
// ComplianceSummary 合规级别摘要
type ComplianceSummary struct {
BasicImplemented int `json:"basic_implemented"`
BasicRequired int `json:"basic_required"`
CompleteImplemented int `json:"complete_implemented"`
CompleteRequired int `json:"complete_required"`
AdvancedImplemented int `json:"advanced_implemented"`
OverallLevel string `json:"overall_level"` // "basic", "complete", "advanced"
}
// AgentDependency Agent依赖
type AgentDependency struct {
AgentID string `json:"agent_id"`
SystemID string `json:"system_id"`
Required bool `json:"required"`
Description string `json:"description"`
}
// HealthInfo 健康信息
type HealthInfo struct {
Status string `json:"status"` // "healthy", "degraded", "unhealthy"
LastCheckAt string `json:"last_check_at"`
Uptime int64 `json:"uptime_seconds"`
Version string `json:"version"`
ActiveTasks int `json:"active_tasks"`
}
// =============================================================================
// HTTP Handler for Capabilities Endpoint
// =============================================================================
// CapabilitiesHandler 处理 GET /synapse/capabilities 请求
type CapabilitiesHandler struct {
provider CapabilitiesProvider
}
// NewCapabilitiesHandler 创建能力查询处理器
func NewCapabilitiesHandler(provider CapabilitiesProvider) *CapabilitiesHandler {
return &CapabilitiesHandler{provider: provider}
}
// ServeHTTP 处理HTTP请求
func (h *CapabilitiesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
ctx := r.Context()
// 获取能力
caps, err := h.provider.GetCapabilities(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 获取实现的接口
interfaces := h.provider.GetSupportedInterfaces()
// 构建扩展能力响应
extended := &ExtendedAgentCapabilities{
AgentCapabilities: *caps,
ImplementedInterfaces: interfaces,
ComplianceSummary: CalculateComplianceSummary(interfaces),
}
// 返回响应
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Synapse-Version", "1.0.0")
resp := types.NewCapabilitiesResponse(&extended.AgentCapabilities)
respJSON, _ := json.Marshal(resp)
w.Write(respJSON)
}
// =============================================================================
// Helper Functions
// =============================================================================
// CalculateComplianceSummary 计算合规摘要
func CalculateComplianceSummary(interfaces []ImplementedInterface) ComplianceSummary {
summary := ComplianceSummary{}
for _, iface := range interfaces {
switch iface.ComplianceLevel {
case "basic":
summary.BasicImplemented++
case "complete":
summary.CompleteImplemented++
case "advanced":
summary.AdvancedImplemented++
}
}
// 计算要求的数量
for _, std := range StandardInterfaces {
switch std.ComplianceLevel {
case "basic":
summary.BasicRequired++
case "complete":
summary.CompleteRequired++
}
}
// 确定整体级别
if summary.BasicImplemented >= summary.BasicRequired {
if summary.CompleteImplemented >= summary.CompleteRequired {
if summary.AdvancedImplemented > 0 {
summary.OverallLevel = "advanced"
} else {
summary.OverallLevel = "complete"
}
} else {
summary.OverallLevel = "basic"
}
} else {
summary.OverallLevel = "none"
}
return summary
}
// GetStandardInterface 获取标准接口定义
func GetStandardInterface(name string) *ImplementedInterface {
if iface, ok := StandardInterfaces[name]; ok {
return &iface
}
return nil
}
// GetAllStandardInterfaces 获取所有标准接口
func GetAllStandardInterfaces() map[string]ImplementedInterface {
return StandardInterfaces
}
// GetInterfacesByCategory 获取指定分类的接口
func GetInterfacesByCategory(category InterfaceCategory) []ImplementedInterface {
var result []ImplementedInterface
for _, iface := range StandardInterfaces {
if iface.Category == category {
result = append(result, iface)
}
}
return result
}
// GetInterfacesByComplianceLevel 获取指定合规级别的接口
func GetInterfacesByComplianceLevel(level string) []ImplementedInterface {
var result []ImplementedInterface
for _, iface := range StandardInterfaces {
if iface.ComplianceLevel == level {
result = append(result, iface)
}
}
return result
}
// CheckInterfaceCompliance 检查接口合规性
func CheckInterfaceCompliance(implemented []ImplementedInterface) map[string][]string {
result := map[string][]string{
"missing_basic": []string{},
"missing_complete": []string{},
"implemented": []string{},
}
implementedSet := make(map[string]bool)
for _, iface := range implemented {
implementedSet[iface.Name] = true
}
for name, std := range StandardInterfaces {
if implementedSet[std.Name] {
result["implemented"] = append(result["implemented"], name)
} else {
switch std.ComplianceLevel {
case "basic":
result["missing_basic"] = append(result["missing_basic"], name)
case "complete":
result["missing_complete"] = append(result["missing_complete"], name)
}
}
}
return result
}

View File

@@ -0,0 +1,328 @@
# 灵隙原始协议(Synapse Raw Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:纲领性文档
---
## 1. 协议愿景
灵隙(Synapse,原 Agent-to-Agent)协议旨在定义一套通用的、可扩展的智能体间通信标准,使不同系统、不同厂商、不同能力的智能体能够在统一的协议框架下进行协作、交易与能力组合。
本协议是灵隙协议族(Synapse Protocol Suite)的原始纲领,定义核心设计理念、架构原则与术语体系,为所有衍生协议提供顶层指导。
---
## 2. 核心设计理念
### 2.1 去中心化对等(Peer-to-Peer)
- 所有 Agent 在协议层面地位平等,无中心化控制节点
- 每个 Agent 既是服务提供者,也是服务消费者
- Agent 间直接通信,不依赖中间 broker(传输层可选择不同实现)
### 2.2 能力驱动(Capability-Driven)
- 通信以能力为核心,而非以身份为核心
- Agent 声明自身能力,按需发现与调用
- 能力可组合、可分解、可升级
### 2.3 任务导向(Task-Oriented)
- 所有交互围绕"任务"展开
- 任务有明确的生命周期、状态机与超时机制
- 任务支持拆分、合并、委派与协作
### 2.4 人类可介入(Human-in-the-Loop)
- 协议原生支持人类介入决策
- Agent 可主动请求人类判断
- 人类决策优先于 Agent 自动决策
- 支持多级升级(escalation)机制
### 2.5 群组协作(Group Collaboration)
- 支持多 Agent 组成临时或永久群组
- 群组内有角色分工与权限控制
- 支持任务拆分-执行-合并的 MapReduce 模式
- 支持投票、共识等群体决策机制
### 2.6 传输无关(Transport Agnostic)
- 协议语义与底层传输分离
- 支持 HTTP/2、gRPC、WebSocket、消息队列等多种传输
- 支持回调(Callback)、轮询(Polling)、同步(Sync)、混合(Hybrid)等多种交互模式
### 2.7 安全可信(Secure & Trustworthy)
- 消息级签名与验证
- Agent 身份认证与信任等级
- 支持加密传输
- 可审计、可追溯
---
## 3. 协议分层架构
```
┌─────────────────────────────────────────┐
│ 应用层 (Application) │
│ 业务逻辑、工作流、领域特定逻辑 │
├─────────────────────────────────────────┤
│ 协议层 (Protocol) │
├─────────────┬───────────────────────────┤
│ A2A 协议 │ A2N 组群协议 │ ← 本协议族
│ (P2P) │ (Group) │
├─────────────┴───────────────────────────┤
│ 传输层 (Transport) │
│ HTTP / gRPC / WebSocket / MQ ... │
├─────────────────────────────────────────┤
│ 网络层 (Network) │
│ TCP / UDP / QUIC ... │
└─────────────────────────────────────────┘
```
### 3.1 协议族构成
| 协议名称 | 缩写 | 描述 | 适用场景 |
|---------|------|------|---------|
| 灵隙原始协议(Synapse Raw Protocol) | A2A-Raw | 顶层纲领,定义理念、术语、原则 | 指导所有协议设计 |
| A2A 协议 | A2A-Base | 点对点任务协作协议 | 两个 Agent 间的任务交互 |
| A2N 组群协议 | A2N-Group | 多 Agent 群组协作协议 | 多个 Agent 协同完成任务 |
| P2A 调用协议 | P2A-Caller | 非 Agent 调用者接入协议 | 普通服务/前端调用 Agent 能力 |
---
## 4. 核心概念与术语
### 4.1 Agent(智能体)
能够自主感知环境、做出决策并执行动作的计算实体。
**属性:**
- `agent_id`:Agent 唯一标识
- `system_id`:所属系统标识
- `endpoint`:通信端点地址
- `capabilities`:能力清单
- `trust_level`:信任等级
### 4.2 Task(任务)
Agent 间协作的基本单位,具有明确的输入、输出与生命周期。
**属性:**
- `task_id`:任务唯一标识
- `task_type`:任务类型
- `payload`:任务载荷
- `status`:任务状态
- `priority`:优先级
- `deadline`:截止时间
### 4.3 Message(消息)
Agent 间通信的基本单位。
**属性:**
- `message_id`:消息唯一标识
- `type`:消息类型
- `sender`:发送方
- `receiver`:接收方(P2P)
- `group_id`:群组标识(群组)
- `payload`:消息载荷
- `correlation_id`:关联标识(用于请求-响应关联)
- `timestamp`:时间戳
- `signature`:签名
### 4.4 Capability(能力)
Agent 能够执行的任务类型的声明。
**属性:**
- `type`:能力对应的任务类型
- `name`:能力名称
- `description`:能力描述
- `input_schema`:输入参数 schema
- `output_schema`:输出结果 schema
- `estimated_time`:预估耗时
### 4.5 Group(群组)
由多个 Agent 组成的协作单元。
**属性:**
- `group_id`:群组唯一标识
- `name`:群组名称
- `topology`:拓扑结构
- `members`:成员列表
- `roles`:角色定义
- `status`:群组状态
### 4.6 Human-in-the-Loop(人在回路)
在 Agent 协作过程中,人类可介入进行决策的机制。
**关键概念:**
- `escalation`:升级(将问题提交给人类)
- `decision`:决策(人类做出的判断)
- `resume`:恢复(人类决策后继续执行)
---
## 5. 设计原则
### 5.1 简约性(Simplicity)
- 核心机制保持简单
- 复杂功能通过组合基础机制实现
- 可选功能通过能力声明来协商
### 5.2 可扩展性(Extensibility)
- 消息类型可扩展
- 能力类型可扩展
- 传输方式可扩展
- 通过版本号与能力协商保证向后兼容
### 5.3 容错性(Fault Tolerance)
- 网络不可靠是常态
- 消息可能丢失、重复、延迟
- Agent 可能故障、离线
- 协议必须有重试、超时、降级机制
### 5.4 可观测性(Observability)
- 所有交互可追踪
- 任务状态可查询
- 支持日志、指标、链路追踪
### 5.5 互操作性(Interoperability)
- 不同语言、不同框架的 Agent 可互操作
- 协议规范优先于具体实现
- 提供标准测试用例验证兼容性
---
## 6. 消息模型总则
### 6.1 统一消息信封
所有协议共享统一的消息信封结构,具体载荷由消息类型决定。
```
UnifiedSynapseMessage
├── id // 消息ID
├── version // 协议版本
├── type // 消息类型
├── sender // 发送方 AgentEndpoint
├── receiver? // 接收方(P2P 时)
├── group_id? // 群组ID(群组时)
├── correlation_id // 关联ID
├── reply_to? // 回复的消息ID
├── payload // 消息载荷(JSON)
├── priority? // 优先级
├── signature? // 签名
├── timestamp // 发送时间
└── expires_at? // 过期时间
```
### 6.2 消息命名规范
- 使用 `领域:动作` 格式
- 全小写,用下划线分隔
- 示例:`p2p:task_request`, `group:create`, `hil:decision`
### 6.3 请求-响应模式
- 请求消息必须携带唯一 `id`
- 响应消息通过 `reply_to` 关联请求
- 支持 ACK/NACK 机制
- 超时未收到响应应重试或报错
---
## 7. 任务生命周期总则
### 7.1 状态机
```
┌──────────┐
│ pending │ 待确认
└────┬─────┘
│ 确认/拒绝
┌─────┴──────┐
▼ ▼
┌─────────┐ ┌──────────┐
│confirmed│ │ rejected │ 已拒绝(终态)
└────┬────┘ └──────────┘
│ 开始执行
▼
┌──────────┐
│executing │ 执行中
└────┬─────┘
│ 信息缺失
┌────▼─────┐
│info_needed│ 需要信息
└────┬─────┘
│ 信息补充
▼
回到 executing
│
├──────────────┐
│ 需人类决策 │
▼ ▼
┌──────────┐ ┌───────────┐
│need_human│ │ 各种终态 │
└────┬─────┘ └───────────┘
│ 人类决策
▼
回到 executing
│
┌────┴────┬─────────┐
▼ ▼ ▼
completed failed cancelled
(完成) (失败) (取消)
```
### 7.2 终态
- `completed`:成功完成
- `failed`:执行失败
- `cancelled`:被取消
- `rejected`:被拒绝
---
## 8. 安全模型总则
### 8.1 身份认证
- 每个 Agent 有唯一身份
- 支持 HMAC、JWT、OAuth2 等多种认证方式
- 信任等级机制
### 8.2 消息完整性
- 消息签名机制
- 防止篡改与伪造
### 8.3 机密性
- 支持端到端加密
- 敏感载荷加密传输
### 8.4 访问控制
- 基于信任等级的权限控制
- 群组内基于角色的权限控制
---
## 9. 非目标
以下内容**不在**本原始协议的讨论范围内:
1. **具体传输协议的实现细节** - 由各传输层实现定义
2. **特定领域的任务类型** - 由应用层或领域扩展定义
3. **Agent 内部实现架构** - 仅定义对外接口与行为
4. **具体编程语言绑定** - 语言无关,可多语言实现
5. **UI/UX 设计** - 人机界面不在协议范围内
---
## 10. 参考实现
本协议族的参考实现位于:`dp-headless/synapse/`
| 模块 | 对应协议 | 文件路径 |
|------|---------|---------|
| types 包 | 公共类型定义 | `types/` |
| p2p 包 | A2A 协议 | `p2p/` |
| group 包 | A2N 组群协议 | `group/` |
| externalapi 包 | P2A 调用协议 | `externalapi/` |
| sdk 包 | P2A 客户端 SDK | `sdk/` |
---
## 附录 A:协议演进
本原始协议作为顶层指导,保持相对稳定。各具体协议可独立版本化演进,但需遵循本协议定义的核心原则。
**版本历史:**
- v1.0.0:初始版本,定义核心概念与设计原则

465
docs/02-p2p-protocol.md Normal file
View File

@@ -0,0 +1,465 @@
# A2A 协议(Agent-to-Agent Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:实现规范
> 基于:[灵隙原始协议(Synapse Raw Protocol) v1.0.0](./01-a2a-raw-protocol.md)
---
## 1. 概述
A2A 协议定义了两个 Agent 之间进行点对点(P2P)任务协作的完整规范,包括消息类型、交互流程、状态机、接口定义与错误处理。
### 1.1 适用场景
- 单个任务请求与响应
- 任务状态实时同步
- 任务取消
- 信息补全交互
- 人类介入决策
### 1.2 非适用场景
- 多 Agent 群组协作(参见 A2N 组群协议)
- 非 Agent 调用方接入(参见 P2A 调用协议)
---
## 2. 消息类型
### 2.1 消息类型清单
所有消息类型前缀:`p2p:`
| 消息类型 | 方向 | 说明 |
|---------|------|------|
| `p2p:task_request` | 请求方 → 执行方 | 提交任务请求 |
| `p2p:task_confirm` | 执行方 → 请求方 | 确认接受任务 |
| `p2p:task_reject` | 执行方 → 请求方 | 拒绝任务 |
| `p2p:task_need_info` | 执行方 → 请求方 | 请求补充信息 |
| `p2p:task_info_provided` | 请求方 → 执行方 | 提供补充信息 |
| `p2p:task_status_update` | 执行方 → 请求方 | 任务进度更新 |
| `p2p:task_complete` | 执行方 → 请求方 | 任务完成 |
| `p2p:task_failed` | 执行方 → 请求方 | 任务失败 |
| `p2p:task_cancel` | 请求方 → 执行方 | 取消任务 |
| `p2p:task_ack` | 双向 | 任务级确认 |
| `p2p:ack` | 双向 | 通用确认 |
| `p2p:nack` | 双向 | 通用否定确认(错误) |
| `p2p:heartbeat` | 双向 | 心跳保活 |
| `p2p:capability_query` | 双向 | 查询对方能力 |
| `p2p:capability_response` | 双向 | 返回能力清单 |
| `p2p:task_need_human` | 执行方 → 请求方 | 请求人类介入 |
| `p2p:human_decision` | 请求方 → 执行方 | 人类决策结果 |
### 2.2 消息类型完整定义
参见代码:[types/types.go](../types/types.go)
---
## 3. 任务状态机
### 3.1 状态定义
| 状态 | 说明 |
|------|------|
| `idle` | 空闲(初始状态) |
| `pending` | 待确认(已发送请求,等待确认) |
| `confirmed` | 已确认(对方接受任务) |
| `executing` | 执行中 |
| `info_needed` | 需要信息 |
| `info_provided` | 信息已提供 |
| `need_human` | 需要人类介入 |
| `human_deciding` | 人类决策中 |
| `human_approved` | 人类已批准 |
| `human_rejected` | 人类已拒绝 |
| `completed` | 已完成(终态) |
| `failed` | 已失败(终态) |
| `cancelled` | 已取消(终态) |
| `rejected` | 已拒绝(终态) |
### 3.2 状态流转图
```
请求方视角:
idle → pending → confirmed → executing → completed
↓ ↓ → failed
rejected info_needed → cancelled
↑↓
info_provided
↓
need_human
↑↓
human_decision
```
---
## 4. 核心交互流程
### 4.1 正常任务提交流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->| 1. 提交任务
| |
|<------- task_confirm / task_reject -| 2. 确认/拒绝
| |
|<------- task_status_update --------| 3. 进度更新(多次)
| |
|<------- task_complete -------------| 4. 完成通知
| |
|-------- task_ack ----------------->| 5. 确认收到
| |
```
### 4.2 信息补全流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->|
| |
|<------- task_need_info ------------| 发现信息缺失
| |
|-------- task_info_provided ------->| 补充信息
| |
|<------- task_status_update --------| 继续执行
| |
```
### 4.3 人类介入流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->|
| |
|<------- task_need_human -----------| 需要人类决策
| |
| [人类做出决策] |
| |
|-------- human_decision ----------->| 决策结果
| |
|<------- task_status_update --------| 继续执行
| |
```
### 4.4 任务取消流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->|
| |
|-------- task_cancel -------------->| 取消任务
| |
|<------- task_ack ------------------| 确认取消
| |
```
---
## 5. 接口规范
### 5.1 P2P 协议处理器接口
**接口定义:**
```go
type P2PProtocol interface {
// 处理收到的消息
HandleMessage(ctx context.Context, msg *UnifiedSynapseMessage) (*UnifiedSynapseMessage, error)
// 设置任务处理器
SetTaskHandler(h TaskHandler)
// 设置回调发送器
SetCallbackSender(s CallbackSender)
// 获取 Agent 端点
AgentEndpoint() AgentEndpoint
// 查询任务状态
GetTaskStatus(taskID string) (P2PTaskStatus, bool)
// 列出所有任务
ListTasks() map[string]P2PTaskStatus
}
```
**参考实现:** [p2p/protocol.go](../p2p/protocol.go)
### 5.2 任务处理器接口
```go
type TaskHandler interface {
// 执行任务并返回结果
ExecuteTask(ctx context.Context, taskType string, payload map[string]interface{}) (any, error)
// 验证任务是否可执行,返回缺失字段列表和是否可处理
ValidateTask(taskType string, payload map[string]interface{}) ([]MissingField, bool)
// 获取能力列表
GetCapabilities() []string
}
```
**说明:**
- `ExecuteTask`:实际执行业务逻辑的入口
- `ValidateTask`:在任务确认前调用,用于判断是否接受任务或需要补充信息
- `GetCapabilities`:返回 Agent 支持的任务类型列表
### 5.3 回调发送器接口
```go
type CallbackSender interface {
// 向远程 Agent 发送消息
SendCallback(ctx context.Context, endpoint string, msg *UnifiedSynapseMessage) error
}
```
**说明:**
- 传输层实现此接口
- 支持多种传输方式(HTTP、gRPC、WebSocket 等)
### 5.4 配置项
```go
type P2PConfig struct {
MaxRetries int // 最大重试次数(默认 5)
RetryBaseDelay time.Duration // 重试基础延迟(默认 2s,指数退避)
TaskTimeout time.Duration // 任务执行超时(默认 5 分钟)
HeartbeatInterval time.Duration // 心跳间隔(默认 30 秒)
}
```
---
## 6. 载荷格式
### 6.1 TaskRequestPayload(任务请求)
```json
{
"task": {
"id": "task-xxx",
"type": "text_generation",
"priority": "normal",
"deadline": "2026-01-01T00:00:00Z",
"payload": {
"prompt": "写一首诗",
"style": "古典"
}
},
"context": {
"session_id": "sess-xxx",
"parent_task_id": "parent-xxx",
"correlation_id": "corr-xxx",
"metadata": {}
}
}
```
### 6.2 TaskConfirmPayload(任务确认)
```json
{
"task_id": "task-xxx",
"confirmed": true,
"estimated_duration": 120,
"expected_completion": "2026-01-01T00:02:00Z",
"capabilities_used": ["text_generation"],
"constraints": {
"max_retries": 3,
"fallback_enabled": true
}
}
```
### 6.3 TaskRejectPayload(任务拒绝)
```json
{
"task_id": "task-xxx",
"reason": {
"code": "capability_lacking",
"message": "不支持该任务类型",
"details": null
},
"alternatives": {
"suggested_agent_id": "other-agent",
"retry_after": 300,
"partial_support": false
}
}
```
### 6.4 TaskStatusUpdatePayload(状态更新)
```json
{
"task_id": "task-xxx",
"status": "executing",
"progress": 50,
"message": "正在生成内容",
"intermediate_result": null
}
```
### 6.5 TaskCompletePayload(任务完成)
```json
{
"task_id": "task-xxx",
"result": {
"output_type": "text",
"output": "生成的文本内容...",
"metadata": {
"tokens": 1024,
"model": "gpt-4"
}
},
"summary": "任务已完成"
}
```
### 6.6 TaskFailedPayload(任务失败)
```json
{
"task_id": "task-xxx",
"error": {
"code": "execution_error",
"message": "模型调用超时",
"recoverable": true,
"details": null
},
"partial_result": null
}
```
### 6.7 TaskNeedInfoPayload(需要信息)
```json
{
"task_id": "task-xxx",
"missing_fields": [
{
"field": "style",
"reason": "未指定生成风格",
"suggested_value": "古典",
"alternatives": ["现代", "浪漫"]
}
],
"question": "请选择生成风格",
"required_by": "2026-01-01T00:01:00Z"
}
```
### 6.8 TaskNeedHumanPayload(需要人类)
```json
{
"task_id": "task-xxx",
"reason": {
"code": "ethical_decision",
"message": "涉及伦理决策需要人工审核"
},
"context": {
"task_summary": "生成一张人物肖像",
"current_progress": 30,
"blocked_reason": "需要确认肖像使用授权"
},
"options": [
{
"field": "authorized",
"value": true,
"pros_cons": "继续生成,假设已授权",
"confidence": 70
}
],
"escalation_level": 1,
"deadline": "2026-01-01T00:05:00Z"
}
```
### 6.9 HumanDecisionPayload(人类决策)
```json
{
"task_id": "task-xxx",
"decision_id": "dec-xxx",
"reply_to": "msg-xxx",
"decided_by": {
"user_id": "user-123",
"user_name": "张三",
"role": "reviewer"
},
"decision": {
"action": "approve",
"approved_fields": {
"authorized": true
},
"rejection_reason": ""
},
"notes": "已确认授权",
"timestamp": "2026-01-01T00:03:00Z"
}
```
**完整载荷定义:** [types/types.go](../types/types.go)
---
## 7. 错误码
| 错误码 | 说明 | HTTP 状态码(如适用) |
|--------|------|---------------------|
| `message_format_invalid` | 消息格式无效 | 400 |
| `capability_lacking` | 能力不足 | 403 |
| `agent_not_ready` | Agent 未就绪 | 503 |
| `task_not_found` | 任务不存在 | 404 |
| `task_already_completed` | 任务已完成 | 409 |
| `execution_timeout` | 执行超时 | 504 |
| `execution_failed` | 执行失败 | 500 |
| `permission_denied` | 权限不足 | 403 |
| `invalid_params` | 参数无效 | 400 |
| `rate_limited` | 限流 | 429 |
**完整错误定义:** [types/errors.go](../types/errors.go)
---
## 8. 传输要求
### 8.1 HTTP 传输
- Content-Type: `application/json`
- 请求方法:`POST`
- 端点路径:由 Agent 自行定义,建议 `/synapse/callback`
- 超时:建议 30 秒
### 8.2 签名验证(可选)
- 使用 HMAC-SHA256 签名
- 签名放在 Header:`X-Synapse-Signature`
- 或放在消息体:`signature` 字段
---
## 9. 参考实现
本协议的参考实现位于 `p2p/` 目录:
| 文件 | 说明 |
|------|------|
| [p2p/protocol.go](../p2p/protocol.go) | P2P 协议核心实现 |
| [types/types.go](../types/types.go) | 数据类型定义 |
| [types/errors.go](../types/errors.go) | 错误定义 |
| [transport/transport.go](../transport/transport.go) | HTTP 传输实现 |
---
## 附录 A:能力声明格式
参见 [灵隙原始协议(Synapse Raw Protocol) - Capability](./01-a2a-raw-protocol.md#44-capability能力)

545
docs/03-group-protocol.md Normal file
View File

@@ -0,0 +1,545 @@
# A2N 协议(Agent-to-N Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:实现规范
> 基于:[灵隙原始协议(Synapse Raw Protocol) v1.0.0](./01-a2a-raw-protocol.md)
---
## 1. 概述
A2N(Agent-to-N)组群协议定义了多个 Agent 组成群组进行协同工作的完整规范,包括群组管理、任务拆分与合并、群体决策、角色与权限等。
### 1.1 适用场景
- 复杂任务的拆分与并行执行(MapReduce 模式)
- 多 Agent 协同完成单一目标
- 群体投票与共识决策
- 临时或永久的 Agent 工作组
- 分级升级(escalation)机制
### 1.2 依赖
- 依赖 A2A 协议的点对点通信能力
- 群组内成员间的单播通信使用 A2A 协议
---
## 2. 核心概念
### 2.1 Group(群组)
由多个 Agent 组成的协作单元,具有明确的拓扑结构、角色分工与协作规则。
### 2.2 Topology(拓扑结构)
| 拓扑类型 | 说明 | 适用场景 |
|---------|------|---------|
| `star` | 星型,中心 coordinator 协调 | 简单任务分发 |
| `mesh` | 网状,成员间可直接通信 | 对等协作 |
| `chain` | 链式,流水线模式 | 顺序处理 |
| `hierarchical` | 层级型,多层管理 | 大型复杂任务 |
| `fullmesh` | 全连接网状 | 高可靠协作 |
### 2.3 Role(角色)
| 角色 | 说明 | 权限 |
|------|------|------|
| `coordinator` | 协调者 | 创建/解散群组、任务拆分、发起投票 |
| `worker` | 工作者 | 执行子任务、提交结果 |
| `reviewer` | 审核者 | 审核结果、发起投票 |
| `observer` | 观察者 | 只读,查看进度 |
### 2.4 GroupStatus(群组状态)
| 状态 | 说明 |
|------|------|
| `creating` | 创建中 |
| `active` | 活跃 |
| `frozen` | 冻结(不可执行新任务) |
| `disbanded` | 已解散(终态) |
---
## 3. 消息类型
### 3.1 消息类型清单
所有消息类型前缀:`group:` 或 `inter_group:`
#### 群组管理
| 消息类型 | 说明 |
|---------|------|
| `group:create` | 创建群组 |
| `group:disband` | 解散群组 |
| `group:join` | 加入群组 |
| `group:leave` | 离开群组 |
| `group:invite` | 邀请加入 |
| `group:accept_invite` | 接受邀请 |
| `group:reject_invite` | 拒绝邀请 |
| `group:member_update` | 成员更新通知 |
| `group:role_change` | 角色变更 |
#### 群组通信
| 消息类型 | 说明 |
|---------|------|
| `group:broadcast` | 广播(所有成员) |
| `group:multicast` | 组播(指定成员) |
| `group:direct` | 群内单播 |
| `group:relay` | 消息中继 |
#### 任务协作
| 消息类型 | 说明 |
|---------|------|
| `group:task_split` | 任务拆分 |
| `group:task_assign` | 任务分配 |
| `group:task_merge` | 结果合并 |
| `group:task_progress` | 子任务进度 |
#### 共识与投票
| 消息类型 | 说明 |
|---------|------|
| `group:vote` | 发起投票 |
| `group:vote_submit` | 提交投票 |
| `group:consensus` | 达成共识 |
#### 人在回路(HiL)
| 消息类型 | 说明 |
|---------|------|
| `group:consensus_escalate` | 共识僵局升级给人类 |
| `group:subtask_escalate` | 子任务阻塞升级给人类 |
| `group:human_decision` | 人类决策结果 |
| `inter_group:escalate` | 跨群组升级 |
**完整定义:** [types/types.go](../types/types.go)
---
## 4. 群组生命周期
### 4.1 状态机
```
creating → active → frozen → disbanded
↑ ↓
└─────────┘
解冻
```
### 4.2 群组创建流程
```
创建者 被邀请成员
| |
|------ group:invite -------------->| 发送邀请
| |
|<----- group:accept_invite --------| 接受邀请
| |
|------ group:member_update ------->| 通知成员更新
| |
[群组创建完成,状态变为 active] |
```
### 4.3 群组解散流程
```
coordinator 所有成员
| |
|------ group:disband ------------->| 通知解散
| |
[群组状态变为 disbanded] |
```
---
## 5. 任务协作流程
### 5.1 MapReduce 模式(拆分-执行-合并)
```
┌──────────────┐
│ coordinator │
└──────┬───────┘
│
group:task_split(拆分任务)
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ worker1 │ │ worker2 │ │ worker3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
│ 执行子任务 │ 执行子任务 │ 执行子任务
│ │ │
▼ ▼ ▼
子任务结果 子任务结果 子任务结果
└────────────────┼────────────────┘
│
group:task_merge(合并结果)
▼
┌──────────────┐
│ 最终结果 │
└──────────────┘
```
### 5.2 详细流程
1. **任务拆分阶段**
- coordinator 接收原始任务
- 将任务拆分为多个子任务(subtask)
- 为每个子任务指定执行的 worker
- 定义合并策略(merge strategy)
2. **并行执行阶段**
- 各 worker 并行执行子任务
- 定期上报进度(`group:task_progress`)
- 子任务完成后上报结果
3. **结果合并阶段**
- 所有子任务完成后,coordinator 合并结果
- 根据合并策略决定最终结果
- 输出最终结果
---
## 6. 合并策略(Merge Strategy)
| 策略 | 说明 |
|------|------|
| `all_success` | 全部成功才认为成功,否则失败 |
| `majority` | 多数子任务成功即可,取多数结果 |
| `any_complete` | 任意一个完成即返回结果 |
| `priority` | 按优先级,高优先级子任务结果优先 |
---
## 7. 投票与共识
### 7.1 投票流程
```
发起者 投票者
| |
|-------- group:vote -------------->| 发起投票
| |
|<------- group:vote_submit --------| 提交投票
| |
[计票,判断是否达到阈值] |
| |
|-------- group:consensus --------->| 公布结果
| |
```
### 7.2 投票规则
- `threshold`:通过阈值(0-1),如 0.5 表示过半
- `deadline`:投票截止时间
- `voters`:有投票权的成员列表
- 超时未投票视为弃权
### 7.3 共识僵局(Consensus Deadlock)
当投票无法达成共识(如平票、未达阈值且超时)时,可升级给人类决策。
---
## 8. 人在回路(HiL)
群组协作中有两种升级场景:
### 8.1 共识僵局升级
投票无法达成共识时,升级给人类裁决。
**消息:** `group:consensus_escalate`
### 8.2 子任务阻塞升级
某个子任务遇到无法自动解决的问题时,升级给人类。
**消息:** `group:subtask_escalate`
### 8.3 跨群组升级
问题超出当前群组能力时,可升级到上级群组或人类。
**消息:** `inter_group:escalate`
---
## 9. 接口规范
### 9.1 GroupProtocol 接口
```go
type GroupProtocol interface {
// 处理群组消息
HandleMessage(ctx context.Context, msg *UnifiedSynapseMessage) (*UnifiedSynapseMessage, error)
// 设置任务处理器
SetTaskHandler(h GroupTaskHandler)
// 查询群组列表
GetGroups() []*AgentGroup
// 查询特定群组
GetGroup(groupID string) (*AgentGroup, bool)
}
```
**参考实现:** [group/protocol.go](../group/protocol.go)
### 9.2 GroupTaskHandler 接口
```go
type GroupTaskHandler interface {
// 执行子任务
ExecuteSubtask(ctx context.Context, subtaskID string, payload map[string]interface{}) (any, error)
// 合并多个子任务结果
MergeResults(results []SubtaskResult, strategy MergeStrategy) (any, error)
// 验证任务是否可拆分,返回拆分方案
ValidateTaskSplit(taskType string, requirements map[string]interface{}) ([]Subtask, bool)
}
```
**说明:**
- `ExecuteSubtask`:执行单个子任务的业务逻辑
- `MergeResults`:根据策略合并多个子任务的结果
- `ValidateTaskSplit`:验证任务是否可拆分,并给出拆分方案
### 9.3 配置项
```go
type GroupConfig struct {
MaxGroupSize int // 最大群组人数(默认 20)
MaxSubtasks int // 最大子任务数(默认 50)
ConsensusTimeout time.Duration // 共识超时(默认 5 分钟)
TaskTimeout time.Duration // 任务超时(默认 10 分钟)
}
```
---
## 10. 载荷格式
### 10.1 GroupCreatePayload(创建群组)
```json
{
"group": {
"name": "视频制作组",
"topology": "star",
"config": {
"allow_self_leave": true,
"requires_approval": false,
"consensus_required": false,
"consensus_threshold": 0.5,
"broadcast_mode": "all"
},
"initial_members": [
{
"agent_id": "worker-1",
"system_id": "sys-a",
"endpoint": "http://worker1/synapse"
}
],
"roles": [
{
"id": "coordinator",
"name": "协调者",
"permissions": ["create", "disband", "split", "vote"],
"max_tasks": 10
}
]
}
}
```
### 10.2 GroupTaskSplitPayload(任务拆分)
```json
{
"original_task": {
"id": "task-xxx",
"type": "video_generation",
"requirements": {
"script": "xxx",
"duration": 60
},
"deadline": "2026-01-01T00:10:00Z"
},
"subtasks": [
{
"id": "sub-1",
"description": "生成脚本",
"assigned_to": "writer-agent",
"dependencies": [],
"estimated_duration": 120,
"priority": 1
},
{
"id": "sub-2",
"description": "生成配音",
"assigned_to": "voice-agent",
"dependencies": ["sub-1"],
"estimated_duration": 180,
"priority": 2
}
],
"merge_strategy": "all_success"
}
```
### 10.3 GroupTaskMergePayload(结果合并)
```json
{
"original_task_id": "task-xxx",
"subtask_results": [
{
"subtask_id": "sub-1",
"assigned_to": "writer-agent",
"status": "success",
"result": { "script": "..." },
"completed_at": "2026-01-01T00:02:00Z"
}
],
"merge_strategy_used": "all_success",
"merged_result": {
"video_url": "http://..."
},
"consensus_achieved": true
}
```
### 10.4 GroupVotePayload(投票)
```json
{
"vote_id": "vote-xxx",
"question": "是否采用方案A?",
"options": ["方案A", "方案B", "方案C"],
"voters": ["agent-1", "agent-2", "agent-3"],
"deadline": "2026-01-01T00:05:00Z",
"threshold": 0.66,
"status": "open",
"votes": []
}
```
### 10.5 GroupConsensusEscalatePayload(共识升级)
```json
{
"vote_id": "vote-xxx",
"escalation_id": "esc-xxx",
"question": "是否采用方案A?",
"votes": [
{
"voter_id": "agent-1",
"voter_role": "reviewer",
"option": "方案A",
"reason": "质量更好"
}
],
"consensus_status": {
"required_threshold": 0.66,
"actual_threshold": 0.33,
"leading_option": "方案A",
"options_tied": false
},
"escalate_reason": {
"code": "no_consensus",
"message": "无法达成共识"
},
"escalation_level": 2,
"deadline": "2026-01-01T00:10:00Z"
}
```
**完整载荷定义:** [types/types.go](../types/types.go)
---
## 11. 数据结构
### 11.1 AgentGroup(群组)
```go
type AgentGroup struct {
ID string
Name string
Topology GroupTopology
CreatedBy string
CreatedAt Timestamp
Config GroupConfig
Members []GroupMember
Roles []GroupRole
Status GroupStatus
}
```
### 11.2 GroupMember(群组成员)
```go
type GroupMember struct {
AgentID string
AgentName string
Role string
JoinedAt Timestamp
Status GroupMemberStatus
Capabilities []string
LoadFactor float64 // 负载因子 0-1,用于任务分配
}
```
### 11.3 Subtask(子任务)
```go
type Subtask struct {
ID string
Description string
AssignedTo string
Dependencies []string
EstimatedDuration int
Priority int
}
```
---
## 12. 错误码
群组协议特有错误码:
| 错误码 | 说明 |
|--------|------|
| `group_not_found` | 群组不存在 |
| `group_not_active` | 群组不活跃 |
| `group_full` | 群组已满 |
| `already_member` | 已是成员 |
| `not_group_member` | 不是群组成员 |
| `member_not_found` | 成员不存在 |
| `permission_denied` | 权限不足 |
| `invalid_params` | 参数无效 |
---
## 13. 参考实现
本协议的参考实现位于 `group/` 目录:
| 文件 | 说明 |
|------|------|
| [group/protocol.go](../group/protocol.go) | 群组协议核心实现 |
| [types/types.go](../types/types.go) | 群组相关数据类型 |
---
## 附录 A:与 A2A 协议的关系
- 群组内的点对点通信使用 A2A 协议
- 群组协议在 A2A 协议之上提供群组管理、任务拆分合并、共识投票等能力
- 群组消息同样使用统一消息信封(`UnifiedSynapseMessage`),通过 `group_id` 字段标识群组上下文

503
docs/04-caller-protocol.md Normal file
View File

@@ -0,0 +1,503 @@
# P2A 协议(Person/Program-to-Agent Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:实现规范
> 基于:[灵隙原始协议(Synapse Raw Protocol) v1.0.0](./01-a2a-raw-protocol.md)
---
## 1. 概述
P2A(Person/Program-to-Agent)调用协议定义了**非 Agent 调用方**(如普通后端服务、前端应用、第三方系统、人类用户)如何调用 Agent 能力的规范。
本协议将 A2A 协议的复杂性封装起来,提供简洁的 RESTful API,使调用方无需了解 A2A 协议细节即可使用 Agent 能力。
### 1.1 适用场景
- 前端应用直接调用 Agent 能力
- 后端业务系统集成 Agent 服务
- 第三方开发者通过 API 接入
- 无需实现完整 A2A 协议的调用方
### 1.2 设计原则
- **简单易用**:标准 RESTful API,JSON 格式
- **两种模式**:回调模式 + 轮询模式,调用方按需选择
- **认证简单**:API Key 认证,无需 HMAC 签名
- **文件支持**:支持 multipart/form-data 上传文件
- **错误友好**:清晰的错误码和错误信息
### 1.3 与 A2A 协议的关系
```
外部调用方 ──P2A 协议──▶ External API Layer ──A2A 协议──▶ Agent
```
P2A 协议是 A2A 协议的"简化入口",所有请求最终会转换为 灵隙消息发送给 Agent。
---
## 2. 认证方式
### 2.1 API Key 认证
- 在 HTTP Header 中传递:`X-API-Key: {api_key}`
- 或在 URL Query 中传递:`?api_key={api_key}`
- 如果服务端未配置 API Key,则跳过认证
### 2.2 回调验证(可选)
如果调用方提供了 `callback_token`,则服务端在回调时会在 Header 中带上 `X-Callback-Token`,用于调用方验证回调来源。
---
## 3. API 端点
所有端点默认前缀:`/api/v1/synapse`
### 3.1 端点清单
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/task` | 提交任务(JSON) |
| POST | `/task/multipart` | 提交任务(带文件上传) |
| GET | `/task/{task_id}` | 获取任务详情 |
| GET | `/task/{task_id}/status` | 获取任务状态(简化版) |
| GET | `/tasks` | 获取任务列表 |
| POST | `/task/{task_id}/cancel` | 取消任务 |
| GET | `/capabilities` | 获取能力清单 |
| POST | `/callback` | 接收回调(调用方实现) |
---
## 4. 任务提交
### 4.1 提交任务(JSON)
**请求:**
```http
POST /api/v1/synapse/task
Content-Type: application/json
X-API-Key: your-api-key
{
"task_type": "text_generation",
"payload": {
"prompt": "写一首诗",
"style": "古典"
},
"external_task_id": "my-task-001",
"priority": "normal",
"callback_url": "https://your-domain.com/callback",
"callback_token": "your-callback-token"
}
```
**请求字段说明:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `task_type` | string | 是 | 任务类型,如 `video_generation`, `image_generation` |
| `payload` | object | 是 | 任务参数,具体结构取决于 task_type |
| `external_task_id` | string | 否 | 调用方的任务 ID,用于关联 |
| `priority` | string | 否 | 优先级:`low`, `normal`, `high`, `urgent`,默认 `normal` |
| `callback_url` | string | 否 | 回调 URL,不提供则需轮询 |
| `callback_token` | string | 否 | 回调验证 token |
**响应:**
```json
{
"success": true,
"task_id": "task-abc123",
"status": "received",
"message": "任务已接收"
}
```
**响应字段说明:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | bool | 是否成功 |
| `task_id` | string | 服务端生成的任务 ID |
| `status` | string | 初始状态,通常为 `received` |
| `message` | string | 提示信息 |
| `error` | string | 错误信息(失败时) |
### 4.2 提交任务(Multipart 带文件)
**请求:**
```http
POST /api/v1/synapse/task/multipart
Content-Type: multipart/form-data
X-API-Key: your-api-key
------WebKitFormBoundary
Content-Disposition: form-data; name="task_type"
image_generation
------WebKitFormBoundary
Content-Disposition: form-data; name="payload"
{"prompt":"一只猫","style":"写实"}
------WebKitFormBoundary
Content-Disposition: form-data; name="priority"
normal
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="reference.jpg"
Content-Type: image/jpeg
[二进制数据]
------WebKitFormBoundary--
```
**表单字段:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `task_type` | string | 是 | 任务类型 |
| `payload` | string(JSON) | 是 | 任务参数(JSON 字符串) |
| `external_task_id` | string | 否 | 外部任务 ID |
| `priority` | string | 否 | 优先级 |
| `callback_url` | string | 否 | 回调 URL |
| `callback_token` | string | 否 | 回调 token |
| `file` | file | 否 | 上传的文件(可多个) |
**响应:** 同 JSON 提交方式
---
## 5. 任务查询
### 5.1 获取任务详情
**请求:**
```http
GET /api/v1/synapse/task/{task_id}
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"task": {
"id": "task-abc123",
"type": "text_generation",
"status": "completed",
"progress": 100,
"message": "任务已完成",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:01:00Z",
"completed_at": "2026-01-01T00:01:00Z",
"eta": "2026-01-01T00:01:00Z",
"result": {
"text": "生成的文本...",
"metadata": { "tokens": 1024 }
},
"error": null,
"external_task_id": "my-task-001"
}
}
```
**任务状态:**
| 状态 | 说明 |
|------|------|
| `received` | 已接收 |
| `processing` | 处理中 |
| `completed` | 已完成 |
| `failed` | 已失败 |
| `cancelled` | 已取消 |
### 5.2 获取任务状态(简化版)
**请求:**
```http
GET /api/v1/synapse/task/{task_id}/status
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"task_id": "task-abc123",
"status": "processing",
"progress": 50
}
```
### 5.3 获取任务列表
**请求:**
```http
GET /api/v1/synapse/tasks?status=processing&limit=20&offset=0
X-API-Key: your-api-key
```
**查询参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `status` | string | 否 | 按状态过滤 |
| `limit` | int | 否 | 每页数量,默认 20 |
| `offset` | int | 否 | 偏移量,默认 0 |
**响应:**
```json
{
"success": true,
"tasks": [
{
"id": "task-abc123",
"type": "text_generation",
"status": "completed",
"progress": 100,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:01:00Z"
}
],
"total": 1,
"limit": 20,
"offset": 0,
"has_more": false
}
```
---
## 6. 任务取消
**请求:**
```http
POST /api/v1/synapse/task/{task_id}/cancel
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"task_id": "task-abc123",
"status": "cancelled",
"message": "任务已取消"
}
```
---
## 7. 能力查询
**请求:**
```http
GET /api/v1/synapse/capabilities
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"capabilities": {
"agent_id": "my-agent",
"agent_name": "我的Agent",
"system_id": "my-system",
"protocol_version": "1.0.0",
"supported_task_types": [
{
"type": "text_generation",
"name": "文本生成",
"description": "生成各种类型的文本内容",
"estimated_time": "30s",
"input_schema": "...",
"output_schema": "..."
}
],
"supported_transport_modes": ["callback", "polling"],
"max_file_size": 104857600,
"max_concurrent_tasks": 10,
"support_progress_updates": true,
"support_streaming": false,
"support_hil": true,
"support_groups": true
}
}
```
---
## 8. 回调机制
如果调用方提供了 `callback_url`,服务端会在任务状态变化时主动回调。
### 8.1 回调请求
```http
POST {callback_url}
Content-Type: application/json
X-Callback-Token: {callback_token}
{
"task_id": "task-abc123",
"status": "completed",
"progress": 100,
"message": "任务已完成",
"result": {
"text": "生成的文本..."
},
"error": null,
"timestamp": "2026-01-01T00:01:00Z"
}
```
### 8.2 回调类型
| 触发时机 | 说明 |
|---------|------|
| 任务开始处理 | `status=processing`, `progress=0` |
| 进度更新 | `status=processing`, `progress=N` |
| 任务完成 | `status=completed`, `progress=100`, 包含 result |
| 任务失败 | `status=failed`, 包含 error |
| 任务取消 | `status=cancelled` |
### 8.3 回调响应
调用方应返回 2xx 状态码表示成功接收。如果返回非 2xx,服务端会重试(指数退避,最多 5 次)。
---
## 9. 错误处理
### 9.1 错误响应格式
```json
{
"success": false,
"error": "invalid_task_type",
"message": "不支持的任务类型"
}
```
### 9.2 错误码列表
| 错误码 | HTTP 状态码 | 说明 |
|--------|------------|------|
| `unauthorized` | 401 | API Key 无效或缺失 |
| `invalid_request` | 400 | 请求格式无效 |
| `invalid_task_type` | 400 | 任务类型不支持 |
| `invalid_params` | 400 | 参数无效 |
| `task_not_found` | 404 | 任务不存在 |
| `task_already_completed` | 409 | 任务已完成,无法取消 |
| `file_too_large` | 413 | 文件过大 |
| `rate_limited` | 429 | 请求过于频繁 |
| `internal_error` | 500 | 服务内部错误 |
| `service_unavailable` | 503 | 服务不可用 |
---
## 10. SDK 客户端接口
为方便调用方集成,提供 SDK 客户端。
### 10.1 SDK 初始化
```go
client := sdk.NewClient(sdk.ClientConfig{
BaseURL: "https://agent-api.example.com",
APIKey: "your-api-key",
Timeout: 30 * time.Second,
})
```
### 10.2 SDK 方法清单
| 方法 | 说明 |
|------|------|
| `SubmitTask(ctx, req)` | 提交任务(JSON) |
| `SubmitTaskWithFiles(ctx, req, files)` | 提交任务(带文件) |
| `GetTask(ctx, taskID)` | 获取任务详情 |
| `GetTaskStatus(ctx, taskID)` | 获取任务状态 |
| `ListTasks(ctx, status, limit, offset)` | 获取任务列表 |
| `CancelTask(ctx, taskID)` | 取消任务 |
| `GetCapabilities(ctx)` | 获取能力清单 |
| `WaitForTask(ctx, taskID, interval)` | 等待任务完成(轮询) |
### 10.3 数据结构
```go
type TaskRequest struct {
TaskType string
Payload map[string]any
ExternalTaskID string
Priority string
CallbackURL string
CallbackToken string
}
type TaskInfo struct {
ID string
Type string
Status string
Progress int
Message string
CreatedAt string
UpdatedAt string
CompletedAt string
ETA string
Result map[string]any
Error *TaskError
ExternalTaskID string
}
```
**完整 SDK 实现:** [sdk/client.go](../sdk/client.go)
---
## 11. 参考实现
本协议的参考实现:
| 模块 | 文件路径 | 说明 |
|------|---------|------|
| 服务端 | [externalapi/handler.go](../externalapi/handler.go) | External API Handler |
| 客户端 SDK | [sdk/client.go](../sdk/client.go) | Go 语言 SDK |
---
## 附录 A:调用模式选择
### 回调模式 vs 轮询模式
| 对比项 | 回调模式 | 轮询模式 |
|--------|---------|---------|
| 实时性 | 高,状态变化即时通知 | 低,取决于轮询间隔 |
| 实现复杂度 | 调用方需提供公网可访问的回调接口 | 简单,只需调用 GET 接口 |
| 网络要求 | 调用方需要公网可达 | 只需调用方能访问服务端 |
| 适用场景 | 生产环境、需要实时通知 | 开发测试、内网环境 |
### 选择建议
- **有公网服务器**:使用回调模式
- **本地开发 / 内网环境**:使用轮询模式
- **长时间任务**:建议回调模式,避免频繁轮询
- **短任务(<10s)**:可直接同步等待或高频轮询
---
## 附录 B:从 A2A 协议到 P2A 协议的映射
| 灵隙概念 | P2A 对应 | 说明 |
|---------|---------|------|
| `p2p:task_request` | `POST /task` | 任务提交 |
| `p2p:task_status_update` | 回调 / GET /task | 状态更新 |
| `p2p:task_complete` | 回调(status=completed) | 任务完成 |
| `p2p:task_failed` | 回调(status=failed) | 任务失败 |
| `p2p:task_cancel` | `POST /task/{id}/cancel` | 任务取消 |
| `p2p:capability_query` | `GET /capabilities` | 能力查询 |
| Agent 身份 | API Key | 简化认证 |
| HMAC 签名 | Callback Token | 简化验证 |

View File

@@ -0,0 +1,519 @@
# SDK 实现说明
> 版本:1.0.0
> 更新日期:2026-06-27
> 协议版本:1.0.0
> 关联协议:
> - [A2A 协议](./02-a2a-protocol.md)
> - [A2N 协议](./03-a2n-protocol.md)
> - [P2A 协议](./04-p2a-protocol.md)
---
## 1. 概述
SDK 按照灵隙协议族(Synapse Protocol Suite)的三个子协议进行了模块化设计,分为三个子包,命名与协议名保持一致:
```
┌─────────────────────────────────────────────────────────────┐
│ SDK (sdk/) │
├──────────────┬───────────────────┬───────────────────────────┤
│ sdk/p2a │ sdk/a2a │ sdk/a2n │
│ P2A 协议 │ A2A 协议 │ A2N 协议 │
│ (外部调用) │ (Agent间通信) │ (群组协作) │
│ ✅ 完整实现 │ ✅ 完整实现 │ ✅ 完整实现 │
└──────────────┴───────────────────┴───────────────────────────┘
```
---
## 2. SDK 目录结构
```
sdk/
├── doc.go # SDK 包文档(使用说明)
├── p2a/
│ └── client.go # P2A 协议 - 外部调用客户端
├── a2a/
│ └── client.go # A2A 协议 - Agent间通信客户端
└── a2n/
└── client.go # A2N 协议 - 群组协作客户端
```
---
## 3. 第一部分:sdk/p2a - P2A 协议
**协议对应**:[P2A 协议](./04-p2a-protocol.md)
**版本**:1.0.0
**用途**:非 Agent 调用方(前端、后端服务、第三方应用)通过 REST API 调用 Agent 能力
### 3.1 实现对应关系
| P2A 协议功能 | SDK 方法 | 位置 |
|-------------|---------|------|
| 认证方式 | `setHeaders()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 提交任务(JSON) | `SubmitTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 提交任务(Multipart) | `SubmitTaskWithFiles()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 获取任务详情 | `GetTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 获取任务状态(简化版) | `GetTaskStatus()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 获取任务列表 | `ListTasks()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 取消任务 | `CancelTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 能力查询 | `GetCapabilities()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 回调验证 | `VerifyCallback()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 轮询等待 | `WaitForTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
### 3.2 核心数据结构
```go
// Client 客户端
type Client struct {
baseURL string
apiKey string
httpClient *http.Client
}
// 配置
type ClientConfig struct {
BaseURL string
APIKey string
Timeout time.Duration
MaxRetries int
}
// 任务请求
type TaskRequest struct {
TaskType string
Payload map[string]any
ExternalTaskID string
Priority string
CallbackURL string
CallbackToken string
}
// 任务信息
type TaskInfo struct {
ID string
Type string
Status string
Progress int
Result map[string]any
Error *TaskError
}
// 文件上传参数
type FileParam struct {
FieldName string
Filename string
Data []byte
MimeType string
}
```
### 3.3 使用示例
```go
import "dp-headless/synapse/sdk/p2a"
client := p2a.NewClient(p2a.ClientConfig{
BaseURL: "https://agent-api.example.com",
APIKey: "your-api-key",
})
// 提交任务并等待完成
taskInfo, err := client.CreateAndWait(ctx, "text_generation", map[string]any{
"prompt": "写一首诗",
"style": "古典",
}, 5*time.Second)
// 使用回调模式
resp, err := client.CreateWithCallback(ctx, "image_generation", payload,
"https://your-domain.com/callback",
"callback-token",
)
```
---
## 4. 第二部分:sdk/a2a - A2A 协议
**协议对应**:[A2A 协议](./02-a2a-protocol.md)
**版本**:1.0.0
**用途**:Agent 之间的点对点通信,包含消息发送、签名验证、回调处理
### 4.1 实现对应关系
| A2A 协议功能 | SDK 方法 | 位置 |
|------------|---------|------|
| 发送任务请求 | `SendTaskRequest()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 发送任务取消 | `SendTaskCancel()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 发送人类决策 | `SendHumanDecision()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 查询能力 | `SendCapabilityQuery()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 消息签名 | `signMessage()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 签名验证 | `verifySignature()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 任务状态管理 | `GetTaskState()`, `ListTasks()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 等待任务完成 | `WaitForTask()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 回调服务 | `startCallbackServer()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
### 4.2 核心数据结构
```go
// Client A2A客户端
type Client struct {
agentEndpoint types.AgentEndpoint
authSecret string
callbackHandler MessageCallbackHandler
tasks map[string]*A2ATaskState
}
// 配置
type ClientConfig struct {
AgentEndpoint types.AgentEndpoint
AuthSecret string
CallbackPort int
Timeout time.Duration
MaxRetries int
}
// 消息回调处理器接口
type MessageCallbackHandler interface {
HandleTaskRequest(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error)
HandleTaskStatusUpdate(ctx context.Context, msg *types.UnifiedSynapseMessage) error
HandleTaskComplete(ctx context.Context, msg *types.UnifiedSynapseMessage) error
HandleTaskFailed(ctx context.Context, msg *types.UnifiedSynapseMessage) error
HandleNeedHuman(ctx context.Context, msg *types.UnifiedSynapseMessage) error
}
// 任务状态
type A2ATaskState struct {
TaskID string
Status types.P2PTaskStatus
Progress int
Message string
Result any
Error *types.SynapseError
}
```
### 4.3 使用示例
```go
import "dp-headless/synapse/sdk/a2a"
a2aClient, err := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: types.AgentEndpoint{
AgentID: "my-agent",
AgentName: "我的Agent",
SystemID: "my-system",
Endpoint: "http://localhost:8080/synapse/callback",
},
AuthSecret: "my-hmac-secret",
CallbackPort: 8080,
})
// 设置消息处理器
a2aClient.SetMessageHandler(&myHandler{})
// 发送任务请求给其他Agent
taskID, err := a2aClient.SendTaskRequest(ctx, targetAgent, "video_generation", map[string]any{
"prompt": "生成一段视频",
})
```
---
## 5. 第三部分:sdk/a2n - A2N 协议
**协议对应**:[A2N 协议](./03-a2n-protocol.md)
**版本**:1.0.0
**用途**:多 Agent 群组协作,包含群组管理、任务拆分合并、投票、升级
### 5.1 实现对应关系
| A2N 协议功能 | SDK 方法 | 位置 |
|------------|---------|------|
| 创建群组 | `CreateGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 加入群组 | `JoinGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 离开群组 | `LeaveGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 解散群组 | `DisbandGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 提交群组任务 | `SubmitGroupTask()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 发送子任务结果 | `SendSubtaskResult()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 发起投票 | `InitiateVote()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 提交投票 | `SubmitVote()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 广播消息 | `Broadcast()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 升级到人类 | `EscalateToHuman()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
### 5.2 核心数据结构
```go
// Client A2N客户端
type Client struct {
a2aClient *a2a.Client
groups map[string]*GroupState
eventHandler GroupEventHandler
}
// 配置
type ClientConfig struct {
A2AClient *a2a.Client
EventHandler GroupEventHandler
}
// 群组状态
type GroupState struct {
GroupID string
Name string
Topology types.GroupTopology
Status types.GroupStatus
Members []types.GroupMember
Role string // "coordinator" | "worker"
}
// 群组事件处理器接口
type GroupEventHandler interface {
OnGroupCreated(ctx context.Context, groupID string) error
OnGroupDisbanded(ctx context.Context, groupID string) error
OnMemberJoined(ctx context.Context, groupID string, member types.GroupMember) error
OnMemberLeft(ctx context.Context, groupID string, agentID string) error
OnTaskSplit(ctx context.Context, groupID string, originalTaskID string, subtasks []types.Subtask) error
OnSubtaskProgress(ctx context.Context, groupID string, subtaskID string, progress int) error
OnTaskMerged(ctx context.Context, groupID string, originalTaskID string, result any) error
OnVoteInitiated(ctx context.Context, groupID string, vote types.GroupVotePayload) error
OnVoteResult(ctx context.Context, groupID string, voteID string, option string, passed bool) error
OnEscalation(ctx context.Context, groupID string, escalationID string, level int) error
OnBroadcast(ctx context.Context, groupID string, sender types.AgentEndpoint, message []byte) error
}
```
### 5.3 使用示例
```go
import (
"dp-headless/synapse/sdk/a2n"
"dp-headless/synapse/sdk/a2a"
)
// 创建A2A客户端
a2aClient, _ := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: myEndpoint,
})
// 创建A2N客户端
a2nClient, err := a2n.NewClient(&a2n.ClientConfig{
A2AClient: a2aClient,
EventHandler: &myGroupHandler{},
})
// 创建群组
group, err := a2nClient.CreateGroup(ctx, "视频制作组", types.GroupTopologyStar, nil, members)
// 作为Coordinator,提交任务
subtasks, err := a2nClient.SubmitGroupTask(ctx, group.GroupID, "video_generation",
map[string]any{"script": "..."},
types.MergeAllSuccess,
)
// 发起投票
voteID, err := a2nClient.InitiateVote(ctx, group.GroupID,
"选择哪个方案?",
[]string{"方案A", "方案B"},
voterIDs,
0.5,
5*time.Minute,
)
```
---
## 6. SDK 架构总览
```
┌─────────────────────────────────────────────────────────────┐
│ SDK (dp-headless/synapse/sdk) │
├─────────────────────────────────────────────────────────────┤
│ doc.go │
│ ├── 包文档和使用说明 │
│ └── 指向各子包的链接 │
├────────────────┬────────────────┬─────────────────────────┤
│ sdk/p2a │ sdk/a2a │ sdk/a2n │
│ │ │ │
│ P2A协议 │ 灵隙协议 │ A2N协议 │
│ │ │ │
│ ├── Client │ ├── Client │ ├── Client │
│ ├── 配置 │ ├── 配置 │ ├── 配置 │
│ ├── 任务操作 │ ├── 消息发送 │ ├── 群组管理 │
│ ├── 能力查询 │ ├── 任务状态 │ ├── 任务协作 │
│ └── 回调验证 │ ├── 回调服务 │ ├── 投票 │
│ │ └── 签名 │ └── 升级 │
└────────────────┴────────────────┴─────────────────────────┘
```
---
## 7. 快速开始
### 7.1 P2A 调用(外部调用者)
```go
package main
import (
"context"
"fmt"
"dp-headless/synapse/sdk/p2a"
)
func main() {
client := p2a.NewClient(p2a.ClientConfig{
BaseURL: "https://agent.example.com",
APIKey: "your-api-key",
})
ctx := context.Background()
// 方式1: 轮询等待
result, err := client.CreateAndWait(ctx, "text_generation", map[string]any{
"prompt": "写一首诗",
}, 5*time.Second)
if err != nil {
fmt.Println("Error:", err)
}
fmt.Printf("Result: %v\n", result.Result)
// 方式2: 回调模式
resp, err := client.CreateWithCallback(ctx, "image_generation",
map[string]any{"prompt": "一只猫"},
"https://your-domain.com/callback",
"token",
)
fmt.Printf("Task ID: %s\n", resp.TaskID)
}
```
### 7.2 灵隙 通信(Agent 间)
```go
package main
import (
"context"
"fmt"
"dp-headless/synapse/sdk/a2a"
"dp-headless/synapse/types"
)
func main() {
client, _ := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: types.AgentEndpoint{
AgentID: "agent-001",
AgentName: "Agent 001",
SystemID: "my-system",
Endpoint: "http://localhost:8080/synapse/callback",
},
AuthSecret: "secret-key",
CallbackPort: 8080,
})
ctx := context.Background()
target := types.AgentEndpoint{
AgentID: "agent-002",
Endpoint: "http://agent-002:8080/synapse/callback",
}
// 发送任务请求
taskID, err := client.SendTaskRequest(ctx, target, "data_analysis", map[string]any{
"dataset": "sales.csv",
})
fmt.Printf("Task ID: %s\n", taskID)
// 等待完成
state, err := client.WaitForTask(ctx, taskID, 5*time.Minute)
fmt.Printf("Status: %s\n", state.Status)
}
```
### 7.3 A2N 群组协作
```go
package main
import (
"context"
"fmt"
"dp-headless/synapse/sdk/a2n"
"dp-headless/synapse/sdk/a2a"
"dp-headless/synapse/types"
)
func main() {
a2aClient, _ := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: myEndpoint,
})
a2nClient, _ := a2n.NewClient(&a2n.ClientConfig{
A2AClient: a2aClient,
})
ctx := context.Background()
// 创建群组
group, _ := a2nClient.CreateGroup(ctx, "协作组", types.GroupTopologyStar, nil, []types.AgentEndpoint{
{AgentID: "member-1", Endpoint: "http://member-1:8080"},
{AgentID: "member-2", Endpoint: "http://member-2:8080"},
})
fmt.Printf("Group ID: %s\n", group.GroupID)
// 提交任务
subtasks, _ := a2nClient.SubmitGroupTask(ctx, group.GroupID, "video_generation",
map[string]any{"script": "..."},
types.MergeAllSuccess,
)
fmt.Printf("Split into %d subtasks\n", len(subtasks))
}
```
---
## 8. 协议与包名对应关系
| 协议名称 | 协议文档 | SDK 包名 | 包路径 |
|---------|---------|---------|--------|
| 灵隙原始协议(Synapse Raw Protocol) | [01-a2a-raw-protocol.md](./01-a2a-raw-protocol.md) | - | 纲领性文档 |
| A2A 协议 | [02-a2a-protocol.md](./02-a2a-protocol.md) | `a2a` | `sdk/a2a/` |
| A2N 协议 | [03-a2n-protocol.md](./03-a2n-protocol.md) | `a2n` | `sdk/a2n/` |
| P2A 协议 | [04-p2a-protocol.md](./04-p2a-protocol.md) | `p2a` | `sdk/p2a/` |
**所有协议版本:1.0.0**
---
## 9. 扩展建议
| 优先级 | 扩展项 | 说明 |
|-------|-------|------|
| 高 | gRPC 传输 | 添加 gRPC 协议支持 |
| 高 | WebSocket | 添加 WebSocket 流式传输 |
| 中 | 批量操作 | 批量任务提交、批量查询 |
| 中 | 指标监控 | 内置指标收集 |
| 低 | 多语言 SDK | Python、JavaScript 等 |
---
## 附录:相关文件索引
| 文件路径 | 说明 |
|---------|------|
| [sdk/doc.go](../sdk/doc.go) | SDK 包文档 |
| [sdk/p2a/client.go](../sdk/p2a/client.go) | P2A 客户端实现 |
| [sdk/a2a/client.go](../sdk/a2a/client.go) | A2A 客户端实现 |
| [sdk/a2n/client.go](../sdk/a2n/client.go) | A2N 客户端实现 |
| [externalapi/handler.go](../externalapi/handler.go) | P2A 协议服务端实现 |
| [p2p/protocol.go](../p2p/protocol.go) | A2A 协议核心实现 |
| [group/protocol.go](../group/protocol.go) | A2N 协议核心实现 |
| [types/types.go](../types/types.go) | 公共类型定义 |
| [transport/transport.go](../transport/transport.go) | HTTP 传输层实现 |

View File

@@ -0,0 +1,777 @@
# 灵隙协议族(Synapse Protocol Suite) - 解决方案白皮书
> 版本:1.0.0
> 日期:2026-06-27
> 状态:草案
> 前置阅读:[灵隙原始协议(Synapse Raw Protocol)](./01-a2a-raw-protocol.md)
---
## 目录
1. [概述](#1-概述)
2. [方案一:Agent 发现与广播机制](#2-方案一agent-发现与广播机制)
3. [方案二:身份认证与信任体系](#3-方案二身份认证与信任体系)
4. [方案三:协议缺失内容补全](#4-方案三协议缺失内容补全)
5. [分阶段实施路线图](#5-分阶段实施路线图)
6. [技术选型建议](#6-技术选型建议)
---
## 1. 概述
### 1.1 背景
当前灵隙协议族(Synapse Protocol Suite)定义了 Agent 之间"怎么说话"(消息格式、任务流程),但缺少:
- **怎么找到对方** — Agent 发现与广播机制
- **怎么相信对方** — 身份认证与信任体系
- **怎么规模化协作** — 路由、调度、激励等基础设施
本文档针对上述问题给出完整的解决方案。
### 1.2 设计原则
| 原则 | 说明 |
|------|------|
| **渐进式** | 从简单到复杂,每阶段都可用 |
| **可插拔** | 组件可替换,不绑定具体实现 |
| **传输无关** | 不依赖特定底层网络 |
| **安全优先** | 身份认证是基础设施,不是可选功能 |
| **向后兼容** | 不破坏现有协议,增量扩展 |
---
## 2. 方案一:Agent 发现与广播机制
### 2.1 整体架构
```
┌─────────────────────────────────────────────────────────────────────┐
│ Agent 发现与广播层 │
├─────────────────────┬─────────────────────┬─────────────────────────┤
│ 集中式目录服务 │ 分布式 Gossip │ 组播/广播 │
│ (Directory) │ (Gossip) │ (Multicast) │
│ ┌───────────────┐ │ ┌───────────────┐ │ ┌────────────────┐ │
│ │ 注册/注销 │ │ │ 消息泛洪 │ │ │ 子网内广播 │ │
│ │ 能力索引 │ │ │ 状态同步 │ │ │ 邻居发现 │ │
│ │ 名字解析 │ │ │ 节点发现 │ │ │ 本地心跳 │ │
│ └───────────────┘ │ └───────────────┘ │ └────────────────┘ │
└─────────────────────┴─────────────────────┴─────────────────────────┘
```
### 2.2 模式一:集中式目录服务(推荐首选)
#### 2.2.1 架构说明
引入 **Agent Directory Service(ADS)** 作为中心化的 Agent 目录服务,类似 DNS。
```
Agent A ──注册──▶ ┌─────────────┐ ◀──注册── Agent B
│ Directory │
Agent C ──查询──▶ │ Service │ ◀──心跳── Agent D
└─────────────┘
│
▼
能力索引
在线状态
端点地址
```
#### 2.2.2 核心接口
**注册协议(Agent → Directory):**
| 操作 | 消息类型 | 说明 |
|------|---------|------|
| 注册 | `dir:register` | Agent 上线时注册自身信息 |
| 注销 | `dir:unregister` | Agent 下线时注销 |
| 心跳 | `dir:heartbeat` | 定期上报存活状态 |
| 更新能力 | `dir:update_capabilities` | 能力变化时更新 |
**查询协议(Agent → Directory):**
| 操作 | 消息类型 | 说明 |
|------|---------|------|
| 按 ID 查询 | `dir:query_by_id` | 根据 AgentID 查端点 |
| 按能力查询 | `dir:query_by_capability` | 找具备某能力的 Agent |
| 列表查询 | `dir:list` | 分页列出所有 Agent |
| 批量查询 | `dir:batch_query` | 批量查询多个 Agent |
#### 2.2.3 注册数据结构
```json
{
"agent_id": "agent-001",
"agent_name": "视频生成Agent",
"system_id": "video-system",
"endpoint": "https://agent-001.example.com/synapse/callback",
"version": "1.0.0",
"capabilities": [
{
"type": "video_generation",
"name": "视频生成",
"description": "根据脚本生成视频",
"max_concurrent": 10,
"avg_latency_ms": 5000
}
],
"transports": ["http", "grpc"],
"status": "online",
"last_heartbeat": "2026-06-27T10:00:00Z",
"trust_score": 85,
"public_key": "-----BEGIN PUBLIC KEY-----\n..."
}
```
#### 2.2.4 优缺点
| 优点 | 缺点 |
|------|------|
| 查询效率高(O(1)) | 单点故障风险 |
| 支持复杂查询(按能力、按系统等) | 中心节点需信任 |
| 易于管理和监控 | 扩展性有上限 |
| 实现简单 | 存在注册/注销时序问题 |
---
### 2.3 模式二:分布式 Gossip 协议(可选扩展)
#### 2.3.1 架构说明
无中心节点,Agent 之间通过 Gossip 协议(epidemic protocol)扩散状态信息,类似比特币网络的节点发现。
```
Agent A ──gossip──▶ Agent B ──gossip──▶ Agent D
│ │
└──gossip──▶ Agent C ──gossip──▶ Agent E
```
#### 2.3.2 核心机制
| 机制 | 说明 |
|------|------|
| **Peer Sampling** | 每个 Agent 维护一个部分视图(partial view) |
| **Active Push** | 定期随机选几个节点推送本地状态 |
| **Passive Pull** | 收到推送后,有概率回传自身状态 |
| **TTL** | 消息有存活跳数,防止无限扩散 |
| **Deduplication** | 消息 ID 去重,避免重复处理 |
#### 2.3.3 Gossip 消息格式
```json
{
"type": "gossip:state",
"from": "agent-001",
"view": [
{"agent_id": "agent-002", "endpoint": "...", "capabilities": [...], "timestamp": "..."},
{"agent_id": "agent-003", "endpoint": "...", "capabilities": [...], "timestamp": "..."},
{"agent_id": "agent-004", "endpoint": "...", "capabilities": [...], "timestamp": "..."}
],
"ttl": 5,
"msg_id": "msg-xxx"
}
```
#### 2.3.4 优缺点
| 优点 | 缺点 |
|------|------|
| 完全去中心化 | 查询效率低(需本地构建视图) |
| 容错性强 | 状态最终一致,有延迟 |
| 可无限扩展 | 实现复杂度高 |
| 无单点故障 | 网络流量大 |
---
### 2.4 模式三:本地组播/邻居发现(局域网场景)
适用于同一内网/集群内的 Agent 快速发现。
#### 2.4.1 机制
| 机制 | 说明 |
|------|------|
| **UDP 组播** | 定期在组播地址发送广播 |
| **mDNS/DNS-SD** | 类似 Apple Bonjour 的服务发现 |
| **ARP/NDP** | 链路层发现(不推荐) |
#### 2.4.2 适用场景
- 边缘计算集群
- 同一机房内的 Agent 集群
- 本地开发测试环境
---
### 2.5 广播机制设计
#### 2.5.1 广播类型
| 广播范围 | 消息类型前缀 | 说明 |
|---------|-------------|------|
| 全网广播 | `global:` | 所有在线 Agent |
| 系统广播 | `system:` | 同一 system_id 内 |
| 群组广播 | `group:` | 同一群组内(已有) |
| 能力组广播 | `cap:` | 具备某能力的 Agent |
#### 2.5.2 广播实现
**集中式模式下:**
- 由 Directory Service 转发广播消息
- 支持按条件过滤(按能力、按系统等)
**分布式模式下:**
- 通过 Gossip 协议泛洪
- TTL 控制扩散范围
- 消息 ID 去重
---
### 2.6 心跳与存活检测
#### 2.6.1 心跳机制
```
Agent ───heartbeat──▶ Directory
(每30秒)
Directory 判定离线:
- 连续 3 次未收到心跳 → 标记为 offline
- 离线 24 小时 → 自动注销
```
#### 2.6.2 主动探测
```
Directory ───ping──▶ Agent
◀──pong──
```
用于验证 Agent 是否真的在线(防止心跳伪造)。
---
## 3. 方案二:身份认证与信任体系
### 3.1 三层身份架构
```
┌─────────────────────────────────────────────────┐
│ Layer 3: 信任评分 (Trust Score) │
│ 声誉系统、历史行为、第三方评价 │
├─────────────────────────────────────────────────┤
│ Layer 2: 身份验证 (Authentication) │
│ 签名验证、证书校验、DID 验证 │
├─────────────────────────────────────────────────┤
│ Layer 1: 身份标识 (Identity) │
│ AgentID + 公钥 / 证书 / DID │
└─────────────────────────────────────────────────┘
```
### 3.2 Layer 1:身份标识方案
#### 3.2.1 方案 A:公钥身份(推荐基础版)
**核心思想**:AgentID = 公钥哈希(类似比特币地址)
```
AgentID = base58(sha256(public_key)[:20])
例:agent-1A2B3C4D5E6F...
```
**优点:**
- 自包含,不需要中心 CA
- 公钥可验证身份
- 实现简单
**缺点:**
- 身份无法撤销(私钥泄露 = 身份被盗)
- 没有人类可读的名字
- 无法关联真实世界身份
**适用场景**:内部系统、测试环境、信任度要求不高的场景
#### 3.2.2 方案 B:PKI 证书体系
**核心思想**:引入 CA(Certificate Authority)颁发证书
```
CA 根证书
├── 系统A证书
│ ├── Agent-001 证书
│ └── Agent-002 证书
└── 系统B证书
└── Agent-003 证书
```
**优点:**
- 成熟的工业标准
- 支持证书撤销(CRL/OCSP)
- 有层级信任关系
**缺点:**
- 需要信任中心化 CA
- 证书管理复杂
- 跨 CA 互操作需要桥接
**适用场景**:企业内部、有明确信任中心的场景
#### 3.2.3 方案 C:区块链 DID(去中心化身份)
**核心思想**:身份上链,链上存公钥和属性,类似 W3C DID 标准。
```
链上存储:
DID → {
"publicKey": "...",
"controller": "...",
"authentication": [...],
"service": [...],
"revoked": false,
"timestamp": "..."
}
```
**三种实现路径:**
| 路径 | 说明 | 适用场景 |
|------|------|---------|
| **公链 DID** | 使用以太坊/Solana 等公链 | 开放生态、跨组织协作 |
| **联盟链 DID** | 多机构共建联盟链 | 行业联盟、政企合作 |
| **私有链 DID** | 单组织内部链 | 企业内部、对性能要求高 |
**优点:**
- 去中心化,无单点故障
- 身份可验证、可追溯
- 支持跨系统互认
- 不可篡改的身份历史
**缺点:**
- 链上操作有延迟
- 需要支付 Gas 费(公链)
- 实现复杂度高
- 隐私保护需额外设计
---
### 3.3 Layer 2:身份验证方案
#### 3.3.1 消息签名验证
所有灵隙消息必须签名,签名方式:
```
签名内容 = SHA256(message_id + sender + receiver + payload + timestamp)
签名 = Sign(private_key, 签名内容)
验证 = Verify(public_key, 签名内容, 签名)
```
#### 3.3.2 签名放置位置
| 位置 | 说明 |
|------|------|
| 消息体内 | `signature` 字段 |
| HTTP Header | `X-Synapse-Signature` |
| gRPC Metadata | `x-synapse-signature` |
#### 3.3.3 防重放攻击
```
每个消息有唯一 message_id + timestamp
接收方维护已处理 message_id 缓存(如最近 1 小时)
timestamp 与本地时间差超过 5 分钟 → 拒绝
```
---
### 3.4 Layer 3:信任评分体系
#### 3.4.1 信任分维度
| 维度 | 权重 | 说明 |
|------|------|------|
| 任务完成率 | 30% | 历史任务成功 / 总任务数 |
| 任务质量 | 25% | 结果评分、返工率 |
| 响应速度 | 15% | 平均响应时间 |
| 在线稳定性 | 15% | 在线时长 / 总时长 |
| 历史信誉 | 15% | 其他 Agent 的评价 |
#### 3.4.2 信任分计算
```
trust_score = Σ(维度_i × 权重_i)
范围:0 - 100
等级:
90-100 S 级 高度可信
75-89 A 级 可信
60-74 B 级 一般可信
40-59 C 级 谨慎使用
0-39 D 级 不可信
```
#### 3.4.3 信任传递
```
A 信任 B(评分 90)
B 信任 C(评分 85)
A 对 C 的初始信任 = 90 × 85% × 衰减因子 = 约 68
```
**注意**:信任传递有衰减,防止信任污染。
---
### 3.5 区块链适用性决策树
```
是否需要跨组织协作?
├── 否 → 用 PKI 或公钥身份(简单高效)
└── 是 → 是否有共同信任的中心机构?
├── 是 → 用 PKI 体系(成熟可靠)
└── 否 → 是否需要不可篡改的审计?
├── 是 → 考虑联盟链 DID
└── 否 → 公钥身份 + 声誉系统可能够用
```
---
## 4. 方案三:协议缺失内容补全
### 4.1 第一优先级补全(核心基础设施)
#### 4.1.1 Agent 发现协议(ADS - Agent Discovery Service)
**新增消息类型:**
| 消息类型 | 方向 | 说明 |
|---------|------|------|
| `dir:register` | Agent → Directory | 注册 Agent |
| `dir:unregister` | Agent → Directory | 注销 Agent |
| `dir:heartbeat` | Agent → Directory | 心跳 |
| `dir:update_capabilities` | Agent → Directory | 更新能力 |
| `dir:query_by_id` | Agent → Directory | 按 ID 查询 |
| `dir:query_by_capability` | Agent → Directory | 按能力查询 |
| `dir:list` | Agent → Directory | 列表查询 |
| `dir:query_result` | Directory → Agent | 查询结果 |
**新增数据结构:** `AgentDirectoryEntry`, `CapabilityIndex`, `QueryFilter`
#### 4.1.2 身份认证规范
**新增消息类型:**
| 消息类型 | 说明 |
|---------|------|
| `auth:challenge` | 身份挑战 |
| `auth:response` | 挑战响应 |
| `auth:cert_exchange` | 证书交换 |
**新增内容:**
- 标准签名算法列表(Ed25519, ECDSA P-256, RSA 2048)
- 消息签名格式规范
- 防重放攻击机制
- 身份验证流程规范
#### 4.1.3 心跳与存活检测
**新增消息类型:**
| 消息类型 | 说明 |
|---------|------|
| `net:ping` | 存活探测 |
| `net:pong` | 存活响应 |
| `net:offline_notify` | 下线通知 |
**规范内容:**
- 心跳间隔(默认 30 秒)
- 超时判定(连续 3 次未收到 → 离线)
- 离线通知机制
#### 4.1.4 统一错误码体系
**错误分类:**
| 错误码前缀 | 类别 | 示例 |
|-----------|------|------|
| `msg_` | 消息层 | msg_invalid_format, msg_signature_invalid |
| `auth_` | 认证层 | auth_failed, auth_expired, auth_forbidden |
| `task_` | 任务层 | task_not_found, task_timeout, task_cancelled |
| `cap_` | 能力层 | cap_lacking, cap_busy, cap_not_supported |
| `group_` | 群组层 | group_not_found, group_full, group_permission_denied |
| `dir_` | 目录层 | dir_agent_not_found, dir_query_failed |
| `net_` | 网络层 | net_timeout, net_offline, net_unreachable |
| `sys_` | 系统层 | sys_internal_error, sys_not_implemented |
---
### 4.2 第二优先级补全(重要功能)
#### 4.2.1 能力匹配与任务路由
**功能:**
- 智能选择最合适的 Agent 执行任务
- 支持负载均衡(按并发数、响应速度、信任分)
- 支持任务失败自动重试/转发
**新增消息类型:**
- `router:submit_task` - 提交给路由器自动选择执行者
- `router:forward` - 任务转发
#### 4.2.2 全网广播协议
**新增消息类型:**
| 消息类型 | 说明 |
|---------|------|
| `global:broadcast` | 全网广播 |
| `system:broadcast` | 系统内广播 |
| `cap:broadcast` | 能力组广播 |
**规范内容:**
- 广播范围定义
- TTL 与去重机制
- 广播风暴防护
#### 4.2.3 版本协商机制
**功能:**
- Agent 之间协商使用的协议版本
- 向后兼容处理
- 能力降级策略
**新增消息类型:**
- `meta:version_query` - 查询支持的版本
- `meta:version_negotiate` - 版本协商
#### 4.2.4 幂等性保证
**规范内容:**
- 幂等键(Idempotency Key)机制
- 重复消息处理规范
- 任务级幂等 vs 消息级幂等
---
### 4.3 第三优先级补全(增强功能)
#### 4.3.1 流式输出协议
**功能:**
- 长任务实时进度推送
- 生成式 AI 的 token 流式输出
- WebSocket/SSE 传输支持
#### 4.3.2 批量任务协议
**功能:**
- 一次性提交多个同类型任务
- 批量状态查询
- 批量结果收集
#### 4.3.3 支付与激励机制
**功能:**
- 任务定价与结算
- Token 或积分系统
- 智能合约自动结算(区块链场景)
#### 4.3.4 群组发现协议
**功能:**
- 按主题/任务类型查找群组
- 推荐加入合适的群组
- 群组热度/活跃度排名
---
### 4.4 安全相关补全
#### 4.4.1 端到端加密
**规范内容:**
- 密钥协商(ECDH)
- 对称加密载荷(AES-GCM)
- 前向保密(PFS)
#### 4.4.2 权限委托
**功能:**
- Agent A 委托 Agent B 代表自己执行任务
- 委托令牌(Delegation Token)
- 委托范围与时效控制
#### 4.4.3 审计日志标准
**规范内容:**
- 统一审计日志格式
- 关键操作必须记录
- 日志完整性保护(哈希链)
---
## 5. 分阶段实施路线图
### 5.1 Phase 0:基础版(MVP) - 预计 2 周
**目标**:协议可用,能跑通基本流程
**内容:**
- [x] 灵隙点对点任务协议(已完成)
- [x] A2N 群组协作协议(已完成)
- [x] P2A 外部调用协议(已完成)
- [x] 三套协议的 SDK 实现(已完成)
---
### 5.2 Phase 1:发现与身份 - 预计 4 周
**目标**:Agent 能互相找到对方,能验证对方身份
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| Agent 目录服务协议 | 集中式 ADS | P0 |
| 公钥身份规范 | Ed25519 签名验证 | P0 |
| 心跳与存活检测 | 30 秒心跳,3 次超时判定离线 | P0 |
| 统一错误码体系 | 8 大类错误码 | P0 |
| 防重放攻击 | message_id + timestamp | P0 |
**输出物:**
- 新协议文档:`06-agent-discovery.md`
- 新协议文档:`07-authentication.md`
- 代码:`discovery/` 包(目录服务客户端)
- 代码:`auth/` 包(签名验证工具)
- 更新 types 包:新增消息类型和错误码
---
### 5.3 Phase 2:路由与广播 - 预计 4 周
**目标**:支持智能任务路由和全网广播
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| 任务路由协议 | 按能力 + 负载 + 信任分选择执行者 | P1 |
| 全网广播协议 | 基于目录服务的广播转发 | P1 |
| 版本协商机制 | 协议版本兼容 | P1 |
| 幂等性保证 | 幂等键 + 去重 | P1 |
**输出物:**
- 新协议文档:`08-routing-and-broadcast.md`
- 代码:`routing/` 包增强(增加能力路由)
- 更新目录服务:支持广播转发
---
### 5.4 Phase 3:信任与激励 - 预计 6 周
**目标**:建立信任体系,支持规模化协作
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| 信任评分体系 | 5 维度信任分计算 | P2 |
| 流式输出协议 | WebSocket/SSE 流式进度 | P2 |
| 批量任务协议 | 批量提交/查询/收集 | P2 |
| 群组发现协议 | 群组搜索与推荐 | P2 |
| SLA 协议 | 服务等级承诺 | P3 |
**输出物:**
- 新协议文档:`09-trust-and-reputation.md`
- 代码:`trust/` 包(信任评分引擎)
---
### 5.5 Phase 4:高级特性 - 预计 8 周+
**目标**:企业级、生产级完整功能
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| 端到端加密 | ECDH + AES-GCM | P3 |
| 权限委托 | 委托令牌机制 | P3 |
| 支付与激励 | Token/积分系统 | P3 |
| 区块链 DID 集成 | 可选模块 | P4 |
| 审计日志标准 | 统一审计格式 | P3 |
| Gossip 协议 | 分布式发现(可选) | P4 |
---
## 6. 技术选型建议
### 6.1 目录服务存储选型
| 方案 | 适用场景 | 优点 | 缺点 |
|------|---------|------|------|
| **Redis** | 中小规模 | 高性能,支持 TTL | 数据量受限 |
| **PostgreSQL** | 中大规模 | 复杂查询,持久化 | 需运维 |
| **Elasticsearch** | 大规模 + 全文检索 | 强大的搜索能力 | 运维复杂 |
**推荐**:先用 Redis(简单快),规模大了再迁 PostgreSQL。
---
### 6.2 签名算法选型
| 算法 | 安全性 | 性能 | 密钥长度 | 推荐度 |
|------|--------|------|---------|--------|
| Ed25519 | 高 | 快 | 256 位 | ⭐⭐⭐⭐⭐ 首选 |
| ECDSA P-256 | 高 | 中 | 256 位 | ⭐⭐⭐⭐ 兼容 |
| RSA 2048 | 中 | 慢 | 2048 位 | ⭐⭐ 仅兼容 |
**推荐**:默认 Ed25519,需要兼容旧系统时支持 ECDSA。
---
### 6.3 区块链选型(如果需要)
| 场景 | 推荐方案 | 说明 |
|------|---------|------|
| 企业内部 | **私有链(Hyperledger Fabric)** | 可控,高性能,无 Gas 费 |
| 行业联盟 | **联盟链(FISCO BCOS / ChainMaker)** | 多机构共建,合规 |
| 开放生态 | **公链(以太坊 / Solana)** | 完全开放,需 Gas 费 |
| 简单原型 | **模拟链(数据库模拟)** | 先验证逻辑,再上真链 |
**推荐**:90% 的场景用不到真区块链,先用数据库模拟验证业务逻辑。
---
## 7. 总结与建议
### 7.1 核心建议
1. **先集中后分布**:先用集中式目录服务,简单高效;规模大了再考虑分布式
2. **先公钥后区块链**:先用公钥身份解决 80% 的问题,真的需要了再上链
3. **先 MVP 后完善**:先跑通核心流程,再逐步加高级功能
4. **标准接口可替换**:目录服务、身份系统都做成可插拔的,方便未来升级
### 7.2 下一步行动
| 序号 | 行动 | 负责人 | 产出 |
|------|------|--------|------|
| 1 | 评审本方案,确认 Phase 1 范围 | 架构组 | 确认后的方案 |
| 2 | 设计目录服务 API 详细规范 | 协议组 | `06-agent-discovery.md` |
| 3 | 设计身份认证详细规范 | 协议组 | `07-authentication.md` |
| 4 | 实现目录服务原型 | 开发组 | 可运行的 ADS 服务 |
| 5 | 更新 SDK 支持发现和认证 | 开发组 | 新版 SDK |
---
## 附录:相关文档索引
| 文档 | 说明 |
|------|------|
| [01-a2a-raw-protocol.md](./01-a2a-raw-protocol.md) | 灵隙原始协议(Synapse Raw Protocol)(纲领) |
| [02-a2a-protocol.md](./02-a2a-protocol.md) | A2A 协议(P2P 任务) |
| [03-a2n-protocol.md](./03-a2n-protocol.md) | A2N 协议(群组协作) |
| [04-p2a-protocol.md](./04-p2a-protocol.md) | P2A 协议(外部调用) |
| [05-sdk-implementation.md](./05-sdk-implementation.md) | SDK 实现说明 |
| **本文档** | **解决方案白皮书** |

View File

@@ -0,0 +1,328 @@
# A2A 原始协议(Agent-to-Agent Raw Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:纲领性文档
---
## 1. 协议愿景
A2A(Agent-to-Agent)协议旨在定义一套通用的、可扩展的智能体间通信标准,使不同系统、不同厂商、不同能力的智能体能够在统一的协议框架下进行协作、交易与能力组合。
本协议是 A2A 协议族的原始纲领,定义核心设计理念、架构原则与术语体系,为所有衍生协议提供顶层指导。
---
## 2. 核心设计理念
### 2.1 去中心化对等(Peer-to-Peer)
- 所有 Agent 在协议层面地位平等,无中心化控制节点
- 每个 Agent 既是服务提供者,也是服务消费者
- Agent 间直接通信,不依赖中间 broker(传输层可选择不同实现)
### 2.2 能力驱动(Capability-Driven)
- 通信以能力为核心,而非以身份为核心
- Agent 声明自身能力,按需发现与调用
- 能力可组合、可分解、可升级
### 2.3 任务导向(Task-Oriented)
- 所有交互围绕"任务"展开
- 任务有明确的生命周期、状态机与超时机制
- 任务支持拆分、合并、委派与协作
### 2.4 人类可介入(Human-in-the-Loop)
- 协议原生支持人类介入决策
- Agent 可主动请求人类判断
- 人类决策优先于 Agent 自动决策
- 支持多级升级(escalation)机制
### 2.5 群组协作(Group Collaboration)
- 支持多 Agent 组成临时或永久群组
- 群组内有角色分工与权限控制
- 支持任务拆分-执行-合并的 MapReduce 模式
- 支持投票、共识等群体决策机制
### 2.6 传输无关(Transport Agnostic)
- 协议语义与底层传输分离
- 支持 HTTP/2、gRPC、WebSocket、消息队列等多种传输
- 支持回调(Callback)、轮询(Polling)、同步(Sync)、混合(Hybrid)等多种交互模式
### 2.7 安全可信(Secure & Trustworthy)
- 消息级签名与验证
- Agent 身份认证与信任等级
- 支持加密传输
- 可审计、可追溯
---
## 3. 协议分层架构
```
┌─────────────────────────────────────────┐
│ 应用层 (Application) │
│ 业务逻辑、工作流、领域特定逻辑 │
├─────────────────────────────────────────┤
│ 协议层 (Protocol) │
├─────────────┬───────────────────────────┤
│ A2A 基础协议 │ A2N 组群协议 │ ← 本协议族
│ (P2P) │ (Group) │
├─────────────┴───────────────────────────┤
│ 传输层 (Transport) │
│ HTTP / gRPC / WebSocket / MQ ... │
├─────────────────────────────────────────┤
│ 网络层 (Network) │
│ TCP / UDP / QUIC ... │
└─────────────────────────────────────────┘
```
### 3.1 协议族构成
| 协议名称 | 缩写 | 描述 | 适用场景 |
|---------|------|------|---------|
| A2A 原始协议 | A2A-Raw | 顶层纲领,定义理念、术语、原则 | 指导所有协议设计 |
| A2A 基础协议 | A2A-Base | 点对点任务协作协议 | 两个 Agent 间的任务交互 |
| A2N 组群协议 | A2N-Group | 多 Agent 群组协作协议 | 多个 Agent 协同完成任务 |
| P2A 调用协议 | P2A-Caller | 非 Agent 调用者接入协议 | 普通服务/前端调用 Agent 能力 |
---
## 4. 核心概念与术语
### 4.1 Agent(智能体)
能够自主感知环境、做出决策并执行动作的计算实体。
**属性:**
- `agent_id`:Agent 唯一标识
- `system_id`:所属系统标识
- `endpoint`:通信端点地址
- `capabilities`:能力清单
- `trust_level`:信任等级
### 4.2 Task(任务)
Agent 间协作的基本单位,具有明确的输入、输出与生命周期。
**属性:**
- `task_id`:任务唯一标识
- `task_type`:任务类型
- `payload`:任务载荷
- `status`:任务状态
- `priority`:优先级
- `deadline`:截止时间
### 4.3 Message(消息)
Agent 间通信的基本单位。
**属性:**
- `message_id`:消息唯一标识
- `type`:消息类型
- `sender`:发送方
- `receiver`:接收方(P2P)
- `group_id`:群组标识(群组)
- `payload`:消息载荷
- `correlation_id`:关联标识(用于请求-响应关联)
- `timestamp`:时间戳
- `signature`:签名
### 4.4 Capability(能力)
Agent 能够执行的任务类型的声明。
**属性:**
- `type`:能力对应的任务类型
- `name`:能力名称
- `description`:能力描述
- `input_schema`:输入参数 schema
- `output_schema`:输出结果 schema
- `estimated_time`:预估耗时
### 4.5 Group(群组)
由多个 Agent 组成的协作单元。
**属性:**
- `group_id`:群组唯一标识
- `name`:群组名称
- `topology`:拓扑结构
- `members`:成员列表
- `roles`:角色定义
- `status`:群组状态
### 4.6 Human-in-the-Loop(人在回路)
在 Agent 协作过程中,人类可介入进行决策的机制。
**关键概念:**
- `escalation`:升级(将问题提交给人类)
- `decision`:决策(人类做出的判断)
- `resume`:恢复(人类决策后继续执行)
---
## 5. 设计原则
### 5.1 简约性(Simplicity)
- 核心机制保持简单
- 复杂功能通过组合基础机制实现
- 可选功能通过能力声明来协商
### 5.2 可扩展性(Extensibility)
- 消息类型可扩展
- 能力类型可扩展
- 传输方式可扩展
- 通过版本号与能力协商保证向后兼容
### 5.3 容错性(Fault Tolerance)
- 网络不可靠是常态
- 消息可能丢失、重复、延迟
- Agent 可能故障、离线
- 协议必须有重试、超时、降级机制
### 5.4 可观测性(Observability)
- 所有交互可追踪
- 任务状态可查询
- 支持日志、指标、链路追踪
### 5.5 互操作性(Interoperability)
- 不同语言、不同框架的 Agent 可互操作
- 协议规范优先于具体实现
- 提供标准测试用例验证兼容性
---
## 6. 消息模型总则
### 6.1 统一消息信封
所有协议共享统一的消息信封结构,具体载荷由消息类型决定。
```
UnifiedA2AMessage
├── id // 消息ID
├── version // 协议版本
├── type // 消息类型
├── sender // 发送方 AgentEndpoint
├── receiver? // 接收方(P2P 时)
├── group_id? // 群组ID(群组时)
├── correlation_id // 关联ID
├── reply_to? // 回复的消息ID
├── payload // 消息载荷(JSON)
├── priority? // 优先级
├── signature? // 签名
├── timestamp // 发送时间
└── expires_at? // 过期时间
```
### 6.2 消息命名规范
- 使用 `领域:动作` 格式
- 全小写,用下划线分隔
- 示例:`p2p:task_request`, `group:create`, `hil:decision`
### 6.3 请求-响应模式
- 请求消息必须携带唯一 `id`
- 响应消息通过 `reply_to` 关联请求
- 支持 ACK/NACK 机制
- 超时未收到响应应重试或报错
---
## 7. 任务生命周期总则
### 7.1 状态机
```
┌──────────┐
│ pending │ 待确认
└────┬─────┘
│ 确认/拒绝
┌─────┴──────┐
▼ ▼
┌─────────┐ ┌──────────┐
│confirmed│ │ rejected │ 已拒绝(终态)
└────┬────┘ └──────────┘
│ 开始执行
▼
┌──────────┐
│executing │ 执行中
└────┬─────┘
│ 信息缺失
┌────▼─────┐
│info_needed│ 需要信息
└────┬─────┘
│ 信息补充
▼
回到 executing
│
├──────────────┐
│ 需人类决策 │
▼ ▼
┌──────────┐ ┌───────────┐
│need_human│ │ 各种终态 │
└────┬─────┘ └───────────┘
│ 人类决策
▼
回到 executing
│
┌────┴────┬─────────┐
▼ ▼ ▼
completed failed cancelled
(完成) (失败) (取消)
```
### 7.2 终态
- `completed`:成功完成
- `failed`:执行失败
- `cancelled`:被取消
- `rejected`:被拒绝
---
## 8. 安全模型总则
### 8.1 身份认证
- 每个 Agent 有唯一身份
- 支持 HMAC、JWT、OAuth2 等多种认证方式
- 信任等级机制
### 8.2 消息完整性
- 消息签名机制
- 防止篡改与伪造
### 8.3 机密性
- 支持端到端加密
- 敏感载荷加密传输
### 8.4 访问控制
- 基于信任等级的权限控制
- 群组内基于角色的权限控制
---
## 9. 非目标
以下内容**不在**本原始协议的讨论范围内:
1. **具体传输协议的实现细节** - 由各传输层实现定义
2. **特定领域的任务类型** - 由应用层或领域扩展定义
3. **Agent 内部实现架构** - 仅定义对外接口与行为
4. **具体编程语言绑定** - 语言无关,可多语言实现
5. **UI/UX 设计** - 人机界面不在协议范围内
---
## 10. 参考实现
本协议族的参考实现位于:`dp-headless/a2a/`
| 模块 | 对应协议 | 文件路径 |
|------|---------|---------|
| types 包 | 公共类型定义 | `types/` |
| p2p 包 | A2A 基础协议 | `p2p/` |
| group 包 | A2N 组群协议 | `group/` |
| externalapi 包 | P2A 调用协议 | `externalapi/` |
| sdk 包 | P2A 客户端 SDK | `sdk/` |
---
## 附录 A:协议演进
本原始协议作为顶层指导,保持相对稳定。各具体协议可独立版本化演进,但需遵循本协议定义的核心原则。
**版本历史:**
- v1.0.0:初始版本,定义核心概念与设计原则

View File

@@ -0,0 +1,465 @@
# A2A 协议(Agent-to-Agent Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:实现规范
> 基于:[A2A 原始协议 v1.0.0](./01-a2a-raw-protocol.md)
---
## 1. 概述
A2A 基础协议定义了两个 Agent 之间进行点对点(P2P)任务协作的完整规范,包括消息类型、交互流程、状态机、接口定义与错误处理。
### 1.1 适用场景
- 单个任务请求与响应
- 任务状态实时同步
- 任务取消
- 信息补全交互
- 人类介入决策
### 1.2 非适用场景
- 多 Agent 群组协作(参见 A2N 组群协议)
- 非 Agent 调用方接入(参见 P2A 调用协议)
---
## 2. 消息类型
### 2.1 消息类型清单
所有消息类型前缀:`p2p:`
| 消息类型 | 方向 | 说明 |
|---------|------|------|
| `p2p:task_request` | 请求方 → 执行方 | 提交任务请求 |
| `p2p:task_confirm` | 执行方 → 请求方 | 确认接受任务 |
| `p2p:task_reject` | 执行方 → 请求方 | 拒绝任务 |
| `p2p:task_need_info` | 执行方 → 请求方 | 请求补充信息 |
| `p2p:task_info_provided` | 请求方 → 执行方 | 提供补充信息 |
| `p2p:task_status_update` | 执行方 → 请求方 | 任务进度更新 |
| `p2p:task_complete` | 执行方 → 请求方 | 任务完成 |
| `p2p:task_failed` | 执行方 → 请求方 | 任务失败 |
| `p2p:task_cancel` | 请求方 → 执行方 | 取消任务 |
| `p2p:task_ack` | 双向 | 任务级确认 |
| `p2p:ack` | 双向 | 通用确认 |
| `p2p:nack` | 双向 | 通用否定确认(错误) |
| `p2p:heartbeat` | 双向 | 心跳保活 |
| `p2p:capability_query` | 双向 | 查询对方能力 |
| `p2p:capability_response` | 双向 | 返回能力清单 |
| `p2p:task_need_human` | 执行方 → 请求方 | 请求人类介入 |
| `p2p:human_decision` | 请求方 → 执行方 | 人类决策结果 |
### 2.2 消息类型完整定义
参见代码:[types/types.go](../types/types.go)
---
## 3. 任务状态机
### 3.1 状态定义
| 状态 | 说明 |
|------|------|
| `idle` | 空闲(初始状态) |
| `pending` | 待确认(已发送请求,等待确认) |
| `confirmed` | 已确认(对方接受任务) |
| `executing` | 执行中 |
| `info_needed` | 需要信息 |
| `info_provided` | 信息已提供 |
| `need_human` | 需要人类介入 |
| `human_deciding` | 人类决策中 |
| `human_approved` | 人类已批准 |
| `human_rejected` | 人类已拒绝 |
| `completed` | 已完成(终态) |
| `failed` | 已失败(终态) |
| `cancelled` | 已取消(终态) |
| `rejected` | 已拒绝(终态) |
### 3.2 状态流转图
```
请求方视角:
idle → pending → confirmed → executing → completed
↓ ↓ → failed
rejected info_needed → cancelled
↑↓
info_provided
↓
need_human
↑↓
human_decision
```
---
## 4. 核心交互流程
### 4.1 正常任务提交流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->| 1. 提交任务
| |
|<------- task_confirm / task_reject -| 2. 确认/拒绝
| |
|<------- task_status_update --------| 3. 进度更新(多次)
| |
|<------- task_complete -------------| 4. 完成通知
| |
|-------- task_ack ----------------->| 5. 确认收到
| |
```
### 4.2 信息补全流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->|
| |
|<------- task_need_info ------------| 发现信息缺失
| |
|-------- task_info_provided ------->| 补充信息
| |
|<------- task_status_update --------| 继续执行
| |
```
### 4.3 人类介入流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->|
| |
|<------- task_need_human -----------| 需要人类决策
| |
| [人类做出决策] |
| |
|-------- human_decision ----------->| 决策结果
| |
|<------- task_status_update --------| 继续执行
| |
```
### 4.4 任务取消流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->|
| |
|-------- task_cancel -------------->| 取消任务
| |
|<------- task_ack ------------------| 确认取消
| |
```
---
## 5. 接口规范
### 5.1 P2P 协议处理器接口
**接口定义:**
```go
type P2PProtocol interface {
// 处理收到的消息
HandleMessage(ctx context.Context, msg *UnifiedA2AMessage) (*UnifiedA2AMessage, error)
// 设置任务处理器
SetTaskHandler(h TaskHandler)
// 设置回调发送器
SetCallbackSender(s CallbackSender)
// 获取 Agent 端点
AgentEndpoint() AgentEndpoint
// 查询任务状态
GetTaskStatus(taskID string) (P2PTaskStatus, bool)
// 列出所有任务
ListTasks() map[string]P2PTaskStatus
}
```
**参考实现:** [p2p/protocol.go](../p2p/protocol.go)
### 5.2 任务处理器接口
```go
type TaskHandler interface {
// 执行任务并返回结果
ExecuteTask(ctx context.Context, taskType string, payload map[string]interface{}) (any, error)
// 验证任务是否可执行,返回缺失字段列表和是否可处理
ValidateTask(taskType string, payload map[string]interface{}) ([]MissingField, bool)
// 获取能力列表
GetCapabilities() []string
}
```
**说明:**
- `ExecuteTask`:实际执行业务逻辑的入口
- `ValidateTask`:在任务确认前调用,用于判断是否接受任务或需要补充信息
- `GetCapabilities`:返回 Agent 支持的任务类型列表
### 5.3 回调发送器接口
```go
type CallbackSender interface {
// 向远程 Agent 发送消息
SendCallback(ctx context.Context, endpoint string, msg *UnifiedA2AMessage) error
}
```
**说明:**
- 传输层实现此接口
- 支持多种传输方式(HTTP、gRPC、WebSocket 等)
### 5.4 配置项
```go
type P2PConfig struct {
MaxRetries int // 最大重试次数(默认 5)
RetryBaseDelay time.Duration // 重试基础延迟(默认 2s,指数退避)
TaskTimeout time.Duration // 任务执行超时(默认 5 分钟)
HeartbeatInterval time.Duration // 心跳间隔(默认 30 秒)
}
```
---
## 6. 载荷格式
### 6.1 TaskRequestPayload(任务请求)
```json
{
"task": {
"id": "task-xxx",
"type": "text_generation",
"priority": "normal",
"deadline": "2026-01-01T00:00:00Z",
"payload": {
"prompt": "写一首诗",
"style": "古典"
}
},
"context": {
"session_id": "sess-xxx",
"parent_task_id": "parent-xxx",
"correlation_id": "corr-xxx",
"metadata": {}
}
}
```
### 6.2 TaskConfirmPayload(任务确认)
```json
{
"task_id": "task-xxx",
"confirmed": true,
"estimated_duration": 120,
"expected_completion": "2026-01-01T00:02:00Z",
"capabilities_used": ["text_generation"],
"constraints": {
"max_retries": 3,
"fallback_enabled": true
}
}
```
### 6.3 TaskRejectPayload(任务拒绝)
```json
{
"task_id": "task-xxx",
"reason": {
"code": "capability_lacking",
"message": "不支持该任务类型",
"details": null
},
"alternatives": {
"suggested_agent_id": "other-agent",
"retry_after": 300,
"partial_support": false
}
}
```
### 6.4 TaskStatusUpdatePayload(状态更新)
```json
{
"task_id": "task-xxx",
"status": "executing",
"progress": 50,
"message": "正在生成内容",
"intermediate_result": null
}
```
### 6.5 TaskCompletePayload(任务完成)
```json
{
"task_id": "task-xxx",
"result": {
"output_type": "text",
"output": "生成的文本内容...",
"metadata": {
"tokens": 1024,
"model": "gpt-4"
}
},
"summary": "任务已完成"
}
```
### 6.6 TaskFailedPayload(任务失败)
```json
{
"task_id": "task-xxx",
"error": {
"code": "execution_error",
"message": "模型调用超时",
"recoverable": true,
"details": null
},
"partial_result": null
}
```
### 6.7 TaskNeedInfoPayload(需要信息)
```json
{
"task_id": "task-xxx",
"missing_fields": [
{
"field": "style",
"reason": "未指定生成风格",
"suggested_value": "古典",
"alternatives": ["现代", "浪漫"]
}
],
"question": "请选择生成风格",
"required_by": "2026-01-01T00:01:00Z"
}
```
### 6.8 TaskNeedHumanPayload(需要人类)
```json
{
"task_id": "task-xxx",
"reason": {
"code": "ethical_decision",
"message": "涉及伦理决策需要人工审核"
},
"context": {
"task_summary": "生成一张人物肖像",
"current_progress": 30,
"blocked_reason": "需要确认肖像使用授权"
},
"options": [
{
"field": "authorized",
"value": true,
"pros_cons": "继续生成,假设已授权",
"confidence": 70
}
],
"escalation_level": 1,
"deadline": "2026-01-01T00:05:00Z"
}
```
### 6.9 HumanDecisionPayload(人类决策)
```json
{
"task_id": "task-xxx",
"decision_id": "dec-xxx",
"reply_to": "msg-xxx",
"decided_by": {
"user_id": "user-123",
"user_name": "张三",
"role": "reviewer"
},
"decision": {
"action": "approve",
"approved_fields": {
"authorized": true
},
"rejection_reason": ""
},
"notes": "已确认授权",
"timestamp": "2026-01-01T00:03:00Z"
}
```
**完整载荷定义:** [types/types.go](../types/types.go)
---
## 7. 错误码
| 错误码 | 说明 | HTTP 状态码(如适用) |
|--------|------|---------------------|
| `message_format_invalid` | 消息格式无效 | 400 |
| `capability_lacking` | 能力不足 | 403 |
| `agent_not_ready` | Agent 未就绪 | 503 |
| `task_not_found` | 任务不存在 | 404 |
| `task_already_completed` | 任务已完成 | 409 |
| `execution_timeout` | 执行超时 | 504 |
| `execution_failed` | 执行失败 | 500 |
| `permission_denied` | 权限不足 | 403 |
| `invalid_params` | 参数无效 | 400 |
| `rate_limited` | 限流 | 429 |
**完整错误定义:** [types/errors.go](../types/errors.go)
---
## 8. 传输要求
### 8.1 HTTP 传输
- Content-Type: `application/json`
- 请求方法:`POST`
- 端点路径:由 Agent 自行定义,建议 `/a2a/callback`
- 超时:建议 30 秒
### 8.2 签名验证(可选)
- 使用 HMAC-SHA256 签名
- 签名放在 Header:`X-A2A-Signature`
- 或放在消息体:`signature` 字段
---
## 9. 参考实现
本协议的参考实现位于 `p2p/` 目录:
| 文件 | 说明 |
|------|------|
| [p2p/protocol.go](../p2p/protocol.go) | P2P 协议核心实现 |
| [types/types.go](../types/types.go) | 数据类型定义 |
| [types/errors.go](../types/errors.go) | 错误定义 |
| [transport/transport.go](../transport/transport.go) | HTTP 传输实现 |
---
## 附录 A:能力声明格式
参见 [A2A 原始协议 - Capability](./01-a2a-raw-protocol.md#44-capability能力)

View File

@@ -0,0 +1,545 @@
# A2N 协议(Agent-to-N Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:实现规范
> 基于:[A2A 原始协议 v1.0.0](./01-a2a-raw-protocol.md)
---
## 1. 概述
A2N(Agent-to-N)组群协议定义了多个 Agent 组成群组进行协同工作的完整规范,包括群组管理、任务拆分与合并、群体决策、角色与权限等。
### 1.1 适用场景
- 复杂任务的拆分与并行执行(MapReduce 模式)
- 多 Agent 协同完成单一目标
- 群体投票与共识决策
- 临时或永久的 Agent 工作组
- 分级升级(escalation)机制
### 1.2 依赖
- 依赖 A2A 基础协议的点对点通信能力
- 群组内成员间的单播通信使用 A2A 基础协议
---
## 2. 核心概念
### 2.1 Group(群组)
由多个 Agent 组成的协作单元,具有明确的拓扑结构、角色分工与协作规则。
### 2.2 Topology(拓扑结构)
| 拓扑类型 | 说明 | 适用场景 |
|---------|------|---------|
| `star` | 星型,中心 coordinator 协调 | 简单任务分发 |
| `mesh` | 网状,成员间可直接通信 | 对等协作 |
| `chain` | 链式,流水线模式 | 顺序处理 |
| `hierarchical` | 层级型,多层管理 | 大型复杂任务 |
| `fullmesh` | 全连接网状 | 高可靠协作 |
### 2.3 Role(角色)
| 角色 | 说明 | 权限 |
|------|------|------|
| `coordinator` | 协调者 | 创建/解散群组、任务拆分、发起投票 |
| `worker` | 工作者 | 执行子任务、提交结果 |
| `reviewer` | 审核者 | 审核结果、发起投票 |
| `observer` | 观察者 | 只读,查看进度 |
### 2.4 GroupStatus(群组状态)
| 状态 | 说明 |
|------|------|
| `creating` | 创建中 |
| `active` | 活跃 |
| `frozen` | 冻结(不可执行新任务) |
| `disbanded` | 已解散(终态) |
---
## 3. 消息类型
### 3.1 消息类型清单
所有消息类型前缀:`group:` 或 `inter_group:`
#### 群组管理
| 消息类型 | 说明 |
|---------|------|
| `group:create` | 创建群组 |
| `group:disband` | 解散群组 |
| `group:join` | 加入群组 |
| `group:leave` | 离开群组 |
| `group:invite` | 邀请加入 |
| `group:accept_invite` | 接受邀请 |
| `group:reject_invite` | 拒绝邀请 |
| `group:member_update` | 成员更新通知 |
| `group:role_change` | 角色变更 |
#### 群组通信
| 消息类型 | 说明 |
|---------|------|
| `group:broadcast` | 广播(所有成员) |
| `group:multicast` | 组播(指定成员) |
| `group:direct` | 群内单播 |
| `group:relay` | 消息中继 |
#### 任务协作
| 消息类型 | 说明 |
|---------|------|
| `group:task_split` | 任务拆分 |
| `group:task_assign` | 任务分配 |
| `group:task_merge` | 结果合并 |
| `group:task_progress` | 子任务进度 |
#### 共识与投票
| 消息类型 | 说明 |
|---------|------|
| `group:vote` | 发起投票 |
| `group:vote_submit` | 提交投票 |
| `group:consensus` | 达成共识 |
#### 人在回路(HiL)
| 消息类型 | 说明 |
|---------|------|
| `group:consensus_escalate` | 共识僵局升级给人类 |
| `group:subtask_escalate` | 子任务阻塞升级给人类 |
| `group:human_decision` | 人类决策结果 |
| `inter_group:escalate` | 跨群组升级 |
**完整定义:** [types/types.go](../types/types.go)
---
## 4. 群组生命周期
### 4.1 状态机
```
creating → active → frozen → disbanded
↑ ↓
└─────────┘
解冻
```
### 4.2 群组创建流程
```
创建者 被邀请成员
| |
|------ group:invite -------------->| 发送邀请
| |
|<----- group:accept_invite --------| 接受邀请
| |
|------ group:member_update ------->| 通知成员更新
| |
[群组创建完成,状态变为 active] |
```
### 4.3 群组解散流程
```
coordinator 所有成员
| |
|------ group:disband ------------->| 通知解散
| |
[群组状态变为 disbanded] |
```
---
## 5. 任务协作流程
### 5.1 MapReduce 模式(拆分-执行-合并)
```
┌──────────────┐
│ coordinator │
└──────┬───────┘
│
group:task_split(拆分任务)
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ worker1 │ │ worker2 │ │ worker3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
│ 执行子任务 │ 执行子任务 │ 执行子任务
│ │ │
▼ ▼ ▼
子任务结果 子任务结果 子任务结果
└────────────────┼────────────────┘
│
group:task_merge(合并结果)
▼
┌──────────────┐
│ 最终结果 │
└──────────────┘
```
### 5.2 详细流程
1. **任务拆分阶段**
- coordinator 接收原始任务
- 将任务拆分为多个子任务(subtask)
- 为每个子任务指定执行的 worker
- 定义合并策略(merge strategy)
2. **并行执行阶段**
- 各 worker 并行执行子任务
- 定期上报进度(`group:task_progress`)
- 子任务完成后上报结果
3. **结果合并阶段**
- 所有子任务完成后,coordinator 合并结果
- 根据合并策略决定最终结果
- 输出最终结果
---
## 6. 合并策略(Merge Strategy)
| 策略 | 说明 |
|------|------|
| `all_success` | 全部成功才认为成功,否则失败 |
| `majority` | 多数子任务成功即可,取多数结果 |
| `any_complete` | 任意一个完成即返回结果 |
| `priority` | 按优先级,高优先级子任务结果优先 |
---
## 7. 投票与共识
### 7.1 投票流程
```
发起者 投票者
| |
|-------- group:vote -------------->| 发起投票
| |
|<------- group:vote_submit --------| 提交投票
| |
[计票,判断是否达到阈值] |
| |
|-------- group:consensus --------->| 公布结果
| |
```
### 7.2 投票规则
- `threshold`:通过阈值(0-1),如 0.5 表示过半
- `deadline`:投票截止时间
- `voters`:有投票权的成员列表
- 超时未投票视为弃权
### 7.3 共识僵局(Consensus Deadlock)
当投票无法达成共识(如平票、未达阈值且超时)时,可升级给人类决策。
---
## 8. 人在回路(HiL)
群组协作中有两种升级场景:
### 8.1 共识僵局升级
投票无法达成共识时,升级给人类裁决。
**消息:** `group:consensus_escalate`
### 8.2 子任务阻塞升级
某个子任务遇到无法自动解决的问题时,升级给人类。
**消息:** `group:subtask_escalate`
### 8.3 跨群组升级
问题超出当前群组能力时,可升级到上级群组或人类。
**消息:** `inter_group:escalate`
---
## 9. 接口规范
### 9.1 GroupProtocol 接口
```go
type GroupProtocol interface {
// 处理群组消息
HandleMessage(ctx context.Context, msg *UnifiedA2AMessage) (*UnifiedA2AMessage, error)
// 设置任务处理器
SetTaskHandler(h GroupTaskHandler)
// 查询群组列表
GetGroups() []*AgentGroup
// 查询特定群组
GetGroup(groupID string) (*AgentGroup, bool)
}
```
**参考实现:** [group/protocol.go](../group/protocol.go)
### 9.2 GroupTaskHandler 接口
```go
type GroupTaskHandler interface {
// 执行子任务
ExecuteSubtask(ctx context.Context, subtaskID string, payload map[string]interface{}) (any, error)
// 合并多个子任务结果
MergeResults(results []SubtaskResult, strategy MergeStrategy) (any, error)
// 验证任务是否可拆分,返回拆分方案
ValidateTaskSplit(taskType string, requirements map[string]interface{}) ([]Subtask, bool)
}
```
**说明:**
- `ExecuteSubtask`:执行单个子任务的业务逻辑
- `MergeResults`:根据策略合并多个子任务的结果
- `ValidateTaskSplit`:验证任务是否可拆分,并给出拆分方案
### 9.3 配置项
```go
type GroupConfig struct {
MaxGroupSize int // 最大群组人数(默认 20)
MaxSubtasks int // 最大子任务数(默认 50)
ConsensusTimeout time.Duration // 共识超时(默认 5 分钟)
TaskTimeout time.Duration // 任务超时(默认 10 分钟)
}
```
---
## 10. 载荷格式
### 10.1 GroupCreatePayload(创建群组)
```json
{
"group": {
"name": "视频制作组",
"topology": "star",
"config": {
"allow_self_leave": true,
"requires_approval": false,
"consensus_required": false,
"consensus_threshold": 0.5,
"broadcast_mode": "all"
},
"initial_members": [
{
"agent_id": "worker-1",
"system_id": "sys-a",
"endpoint": "http://worker1/a2a"
}
],
"roles": [
{
"id": "coordinator",
"name": "协调者",
"permissions": ["create", "disband", "split", "vote"],
"max_tasks": 10
}
]
}
}
```
### 10.2 GroupTaskSplitPayload(任务拆分)
```json
{
"original_task": {
"id": "task-xxx",
"type": "video_generation",
"requirements": {
"script": "xxx",
"duration": 60
},
"deadline": "2026-01-01T00:10:00Z"
},
"subtasks": [
{
"id": "sub-1",
"description": "生成脚本",
"assigned_to": "writer-agent",
"dependencies": [],
"estimated_duration": 120,
"priority": 1
},
{
"id": "sub-2",
"description": "生成配音",
"assigned_to": "voice-agent",
"dependencies": ["sub-1"],
"estimated_duration": 180,
"priority": 2
}
],
"merge_strategy": "all_success"
}
```
### 10.3 GroupTaskMergePayload(结果合并)
```json
{
"original_task_id": "task-xxx",
"subtask_results": [
{
"subtask_id": "sub-1",
"assigned_to": "writer-agent",
"status": "success",
"result": { "script": "..." },
"completed_at": "2026-01-01T00:02:00Z"
}
],
"merge_strategy_used": "all_success",
"merged_result": {
"video_url": "http://..."
},
"consensus_achieved": true
}
```
### 10.4 GroupVotePayload(投票)
```json
{
"vote_id": "vote-xxx",
"question": "是否采用方案A?",
"options": ["方案A", "方案B", "方案C"],
"voters": ["agent-1", "agent-2", "agent-3"],
"deadline": "2026-01-01T00:05:00Z",
"threshold": 0.66,
"status": "open",
"votes": []
}
```
### 10.5 GroupConsensusEscalatePayload(共识升级)
```json
{
"vote_id": "vote-xxx",
"escalation_id": "esc-xxx",
"question": "是否采用方案A?",
"votes": [
{
"voter_id": "agent-1",
"voter_role": "reviewer",
"option": "方案A",
"reason": "质量更好"
}
],
"consensus_status": {
"required_threshold": 0.66,
"actual_threshold": 0.33,
"leading_option": "方案A",
"options_tied": false
},
"escalate_reason": {
"code": "no_consensus",
"message": "无法达成共识"
},
"escalation_level": 2,
"deadline": "2026-01-01T00:10:00Z"
}
```
**完整载荷定义:** [types/types.go](../types/types.go)
---
## 11. 数据结构
### 11.1 AgentGroup(群组)
```go
type AgentGroup struct {
ID string
Name string
Topology GroupTopology
CreatedBy string
CreatedAt Timestamp
Config GroupConfig
Members []GroupMember
Roles []GroupRole
Status GroupStatus
}
```
### 11.2 GroupMember(群组成员)
```go
type GroupMember struct {
AgentID string
AgentName string
Role string
JoinedAt Timestamp
Status GroupMemberStatus
Capabilities []string
LoadFactor float64 // 负载因子 0-1,用于任务分配
}
```
### 11.3 Subtask(子任务)
```go
type Subtask struct {
ID string
Description string
AssignedTo string
Dependencies []string
EstimatedDuration int
Priority int
}
```
---
## 12. 错误码
群组协议特有错误码:
| 错误码 | 说明 |
|--------|------|
| `group_not_found` | 群组不存在 |
| `group_not_active` | 群组不活跃 |
| `group_full` | 群组已满 |
| `already_member` | 已是成员 |
| `not_group_member` | 不是群组成员 |
| `member_not_found` | 成员不存在 |
| `permission_denied` | 权限不足 |
| `invalid_params` | 参数无效 |
---
## 13. 参考实现
本协议的参考实现位于 `group/` 目录:
| 文件 | 说明 |
|------|------|
| [group/protocol.go](../group/protocol.go) | 群组协议核心实现 |
| [types/types.go](../types/types.go) | 群组相关数据类型 |
---
## 附录 A:与 A2A 基础协议的关系
- 群组内的点对点通信使用 A2A 基础协议
- 群组协议在 A2A 基础协议之上提供群组管理、任务拆分合并、共识投票等能力
- 群组消息同样使用统一消息信封(`UnifiedA2AMessage`),通过 `group_id` 字段标识群组上下文

View File

@@ -0,0 +1,503 @@
# P2A 协议(Person/Program-to-Agent Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:实现规范
> 基于:[A2A 原始协议 v1.0.0](./01-a2a-raw-protocol.md)
---
## 1. 概述
P2A(Person/Program-to-Agent)调用协议定义了**非 Agent 调用方**(如普通后端服务、前端应用、第三方系统、人类用户)如何调用 Agent 能力的规范。
本协议将 A2A 协议的复杂性封装起来,提供简洁的 RESTful API,使调用方无需了解 A2A 协议细节即可使用 Agent 能力。
### 1.1 适用场景
- 前端应用直接调用 Agent 能力
- 后端业务系统集成 Agent 服务
- 第三方开发者通过 API 接入
- 无需实现完整 A2A 协议的调用方
### 1.2 设计原则
- **简单易用**:标准 RESTful API,JSON 格式
- **两种模式**:回调模式 + 轮询模式,调用方按需选择
- **认证简单**:API Key 认证,无需 HMAC 签名
- **文件支持**:支持 multipart/form-data 上传文件
- **错误友好**:清晰的错误码和错误信息
### 1.3 与 A2A 基础协议的关系
```
外部调用方 ──P2A 协议──▶ External API Layer ──A2A 基础协议──▶ Agent
```
P2A 协议是 A2A 协议的"简化入口",所有请求最终会转换为 A2A 消息发送给 Agent。
---
## 2. 认证方式
### 2.1 API Key 认证
- 在 HTTP Header 中传递:`X-API-Key: {api_key}`
- 或在 URL Query 中传递:`?api_key={api_key}`
- 如果服务端未配置 API Key,则跳过认证
### 2.2 回调验证(可选)
如果调用方提供了 `callback_token`,则服务端在回调时会在 Header 中带上 `X-Callback-Token`,用于调用方验证回调来源。
---
## 3. API 端点
所有端点默认前缀:`/api/v1/a2a`
### 3.1 端点清单
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/task` | 提交任务(JSON) |
| POST | `/task/multipart` | 提交任务(带文件上传) |
| GET | `/task/{task_id}` | 获取任务详情 |
| GET | `/task/{task_id}/status` | 获取任务状态(简化版) |
| GET | `/tasks` | 获取任务列表 |
| POST | `/task/{task_id}/cancel` | 取消任务 |
| GET | `/capabilities` | 获取能力清单 |
| POST | `/callback` | 接收回调(调用方实现) |
---
## 4. 任务提交
### 4.1 提交任务(JSON)
**请求:**
```http
POST /api/v1/a2a/task
Content-Type: application/json
X-API-Key: your-api-key
{
"task_type": "text_generation",
"payload": {
"prompt": "写一首诗",
"style": "古典"
},
"external_task_id": "my-task-001",
"priority": "normal",
"callback_url": "https://your-domain.com/callback",
"callback_token": "your-callback-token"
}
```
**请求字段说明:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `task_type` | string | 是 | 任务类型,如 `video_generation`, `image_generation` |
| `payload` | object | 是 | 任务参数,具体结构取决于 task_type |
| `external_task_id` | string | 否 | 调用方的任务 ID,用于关联 |
| `priority` | string | 否 | 优先级:`low`, `normal`, `high`, `urgent`,默认 `normal` |
| `callback_url` | string | 否 | 回调 URL,不提供则需轮询 |
| `callback_token` | string | 否 | 回调验证 token |
**响应:**
```json
{
"success": true,
"task_id": "task-abc123",
"status": "received",
"message": "任务已接收"
}
```
**响应字段说明:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | bool | 是否成功 |
| `task_id` | string | 服务端生成的任务 ID |
| `status` | string | 初始状态,通常为 `received` |
| `message` | string | 提示信息 |
| `error` | string | 错误信息(失败时) |
### 4.2 提交任务(Multipart 带文件)
**请求:**
```http
POST /api/v1/a2a/task/multipart
Content-Type: multipart/form-data
X-API-Key: your-api-key
------WebKitFormBoundary
Content-Disposition: form-data; name="task_type"
image_generation
------WebKitFormBoundary
Content-Disposition: form-data; name="payload"
{"prompt":"一只猫","style":"写实"}
------WebKitFormBoundary
Content-Disposition: form-data; name="priority"
normal
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="reference.jpg"
Content-Type: image/jpeg
[二进制数据]
------WebKitFormBoundary--
```
**表单字段:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `task_type` | string | 是 | 任务类型 |
| `payload` | string(JSON) | 是 | 任务参数(JSON 字符串) |
| `external_task_id` | string | 否 | 外部任务 ID |
| `priority` | string | 否 | 优先级 |
| `callback_url` | string | 否 | 回调 URL |
| `callback_token` | string | 否 | 回调 token |
| `file` | file | 否 | 上传的文件(可多个) |
**响应:** 同 JSON 提交方式
---
## 5. 任务查询
### 5.1 获取任务详情
**请求:**
```http
GET /api/v1/a2a/task/{task_id}
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"task": {
"id": "task-abc123",
"type": "text_generation",
"status": "completed",
"progress": 100,
"message": "任务已完成",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:01:00Z",
"completed_at": "2026-01-01T00:01:00Z",
"eta": "2026-01-01T00:01:00Z",
"result": {
"text": "生成的文本...",
"metadata": { "tokens": 1024 }
},
"error": null,
"external_task_id": "my-task-001"
}
}
```
**任务状态:**
| 状态 | 说明 |
|------|------|
| `received` | 已接收 |
| `processing` | 处理中 |
| `completed` | 已完成 |
| `failed` | 已失败 |
| `cancelled` | 已取消 |
### 5.2 获取任务状态(简化版)
**请求:**
```http
GET /api/v1/a2a/task/{task_id}/status
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"task_id": "task-abc123",
"status": "processing",
"progress": 50
}
```
### 5.3 获取任务列表
**请求:**
```http
GET /api/v1/a2a/tasks?status=processing&limit=20&offset=0
X-API-Key: your-api-key
```
**查询参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `status` | string | 否 | 按状态过滤 |
| `limit` | int | 否 | 每页数量,默认 20 |
| `offset` | int | 否 | 偏移量,默认 0 |
**响应:**
```json
{
"success": true,
"tasks": [
{
"id": "task-abc123",
"type": "text_generation",
"status": "completed",
"progress": 100,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:01:00Z"
}
],
"total": 1,
"limit": 20,
"offset": 0,
"has_more": false
}
```
---
## 6. 任务取消
**请求:**
```http
POST /api/v1/a2a/task/{task_id}/cancel
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"task_id": "task-abc123",
"status": "cancelled",
"message": "任务已取消"
}
```
---
## 7. 能力查询
**请求:**
```http
GET /api/v1/a2a/capabilities
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"capabilities": {
"agent_id": "my-agent",
"agent_name": "我的Agent",
"system_id": "my-system",
"protocol_version": "1.0.0",
"supported_task_types": [
{
"type": "text_generation",
"name": "文本生成",
"description": "生成各种类型的文本内容",
"estimated_time": "30s",
"input_schema": "...",
"output_schema": "..."
}
],
"supported_transport_modes": ["callback", "polling"],
"max_file_size": 104857600,
"max_concurrent_tasks": 10,
"support_progress_updates": true,
"support_streaming": false,
"support_hil": true,
"support_groups": true
}
}
```
---
## 8. 回调机制
如果调用方提供了 `callback_url`,服务端会在任务状态变化时主动回调。
### 8.1 回调请求
```http
POST {callback_url}
Content-Type: application/json
X-Callback-Token: {callback_token}
{
"task_id": "task-abc123",
"status": "completed",
"progress": 100,
"message": "任务已完成",
"result": {
"text": "生成的文本..."
},
"error": null,
"timestamp": "2026-01-01T00:01:00Z"
}
```
### 8.2 回调类型
| 触发时机 | 说明 |
|---------|------|
| 任务开始处理 | `status=processing`, `progress=0` |
| 进度更新 | `status=processing`, `progress=N` |
| 任务完成 | `status=completed`, `progress=100`, 包含 result |
| 任务失败 | `status=failed`, 包含 error |
| 任务取消 | `status=cancelled` |
### 8.3 回调响应
调用方应返回 2xx 状态码表示成功接收。如果返回非 2xx,服务端会重试(指数退避,最多 5 次)。
---
## 9. 错误处理
### 9.1 错误响应格式
```json
{
"success": false,
"error": "invalid_task_type",
"message": "不支持的任务类型"
}
```
### 9.2 错误码列表
| 错误码 | HTTP 状态码 | 说明 |
|--------|------------|------|
| `unauthorized` | 401 | API Key 无效或缺失 |
| `invalid_request` | 400 | 请求格式无效 |
| `invalid_task_type` | 400 | 任务类型不支持 |
| `invalid_params` | 400 | 参数无效 |
| `task_not_found` | 404 | 任务不存在 |
| `task_already_completed` | 409 | 任务已完成,无法取消 |
| `file_too_large` | 413 | 文件过大 |
| `rate_limited` | 429 | 请求过于频繁 |
| `internal_error` | 500 | 服务内部错误 |
| `service_unavailable` | 503 | 服务不可用 |
---
## 10. SDK 客户端接口
为方便调用方集成,提供 SDK 客户端。
### 10.1 SDK 初始化
```go
client := sdk.NewClient(sdk.ClientConfig{
BaseURL: "https://agent-api.example.com",
APIKey: "your-api-key",
Timeout: 30 * time.Second,
})
```
### 10.2 SDK 方法清单
| 方法 | 说明 |
|------|------|
| `SubmitTask(ctx, req)` | 提交任务(JSON) |
| `SubmitTaskWithFiles(ctx, req, files)` | 提交任务(带文件) |
| `GetTask(ctx, taskID)` | 获取任务详情 |
| `GetTaskStatus(ctx, taskID)` | 获取任务状态 |
| `ListTasks(ctx, status, limit, offset)` | 获取任务列表 |
| `CancelTask(ctx, taskID)` | 取消任务 |
| `GetCapabilities(ctx)` | 获取能力清单 |
| `WaitForTask(ctx, taskID, interval)` | 等待任务完成(轮询) |
### 10.3 数据结构
```go
type TaskRequest struct {
TaskType string
Payload map[string]any
ExternalTaskID string
Priority string
CallbackURL string
CallbackToken string
}
type TaskInfo struct {
ID string
Type string
Status string
Progress int
Message string
CreatedAt string
UpdatedAt string
CompletedAt string
ETA string
Result map[string]any
Error *TaskError
ExternalTaskID string
}
```
**完整 SDK 实现:** [sdk/client.go](../sdk/client.go)
---
## 11. 参考实现
本协议的参考实现:
| 模块 | 文件路径 | 说明 |
|------|---------|------|
| 服务端 | [externalapi/handler.go](../externalapi/handler.go) | External API Handler |
| 客户端 SDK | [sdk/client.go](../sdk/client.go) | Go 语言 SDK |
---
## 附录 A:调用模式选择
### 回调模式 vs 轮询模式
| 对比项 | 回调模式 | 轮询模式 |
|--------|---------|---------|
| 实时性 | 高,状态变化即时通知 | 低,取决于轮询间隔 |
| 实现复杂度 | 调用方需提供公网可访问的回调接口 | 简单,只需调用 GET 接口 |
| 网络要求 | 调用方需要公网可达 | 只需调用方能访问服务端 |
| 适用场景 | 生产环境、需要实时通知 | 开发测试、内网环境 |
### 选择建议
- **有公网服务器**:使用回调模式
- **本地开发 / 内网环境**:使用轮询模式
- **长时间任务**:建议回调模式,避免频繁轮询
- **短任务(<10s)**:可直接同步等待或高频轮询
---
## 附录 B:从 A2A 协议到 P2A 协议的映射
| A2A 概念 | P2A 对应 | 说明 |
|---------|---------|------|
| `p2p:task_request` | `POST /task` | 任务提交 |
| `p2p:task_status_update` | 回调 / GET /task | 状态更新 |
| `p2p:task_complete` | 回调(status=completed) | 任务完成 |
| `p2p:task_failed` | 回调(status=failed) | 任务失败 |
| `p2p:task_cancel` | `POST /task/{id}/cancel` | 任务取消 |
| `p2p:capability_query` | `GET /capabilities` | 能力查询 |
| Agent 身份 | API Key | 简化认证 |
| HMAC 签名 | Callback Token | 简化验证 |

View File

@@ -0,0 +1,519 @@
# SDK 实现说明
> 版本:1.0.0
> 更新日期:2026-06-27
> 协议版本:1.0.0
> 关联协议:
> - [A2A 协议](./02-a2a-protocol.md)
> - [A2N 协议](./03-a2n-protocol.md)
> - [P2A 协议](./04-p2a-protocol.md)
---
## 1. 概述
SDK 按照 A2A 协议族的三个子协议进行了模块化设计,分为三个子包,命名与协议名保持一致:
```
┌─────────────────────────────────────────────────────────────┐
│ SDK (sdk/) │
├──────────────┬───────────────────┬───────────────────────────┤
│ sdk/p2a │ sdk/a2a │ sdk/a2n │
│ P2A 协议 │ A2A 协议 │ A2N 协议 │
│ (外部调用) │ (Agent间通信) │ (群组协作) │
│ ✅ 完整实现 │ ✅ 完整实现 │ ✅ 完整实现 │
└──────────────┴───────────────────┴───────────────────────────┘
```
---
## 2. SDK 目录结构
```
sdk/
├── doc.go # SDK 包文档(使用说明)
├── p2a/
│ └── client.go # P2A 协议 - 外部调用客户端
├── a2a/
│ └── client.go # A2A 协议 - Agent间通信客户端
└── a2n/
└── client.go # A2N 协议 - 群组协作客户端
```
---
## 3. 第一部分:sdk/p2a - P2A 协议
**协议对应**:[P2A 协议](./04-p2a-protocol.md)
**版本**:1.0.0
**用途**:非 Agent 调用方(前端、后端服务、第三方应用)通过 REST API 调用 Agent 能力
### 3.1 实现对应关系
| P2A 协议功能 | SDK 方法 | 位置 |
|-------------|---------|------|
| 认证方式 | `setHeaders()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 提交任务(JSON) | `SubmitTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 提交任务(Multipart) | `SubmitTaskWithFiles()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 获取任务详情 | `GetTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 获取任务状态(简化版) | `GetTaskStatus()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 获取任务列表 | `ListTasks()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 取消任务 | `CancelTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 能力查询 | `GetCapabilities()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 回调验证 | `VerifyCallback()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 轮询等待 | `WaitForTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
### 3.2 核心数据结构
```go
// Client 客户端
type Client struct {
baseURL string
apiKey string
httpClient *http.Client
}
// 配置
type ClientConfig struct {
BaseURL string
APIKey string
Timeout time.Duration
MaxRetries int
}
// 任务请求
type TaskRequest struct {
TaskType string
Payload map[string]any
ExternalTaskID string
Priority string
CallbackURL string
CallbackToken string
}
// 任务信息
type TaskInfo struct {
ID string
Type string
Status string
Progress int
Result map[string]any
Error *TaskError
}
// 文件上传参数
type FileParam struct {
FieldName string
Filename string
Data []byte
MimeType string
}
```
### 3.3 使用示例
```go
import "dp-headless/a2a/sdk/p2a"
client := p2a.NewClient(p2a.ClientConfig{
BaseURL: "https://agent-api.example.com",
APIKey: "your-api-key",
})
// 提交任务并等待完成
taskInfo, err := client.CreateAndWait(ctx, "text_generation", map[string]any{
"prompt": "写一首诗",
"style": "古典",
}, 5*time.Second)
// 使用回调模式
resp, err := client.CreateWithCallback(ctx, "image_generation", payload,
"https://your-domain.com/callback",
"callback-token",
)
```
---
## 4. 第二部分:sdk/a2a - A2A 协议
**协议对应**:[A2A 协议](./02-a2a-protocol.md)
**版本**:1.0.0
**用途**:Agent 之间的点对点通信,包含消息发送、签名验证、回调处理
### 4.1 实现对应关系
| A2A 协议功能 | SDK 方法 | 位置 |
|------------|---------|------|
| 发送任务请求 | `SendTaskRequest()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 发送任务取消 | `SendTaskCancel()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 发送人类决策 | `SendHumanDecision()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 查询能力 | `SendCapabilityQuery()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 消息签名 | `signMessage()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 签名验证 | `verifySignature()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 任务状态管理 | `GetTaskState()`, `ListTasks()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 等待任务完成 | `WaitForTask()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 回调服务 | `startCallbackServer()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
### 4.2 核心数据结构
```go
// Client A2A客户端
type Client struct {
agentEndpoint types.AgentEndpoint
authSecret string
callbackHandler MessageCallbackHandler
tasks map[string]*A2ATaskState
}
// 配置
type ClientConfig struct {
AgentEndpoint types.AgentEndpoint
AuthSecret string
CallbackPort int
Timeout time.Duration
MaxRetries int
}
// 消息回调处理器接口
type MessageCallbackHandler interface {
HandleTaskRequest(ctx context.Context, msg *types.UnifiedA2AMessage) (*types.UnifiedA2AMessage, error)
HandleTaskStatusUpdate(ctx context.Context, msg *types.UnifiedA2AMessage) error
HandleTaskComplete(ctx context.Context, msg *types.UnifiedA2AMessage) error
HandleTaskFailed(ctx context.Context, msg *types.UnifiedA2AMessage) error
HandleNeedHuman(ctx context.Context, msg *types.UnifiedA2AMessage) error
}
// 任务状态
type A2ATaskState struct {
TaskID string
Status types.P2PTaskStatus
Progress int
Message string
Result any
Error *types.A2AError
}
```
### 4.3 使用示例
```go
import "dp-headless/a2a/sdk/a2a"
a2aClient, err := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: types.AgentEndpoint{
AgentID: "my-agent",
AgentName: "我的Agent",
SystemID: "my-system",
Endpoint: "http://localhost:8080/a2a/callback",
},
AuthSecret: "my-hmac-secret",
CallbackPort: 8080,
})
// 设置消息处理器
a2aClient.SetMessageHandler(&myHandler{})
// 发送任务请求给其他Agent
taskID, err := a2aClient.SendTaskRequest(ctx, targetAgent, "video_generation", map[string]any{
"prompt": "生成一段视频",
})
```
---
## 5. 第三部分:sdk/a2n - A2N 协议
**协议对应**:[A2N 协议](./03-a2n-protocol.md)
**版本**:1.0.0
**用途**:多 Agent 群组协作,包含群组管理、任务拆分合并、投票、升级
### 5.1 实现对应关系
| A2N 协议功能 | SDK 方法 | 位置 |
|------------|---------|------|
| 创建群组 | `CreateGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 加入群组 | `JoinGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 离开群组 | `LeaveGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 解散群组 | `DisbandGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 提交群组任务 | `SubmitGroupTask()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 发送子任务结果 | `SendSubtaskResult()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 发起投票 | `InitiateVote()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 提交投票 | `SubmitVote()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 广播消息 | `Broadcast()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 升级到人类 | `EscalateToHuman()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
### 5.2 核心数据结构
```go
// Client A2N客户端
type Client struct {
a2aClient *a2a.Client
groups map[string]*GroupState
eventHandler GroupEventHandler
}
// 配置
type ClientConfig struct {
A2AClient *a2a.Client
EventHandler GroupEventHandler
}
// 群组状态
type GroupState struct {
GroupID string
Name string
Topology types.GroupTopology
Status types.GroupStatus
Members []types.GroupMember
Role string // "coordinator" | "worker"
}
// 群组事件处理器接口
type GroupEventHandler interface {
OnGroupCreated(ctx context.Context, groupID string) error
OnGroupDisbanded(ctx context.Context, groupID string) error
OnMemberJoined(ctx context.Context, groupID string, member types.GroupMember) error
OnMemberLeft(ctx context.Context, groupID string, agentID string) error
OnTaskSplit(ctx context.Context, groupID string, originalTaskID string, subtasks []types.Subtask) error
OnSubtaskProgress(ctx context.Context, groupID string, subtaskID string, progress int) error
OnTaskMerged(ctx context.Context, groupID string, originalTaskID string, result any) error
OnVoteInitiated(ctx context.Context, groupID string, vote types.GroupVotePayload) error
OnVoteResult(ctx context.Context, groupID string, voteID string, option string, passed bool) error
OnEscalation(ctx context.Context, groupID string, escalationID string, level int) error
OnBroadcast(ctx context.Context, groupID string, sender types.AgentEndpoint, message []byte) error
}
```
### 5.3 使用示例
```go
import (
"dp-headless/a2a/sdk/a2n"
"dp-headless/a2a/sdk/a2a"
)
// 创建A2A客户端
a2aClient, _ := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: myEndpoint,
})
// 创建A2N客户端
a2nClient, err := a2n.NewClient(&a2n.ClientConfig{
A2AClient: a2aClient,
EventHandler: &myGroupHandler{},
})
// 创建群组
group, err := a2nClient.CreateGroup(ctx, "视频制作组", types.GroupTopologyStar, nil, members)
// 作为Coordinator,提交任务
subtasks, err := a2nClient.SubmitGroupTask(ctx, group.GroupID, "video_generation",
map[string]any{"script": "..."},
types.MergeAllSuccess,
)
// 发起投票
voteID, err := a2nClient.InitiateVote(ctx, group.GroupID,
"选择哪个方案?",
[]string{"方案A", "方案B"},
voterIDs,
0.5,
5*time.Minute,
)
```
---
## 6. SDK 架构总览
```
┌─────────────────────────────────────────────────────────────┐
│ SDK (dp-headless/a2a/sdk) │
├─────────────────────────────────────────────────────────────┤
│ doc.go │
│ ├── 包文档和使用说明 │
│ └── 指向各子包的链接 │
├────────────────┬────────────────┬─────────────────────────┤
│ sdk/p2a │ sdk/a2a │ sdk/a2n │
│ │ │ │
│ P2A协议 │ A2A协议 │ A2N协议 │
│ │ │ │
│ ├── Client │ ├── Client │ ├── Client │
│ ├── 配置 │ ├── 配置 │ ├── 配置 │
│ ├── 任务操作 │ ├── 消息发送 │ ├── 群组管理 │
│ ├── 能力查询 │ ├── 任务状态 │ ├── 任务协作 │
│ └── 回调验证 │ ├── 回调服务 │ ├── 投票 │
│ │ └── 签名 │ └── 升级 │
└────────────────┴────────────────┴─────────────────────────┘
```
---
## 7. 快速开始
### 7.1 P2A 调用(外部调用者)
```go
package main
import (
"context"
"fmt"
"dp-headless/a2a/sdk/p2a"
)
func main() {
client := p2a.NewClient(p2a.ClientConfig{
BaseURL: "https://agent.example.com",
APIKey: "your-api-key",
})
ctx := context.Background()
// 方式1: 轮询等待
result, err := client.CreateAndWait(ctx, "text_generation", map[string]any{
"prompt": "写一首诗",
}, 5*time.Second)
if err != nil {
fmt.Println("Error:", err)
}
fmt.Printf("Result: %v\n", result.Result)
// 方式2: 回调模式
resp, err := client.CreateWithCallback(ctx, "image_generation",
map[string]any{"prompt": "一只猫"},
"https://your-domain.com/callback",
"token",
)
fmt.Printf("Task ID: %s\n", resp.TaskID)
}
```
### 7.2 A2A 通信(Agent 间)
```go
package main
import (
"context"
"fmt"
"dp-headless/a2a/sdk/a2a"
"dp-headless/a2a/types"
)
func main() {
client, _ := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: types.AgentEndpoint{
AgentID: "agent-001",
AgentName: "Agent 001",
SystemID: "my-system",
Endpoint: "http://localhost:8080/a2a/callback",
},
AuthSecret: "secret-key",
CallbackPort: 8080,
})
ctx := context.Background()
target := types.AgentEndpoint{
AgentID: "agent-002",
Endpoint: "http://agent-002:8080/a2a/callback",
}
// 发送任务请求
taskID, err := client.SendTaskRequest(ctx, target, "data_analysis", map[string]any{
"dataset": "sales.csv",
})
fmt.Printf("Task ID: %s\n", taskID)
// 等待完成
state, err := client.WaitForTask(ctx, taskID, 5*time.Minute)
fmt.Printf("Status: %s\n", state.Status)
}
```
### 7.3 A2N 群组协作
```go
package main
import (
"context"
"fmt"
"dp-headless/a2a/sdk/a2n"
"dp-headless/a2a/sdk/a2a"
"dp-headless/a2a/types"
)
func main() {
a2aClient, _ := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: myEndpoint,
})
a2nClient, _ := a2n.NewClient(&a2n.ClientConfig{
A2AClient: a2aClient,
})
ctx := context.Background()
// 创建群组
group, _ := a2nClient.CreateGroup(ctx, "协作组", types.GroupTopologyStar, nil, []types.AgentEndpoint{
{AgentID: "member-1", Endpoint: "http://member-1:8080"},
{AgentID: "member-2", Endpoint: "http://member-2:8080"},
})
fmt.Printf("Group ID: %s\n", group.GroupID)
// 提交任务
subtasks, _ := a2nClient.SubmitGroupTask(ctx, group.GroupID, "video_generation",
map[string]any{"script": "..."},
types.MergeAllSuccess,
)
fmt.Printf("Split into %d subtasks\n", len(subtasks))
}
```
---
## 8. 协议与包名对应关系
| 协议名称 | 协议文档 | SDK 包名 | 包路径 |
|---------|---------|---------|--------|
| A2A 原始协议 | [01-a2a-raw-protocol.md](./01-a2a-raw-protocol.md) | - | 纲领性文档 |
| A2A 协议 | [02-a2a-protocol.md](./02-a2a-protocol.md) | `a2a` | `sdk/a2a/` |
| A2N 协议 | [03-a2n-protocol.md](./03-a2n-protocol.md) | `a2n` | `sdk/a2n/` |
| P2A 协议 | [04-p2a-protocol.md](./04-p2a-protocol.md) | `p2a` | `sdk/p2a/` |
**所有协议版本:1.0.0**
---
## 9. 扩展建议
| 优先级 | 扩展项 | 说明 |
|-------|-------|------|
| 高 | gRPC 传输 | 添加 gRPC 协议支持 |
| 高 | WebSocket | 添加 WebSocket 流式传输 |
| 中 | 批量操作 | 批量任务提交、批量查询 |
| 中 | 指标监控 | 内置指标收集 |
| 低 | 多语言 SDK | Python、JavaScript 等 |
---
## 附录:相关文件索引
| 文件路径 | 说明 |
|---------|------|
| [sdk/doc.go](../sdk/doc.go) | SDK 包文档 |
| [sdk/p2a/client.go](../sdk/p2a/client.go) | P2A 客户端实现 |
| [sdk/a2a/client.go](../sdk/a2a/client.go) | A2A 客户端实现 |
| [sdk/a2n/client.go](../sdk/a2n/client.go) | A2N 客户端实现 |
| [externalapi/handler.go](../externalapi/handler.go) | P2A 协议服务端实现 |
| [p2p/protocol.go](../p2p/protocol.go) | A2A 协议核心实现 |
| [group/protocol.go](../group/protocol.go) | A2N 协议核心实现 |
| [types/types.go](../types/types.go) | 公共类型定义 |
| [transport/transport.go](../transport/transport.go) | HTTP 传输层实现 |

View File

@@ -0,0 +1,777 @@
# A2A 协议族 - 解决方案白皮书
> 版本:1.0.0
> 日期:2026-06-27
> 状态:草案
> 前置阅读:[A2A 原始协议](./01-a2a-raw-protocol.md)
---
## 目录
1. [概述](#1-概述)
2. [方案一:Agent 发现与广播机制](#2-方案一agent-发现与广播机制)
3. [方案二:身份认证与信任体系](#3-方案二身份认证与信任体系)
4. [方案三:协议缺失内容补全](#4-方案三协议缺失内容补全)
5. [分阶段实施路线图](#5-分阶段实施路线图)
6. [技术选型建议](#6-技术选型建议)
---
## 1. 概述
### 1.1 背景
当前 A2A 协议族定义了 Agent 之间"怎么说话"(消息格式、任务流程),但缺少:
- **怎么找到对方** — Agent 发现与广播机制
- **怎么相信对方** — 身份认证与信任体系
- **怎么规模化协作** — 路由、调度、激励等基础设施
本文档针对上述问题给出完整的解决方案。
### 1.2 设计原则
| 原则 | 说明 |
|------|------|
| **渐进式** | 从简单到复杂,每阶段都可用 |
| **可插拔** | 组件可替换,不绑定具体实现 |
| **传输无关** | 不依赖特定底层网络 |
| **安全优先** | 身份认证是基础设施,不是可选功能 |
| **向后兼容** | 不破坏现有协议,增量扩展 |
---
## 2. 方案一:Agent 发现与广播机制
### 2.1 整体架构
```
┌─────────────────────────────────────────────────────────────────────┐
│ Agent 发现与广播层 │
├─────────────────────┬─────────────────────┬─────────────────────────┤
│ 集中式目录服务 │ 分布式 Gossip │ 组播/广播 │
│ (Directory) │ (Gossip) │ (Multicast) │
│ ┌───────────────┐ │ ┌───────────────┐ │ ┌────────────────┐ │
│ │ 注册/注销 │ │ │ 消息泛洪 │ │ │ 子网内广播 │ │
│ │ 能力索引 │ │ │ 状态同步 │ │ │ 邻居发现 │ │
│ │ 名字解析 │ │ │ 节点发现 │ │ │ 本地心跳 │ │
│ └───────────────┘ │ └───────────────┘ │ └────────────────┘ │
└─────────────────────┴─────────────────────┴─────────────────────────┘
```
### 2.2 模式一:集中式目录服务(推荐首选)
#### 2.2.1 架构说明
引入 **Agent Directory Service(ADS)** 作为中心化的 Agent 目录服务,类似 DNS。
```
Agent A ──注册──▶ ┌─────────────┐ ◀──注册── Agent B
│ Directory │
Agent C ──查询──▶ │ Service │ ◀──心跳── Agent D
└─────────────┘
│
▼
能力索引
在线状态
端点地址
```
#### 2.2.2 核心接口
**注册协议(Agent → Directory):**
| 操作 | 消息类型 | 说明 |
|------|---------|------|
| 注册 | `dir:register` | Agent 上线时注册自身信息 |
| 注销 | `dir:unregister` | Agent 下线时注销 |
| 心跳 | `dir:heartbeat` | 定期上报存活状态 |
| 更新能力 | `dir:update_capabilities` | 能力变化时更新 |
**查询协议(Agent → Directory):**
| 操作 | 消息类型 | 说明 |
|------|---------|------|
| 按 ID 查询 | `dir:query_by_id` | 根据 AgentID 查端点 |
| 按能力查询 | `dir:query_by_capability` | 找具备某能力的 Agent |
| 列表查询 | `dir:list` | 分页列出所有 Agent |
| 批量查询 | `dir:batch_query` | 批量查询多个 Agent |
#### 2.2.3 注册数据结构
```json
{
"agent_id": "agent-001",
"agent_name": "视频生成Agent",
"system_id": "video-system",
"endpoint": "https://agent-001.example.com/a2a/callback",
"version": "1.0.0",
"capabilities": [
{
"type": "video_generation",
"name": "视频生成",
"description": "根据脚本生成视频",
"max_concurrent": 10,
"avg_latency_ms": 5000
}
],
"transports": ["http", "grpc"],
"status": "online",
"last_heartbeat": "2026-06-27T10:00:00Z",
"trust_score": 85,
"public_key": "-----BEGIN PUBLIC KEY-----\n..."
}
```
#### 2.2.4 优缺点
| 优点 | 缺点 |
|------|------|
| 查询效率高(O(1)) | 单点故障风险 |
| 支持复杂查询(按能力、按系统等) | 中心节点需信任 |
| 易于管理和监控 | 扩展性有上限 |
| 实现简单 | 存在注册/注销时序问题 |
---
### 2.3 模式二:分布式 Gossip 协议(可选扩展)
#### 2.3.1 架构说明
无中心节点,Agent 之间通过 Gossip 协议(epidemic protocol)扩散状态信息,类似比特币网络的节点发现。
```
Agent A ──gossip──▶ Agent B ──gossip──▶ Agent D
│ │
└──gossip──▶ Agent C ──gossip──▶ Agent E
```
#### 2.3.2 核心机制
| 机制 | 说明 |
|------|------|
| **Peer Sampling** | 每个 Agent 维护一个部分视图(partial view) |
| **Active Push** | 定期随机选几个节点推送本地状态 |
| **Passive Pull** | 收到推送后,有概率回传自身状态 |
| **TTL** | 消息有存活跳数,防止无限扩散 |
| **Deduplication** | 消息 ID 去重,避免重复处理 |
#### 2.3.3 Gossip 消息格式
```json
{
"type": "gossip:state",
"from": "agent-001",
"view": [
{"agent_id": "agent-002", "endpoint": "...", "capabilities": [...], "timestamp": "..."},
{"agent_id": "agent-003", "endpoint": "...", "capabilities": [...], "timestamp": "..."},
{"agent_id": "agent-004", "endpoint": "...", "capabilities": [...], "timestamp": "..."}
],
"ttl": 5,
"msg_id": "msg-xxx"
}
```
#### 2.3.4 优缺点
| 优点 | 缺点 |
|------|------|
| 完全去中心化 | 查询效率低(需本地构建视图) |
| 容错性强 | 状态最终一致,有延迟 |
| 可无限扩展 | 实现复杂度高 |
| 无单点故障 | 网络流量大 |
---
### 2.4 模式三:本地组播/邻居发现(局域网场景)
适用于同一内网/集群内的 Agent 快速发现。
#### 2.4.1 机制
| 机制 | 说明 |
|------|------|
| **UDP 组播** | 定期在组播地址发送广播 |
| **mDNS/DNS-SD** | 类似 Apple Bonjour 的服务发现 |
| **ARP/NDP** | 链路层发现(不推荐) |
#### 2.4.2 适用场景
- 边缘计算集群
- 同一机房内的 Agent 集群
- 本地开发测试环境
---
### 2.5 广播机制设计
#### 2.5.1 广播类型
| 广播范围 | 消息类型前缀 | 说明 |
|---------|-------------|------|
| 全网广播 | `global:` | 所有在线 Agent |
| 系统广播 | `system:` | 同一 system_id 内 |
| 群组广播 | `group:` | 同一群组内(已有) |
| 能力组广播 | `cap:` | 具备某能力的 Agent |
#### 2.5.2 广播实现
**集中式模式下:**
- 由 Directory Service 转发广播消息
- 支持按条件过滤(按能力、按系统等)
**分布式模式下:**
- 通过 Gossip 协议泛洪
- TTL 控制扩散范围
- 消息 ID 去重
---
### 2.6 心跳与存活检测
#### 2.6.1 心跳机制
```
Agent ───heartbeat──▶ Directory
(每30秒)
Directory 判定离线:
- 连续 3 次未收到心跳 → 标记为 offline
- 离线 24 小时 → 自动注销
```
#### 2.6.2 主动探测
```
Directory ───ping──▶ Agent
◀──pong──
```
用于验证 Agent 是否真的在线(防止心跳伪造)。
---
## 3. 方案二:身份认证与信任体系
### 3.1 三层身份架构
```
┌─────────────────────────────────────────────────┐
│ Layer 3: 信任评分 (Trust Score) │
│ 声誉系统、历史行为、第三方评价 │
├─────────────────────────────────────────────────┤
│ Layer 2: 身份验证 (Authentication) │
│ 签名验证、证书校验、DID 验证 │
├─────────────────────────────────────────────────┤
│ Layer 1: 身份标识 (Identity) │
│ AgentID + 公钥 / 证书 / DID │
└─────────────────────────────────────────────────┘
```
### 3.2 Layer 1:身份标识方案
#### 3.2.1 方案 A:公钥身份(推荐基础版)
**核心思想**:AgentID = 公钥哈希(类似比特币地址)
```
AgentID = base58(sha256(public_key)[:20])
例:agent-1A2B3C4D5E6F...
```
**优点:**
- 自包含,不需要中心 CA
- 公钥可验证身份
- 实现简单
**缺点:**
- 身份无法撤销(私钥泄露 = 身份被盗)
- 没有人类可读的名字
- 无法关联真实世界身份
**适用场景**:内部系统、测试环境、信任度要求不高的场景
#### 3.2.2 方案 B:PKI 证书体系
**核心思想**:引入 CA(Certificate Authority)颁发证书
```
CA 根证书
├── 系统A证书
│ ├── Agent-001 证书
│ └── Agent-002 证书
└── 系统B证书
└── Agent-003 证书
```
**优点:**
- 成熟的工业标准
- 支持证书撤销(CRL/OCSP)
- 有层级信任关系
**缺点:**
- 需要信任中心化 CA
- 证书管理复杂
- 跨 CA 互操作需要桥接
**适用场景**:企业内部、有明确信任中心的场景
#### 3.2.3 方案 C:区块链 DID(去中心化身份)
**核心思想**:身份上链,链上存公钥和属性,类似 W3C DID 标准。
```
链上存储:
DID → {
"publicKey": "...",
"controller": "...",
"authentication": [...],
"service": [...],
"revoked": false,
"timestamp": "..."
}
```
**三种实现路径:**
| 路径 | 说明 | 适用场景 |
|------|------|---------|
| **公链 DID** | 使用以太坊/Solana 等公链 | 开放生态、跨组织协作 |
| **联盟链 DID** | 多机构共建联盟链 | 行业联盟、政企合作 |
| **私有链 DID** | 单组织内部链 | 企业内部、对性能要求高 |
**优点:**
- 去中心化,无单点故障
- 身份可验证、可追溯
- 支持跨系统互认
- 不可篡改的身份历史
**缺点:**
- 链上操作有延迟
- 需要支付 Gas 费(公链)
- 实现复杂度高
- 隐私保护需额外设计
---
### 3.3 Layer 2:身份验证方案
#### 3.3.1 消息签名验证
所有 A2A 消息必须签名,签名方式:
```
签名内容 = SHA256(message_id + sender + receiver + payload + timestamp)
签名 = Sign(private_key, 签名内容)
验证 = Verify(public_key, 签名内容, 签名)
```
#### 3.3.2 签名放置位置
| 位置 | 说明 |
|------|------|
| 消息体内 | `signature` 字段 |
| HTTP Header | `X-A2A-Signature` |
| gRPC Metadata | `x-a2a-signature` |
#### 3.3.3 防重放攻击
```
每个消息有唯一 message_id + timestamp
接收方维护已处理 message_id 缓存(如最近 1 小时)
timestamp 与本地时间差超过 5 分钟 → 拒绝
```
---
### 3.4 Layer 3:信任评分体系
#### 3.4.1 信任分维度
| 维度 | 权重 | 说明 |
|------|------|------|
| 任务完成率 | 30% | 历史任务成功 / 总任务数 |
| 任务质量 | 25% | 结果评分、返工率 |
| 响应速度 | 15% | 平均响应时间 |
| 在线稳定性 | 15% | 在线时长 / 总时长 |
| 历史信誉 | 15% | 其他 Agent 的评价 |
#### 3.4.2 信任分计算
```
trust_score = Σ(维度_i × 权重_i)
范围:0 - 100
等级:
90-100 S 级 高度可信
75-89 A 级 可信
60-74 B 级 一般可信
40-59 C 级 谨慎使用
0-39 D 级 不可信
```
#### 3.4.3 信任传递
```
A 信任 B(评分 90)
B 信任 C(评分 85)
A 对 C 的初始信任 = 90 × 85% × 衰减因子 = 约 68
```
**注意**:信任传递有衰减,防止信任污染。
---
### 3.5 区块链适用性决策树
```
是否需要跨组织协作?
├── 否 → 用 PKI 或公钥身份(简单高效)
└── 是 → 是否有共同信任的中心机构?
├── 是 → 用 PKI 体系(成熟可靠)
└── 否 → 是否需要不可篡改的审计?
├── 是 → 考虑联盟链 DID
└── 否 → 公钥身份 + 声誉系统可能够用
```
---
## 4. 方案三:协议缺失内容补全
### 4.1 第一优先级补全(核心基础设施)
#### 4.1.1 Agent 发现协议(ADS - Agent Discovery Service)
**新增消息类型:**
| 消息类型 | 方向 | 说明 |
|---------|------|------|
| `dir:register` | Agent → Directory | 注册 Agent |
| `dir:unregister` | Agent → Directory | 注销 Agent |
| `dir:heartbeat` | Agent → Directory | 心跳 |
| `dir:update_capabilities` | Agent → Directory | 更新能力 |
| `dir:query_by_id` | Agent → Directory | 按 ID 查询 |
| `dir:query_by_capability` | Agent → Directory | 按能力查询 |
| `dir:list` | Agent → Directory | 列表查询 |
| `dir:query_result` | Directory → Agent | 查询结果 |
**新增数据结构:** `AgentDirectoryEntry`, `CapabilityIndex`, `QueryFilter`
#### 4.1.2 身份认证规范
**新增消息类型:**
| 消息类型 | 说明 |
|---------|------|
| `auth:challenge` | 身份挑战 |
| `auth:response` | 挑战响应 |
| `auth:cert_exchange` | 证书交换 |
**新增内容:**
- 标准签名算法列表(Ed25519, ECDSA P-256, RSA 2048)
- 消息签名格式规范
- 防重放攻击机制
- 身份验证流程规范
#### 4.1.3 心跳与存活检测
**新增消息类型:**
| 消息类型 | 说明 |
|---------|------|
| `net:ping` | 存活探测 |
| `net:pong` | 存活响应 |
| `net:offline_notify` | 下线通知 |
**规范内容:**
- 心跳间隔(默认 30 秒)
- 超时判定(连续 3 次未收到 → 离线)
- 离线通知机制
#### 4.1.4 统一错误码体系
**错误分类:**
| 错误码前缀 | 类别 | 示例 |
|-----------|------|------|
| `msg_` | 消息层 | msg_invalid_format, msg_signature_invalid |
| `auth_` | 认证层 | auth_failed, auth_expired, auth_forbidden |
| `task_` | 任务层 | task_not_found, task_timeout, task_cancelled |
| `cap_` | 能力层 | cap_lacking, cap_busy, cap_not_supported |
| `group_` | 群组层 | group_not_found, group_full, group_permission_denied |
| `dir_` | 目录层 | dir_agent_not_found, dir_query_failed |
| `net_` | 网络层 | net_timeout, net_offline, net_unreachable |
| `sys_` | 系统层 | sys_internal_error, sys_not_implemented |
---
### 4.2 第二优先级补全(重要功能)
#### 4.2.1 能力匹配与任务路由
**功能:**
- 智能选择最合适的 Agent 执行任务
- 支持负载均衡(按并发数、响应速度、信任分)
- 支持任务失败自动重试/转发
**新增消息类型:**
- `router:submit_task` - 提交给路由器自动选择执行者
- `router:forward` - 任务转发
#### 4.2.2 全网广播协议
**新增消息类型:**
| 消息类型 | 说明 |
|---------|------|
| `global:broadcast` | 全网广播 |
| `system:broadcast` | 系统内广播 |
| `cap:broadcast` | 能力组广播 |
**规范内容:**
- 广播范围定义
- TTL 与去重机制
- 广播风暴防护
#### 4.2.3 版本协商机制
**功能:**
- Agent 之间协商使用的协议版本
- 向后兼容处理
- 能力降级策略
**新增消息类型:**
- `meta:version_query` - 查询支持的版本
- `meta:version_negotiate` - 版本协商
#### 4.2.4 幂等性保证
**规范内容:**
- 幂等键(Idempotency Key)机制
- 重复消息处理规范
- 任务级幂等 vs 消息级幂等
---
### 4.3 第三优先级补全(增强功能)
#### 4.3.1 流式输出协议
**功能:**
- 长任务实时进度推送
- 生成式 AI 的 token 流式输出
- WebSocket/SSE 传输支持
#### 4.3.2 批量任务协议
**功能:**
- 一次性提交多个同类型任务
- 批量状态查询
- 批量结果收集
#### 4.3.3 支付与激励机制
**功能:**
- 任务定价与结算
- Token 或积分系统
- 智能合约自动结算(区块链场景)
#### 4.3.4 群组发现协议
**功能:**
- 按主题/任务类型查找群组
- 推荐加入合适的群组
- 群组热度/活跃度排名
---
### 4.4 安全相关补全
#### 4.4.1 端到端加密
**规范内容:**
- 密钥协商(ECDH)
- 对称加密载荷(AES-GCM)
- 前向保密(PFS)
#### 4.4.2 权限委托
**功能:**
- Agent A 委托 Agent B 代表自己执行任务
- 委托令牌(Delegation Token)
- 委托范围与时效控制
#### 4.4.3 审计日志标准
**规范内容:**
- 统一审计日志格式
- 关键操作必须记录
- 日志完整性保护(哈希链)
---
## 5. 分阶段实施路线图
### 5.1 Phase 0:基础版(MVP) - 预计 2 周
**目标**:协议可用,能跑通基本流程
**内容:**
- [x] A2A 点对点任务协议(已完成)
- [x] A2N 群组协作协议(已完成)
- [x] P2A 外部调用协议(已完成)
- [x] 三套协议的 SDK 实现(已完成)
---
### 5.2 Phase 1:发现与身份 - 预计 4 周
**目标**:Agent 能互相找到对方,能验证对方身份
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| Agent 目录服务协议 | 集中式 ADS | P0 |
| 公钥身份规范 | Ed25519 签名验证 | P0 |
| 心跳与存活检测 | 30 秒心跳,3 次超时判定离线 | P0 |
| 统一错误码体系 | 8 大类错误码 | P0 |
| 防重放攻击 | message_id + timestamp | P0 |
**输出物:**
- 新协议文档:`06-agent-discovery.md`
- 新协议文档:`07-authentication.md`
- 代码:`discovery/` 包(目录服务客户端)
- 代码:`auth/` 包(签名验证工具)
- 更新 types 包:新增消息类型和错误码
---
### 5.3 Phase 2:路由与广播 - 预计 4 周
**目标**:支持智能任务路由和全网广播
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| 任务路由协议 | 按能力 + 负载 + 信任分选择执行者 | P1 |
| 全网广播协议 | 基于目录服务的广播转发 | P1 |
| 版本协商机制 | 协议版本兼容 | P1 |
| 幂等性保证 | 幂等键 + 去重 | P1 |
**输出物:**
- 新协议文档:`08-routing-and-broadcast.md`
- 代码:`routing/` 包增强(增加能力路由)
- 更新目录服务:支持广播转发
---
### 5.4 Phase 3:信任与激励 - 预计 6 周
**目标**:建立信任体系,支持规模化协作
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| 信任评分体系 | 5 维度信任分计算 | P2 |
| 流式输出协议 | WebSocket/SSE 流式进度 | P2 |
| 批量任务协议 | 批量提交/查询/收集 | P2 |
| 群组发现协议 | 群组搜索与推荐 | P2 |
| SLA 协议 | 服务等级承诺 | P3 |
**输出物:**
- 新协议文档:`09-trust-and-reputation.md`
- 代码:`trust/` 包(信任评分引擎)
---
### 5.5 Phase 4:高级特性 - 预计 8 周+
**目标**:企业级、生产级完整功能
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| 端到端加密 | ECDH + AES-GCM | P3 |
| 权限委托 | 委托令牌机制 | P3 |
| 支付与激励 | Token/积分系统 | P3 |
| 区块链 DID 集成 | 可选模块 | P4 |
| 审计日志标准 | 统一审计格式 | P3 |
| Gossip 协议 | 分布式发现(可选) | P4 |
---
## 6. 技术选型建议
### 6.1 目录服务存储选型
| 方案 | 适用场景 | 优点 | 缺点 |
|------|---------|------|------|
| **Redis** | 中小规模 | 高性能,支持 TTL | 数据量受限 |
| **PostgreSQL** | 中大规模 | 复杂查询,持久化 | 需运维 |
| **Elasticsearch** | 大规模 + 全文检索 | 强大的搜索能力 | 运维复杂 |
**推荐**:先用 Redis(简单快),规模大了再迁 PostgreSQL。
---
### 6.2 签名算法选型
| 算法 | 安全性 | 性能 | 密钥长度 | 推荐度 |
|------|--------|------|---------|--------|
| Ed25519 | 高 | 快 | 256 位 | ⭐⭐⭐⭐⭐ 首选 |
| ECDSA P-256 | 高 | 中 | 256 位 | ⭐⭐⭐⭐ 兼容 |
| RSA 2048 | 中 | 慢 | 2048 位 | ⭐⭐ 仅兼容 |
**推荐**:默认 Ed25519,需要兼容旧系统时支持 ECDSA。
---
### 6.3 区块链选型(如果需要)
| 场景 | 推荐方案 | 说明 |
|------|---------|------|
| 企业内部 | **私有链(Hyperledger Fabric)** | 可控,高性能,无 Gas 费 |
| 行业联盟 | **联盟链(FISCO BCOS / ChainMaker)** | 多机构共建,合规 |
| 开放生态 | **公链(以太坊 / Solana)** | 完全开放,需 Gas 费 |
| 简单原型 | **模拟链(数据库模拟)** | 先验证逻辑,再上真链 |
**推荐**:90% 的场景用不到真区块链,先用数据库模拟验证业务逻辑。
---
## 7. 总结与建议
### 7.1 核心建议
1. **先集中后分布**:先用集中式目录服务,简单高效;规模大了再考虑分布式
2. **先公钥后区块链**:先用公钥身份解决 80% 的问题,真的需要了再上链
3. **先 MVP 后完善**:先跑通核心流程,再逐步加高级功能
4. **标准接口可替换**:目录服务、身份系统都做成可插拔的,方便未来升级
### 7.2 下一步行动
| 序号 | 行动 | 负责人 | 产出 |
|------|------|--------|------|
| 1 | 评审本方案,确认 Phase 1 范围 | 架构组 | 确认后的方案 |
| 2 | 设计目录服务 API 详细规范 | 协议组 | `06-agent-discovery.md` |
| 3 | 设计身份认证详细规范 | 协议组 | `07-authentication.md` |
| 4 | 实现目录服务原型 | 开发组 | 可运行的 ADS 服务 |
| 5 | 更新 SDK 支持发现和认证 | 开发组 | 新版 SDK |
---
## 附录:相关文档索引
| 文档 | 说明 |
|------|------|
| [01-a2a-raw-protocol.md](./01-a2a-raw-protocol.md) | A2A 原始协议(纲领) |
| [02-a2a-protocol.md](./02-a2a-protocol.md) | A2A 协议(P2P 任务) |
| [03-a2n-protocol.md](./03-a2n-protocol.md) | A2N 协议(群组协作) |
| [04-p2a-protocol.md](./04-p2a-protocol.md) | P2A 协议(外部调用) |
| [05-sdk-implementation.md](./05-sdk-implementation.md) | SDK 实现说明 |
| **本文档** | **解决方案白皮书** |

414
examples/group_demo.go Normal file
View File

@@ -0,0 +1,414 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"synapse/sdk/group"
"synapse/types"
)
// =============================================================================
// Group 群组测试 - 三个 Agent 之间的群组协作
// =============================================================================
func main() {
fmt.Println("=== 灵隙(Synapse)Group 群组测试 ===")
fmt.Println("启动三个 Agent,进行群组协作验证...")
fmt.Println()
// 创建群组客户端(简化版,直接在主程序模拟)
groupClient := &simpleGroupClient{
groups: make(map[string]*group.GroupState),
}
// 启动 Agent 1 (Coordinator) - 监听 9091 端口
go runCoordinatorAgent(groupClient)
// 等待 Agent 1 启动
time.Sleep(1 * time.Second)
// 启动 Agent 2 (Worker1) - 监听 9092 端口
go runWorkerAgent(groupClient, "worker-1", 9092)
// 等待 Agent 2 启动
time.Sleep(1 * time.Second)
// 启动 Agent 3 (Worker2) - 监听 9093 端口
go runWorkerAgent(groupClient, "worker-2", 9093)
// 保持主程序运行
fmt.Println("\n=== Agent 已启动,等待群组测试 ===")
fmt.Println("Agent 1 (Coordinator): http://localhost:9091")
fmt.Println("Agent 2 (Worker-1): http://localhost:9092")
fmt.Println("Agent 3 (Worker-2): http://localhost:9093")
fmt.Println("\n按 Ctrl+C 停止...")
// 自动创建群组并开始测试
go func() {
time.Sleep(2 * time.Second)
createGroup()
}()
select {}
}
// 简化的群组客户端
type simpleGroupClient struct {
groups map[string]*group.GroupState
members map[string][]string // groupID -> agentIDs
}
// 创建群组并开始测试
func createGroup() {
log.Printf("\n=== [主程序] 创建群组 ===")
resp, err := http.Post(
"http://localhost:9091/api/group/create",
"application/json",
jsonBody(map[string]any{}),
)
if err != nil {
log.Printf("[主程序] 创建群组失败: %v", err)
return
}
defer resp.Body.Close()
var result map[string]any
json.NewDecoder(resp.Body).Decode(&result)
log.Printf("[主程序] 创建群组响应: %v", result)
// 等待任务完成
time.Sleep(8 * time.Second)
}
type agentInfo struct {
ID string
Name string
Port int
Handler interface{}
}
// Coordinator Agent(协调者)
func runCoordinatorAgent(client *simpleGroupClient) {
mux := http.NewServeMux()
// 健康检查
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write([]byte("OK - Coordinator"))
})
// 群组管理 API
mux.HandleFunc("/api/group/create", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", 405)
return
}
groupID := fmt.Sprintf("group-%d", time.Now().UnixNano())
group := &group.GroupState{
GroupID: groupID,
Name: "视频制作组",
Topology: types.GroupTopologyStar,
Status: types.GroupStatusActive,
Members: []types.GroupMember{
{
AgentID: "coordinator",
AgentName: "协调者",
Role: "coordinator",
JoinedAt: types.NowTimestamp(),
Status: types.GroupMemberActive,
Capabilities: []string{"coordination"},
LoadFactor: 0,
},
{
AgentID: "worker-1",
AgentName: "工作者1",
Role: "worker",
JoinedAt: types.NowTimestamp(),
Status: types.GroupMemberActive,
Capabilities: []string{"video_generation"},
LoadFactor: 0,
},
{
AgentID: "worker-2",
AgentName: "工作者2",
Role: "worker",
JoinedAt: types.NowTimestamp(),
Status: types.GroupMemberActive,
Capabilities: []string{"audio_generation"},
LoadFactor: 0,
},
},
Role: "coordinator",
}
client.groups[groupID] = group
log.Printf("\n=== [Coordinator] 群组创建成功 ===")
log.Printf("Group ID: %s", groupID)
log.Printf("Members: coordinator, worker-1, worker-2")
// 模拟任务拆分
go func() {
time.Sleep(1 * time.Second)
simulateTaskSplit(groupID, client)
}()
// 模拟投票
go func() {
time.Sleep(3 * time.Second)
simulateVote(groupID)
}()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"success": true,
"group_id": groupID,
"message": "群组创建成功",
})
})
// 获取群组信息
mux.HandleFunc("/api/group/", func(w http.ResponseWriter, r *http.Request) {
groupID := r.URL.Path[len("/api/group/"):]
group, ok := client.groups[groupID]
if !ok {
http.Error(w, "group not found", 404)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"success": true,
"group": group,
})
})
log.Println("[Coordinator] 协调者 Agent 启动,监听端口 9091")
if err := http.ListenAndServe(":9091", mux); err != nil {
log.Fatalf("[Coordinator] Server error: %v", err)
}
}
// Worker Agent(工作者)
func runWorkerAgent(client *simpleGroupClient, workerID string, port int) {
mux := http.NewServeMux()
// 健康检查
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write([]byte(fmt.Sprintf("OK - %s", workerID)))
})
// 加入群组
mux.HandleFunc("/api/group/join", func(w http.ResponseWriter, r *http.Request) {
var req struct {
GroupID string `json:"group_id"`
}
json.NewDecoder(r.Body).Decode(&req)
log.Printf("\n=== [%s] 申请加入群组 ===", workerID)
log.Printf("Group ID: %s", req.GroupID)
// 模拟处理加入请求
time.Sleep(500 * time.Millisecond)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"success": true,
"message": fmt.Sprintf("%s 已加入群组", workerID),
"role": "worker",
"group_id": req.GroupID,
})
})
// 接收任务
mux.HandleFunc("/api/task/subtask", func(w http.ResponseWriter, r *http.Request) {
var req struct {
GroupID string `json:"group_id"`
Subtask string `json:"subtask"`
TaskType string `json:"task_type"`
Payload map[string]interface{} `json:"payload"`
}
json.NewDecoder(r.Body).Decode(&req)
log.Printf("\n=== [%s] 收到子任务 ===", workerID)
log.Printf("Group ID: %s", req.GroupID)
log.Printf("Subtask: %s", req.Subtask)
log.Printf("Task Type: %s", req.TaskType)
log.Printf("Payload: %v", req.Payload)
// 模拟执行任务
go func() {
time.Sleep(1 * time.Second)
log.Printf("[%s] 子任务执行完成: %s", workerID, req.Subtask)
// 发送结果(这里简化处理,只是打印日志)
log.Printf("[%s] 发送子任务结果: %s -> 完成了 %s 的工作", workerID, req.Subtask, req.TaskType)
}()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"success": true,
"message": fmt.Sprintf("%s 开始执行任务", workerID),
"subtask": req.Subtask,
"progress": 0,
})
})
// 接收投票
mux.HandleFunc("/api/vote", func(w http.ResponseWriter, r *http.Request) {
var req struct {
GroupID string `json:"group_id"`
VoteID string `json:"vote_id"`
Question string `json:"question"`
Options []string `json:"options"`
}
json.NewDecoder(r.Body).Decode(&req)
log.Printf("\n=== [%s] 收到投票请求 ===", workerID)
log.Printf("Vote ID: %s", req.VoteID)
log.Printf("Question: %s", req.Question)
log.Printf("Options: %v", req.Options)
// 模拟投票决策(选择第一个选项)
selectedOption := req.Options[0]
log.Printf("[%s] 投票: %s", workerID, selectedOption)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"success": true,
"vote_id": req.VoteID,
"selected_option": selectedOption,
"reason": "看起来更合理",
})
})
log.Printf("[%s] 工作者 Agent 启动,监听端口 %d", workerID, port)
if err := http.ListenAndServe(fmt.Sprintf(":%d", port), mux); err != nil {
log.Fatalf("[%s] Server error: %v", workerID, err)
}
}
// 模拟任务拆分
func simulateTaskSplit(groupID string, client *simpleGroupClient) {
log.Printf("\n=== [Coordinator] 执行任务拆分 ===")
log.Printf("Original Task: 制作视频")
subtasks := []struct {
ID string
Description string
AssignedTo string
}{
{"sub-1", "生成视频脚本", "worker-1"},
{"sub-2", "生成配音", "worker-2"},
}
for _, st := range subtasks {
log.Printf("拆分任务: %s -> %s", st.ID, st.AssignedTo)
// 发送任务给 worker
port := 9092
if st.AssignedTo == "worker-2" {
port = 9093
}
resp, err := http.Post(
fmt.Sprintf("http://localhost:%d/api/task/subtask", port),
"application/json",
jsonBody(map[string]any{
"group_id": groupID,
"subtask": st.ID,
"task_type": "video_generation",
"payload": map[string]any{"description": st.Description},
}),
)
if err != nil {
log.Printf("[Coordinator] 发送任务失败: %v", err)
continue
}
resp.Body.Close()
time.Sleep(500 * time.Millisecond)
}
}
// 模拟投票
func simulateVote(groupID string) {
log.Printf("\n=== [Coordinator] 发起投票 ===")
voteQuestion := "选择视频风格?"
voteOptions := []string{"现代风格", "复古风格", "卡通风格"}
log.Printf("Question: %s", voteQuestion)
log.Printf("Options: %v", voteOptions)
// 收集投票结果
votes := make(map[string]int)
for _, opt := range voteOptions {
votes[opt] = 0
}
// 向两个 worker 发送投票
for _, port := range []int{9092, 9093} {
resp, err := http.Post(
fmt.Sprintf("http://localhost:%d/api/vote", port),
"application/json",
jsonBody(map[string]any{
"group_id": groupID,
"vote_id": fmt.Sprintf("vote-%d", time.Now().UnixNano()),
"question": voteQuestion,
"options": voteOptions,
}),
)
if err != nil {
log.Printf("[Coordinator] 投票请求失败: %v", err)
continue
}
var result map[string]any
json.NewDecoder(resp.Body).Decode(&result)
resp.Body.Close()
if selected, ok := result["selected_option"].(string); ok {
votes[selected]++
}
}
// 统计结果
log.Printf("\n=== [Coordinator] 投票结果 ===")
for opt, count := range votes {
log.Printf("%s: %d 票", opt, count)
}
// 确定获胜者
var winner string
maxVotes := 0
for opt, count := range votes {
if count > maxVotes {
maxVotes = count
winner = opt
}
}
log.Printf("\n最终决定: %s (获得 %d 票)", winner, maxVotes)
// 模拟结果合并
go func() {
time.Sleep(1 * time.Second)
log.Printf("\n=== [Coordinator] 合并任务结果 ===")
log.Printf("所有子任务已完成")
log.Printf("最终结果: 视频制作完成,采用 %s", winner)
}()
}
func jsonBody(v map[string]any) *bytes.Buffer {
data, _ := json.Marshal(v)
return bytes.NewBuffer(data)
}

368
examples/p2p_demo.go Normal file
View File

@@ -0,0 +1,368 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"synapse/sdk/p2p"
"synapse/types"
)
// =============================================================================
// P2P 通讯测试 - 两个 Agent 之间的点对点通信
// =============================================================================
func main() {
fmt.Println("=== 灵隙(Synapse)P2P 通讯测试 ===")
fmt.Println("启动两个 Agent,进行点对点通讯验证...")
fmt.Println()
// 启动 Agent 1 (Writer Agent) - 监听 8081 端口
go runAgent1()
// 等待 Agent 1 启动
time.Sleep(1 * time.Second)
// 启动 Agent 2 (Caller) - 监听 8082 端口
go runAgent2()
// 保持主程序运行
fmt.Println("\n=== Agent 已启动,等待通讯测试 ===")
fmt.Println("Agent 1 (Writer): http://localhost:8081")
fmt.Println("Agent 2 (Caller): http://localhost:8082")
fmt.Println("\n按 Ctrl+C 停止...")
select {}
}
// Agent 1 - Writer Agent(任务执行方)
func runAgent1() {
// 创建 P2P 客户端
client, err := p2p.NewClient(&p2p.ClientConfig{
AgentEndpoint: types.AgentEndpoint{
AgentID: "writer-agent",
AgentName: "作家 Agent",
SystemID: "test-system",
Endpoint: "http://localhost:8081/callback",
},
AuthSecret: "secret-123",
CallbackPort: 8081,
})
if err != nil {
log.Fatalf("Failed to create Agent 1: %v", err)
}
// 设置消息处理器
client.SetMessageHandler(&writerHandler{})
// 启动 HTTP 服务处理回调
mux := http.NewServeMux()
mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
var msg types.UnifiedSynapseMessage
if err := json.NewDecoder(r.Body).Decode(&msg); err != nil {
http.Error(w, "bad request", 400)
return
}
resp, err := client.HandleMessage(r.Context(), &msg)
if err != nil {
log.Printf("[Agent1] Error handling message: %v", err)
http.Error(w, "internal error", 500)
return
}
if resp != nil {
respBytes, _ := json.Marshal(resp)
w.Header().Set("Content-Type", "application/json")
w.Write(respBytes)
}
})
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write([]byte("OK"))
})
log.Println("[Agent1] Writer Agent 启动,监听端口 8081")
if err := http.ListenAndServe(":8081", mux); err != nil {
log.Fatalf("[Agent1] Server error: %v", err)
}
}
// Agent 2 - Caller Agent(任务请求方)
func runAgent2() {
// 创建 P2P 客户端
client, err := p2p.NewClient(&p2p.ClientConfig{
AgentEndpoint: types.AgentEndpoint{
AgentID: "caller-agent",
AgentName: "调用方 Agent",
SystemID: "test-system",
Endpoint: "http://localhost:8082/callback",
},
AuthSecret: "secret-123",
CallbackPort: 8082,
})
if err != nil {
log.Fatalf("Failed to create Agent 2: %v", err)
}
// 设置消息处理器
client.SetMessageHandler(&callerHandler{})
// 启动 HTTP 服务
mux := http.NewServeMux()
mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
var msg types.UnifiedSynapseMessage
if err := json.NewDecoder(r.Body).Decode(&msg); err != nil {
http.Error(w, "bad request", 400)
return
}
resp, err := client.HandleMessage(r.Context(), &msg)
if err != nil {
log.Printf("[Agent2] Error handling message: %v", err)
http.Error(w, "internal error", 500)
return
}
if resp != nil {
respBytes, _ := json.Marshal(resp)
w.Header().Set("Content-Type", "application/json")
w.Write(respBytes)
}
})
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write([]byte("OK"))
})
// 启动后发送测试任务
go func() {
time.Sleep(2 * time.Second)
sendTestTask(client)
}()
log.Println("[Agent2] Caller Agent 启动,监听端口 8082")
if err := http.ListenAndServe(":8082", mux); err != nil {
log.Fatalf("[Agent2] Server error: %v", err)
}
}
// Writer Agent 的消息处理器
type writerHandler struct{}
func (h *writerHandler) HandleTaskRequest(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskRequestPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return nil, err
}
log.Printf("\n=== [Agent1] 收到任务请求 ===")
log.Printf("Task ID: %s", payload.Task.ID)
log.Printf("Task Type: %s", payload.Task.Type)
log.Printf("Payload: %v", payload.Task.Payload)
// 发送任务确认
confirmPayload, _ := json.Marshal(types.TaskConfirmPayload{
TaskID: payload.Task.ID,
Confirmed: true,
EstimatedDuration: 5,
ExpectedCompletion: types.Timestamp(time.Now().Add(5 * time.Second).Format(time.RFC3339)),
CapabilitiesUsed: []string{"text_generation"},
})
confirmMsg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskConfirm),
Sender: *msg.Receiver,
Receiver: &msg.Sender,
Payload: confirmPayload,
Timestamp: types.NowTimestamp(),
}
log.Printf("[Agent1] 发送任务确认: %s\n", payload.Task.ID)
// 模拟处理,然后发送完成消息
go func() {
time.Sleep(1 * time.Second)
// 发送进度更新
progressPayload, _ := json.Marshal(types.TaskStatusUpdatePayload{
TaskID: payload.Task.ID,
Status: "executing",
Progress: 50,
Message: "正在生成文本...",
})
progressMsg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskStatusUpdate),
Sender: *msg.Receiver,
Receiver: &msg.Sender,
Payload: progressPayload,
Timestamp: types.NowTimestamp(),
}
log.Printf("[Agent1] 发送进度更新: 50%%")
_ = sendMessage(progressMsg)
time.Sleep(1 * time.Second)
// 发送完成消息
result := fmt.Sprintf("生成了一个关于「%v」的故事", payload.Task.Payload["topic"])
completePayload, _ := json.Marshal(types.TaskCompletePayload{
TaskID: payload.Task.ID,
Result: struct {
OutputType string `json:"output_type"`
Output any `json:"output"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}{
OutputType: "text",
Output: result,
Metadata: map[string]interface{}{"tokens": len(result)},
},
Summary: "任务已完成",
})
completeMsg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskComplete),
Sender: *msg.Receiver,
Receiver: &msg.Sender,
Payload: completePayload,
Timestamp: types.NowTimestamp(),
}
log.Printf("[Agent1] 发送任务完成: %s\n", result)
_ = sendMessage(completeMsg)
}()
return confirmMsg, nil
}
func (h *writerHandler) HandleTaskStatusUpdate(ctx context.Context, msg *types.UnifiedSynapseMessage) error {
log.Printf("[Agent1] 收到状态更新: %s", msg.Type)
return nil
}
func (h *writerHandler) HandleTaskComplete(ctx context.Context, msg *types.UnifiedSynapseMessage) error {
var payload types.TaskCompletePayload
json.Unmarshal(msg.Payload, &payload)
log.Printf("[Agent1] 任务完成: %s", payload.Summary)
return nil
}
func (h *writerHandler) HandleTaskFailed(ctx context.Context, msg *types.UnifiedSynapseMessage) error {
var payload types.TaskFailedPayload
json.Unmarshal(msg.Payload, &payload)
log.Printf("[Agent1] 任务失败: %s", payload.Error.Message)
return nil
}
func (h *writerHandler) HandleNeedHuman(ctx context.Context, msg *types.UnifiedSynapseMessage) error {
var payload types.TaskNeedHumanPayload
json.Unmarshal(msg.Payload, &payload)
log.Printf("[Agent1] 需要人类介入: %s", payload.Reason.Message)
return nil
}
// Caller Agent 的消息处理器
type callerHandler struct{}
func (h *callerHandler) HandleTaskRequest(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
log.Printf("[Agent2] 收到任务请求")
return nil, nil
}
func (h *callerHandler) HandleTaskStatusUpdate(ctx context.Context, msg *types.UnifiedSynapseMessage) error {
var payload types.TaskStatusUpdatePayload
json.Unmarshal(msg.Payload, &payload)
log.Printf("\n=== [Agent2] 收到状态更新 ===")
log.Printf("Task ID: %s", payload.TaskID)
log.Printf("Status: %s", payload.Status)
log.Printf("Progress: %d%%", payload.Progress)
log.Printf("Message: %s\n", payload.Message)
return nil
}
func (h *callerHandler) HandleTaskComplete(ctx context.Context, msg *types.UnifiedSynapseMessage) error {
var payload types.TaskCompletePayload
json.Unmarshal(msg.Payload, &payload)
log.Printf("\n=== [Agent2] 收到任务完成 ===")
log.Printf("Task ID: %s", payload.TaskID)
log.Printf("Result: %v", payload.Result.Output)
log.Printf("Summary: %s\n", payload.Summary)
return nil
}
func (h *callerHandler) HandleTaskFailed(ctx context.Context, msg *types.UnifiedSynapseMessage) error {
var payload types.TaskFailedPayload
json.Unmarshal(msg.Payload, &payload)
log.Printf("\n=== [Agent2] 收到任务失败 ===")
log.Printf("Task ID: %s", payload.TaskID)
log.Printf("Error: %s\n", payload.Error.Message)
return nil
}
func (h *callerHandler) HandleNeedHuman(ctx context.Context, msg *types.UnifiedSynapseMessage) error {
var payload types.TaskNeedHumanPayload
json.Unmarshal(msg.Payload, &payload)
log.Printf("\n=== [Agent2] 收到人工介入请求 ===")
log.Printf("Task ID: %s", payload.TaskID)
log.Printf("Reason: %s\n", payload.Reason.Message)
return nil
}
// 发送测试任务
func sendTestTask(client *p2p.Client) {
log.Println("\n=== [Agent2] 发送测试任务 ===")
target := types.AgentEndpoint{
AgentID: "writer-agent",
AgentName: "作家 Agent",
SystemID: "test-system",
Endpoint: "http://localhost:8081/callback",
}
taskID, err := client.SendTaskRequest(context.Background(), target, "text_generation", map[string]any{
"topic": "春天",
"style": "古诗",
"length": "short",
})
if err != nil {
log.Printf("[Agent2] 发送任务失败: %v", err)
return
}
log.Printf("[Agent2] 任务已发送,Task ID: %s", taskID)
// 等待任务完成
state, err := client.WaitForTask(context.Background(), taskID, 30*time.Second)
if err != nil {
log.Printf("[Agent2] 等待任务失败: %v", err)
return
}
log.Printf("\n=== [测试结果] ===")
log.Printf("最终状态: %s", state.Status)
log.Printf("结果: %v", state.Result)
}
func sendMessage(msg *types.UnifiedSynapseMessage) error {
body, _ := json.Marshal(msg)
resp, err := http.Post(msg.Receiver.Endpoint, "application/json", bytes.NewReader(body))
if err != nil {
return err
}
resp.Body.Close()
return nil
}

533
externalapi/handler.go Normal file
View File

@@ -0,0 +1,533 @@
package externalapi
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"strconv"
"strings"
"time"
"synapse/types"
)
// =============================================================================
// External API Layer - 非Agent调用者API
// =============================================================================
//
// 这一层为非Agent调用者(如普通服务、前端、第三方应用)提供了更简单的RESTful API,
// 隐藏了Synapse协议的复杂性。
//
// 与Synapse协议的区别:
// - 使用API Key认证,而非HMAC签名
// - 简化的请求/响应格式
// - 支持回调和轮询两种模式
// - 无需注册为Agent即可调用
// =============================================================================
// =============================================================================
// API Handler
// =============================================================================
// ExternalAPIHandler 处理外部API请求
type ExternalAPIHandler struct {
// TaskExecutor 任务执行器
TaskExecutor TaskExecutor
// APIKey API密钥(如果为空则不验证)
APIKey string
// MaxFileSize 最大文件大小
MaxFileSize int64
}
// TaskExecutor 任务执行器接口
type TaskExecutor interface {
// SubmitTask 提交任务
SubmitTask(ctx context.Context, taskType string, payload map[string]any, externalTaskID string, priority string, callbackURL string, callbackToken string) (string, string, error)
// GetTaskStatus 获取任务状态
GetTaskStatus(ctx context.Context, taskID string) (*types.TaskInfo, error)
// ListTasks 列出任务
ListTasks(ctx context.Context, status string, limit, offset int) ([]types.TaskInfo, int, error)
// CancelTask 取消任务
CancelTask(ctx context.Context, taskID string) error
}
// NewExternalAPIHandler 创建外部API处理器
func NewExternalAPIHandler(executor TaskExecutor, apiKey string) *ExternalAPIHandler {
return &ExternalAPIHandler{
TaskExecutor: executor,
APIKey: apiKey,
MaxFileSize: 100 << 20, // 100MB
}
}
// RegisterRoutes 注册路由
func (h *ExternalAPIHandler) RegisterRoutes(mux *http.ServeMux, prefix string) {
// 任务相关
mux.HandleFunc(prefix+"/task", h.withAuth(h.handleTaskCreate))
mux.HandleFunc(prefix+"/task/", h.withAuth(h.handleTaskDetail))
mux.HandleFunc(prefix+"/tasks", h.withAuth(h.handleTaskList))
// 多部分表单提交
mux.HandleFunc(prefix+"/task/multipart", h.withAuth(h.handleTaskMultipart))
}
// =============================================================================
// 认证中间件
// =============================================================================
func (h *ExternalAPIHandler) withAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 如果没有配置API Key,跳过验证
if h.APIKey != "" {
key := r.Header.Get("X-API-Key")
if key == "" {
key = r.URL.Query().Get("api_key")
}
if key != h.APIKey {
h.writeError(w, http.StatusUnauthorized, "unauthorized", "invalid or missing API key")
return
}
}
// CORS
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-API-Key")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next(w, r)
}
}
// =============================================================================
// 任务创建
// =============================================================================
// ExternalTaskRequest 外部任务请求
type ExternalTaskRequest struct {
TaskType string `json:"task_type"`
Payload map[string]any `json:"payload"`
ExternalTaskID string `json:"external_task_id,omitempty"`
Priority string `json:"priority,omitempty"`
CallbackURL string `json:"callback_url,omitempty"`
CallbackToken string `json:"callback_token,omitempty"`
}
func (h *ExternalAPIHandler) handleTaskCreate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
h.writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "only POST is supported")
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
h.writeError(w, http.StatusBadRequest, "bad_request", "failed to read body")
return
}
var req ExternalTaskRequest
if err := json.Unmarshal(body, &req); err != nil {
h.writeError(w, http.StatusBadRequest, "bad_request", "invalid JSON format")
return
}
// 验证必填字段
if req.TaskType == "" {
h.writeError(w, http.StatusBadRequest, "bad_request", "task_type is required")
return
}
if req.Payload == nil {
h.writeError(w, http.StatusBadRequest, "bad_request", "payload is required")
return
}
// 默认优先级
if req.Priority == "" {
req.Priority = "normal"
}
// 提交任务
taskID, status, err := h.TaskExecutor.SubmitTask(
r.Context(),
req.TaskType,
req.Payload,
req.ExternalTaskID,
req.Priority,
req.CallbackURL,
req.CallbackToken,
)
if err != nil {
log.Printf("[ExternalAPI] 任务提交失败: %v", err)
h.writeError(w, http.StatusInternalServerError, "task_submission_failed", err.Error())
return
}
log.Printf("[ExternalAPI] 任务提交成功: type=%s, task_id=%s", req.TaskType, taskID)
h.writeJSON(w, http.StatusAccepted, map[string]any{
"success": true,
"task_id": taskID,
"status": status,
"message": "任务已接收,正在处理中",
})
}
// =============================================================================
// Multipart任务创建(带文件)
// =============================================================================
func (h *ExternalAPIHandler) handleTaskMultipart(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
h.writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "only POST is supported")
return
}
// 限制请求大小
r.Body = http.MaxBytesReader(w, r.Body, h.MaxFileSize)
if err := r.ParseMultipartForm(h.MaxFileSize); err != nil {
h.writeError(w, http.StatusBadRequest, "bad_request", "failed to parse multipart form: "+err.Error())
return
}
// 获取请求JSON
requestStr := r.FormValue("request")
if requestStr == "" {
h.writeError(w, http.StatusBadRequest, "bad_request", "missing 'request' field")
return
}
var req ExternalTaskRequest
if err := json.Unmarshal([]byte(requestStr), &req); err != nil {
h.writeError(w, http.StatusBadRequest, "bad_request", "invalid request JSON")
return
}
// 提取文件
files := make(map[string][]byte)
if r.MultipartForm != nil {
for fieldName, fileHeaders := range r.MultipartForm.File {
if fieldName == "request" {
continue
}
for _, fh := range fileHeaders {
file, err := fh.Open()
if err != nil {
continue
}
data, err := io.ReadAll(file)
file.Close()
if err != nil {
continue
}
files[fieldName] = data
}
}
}
// 将文件信息加入payload
if len(files) > 0 {
if req.Payload == nil {
req.Payload = make(map[string]any)
}
req.Payload["__files"] = files
}
// 提交任务
taskID, status, err := h.TaskExecutor.SubmitTask(
r.Context(),
req.TaskType,
req.Payload,
req.ExternalTaskID,
req.Priority,
req.CallbackURL,
req.CallbackToken,
)
if err != nil {
h.writeError(w, http.StatusInternalServerError, "task_submission_failed", err.Error())
return
}
h.writeJSON(w, http.StatusAccepted, map[string]any{
"success": true,
"task_id": taskID,
"status": status,
"message": "任务已接收,正在处理中",
"files_uploaded": len(files),
})
}
// =============================================================================
// 任务详情/状态
// =============================================================================
func (h *ExternalAPIHandler) handleTaskDetail(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/api/v1/synapse/task/")
parts := strings.SplitN(path, "/", 2)
taskID := parts[0]
if taskID == "" {
h.writeError(w, http.StatusNotFound, "not_found", "task_id is required")
return
}
// /task/{id}/status
if len(parts) == 2 {
switch parts[1] {
case "status":
h.handleTaskStatus(w, r, taskID)
return
case "cancel":
h.handleTaskCancel(w, r, taskID)
return
default:
h.writeError(w, http.StatusNotFound, "not_found", "unknown subpath")
return
}
}
// /task/{id} (GET)
if r.Method != http.MethodGet {
h.writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "only GET is supported")
return
}
task, err := h.TaskExecutor.GetTaskStatus(r.Context(), taskID)
if err != nil {
h.writeError(w, http.StatusNotFound, "task_not_found", err.Error())
return
}
h.writeJSON(w, http.StatusOK, map[string]any{
"success": true,
"task": task,
})
}
func (h *ExternalAPIHandler) handleTaskStatus(w http.ResponseWriter, r *http.Request, taskID string) {
if r.Method != http.MethodGet {
h.writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "only GET is supported")
return
}
task, err := h.TaskExecutor.GetTaskStatus(r.Context(), taskID)
if err != nil {
h.writeError(w, http.StatusNotFound, "task_not_found", err.Error())
return
}
h.writeJSON(w, http.StatusOK, map[string]any{
"success": true,
"task_id": task.ID,
"status": task.Status,
"progress": task.Progress,
"message": task.Message,
})
}
func (h *ExternalAPIHandler) handleTaskCancel(w http.ResponseWriter, r *http.Request, taskID string) {
if r.Method != http.MethodPost {
h.writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "only POST is supported")
return
}
if err := h.TaskExecutor.CancelTask(r.Context(), taskID); err != nil {
h.writeError(w, http.StatusInternalServerError, "cancel_failed", err.Error())
return
}
h.writeJSON(w, http.StatusOK, map[string]any{
"success": true,
"task_id": taskID,
"status": "cancelled",
"message": "任务已取消",
})
}
// =============================================================================
// 任务列表
// =============================================================================
func (h *ExternalAPIHandler) handleTaskList(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
h.writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "only GET is supported")
return
}
status := r.URL.Query().Get("status")
limitStr := r.URL.Query().Get("limit")
offsetStr := r.URL.Query().Get("offset")
limit := 20
offset := 0
if limitStr != "" {
if n, err := strconv.Atoi(limitStr); err == nil && n > 0 && n <= 100 {
limit = n
}
}
if offsetStr != "" {
if n, err := strconv.Atoi(offsetStr); err == nil && n >= 0 {
offset = n
}
}
tasks, total, err := h.TaskExecutor.ListTasks(r.Context(), status, limit, offset)
if err != nil {
h.writeError(w, http.StatusInternalServerError, "list_failed", err.Error())
return
}
hasMore := offset+limit < total
h.writeJSON(w, http.StatusOK, map[string]any{
"success": true,
"tasks": tasks,
"total": total,
"limit": limit,
"offset": offset,
"has_more": hasMore,
})
}
// =============================================================================
// 回调发送器
// =============================================================================
// CallbackSender 发送回调通知
type CallbackSender struct {
HTTPClient *http.Client
}
// NewCallbackSender 创建回调发送器
func NewCallbackSender() *CallbackSender {
return &CallbackSender{
HTTPClient: &http.Client{Timeout: 10 * time.Second},
}
}
// CallbackPayload 回调负载
type CallbackPayload struct {
MessageID string `json:"message_id"`
MessageType string `json:"message_type"` // task_status, task_complete, task_failed
TaskID string `json:"task_id"`
Status string `json:"status"`
Progress int `json:"progress"`
Result map[string]any `json:"result,omitempty"`
Error *CallbackError `json:"error,omitempty"`
Timestamp string `json:"timestamp"`
}
// CallbackError 回调错误
type CallbackError struct {
Code string `json:"code"`
Message string `json:"message"`
}
// SendCallback 发送回调
func (s *CallbackSender) SendCallback(ctx context.Context, callbackURL, callbackToken string, payload *CallbackPayload) error {
if callbackURL == "" {
return nil
}
body, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, "POST", callbackURL, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if callbackToken != "" {
req.Header.Set("X-Callback-Token", callbackToken)
}
resp, err := s.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("callback returned HTTP %d: %s", resp.StatusCode, string(respBody))
}
return nil
}
// =============================================================================
// 辅助函数
// =============================================================================
func (h *ExternalAPIHandler) writeJSON(w http.ResponseWriter, status int, data map[string]any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func (h *ExternalAPIHandler) writeError(w http.ResponseWriter, status int, code, message string) {
h.writeJSON(w, status, map[string]any{
"success": false,
"error": code,
"message": message,
})
}
// FileInfo 上传的文件信息
type FileInfo struct {
Fieldname string
Filename string
Size int64
Data []byte
Header multipart.FileHeader
}
// ExtractFiles 从multipart请求中提取文件
func ExtractFiles(r *http.Request) ([]FileInfo, error) {
if r.MultipartForm == nil {
if err := r.ParseMultipartForm(32 << 20); err != nil {
return nil, err
}
}
var files []FileInfo
for fieldName, fileHeaders := range r.MultipartForm.File {
for _, fh := range fileHeaders {
file, err := fh.Open()
if err != nil {
continue
}
data, err := io.ReadAll(file)
file.Close()
if err != nil {
continue
}
files = append(files, FileInfo{
Fieldname: fieldName,
Filename: fh.Filename,
Size: fh.Size,
Data: data,
Header: *fh,
})
}
}
return files, nil
}

16
go.mod Normal file
View File

@@ -0,0 +1,16 @@
module synapse
go 1.25.0
require (
github.com/golang/protobuf v1.5.4
google.golang.org/grpc v1.68.0
google.golang.org/protobuf v1.35.2
)
require (
golang.org/x/net v0.55.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/text v0.38.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
)

16
go.sum Normal file
View File

@@ -0,0 +1,16 @@
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=

794
group/protocol.go Normal file
View File

@@ -0,0 +1,794 @@
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
}

706
grpcapi/client.go Normal file
View File

@@ -0,0 +1,706 @@
package grpcapi
import (
"context"
"fmt"
"io"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
)
// =============================================================================
// gRPC Client SDK for Synapse Protocol
// =============================================================================
// GRPCClient is a high-level client for Synapse gRPC services
type GRPCClient struct {
conn *grpc.ClientConn
taskClient TaskServiceClient
messageClient A2AMessageServiceClient
capsClient CapabilitiesServiceClient
groupClient GroupServiceClient
healthClient HealthServiceClient
// Configuration
apiKey string
timeout time.Duration
}
// GRPCClientConfig client configuration
type GRPCClientConfig struct {
Address string
APIKey string
Timeout time.Duration
Insecure bool // For development only
}
// NewGRPCClient creates a new gRPC client
func NewGRPCClient(config GRPCClientConfig) (*GRPCClient, error) {
if config.Timeout == 0 {
config.Timeout = 30 * time.Second
}
var opts []grpc.DialOption
if config.Insecure {
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
// Add interceptor for API key
opts = append(opts, grpc.WithUnaryInterceptor(apiKeyInterceptor(config.APIKey)))
opts = append(opts, grpc.WithStreamInterceptor(apiKeyStreamInterceptor(config.APIKey)))
ctx, cancel := context.WithTimeout(context.Background(), config.Timeout)
defer cancel()
conn, err := grpc.DialContext(ctx, config.Address, opts...)
if err != nil {
return nil, fmt.Errorf("failed to connect: %w", err)
}
return &GRPCClient{
conn: conn,
taskClient: NewTaskServiceClient(conn),
messageClient: NewA2AMessageServiceClient(conn),
capsClient: NewCapabilitiesServiceClient(conn),
groupClient: NewGroupServiceClient(conn),
healthClient: NewHealthServiceClient(conn),
apiKey: config.APIKey,
timeout: config.Timeout,
}, nil
}
// Close closes the client connection
func (c *GRPCClient) Close() error {
return c.conn.Close()
}
// =============================================================================
// Interceptors
// =============================================================================
func apiKeyInterceptor(apiKey string) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
if apiKey != "" {
ctx = metadata.AppendToOutgoingContext(ctx, "x-api-key", apiKey)
}
return invoker(ctx, method, req, reply, cc, opts...)
}
}
func apiKeyStreamInterceptor(apiKey string) grpc.StreamClientInterceptor {
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
if apiKey != "" {
ctx = metadata.AppendToOutgoingContext(ctx, "x-api-key", apiKey)
}
return streamer(ctx, desc, cc, method, opts...)
}
}
// =============================================================================
// Task Operations
// =============================================================================
// SubmitTask submits a new task
func (c *GRPCClient) SubmitTask(ctx context.Context, taskType string, payload map[string]any, opts ...TaskOption) (*SubmitTaskResponse, error) {
req := &SubmitTaskRequest{
TaskType: taskType,
Priority: "normal",
}
// Apply options
for _, opt := range opts {
opt(req)
}
// Convert payload to Any
if payload != nil {
// In real implementation, use protojson.Marshal
req.Payload = &anypb.Any{
Value: []byte(fmt.Sprintf("%v", payload)),
}
}
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
return c.taskClient.SubmitTask(ctx, req)
}
// TaskOption configures task submission
type TaskOption func(*SubmitTaskRequest)
func WithExternalTaskID(id string) TaskOption {
return func(r *SubmitTaskRequest) {
r.ExternalTaskId = id
}
}
func WithPriority(priority string) TaskOption {
return func(r *SubmitTaskRequest) {
r.Priority = priority
}
}
func WithCallback(url, token string) TaskOption {
return func(r *SubmitTaskRequest) {
r.CallbackUrl = url
r.CallbackToken = token
}
}
func WithTransportMode(mode TransportMode) TaskOption {
return func(r *SubmitTaskRequest) {
r.PreferredTransport = mode
}
}
// GetTask gets task details
func (c *GRPCClient) GetTask(ctx context.Context, taskID string) (*TaskInfo, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.taskClient.GetTask(ctx, &GetTaskRequest{TaskId: taskID})
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("task query failed: %s", resp.ErrorMessage)
}
return resp.Task, nil
}
// GetTaskStatus gets simplified task status
func (c *GRPCClient) GetTaskStatus(ctx context.Context, taskID string) (*TaskInfo, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.taskClient.GetTaskStatus(ctx, &GetTaskRequest{TaskId: taskID})
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("status query failed: %s", resp.ErrorMessage)
}
return resp.Task, nil
}
// ListTasks lists tasks with filtering
func (c *GRPCClient) ListTasks(ctx context.Context, status string, limit, offset int) (*ListTasksResponse, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
return c.taskClient.ListTasks(ctx, &ListTasksRequest{
Status: status,
Limit: int32(limit),
Offset: int32(offset),
})
}
// CancelTask cancels a task
func (c *GRPCClient) CancelTask(ctx context.Context, taskID string) (*CancelTaskResponse, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
return c.taskClient.CancelTask(ctx, &CancelTaskRequest{TaskId: taskID})
}
// =============================================================================
// Streaming Operations
// =============================================================================
// StreamTaskStatus streams task status updates
func (c *GRPCClient) StreamTaskStatus(ctx context.Context, taskID string) (<-chan *TaskStatusUpdate, error) {
stream, err := c.taskClient.StreamTaskStatus(ctx)
if err != nil {
return nil, fmt.Errorf("failed to start stream: %w", err)
}
// Send subscription request
if err := stream.Send(&TaskStatusStreamRequest{
TaskId: taskID,
Subscribe: true,
}); err != nil {
return nil, fmt.Errorf("failed to subscribe: %w", err)
}
updateChan := make(chan *TaskStatusUpdate, 100)
go func() {
defer close(updateChan)
for {
update, err := stream.Recv()
if err == io.EOF {
return
}
if err != nil {
log.Printf("[gRPC Stream] Receive error: %v", err)
return
}
updateChan <- update
}
}()
return updateChan, nil
}
// StreamMessages opens a bidirectional message stream
func (c *GRPCClient) StreamMessages(ctx context.Context) (chan<- *A2AMessage, <-chan *A2AMessage, error) {
stream, err := c.messageClient.StreamMessages(ctx)
if err != nil {
return nil, nil, fmt.Errorf("failed to start message stream: %w", err)
}
sendChan := make(chan *A2AMessage, 100)
recvChan := make(chan *A2AMessage, 100)
// Send goroutine
go func() {
for msg := range sendChan {
if err := stream.Send(msg); err != nil {
log.Printf("[gRPC Stream] Send error: %v", err)
return
}
}
}()
// Receive goroutine
go func() {
defer close(recvChan)
for {
msg, err := stream.Recv()
if err == io.EOF {
return
}
if err != nil {
log.Printf("[gRPC Stream] Receive error: %v", err)
return
}
recvChan <- msg
}
}()
return sendChan, recvChan, nil
}
// =============================================================================
// Message Operations
// =============================================================================
// SendMessage sends a single Synapse message
func (c *GRPCClient) SendMessage(ctx context.Context, msg *A2AMessage) (*SendMessageResponse, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
return c.messageClient.SendMessage(ctx, &SendMessageRequest{Message: msg})
}
// =============================================================================
// Capabilities Operations
// =============================================================================
// GetCapabilities gets agent capabilities
func (c *GRPCClient) GetCapabilities(ctx context.Context) (*AgentCapabilities, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.capsClient.GetCapabilities(ctx, &emptypb.Empty{})
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("capabilities query failed: %s", resp.Error)
}
return resp.Capabilities, nil
}
// ListAgents lists available agents
func (c *GRPCClient) ListAgents(ctx context.Context) ([]*AgentInfo, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.capsClient.ListAgents(ctx, &emptypb.Empty{})
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("agent list failed")
}
return resp.Agents, nil
}
// =============================================================================
// Group Operations
// =============================================================================
// CreateGroup creates a new group
func (c *GRPCClient) CreateGroup(ctx context.Context, name, description, coordinatorID string, initialMembers []string) (*GroupInfo, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.groupClient.CreateGroup(ctx, &CreateGroupRequest{
Name: name,
Description: description,
CoordinatorId: coordinatorID,
InitialMembers: initialMembers,
})
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("group creation failed: %s", resp.Error)
}
return resp.Group, nil
}
// JoinGroup joins an existing group
func (c *GRPCClient) JoinGroup(ctx context.Context, groupID string, agent *AgentEndpoint) error {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.groupClient.JoinGroup(ctx, &JoinGroupRequest{
GroupId: groupID,
Agent: agent,
})
if err != nil {
return err
}
if !resp.Success {
return fmt.Errorf("join failed: %s", resp.Error)
}
return nil
}
// LeaveGroup leaves a group
func (c *GRPCClient) LeaveGroup(ctx context.Context, groupID, agentID string) error {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.groupClient.LeaveGroup(ctx, &LeaveGroupRequest{
GroupId: groupID,
AgentId: agentID,
})
if err != nil {
return err
}
if !resp.Success {
return fmt.Errorf("leave failed: %s", resp.Error)
}
return nil
}
// ListGroupMembers lists group members
func (c *GRPCClient) ListGroupMembers(ctx context.Context, groupID string) ([]*GroupMember, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.groupClient.ListGroupMembers(ctx, &ListGroupMembersRequest{GroupId: groupID})
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("member list failed")
}
return resp.Members, nil
}
// =============================================================================
// Voting Operations (Group Protocol)
// =============================================================================
// InitiateVote initiates a vote in a group
func (c *GRPCClient) InitiateVote(ctx context.Context, groupID, initiatorID, question string, options []string, threshold float64, timeoutSeconds int32) (string, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.groupClient.InitiateVote(ctx, &VoteRequest{
GroupId: groupID,
InitiatorId: initiatorID,
Question: question,
Options: options,
Threshold: threshold,
TimeoutSeconds: timeoutSeconds,
})
if err != nil {
return "", err
}
if !resp.Success {
return "", fmt.Errorf("initiate vote failed: %s", resp.Error)
}
return resp.VoteId, nil
}
// SubmitVote submits a vote
func (c *GRPCClient) SubmitVote(ctx context.Context, voteID, agentID, option, reason string) error {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.groupClient.SubmitVote(ctx, &SubmitVoteRequest{
VoteId: voteID,
AgentId: agentID,
Option: option,
Reason: reason,
})
if err != nil {
return err
}
if !resp.Success {
return fmt.Errorf("submit vote failed: %s", resp.Error)
}
return nil
}
// GetVoteStatus gets the current status of a vote
func (c *GRPCClient) GetVoteStatus(ctx context.Context, voteID string) (*VoteStatusResponse, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
return c.groupClient.GetVoteStatus(ctx, &GetVoteStatusRequest{VoteId: voteID})
}
// WaitForVote waits for a vote to complete
func (c *GRPCClient) WaitForVote(ctx context.Context, voteID string, interval time.Duration) (*VoteStatusResponse, error) {
if interval == 0 {
interval = 2 * time.Second
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
status, err := c.GetVoteStatus(ctx, voteID)
if err != nil {
continue
}
if status.Status == "decided" || status.Status == "expired" {
return status, nil
}
}
}
}
// =============================================================================
// Task Split & Merge Operations (Group Protocol)
// =============================================================================
// SplitTask splits a task into subtasks
func (c *GRPCClient) SplitTask(ctx context.Context, groupID, taskID, taskType, requirements string, strategy SplitStrategy) ([]*SubtaskInfo, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.groupClient.SplitTask(ctx, &SplitTaskRequest{
GroupId: groupID,
TaskId: taskID,
TaskType: taskType,
Requirements: requirements,
Strategy: strategy,
})
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("split task failed: %s", resp.Error)
}
return resp.Subtasks, nil
}
// MergeResults merges subtask results
func (c *GRPCClient) MergeResults(ctx context.Context, groupID, parentTaskID string, strategy MergeStrategy) (*MergeResultsResponse, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
return c.groupClient.MergeResults(ctx, &MergeResultsRequest{
GroupId: groupID,
ParentTaskId: parentTaskID,
Strategy: strategy,
})
}
// =============================================================================
// Health Operations
// =============================================================================
// HealthCheck checks server health
func (c *GRPCClient) HealthCheck(ctx context.Context) (*HealthCheckResponse, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
return c.healthClient.HealthCheck(ctx, &emptypb.Empty{})
}
// =============================================================================
// High-Level Helpers
// =============================================================================
// CreateAndWait creates a task and waits for completion via streaming
func (c *GRPCClient) CreateAndWait(ctx context.Context, taskType string, payload map[string]any, timeout time.Duration) (*TaskInfo, error) {
// Submit task
resp, err := c.SubmitTask(ctx, taskType, payload)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("task submission failed: %s", resp.Message)
}
// Stream status until completion
updateChan, err := c.StreamTaskStatus(ctx, resp.TaskId)
if err != nil {
return nil, err
}
// Wait with timeout
if timeout == 0 {
timeout = 5 * time.Minute
}
timer := time.NewTimer(timeout)
defer timer.Stop()
for {
select {
case update := <-updateChan:
if update == nil {
return nil, fmt.Errorf("stream closed unexpectedly")
}
switch update.Status {
case TaskStatus_TASK_STATUS_COMPLETED:
return &TaskInfo{
Id: resp.TaskId,
Status: update.Status,
Progress: update.Progress,
Message: update.Message,
}, nil
case TaskStatus_TASK_STATUS_FAILED, TaskStatus_TASK_STATUS_CANCELLED:
return nil, fmt.Errorf("task failed: %s", update.Message)
}
case <-timer.C:
return nil, fmt.Errorf("timeout waiting for task completion")
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
// WaitForTask waits for a task to complete using polling
func (c *GRPCClient) WaitForTask(ctx context.Context, taskID string, interval time.Duration) (*TaskInfo, error) {
if interval == 0 {
interval = 5 * time.Second
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
task, err := c.GetTaskStatus(ctx, taskID)
if err != nil {
continue
}
switch task.Status {
case TaskStatus_TASK_STATUS_COMPLETED:
return task, nil
case TaskStatus_TASK_STATUS_FAILED, TaskStatus_TASK_STATUS_CANCELLED:
return task, fmt.Errorf("task failed: %s", task.Message)
}
}
}
}
// =============================================================================
// Builder Pattern for A2AMessage
// =============================================================================
// MessageBuilder builds Synapse messages
type MessageBuilder struct {
msg *A2AMessage
}
// NewMessage creates a new message builder
func NewMessage() *MessageBuilder {
return &MessageBuilder{
msg: &A2AMessage{
Id: generateMessageID(),
Version: "1.0.0",
Timestamp: timestamppb.Now(),
},
}
}
func (b *MessageBuilder) Type(msgType MessageType) *MessageBuilder {
b.msg.Type = msgType
return b
}
func (b *MessageBuilder) From(sender *AgentEndpoint) *MessageBuilder {
b.msg.Sender = sender
return b
}
func (b *MessageBuilder) To(receiver *AgentEndpoint) *MessageBuilder {
b.msg.Receiver = receiver
return b
}
func (b *MessageBuilder) CorrelationID(id string) *MessageBuilder {
b.msg.CorrelationId = id
return b
}
func (b *MessageBuilder) GroupID(id string) *MessageBuilder {
b.msg.GroupId = id
return b
}
func (b *MessageBuilder) Payload(payload []byte) *MessageBuilder {
b.msg.Payload = &anypb.Any{Value: payload}
return b
}
func (b *MessageBuilder) Build() *A2AMessage {
return b.msg
}
func generateMessageID() string {
return fmt.Sprintf("msg-%d", time.Now().UnixNano())
}

861
grpcapi/server.go Normal file
View File

@@ -0,0 +1,861 @@
package grpcapi
import (
"context"
"fmt"
"io"
"log"
"net"
"net/http"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
"synapse/types"
)
// =============================================================================
// gRPC Server Implementation
// =============================================================================
// GRPCServer wraps the gRPC server and gateway
type GRPCServer struct {
grpcServer *grpc.Server
gateway *http.Server
mu sync.RWMutex
// Services
taskService *TaskServiceImpl
messageService *MessageServiceImpl
capabilitiesService *CapabilitiesServiceImpl
groupService *GroupServiceImpl
healthService *HealthServiceImpl
// Dependencies
taskExecutor TaskExecutor
msgRouter MessageRouter
capsProvider CapabilitiesProvider
groupManager GroupManager
}
// TaskExecutor interface for task operations
type TaskExecutor interface {
SubmitTask(ctx context.Context, taskType string, payload map[string]any, externalTaskID, priority, callbackURL, callbackToken string) (string, string, error)
GetTaskStatus(ctx context.Context, taskID string) (*types.TaskInfo, error)
ListTasks(ctx context.Context, status string, limit, offset int) ([]types.TaskInfo, int, error)
CancelTask(ctx context.Context, taskID string) error
}
type MessageRouter interface {
RouteMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error)
}
type CapabilitiesProvider interface {
GetCapabilities(ctx context.Context) (*types.AgentCapabilities, error)
}
type GroupManager interface {
CreateGroup(ctx context.Context, name, description, coordinatorID string, initialMembers []string) (string, error)
JoinGroup(ctx context.Context, groupID string, agent *types.AgentEndpoint) error
LeaveGroup(ctx context.Context, groupID, agentID string) error
DisbandGroup(ctx context.Context, groupID string) error
ListMembers(ctx context.Context, groupID string) ([]types.GroupMember, error)
// Voting operations
InitiateVote(ctx context.Context, groupID, initiatorID, question string, options []string, threshold float64, timeoutSeconds int32) (string, error)
SubmitVote(ctx context.Context, voteID, agentID, option, reason string) error
GetVoteStatus(ctx context.Context, voteID string) (*VoteStatus, error)
// Task split and merge
SplitTask(ctx context.Context, groupID, taskID, taskType, requirements string, strategy SplitStrategy) ([]*SubtaskInfo, error)
MergeResults(ctx context.Context, groupID, parentTaskID string, strategy MergeStrategy) (*anypb.Any, string, error)
}
// VoteStatus represents the status of a vote
type VoteStatus struct {
VoteID string
Status string // open, decided, expired
Question string
TotalVotes int
RequiredVotes int
WinningOption string
Votes map[string]string // agentID -> option
}
// =============================================================================
// Server Constructor
// =============================================================================
// NewGRPCServer creates a new gRPC server
func NewGRPCServer(taskExec TaskExecutor, msgRouter MessageRouter, capsProvider CapabilitiesProvider, groupManager GroupManager) *GRPCServer {
return &GRPCServer{
taskExecutor: taskExec,
msgRouter: msgRouter,
capsProvider: capsProvider,
groupManager: groupManager,
taskService: &TaskServiceImpl{},
messageService: &MessageServiceImpl{},
capabilitiesService: &CapabilitiesServiceImpl{},
groupService: &GroupServiceImpl{},
healthService: &HealthServiceImpl{},
}
}
// Start starts both gRPC and gateway servers
func (s *GRPCServer) Start(grpcPort, gatewayPort int) error {
// Start gRPC server
go func() {
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", grpcPort))
if err != nil {
log.Fatalf("[gRPC] Failed to listen: %v", err)
}
// Create gRPC server with interceptors
opts := []grpc.ServerOption{
grpc.UnaryInterceptor(s.unaryInterceptor),
grpc.StreamInterceptor(s.streamInterceptor),
}
s.grpcServer = grpc.NewServer(opts...)
// Register services
// Note: These would be generated from proto
// RegisterTaskServiceServer(s.grpcServer, s.taskService)
// RegisterA2AMessageServiceServer(s.grpcServer, s.messageService)
// ... etc
log.Printf("[gRPC] Server started on port %d", grpcPort)
if err := s.grpcServer.Serve(lis); err != nil {
log.Fatalf("[gRPC] Failed to serve: %v", err)
}
}()
// Start gRPC-Gateway
go func() {
_ = context.Background() // ctx reserved for future gateway handler registration
mux := http.NewServeMux()
// Register gateway handlers
// Note: These would be generated from proto by grpc-gateway
// err := RegisterTaskServiceHandlerFromEndpoint(ctx, mux, fmt.Sprintf("localhost:%d", grpcPort), opts)
// ... etc
s.gateway = &http.Server{
Addr: fmt.Sprintf(":%d", gatewayPort),
Handler: mux,
}
log.Printf("[Gateway] HTTP server started on port %d", gatewayPort)
if err := s.gateway.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("[Gateway] Failed to serve: %v", err)
}
}()
return nil
}
// Stop gracefully stops both servers
func (s *GRPCServer) Stop() {
if s.grpcServer != nil {
s.grpcServer.GracefulStop()
}
if s.gateway != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s.gateway.Shutdown(ctx)
}
}
// =============================================================================
// Interceptors
// =============================================================================
func (s *GRPCServer) unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// Extract metadata
md, ok := metadata.FromIncomingContext(ctx)
if ok {
// Check API key
apiKeys := md.Get("x-api-key")
if len(apiKeys) > 0 {
// Validate API key
log.Printf("[gRPC] API Key: %s", apiKeys[0])
}
}
// Call handler
return handler(ctx, req)
}
func (s *GRPCServer) streamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
// Extract metadata
md, ok := metadata.FromIncomingContext(ss.Context())
if ok {
apiKeys := md.Get("x-api-key")
if len(apiKeys) > 0 {
log.Printf("[gRPC Stream] API Key: %s", apiKeys[0])
}
}
return handler(srv, ss)
}
// =============================================================================
// Task Service Implementation
// =============================================================================
type TaskServiceImpl struct {
UnimplementedTaskServiceServer
taskExecutor TaskExecutor
mu sync.RWMutex
streamClients map[string]chan *TaskStatusUpdate
}
func (s *TaskServiceImpl) SubmitTask(ctx context.Context, req *SubmitTaskRequest) (*SubmitTaskResponse, error) {
// Convert payload
payload := make(map[string]any)
if req.Payload != nil {
// Unpack Any to map
// This would use protojson or similar
}
taskID, status, err := s.taskExecutor.SubmitTask(
ctx,
req.TaskType,
payload,
req.ExternalTaskId,
req.Priority,
req.CallbackUrl,
req.CallbackToken,
)
if err != nil {
return &SubmitTaskResponse{
Success: false,
ErrorCode: "TASK_SUBMISSION_FAILED",
Message: err.Error(),
}, nil
}
return &SubmitTaskResponse{
Success: true,
TaskId: taskID,
Status: parseTaskStatus(status),
Message: "任务已接收",
}, nil
}
func (s *TaskServiceImpl) SubmitTaskWithFiles(ctx context.Context, req *SubmitTaskWithFilesRequest) (*SubmitTaskResponse, error) {
// Handle file uploads
// Convert files and call SubmitTask
return s.SubmitTask(ctx, req.Request)
}
func (s *TaskServiceImpl) GetTask(ctx context.Context, req *GetTaskRequest) (*TaskStatusResponse, error) {
task, err := s.taskExecutor.GetTaskStatus(ctx, req.TaskId)
if err != nil {
return &TaskStatusResponse{
Success: false,
ErrorCode: "TASK_NOT_FOUND",
ErrorMessage: err.Error(),
}, nil
}
return &TaskStatusResponse{
Success: true,
Task: convertTaskInfo(task),
}, nil
}
func (s *TaskServiceImpl) GetTaskStatus(ctx context.Context, req *GetTaskRequest) (*TaskStatusResponse, error) {
return s.GetTask(ctx, req)
}
func (s *TaskServiceImpl) ListTasks(ctx context.Context, req *ListTasksRequest) (*ListTasksResponse, error) {
tasks, total, err := s.taskExecutor.ListTasks(ctx, req.Status, int(req.Limit), int(req.Offset))
if err != nil {
return &ListTasksResponse{
Success: false,
}, err
}
var protoTasks []*TaskInfo
for _, t := range tasks {
protoTasks = append(protoTasks, convertTaskInfo(&t))
}
return &ListTasksResponse{
Success: true,
Tasks: protoTasks,
Total: int32(total),
Limit: req.Limit,
Offset: req.Offset,
HasMore: int(req.Offset+req.Limit) < total,
}, nil
}
func (s *TaskServiceImpl) CancelTask(ctx context.Context, req *CancelTaskRequest) (*CancelTaskResponse, error) {
if err := s.taskExecutor.CancelTask(ctx, req.TaskId); err != nil {
return &CancelTaskResponse{
Success: false,
Message: err.Error(),
}, nil
}
return &CancelTaskResponse{
Success: true,
TaskId: req.TaskId,
Status: TaskStatus_TASK_STATUS_CANCELLED,
Message: "任务已取消",
}, nil
}
// StreamTaskStatus implements bidirectional streaming for task status updates
func (s *TaskServiceImpl) StreamTaskStatus(stream TaskService_StreamTaskStatusServer) error {
// Register client
clientID := fmt.Sprintf("stream-%d", time.Now().UnixNano())
statusChan := make(chan *TaskStatusUpdate, 100)
s.mu.Lock()
s.streamClients[clientID] = statusChan
s.mu.Unlock()
defer func() {
s.mu.Lock()
delete(s.streamClients, clientID)
s.mu.Unlock()
close(statusChan)
}()
// Goroutine to send status updates
go func() {
for update := range statusChan {
if err := stream.Send(update); err != nil {
log.Printf("[gRPC Stream] Send error: %v", err)
return
}
}
}()
// Receive subscription requests
for {
req, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
if req.Subscribe {
log.Printf("[gRPC Stream] Client %s subscribed to task %s", clientID, req.TaskId)
// Start watching task status
go s.watchTaskStatus(req.TaskId, statusChan)
} else {
log.Printf("[gRPC Stream] Client %s unsubscribed from task %s", clientID, req.TaskId)
}
}
}
func (s *TaskServiceImpl) watchTaskStatus(taskID string, ch chan *TaskStatusUpdate) {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for range ticker.C {
task, err := s.taskExecutor.GetTaskStatus(context.Background(), taskID)
if err != nil {
continue
}
ch <- &TaskStatusUpdate{
TaskId: taskID,
Status: parseTaskStatus(task.Status),
Progress: int32(task.Progress),
Message: task.Message,
}
// Stop if terminal state
if task.Status == "completed" || task.Status == "failed" || task.Status == "cancelled" {
return
}
}
}
// =============================================================================
// Message Service Implementation
// =============================================================================
type MessageServiceImpl struct {
UnimplementedA2AMessageServiceServer
msgRouter MessageRouter
}
func (s *MessageServiceImpl) SendMessage(ctx context.Context, req *SendMessageRequest) (*SendMessageResponse, error) {
// Convert proto message to internal format
msg := convertProtoToInternalMessage(req.Message)
resp, err := s.msgRouter.RouteMessage(ctx, msg)
if err != nil {
return &SendMessageResponse{
Success: false,
Error: err.Error(),
}, nil
}
return &SendMessageResponse{
Success: true,
MessageId: resp.ID,
}, nil
}
// StreamMessages implements bidirectional streaming for Synapse messages
func (s *MessageServiceImpl) StreamMessages(stream A2AMessageService_StreamMessagesServer) error {
ctx := stream.Context()
clientID := fmt.Sprintf("msg-stream-%d", time.Now().UnixNano())
log.Printf("[gRPC Stream] New message stream: %s", clientID)
// Goroutine to receive messages
go func() {
for {
msg, err := stream.Recv()
if err == io.EOF {
return
}
if err != nil {
log.Printf("[gRPC Stream] Receive error: %v", err)
return
}
internalMsg := convertProtoToInternalMessage(msg)
resp, err := s.msgRouter.RouteMessage(ctx, internalMsg)
if err != nil {
log.Printf("[gRPC Stream] Route error: %v", err)
continue
}
protoResp := convertInternalToProtoMessage(resp)
if err := stream.Send(protoResp); err != nil {
log.Printf("[gRPC Stream] Send error: %v", err)
return
}
}
}()
// Keep stream alive
<-ctx.Done()
log.Printf("[gRPC Stream] Message stream closed: %s", clientID)
return nil
}
func (s *MessageServiceImpl) ReceiveCallback(req *emptypb.Empty, stream A2AMessageService_ReceiveCallbackServer) error {
// This would be used by agents to receive callback messages via gRPC stream
// Implementation depends on callback routing mechanism
return status.Error(codes.Unimplemented, "not implemented")
}
// =============================================================================
// Capabilities Service Implementation
// =============================================================================
type CapabilitiesServiceImpl struct {
UnimplementedCapabilitiesServiceServer
provider CapabilitiesProvider
}
func (s *CapabilitiesServiceImpl) GetCapabilities(ctx context.Context, req *emptypb.Empty) (*CapabilitiesResponse, error) {
caps, err := s.provider.GetCapabilities(ctx)
if err != nil {
return &CapabilitiesResponse{
Success: false,
Error: err.Error(),
}, nil
}
return &CapabilitiesResponse{
Success: true,
Capabilities: convertAgentCapabilities(caps),
}, nil
}
func (s *CapabilitiesServiceImpl) QueryAgent(ctx context.Context, req *QueryAgentRequest) (*CapabilitiesResponse, error) {
// Query specific agent's capabilities
return s.GetCapabilities(ctx, nil)
}
func (s *CapabilitiesServiceImpl) ListAgents(ctx context.Context, req *emptypb.Empty) (*ListAgentsResponse, error) {
return &ListAgentsResponse{
Success: true,
Agents: []*AgentInfo{},
}, nil
}
// =============================================================================
// Group Service Implementation
// =============================================================================
type GroupServiceImpl struct {
UnimplementedGroupServiceServer
groupManager GroupManager
}
func (s *GroupServiceImpl) CreateGroup(ctx context.Context, req *CreateGroupRequest) (*CreateGroupResponse, error) {
groupID, err := s.groupManager.CreateGroup(ctx, req.Name, req.Description, req.CoordinatorId, req.InitialMembers)
if err != nil {
return &CreateGroupResponse{
Success: false,
Error: err.Error(),
}, nil
}
return &CreateGroupResponse{
Success: true,
GroupId: groupID,
Group: &GroupInfo{
Id: groupID,
Name: req.Name,
Description: req.Description,
CoordinatorId: req.CoordinatorId,
Status: "active",
CreatedAt: timestamppb.Now(),
},
}, nil
}
func (s *GroupServiceImpl) JoinGroup(ctx context.Context, req *JoinGroupRequest) (*JoinGroupResponse, error) {
err := s.groupManager.JoinGroup(ctx, req.GroupId, convertProtoEndpoint(req.Agent))
if err != nil {
return &JoinGroupResponse{Success: false, Error: err.Error()}, nil
}
return &JoinGroupResponse{Success: true}, nil
}
func (s *GroupServiceImpl) LeaveGroup(ctx context.Context, req *LeaveGroupRequest) (*LeaveGroupResponse, error) {
err := s.groupManager.LeaveGroup(ctx, req.GroupId, req.AgentId)
if err != nil {
return &LeaveGroupResponse{Success: false, Error: err.Error()}, nil
}
return &LeaveGroupResponse{Success: true}, nil
}
func (s *GroupServiceImpl) DisbandGroup(ctx context.Context, req *DisbandGroupRequest) (*DisbandGroupResponse, error) {
err := s.groupManager.DisbandGroup(ctx, req.GroupId)
if err != nil {
return &DisbandGroupResponse{Success: false, Error: err.Error()}, nil
}
return &DisbandGroupResponse{Success: true}, nil
}
func (s *GroupServiceImpl) ListGroupMembers(ctx context.Context, req *ListGroupMembersRequest) (*ListGroupMembersResponse, error) {
members, err := s.groupManager.ListMembers(ctx, req.GroupId)
if err != nil {
return &ListGroupMembersResponse{Success: false}, nil
}
var protoMembers []*GroupMember
for _, m := range members {
protoMembers = append(protoMembers, &GroupMember{
AgentId: m.AgentID,
AgentName: m.AgentName,
Role: m.Role,
Status: string(m.Status),
JoinedAt: timestamppb.Now(),
})
}
return &ListGroupMembersResponse{
Success: true,
Members: protoMembers,
}, nil
}
// StreamGroupMessages implements bidirectional streaming for group messages
func (s *GroupServiceImpl) StreamGroupMessages(stream GroupService_StreamGroupMessagesServer) error {
ctx := stream.Context()
clientID := fmt.Sprintf("group-stream-%d", time.Now().UnixNano())
log.Printf("[gRPC Stream] New group stream: %s", clientID)
go func() {
for {
msg, err := stream.Recv()
if err == io.EOF {
return
}
if err != nil {
log.Printf("[gRPC Stream] Receive error: %v", err)
return
}
log.Printf("[gRPC Stream] Group message received: group=%s, type=%v", msg.GroupId, msg.Type)
// Echo back for now
if err := stream.Send(msg); err != nil {
log.Printf("[gRPC Stream] Send error: %v", err)
return
}
}
}()
<-ctx.Done()
log.Printf("[gRPC Stream] Group stream closed: %s", clientID)
return nil
}
// InitiateVote initiates a voting process in the group
func (s *GroupServiceImpl) InitiateVote(ctx context.Context, req *VoteRequest) (*VoteResponse, error) {
voteID, err := s.groupManager.InitiateVote(ctx, req.GroupId, req.InitiatorId, req.Question, req.Options, req.Threshold, req.TimeoutSeconds)
if err != nil {
return &VoteResponse{
Success: false,
Error: err.Error(),
}, nil
}
return &VoteResponse{
Success: true,
VoteId: voteID,
}, nil
}
// SubmitVote submits a vote in an ongoing voting process
func (s *GroupServiceImpl) SubmitVote(ctx context.Context, req *SubmitVoteRequest) (*SubmitVoteResponse, error) {
err := s.groupManager.SubmitVote(ctx, req.VoteId, req.AgentId, req.Option, req.Reason)
if err != nil {
return &SubmitVoteResponse{
Success: false,
Error: err.Error(),
}, nil
}
return &SubmitVoteResponse{
Success: true,
}, nil
}
// GetVoteStatus gets the current status of a vote
func (s *GroupServiceImpl) GetVoteStatus(ctx context.Context, req *GetVoteStatusRequest) (*VoteStatusResponse, error) {
status, err := s.groupManager.GetVoteStatus(ctx, req.VoteId)
if err != nil {
return &VoteStatusResponse{
Success: false,
Error: err.Error(),
}, nil
}
return &VoteStatusResponse{
Success: true,
VoteId: status.VoteID,
Status: status.Status,
Question: status.Question,
TotalVotes: int32(status.TotalVotes),
RequiredVotes: int32(status.RequiredVotes),
WinningOption: status.WinningOption,
}, nil
}
// SplitTask splits a task into subtasks across group members
func (s *GroupServiceImpl) SplitTask(ctx context.Context, req *SplitTaskRequest) (*SplitTaskResponse, error) {
subtasks, err := s.groupManager.SplitTask(ctx, req.GroupId, req.TaskId, req.TaskType, req.Requirements, req.Strategy)
if err != nil {
return &SplitTaskResponse{
Success: false,
Error: err.Error(),
}, nil
}
return &SplitTaskResponse{
Success: true,
GroupId: req.GroupId,
ParentTaskId: req.TaskId,
Subtasks: subtasks,
}, nil
}
// MergeResults merges results from subtasks
func (s *GroupServiceImpl) MergeResults(ctx context.Context, req *MergeResultsRequest) (*MergeResultsResponse, error) {
mergedResult, summary, err := s.groupManager.MergeResults(ctx, req.GroupId, req.ParentTaskId, req.Strategy)
if err != nil {
return &MergeResultsResponse{
Success: false,
Error: err.Error(),
}, nil
}
return &MergeResultsResponse{
Success: true,
ParentTaskId: req.ParentTaskId,
MergedResult: mergedResult,
Summary: summary,
}, nil
}
// =============================================================================
// Health Service Implementation
// =============================================================================
type HealthServiceImpl struct {
UnimplementedHealthServiceServer
}
func (s *HealthServiceImpl) HealthCheck(ctx context.Context, req *emptypb.Empty) (*HealthCheckResponse, error) {
return &HealthCheckResponse{
Status: "healthy",
Version: "1.0.0",
UptimeSeconds: 3600,
ActiveTasks: 0,
}, nil
}
// =============================================================================
// Conversion Helpers
// =============================================================================
func parseTaskStatus(status string) TaskStatus {
switch status {
case "received":
return TaskStatus_TASK_STATUS_RECEIVED
case "processing":
return TaskStatus_TASK_STATUS_PROCESSING
case "completed":
return TaskStatus_TASK_STATUS_COMPLETED
case "failed":
return TaskStatus_TASK_STATUS_FAILED
case "cancelled":
return TaskStatus_TASK_STATUS_CANCELLED
case "rejected":
return TaskStatus_TASK_STATUS_REJECTED
case "waiting":
return TaskStatus_TASK_STATUS_WAITING
default:
return TaskStatus_TASK_STATUS_UNSPECIFIED
}
}
func convertTaskInfo(task *types.TaskInfo) *TaskInfo {
if task == nil {
return nil
}
var result *anypb.Any
if task.Result != nil {
result, _ = anypb.New(&anypb.Any{Value: []byte(fmt.Sprintf("%v", task.Result))})
}
return &TaskInfo{
Id: task.ID,
Type: task.Type,
Status: parseTaskStatus(task.Status),
Progress: int32(task.Progress),
Message: task.Message,
CreatedAt: timestamppb.Now(),
UpdatedAt: timestamppb.Now(),
Result: result,
}
}
func convertAgentCapabilities(caps *types.AgentCapabilities) *AgentCapabilities {
if caps == nil {
return nil
}
var taskTypes []*SupportedTaskType
for _, tt := range caps.SupportedTaskTypes {
taskTypes = append(taskTypes, &SupportedTaskType{
Type: tt.Type,
Name: tt.Name,
Description: tt.Description,
EstimatedTime: tt.EstimatedTime,
InputSchema: tt.InputSchema,
OutputSchema: tt.OutputSchema,
Capabilities: tt.Capabilities,
})
}
var transportModes []TransportMode
for _, tm := range caps.SupportedTransportModes {
transportModes = append(transportModes, TransportMode(TransportMode_value[string(tm)]))
}
return &AgentCapabilities{
AgentId: caps.AgentID,
AgentName: caps.AgentName,
SystemId: caps.SystemID,
ProtocolVersion: caps.ProtocolVersion,
SupportedTaskTypes: taskTypes,
SupportedContentTypes: caps.SupportedContentTypes,
SupportedTransportModes: transportModes,
MaxFileSize: caps.MaxFileSize,
MaxConcurrentTasks: int32(caps.MaxConcurrentTasks),
MaxTaskDurationSeconds: int32(caps.MaxTaskDuration),
SupportProgressUpdates: caps.SupportProgressUpdates,
SupportStreaming: caps.SupportStreaming,
SupportHil: caps.SupportHiL,
SupportGroups: caps.SupportGroups,
}
}
func convertProtoToInternalMessage(msg *A2AMessage) *types.UnifiedSynapseMessage {
if msg == nil {
return nil
}
var payload []byte
if msg.Payload != nil {
payload = msg.Payload.Value
}
return &types.UnifiedSynapseMessage{
ID: msg.Id,
Version: msg.Version,
Type: string(MessageType_name[int32(msg.Type)]),
Sender: types.AgentEndpoint{AgentID: msg.Sender.AgentId},
CorrelationID: msg.CorrelationId,
ReplyTo: msg.ReplyTo,
GroupID: msg.GroupId,
Timestamp: types.Timestamp(msg.Timestamp.AsTime().Format(time.RFC3339)),
Payload: payload,
Signature: msg.Signature,
}
}
func convertInternalToProtoMessage(msg *types.UnifiedSynapseMessage) *A2AMessage {
if msg == nil {
return nil
}
var payload *anypb.Any
if len(msg.Payload) > 0 {
payload = &anypb.Any{Value: msg.Payload}
}
return &A2AMessage{
Id: msg.ID,
Version: msg.Version,
Type: MessageType(MessageType_value[msg.Type]),
Sender: &AgentEndpoint{AgentId: msg.Sender.AgentID},
CorrelationId: msg.CorrelationID,
ReplyTo: msg.ReplyTo,
GroupId: msg.GroupID,
Timestamp: timestamppb.Now(),
Payload: payload,
Signature: msg.Signature,
}
}
func convertProtoEndpoint(endpoint *AgentEndpoint) *types.AgentEndpoint {
if endpoint == nil {
return nil
}
return &types.AgentEndpoint{
AgentID: endpoint.AgentId,
AgentName: endpoint.AgentName,
SystemID: endpoint.SystemId,
Endpoint: endpoint.Endpoint,
Version: endpoint.Version,
}
}

850
grpcapi/synapse.pb.go Normal file
View File

@@ -0,0 +1,850 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// version: proto synapse v1.0.0
// This file contains manually created stubs matching proto/synapse.proto
package grpcapi
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
anypb "google.golang.org/protobuf/types/known/anypb"
timestamp "google.golang.org/protobuf/types/known/timestamppb"
)
// TransportMode represents the transport mode
type TransportMode int32
const (
TransportMode_TRANSPORT_MODE_UNSPECIFIED TransportMode = 0
TransportMode_TRANSPORT_MODE_CALLBACK TransportMode = 1
TransportMode_TRANSPORT_MODE_POLLING TransportMode = 2
TransportMode_TRANSPORT_MODE_SYNC TransportMode = 3
TransportMode_TRANSPORT_MODE_HYBRID TransportMode = 4
)
var TransportMode_name = map[int32]string{
0: "TRANSPORT_MODE_UNSPECIFIED",
1: "TRANSPORT_MODE_CALLBACK",
2: "TRANSPORT_MODE_POLLING",
3: "TRANSPORT_MODE_SYNC",
4: "TRANSPORT_MODE_HYBRID",
}
var TransportMode_value = map[string]int32{
"TRANSPORT_MODE_UNSPECIFIED": 0,
"TRANSPORT_MODE_CALLBACK": 1,
"TRANSPORT_MODE_POLLING": 2,
"TRANSPORT_MODE_SYNC": 3,
"TRANSPORT_MODE_HYBRID": 4,
}
// MessageType represents the type of Synapse message
type MessageType int32
const (
MessageType_MESSAGE_TYPE_UNSPECIFIED MessageType = 0
MessageType_MESSAGE_TYPE_P2P_TASK_REQUEST MessageType = 1
MessageType_MESSAGE_TYPE_P2P_TASK_COMPLETE MessageType = 2
MessageType_MESSAGE_TYPE_P2P_TASK_FAILED MessageType = 3
MessageType_MESSAGE_TYPE_P2P_STATUS_UPDATE MessageType = 4
MessageType_MESSAGE_TYPE_P2P_INFO_MISSING MessageType = 5
MessageType_MESSAGE_TYPE_P2P_ACK MessageType = 6
MessageType_MESSAGE_TYPE_P2P_NACK MessageType = 7
MessageType_MESSAGE_TYPE_GROUP_INVITE MessageType = 8
MessageType_MESSAGE_TYPE_GROUP_JOIN MessageType = 9
MessageType_MESSAGE_TYPE_GROUP_LEAVE MessageType = 10
MessageType_MESSAGE_TYPE_GROUP_TASK_SPLIT MessageType = 11
MessageType_MESSAGE_TYPE_GROUP_SUBTASK_RESULT MessageType = 12
MessageType_MESSAGE_TYPE_GROUP_RESULT_MERGE MessageType = 13
MessageType_MESSAGE_TYPE_GROUP_VOTE_INITIATE MessageType = 14
MessageType_MESSAGE_TYPE_GROUP_VOTE_SUBMIT MessageType = 15
MessageType_MESSAGE_TYPE_GROUP_VOTE_RESULT MessageType = 16
MessageType_MESSAGE_TYPE_HIL_DECISION_REQUEST MessageType = 17
MessageType_MESSAGE_TYPE_HIL_DECISION_RESPONSE MessageType = 18
MessageType_MESSAGE_TYPE_HEARTBEAT MessageType = 19
)
var MessageType_name = map[int32]string{
0: "MESSAGE_TYPE_UNSPECIFIED",
1: "MESSAGE_TYPE_P2P_TASK_REQUEST",
2: "MESSAGE_TYPE_P2P_TASK_COMPLETE",
3: "MESSAGE_TYPE_P2P_TASK_FAILED",
4: "MESSAGE_TYPE_P2P_STATUS_UPDATE",
5: "MESSAGE_TYPE_P2P_INFO_MISSING",
6: "MESSAGE_TYPE_P2P_ACK",
7: "MESSAGE_TYPE_P2P_NACK",
8: "MESSAGE_TYPE_GROUP_INVITE",
9: "MESSAGE_TYPE_GROUP_JOIN",
10: "MESSAGE_TYPE_GROUP_LEAVE",
11: "MESSAGE_TYPE_GROUP_TASK_SPLIT",
12: "MESSAGE_TYPE_GROUP_SUBTASK_RESULT",
13: "MESSAGE_TYPE_GROUP_RESULT_MERGE",
14: "MESSAGE_TYPE_GROUP_VOTE_INITIATE",
15: "MESSAGE_TYPE_GROUP_VOTE_SUBMIT",
16: "MESSAGE_TYPE_GROUP_VOTE_RESULT",
17: "MESSAGE_TYPE_HIL_DECISION_REQUEST",
18: "MESSAGE_TYPE_HIL_DECISION_RESPONSE",
19: "MESSAGE_TYPE_HEARTBEAT",
}
var MessageType_value = map[string]int32{
"MESSAGE_TYPE_UNSPECIFIED": 0,
"MESSAGE_TYPE_P2P_TASK_REQUEST": 1,
"MESSAGE_TYPE_P2P_TASK_COMPLETE": 2,
"MESSAGE_TYPE_P2P_TASK_FAILED": 3,
"MESSAGE_TYPE_P2P_STATUS_UPDATE": 4,
"MESSAGE_TYPE_P2P_INFO_MISSING": 5,
"MESSAGE_TYPE_P2P_ACK": 6,
"MESSAGE_TYPE_P2P_NACK": 7,
"MESSAGE_TYPE_GROUP_INVITE": 8,
"MESSAGE_TYPE_GROUP_JOIN": 9,
"MESSAGE_TYPE_GROUP_LEAVE": 10,
"MESSAGE_TYPE_GROUP_TASK_SPLIT": 11,
"MESSAGE_TYPE_GROUP_SUBTASK_RESULT": 12,
"MESSAGE_TYPE_GROUP_RESULT_MERGE": 13,
"MESSAGE_TYPE_GROUP_VOTE_INITIATE": 14,
"MESSAGE_TYPE_GROUP_VOTE_SUBMIT": 15,
"MESSAGE_TYPE_GROUP_VOTE_RESULT": 16,
"MESSAGE_TYPE_HIL_DECISION_REQUEST": 17,
"MESSAGE_TYPE_HIL_DECISION_RESPONSE": 18,
"MESSAGE_TYPE_HEARTBEAT": 19,
}
// TaskStatus represents the status of a task
type TaskStatus int32
const (
TaskStatus_TASK_STATUS_UNSPECIFIED TaskStatus = 0
TaskStatus_TASK_STATUS_RECEIVED TaskStatus = 1
TaskStatus_TASK_STATUS_PROCESSING TaskStatus = 2
TaskStatus_TASK_STATUS_COMPLETED TaskStatus = 3
TaskStatus_TASK_STATUS_FAILED TaskStatus = 4
TaskStatus_TASK_STATUS_CANCELLED TaskStatus = 5
TaskStatus_TASK_STATUS_REJECTED TaskStatus = 6
TaskStatus_TASK_STATUS_WAITING TaskStatus = 7
)
var TaskStatus_name = map[int32]string{
0: "TASK_STATUS_UNSPECIFIED",
1: "TASK_STATUS_RECEIVED",
2: "TASK_STATUS_PROCESSING",
3: "TASK_STATUS_COMPLETED",
4: "TASK_STATUS_FAILED",
5: "TASK_STATUS_CANCELLED",
6: "TASK_STATUS_REJECTED",
7: "TASK_STATUS_WAITING",
}
var TaskStatus_value = map[string]int32{
"TASK_STATUS_UNSPECIFIED": 0,
"TASK_STATUS_RECEIVED": 1,
"TASK_STATUS_PROCESSING": 2,
"TASK_STATUS_COMPLETED": 3,
"TASK_STATUS_FAILED": 4,
"TASK_STATUS_CANCELLED": 5,
"TASK_STATUS_REJECTED": 6,
"TASK_STATUS_WAITING": 7,
}
func (x TransportMode) String() string { return fmt.Sprintf("%v", x) }
func (x MessageType) String() string { return fmt.Sprintf("%v", x) }
func (x TaskStatus) String() string { return fmt.Sprintf("%v", x) }
func init() {
proto.RegisterEnum("a2a.TransportMode", TransportMode_name, TransportMode_value)
proto.RegisterEnum("a2a.MessageType", MessageType_name, MessageType_value)
proto.RegisterEnum("a2a.TaskStatus", TaskStatus_name, TaskStatus_value)
}
// AgentEndpoint represents an agent's endpoint
type AgentEndpoint struct {
AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId" json:"agent_id,omitempty"`
AgentName string `protobuf:"bytes,2,opt,name=agent_name,json=agentName" json:"agent_name,omitempty"`
SystemId string `protobuf:"bytes,3,opt,name=system_id,json=systemId" json:"system_id,omitempty"`
Endpoint string `protobuf:"bytes,4,opt,name=endpoint" json:"endpoint,omitempty"`
Version string `protobuf:"bytes,5,opt,name=version" json:"version,omitempty"`
}
func (m *AgentEndpoint) Reset() { *m = AgentEndpoint{} }
func (m *AgentEndpoint) String() string { return fmt.Sprintf("%+v", m) }
func (*AgentEndpoint) ProtoMessage() {}
func (m *AgentEndpoint) GetAgentId() string {
if m != nil {
return m.AgentId
}
return ""
}
// A2AMessage represents a complete Synapse message
type A2AMessage struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
Version string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"`
Type MessageType `protobuf:"varint,3,opt,name=type,enum=a2a.MessageType" json:"type,omitempty"`
Sender *AgentEndpoint `protobuf:"bytes,4,opt,name=sender" json:"sender,omitempty"`
Receiver *AgentEndpoint `protobuf:"bytes,5,opt,name=receiver" json:"receiver,omitempty"`
CorrelationId string `protobuf:"bytes,6,opt,name=correlation_id,json=correlationId" json:"correlation_id,omitempty"`
ReplyTo string `protobuf:"bytes,7,opt,name=reply_to,json=replyTo" json:"reply_to,omitempty"`
GroupId string `protobuf:"bytes,8,opt,name=group_id,json=groupId" json:"group_id,omitempty"`
Timestamp *timestamp.Timestamp `protobuf:"bytes,9,opt,name=timestamp" json:"timestamp,omitempty"`
Payload *anypb.Any `protobuf:"bytes,10,opt,name=payload" json:"payload,omitempty"`
Signature string `protobuf:"bytes,11,opt,name=signature" json:"signature,omitempty"`
}
func (m *A2AMessage) Reset() { *m = A2AMessage{} }
func (m *A2AMessage) String() string { return fmt.Sprintf("%+v", m) }
func (*A2AMessage) ProtoMessage() {}
// TaskPayload represents task submission payload
type TaskPayload struct {
TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId" json:"task_id,omitempty"`
TaskType string `protobuf:"bytes,2,opt,name=task_type,json=taskType" json:"task_type,omitempty"`
Payload *anypb.Any `protobuf:"bytes,3,opt,name=payload" json:"payload,omitempty"`
ExternalTaskId string `protobuf:"bytes,4,opt,name=external_task_id,json=externalTaskId" json:"external_task_id,omitempty"`
}
func (m *TaskPayload) Reset() { *m = TaskPayload{} }
func (m *TaskPayload) String() string { return fmt.Sprintf("%+v", m) }
func (*TaskPayload) ProtoMessage() {}
// TaskResult represents task result
type TaskResult struct {
TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId" json:"task_id,omitempty"`
Type string `protobuf:"bytes,2,opt,name=type" json:"type,omitempty"`
Result *anypb.Any `protobuf:"bytes,3,opt,name=result" json:"result,omitempty"`
Summary string `protobuf:"bytes,4,opt,name=summary" json:"summary,omitempty"`
}
func (m *TaskResult) Reset() { *m = TaskResult{} }
func (m *TaskResult) String() string { return fmt.Sprintf("%+v", m) }
func (*TaskResult) ProtoMessage() {}
// TaskStatusUpdate represents task status update
type TaskStatusUpdate struct {
TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId" json:"task_id,omitempty"`
Status TaskStatus `protobuf:"varint,2,opt,name=status,enum=a2a.TaskStatus" json:"status,omitempty"`
Progress int32 `protobuf:"varint,3,opt,name=progress" json:"progress,omitempty"`
Message string `protobuf:"bytes,4,opt,name=message" json:"message,omitempty"`
Details *anypb.Any `protobuf:"bytes,5,opt,name=details" json:"details,omitempty"`
}
func (m *TaskStatusUpdate) Reset() { *m = TaskStatusUpdate{} }
func (m *TaskStatusUpdate) String() string { return fmt.Sprintf("%+v", m) }
func (*TaskStatusUpdate) ProtoMessage() {}
// =============================================================================
// Task Service Messages
// =============================================================================
// SubmitTaskRequest is the request for submitting a task
type SubmitTaskRequest struct {
TaskType string `protobuf:"bytes,1,opt,name=task_type,json=taskType" json:"task_type,omitempty"`
Payload *anypb.Any `protobuf:"bytes,2,opt,name=payload" json:"payload,omitempty"`
ExternalTaskId string `protobuf:"bytes,3,opt,name=external_task_id,json=externalTaskId" json:"external_task_id,omitempty"`
Priority string `protobuf:"bytes,4,opt,name=priority" json:"priority,omitempty"`
CallbackUrl string `protobuf:"bytes,5,opt,name=callback_url,json=callbackUrl" json:"callback_url,omitempty"`
CallbackToken string `protobuf:"bytes,6,opt,name=callback_token,json=callbackToken" json:"callback_token,omitempty"`
PreferredTransport TransportMode `protobuf:"varint,7,opt,name=preferred_transport,json=preferredTransport" json:"preferred_transport,omitempty"`
}
func (m *SubmitTaskRequest) Reset() { *m = SubmitTaskRequest{} }
func (m *SubmitTaskRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*SubmitTaskRequest) ProtoMessage() {}
func (m *SubmitTaskRequest) GetTaskType() string {
if m != nil {
return m.TaskType
}
return ""
}
// SubmitTaskResponse is the response for task submission
type SubmitTaskResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
TaskId string `protobuf:"bytes,2,opt,name=task_id,json=taskId" json:"task_id,omitempty"`
Status TaskStatus `protobuf:"varint,3,opt,name=status,enum=a2a.TaskStatus" json:"status,omitempty"`
Message string `protobuf:"bytes,4,opt,name=message" json:"message,omitempty"`
ErrorCode string `protobuf:"bytes,5,opt,name=error_code,json=errorCode" json:"error_code,omitempty"`
}
func (m *SubmitTaskResponse) Reset() { *m = SubmitTaskResponse{} }
func (m *SubmitTaskResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*SubmitTaskResponse) ProtoMessage() {}
// SubmitTaskWithFilesRequest is the request for submitting a task with files
type SubmitTaskWithFilesRequest struct {
Request *SubmitTaskRequest `protobuf:"bytes,1,opt,name=request" json:"request,omitempty"`
Files []*FileUpload `protobuf:"bytes,2,rep,name=files" json:"files,omitempty"`
}
func (m *SubmitTaskWithFilesRequest) Reset() { *m = SubmitTaskWithFilesRequest{} }
func (m *SubmitTaskWithFilesRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*SubmitTaskWithFilesRequest) ProtoMessage() {}
// FileUpload represents an uploaded file
type FileUpload struct {
FieldName string `protobuf:"bytes,1,opt,name=field_name,json=fieldName" json:"field_name,omitempty"`
Filename string `protobuf:"bytes,2,opt,name=filename" json:"filename,omitempty"`
MimeType string `protobuf:"bytes,3,opt,name=mime_type,json=mimeType" json:"mime_type,omitempty"`
Data []byte `protobuf:"bytes,4,opt,name=data" json:"data,omitempty"`
}
func (m *FileUpload) Reset() { *m = FileUpload{} }
func (m *FileUpload) String() string { return fmt.Sprintf("%+v", m) }
func (*FileUpload) ProtoMessage() {}
// GetTaskRequest is the request for getting task details
type GetTaskRequest struct {
TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId" json:"task_id,omitempty"`
}
func (m *GetTaskRequest) Reset() { *m = GetTaskRequest{} }
func (m *GetTaskRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*GetTaskRequest) ProtoMessage() {}
// TaskStatusResponse is the response for task status query
type TaskStatusResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
Task *TaskInfo `protobuf:"bytes,2,opt,name=task" json:"task,omitempty"`
ErrorCode string `protobuf:"bytes,3,opt,name=error_code,json=errorCode" json:"error_code,omitempty"`
ErrorMessage string `protobuf:"bytes,4,opt,name=error_message,json=errorMessage" json:"error_message,omitempty"`
}
func (m *TaskStatusResponse) Reset() { *m = TaskStatusResponse{} }
func (m *TaskStatusResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*TaskStatusResponse) ProtoMessage() {}
// TaskInfo represents task information
type TaskInfo struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
Type string `protobuf:"bytes,2,opt,name=type" json:"type,omitempty"`
Status TaskStatus `protobuf:"varint,3,opt,name=status,enum=a2a.TaskStatus" json:"status,omitempty"`
Progress int32 `protobuf:"varint,4,opt,name=progress" json:"progress,omitempty"`
Message string `protobuf:"bytes,5,opt,name=message" json:"message,omitempty"`
CreatedAt *timestamp.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt" json:"created_at,omitempty"`
UpdatedAt *timestamp.Timestamp `protobuf:"bytes,7,opt,name=updated_at,json=updatedAt" json:"updated_at,omitempty"`
CompletedAt *timestamp.Timestamp `protobuf:"bytes,8,opt,name=completed_at,json=completedAt" json:"completed_at,omitempty"`
ETA *timestamp.Timestamp `protobuf:"bytes,9,opt,name=e_t_a,json=eta" json:"eta,omitempty"`
Result *anypb.Any `protobuf:"bytes,10,opt,name=result" json:"result,omitempty"`
Error *TaskError `protobuf:"bytes,11,opt,name=error" json:"error,omitempty"`
ExternalTaskId string `protobuf:"bytes,12,opt,name=external_task_id,json=externalTaskId" json:"external_task_id,omitempty"`
}
func (m *TaskInfo) Reset() { *m = TaskInfo{} }
func (m *TaskInfo) String() string { return fmt.Sprintf("%+v", m) }
func (*TaskInfo) ProtoMessage() {}
// TaskError represents a task error
type TaskError struct {
Code string `protobuf:"bytes,1,opt,name=code" json:"code,omitempty"`
Message string `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"`
}
func (m *TaskError) Reset() { *m = TaskError{} }
func (m *TaskError) String() string { return fmt.Sprintf("%+v", m) }
func (*TaskError) ProtoMessage() {}
// ListTasksRequest is the request for listing tasks
type ListTasksRequest struct {
Status string `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"`
Limit int32 `protobuf:"varint,2,opt,name=limit" json:"limit,omitempty"`
Offset int32 `protobuf:"varint,3,opt,name=offset" json:"offset,omitempty"`
}
func (m *ListTasksRequest) Reset() { *m = ListTasksRequest{} }
func (m *ListTasksRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*ListTasksRequest) ProtoMessage() {}
// ListTasksResponse is the response for listing tasks
type ListTasksResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
Tasks []*TaskInfo `protobuf:"bytes,2,rep,name=tasks" json:"tasks,omitempty"`
Total int32 `protobuf:"varint,3,opt,name=total" json:"total,omitempty"`
Limit int32 `protobuf:"varint,4,opt,name=limit" json:"limit,omitempty"`
Offset int32 `protobuf:"varint,5,opt,name=offset" json:"offset,omitempty"`
HasMore bool `protobuf:"varint,6,opt,name=has_more,json=hasMore" json:"has_more,omitempty"`
}
func (m *ListTasksResponse) Reset() { *m = ListTasksResponse{} }
func (m *ListTasksResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*ListTasksResponse) ProtoMessage() {}
// CancelTaskRequest is the request for cancelling a task
type CancelTaskRequest struct {
TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId" json:"task_id,omitempty"`
}
func (m *CancelTaskRequest) Reset() { *m = CancelTaskRequest{} }
func (m *CancelTaskRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*CancelTaskRequest) ProtoMessage() {}
// CancelTaskResponse is the response for cancelling a task
type CancelTaskResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
TaskId string `protobuf:"bytes,2,opt,name=task_id,json=taskId" json:"task_id,omitempty"`
Status TaskStatus `protobuf:"varint,3,opt,name=status,enum=a2a.TaskStatus" json:"status,omitempty"`
Message string `protobuf:"bytes,4,opt,name=message" json:"message,omitempty"`
}
func (m *CancelTaskResponse) Reset() { *m = CancelTaskResponse{} }
func (m *CancelTaskResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*CancelTaskResponse) ProtoMessage() {}
// TaskStatusStreamRequest is the request for streaming task status
type TaskStatusStreamRequest struct {
TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId" json:"task_id,omitempty"`
Subscribe bool `protobuf:"varint,2,opt,name=subscribe" json:"subscribe,omitempty"`
}
func (m *TaskStatusStreamRequest) Reset() { *m = TaskStatusStreamRequest{} }
func (m *TaskStatusStreamRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*TaskStatusStreamRequest) ProtoMessage() {}
// =============================================================================
// Synapse Message Service Messages
// =============================================================================
// SendMessageRequest is the request for sending an Synapse message
type SendMessageRequest struct {
Message *A2AMessage `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"`
}
func (m *SendMessageRequest) Reset() { *m = SendMessageRequest{} }
func (m *SendMessageRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*SendMessageRequest) ProtoMessage() {}
// SendMessageResponse is the response for sending an Synapse message
type SendMessageResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
MessageId string `protobuf:"bytes,2,opt,name=message_id,json=messageId" json:"message_id,omitempty"`
Error string `protobuf:"bytes,3,opt,name=error" json:"error,omitempty"`
}
func (m *SendMessageResponse) Reset() { *m = SendMessageResponse{} }
func (m *SendMessageResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*SendMessageResponse) ProtoMessage() {}
// =============================================================================
// Capabilities Service Messages
// =============================================================================
// CapabilitiesResponse is the response for capabilities query
type CapabilitiesResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
Capabilities *AgentCapabilities `protobuf:"bytes,2,opt,name=capabilities" json:"capabilities,omitempty"`
Error string `protobuf:"bytes,3,opt,name=error" json:"error,omitempty"`
}
func (m *CapabilitiesResponse) Reset() { *m = CapabilitiesResponse{} }
func (m *CapabilitiesResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*CapabilitiesResponse) ProtoMessage() {}
// AgentCapabilities represents agent capabilities
type AgentCapabilities struct {
AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId" json:"agent_id,omitempty"`
AgentName string `protobuf:"bytes,2,opt,name=agent_name,json=agentName" json:"agent_name,omitempty"`
SystemId string `protobuf:"bytes,3,opt,name=system_id,json=systemId" json:"system_id,omitempty"`
ProtocolVersion string `protobuf:"bytes,4,opt,name=protocol_version,json=protocolVersion" json:"protocol_version,omitempty"`
SupportedTaskTypes []*SupportedTaskType `protobuf:"bytes,5,rep,name=supported_task_types,json=supportedTaskTypes" json:"supported_task_types,omitempty"`
SupportedContentTypes []string `protobuf:"bytes,6,rep,name=supported_content_types,json=supportedContentTypes" json:"supported_content_types,omitempty"`
SupportedTransportModes []TransportMode `protobuf:"varint,7,rep,name=supported_transport_modes,json=supportedTransportModes" json:"supported_transport_modes,omitempty"`
MaxFileSize int64 `protobuf:"varint,8,opt,name=max_file_size,json=maxFileSize" json:"max_file_size,omitempty"`
MaxConcurrentTasks int32 `protobuf:"varint,9,opt,name=max_concurrent_tasks,json=maxConcurrentTasks" json:"max_concurrent_tasks,omitempty"`
MaxTaskDurationSeconds int32 `protobuf:"varint,10,opt,name=max_task_duration_seconds,json=maxTaskDurationSeconds" json:"max_task_duration_seconds,omitempty"`
SupportProgressUpdates bool `protobuf:"varint,11,opt,name=support_progress_updates,json=supportProgressUpdates" json:"support_progress_updates,omitempty"`
SupportStreaming bool `protobuf:"varint,12,opt,name=support_streaming,json=supportStreaming" json:"support_streaming,omitempty"`
SupportHil bool `protobuf:"varint,13,opt,name=support_hil,json=supportHil" json:"support_hil,omitempty"`
SupportGroups bool `protobuf:"varint,14,opt,name=support_groups,json=supportGroups" json:"support_groups,omitempty"`
}
func (m *AgentCapabilities) Reset() { *m = AgentCapabilities{} }
func (m *AgentCapabilities) String() string { return fmt.Sprintf("%+v", m) }
func (*AgentCapabilities) ProtoMessage() {}
// SupportedTaskType represents a supported task type
type SupportedTaskType struct {
Type string `protobuf:"bytes,1,opt,name=type" json:"type,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
Description string `protobuf:"bytes,3,opt,name=description" json:"description,omitempty"`
EstimatedTime string `protobuf:"bytes,4,opt,name=estimated_time,json=estimatedTime" json:"estimated_time,omitempty"`
InputSchema string `protobuf:"bytes,5,opt,name=input_schema,json=inputSchema" json:"input_schema,omitempty"`
OutputSchema string `protobuf:"bytes,6,opt,name=output_schema,json=outputSchema" json:"output_schema,omitempty"`
Capabilities []string `protobuf:"bytes,7,rep,name=capabilities" json:"capabilities,omitempty"`
}
func (m *SupportedTaskType) Reset() { *m = SupportedTaskType{} }
func (m *SupportedTaskType) String() string { return fmt.Sprintf("%+v", m) }
func (*SupportedTaskType) ProtoMessage() {}
// QueryAgentRequest is the request for querying an agent
type QueryAgentRequest struct {
AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId" json:"agent_id,omitempty"`
}
func (m *QueryAgentRequest) Reset() { *m = QueryAgentRequest{} }
func (m *QueryAgentRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*QueryAgentRequest) ProtoMessage() {}
// ListAgentsResponse is the response for listing agents
type ListAgentsResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
Agents []*AgentInfo `protobuf:"bytes,2,rep,name=agents" json:"agents,omitempty"`
Total int32 `protobuf:"varint,3,opt,name=total" json:"total,omitempty"`
}
func (m *ListAgentsResponse) Reset() { *m = ListAgentsResponse{} }
func (m *ListAgentsResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*ListAgentsResponse) ProtoMessage() {}
// AgentInfo represents agent information
type AgentInfo struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
AgentId string `protobuf:"bytes,2,opt,name=agent_id,json=agentId" json:"agent_id,omitempty"`
AgentName string `protobuf:"bytes,3,opt,name=agent_name,json=agentName" json:"agent_name,omitempty"`
SystemId string `protobuf:"bytes,4,opt,name=system_id,json=systemId" json:"system_id,omitempty"`
Endpoint string `protobuf:"bytes,5,opt,name=endpoint" json:"endpoint,omitempty"`
IsActive bool `protobuf:"varint,6,opt,name=is_active,json=isActive" json:"is_active,omitempty"`
TrustLevel int32 `protobuf:"varint,7,opt,name=trust_level,json=trustLevel" json:"trust_level,omitempty"`
}
func (m *AgentInfo) Reset() { *m = AgentInfo{} }
func (m *AgentInfo) String() string { return fmt.Sprintf("%+v", m) }
func (*AgentInfo) ProtoMessage() {}
// =============================================================================
// Group Service Messages
// =============================================================================
// CreateGroupRequest is the request for creating a group
type CreateGroupRequest struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Description string `protobuf:"bytes,2,opt,name=description" json:"description,omitempty"`
CoordinatorId string `protobuf:"bytes,3,opt,name=coordinator_id,json=coordinatorId" json:"coordinator_id,omitempty"`
InitialMembers []string `protobuf:"bytes,4,rep,name=initial_members,json=initialMembers" json:"initial_members,omitempty"`
}
func (m *CreateGroupRequest) Reset() { *m = CreateGroupRequest{} }
func (m *CreateGroupRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*CreateGroupRequest) ProtoMessage() {}
// CreateGroupResponse is the response for creating a group
type CreateGroupResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
GroupId string `protobuf:"bytes,2,opt,name=group_id,json=groupId" json:"group_id,omitempty"`
Group *GroupInfo `protobuf:"bytes,3,opt,name=group" json:"group,omitempty"`
Error string `protobuf:"bytes,4,opt,name=error" json:"error,omitempty"`
}
func (m *CreateGroupResponse) Reset() { *m = CreateGroupResponse{} }
func (m *CreateGroupResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*CreateGroupResponse) ProtoMessage() {}
// GroupInfo represents group information
type GroupInfo struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
Description string `protobuf:"bytes,3,opt,name=description" json:"description,omitempty"`
CoordinatorId string `protobuf:"bytes,4,opt,name=coordinator_id,json=coordinatorId" json:"coordinator_id,omitempty"`
Status string `protobuf:"bytes,5,opt,name=status" json:"status,omitempty"`
CreatedAt *timestamp.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt" json:"created_at,omitempty"`
}
func (m *GroupInfo) Reset() { *m = GroupInfo{} }
func (m *GroupInfo) String() string { return fmt.Sprintf("%+v", m) }
func (*GroupInfo) ProtoMessage() {}
// JoinGroupRequest is the request for joining a group
type JoinGroupRequest struct {
GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"`
Agent *AgentEndpoint `protobuf:"bytes,2,opt,name=agent" json:"agent,omitempty"`
}
func (m *JoinGroupRequest) Reset() { *m = JoinGroupRequest{} }
func (m *JoinGroupRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*JoinGroupRequest) ProtoMessage() {}
// JoinGroupResponse is the response for joining a group
type JoinGroupResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
}
func (m *JoinGroupResponse) Reset() { *m = JoinGroupResponse{} }
func (m *JoinGroupResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*JoinGroupResponse) ProtoMessage() {}
// LeaveGroupRequest is the request for leaving a group
type LeaveGroupRequest struct {
GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"`
AgentId string `protobuf:"bytes,2,opt,name=agent_id,json=agentId" json:"agent_id,omitempty"`
}
func (m *LeaveGroupRequest) Reset() { *m = LeaveGroupRequest{} }
func (m *LeaveGroupRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*LeaveGroupRequest) ProtoMessage() {}
// LeaveGroupResponse is the response for leaving a group
type LeaveGroupResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
}
func (m *LeaveGroupResponse) Reset() { *m = LeaveGroupResponse{} }
func (m *LeaveGroupResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*LeaveGroupResponse) ProtoMessage() {}
// DisbandGroupRequest is the request for disbanding a group
type DisbandGroupRequest struct {
GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"`
}
func (m *DisbandGroupRequest) Reset() { *m = DisbandGroupRequest{} }
func (m *DisbandGroupRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*DisbandGroupRequest) ProtoMessage() {}
// DisbandGroupResponse is the response for disbanding a group
type DisbandGroupResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
}
func (m *DisbandGroupResponse) Reset() { *m = DisbandGroupResponse{} }
func (m *DisbandGroupResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*DisbandGroupResponse) ProtoMessage() {}
// ListGroupMembersRequest is the request for listing group members
type ListGroupMembersRequest struct {
GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"`
}
func (m *ListGroupMembersRequest) Reset() { *m = ListGroupMembersRequest{} }
func (m *ListGroupMembersRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*ListGroupMembersRequest) ProtoMessage() {}
// ListGroupMembersResponse is the response for listing group members
type ListGroupMembersResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
Members []*GroupMember `protobuf:"bytes,2,rep,name=members" json:"members,omitempty"`
}
func (m *ListGroupMembersResponse) Reset() { *m = ListGroupMembersResponse{} }
func (m *ListGroupMembersResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*ListGroupMembersResponse) ProtoMessage() {}
// GroupMember represents a group member
type GroupMember struct {
AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId" json:"agent_id,omitempty"`
AgentName string `protobuf:"bytes,2,opt,name=agent_name,json=agentName" json:"agent_name,omitempty"`
Role string `protobuf:"bytes,3,opt,name=role" json:"role,omitempty"`
Status string `protobuf:"bytes,4,opt,name=status" json:"status,omitempty"`
JoinedAt *timestamp.Timestamp `protobuf:"bytes,5,opt,name=joined_at,json=joinedAt" json:"joined_at,omitempty"`
}
func (m *GroupMember) Reset() { *m = GroupMember{} }
func (m *GroupMember) String() string { return fmt.Sprintf("%+v", m) }
func (*GroupMember) ProtoMessage() {}
// =============================================================================
// Group Voting Messages (NEW!)
// =============================================================================
// VoteRequest is the request for initiating a vote
type VoteRequest struct {
GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"`
InitiatorId string `protobuf:"bytes,2,opt,name=initiator_id,json=initiatorId" json:"initiator_id,omitempty"`
Question string `protobuf:"bytes,3,opt,name=question" json:"question,omitempty"`
Options []string `protobuf:"bytes,4,rep,name=options" json:"options,omitempty"`
Threshold float64 `protobuf:"fixed64,5,opt,name=threshold" json:"threshold,omitempty"`
TimeoutSeconds int32 `protobuf:"varint,6,opt,name=timeout_seconds,json=timeoutSeconds" json:"timeout_seconds,omitempty"`
}
func (m *VoteRequest) Reset() { *m = VoteRequest{} }
func (m *VoteRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*VoteRequest) ProtoMessage() {}
// VoteResponse is the response for initiating a vote
type VoteResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
VoteId string `protobuf:"bytes,2,opt,name=vote_id,json=voteId" json:"vote_id,omitempty"`
Error string `protobuf:"bytes,3,opt,name=error" json:"error,omitempty"`
}
func (m *VoteResponse) Reset() { *m = VoteResponse{} }
func (m *VoteResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*VoteResponse) ProtoMessage() {}
// SubmitVoteRequest is the request for submitting a vote
type SubmitVoteRequest struct {
VoteId string `protobuf:"bytes,1,opt,name=vote_id,json=voteId" json:"vote_id,omitempty"`
AgentId string `protobuf:"bytes,2,opt,name=agent_id,json=agentId" json:"agent_id,omitempty"`
Option string `protobuf:"bytes,3,opt,name=option" json:"option,omitempty"`
Reason string `protobuf:"bytes,4,opt,name=reason" json:"reason,omitempty"`
}
func (m *SubmitVoteRequest) Reset() { *m = SubmitVoteRequest{} }
func (m *SubmitVoteRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*SubmitVoteRequest) ProtoMessage() {}
// SubmitVoteResponse is the response for submitting a vote
type SubmitVoteResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
}
func (m *SubmitVoteResponse) Reset() { *m = SubmitVoteResponse{} }
func (m *SubmitVoteResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*SubmitVoteResponse) ProtoMessage() {}
// GetVoteStatusRequest is the request for getting vote status
type GetVoteStatusRequest struct {
VoteId string `protobuf:"bytes,1,opt,name=vote_id,json=voteId" json:"vote_id,omitempty"`
}
func (m *GetVoteStatusRequest) Reset() { *m = GetVoteStatusRequest{} }
func (m *GetVoteStatusRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*GetVoteStatusRequest) ProtoMessage() {}
// VoteStatusResponse is the response for vote status
type VoteStatusResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
VoteId string `protobuf:"bytes,2,opt,name=vote_id,json=voteId" json:"vote_id,omitempty"`
Status string `protobuf:"bytes,3,opt,name=status" json:"status,omitempty"` // open, decided, expired
Question string `protobuf:"bytes,4,opt,name=question" json:"question,omitempty"`
TotalVotes int32 `protobuf:"varint,5,opt,name=total_votes,json=totalVotes" json:"total_votes,omitempty"`
RequiredVotes int32 `protobuf:"varint,6,opt,name=required_votes,json=requiredVotes" json:"required_votes,omitempty"`
WinningOption string `protobuf:"bytes,7,opt,name=winning_option,json=winningOption" json:"winning_option,omitempty"`
Error string `protobuf:"bytes,8,opt,name=error" json:"error,omitempty"`
}
func (m *VoteStatusResponse) Reset() { *m = VoteStatusResponse{} }
func (m *VoteStatusResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*VoteStatusResponse) ProtoMessage() {}
// =============================================================================
// Task Split Messages (NEW!)
// =============================================================================
// SplitTaskRequest is the request for splitting a task
type SplitTaskRequest struct {
GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"`
TaskId string `protobuf:"bytes,2,opt,name=task_id,json=taskId" json:"task_id,omitempty"`
TaskType string `protobuf:"bytes,3,opt,name=task_type,json=taskType" json:"task_type,omitempty"`
Requirements string `protobuf:"bytes,4,opt,name=requirements" json:"requirements,omitempty"`
Strategy SplitStrategy `protobuf:"varint,5,opt,name=strategy,enum=a2a.SplitStrategy" json:"strategy,omitempty"`
}
func (m *SplitTaskRequest) Reset() { *m = SplitTaskRequest{} }
func (m *SplitTaskRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*SplitTaskRequest) ProtoMessage() {}
type SplitStrategy int32
const (
SplitStrategy_SPLIT_BY_TYPE SplitStrategy = 0
SplitStrategy_SPLIT_BY_DATA SplitStrategy = 1
SplitStrategy_SPLIT_BY_REGION SplitStrategy = 2
SplitStrategy_SPLIT_BY_ROUNDROBIN SplitStrategy = 3
)
// SplitTaskResponse is the response for splitting a task
type SplitTaskResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
GroupId string `protobuf:"bytes,2,opt,name=group_id,json=groupId" json:"group_id,omitempty"`
ParentTaskId string `protobuf:"bytes,3,opt,name=parent_task_id,json=parentTaskId" json:"parent_task_id,omitempty"`
Subtasks []*SubtaskInfo `protobuf:"bytes,4,rep,name=subtasks" json:"subtasks,omitempty"`
Error string `protobuf:"bytes,5,opt,name=error" json:"error,omitempty"`
}
func (m *SplitTaskResponse) Reset() { *m = SplitTaskResponse{} }
func (m *SplitTaskResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*SplitTaskResponse) ProtoMessage() {}
type SubtaskInfo struct {
SubtaskId string `protobuf:"bytes,1,opt,name=subtask_id,json=subtaskId" json:"subtask_id,omitempty"`
Assignee string `protobuf:"bytes,2,opt,name=assignee" json:"assignee,omitempty"`
Scope string `protobuf:"bytes,3,opt,name=scope" json:"scope,omitempty"`
Dependencies []string `protobuf:"bytes,4,rep,name=dependencies" json:"dependencies,omitempty"`
}
func (m *SubtaskInfo) Reset() { *m = SubtaskInfo{} }
func (m *SubtaskInfo) String() string { return fmt.Sprintf("%+v", m) }
func (*SubtaskInfo) ProtoMessage() {}
// MergeResultsRequest is the request for merging results
type MergeResultsRequest struct {
GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId" json:"group_id,omitempty"`
ParentTaskId string `protobuf:"bytes,2,opt,name=parent_task_id,json=parentTaskId" json:"parent_task_id,omitempty"`
Strategy MergeStrategy `protobuf:"varint,3,opt,name=strategy,enum=a2a.MergeStrategy" json:"strategy,omitempty"`
}
func (m *MergeResultsRequest) Reset() { *m = MergeResultsRequest{} }
func (m *MergeResultsRequest) String() string { return fmt.Sprintf("%+v", m) }
func (*MergeResultsRequest) ProtoMessage() {}
type MergeStrategy int32
const (
MergeStrategy_MERGE_ALL MergeStrategy = 0
MergeStrategy_MERGE_BEST MergeStrategy = 1
MergeStrategy_MERGE_CONSENSUS MergeStrategy = 2
MergeStrategy_MERGE_AGGREGATE MergeStrategy = 3
)
// MergeResultsResponse is the response for merging results
type MergeResultsResponse struct {
Success bool `protobuf:"varint,1,opt,name=success" json:"success,omitempty"`
ParentTaskId string `protobuf:"bytes,2,opt,name=parent_task_id,json=parentTaskId" json:"parent_task_id,omitempty"`
MergedResult *anypb.Any `protobuf:"bytes,3,opt,name=merged_result,json=mergedResult" json:"merged_result,omitempty"`
Summary string `protobuf:"bytes,4,opt,name=summary" json:"summary,omitempty"`
Error string `protobuf:"bytes,5,opt,name=error" json:"error,omitempty"`
}
func (m *MergeResultsResponse) Reset() { *m = MergeResultsResponse{} }
func (m *MergeResultsResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*MergeResultsResponse) ProtoMessage() {}
// =============================================================================
// Health Service Messages
// =============================================================================
// HealthCheckResponse is the response for health check
type HealthCheckResponse struct {
Status string `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"`
Version string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"`
UptimeSeconds int64 `protobuf:"varint,3,opt,name=uptime_seconds,json=uptimeSeconds" json:"uptime_seconds,omitempty"`
ActiveTasks int32 `protobuf:"varint,4,opt,name=active_tasks,json=activeTasks" json:"active_tasks,omitempty"`
}
func (m *HealthCheckResponse) Reset() { *m = HealthCheckResponse{} }
func (m *HealthCheckResponse) String() string { return fmt.Sprintf("%+v", m) }
func (*HealthCheckResponse) ProtoMessage() {}
// =============================================================================
// Unimplemented types for proto compatibility
// =============================================================================
// Ensure proto compatibility
var _ proto.Message = (*AgentEndpoint)(nil)
var _ proto.Message = (*A2AMessage)(nil)
var _ proto.Message = (*SubmitTaskRequest)(nil)
var _ proto.Message = (*SubmitTaskResponse)(nil)
var _ proto.Message = (*GetTaskRequest)(nil)
var _ proto.Message = (*TaskStatusResponse)(nil)
var _ proto.Message = (*ListTasksRequest)(nil)
var _ proto.Message = (*ListTasksResponse)(nil)
var _ proto.Message = (*CancelTaskRequest)(nil)
var _ proto.Message = (*CancelTaskResponse)(nil)
var _ proto.Message = (*SendMessageRequest)(nil)
var _ proto.Message = (*SendMessageResponse)(nil)
var _ proto.Message = (*CapabilitiesResponse)(nil)
var _ proto.Message = (*CreateGroupRequest)(nil)
var _ proto.Message = (*CreateGroupResponse)(nil)
var _ proto.Message = (*JoinGroupRequest)(nil)
var _ proto.Message = (*JoinGroupResponse)(nil)
var _ proto.Message = (*LeaveGroupRequest)(nil)
var _ proto.Message = (*LeaveGroupResponse)(nil)
var _ proto.Message = (*DisbandGroupRequest)(nil)
var _ proto.Message = (*DisbandGroupResponse)(nil)
var _ proto.Message = (*ListGroupMembersRequest)(nil)
var _ proto.Message = (*ListGroupMembersResponse)(nil)
var _ proto.Message = (*HealthCheckResponse)(nil)

1145
grpcapi/synapse_grpc.pb.go Normal file

File diff suppressed because it is too large Load Diff

683
hil/protocol.go Normal file
View File

@@ -0,0 +1,683 @@
package hil
import (
"context"
"encoding/json"
"fmt"
"log"
"sync"
"time"
"synapse/types"
)
// =============================================================================
// HiL Manager
// =============================================================================
// DecisionMaker is the interface for human decision makers
type DecisionMaker interface {
// RequestDecision sends a decision request to a human
RequestDecision(ctx context.Context, request *HILRequest) (*HILResponse, error)
// GetUserRole returns the user's role for authorization
GetUserRole(userID string) string
}
// HILConfig holds HiL configuration
type HILConfig struct {
// Level 1: Agent suggests + human confirms (24h default)
Level1Timeout time.Duration
// Level 2: Must human decide (12h default, 2h urgent)
Level2Timeout time.Duration
Level2UrgentTimeout time.Duration
// Level 3: Expert/management decision (on-demand)
Level3Timeout time.Duration
// Default timeouts
DefaultTimeout time.Duration
}
func DefaultHILConfig() *HILConfig {
return &HILConfig{
Level1Timeout: 24 * time.Hour,
Level2Timeout: 12 * time.Hour,
Level2UrgentTimeout: 2 * time.Hour,
Level3Timeout: 72 * time.Hour,
DefaultTimeout: 24 * time.Hour,
}
}
// =============================================================================
// HILRequest represents a human-in-the-loop request
// =============================================================================
type HILRequest struct {
ID string
TaskID string
EscalationType string // P2P, GROUP, INTER_GROUP
EscalationLevel int // 1, 2, 3
Reason HILReason
Context HILContext
Options []HILOption
SuggestedValue any
Deadline time.Time
Status HILStatus
CreatedAt time.Time
UpdatedAt time.Time
DecidedAt *time.Time
Response *HILResponse
}
// HILReason describes why human intervention is needed
type HILReason struct {
Code string
Message string
}
// HILContext provides context for the decision
type HILContext struct {
TaskSummary string
CurrentProgress int
BlockedReason string
RelevantHistory []HILHistoryItem
}
// HILHistoryItem is a historical item for context
type HILHistoryItem struct {
Timestamp time.Time
Event string
Details string
}
// HILOption represents a decision option
type HILOption struct {
Field string
Value any
Label string
ProsCons string
Confidence int // 0-100
}
// HILStatus represents the status of a HiL request
type HILStatus string
const (
HILStatusPending HILStatus = "pending"
HILStatusWaiting HILStatus = "waiting"
HILStatusDeciding HILStatus = "deciding"
HILStatusApproved HILStatus = "approved"
HILStatusRejected HILStatus = "rejected"
HILStatusTimeout HILStatus = "timeout"
HILStatusEscalated HILStatus = "escalated"
)
// HILResponse represents a human's decision
type HILResponse struct {
DecisionID string
UserID string
UserName string
UserRole string
Action string // approve, reject, modify, select
Decision HILDecision
Notes string
Timestamp time.Time
}
// HILDecision contains the actual decision
type HILDecision struct {
ApprovedFields map[string]interface{}
ModifiedFields map[string]interface{}
SelectedOption any
RejectionReason string
}
// =============================================================================
// HILProtocol
// =============================================================================
type HILProtocol struct {
mu sync.RWMutex
// Agent identity
agentEndpoint types.AgentEndpoint
// HIL requests
requests map[string]*HILRequest
// Decision maker
decisionMaker DecisionMaker
// Configuration
config *HILConfig
// Callbacks
callbacks map[string]chan *HILResponse
}
// NewHILProtocol creates a new HiL protocol handler
func NewHILProtocol(agentEndpoint types.AgentEndpoint, config *HILConfig) *HILProtocol {
if config == nil {
config = DefaultHILConfig()
}
return &HILProtocol{
agentEndpoint: agentEndpoint,
requests: make(map[string]*HILRequest),
config: config,
callbacks: make(map[string]chan *HILResponse),
}
}
// SetDecisionMaker sets the decision maker
func (h *HILProtocol) SetDecisionMaker(m DecisionMaker) {
h.decisionMaker = m
}
// =============================================================================
// P2P HiL Methods
// =============================================================================
// CreateP2PHILRequest creates a HiL request for P2P task
func (h *HILProtocol) CreateP2PHILRequest(
taskID string,
reasonCode, reasonMessage string,
context HILContext,
options []HILOption,
suggestedValue any,
escalationLevel int,
) *HILRequest {
h.mu.Lock()
defer h.mu.Unlock()
req := &HILRequest{
ID: fmt.Sprintf("hil-%d", time.Now().UnixNano()),
TaskID: taskID,
EscalationType: "p2p",
EscalationLevel: escalationLevel,
Reason: HILReason{
Code: reasonCode,
Message: reasonMessage,
},
Context: context,
Options: options,
SuggestedValue: suggestedValue,
Status: HILStatusPending,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// Set deadline based on escalation level
req.Deadline = h.getDeadline(escalationLevel)
h.requests[req.ID] = req
log.Printf("[HiL] P2P HiL request created: %s (level: %d, task: %s)", req.ID, escalationLevel, taskID)
return req
}
// CreateP2PHumanDecisionMessage creates a HUMAN_DECISION message payload
func (h *HILProtocol) CreateP2PHumanDecisionMessage(req *HILRequest, response *HILResponse) *types.UnifiedSynapseMessage {
payload := types.HumanDecisionPayload{}
payload.TaskID = req.TaskID
payload.DecisionID = response.DecisionID
payload.ReplyTo = req.ID
payload.DecidedBy.UserID = response.UserID
payload.DecidedBy.UserName = response.UserName
payload.DecidedBy.Role = response.UserRole
payload.Decision.Action = response.Action
payload.Notes = response.Notes
payload.Timestamp = types.NowTimestamp()
switch response.Action {
case "approve":
if response.Decision.ApprovedFields != nil {
payload.Decision.ApprovedFields = response.Decision.ApprovedFields
}
case "reject":
payload.Decision.RejectionReason = response.Decision.RejectionReason
case "select":
payload.Decision.SelectedOption = response.Decision.SelectedOption
case "modify":
payload.Decision.ModifiedFields = response.Decision.ModifiedFields
}
payloadBytes, _ := json.Marshal(payload)
return &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PHumanDecision),
Sender: h.agentEndpoint,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
}
// =============================================================================
// Group HiL Methods
// =============================================================================
// CreateGroupConsensusEscalation creates a consensus escalation request
func (h *HILProtocol) CreateGroupConsensusEscalation(
groupID string,
voteID string,
question string,
votes []struct {
VoterID string
VoterRole string
Option string
Reason string
},
consensusStatus struct {
RequiredThreshold float64
ActualThreshold float64
LeadingOption string
OptionsTied bool
},
escalateReason struct {
Code string
Message string
},
escalationLevel int,
context struct {
TaskID string
OriginalTaskRequirements map[string]interface{}
AgentRecommendations []struct {
AgentID string
SuggestedOption string
}
},
) *types.UnifiedSynapseMessage {
decisionOptions := make([]struct {
Option string
SupportCount int
SupportAgents []string
}, 0)
// Count votes per option
optionCounts := make(map[string]int)
optionAgents := make(map[string][]string)
for _, v := range votes {
optionCounts[v.Option]++
optionAgents[v.Option] = append(optionAgents[v.Option], v.VoterID)
}
for opt, count := range optionCounts {
decisionOptions = append(decisionOptions, struct {
Option string
SupportCount int
SupportAgents []string
}{
Option: opt,
SupportCount: count,
SupportAgents: optionAgents[opt],
})
}
payload := types.GroupConsensusEscalatePayload{}
payload.VoteID = voteID
payload.EscalationID = fmt.Sprintf("hil-%d", time.Now().UnixNano())
payload.Question = question
// Copy votes
payload.Votes = make([]struct {
VoterID string `json:"voter_id"`
VoterRole string `json:"voter_role"`
Option string `json:"option"`
Reason string `json:"reason,omitempty"`
}, len(votes))
for i, v := range votes {
payload.Votes[i].VoterID = v.VoterID
payload.Votes[i].VoterRole = v.VoterRole
payload.Votes[i].Option = v.Option
payload.Votes[i].Reason = v.Reason
}
payload.ConsensusStatus.RequiredThreshold = consensusStatus.RequiredThreshold
payload.ConsensusStatus.ActualThreshold = consensusStatus.ActualThreshold
payload.ConsensusStatus.LeadingOption = consensusStatus.LeadingOption
payload.ConsensusStatus.OptionsTied = consensusStatus.OptionsTied
payload.EscalateReason.Code = escalateReason.Code
payload.EscalateReason.Message = escalateReason.Message
payload.EscalationLevel = escalationLevel
// Copy decision options
payload.DecisionOptions = make([]struct {
Option string `json:"option"`
SupportCount int `json:"support_count"`
SupportAgents []string `json:"support_agents"`
}, len(decisionOptions))
for i, d := range decisionOptions {
payload.DecisionOptions[i].Option = d.Option
payload.DecisionOptions[i].SupportCount = d.SupportCount
payload.DecisionOptions[i].SupportAgents = d.SupportAgents
}
payload.Deadline = types.Timestamp(h.getDeadline(escalationLevel).Format(time.RFC3339))
payload.Context.TaskID = context.TaskID
payload.Context.OriginalTaskRequirements = context.OriginalTaskRequirements
// Copy agent recommendations
payload.Context.AgentRecommendations = make([]struct {
AgentID string `json:"agent_id"`
SuggestedOption string `json:"suggested_option"`
}, len(context.AgentRecommendations))
for i, r := range context.AgentRecommendations {
payload.Context.AgentRecommendations[i].AgentID = r.AgentID
payload.Context.AgentRecommendations[i].SuggestedOption = r.SuggestedOption
}
payloadBytes, _ := json.Marshal(payload)
return &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.GroupConsensusEscalate),
Sender: h.agentEndpoint,
GroupID: groupID,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
}
// CreateGroupSubtaskEscalation creates a subtask escalation request
func (h *HILProtocol) CreateGroupSubtaskEscalation(
groupID string,
originalTask struct {
ID string
Type string
Description string
},
subtask struct {
ID string
Description string
AssignedTo string
BlockedAt string
BlockedReason struct {
Code string
Message string
}
AttemptedSolutions []string
},
requiredDecision struct {
FieldsNeeded []struct {
Field string
CurrentValue any
SuggestedValue any
Alternatives []any
ValidationRules string
}
DecisionType string
AffectsOtherSubtasks bool
RollbackPossible bool
},
escalationLevel int,
) *types.UnifiedSynapseMessage {
payload := types.GroupSubtaskEscalatePayload{}
payload.OriginalTask.ID = originalTask.ID
payload.OriginalTask.Type = originalTask.Type
payload.OriginalTask.Description = originalTask.Description
payload.Subtask.ID = subtask.ID
payload.Subtask.Description = subtask.Description
payload.Subtask.AssignedTo = subtask.AssignedTo
payload.Subtask.BlockedAt = subtask.BlockedAt
payload.Subtask.BlockedReason.Code = subtask.BlockedReason.Code
payload.Subtask.BlockedReason.Message = subtask.BlockedReason.Message
payload.Subtask.AttemptedSolutions = subtask.AttemptedSolutions
// Copy fields needed
payload.RequiredDecision.FieldsNeeded = make([]struct {
Field string `json:"field"`
CurrentValue any `json:"current_value,omitempty"`
SuggestedValue any `json:"suggested_value,omitempty"`
Alternatives []any `json:"alternatives,omitempty"`
ValidationRules string `json:"validation_rules,omitempty"`
}, len(requiredDecision.FieldsNeeded))
for i, f := range requiredDecision.FieldsNeeded {
payload.RequiredDecision.FieldsNeeded[i].Field = f.Field
payload.RequiredDecision.FieldsNeeded[i].CurrentValue = f.CurrentValue
payload.RequiredDecision.FieldsNeeded[i].SuggestedValue = f.SuggestedValue
payload.RequiredDecision.FieldsNeeded[i].Alternatives = f.Alternatives
payload.RequiredDecision.FieldsNeeded[i].ValidationRules = f.ValidationRules
}
payload.RequiredDecision.DecisionType = requiredDecision.DecisionType
payload.RequiredDecision.AffectsOtherSubtasks = requiredDecision.AffectsOtherSubtasks
payload.RequiredDecision.RollbackPossible = requiredDecision.RollbackPossible
payload.EscalationLevel = escalationLevel
deadline := types.Timestamp(h.getDeadline(escalationLevel).Format(time.RFC3339))
payload.Deadline = &deadline
payloadBytes, _ := json.Marshal(payload)
return &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.GroupSubtaskEscalate),
Sender: h.agentEndpoint,
GroupID: groupID,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
}
// =============================================================================
// HiL Status Management
// =============================================================================
// GetRequest returns a HiL request by ID
func (h *HILProtocol) GetRequest(id string) (*HILRequest, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
req, ok := h.requests[id]
return req, ok
}
// ListRequests returns all HiL requests
func (h *HILProtocol) ListRequests() []*HILRequest {
h.mu.RLock()
defer h.mu.RUnlock()
result := make([]*HILRequest, 0, len(h.requests))
for _, req := range h.requests {
result = append(result, req)
}
return result
}
// ListPendingRequests returns all pending HiL requests
func (h *HILProtocol) ListPendingRequests() []*HILRequest {
h.mu.RLock()
defer h.mu.RUnlock()
result := make([]*HILRequest, 0)
for _, req := range h.requests {
if req.Status == HILStatusPending || req.Status == HILStatusWaiting {
result = append(result, req)
}
}
return result
}
// UpdateRequestStatus updates the status of a HiL request
func (h *HILProtocol) UpdateRequestStatus(id string, status HILStatus) error {
h.mu.Lock()
defer h.mu.Unlock()
req, ok := h.requests[id]
if !ok {
return fmt.Errorf("HiL request not found: %s", id)
}
req.Status = status
req.UpdatedAt = time.Now()
if status == HILStatusApproved || status == HILStatusRejected {
now := time.Now()
req.DecidedAt = &now
}
return nil
}
// RecordResponse records a human decision response
func (h *HILProtocol) RecordResponse(id string, response *HILResponse) error {
h.mu.Lock()
defer h.mu.Unlock()
req, ok := h.requests[id]
if !ok {
return fmt.Errorf("HiL request not found: %s", id)
}
req.Response = response
req.Status = HILStatusApproved // or rejected based on action
req.UpdatedAt = time.Now()
now := time.Now()
req.DecidedAt = &now
log.Printf("[HiL] Response recorded for %s: %s by %s", id, response.Action, response.UserName)
return nil
}
// =============================================================================
// Timeout Management
// =============================================================================
func (h *HILProtocol) getDeadline(escalationLevel int) time.Time {
switch escalationLevel {
case 1:
return time.Now().Add(h.config.Level1Timeout)
case 2:
return time.Now().Add(h.config.Level2Timeout)
case 3:
return time.Now().Add(h.config.Level3Timeout)
default:
return time.Now().Add(h.config.DefaultTimeout)
}
}
// CheckTimeouts checks for and handles timed out requests
func (h *HILProtocol) CheckTimeouts() []*HILRequest {
h.mu.Lock()
defer h.mu.Unlock()
timedOut := make([]*HILRequest, 0)
now := time.Now()
for _, req := range h.requests {
if (req.Status == HILStatusPending || req.Status == HILStatusWaiting) && now.After(req.Deadline) {
req.Status = HILStatusTimeout
req.UpdatedAt = now
timedOut = append(timedOut, req)
log.Printf("[HiL] Request timed out: %s (task: %s)", req.ID, req.TaskID)
}
}
return timedOut
}
// =============================================================================
// Human Decision Panel (for frontend integration)
// =============================================================================
// GetDecisionPanelData returns data needed to render a decision panel
func (h *HILProtocol) GetDecisionPanelData(req *HILRequest) map[string]interface{} {
return map[string]interface{}{
"request_id": req.ID,
"task_id": req.TaskID,
"escalation_type": req.EscalationType,
"escalation_level": req.EscalationLevel,
"reason": req.Reason,
"context": req.Context,
"options": req.Options,
"suggested_value": req.SuggestedValue,
"deadline": req.Deadline.Format(time.RFC3339),
"status": req.Status,
"created_at": req.CreatedAt.Format(time.RFC3339),
}
}
// CreateHumanDecisionPayload creates a HUMAN_DECISION payload
func (h *HILProtocol) CreateHumanDecisionPayload(
taskID string,
replyTo string,
user struct {
UserID string
UserName string
Role string
},
decision struct {
Action string
ApprovedFields map[string]interface{}
ModifiedFields map[string]interface{}
SelectedOption any
RejectionReason string
},
notes string,
) *types.HumanDecisionPayload {
payload := &types.HumanDecisionPayload{}
payload.TaskID = taskID
payload.DecisionID = fmt.Sprintf("decision-%d", time.Now().UnixNano())
payload.ReplyTo = replyTo
payload.DecidedBy.UserID = user.UserID
payload.DecidedBy.UserName = user.UserName
payload.DecidedBy.Role = user.Role
payload.Decision.Action = decision.Action
payload.Notes = notes
payload.Timestamp = types.NowTimestamp()
switch decision.Action {
case "approve":
payload.Decision.ApprovedFields = decision.ApprovedFields
case "reject":
payload.Decision.RejectionReason = decision.RejectionReason
case "select":
payload.Decision.SelectedOption = decision.SelectedOption
case "modify":
payload.Decision.ModifiedFields = decision.ModifiedFields
}
return payload
}
// =============================================================================
// Utility Methods
// =============================================================================
// IsHILRequired checks if a task requires human intervention
func IsHILRequired(reasonCode string) bool {
hilRequiredCodes := map[string]bool{
"BRAND_STYLE": true,
"CREATIVE_DIRECTION": true,
"COMPLIANCE": true,
"CAPABILITY_LACKING": true,
"USER_REQUESTED": true,
"HIGH_COST_RISK": true,
}
return hilRequiredCodes[reasonCode]
}
// GetEscalationLevel determines escalation level based on reason code
func GetEscalationLevel(reasonCode string) int {
levelMap := map[string]int{
"INFO_MISSING": 1,
"CREATIVE_DIRECTION": 1,
"BRAND_STYLE": 2,
"COMPLIANCE": 2,
"CAPABILITY_LACKING": 2,
"USER_REQUESTED": 1,
"HIGH_COST_RISK": 2,
"RESOURCE_DEADLOCK": 2,
"STRATEGIC_DECISION": 3,
}
if level, ok := levelMap[reasonCode]; ok {
return level
}
return 1 // Default to level 1
}

595
p2p/protocol.go Normal file
View File

@@ -0,0 +1,595 @@
package p2p
import (
"context"
"encoding/json"
"fmt"
"log"
"sync"
"time"
"synapse/types"
)
// =============================================================================
// P2P Protocol Handler
// =============================================================================
// TaskHandler is the interface for handling task execution
type TaskHandler interface {
// ExecuteTask executes a task and returns the result
ExecuteTask(ctx context.Context, taskType string, payload map[string]interface{}) (any, error)
// ValidateTask validates if the task can be executed
ValidateTask(taskType string, payload map[string]interface{}) ([]types.MissingField, bool)
// GetCapabilities returns the agent's capabilities
GetCapabilities() []string
}
// MessageHandler is the interface for handling incoming messages
type MessageHandler interface {
// HandleMessage handles an incoming P2P message
HandleMessage(msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error)
}
// CallbackSender is the interface for sending callbacks to remote agents
type CallbackSender interface {
// SendCallback sends a message to a remote agent
SendCallback(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error
}
// =============================================================================
// P2PProtocol represents the P2P protocol handler
// =============================================================================
// P2PProtocol handles point-to-point Synapse communication
type P2PProtocol struct {
mu sync.RWMutex
// Agent identity
agentEndpoint types.AgentEndpoint
// Task state
tasks map[string]*types.P2PTaskStatus
// Handlers
taskHandler TaskHandler
callbackSender CallbackSender
// Configuration
config *P2PConfig
}
// P2PConfig holds P2P protocol configuration
type P2PConfig struct {
MaxRetries int // Max callback retries
RetryBaseDelay time.Duration // Base delay for retries
TaskTimeout time.Duration // Task execution timeout
HeartbeatInterval time.Duration // Heartbeat interval
}
// DefaultP2PConfig returns default P2P configuration
func DefaultP2PConfig() *P2PConfig {
return &P2PConfig{
MaxRetries: 5,
RetryBaseDelay: 2 * time.Second,
TaskTimeout: 5 * time.Minute,
HeartbeatInterval: 30 * time.Second,
}
}
// =============================================================================
// P2PTask represents an active task
// =============================================================================
type P2PTask struct {
ID string
Type string
Priority types.Priority
Status types.P2PTaskStatus
Payload map[string]interface{}
Sender types.AgentEndpoint
Receiver types.AgentEndpoint
CorrelationID string
CreatedAt time.Time
UpdatedAt time.Time
CompletedAt *time.Time
Result any
Error *types.SynapseError
}
// =============================================================================
// NewP2PProtocol creates a new P2P protocol handler
// =============================================================================
func NewP2PProtocol(agentEndpoint types.AgentEndpoint, config *P2PConfig) *P2PProtocol {
if config == nil {
config = DefaultP2PConfig()
}
return &P2PProtocol{
agentEndpoint: agentEndpoint,
tasks: make(map[string]*types.P2PTaskStatus),
config: config,
}
}
// SetTaskHandler sets the task handler
func (p *P2PProtocol) SetTaskHandler(h TaskHandler) {
p.taskHandler = h
}
// SetCallbackSender sets the callback sender
func (p *P2PProtocol) SetCallbackSender(s CallbackSender) {
p.callbackSender = s
}
// AgentEndpoint returns the agent's endpoint
func (p *P2PProtocol) AgentEndpoint() types.AgentEndpoint {
return p.agentEndpoint
}
// =============================================================================
// HandleMessage processes an incoming P2P message
// =============================================================================
func (p *P2PProtocol) HandleMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
p.mu.Lock()
defer p.mu.Unlock()
// Validate message
if err := p.validateMessage(msg); err != nil {
a2aErr, ok := err.(*types.SynapseError)
if !ok {
a2aErr = types.NewSynapseError(types.ErrMessageFormatInvalid, err.Error(), nil)
}
return p.newNACK(msg, a2aErr)
}
// Route based on message type
switch types.P2PMessageType(msg.Type) {
case types.P2PTaskRequest:
return p.handleTaskRequest(ctx, msg)
case types.P2PTaskConfirm:
return p.handleTaskConfirm(ctx, msg)
case types.P2PTaskReject:
return p.handleTaskReject(ctx, msg)
case types.P2PTaskNeedInfo:
return p.handleTaskNeedInfo(ctx, msg)
case types.P2PTaskInfoProvided:
return p.handleTaskInfoProvided(ctx, msg)
case types.P2PTaskStatusUpdate:
return p.handleTaskStatusUpdate(ctx, msg)
case types.P2PTaskComplete:
return p.handleTaskComplete(ctx, msg)
case types.P2PTaskFailed:
return p.handleTaskFailed(ctx, msg)
case types.P2PTaskCancel:
return p.handleTaskCancel(ctx, msg)
case types.P2PTaskAck:
return p.handleTaskAck(ctx, msg)
case types.P2PTaskNeedHuman:
return p.handleTaskNeedHuman(ctx, msg)
case types.P2PHumanDecision:
return p.handleHumanDecision(ctx, msg)
case types.P2PAck:
return p.handleAck(ctx, msg)
case types.P2PNack:
return p.handleNack(ctx, msg)
case types.P2PHeartbeat:
return p.handleHeartbeat(ctx, msg)
default:
return p.newNACK(msg, types.NewSynapseError(
types.ErrMessageFormatInvalid,
fmt.Sprintf("Unknown message type: %s", msg.Type),
nil,
))
}
}
// =============================================================================
// Message Handlers
// =============================================================================
// handleTaskRequest handles incoming task requests
func (p *P2PProtocol) handleTaskRequest(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskRequestPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(
types.ErrMessageFormatInvalid,
"Invalid task request payload",
err.Error(),
))
}
taskID := payload.Task.ID
log.Printf("[P2P] Task request received: %s (type: %s)", taskID, payload.Task.Type)
// Check if we have a task handler
if p.taskHandler == nil {
return p.newReject(msg, taskID, "agent_not_ready", "Task handler not configured", nil)
}
// Validate the task
missingFields, canHandle := p.taskHandler.ValidateTask(payload.Task.Type, payload.Task.Payload)
if !canHandle {
if len(missingFields) > 0 {
// Need more info
return p.newNeedInfo(msg, taskID, missingFields)
}
return p.newReject(msg, taskID, "capability_lacking", "Agent cannot handle this task type", nil)
}
// Create task context
taskCtx, cancel := context.WithTimeout(ctx, p.config.TaskTimeout)
defer cancel()
// Execute task asynchronously
go p.executeTask(taskCtx, msg, taskID, payload.Task.Type, payload.Task.Payload)
// Return immediate confirmation
return p.newAck(msg, taskID, "task_accepted")
}
// executeTask executes a task and handles callbacks
func (p *P2PProtocol) executeTask(ctx context.Context, origMsg *types.UnifiedSynapseMessage, taskID, taskType string, payload map[string]interface{}) {
// Send initial status update
p.sendStatusUpdate(ctx, taskID, types.P2PStatusExecuting, 0, "Task started")
// Execute the task
result, err := p.taskHandler.ExecuteTask(ctx, taskType, payload)
if err != nil {
// Task failed
p.sendTaskFailed(ctx, taskID, "execution_failed", err.Error(), true, nil)
return
}
// Task completed
p.sendTaskComplete(ctx, taskID, result)
}
// sendStatusUpdate sends a status update to the sender
func (p *P2PProtocol) sendStatusUpdate(ctx context.Context, taskID string, status types.P2PTaskStatus, progress int, message string) {
if p.callbackSender == nil {
return
}
payload := types.TaskStatusUpdatePayload{
TaskID: taskID,
Status: string(status),
Progress: progress,
Message: message,
}
payloadBytes, _ := json.Marshal(payload)
response := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskStatusUpdate),
Sender: p.agentEndpoint,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
if err := p.callbackSender.SendCallback(ctx, "", response); err != nil {
log.Printf("[P2P] Failed to send status update: %v", err)
}
}
// sendTaskComplete sends task completion notification
func (p *P2PProtocol) sendTaskComplete(ctx context.Context, taskID string, result any) {
if p.callbackSender == nil {
return
}
payload := types.TaskCompletePayload{}
payload.TaskID = taskID
payload.Result.OutputType = "task_result"
payload.Result.Output = result
payloadBytes, _ := json.Marshal(payload)
response := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskComplete),
Sender: p.agentEndpoint,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
if err := p.callbackSender.SendCallback(ctx, "", response); err != nil {
log.Printf("[P2P] Failed to send task complete: %v", err)
}
}
// sendTaskFailed sends task failure notification
func (p *P2PProtocol) sendTaskFailed(ctx context.Context, taskID, errorCode, errorMessage string, recoverable bool, partialResult any) {
if p.callbackSender == nil {
return
}
payload := types.TaskFailedPayload{}
payload.TaskID = taskID
payload.Error.Code = errorCode
payload.Error.Message = errorMessage
payload.Error.Recoverable = recoverable
payload.PartialResult = partialResult
payloadBytes, _ := json.Marshal(payload)
response := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskFailed),
Sender: p.agentEndpoint,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
if err := p.callbackSender.SendCallback(ctx, "", response); err != nil {
log.Printf("[P2P] Failed to send task failed: %v", err)
}
}
// handleTaskConfirm handles task confirmation from receiver
func (p *P2PProtocol) handleTaskConfirm(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskConfirmPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid confirm payload", err.Error()))
}
log.Printf("[P2P] Task confirmed: %s (estimated: %ds)", payload.TaskID, payload.EstimatedDuration)
return p.newAck(msg, payload.TaskID, "confirmed")
}
// handleTaskReject handles task rejection from receiver
func (p *P2PProtocol) handleTaskReject(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskRejectPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid reject payload", err.Error()))
}
log.Printf("[P2P] Task rejected: %s (%s: %s)", payload.TaskID, payload.Reason.Code, payload.Reason.Message)
return p.newAck(msg, payload.TaskID, "rejected")
}
// handleTaskNeedInfo handles information request from receiver
func (p *P2PProtocol) handleTaskNeedInfo(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskNeedInfoPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid need info payload", err.Error()))
}
log.Printf("[P2P] Information requested: %s (missing %d fields)", payload.TaskID, len(payload.MissingFields))
// This would typically be handled by the application logic
// For now, just acknowledge
return p.newAck(msg, payload.TaskID, "info_request_received")
}
// handleTaskInfoProvided handles information provided by sender
func (p *P2PProtocol) handleTaskInfoProvided(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskInfoProvidedPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid info provided payload", err.Error()))
}
log.Printf("[P2P] Information provided: %s (%d fields)", payload.TaskID, len(payload.ProvidedInfo))
return p.newAck(msg, payload.TaskID, "info_received")
}
// handleTaskStatusUpdate handles status updates from receiver
func (p *P2PProtocol) handleTaskStatusUpdate(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskStatusUpdatePayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid status update payload", err.Error()))
}
log.Printf("[P2P] Status update: %s - %s (progress: %d%%)", payload.TaskID, payload.Status, payload.Progress)
return p.newAck(msg, payload.TaskID, "status_received")
}
// handleTaskComplete handles task completion notification
func (p *P2PProtocol) handleTaskComplete(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskCompletePayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid complete payload", err.Error()))
}
log.Printf("[P2P] Task completed: %s", payload.TaskID)
return p.newAck(msg, payload.TaskID, "completed")
}
// handleTaskFailed handles task failure notification
func (p *P2PProtocol) handleTaskFailed(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskFailedPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid failed payload", err.Error()))
}
log.Printf("[P2P] Task failed: %s (%s: %s)", payload.TaskID, payload.Error.Code, payload.Error.Message)
return p.newAck(msg, payload.TaskID, "failure_noted")
}
// handleTaskCancel handles task cancellation
func (p *P2PProtocol) handleTaskCancel(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskCancelPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid cancel payload", err.Error()))
}
log.Printf("[P2P] Task cancelled: %s (by %s)", payload.TaskID, payload.CancelledBy)
return p.newAck(msg, payload.TaskID, "cancelled")
}
// handleTaskAck handles task-level acknowledgment
func (p *P2PProtocol) handleTaskAck(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
log.Printf("[P2P] Task ACK received for message: %s", msg.ReplyTo)
return nil, nil // No response needed for ACK
}
// handleTaskNeedHuman handles human intervention request
func (p *P2PProtocol) handleTaskNeedHuman(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.TaskNeedHumanPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid need human payload", err.Error()))
}
log.Printf("[P2P] Human intervention needed: %s (level: %d)", payload.TaskID, payload.EscalationLevel)
// Store the HiL request and wait for human decision
// This would typically be handled by a HiL manager
return p.newAck(msg, payload.TaskID, "human_intervention_requested")
}
// handleHumanDecision handles human decision response
func (p *P2PProtocol) handleHumanDecision(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
var payload types.HumanDecisionPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return p.newNACK(msg, types.NewSynapseError(types.ErrMessageFormatInvalid, "Invalid human decision payload", err.Error()))
}
log.Printf("[P2P] Human decision: %s (action: %s, by %s)", payload.TaskID, payload.Decision.Action, payload.DecidedBy.UserName)
// Process the human decision
// This would resume the task execution with the decision
return p.newAck(msg, payload.TaskID, "decision_received")
}
// handleAck handles generic ACK
func (p *P2PProtocol) handleAck(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
log.Printf("[P2P] ACK received for: %s", msg.ReplyTo)
return nil, nil
}
// handleNack handles generic NACK
func (p *P2PProtocol) handleNack(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
log.Printf("[P2P] NACK received for: %s", msg.ReplyTo)
return nil, nil
}
// handleHeartbeat handles heartbeat
func (p *P2PProtocol) handleHeartbeat(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
return p.newAck(msg, "", "alive")
}
// =============================================================================
// Response Builders
// =============================================================================
// newAck creates an ACK response
func (p *P2PProtocol) newAck(msg *types.UnifiedSynapseMessage, taskID, note string) (*types.UnifiedSynapseMessage, error) {
return &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PAck),
Sender: p.agentEndpoint,
Receiver: &msg.Sender,
CorrelationID: msg.CorrelationID,
ReplyTo: msg.ID,
Timestamp: types.NowTimestamp(),
}, nil
}
// newNACK creates a NACK response
func (p *P2PProtocol) newNACK(msg *types.UnifiedSynapseMessage, err *types.SynapseError) (*types.UnifiedSynapseMessage, error) {
return nil, err
}
// newReject creates a task rejection response
func (p *P2PProtocol) newReject(msg *types.UnifiedSynapseMessage, taskID, reasonCode, reasonMessage string, alternatives *struct {
SuggestedAgentID string `json:"suggested_agent_id,omitempty"`
RetryAfter int `json:"retry_after,omitempty"`
PartialSupport bool `json:"partial_support,omitempty"`
}) (*types.UnifiedSynapseMessage, error) {
payload := types.TaskRejectPayload{}
payload.TaskID = taskID
payload.Reason.Code = reasonCode
payload.Reason.Message = reasonMessage
if alternatives != nil {
payload.Alternatives = alternatives
}
payloadBytes, _ := json.Marshal(payload)
return &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskReject),
Sender: p.agentEndpoint,
Receiver: &msg.Sender,
CorrelationID: msg.CorrelationID,
ReplyTo: msg.ID,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}, nil
}
// newNeedInfo creates a need info response
func (p *P2PProtocol) newNeedInfo(msg *types.UnifiedSynapseMessage, taskID string, missingFields []types.MissingField) (*types.UnifiedSynapseMessage, error) {
payload := types.TaskNeedInfoPayload{}
payload.TaskID = taskID
payload.MissingFields = missingFields
payloadBytes, _ := json.Marshal(payload)
return &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskNeedInfo),
Sender: p.agentEndpoint,
Receiver: &msg.Sender,
CorrelationID: msg.CorrelationID,
ReplyTo: msg.ID,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}, nil
}
// =============================================================================
// Validation
// =============================================================================
func (p *P2PProtocol) validateMessage(msg *types.UnifiedSynapseMessage) error {
if msg.ID == "" {
return types.ErrMessageFormatInvalidError
}
if msg.Sender.AgentID == "" {
return types.NewSynapseError(types.ErrMessageFormatInvalid, "Missing sender agent_id", nil)
}
if msg.Sender.Endpoint == "" {
return types.NewSynapseError(types.ErrMessageFormatInvalid, "Missing sender endpoint", nil)
}
return nil
}
// =============================================================================
// Task Management
// =============================================================================
// GetTaskStatus returns the status of a task
func (p *P2PProtocol) GetTaskStatus(taskID string) (types.P2PTaskStatus, bool) {
p.mu.RLock()
defer p.mu.RUnlock()
status, ok := p.tasks[taskID]
if !ok {
return "", false
}
return *status, true
}
// ListTasks returns all tasks
func (p *P2PProtocol) ListTasks() map[string]types.P2PTaskStatus {
p.mu.RLock()
defer p.mu.RUnlock()
result := make(map[string]types.P2PTaskStatus)
for k, v := range p.tasks {
result[k] = *v
}
return result
}

525
proto/synapse.proto Normal file
View File

@@ -0,0 +1,525 @@
syntax = "proto3";
package synapse;
option go_package = "synapse/grpcapi;synapse";
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
import "google/api/annotations.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
// =============================================================================
// Synapse gRPC Protocol Definition
// =============================================================================
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
info: {
title: "Synapse Protocol API";
version: "1.0.0";
description: "Synapse (灵隙) - Multi-agent collaboration protocol with gRPC and HTTP gateway support";
};
schemes: HTTPS;
consumes: "application/json";
produces: "application/json";
};
// =============================================================================
// Core Enums
// =============================================================================
enum TransportMode {
TRANSPORT_MODE_UNSPECIFIED = 0;
TRANSPORT_MODE_CALLBACK = 1;
TRANSPORT_MODE_POLLING = 2;
TRANSPORT_MODE_SYNC = 3;
TRANSPORT_MODE_HYBRID = 4;
}
enum MessageType {
MESSAGE_TYPE_UNSPECIFIED = 0;
MESSAGE_TYPE_P2P_TASK_REQUEST = 1;
MESSAGE_TYPE_P2P_TASK_COMPLETE = 2;
MESSAGE_TYPE_P2P_TASK_FAILED = 3;
MESSAGE_TYPE_P2P_STATUS_UPDATE = 4;
MESSAGE_TYPE_P2P_INFO_MISSING = 5;
MESSAGE_TYPE_P2P_ACK = 6;
MESSAGE_TYPE_P2P_NACK = 7;
MESSAGE_TYPE_GROUP_INVITE = 8;
MESSAGE_TYPE_GROUP_JOIN = 9;
MESSAGE_TYPE_GROUP_LEAVE = 10;
MESSAGE_TYPE_GROUP_TASK_SPLIT = 11;
MESSAGE_TYPE_GROUP_SUBTASK_RESULT = 12;
MESSAGE_TYPE_GROUP_RESULT_MERGE = 13;
MESSAGE_TYPE_GROUP_VOTE_INITIATE = 14;
MESSAGE_TYPE_GROUP_VOTE_SUBMIT = 15;
MESSAGE_TYPE_GROUP_VOTE_RESULT = 16;
MESSAGE_TYPE_HIL_DECISION_REQUEST = 17;
MESSAGE_TYPE_HIL_DECISION_RESPONSE = 18;
MESSAGE_TYPE_HEARTBEAT = 19;
}
enum TaskStatus {
TASK_STATUS_UNSPECIFIED = 0;
TASK_STATUS_RECEIVED = 1;
TASK_STATUS_PROCESSING = 2;
TASK_STATUS_COMPLETED = 3;
TASK_STATUS_FAILED = 4;
TASK_STATUS_CANCELLED = 5;
TASK_STATUS_REJECTED = 6;
TASK_STATUS_WAITING = 7;
}
// =============================================================================
// Core Messages
// =============================================================================
message AgentEndpoint {
string agent_id = 1;
string agent_name = 2;
string system_id = 3;
string endpoint = 4;
string version = 5;
}
message SynapseMessage {
string id = 1;
string version = 2;
MessageType type = 3;
AgentEndpoint sender = 4;
AgentEndpoint receiver = 5;
string correlation_id = 6;
string reply_to = 7;
string group_id = 8;
google.protobuf.Timestamp timestamp = 9;
google.protobuf.Any payload = 10;
string signature = 11;
}
message TaskPayload {
string task_id = 1;
string task_type = 2;
google.protobuf.Any payload = 3;
string external_task_id = 4;
}
message TaskResult {
string task_id = 1;
string task_type = 2;
google.protobuf.Any result = 3;
string summary = 4;
}
message TaskStatusUpdate {
string task_id = 1;
TaskStatus status = 2;
int32 progress = 3;
string message = 4;
google.protobuf.Any details = 5;
}
// =============================================================================
// Task Service
// =============================================================================
service TaskService {
// SubmitTask 提交任务
rpc SubmitTask(SubmitTaskRequest) returns (SubmitTaskResponse) {
option (google.api.http) = {
post: "/api/v1/synapse/task"
body: "*"
};
}
// SubmitTaskWithFiles 提交带文件的任务(Multipart)
rpc SubmitTaskWithFiles(SubmitTaskWithFilesRequest) returns (SubmitTaskResponse) {
option (google.api.http) = {
post: "/api/v1/synapse/task/multipart"
body: "*"
};
}
// GetTask 获取任务详情
rpc GetTask(GetTaskRequest) returns (TaskStatusResponse) {
option (google.api.http) = {
get: "/api/v1/synapse/task/{task_id}"
};
}
// GetTaskStatus 获取任务状态(简化)
rpc GetTaskStatus(GetTaskRequest) returns (TaskStatusResponse) {
option (google.api.http) = {
get: "/api/v1/synapse/task/{task_id}/status"
};
}
// ListTasks 任务列表
rpc ListTasks(ListTasksRequest) returns (ListTasksResponse) {
option (google.api.http) = {
get: "/api/v1/synapse/tasks"
};
}
// CancelTask 取消任务
rpc CancelTask(CancelTaskRequest) returns (CancelTaskResponse) {
option (google.api.http) = {
post: "/api/v1/synapse/task/{task_id}/cancel"
};
}
// StreamTaskStatus 流式获取任务状态(双向流)
rpc StreamTaskStatus(stream TaskStatusStreamRequest) returns (stream TaskStatusUpdate);
}
// =============================================================================
// Task Request/Response Messages
// =============================================================================
message SubmitTaskRequest {
string task_type = 1;
google.protobuf.Any payload = 2;
string external_task_id = 3;
string priority = 4;
string callback_url = 5;
string callback_token = 6;
TransportMode preferred_transport = 7;
}
message SubmitTaskResponse {
bool success = 1;
string task_id = 2;
TaskStatus status = 3;
string message = 4;
string error_code = 5;
}
message SubmitTaskWithFilesRequest {
SubmitTaskRequest request = 1;
repeated FileUpload files = 2;
}
message FileUpload {
string field_name = 1;
string filename = 2;
string mime_type = 3;
bytes data = 4;
}
message GetTaskRequest {
string task_id = 1;
}
message TaskStatusResponse {
bool success = 1;
TaskInfo task = 2;
string error_code = 3;
string error_message = 4;
}
message TaskInfo {
string id = 1;
string type = 2;
TaskStatus status = 3;
int32 progress = 4;
string message = 5;
google.protobuf.Timestamp created_at = 6;
google.protobuf.Timestamp updated_at = 7;
google.protobuf.Timestamp completed_at = 8;
google.protobuf.Timestamp eta = 9;
google.protobuf.Any result = 10;
TaskError error = 11;
string external_task_id = 12;
}
message TaskError {
string code = 1;
string message = 2;
}
message ListTasksRequest {
string status = 1;
int32 limit = 2;
int32 offset = 3;
}
message ListTasksResponse {
bool success = 1;
repeated TaskInfo tasks = 2;
int32 total = 3;
int32 limit = 4;
int32 offset = 5;
bool has_more = 6;
}
message CancelTaskRequest {
string task_id = 1;
}
message CancelTaskResponse {
bool success = 1;
string task_id = 2;
TaskStatus status = 3;
string message = 4;
}
// =============================================================================
// Stream Messages
// =============================================================================
message TaskStatusStreamRequest {
string task_id = 1;
bool subscribe = 2; // true=订阅, false=取消订阅
}
// =============================================================================
// Synapse Message Service
// =============================================================================
service SynapseMessageService {
// SendMessage 发送Synapse消息(Unary)
rpc SendMessage(SendMessageRequest) returns (SendMessageResponse) {
option (google.api.http) = {
post: "/api/v1/synapse/message"
body: "*"
};
}
// StreamMessages 双向流式消息传输
rpc StreamMessages(stream SynapseMessage) returns (stream SynapseMessage);
// ReceiveCallback 接收回调消息
rpc ReceiveCallback(google.protobuf.Empty) returns (stream SynapseMessage) {
option (google.api.http) = {
post: "/api/v1/synapse/callback"
body: "*"
};
}
}
message SendMessageRequest {
SynapseMessage message = 1;
}
message SendMessageResponse {
bool success = 1;
string message_id = 2;
string error = 3;
}
// =============================================================================
// Capabilities Service
// =============================================================================
service CapabilitiesService {
// GetCapabilities 获取Agent能力
rpc GetCapabilities(google.protobuf.Empty) returns (CapabilitiesResponse) {
option (google.api.http) = {
get: "/api/v1/synapse/capabilities"
};
}
// QueryAgent 查询特定Agent的能力
rpc QueryAgent(QueryAgentRequest) returns (CapabilitiesResponse) {
option (google.api.http) = {
get: "/api/v1/synapse/agents/{agent_id}"
};
}
// ListAgents 列出可用Agent
rpc ListAgents(google.protobuf.Empty) returns (ListAgentsResponse) {
option (google.api.http) = {
get: "/api/v1/synapse/agents"
};
}
}
message CapabilitiesResponse {
bool success = 1;
AgentCapabilities capabilities = 2;
string error = 3;
}
message AgentCapabilities {
string agent_id = 1;
string agent_name = 2;
string system_id = 3;
string protocol_version = 4;
repeated SupportedTaskType supported_task_types = 5;
repeated string supported_content_types = 6;
repeated TransportMode supported_transport_modes = 7;
int64 max_file_size = 8;
int32 max_concurrent_tasks = 9;
int32 max_task_duration_seconds = 10;
bool support_progress_updates = 11;
bool support_streaming = 12;
bool support_hil = 13;
bool support_groups = 14;
}
message SupportedTaskType {
string type = 1;
string name = 2;
string description = 3;
string estimated_time = 4;
string input_schema = 5;
string output_schema = 6;
repeated string capabilities = 7;
}
message QueryAgentRequest {
string agent_id = 1;
}
message ListAgentsResponse {
bool success = 1;
repeated AgentInfo agents = 2;
int32 total = 3;
}
message AgentInfo {
string id = 1;
string agent_id = 2;
string agent_name = 3;
string system_id = 4;
string endpoint = 5;
bool is_active = 6;
int32 trust_level = 7;
}
// =============================================================================
// Group Service
// =============================================================================
service GroupService {
// CreateGroup 创建群组
rpc CreateGroup(CreateGroupRequest) returns (CreateGroupResponse) {
option (google.api.http) = {
post: "/api/v1/synapse/group"
body: "*"
};
}
// JoinGroup 加入群组
rpc JoinGroup(JoinGroupRequest) returns (JoinGroupResponse) {
option (google.api.http) = {
post: "/api/v1/synapse/group/{group_id}/join"
body: "*"
};
}
// LeaveGroup 离开群组
rpc LeaveGroup(LeaveGroupRequest) returns (LeaveGroupResponse) {
option (google.api.http) = {
post: "/api/v1/synapse/group/{group_id}/leave"
body: "*"
};
}
// DisbandGroup 解散群组
rpc DisbandGroup(DisbandGroupRequest) returns (DisbandGroupResponse) {
option (google.api.http) = {
post: "/api/v1/synapse/group/{group_id}/disband"
body: "*"
};
}
// ListGroupMembers 列出群组成员
rpc ListGroupMembers(ListGroupMembersRequest) returns (ListGroupMembersResponse) {
option (google.api.http) = {
get: "/api/v1/synapse/group/{group_id}/members"
};
}
// StreamGroupMessages 群组消息流
rpc StreamGroupMessages(stream SynapseMessage) returns (stream SynapseMessage);
}
message CreateGroupRequest {
string name = 1;
string description = 2;
string coordinator_id = 3;
repeated string initial_members = 4;
}
message CreateGroupResponse {
bool success = 1;
string group_id = 2;
GroupInfo group = 3;
string error = 4;
}
message GroupInfo {
string id = 1;
string name = 2;
string description = 3;
string coordinator_id = 4;
string status = 5;
google.protobuf.Timestamp created_at = 6;
}
message JoinGroupRequest {
string group_id = 1;
AgentEndpoint agent = 2;
}
message JoinGroupResponse {
bool success = 1;
string error = 2;
}
message LeaveGroupRequest {
string group_id = 1;
string agent_id = 2;
}
message LeaveGroupResponse {
bool success = 1;
string error = 2;
}
message DisbandGroupRequest {
string group_id = 1;
}
message DisbandGroupResponse {
bool success = 1;
string error = 2;
}
message ListGroupMembersRequest {
string group_id = 1;
}
message ListGroupMembersResponse {
bool success = 1;
repeated GroupMember members = 2;
}
message GroupMember {
string agent_id = 1;
string agent_name = 2;
string role = 3;
string status = 4;
google.protobuf.Timestamp joined_at = 5;
}
// =============================================================================
// Health Service
// =============================================================================
service HealthService {
// HealthCheck 健康检查
rpc HealthCheck(google.protobuf.Empty) returns (HealthCheckResponse) {
option (google.api.http) = {
get: "/api/v1/synapse/health"
};
}
}
message HealthCheckResponse {
string status = 1;
string version = 2;
int64 uptime_seconds = 3;
int32 active_tasks = 4;
}

451
routing/router.go Normal file
View File

@@ -0,0 +1,451 @@
package routing
import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
"sync"
"synapse/group"
"synapse/hil"
"synapse/p2p"
"synapse/types"
)
// =============================================================================
// Message Router
// =============================================================================
// Router handles routing of Synapse messages
type Router struct {
mu sync.RWMutex
// Protocols
p2pProtocol *p2p.P2PProtocol
groupProtocol *group.GroupProtocol
hilProtocol *hil.HILProtocol
// External routing
externalRoutes map[string]string // system_id -> gateway endpoint
// Configuration
config *RouterConfig
}
// RouterConfig holds router configuration
type RouterConfig struct {
MaxHops int
EnableCaching bool
}
// DefaultRouterConfig returns default router configuration
func DefaultRouterConfig() *RouterConfig {
return &RouterConfig{
MaxHops: 10,
EnableCaching: true,
}
}
// =============================================================================
// NewRouter creates a new message router
// =============================================================================
func NewRouter(config *RouterConfig) *Router {
if config == nil {
config = DefaultRouterConfig()
}
return &Router{
externalRoutes: make(map[string]string),
config: config,
}
}
// SetP2PProtocol sets the P2P protocol handler
func (r *Router) SetP2PProtocol(p *p2p.P2PProtocol) {
r.mu.Lock()
defer r.mu.Unlock()
r.p2pProtocol = p
}
// SetGroupProtocol sets the Group protocol handler
func (r *Router) SetGroupProtocol(g *group.GroupProtocol) {
r.mu.Lock()
defer r.mu.Unlock()
r.groupProtocol = g
}
// SetHILProtocol sets the HiL protocol handler
func (r *Router) SetHILProtocol(h *hil.HILProtocol) {
r.mu.Lock()
defer r.mu.Unlock()
r.hilProtocol = h
}
// RegisterExternalRoute registers an external system gateway
func (r *Router) RegisterExternalRoute(systemID, endpoint string) {
r.mu.Lock()
defer r.mu.Unlock()
r.externalRoutes[systemID] = endpoint
}
// =============================================================================
// RouteMessage routes an incoming message to the appropriate handler
// =============================================================================
func (r *Router) RouteMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
r.mu.RLock()
defer r.mu.RUnlock()
// Validate message
if err := r.validateMessage(msg); err != nil {
return nil, err
}
// Route based on message category
if isGroupMessage(msg.Type) {
if r.groupProtocol == nil {
return nil, fmt.Errorf("group protocol not configured")
}
return r.groupProtocol.HandleMessage(ctx, msg)
}
if isP2PMessage(msg.Type) {
if r.p2pProtocol == nil {
return nil, fmt.Errorf("p2p protocol not configured")
}
return r.p2pProtocol.HandleMessage(ctx, msg)
}
return nil, fmt.Errorf("unknown message type: %s", msg.Type)
}
// =============================================================================
// SendMessage sends a message to a destination
// =============================================================================
// MessageSender is the interface for sending messages
type MessageSender interface {
Send(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error
}
// TransportSender is used by the router to send messages
var _ MessageSender = (*TransportSender)(nil)
// TransportSender is a placeholder for actual transport implementation
type TransportSender struct {
httpClient *HTTPClient
}
// HTTPClient is a simple HTTP client for sending messages
type HTTPClient struct {
baseURL string
timeoutMs int
}
// Send sends a message via HTTP
func (t *TransportSender) Send(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error {
if endpoint == "" {
return fmt.Errorf("endpoint is required")
}
_, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
// In a real implementation, this would use net/http to send the message
log.Printf("[Router] Sending message to %s: type=%s, id=%s", endpoint, msg.Type, msg.ID)
return nil // Placeholder - actual HTTP call would go here
}
// =============================================================================
// CreateOutboundMessage creates a message for sending to a remote agent
// =============================================================================
func (r *Router) CreateOutboundMessage(
msgType string,
sender types.AgentEndpoint,
receiver *types.AgentEndpoint,
payload any,
correlationID string,
) (*types.UnifiedSynapseMessage, error) {
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("failed to marshal payload: %w", err)
}
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: msgType,
Sender: sender,
Receiver: receiver,
CorrelationID: correlationID,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
return msg, nil
}
// =============================================================================
// CreateTaskRequest creates a task request message
// =============================================================================
func (r *Router) CreateTaskRequest(
sender types.AgentEndpoint,
receiver types.AgentEndpoint,
taskID string,
taskType string,
taskPayload map[string]interface{},
priority types.Priority,
correlationID string,
) (*types.UnifiedSynapseMessage, error) {
payload := types.TaskRequestPayload{}
payload.Task.ID = taskID
payload.Task.Type = taskType
payload.Task.Priority = priority
payload.Task.Payload = taskPayload
return r.CreateOutboundMessage(
string(types.P2PTaskRequest),
sender,
&receiver,
payload,
correlationID,
)
}
// =============================================================================
// CreateTaskNeedHuman creates a need human message
// =============================================================================
func (r *Router) CreateTaskNeedHuman(
sender types.AgentEndpoint,
receiver types.AgentEndpoint,
taskID string,
reasonCode, reasonMessage string,
context struct {
TaskSummary string
CurrentProgress int
BlockedReason string
RelevantHistory []any
},
options []struct {
Field string
Value any
ProsCons string
Confidence int
},
suggestedValue any,
escalationLevel int,
correlationID string,
) (*types.UnifiedSynapseMessage, error) {
payload := types.TaskNeedHumanPayload{}
payload.TaskID = taskID
payload.Reason.Code = reasonCode
payload.Reason.Message = reasonMessage
payload.Context.TaskSummary = context.TaskSummary
payload.Context.CurrentProgress = context.CurrentProgress
payload.Context.BlockedReason = context.BlockedReason
payload.Context.RelevantHistory = context.RelevantHistory
// Copy options
payload.Options = make([]struct {
Field string `json:"field"`
Value any `json:"value"`
ProsCons string `json:"pros_cons,omitempty"`
Confidence int `json:"confidence,omitempty"`
}, len(options))
for i, opt := range options {
payload.Options[i].Field = opt.Field
payload.Options[i].Value = opt.Value
payload.Options[i].ProsCons = opt.ProsCons
payload.Options[i].Confidence = opt.Confidence
}
payload.SuggestedValue = suggestedValue
payload.EscalationLevel = escalationLevel
return r.CreateOutboundMessage(
string(types.P2PTaskNeedHuman),
sender,
&receiver,
payload,
correlationID,
)
}
// =============================================================================
// CreateGroupTaskSplit creates a group task split message
// =============================================================================
func (r *Router) CreateGroupTaskSplit(
sender types.AgentEndpoint,
groupID string,
originalTask struct {
ID string
Type string
Requirements map[string]interface{}
Deadline *types.Timestamp
},
subtasks []types.Subtask,
mergeStrategy types.MergeStrategy,
correlationID string,
) (*types.UnifiedSynapseMessage, error) {
payload := types.GroupTaskSplitPayload{}
payload.OriginalTask.ID = originalTask.ID
payload.OriginalTask.Type = originalTask.Type
payload.OriginalTask.Requirements = originalTask.Requirements
payload.OriginalTask.Deadline = originalTask.Deadline
payload.Subtasks = subtasks
payload.MergeStrategy = mergeStrategy
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("failed to marshal payload: %w", err)
}
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.GroupTaskSplit),
Sender: sender,
GroupID: groupID,
Broadcast: false,
CorrelationID: correlationID,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
return msg, nil
}
// =============================================================================
// CreateGroupBroadcast creates a group broadcast message
// =============================================================================
func (r *Router) CreateGroupBroadcast(
sender types.AgentEndpoint,
groupID string,
msgType string,
payload any,
correlationID string,
) (*types.UnifiedSynapseMessage, error) {
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("failed to marshal payload: %w", err)
}
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: msgType,
Sender: sender,
GroupID: groupID,
Broadcast: true,
CorrelationID: correlationID,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
return msg, nil
}
// =============================================================================
// CreateGroupVote creates a group vote message
// =============================================================================
func (r *Router) CreateGroupVote(
sender types.AgentEndpoint,
groupID string,
voteID string,
question string,
options []string,
voters []string,
threshold float64,
deadline types.Timestamp,
correlationID string,
) (*types.UnifiedSynapseMessage, error) {
payload := types.GroupVotePayload{}
payload.VoteID = voteID
payload.Question = question
payload.Options = options
payload.Voters = voters
payload.Threshold = threshold
payload.Deadline = deadline
payload.Status = "open"
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("failed to marshal payload: %w", err)
}
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.GroupVote),
Sender: sender,
GroupID: groupID,
Broadcast: true,
CorrelationID: correlationID,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
return msg, nil
}
// =============================================================================
// Validation
// =============================================================================
func (r *Router) validateMessage(msg *types.UnifiedSynapseMessage) error {
if msg.ID == "" {
return types.ErrMessageFormatInvalidError
}
if msg.Sender.AgentID == "" {
return types.NewSynapseError(types.ErrMessageFormatInvalid, "Missing sender agent_id", nil)
}
if msg.Sender.Endpoint == "" {
return types.NewSynapseError(types.ErrMessageFormatInvalid, "Missing sender endpoint", nil)
}
return nil
}
// =============================================================================
// Helper Functions
// =============================================================================
func isP2PMessage(msgType string) bool {
return strings.HasPrefix(msgType, "p2p:")
}
func isGroupMessage(msgType string) bool {
return strings.HasPrefix(msgType, "group:") || strings.HasPrefix(msgType, "inter_group:")
}
// GetRoute determines the route for a message
func (r *Router) GetRoute(msg *types.UnifiedSynapseMessage) (routeType string, target string, err error) {
// P2P messages
if msg.Receiver != nil {
if msg.Receiver.SystemID != r.p2pProtocol.AgentEndpoint().SystemID {
// External system
if endpoint, ok := r.externalRoutes[msg.Receiver.SystemID]; ok {
return "external", endpoint, nil
}
return "unknown", "", fmt.Errorf("no route for system: %s", msg.Receiver.SystemID)
}
return "local", msg.Receiver.AgentID, nil
}
// Group messages
if msg.GroupID != "" {
return "group", msg.GroupID, nil
}
return "unknown", "", fmt.Errorf("no route found for message")
}

547
sdk/caller/client.go Normal file
View File

@@ -0,0 +1,547 @@
package caller
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"time"
)
// =============================================================================
// Synapse Caller SDK (P2A Protocol)
// 非Agent调用方使用的客户端SDK
// 基于 P2A 协议:非 Agent 调用方通过 REST API 调用 Agent 能力
// =============================================================================
// Client 是 Synapse P2A 服务的外部调用客户端
type Client struct {
baseURL string
apiKey string
httpClient *http.Client
}
// ClientConfig 客户端配置
type ClientConfig struct {
BaseURL string
APIKey string
Timeout time.Duration
MaxRetries int
}
// NewClient 创建新的 Synapse Caller 客户端
func NewClient(config ClientConfig) *Client {
if config.Timeout == 0 {
config.Timeout = 30 * time.Second
}
return &Client{
baseURL: config.BaseURL,
apiKey: config.APIKey,
httpClient: &http.Client{Timeout: config.Timeout},
}
}
// =============================================================================
// 通用响应结构
// =============================================================================
// APIResponse 通用 API 响应
type APIResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
}
// TaskResponse 任务提交响应
type TaskResponse struct {
APIResponse
TaskID string `json:"task_id"`
Status string `json:"status"`
Message string `json:"message,omitempty"`
}
// TaskStatusResponse 任务状态响应(简化版)
type TaskStatusResponse struct {
APIResponse
TaskID string `json:"task_id"`
Status string `json:"status"`
Progress int `json:"progress"`
}
// TaskDetailResponse 任务详情响应
type TaskDetailResponse struct {
APIResponse
Task TaskInfo `json:"task"`
}
// TaskListResponse 任务列表响应
type TaskListResponse struct {
APIResponse
Tasks []TaskInfo `json:"tasks"`
Total int `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
HasMore bool `json:"has_more"`
}
// TaskInfo 任务详情信息
type TaskInfo struct {
ID string `json:"id"`
Type string `json:"type"`
Status string `json:"status"`
Progress int `json:"progress"`
Message string `json:"message,omitempty"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
CompletedAt string `json:"completed_at,omitempty"`
ETA string `json:"eta,omitempty"`
Result map[string]any `json:"result,omitempty"`
Error *TaskError `json:"error,omitempty"`
ExternalTaskID string `json:"external_task_id,omitempty"`
}
// TaskError 任务错误
type TaskError struct {
Code string `json:"code"`
Message string `json:"message"`
}
// TaskRequest 任务请求
type TaskRequest struct {
TaskType string `json:"task_type"`
Payload map[string]any `json:"payload"`
ExternalTaskID string `json:"external_task_id,omitempty"`
Priority string `json:"priority,omitempty"`
CallbackURL string `json:"callback_url,omitempty"`
CallbackToken string `json:"callback_token,omitempty"`
}
// FileParam 上传文件参数
type FileParam struct {
FieldName string
Filename string
Data []byte
MimeType string
}
// =============================================================================
// 能力查询
// =============================================================================
// CapabilitiesResponse 能力查询响应
type CapabilitiesResponse struct {
APIResponse
Capabilities CapabilitiesInfo `json:"capabilities"`
}
// CapabilitiesInfo Agent 能力信息
type CapabilitiesInfo struct {
AgentID string `json:"agent_id"`
AgentName string `json:"agent_name"`
SystemID string `json:"system_id"`
ProtocolVersion string `json:"protocol_version"`
SupportedTaskTypes []SupportedTaskType `json:"supported_task_types"`
SupportedTransportModes []string `json:"supported_transport_modes"`
MaxFileSize int64 `json:"max_file_size"`
MaxConcurrentTasks int `json:"max_concurrent_tasks"`
SupportProgressUpdates bool `json:"support_progress_updates"`
SupportStreaming bool `json:"support_streaming"`
SupportHiL bool `json:"support_hil"`
SupportGroups bool `json:"support_groups"`
}
// SupportedTaskType 支持的任务类型
type SupportedTaskType struct {
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
EstimatedTime string `json:"estimated_time"`
InputSchema string `json:"input_schema,omitempty"`
OutputSchema string `json:"output_schema,omitempty"`
}
// GetCapabilities 获取 Agent 能力清单
func (c *Client) GetCapabilities(ctx context.Context) (*CapabilitiesResponse, error) {
url := fmt.Sprintf("%s/api/v1/synapse/capabilities", c.baseURL)
httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
c.setAuthHeaders(httpReq)
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var result CapabilitiesResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if resp.StatusCode >= 400 {
return &result, fmt.Errorf("API error: %s", result.Error)
}
return &result, nil
}
// =============================================================================
// 任务提交
// =============================================================================
// SubmitTask 提交任务(JSON 方式)
func (c *Client) SubmitTask(ctx context.Context, req *TaskRequest) (*TaskResponse, error) {
url := fmt.Sprintf("%s/api/v1/synapse/task", c.baseURL)
body, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
c.setAuthHeaders(httpReq)
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var result TaskResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if resp.StatusCode >= 400 {
return &result, fmt.Errorf("API error: %s", result.Error)
}
return &result, nil
}
// SubmitTaskWithFiles 提交带文件的任务(multipart/form-data 方式)
func (c *Client) SubmitTaskWithFiles(ctx context.Context, req *TaskRequest, files []FileParam) (*TaskResponse, error) {
url := fmt.Sprintf("%s/api/v1/synapse/task/multipart", c.baseURL)
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
_ = writer.WriteField("task_type", req.TaskType)
payloadJSON, _ := json.Marshal(req.Payload)
_ = writer.WriteField("payload", string(payloadJSON))
if req.ExternalTaskID != "" {
_ = writer.WriteField("external_task_id", req.ExternalTaskID)
}
if req.Priority != "" {
_ = writer.WriteField("priority", req.Priority)
}
if req.CallbackURL != "" {
_ = writer.WriteField("callback_url", req.CallbackURL)
}
if req.CallbackToken != "" {
_ = writer.WriteField("callback_token", req.CallbackToken)
}
for _, f := range files {
var part io.Writer
var err error
if f.MimeType != "" {
part, err = writer.CreatePart(map[string][]string{
"Content-Disposition": {fmt.Sprintf(`form-data; name="%s"; filename="%s"`, f.FieldName, f.Filename)},
"Content-Type": {f.MimeType},
})
} else {
part, err = writer.CreateFormFile(f.FieldName, f.Filename)
}
if err != nil {
return nil, fmt.Errorf("failed to create form file: %w", err)
}
_, _ = part.Write(f.Data)
}
_ = writer.Close()
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, &buf)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
httpReq.Header.Set("Content-Type", writer.FormDataContentType())
c.setAuthHeaders(httpReq)
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var result TaskResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if resp.StatusCode >= 400 {
return &result, fmt.Errorf("API error: %s", result.Error)
}
return &result, nil
}
// =============================================================================
// 任务查询
// =============================================================================
// GetTask 获取任务详情
func (c *Client) GetTask(ctx context.Context, taskID string) (*TaskDetailResponse, error) {
url := fmt.Sprintf("%s/api/v1/synapse/task/%s", c.baseURL, taskID)
httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
c.setAuthHeaders(httpReq)
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var result TaskDetailResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if resp.StatusCode >= 400 {
return &result, fmt.Errorf("API error: %s", result.Error)
}
return &result, nil
}
// GetTaskStatus 获取任务状态(简化版)
func (c *Client) GetTaskStatus(ctx context.Context, taskID string) (*TaskStatusResponse, error) {
url := fmt.Sprintf("%s/api/v1/synapse/task/%s/status", c.baseURL, taskID)
httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
c.setAuthHeaders(httpReq)
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var result TaskStatusResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if resp.StatusCode >= 400 {
return &result, fmt.Errorf("API error: %s", result.Error)
}
return &result, nil
}
// ListTasks 获取任务列表
func (c *Client) ListTasks(ctx context.Context, status string, limit, offset int) (*TaskListResponse, error) {
if limit <= 0 {
limit = 20
}
url := fmt.Sprintf("%s/api/v1/synapse/tasks?status=%s&limit=%d&offset=%d",
c.baseURL, status, limit, offset)
httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
c.setAuthHeaders(httpReq)
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var result TaskListResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if resp.StatusCode >= 400 {
return &result, fmt.Errorf("API error: %s", result.Error)
}
return &result, nil
}
// =============================================================================
// 任务取消
// =============================================================================
// CancelTask 取消任务
func (c *Client) CancelTask(ctx context.Context, taskID string) (*TaskResponse, error) {
url := fmt.Sprintf("%s/api/v1/synapse/task/%s/cancel", c.baseURL, taskID)
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
c.setAuthHeaders(httpReq)
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var result TaskResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if resp.StatusCode >= 400 {
return &result, fmt.Errorf("API error: %s", result.Error)
}
return &result, nil
}
// =============================================================================
// 等待任务完成
// =============================================================================
// WaitForTask 等待任务完成(轮询方式)
func (c *Client) WaitForTask(ctx context.Context, taskID string, interval time.Duration) (*TaskInfo, error) {
if interval == 0 {
interval = 5 * time.Second
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
resp, err := c.GetTask(ctx, taskID)
if err != nil {
continue
}
switch resp.Task.Status {
case "completed", "failed", "cancelled", "rejected":
return &resp.Task, nil
}
}
}
}
// =============================================================================
// 回调验证
// =============================================================================
// CallbackRequest 回调请求结构
type CallbackRequest struct {
TaskID string `json:"task_id"`
Status string `json:"status"`
Progress int `json:"progress"`
Message string `json:"message,omitempty"`
Result map[string]any `json:"result,omitempty"`
Error *TaskError `json:"error,omitempty"`
Timestamp string `json:"timestamp"`
}
// VerifyCallback 验证回调请求签名
func (c *Client) VerifyCallback(r *http.Request, callbackToken string) bool {
if callbackToken == "" {
return true
}
token := r.Header.Get("X-Callback-Token")
return token == callbackToken
}
// =============================================================================
// 高阶 API: 快速创建任务并等待
// =============================================================================
// CreateAndWait 创建任务并等待完成
func (c *Client) CreateAndWait(ctx context.Context, taskType string, payload map[string]any, interval time.Duration) (*TaskInfo, error) {
req := &TaskRequest{
TaskType: taskType,
Payload: payload,
Priority: "normal",
}
resp, err := c.SubmitTask(ctx, req)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("task submission failed: %s", resp.Message)
}
return c.WaitForTask(ctx, resp.TaskID, interval)
}
// CreateWithCallback 创建任务并指定回调
func (c *Client) CreateWithCallback(ctx context.Context, taskType string, payload map[string]any, callbackURL, callbackToken string) (*TaskResponse, error) {
req := &TaskRequest{
TaskType: taskType,
Payload: payload,
Priority: "normal",
CallbackURL: callbackURL,
CallbackToken: callbackToken,
}
return c.SubmitTask(ctx, req)
}
// =============================================================================
// 辅助方法
// =============================================================================
func (c *Client) setAuthHeaders(req *http.Request) {
if c.apiKey != "" {
req.Header.Set("X-API-Key", c.apiKey)
}
req.Header.Set("Accept", "application/json")
}

10
sdk/doc.go Normal file
View File

@@ -0,0 +1,10 @@
// Package sdk 灵隙(Synapse)协议 SDK
//
// 灵隙协议族(Synapse Protocol Suite)Go 语言 SDK,包含三个子包:
//
// - sdk/caller: P2A 协议 - 非 Agent 调用方通过 REST API 调用 Agent 能力
// - sdk/p2p: A2A 协议 - Agent 之间的点对点任务协作通信
// - sdk/group: A2N 协议 - 多 Agent 群组协作(群组管理、任务拆分合并、投票)
//
// 协议版本:1.0.0
package sdk

346
sdk/group/client.go Normal file
View File

@@ -0,0 +1,346 @@
package group
import (
"context"
"fmt"
"sync"
"time"
"synapse/types"
)
// =============================================================================
// Synapse Group SDK (A2N Protocol)
// 多 Agent 群组协作客户端 SDK
// 基于 A2N 协议:多个 Agent 组成群组进行协同工作
// =============================================================================
// GroupEventHandler 群组事件处理器接口
type GroupEventHandler interface {
OnGroupCreated(ctx context.Context, groupID string) error
OnGroupDisbanded(ctx context.Context, groupID string) error
OnMemberJoined(ctx context.Context, groupID string, member types.GroupMember) error
OnMemberLeft(ctx context.Context, groupID, agentID string) error
OnTaskSplit(ctx context.Context, groupID, originalTaskID string, subtasks []types.Subtask) error
OnSubtaskProgress(ctx context.Context, groupID, subtaskID string, progress int) error
OnTaskMerged(ctx context.Context, groupID, originalTaskID string, result any) error
OnVoteInitiated(ctx context.Context, groupID string, vote types.GroupVotePayload) error
OnVoteResult(ctx context.Context, groupID, voteID, option string, passed bool) error
OnEscalation(ctx context.Context, groupID, escalationID string, level int) error
OnBroadcast(ctx context.Context, groupID string, sender types.AgentEndpoint, message []byte) error
}
// Client A2N 群组协作客户端
type Client struct {
p2pClient *p2pClient
groups map[string]*GroupState
groupsMu sync.RWMutex
eventHandler GroupEventHandler
}
// p2pClient 是 P2P 客户端的简化接口
type p2pClient interface {
SendMessage(ctx context.Context, target types.AgentEndpoint, msg *types.UnifiedSynapseMessage) error
AgentEndpoint() types.AgentEndpoint
}
// ClientConfig A2N 客户端配置
type ClientConfig struct {
P2PClient *p2pClient
EventHandler GroupEventHandler
}
// GroupState 群组状态
type GroupState struct {
GroupID string
Name string
Topology types.GroupTopology
Status types.GroupStatus
Members []types.GroupMember
Role string
}
// NewClient 创建新的 A2N 群组客户端
func NewClient(p2pClient interface{}, eventHandler GroupEventHandler) (*Client, error) {
return &Client{
groups: make(map[string]*GroupState),
eventHandler: eventHandler,
}, nil
}
// SetEventHandler 设置群组事件处理器
func (c *Client) SetEventHandler(handler GroupEventHandler) {
c.eventHandler = handler
}
// =============================================================================
// 群组管理
// =============================================================================
// CreateGroup 创建群组
func (c *Client) CreateGroup(ctx context.Context, name string, topology types.GroupTopology, config *types.GroupConfig, initialMembers []types.AgentEndpoint) (*GroupState, error) {
groupID := fmt.Sprintf("group-%d", time.Now().UnixNano())
if config == nil {
config = &types.GroupConfig{
AllowSelfLeave: true,
RequiresApproval: false,
ConsensusRequired: false,
ConsensusThreshold: 0.5,
BroadcastMode: "all",
}
}
members := make([]types.GroupMember, 0, len(initialMembers)+1)
for _, m := range initialMembers {
members = append(members, types.GroupMember{
AgentID: m.AgentID,
AgentName: m.AgentName,
Role: "worker",
JoinedAt: types.NowTimestamp(),
Status: types.GroupMemberActive,
Capabilities: []string{},
LoadFactor: 0,
})
}
groupState := &GroupState{
GroupID: groupID,
Name: name,
Topology: topology,
Status: types.GroupStatusActive,
Members: members,
Role: "coordinator",
}
c.groupsMu.Lock()
c.groups[groupID] = groupState
c.groupsMu.Unlock()
if c.eventHandler != nil {
_ = c.eventHandler.OnGroupCreated(ctx, groupID)
}
return groupState, nil
}
// JoinGroup 加入群组
func (c *Client) JoinGroup(ctx context.Context, groupID string) error {
c.groupsMu.Lock()
defer c.groupsMu.Unlock()
if _, ok := c.groups[groupID]; !ok {
c.groups[groupID] = &GroupState{
GroupID: groupID,
Status: types.GroupStatusActive,
Role: "worker",
}
}
if c.eventHandler != nil {
_ = c.eventHandler.OnMemberJoined(ctx, groupID, types.GroupMember{})
}
return nil
}
// LeaveGroup 离开群组
func (c *Client) LeaveGroup(ctx context.Context, groupID string) error {
c.groupsMu.Lock()
delete(c.groups, groupID)
c.groupsMu.Unlock()
if c.eventHandler != nil {
_ = c.eventHandler.OnMemberLeft(ctx, groupID, "")
}
return nil
}
// DisbandGroup 解散群组
func (c *Client) DisbandGroup(ctx context.Context, groupID string) error {
c.groupsMu.Lock()
if group, ok := c.groups[groupID]; ok {
group.Status = types.GroupStatusDisbanded
}
c.groupsMu.Unlock()
if c.eventHandler != nil {
_ = c.eventHandler.OnGroupDisbanded(ctx, groupID)
}
return nil
}
// GetGroup 获取群组信息
func (c *Client) GetGroup(groupID string) (*GroupState, bool) {
c.groupsMu.RLock()
defer c.groupsMu.RUnlock()
group, ok := c.groups[groupID]
return group, ok
}
// ListGroups 列出所有群组
func (c *Client) ListGroups() []*GroupState {
c.groupsMu.RLock()
defer c.groupsMu.RUnlock()
result := make([]*GroupState, 0, len(c.groups))
for _, g := range c.groups {
result = append(result, g)
}
return result
}
// =============================================================================
// 任务协作
// =============================================================================
// SubmitGroupTask 提交群组任务(作为 coordinator 拆分任务)
func (c *Client) SubmitGroupTask(ctx context.Context, groupID, taskType string, requirements map[string]any, mergeStrategy types.MergeStrategy) ([]types.Subtask, error) {
subtasks := []types.Subtask{
{
ID: fmt.Sprintf("sub-%d-1", time.Now().UnixNano()),
Description: "子任务 1",
AssignedTo: "",
Dependencies: []string{},
EstimatedDuration: 60,
Priority: 1,
},
}
if c.eventHandler != nil {
_ = c.eventHandler.OnTaskSplit(ctx, groupID, "", subtasks)
}
return subtasks, nil
}
// SendSubtaskResult 发送子任务结果
func (c *Client) SendSubtaskResult(ctx context.Context, groupID, subtaskID string, result any, err error) error {
if c.eventHandler != nil {
_ = c.eventHandler.OnTaskMerged(ctx, groupID, "", result)
}
return nil
}
// =============================================================================
// 投票与共识
// =============================================================================
// InitiateVote 发起投票
func (c *Client) InitiateVote(ctx context.Context, groupID, question string, options []string, voterIDs []string, threshold float64, deadline time.Duration) (string, error) {
voteID := fmt.Sprintf("vote-%d", time.Now().UnixNano())
vote := types.GroupVotePayload{
VoteID: voteID,
Question: question,
Options: options,
Voters: voterIDs,
Deadline: types.Timestamp(time.Now().Add(deadline).UTC().Format(time.RFC3339)),
Threshold: threshold,
Status: "open",
}
if c.eventHandler != nil {
_ = c.eventHandler.OnVoteInitiated(ctx, groupID, vote)
}
return voteID, nil
}
// SubmitVote 提交投票
func (c *Client) SubmitVote(ctx context.Context, groupID, voteID, option, reason string) error {
if c.eventHandler != nil {
_ = c.eventHandler.OnVoteResult(ctx, groupID, voteID, option, true)
}
return nil
}
// =============================================================================
// 广播与升级
// =============================================================================
// Broadcast 广播消息
func (c *Client) Broadcast(ctx context.Context, groupID string, message []byte) error {
if c.eventHandler != nil {
_ = c.eventHandler.OnBroadcast(ctx, groupID, types.AgentEndpoint{}, message)
}
return nil
}
// EscalateToHuman 升级到人类决策
func (c *Client) EscalateToHuman(ctx context.Context, groupID string, level int, reason string) (string, error) {
escalationID := fmt.Sprintf("esc-%d", time.Now().UnixNano())
if c.eventHandler != nil {
_ = c.eventHandler.OnEscalation(ctx, groupID, escalationID, level)
}
return escalationID, nil
}
// =============================================================================
// 消息处理
// =============================================================================
// HandleMessage 处理群组消息
func (c *Client) HandleMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
groupID := msg.GroupID
switch types.GroupMessageType(msg.Type) {
case types.GroupCreate:
if c.eventHandler != nil {
_ = c.eventHandler.OnGroupCreated(ctx, groupID)
}
case types.GroupDisband:
if c.eventHandler != nil {
_ = c.eventHandler.OnGroupDisbanded(ctx, groupID)
}
case types.GroupMemberUpdate:
if c.eventHandler != nil {
_ = c.eventHandler.OnMemberJoined(ctx, groupID, types.GroupMember{})
}
case types.GroupTaskSplit:
var payload types.GroupTaskSplitPayload
if c.eventHandler != nil {
_ = c.eventHandler.OnTaskSplit(ctx, groupID, payload.OriginalTask.ID, payload.Subtasks)
}
case types.GroupTaskProgress:
if c.eventHandler != nil {
_ = c.eventHandler.OnSubtaskProgress(ctx, groupID, "", 0)
}
case types.GroupTaskMerge:
var payload types.GroupTaskMergePayload
if c.eventHandler != nil {
_ = c.eventHandler.OnTaskMerged(ctx, groupID, payload.OriginalTaskID, payload.MergedResult)
}
case types.GroupVote:
var payload types.GroupVotePayload
if c.eventHandler != nil {
_ = c.eventHandler.OnVoteInitiated(ctx, groupID, payload)
}
case types.GroupConsensus:
if c.eventHandler != nil {
_ = c.eventHandler.OnVoteResult(ctx, groupID, "", "", true)
}
case types.GroupConsensusEscalate, types.GroupSubtaskEscalate:
if c.eventHandler != nil {
_ = c.eventHandler.OnEscalation(ctx, groupID, "", 1)
}
case types.GroupBroadcast:
if c.eventHandler != nil {
_ = c.eventHandler.OnBroadcast(ctx, groupID, msg.Sender, msg.Payload)
}
}
return nil, nil
}

459
sdk/p2p/client.go Normal file
View File

@@ -0,0 +1,459 @@
package p2p
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
"time"
"synapse/types"
)
// =============================================================================
// Synapse P2P SDK (A2A Protocol)
// Agent 间点对点通信客户端 SDK
// 基于 A2A 协议:两个 Agent 之间进行点对点任务协作
// =============================================================================
// MessageCallbackHandler 消息回调处理器接口
type MessageCallbackHandler interface {
HandleTaskRequest(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error)
HandleTaskStatusUpdate(ctx context.Context, msg *types.UnifiedSynapseMessage) error
HandleTaskComplete(ctx context.Context, msg *types.UnifiedSynapseMessage) error
HandleTaskFailed(ctx context.Context, msg *types.UnifiedSynapseMessage) error
HandleNeedHuman(ctx context.Context, msg *types.UnifiedSynapseMessage) error
}
// Client A2A 点对点通信客户端
type Client struct {
agentEndpoint types.AgentEndpoint
authSecret string
callbackHandler MessageCallbackHandler
tasks map[string]*P2PTaskState
tasksMu sync.RWMutex
httpClient *http.Client
}
// ClientConfig A2A 客户端配置
type ClientConfig struct {
AgentEndpoint types.AgentEndpoint
AuthSecret string
CallbackPort int
Timeout time.Duration
MaxRetries int
}
// P2PTaskState P2P 任务状态
type P2PTaskState struct {
TaskID string
Status types.P2PTaskStatus
Progress int
Message string
Result any
Error *types.SynapseError
}
// NewClient 创建新的 A2A 客户端
func NewClient(config *ClientConfig) (*Client, error) {
if config.Timeout == 0 {
config.Timeout = 30 * time.Second
}
return &Client{
agentEndpoint: config.AgentEndpoint,
authSecret: config.AuthSecret,
tasks: make(map[string]*P2PTaskState),
httpClient: &http.Client{Timeout: config.Timeout},
}, nil
}
// SetMessageHandler 设置消息回调处理器
func (c *Client) SetMessageHandler(handler MessageCallbackHandler) {
c.callbackHandler = handler
}
// =============================================================================
// 任务状态管理
// =============================================================================
// GetTaskState 获取任务状态
func (c *Client) GetTaskState(taskID string) (*P2PTaskState, bool) {
c.tasksMu.RLock()
defer c.tasksMu.RUnlock()
state, ok := c.tasks[taskID]
return state, ok
}
// ListTasks 列出所有任务
func (c *Client) ListTasks() map[string]*P2PTaskState {
c.tasksMu.RLock()
defer c.tasksMu.RUnlock()
result := make(map[string]*P2PTaskState, len(c.tasks))
for k, v := range c.tasks {
result[k] = v
}
return result
}
func (c *Client) setTaskState(taskID string, status types.P2PTaskStatus, progress int, message string) {
c.tasksMu.Lock()
defer c.tasksMu.Unlock()
if _, ok := c.tasks[taskID]; !ok {
c.tasks[taskID] = &P2PTaskState{TaskID: taskID}
}
c.tasks[taskID].Status = status
c.tasks[taskID].Progress = progress
c.tasks[taskID].Message = message
}
func (c *Client) setTaskResult(taskID string, result any) {
c.tasksMu.Lock()
defer c.tasksMu.Unlock()
if state, ok := c.tasks[taskID]; ok {
state.Result = result
}
}
func (c *Client) setTaskError(taskID string, err *types.SynapseError) {
c.tasksMu.Lock()
defer c.tasksMu.Unlock()
if state, ok := c.tasks[taskID]; ok {
state.Error = err
}
}
// =============================================================================
// 消息发送
// =============================================================================
// SendTaskRequest 发送任务请求
func (c *Client) SendTaskRequest(ctx context.Context, target types.AgentEndpoint, taskType string, payload map[string]any) (string, error) {
taskID := types.GenerateTaskID()
taskReq := types.TaskRequestPayload{}
taskReq.Task.ID = taskID
taskReq.Task.Type = taskType
taskReq.Task.Priority = types.PriorityNormal
taskReq.Task.Payload = payload
payloadBytes, _ := json.Marshal(taskReq)
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskRequest),
Sender: c.agentEndpoint,
Receiver: &target,
Payload: payloadBytes,
Priority: types.PriorityNormal,
Timestamp: types.NowTimestamp(),
}
c.setTaskState(taskID, types.P2PStatusPending, 0, "任务已发送")
err := c.sendMessage(ctx, target.Endpoint, msg)
if err != nil {
return "", fmt.Errorf("failed to send task request: %w", err)
}
return taskID, nil
}
// SendTaskCancel 发送任务取消
func (c *Client) SendTaskCancel(ctx context.Context, target types.AgentEndpoint, taskID, reason string) error {
cancelPayload := types.TaskCancelPayload{
TaskID: taskID,
Reason: reason,
CancelledBy: c.agentEndpoint.AgentID,
}
payloadBytes, _ := json.Marshal(cancelPayload)
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PTaskCancel),
Sender: c.agentEndpoint,
Receiver: &target,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
c.setTaskState(taskID, types.P2PStatusCancelled, 0, "任务已取消")
return c.sendMessage(ctx, target.Endpoint, msg)
}
// SendHumanDecision 发送人类决策结果
func (c *Client) SendHumanDecision(ctx context.Context, target types.AgentEndpoint, taskID string, decision types.HumanDecisionPayload) error {
payloadBytes, _ := json.Marshal(decision)
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PHumanDecision),
Sender: c.agentEndpoint,
Receiver: &target,
Payload: payloadBytes,
Timestamp: types.NowTimestamp(),
}
return c.sendMessage(ctx, target.Endpoint, msg)
}
// SendCapabilityQuery 查询对方能力
func (c *Client) SendCapabilityQuery(ctx context.Context, target types.AgentEndpoint) ([]string, error) {
msg := &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: "1.0.0",
Type: string(types.P2PCapabilityQuery),
Sender: c.agentEndpoint,
Receiver: &target,
Payload: []byte("{}"),
Timestamp: types.NowTimestamp(),
}
respMsg, err := c.sendRequest(ctx, target.Endpoint, msg)
if err != nil {
return nil, fmt.Errorf("failed to query capabilities: %w", err)
}
var resp struct {
Capabilities []string `json:"capabilities"`
}
if err := json.Unmarshal(respMsg.Payload, &resp); err != nil {
return nil, fmt.Errorf("failed to parse capabilities: %w", err)
}
return resp.Capabilities, nil
}
// =============================================================================
// 消息签名与验证
// =============================================================================
func (c *Client) signMessage(msg *types.UnifiedSynapseMessage) string {
if c.authSecret == "" {
return ""
}
h := hmac.New(sha256.New, []byte(c.authSecret))
h.Write([]byte(msg.ID + "." + string(msg.Payload)))
return hex.EncodeToString(h.Sum(nil))
}
// VerifySignature 验证消息签名
func (c *Client) VerifySignature(msg *types.UnifiedSynapseMessage, secret string) bool {
if secret == "" {
return true
}
if msg.Signature == "" {
return false
}
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(msg.ID + "." + string(msg.Payload)))
expected := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(msg.Signature), []byte(expected))
}
// =============================================================================
// 消息发送底层
// =============================================================================
func (c *Client) sendMessage(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error {
msg.Signature = c.signMessage(msg)
body, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(body))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("X-Synapse-Signature", msg.Signature)
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("remote error: %s", string(respBody))
}
return nil
}
func (c *Client) sendRequest(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
msg.Signature = c.signMessage(msg)
body, err := json.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("failed to marshal message: %w", err)
}
httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("X-Synapse-Signature", msg.Signature)
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("remote error: %s", string(respBody))
}
var respMsg types.UnifiedSynapseMessage
if err := json.Unmarshal(respBody, &respMsg); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
return &respMsg, nil
}
// =============================================================================
// 等待任务完成
// =============================================================================
// WaitForTask 等待任务完成(轮询方式,通过回调更新状态)
func (c *Client) WaitForTask(ctx context.Context, taskID string, timeout time.Duration) (*P2PTaskState, error) {
deadline := time.Now().Add(timeout)
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
if time.Now().After(deadline) {
return nil, fmt.Errorf("task timeout")
}
state, ok := c.GetTaskState(taskID)
if !ok {
continue
}
switch state.Status {
case types.P2PStatusCompleted, types.P2PStatusFailed, types.P2PStatusCancelled, types.P2PStatusRejected:
return state, nil
}
}
}
}
// =============================================================================
// 回调处理
// =============================================================================
// HandleMessage 处理收到的消息(供 HTTP 服务调用)
func (c *Client) HandleMessage(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error) {
if c.authSecret != "" && !c.VerifySignature(msg, c.authSecret) {
return nil, fmt.Errorf("invalid signature")
}
switch types.P2PMessageType(msg.Type) {
case types.P2PTaskRequest:
if c.callbackHandler != nil {
return c.callbackHandler.HandleTaskRequest(ctx, msg)
}
return c.ackMessage(msg), nil
case types.P2PTaskConfirm:
var payload types.TaskConfirmPayload
_ = json.Unmarshal(msg.Payload, &payload)
c.setTaskState(payload.TaskID, types.P2PStatusConfirmed, 0, "任务已确认")
return c.ackMessage(msg), nil
case types.P2PTaskReject:
var payload types.TaskRejectPayload
_ = json.Unmarshal(msg.Payload, &payload)
c.setTaskState(payload.TaskID, types.P2PStatusRejected, 0, payload.Reason.Message)
return c.ackMessage(msg), nil
case types.P2PTaskStatusUpdate:
var payload types.TaskStatusUpdatePayload
_ = json.Unmarshal(msg.Payload, &payload)
c.setTaskState(payload.TaskID, types.P2PTaskStatus(payload.Status), payload.Progress, payload.Message)
if c.callbackHandler != nil {
_ = c.callbackHandler.HandleTaskStatusUpdate(ctx, msg)
}
return c.ackMessage(msg), nil
case types.P2PTaskComplete:
var payload types.TaskCompletePayload
_ = json.Unmarshal(msg.Payload, &payload)
c.setTaskState(payload.TaskID, types.P2PStatusCompleted, 100, payload.Summary)
c.setTaskResult(payload.TaskID, payload.Result.Output)
if c.callbackHandler != nil {
_ = c.callbackHandler.HandleTaskComplete(ctx, msg)
}
return c.ackMessage(msg), nil
case types.P2PTaskFailed:
var payload types.TaskFailedPayload
_ = json.Unmarshal(msg.Payload, &payload)
c.setTaskState(payload.TaskID, types.P2PStatusFailed, 0, payload.Error.Message)
c.setTaskError(payload.TaskID, types.NewSynapseError(
types.SynapseErrorCode(0),
payload.Error.Message,
nil,
))
if c.callbackHandler != nil {
_ = c.callbackHandler.HandleTaskFailed(ctx, msg)
}
return c.ackMessage(msg), nil
case types.P2PTaskNeedHuman:
var payload types.TaskNeedHumanPayload
_ = json.Unmarshal(msg.Payload, &payload)
c.setTaskState(payload.TaskID, types.P2PStatusNeedHuman, 0, payload.Reason.Message)
if c.callbackHandler != nil {
_ = c.callbackHandler.HandleNeedHuman(ctx, msg)
}
return c.ackMessage(msg), nil
case types.P2PCapabilityQuery:
return nil, nil
default:
return c.ackMessage(msg), nil
}
}
func (c *Client) ackMessage(msg *types.UnifiedSynapseMessage) *types.UnifiedSynapseMessage {
return &types.UnifiedSynapseMessage{
ID: types.GenerateMessageID(),
Version: msg.Version,
Type: string(types.P2PAck),
Sender: c.agentEndpoint,
Receiver: &msg.Sender,
ReplyTo: msg.ID,
Payload: []byte("{}"),
Timestamp: types.NowTimestamp(),
}
}

View File

@@ -0,0 +1,328 @@
# 灵隙原始协议(Synapse Raw Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:纲领性文档
---
## 1. 协议愿景
灵隙(Synapse,原 Agent-to-Agent)协议旨在定义一套通用的、可扩展的智能体间通信标准,使不同系统、不同厂商、不同能力的智能体能够在统一的协议框架下进行协作、交易与能力组合。
本协议是灵隙协议族(Synapse Protocol Suite)的原始纲领,定义核心设计理念、架构原则与术语体系,为所有衍生协议提供顶层指导。
---
## 2. 核心设计理念
### 2.1 去中心化对等(Peer-to-Peer)
- 所有 Agent 在协议层面地位平等,无中心化控制节点
- 每个 Agent 既是服务提供者,也是服务消费者
- Agent 间直接通信,不依赖中间 broker(传输层可选择不同实现)
### 2.2 能力驱动(Capability-Driven)
- 通信以能力为核心,而非以身份为核心
- Agent 声明自身能力,按需发现与调用
- 能力可组合、可分解、可升级
### 2.3 任务导向(Task-Oriented)
- 所有交互围绕"任务"展开
- 任务有明确的生命周期、状态机与超时机制
- 任务支持拆分、合并、委派与协作
### 2.4 人类可介入(Human-in-the-Loop)
- 协议原生支持人类介入决策
- Agent 可主动请求人类判断
- 人类决策优先于 Agent 自动决策
- 支持多级升级(escalation)机制
### 2.5 群组协作(Group Collaboration)
- 支持多 Agent 组成临时或永久群组
- 群组内有角色分工与权限控制
- 支持任务拆分-执行-合并的 MapReduce 模式
- 支持投票、共识等群体决策机制
### 2.6 传输无关(Transport Agnostic)
- 协议语义与底层传输分离
- 支持 HTTP/2、gRPC、WebSocket、消息队列等多种传输
- 支持回调(Callback)、轮询(Polling)、同步(Sync)、混合(Hybrid)等多种交互模式
### 2.7 安全可信(Secure & Trustworthy)
- 消息级签名与验证
- Agent 身份认证与信任等级
- 支持加密传输
- 可审计、可追溯
---
## 3. 协议分层架构
```
┌─────────────────────────────────────────┐
│ 应用层 (Application) │
│ 业务逻辑、工作流、领域特定逻辑 │
├─────────────────────────────────────────┤
│ 协议层 (Protocol) │
├─────────────┬───────────────────────────┤
│ A2A 协议 │ A2N 组群协议 │ ← 本协议族
│ (P2P) │ (Group) │
├─────────────┴───────────────────────────┤
│ 传输层 (Transport) │
│ HTTP / gRPC / WebSocket / MQ ... │
├─────────────────────────────────────────┤
│ 网络层 (Network) │
│ TCP / UDP / QUIC ... │
└─────────────────────────────────────────┘
```
### 3.1 协议族构成
| 协议名称 | 缩写 | 描述 | 适用场景 |
|---------|------|------|---------|
| 灵隙原始协议(Synapse Raw Protocol) | A2A-Raw | 顶层纲领,定义理念、术语、原则 | 指导所有协议设计 |
| A2A 协议 | A2A-Base | 点对点任务协作协议 | 两个 Agent 间的任务交互 |
| A2N 组群协议 | A2N-Group | 多 Agent 群组协作协议 | 多个 Agent 协同完成任务 |
| P2A 调用协议 | P2A-Caller | 非 Agent 调用者接入协议 | 普通服务/前端调用 Agent 能力 |
---
## 4. 核心概念与术语
### 4.1 Agent(智能体)
能够自主感知环境、做出决策并执行动作的计算实体。
**属性:**
- `agent_id`:Agent 唯一标识
- `system_id`:所属系统标识
- `endpoint`:通信端点地址
- `capabilities`:能力清单
- `trust_level`:信任等级
### 4.2 Task(任务)
Agent 间协作的基本单位,具有明确的输入、输出与生命周期。
**属性:**
- `task_id`:任务唯一标识
- `task_type`:任务类型
- `payload`:任务载荷
- `status`:任务状态
- `priority`:优先级
- `deadline`:截止时间
### 4.3 Message(消息)
Agent 间通信的基本单位。
**属性:**
- `message_id`:消息唯一标识
- `type`:消息类型
- `sender`:发送方
- `receiver`:接收方(P2P)
- `group_id`:群组标识(群组)
- `payload`:消息载荷
- `correlation_id`:关联标识(用于请求-响应关联)
- `timestamp`:时间戳
- `signature`:签名
### 4.4 Capability(能力)
Agent 能够执行的任务类型的声明。
**属性:**
- `type`:能力对应的任务类型
- `name`:能力名称
- `description`:能力描述
- `input_schema`:输入参数 schema
- `output_schema`:输出结果 schema
- `estimated_time`:预估耗时
### 4.5 Group(群组)
由多个 Agent 组成的协作单元。
**属性:**
- `group_id`:群组唯一标识
- `name`:群组名称
- `topology`:拓扑结构
- `members`:成员列表
- `roles`:角色定义
- `status`:群组状态
### 4.6 Human-in-the-Loop(人在回路)
在 Agent 协作过程中,人类可介入进行决策的机制。
**关键概念:**
- `escalation`:升级(将问题提交给人类)
- `decision`:决策(人类做出的判断)
- `resume`:恢复(人类决策后继续执行)
---
## 5. 设计原则
### 5.1 简约性(Simplicity)
- 核心机制保持简单
- 复杂功能通过组合基础机制实现
- 可选功能通过能力声明来协商
### 5.2 可扩展性(Extensibility)
- 消息类型可扩展
- 能力类型可扩展
- 传输方式可扩展
- 通过版本号与能力协商保证向后兼容
### 5.3 容错性(Fault Tolerance)
- 网络不可靠是常态
- 消息可能丢失、重复、延迟
- Agent 可能故障、离线
- 协议必须有重试、超时、降级机制
### 5.4 可观测性(Observability)
- 所有交互可追踪
- 任务状态可查询
- 支持日志、指标、链路追踪
### 5.5 互操作性(Interoperability)
- 不同语言、不同框架的 Agent 可互操作
- 协议规范优先于具体实现
- 提供标准测试用例验证兼容性
---
## 6. 消息模型总则
### 6.1 统一消息信封
所有协议共享统一的消息信封结构,具体载荷由消息类型决定。
```
UnifiedSynapseMessage
├── id // 消息ID
├── version // 协议版本
├── type // 消息类型
├── sender // 发送方 AgentEndpoint
├── receiver? // 接收方(P2P 时)
├── group_id? // 群组ID(群组时)
├── correlation_id // 关联ID
├── reply_to? // 回复的消息ID
├── payload // 消息载荷(JSON)
├── priority? // 优先级
├── signature? // 签名
├── timestamp // 发送时间
└── expires_at? // 过期时间
```
### 6.2 消息命名规范
- 使用 `领域:动作` 格式
- 全小写,用下划线分隔
- 示例:`p2p:task_request`, `group:create`, `hil:decision`
### 6.3 请求-响应模式
- 请求消息必须携带唯一 `id`
- 响应消息通过 `reply_to` 关联请求
- 支持 ACK/NACK 机制
- 超时未收到响应应重试或报错
---
## 7. 任务生命周期总则
### 7.1 状态机
```
┌──────────┐
│ pending │ 待确认
└────┬─────┘
│ 确认/拒绝
┌─────┴──────┐
▼ ▼
┌─────────┐ ┌──────────┐
│confirmed│ │ rejected │ 已拒绝(终态)
└────┬────┘ └──────────┘
│ 开始执行
▼
┌──────────┐
│executing │ 执行中
└────┬─────┘
│ 信息缺失
┌────▼─────┐
│info_needed│ 需要信息
└────┬─────┘
│ 信息补充
▼
回到 executing
│
├──────────────┐
│ 需人类决策 │
▼ ▼
┌──────────┐ ┌───────────┐
│need_human│ │ 各种终态 │
└────┬─────┘ └───────────┘
│ 人类决策
▼
回到 executing
│
┌────┴────┬─────────┐
▼ ▼ ▼
completed failed cancelled
(完成) (失败) (取消)
```
### 7.2 终态
- `completed`:成功完成
- `failed`:执行失败
- `cancelled`:被取消
- `rejected`:被拒绝
---
## 8. 安全模型总则
### 8.1 身份认证
- 每个 Agent 有唯一身份
- 支持 HMAC、JWT、OAuth2 等多种认证方式
- 信任等级机制
### 8.2 消息完整性
- 消息签名机制
- 防止篡改与伪造
### 8.3 机密性
- 支持端到端加密
- 敏感载荷加密传输
### 8.4 访问控制
- 基于信任等级的权限控制
- 群组内基于角色的权限控制
---
## 9. 非目标
以下内容**不在**本原始协议的讨论范围内:
1. **具体传输协议的实现细节** - 由各传输层实现定义
2. **特定领域的任务类型** - 由应用层或领域扩展定义
3. **Agent 内部实现架构** - 仅定义对外接口与行为
4. **具体编程语言绑定** - 语言无关,可多语言实现
5. **UI/UX 设计** - 人机界面不在协议范围内
---
## 10. 参考实现
本协议族的参考实现位于:`dp-headless/synapse/`
| 模块 | 对应协议 | 文件路径 |
|------|---------|---------|
| types 包 | 公共类型定义 | `types/` |
| p2p 包 | A2A 协议 | `p2p/` |
| group 包 | A2N 组群协议 | `group/` |
| externalapi 包 | P2A 调用协议 | `externalapi/` |
| sdk 包 | P2A 客户端 SDK | `sdk/` |
---
## 附录 A:协议演进
本原始协议作为顶层指导,保持相对稳定。各具体协议可独立版本化演进,但需遵循本协议定义的核心原则。
**版本历史:**
- v1.0.0:初始版本,定义核心概念与设计原则

View File

@@ -0,0 +1,465 @@
# A2A 协议(Agent-to-Agent Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:实现规范
> 基于:[灵隙原始协议(Synapse Raw Protocol) v1.0.0](./01-synapse-raw-protocol.md)
---
## 1. 概述
A2A 协议定义了两个 Agent 之间进行点对点(P2P)任务协作的完整规范,包括消息类型、交互流程、状态机、接口定义与错误处理。
### 1.1 适用场景
- 单个任务请求与响应
- 任务状态实时同步
- 任务取消
- 信息补全交互
- 人类介入决策
### 1.2 非适用场景
- 多 Agent 群组协作(参见 A2N 组群协议)
- 非 Agent 调用方接入(参见 P2A 调用协议)
---
## 2. 消息类型
### 2.1 消息类型清单
所有消息类型前缀:`p2p:`
| 消息类型 | 方向 | 说明 |
|---------|------|------|
| `p2p:task_request` | 请求方 → 执行方 | 提交任务请求 |
| `p2p:task_confirm` | 执行方 → 请求方 | 确认接受任务 |
| `p2p:task_reject` | 执行方 → 请求方 | 拒绝任务 |
| `p2p:task_need_info` | 执行方 → 请求方 | 请求补充信息 |
| `p2p:task_info_provided` | 请求方 → 执行方 | 提供补充信息 |
| `p2p:task_status_update` | 执行方 → 请求方 | 任务进度更新 |
| `p2p:task_complete` | 执行方 → 请求方 | 任务完成 |
| `p2p:task_failed` | 执行方 → 请求方 | 任务失败 |
| `p2p:task_cancel` | 请求方 → 执行方 | 取消任务 |
| `p2p:task_ack` | 双向 | 任务级确认 |
| `p2p:ack` | 双向 | 通用确认 |
| `p2p:nack` | 双向 | 通用否定确认(错误) |
| `p2p:heartbeat` | 双向 | 心跳保活 |
| `p2p:capability_query` | 双向 | 查询对方能力 |
| `p2p:capability_response` | 双向 | 返回能力清单 |
| `p2p:task_need_human` | 执行方 → 请求方 | 请求人类介入 |
| `p2p:human_decision` | 请求方 → 执行方 | 人类决策结果 |
### 2.2 消息类型完整定义
参见代码:[types/types.go](../types/types.go)
---
## 3. 任务状态机
### 3.1 状态定义
| 状态 | 说明 |
|------|------|
| `idle` | 空闲(初始状态) |
| `pending` | 待确认(已发送请求,等待确认) |
| `confirmed` | 已确认(对方接受任务) |
| `executing` | 执行中 |
| `info_needed` | 需要信息 |
| `info_provided` | 信息已提供 |
| `need_human` | 需要人类介入 |
| `human_deciding` | 人类决策中 |
| `human_approved` | 人类已批准 |
| `human_rejected` | 人类已拒绝 |
| `completed` | 已完成(终态) |
| `failed` | 已失败(终态) |
| `cancelled` | 已取消(终态) |
| `rejected` | 已拒绝(终态) |
### 3.2 状态流转图
```
请求方视角:
idle → pending → confirmed → executing → completed
↓ ↓ → failed
rejected info_needed → cancelled
↑↓
info_provided
↓
need_human
↑↓
human_decision
```
---
## 4. 核心交互流程
### 4.1 正常任务提交流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->| 1. 提交任务
| |
|<------- task_confirm / task_reject -| 2. 确认/拒绝
| |
|<------- task_status_update --------| 3. 进度更新(多次)
| |
|<------- task_complete -------------| 4. 完成通知
| |
|-------- task_ack ----------------->| 5. 确认收到
| |
```
### 4.2 信息补全流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->|
| |
|<------- task_need_info ------------| 发现信息缺失
| |
|-------- task_info_provided ------->| 补充信息
| |
|<------- task_status_update --------| 继续执行
| |
```
### 4.3 人类介入流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->|
| |
|<------- task_need_human -----------| 需要人类决策
| |
| [人类做出决策] |
| |
|-------- human_decision ----------->| 决策结果
| |
|<------- task_status_update --------| 继续执行
| |
```
### 4.4 任务取消流程
```
请求方 Agent 执行方 Agent
| |
|-------- task_request ------------->|
| |
|-------- task_cancel -------------->| 取消任务
| |
|<------- task_ack ------------------| 确认取消
| |
```
---
## 5. 接口规范
### 5.1 P2P 协议处理器接口
**接口定义:**
```go
type P2PProtocol interface {
// 处理收到的消息
HandleMessage(ctx context.Context, msg *UnifiedSynapseMessage) (*UnifiedSynapseMessage, error)
// 设置任务处理器
SetTaskHandler(h TaskHandler)
// 设置回调发送器
SetCallbackSender(s CallbackSender)
// 获取 Agent 端点
AgentEndpoint() AgentEndpoint
// 查询任务状态
GetTaskStatus(taskID string) (P2PTaskStatus, bool)
// 列出所有任务
ListTasks() map[string]P2PTaskStatus
}
```
**参考实现:** [p2p/protocol.go](../p2p/protocol.go)
### 5.2 任务处理器接口
```go
type TaskHandler interface {
// 执行任务并返回结果
ExecuteTask(ctx context.Context, taskType string, payload map[string]interface{}) (any, error)
// 验证任务是否可执行,返回缺失字段列表和是否可处理
ValidateTask(taskType string, payload map[string]interface{}) ([]MissingField, bool)
// 获取能力列表
GetCapabilities() []string
}
```
**说明:**
- `ExecuteTask`:实际执行业务逻辑的入口
- `ValidateTask`:在任务确认前调用,用于判断是否接受任务或需要补充信息
- `GetCapabilities`:返回 Agent 支持的任务类型列表
### 5.3 回调发送器接口
```go
type CallbackSender interface {
// 向远程 Agent 发送消息
SendCallback(ctx context.Context, endpoint string, msg *UnifiedSynapseMessage) error
}
```
**说明:**
- 传输层实现此接口
- 支持多种传输方式(HTTP、gRPC、WebSocket 等)
### 5.4 配置项
```go
type P2PConfig struct {
MaxRetries int // 最大重试次数(默认 5)
RetryBaseDelay time.Duration // 重试基础延迟(默认 2s,指数退避)
TaskTimeout time.Duration // 任务执行超时(默认 5 分钟)
HeartbeatInterval time.Duration // 心跳间隔(默认 30 秒)
}
```
---
## 6. 载荷格式
### 6.1 TaskRequestPayload(任务请求)
```json
{
"task": {
"id": "task-xxx",
"type": "text_generation",
"priority": "normal",
"deadline": "2026-01-01T00:00:00Z",
"payload": {
"prompt": "写一首诗",
"style": "古典"
}
},
"context": {
"session_id": "sess-xxx",
"parent_task_id": "parent-xxx",
"correlation_id": "corr-xxx",
"metadata": {}
}
}
```
### 6.2 TaskConfirmPayload(任务确认)
```json
{
"task_id": "task-xxx",
"confirmed": true,
"estimated_duration": 120,
"expected_completion": "2026-01-01T00:02:00Z",
"capabilities_used": ["text_generation"],
"constraints": {
"max_retries": 3,
"fallback_enabled": true
}
}
```
### 6.3 TaskRejectPayload(任务拒绝)
```json
{
"task_id": "task-xxx",
"reason": {
"code": "capability_lacking",
"message": "不支持该任务类型",
"details": null
},
"alternatives": {
"suggested_agent_id": "other-agent",
"retry_after": 300,
"partial_support": false
}
}
```
### 6.4 TaskStatusUpdatePayload(状态更新)
```json
{
"task_id": "task-xxx",
"status": "executing",
"progress": 50,
"message": "正在生成内容",
"intermediate_result": null
}
```
### 6.5 TaskCompletePayload(任务完成)
```json
{
"task_id": "task-xxx",
"result": {
"output_type": "text",
"output": "生成的文本内容...",
"metadata": {
"tokens": 1024,
"model": "gpt-4"
}
},
"summary": "任务已完成"
}
```
### 6.6 TaskFailedPayload(任务失败)
```json
{
"task_id": "task-xxx",
"error": {
"code": "execution_error",
"message": "模型调用超时",
"recoverable": true,
"details": null
},
"partial_result": null
}
```
### 6.7 TaskNeedInfoPayload(需要信息)
```json
{
"task_id": "task-xxx",
"missing_fields": [
{
"field": "style",
"reason": "未指定生成风格",
"suggested_value": "古典",
"alternatives": ["现代", "浪漫"]
}
],
"question": "请选择生成风格",
"required_by": "2026-01-01T00:01:00Z"
}
```
### 6.8 TaskNeedHumanPayload(需要人类)
```json
{
"task_id": "task-xxx",
"reason": {
"code": "ethical_decision",
"message": "涉及伦理决策需要人工审核"
},
"context": {
"task_summary": "生成一张人物肖像",
"current_progress": 30,
"blocked_reason": "需要确认肖像使用授权"
},
"options": [
{
"field": "authorized",
"value": true,
"pros_cons": "继续生成,假设已授权",
"confidence": 70
}
],
"escalation_level": 1,
"deadline": "2026-01-01T00:05:00Z"
}
```
### 6.9 HumanDecisionPayload(人类决策)
```json
{
"task_id": "task-xxx",
"decision_id": "dec-xxx",
"reply_to": "msg-xxx",
"decided_by": {
"user_id": "user-123",
"user_name": "张三",
"role": "reviewer"
},
"decision": {
"action": "approve",
"approved_fields": {
"authorized": true
},
"rejection_reason": ""
},
"notes": "已确认授权",
"timestamp": "2026-01-01T00:03:00Z"
}
```
**完整载荷定义:** [types/types.go](../types/types.go)
---
## 7. 错误码
| 错误码 | 说明 | HTTP 状态码(如适用) |
|--------|------|---------------------|
| `message_format_invalid` | 消息格式无效 | 400 |
| `capability_lacking` | 能力不足 | 403 |
| `agent_not_ready` | Agent 未就绪 | 503 |
| `task_not_found` | 任务不存在 | 404 |
| `task_already_completed` | 任务已完成 | 409 |
| `execution_timeout` | 执行超时 | 504 |
| `execution_failed` | 执行失败 | 500 |
| `permission_denied` | 权限不足 | 403 |
| `invalid_params` | 参数无效 | 400 |
| `rate_limited` | 限流 | 429 |
**完整错误定义:** [types/errors.go](../types/errors.go)
---
## 8. 传输要求
### 8.1 HTTP 传输
- Content-Type: `application/json`
- 请求方法:`POST`
- 端点路径:由 Agent 自行定义,建议 `/synapse/callback`
- 超时:建议 30 秒
### 8.2 签名验证(可选)
- 使用 HMAC-SHA256 签名
- 签名放在 Header:`X-Synapse-Signature`
- 或放在消息体:`signature` 字段
---
## 9. 参考实现
本协议的参考实现位于 `p2p/` 目录:
| 文件 | 说明 |
|------|------|
| [p2p/protocol.go](../p2p/protocol.go) | P2P 协议核心实现 |
| [types/types.go](../types/types.go) | 数据类型定义 |
| [types/errors.go](../types/errors.go) | 错误定义 |
| [transport/transport.go](../transport/transport.go) | HTTP 传输实现 |
---
## 附录 A:能力声明格式
参见 [灵隙原始协议(Synapse Raw Protocol) - Capability](./01-synapse-raw-protocol.md#44-capability能力)

View File

@@ -0,0 +1,545 @@
# A2N 协议(Agent-to-N Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:实现规范
> 基于:[灵隙原始协议(Synapse Raw Protocol) v1.0.0](./01-synapse-raw-protocol.md)
---
## 1. 概述
A2N(Agent-to-N)组群协议定义了多个 Agent 组成群组进行协同工作的完整规范,包括群组管理、任务拆分与合并、群体决策、角色与权限等。
### 1.1 适用场景
- 复杂任务的拆分与并行执行(MapReduce 模式)
- 多 Agent 协同完成单一目标
- 群体投票与共识决策
- 临时或永久的 Agent 工作组
- 分级升级(escalation)机制
### 1.2 依赖
- 依赖 A2A 协议的点对点通信能力
- 群组内成员间的单播通信使用 A2A 协议
---
## 2. 核心概念
### 2.1 Group(群组)
由多个 Agent 组成的协作单元,具有明确的拓扑结构、角色分工与协作规则。
### 2.2 Topology(拓扑结构)
| 拓扑类型 | 说明 | 适用场景 |
|---------|------|---------|
| `star` | 星型,中心 coordinator 协调 | 简单任务分发 |
| `mesh` | 网状,成员间可直接通信 | 对等协作 |
| `chain` | 链式,流水线模式 | 顺序处理 |
| `hierarchical` | 层级型,多层管理 | 大型复杂任务 |
| `fullmesh` | 全连接网状 | 高可靠协作 |
### 2.3 Role(角色)
| 角色 | 说明 | 权限 |
|------|------|------|
| `coordinator` | 协调者 | 创建/解散群组、任务拆分、发起投票 |
| `worker` | 工作者 | 执行子任务、提交结果 |
| `reviewer` | 审核者 | 审核结果、发起投票 |
| `observer` | 观察者 | 只读,查看进度 |
### 2.4 GroupStatus(群组状态)
| 状态 | 说明 |
|------|------|
| `creating` | 创建中 |
| `active` | 活跃 |
| `frozen` | 冻结(不可执行新任务) |
| `disbanded` | 已解散(终态) |
---
## 3. 消息类型
### 3.1 消息类型清单
所有消息类型前缀:`group:` 或 `inter_group:`
#### 群组管理
| 消息类型 | 说明 |
|---------|------|
| `group:create` | 创建群组 |
| `group:disband` | 解散群组 |
| `group:join` | 加入群组 |
| `group:leave` | 离开群组 |
| `group:invite` | 邀请加入 |
| `group:accept_invite` | 接受邀请 |
| `group:reject_invite` | 拒绝邀请 |
| `group:member_update` | 成员更新通知 |
| `group:role_change` | 角色变更 |
#### 群组通信
| 消息类型 | 说明 |
|---------|------|
| `group:broadcast` | 广播(所有成员) |
| `group:multicast` | 组播(指定成员) |
| `group:direct` | 群内单播 |
| `group:relay` | 消息中继 |
#### 任务协作
| 消息类型 | 说明 |
|---------|------|
| `group:task_split` | 任务拆分 |
| `group:task_assign` | 任务分配 |
| `group:task_merge` | 结果合并 |
| `group:task_progress` | 子任务进度 |
#### 共识与投票
| 消息类型 | 说明 |
|---------|------|
| `group:vote` | 发起投票 |
| `group:vote_submit` | 提交投票 |
| `group:consensus` | 达成共识 |
#### 人在回路(HiL)
| 消息类型 | 说明 |
|---------|------|
| `group:consensus_escalate` | 共识僵局升级给人类 |
| `group:subtask_escalate` | 子任务阻塞升级给人类 |
| `group:human_decision` | 人类决策结果 |
| `inter_group:escalate` | 跨群组升级 |
**完整定义:** [types/types.go](../types/types.go)
---
## 4. 群组生命周期
### 4.1 状态机
```
creating → active → frozen → disbanded
↑ ↓
└─────────┘
解冻
```
### 4.2 群组创建流程
```
创建者 被邀请成员
| |
|------ group:invite -------------->| 发送邀请
| |
|<----- group:accept_invite --------| 接受邀请
| |
|------ group:member_update ------->| 通知成员更新
| |
[群组创建完成,状态变为 active] |
```
### 4.3 群组解散流程
```
coordinator 所有成员
| |
|------ group:disband ------------->| 通知解散
| |
[群组状态变为 disbanded] |
```
---
## 5. 任务协作流程
### 5.1 MapReduce 模式(拆分-执行-合并)
```
┌──────────────┐
│ coordinator │
└──────┬───────┘
│
group:task_split(拆分任务)
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ worker1 │ │ worker2 │ │ worker3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
│ 执行子任务 │ 执行子任务 │ 执行子任务
│ │ │
▼ ▼ ▼
子任务结果 子任务结果 子任务结果
└────────────────┼────────────────┘
│
group:task_merge(合并结果)
▼
┌──────────────┐
│ 最终结果 │
└──────────────┘
```
### 5.2 详细流程
1. **任务拆分阶段**
- coordinator 接收原始任务
- 将任务拆分为多个子任务(subtask)
- 为每个子任务指定执行的 worker
- 定义合并策略(merge strategy)
2. **并行执行阶段**
- 各 worker 并行执行子任务
- 定期上报进度(`group:task_progress`)
- 子任务完成后上报结果
3. **结果合并阶段**
- 所有子任务完成后,coordinator 合并结果
- 根据合并策略决定最终结果
- 输出最终结果
---
## 6. 合并策略(Merge Strategy)
| 策略 | 说明 |
|------|------|
| `all_success` | 全部成功才认为成功,否则失败 |
| `majority` | 多数子任务成功即可,取多数结果 |
| `any_complete` | 任意一个完成即返回结果 |
| `priority` | 按优先级,高优先级子任务结果优先 |
---
## 7. 投票与共识
### 7.1 投票流程
```
发起者 投票者
| |
|-------- group:vote -------------->| 发起投票
| |
|<------- group:vote_submit --------| 提交投票
| |
[计票,判断是否达到阈值] |
| |
|-------- group:consensus --------->| 公布结果
| |
```
### 7.2 投票规则
- `threshold`:通过阈值(0-1),如 0.5 表示过半
- `deadline`:投票截止时间
- `voters`:有投票权的成员列表
- 超时未投票视为弃权
### 7.3 共识僵局(Consensus Deadlock)
当投票无法达成共识(如平票、未达阈值且超时)时,可升级给人类决策。
---
## 8. 人在回路(HiL)
群组协作中有两种升级场景:
### 8.1 共识僵局升级
投票无法达成共识时,升级给人类裁决。
**消息:** `group:consensus_escalate`
### 8.2 子任务阻塞升级
某个子任务遇到无法自动解决的问题时,升级给人类。
**消息:** `group:subtask_escalate`
### 8.3 跨群组升级
问题超出当前群组能力时,可升级到上级群组或人类。
**消息:** `inter_group:escalate`
---
## 9. 接口规范
### 9.1 GroupProtocol 接口
```go
type GroupProtocol interface {
// 处理群组消息
HandleMessage(ctx context.Context, msg *UnifiedSynapseMessage) (*UnifiedSynapseMessage, error)
// 设置任务处理器
SetTaskHandler(h GroupTaskHandler)
// 查询群组列表
GetGroups() []*AgentGroup
// 查询特定群组
GetGroup(groupID string) (*AgentGroup, bool)
}
```
**参考实现:** [group/protocol.go](../group/protocol.go)
### 9.2 GroupTaskHandler 接口
```go
type GroupTaskHandler interface {
// 执行子任务
ExecuteSubtask(ctx context.Context, subtaskID string, payload map[string]interface{}) (any, error)
// 合并多个子任务结果
MergeResults(results []SubtaskResult, strategy MergeStrategy) (any, error)
// 验证任务是否可拆分,返回拆分方案
ValidateTaskSplit(taskType string, requirements map[string]interface{}) ([]Subtask, bool)
}
```
**说明:**
- `ExecuteSubtask`:执行单个子任务的业务逻辑
- `MergeResults`:根据策略合并多个子任务的结果
- `ValidateTaskSplit`:验证任务是否可拆分,并给出拆分方案
### 9.3 配置项
```go
type GroupConfig struct {
MaxGroupSize int // 最大群组人数(默认 20)
MaxSubtasks int // 最大子任务数(默认 50)
ConsensusTimeout time.Duration // 共识超时(默认 5 分钟)
TaskTimeout time.Duration // 任务超时(默认 10 分钟)
}
```
---
## 10. 载荷格式
### 10.1 GroupCreatePayload(创建群组)
```json
{
"group": {
"name": "视频制作组",
"topology": "star",
"config": {
"allow_self_leave": true,
"requires_approval": false,
"consensus_required": false,
"consensus_threshold": 0.5,
"broadcast_mode": "all"
},
"initial_members": [
{
"agent_id": "worker-1",
"system_id": "sys-a",
"endpoint": "http://worker1/synapse"
}
],
"roles": [
{
"id": "coordinator",
"name": "协调者",
"permissions": ["create", "disband", "split", "vote"],
"max_tasks": 10
}
]
}
}
```
### 10.2 GroupTaskSplitPayload(任务拆分)
```json
{
"original_task": {
"id": "task-xxx",
"type": "video_generation",
"requirements": {
"script": "xxx",
"duration": 60
},
"deadline": "2026-01-01T00:10:00Z"
},
"subtasks": [
{
"id": "sub-1",
"description": "生成脚本",
"assigned_to": "writer-agent",
"dependencies": [],
"estimated_duration": 120,
"priority": 1
},
{
"id": "sub-2",
"description": "生成配音",
"assigned_to": "voice-agent",
"dependencies": ["sub-1"],
"estimated_duration": 180,
"priority": 2
}
],
"merge_strategy": "all_success"
}
```
### 10.3 GroupTaskMergePayload(结果合并)
```json
{
"original_task_id": "task-xxx",
"subtask_results": [
{
"subtask_id": "sub-1",
"assigned_to": "writer-agent",
"status": "success",
"result": { "script": "..." },
"completed_at": "2026-01-01T00:02:00Z"
}
],
"merge_strategy_used": "all_success",
"merged_result": {
"video_url": "http://..."
},
"consensus_achieved": true
}
```
### 10.4 GroupVotePayload(投票)
```json
{
"vote_id": "vote-xxx",
"question": "是否采用方案A?",
"options": ["方案A", "方案B", "方案C"],
"voters": ["agent-1", "agent-2", "agent-3"],
"deadline": "2026-01-01T00:05:00Z",
"threshold": 0.66,
"status": "open",
"votes": []
}
```
### 10.5 GroupConsensusEscalatePayload(共识升级)
```json
{
"vote_id": "vote-xxx",
"escalation_id": "esc-xxx",
"question": "是否采用方案A?",
"votes": [
{
"voter_id": "agent-1",
"voter_role": "reviewer",
"option": "方案A",
"reason": "质量更好"
}
],
"consensus_status": {
"required_threshold": 0.66,
"actual_threshold": 0.33,
"leading_option": "方案A",
"options_tied": false
},
"escalate_reason": {
"code": "no_consensus",
"message": "无法达成共识"
},
"escalation_level": 2,
"deadline": "2026-01-01T00:10:00Z"
}
```
**完整载荷定义:** [types/types.go](../types/types.go)
---
## 11. 数据结构
### 11.1 AgentGroup(群组)
```go
type AgentGroup struct {
ID string
Name string
Topology GroupTopology
CreatedBy string
CreatedAt Timestamp
Config GroupConfig
Members []GroupMember
Roles []GroupRole
Status GroupStatus
}
```
### 11.2 GroupMember(群组成员)
```go
type GroupMember struct {
AgentID string
AgentName string
Role string
JoinedAt Timestamp
Status GroupMemberStatus
Capabilities []string
LoadFactor float64 // 负载因子 0-1,用于任务分配
}
```
### 11.3 Subtask(子任务)
```go
type Subtask struct {
ID string
Description string
AssignedTo string
Dependencies []string
EstimatedDuration int
Priority int
}
```
---
## 12. 错误码
群组协议特有错误码:
| 错误码 | 说明 |
|--------|------|
| `group_not_found` | 群组不存在 |
| `group_not_active` | 群组不活跃 |
| `group_full` | 群组已满 |
| `already_member` | 已是成员 |
| `not_group_member` | 不是群组成员 |
| `member_not_found` | 成员不存在 |
| `permission_denied` | 权限不足 |
| `invalid_params` | 参数无效 |
---
## 13. 参考实现
本协议的参考实现位于 `group/` 目录:
| 文件 | 说明 |
|------|------|
| [group/protocol.go](../group/protocol.go) | 群组协议核心实现 |
| [types/types.go](../types/types.go) | 群组相关数据类型 |
---
## 附录 A:与 A2A 协议的关系
- 群组内的点对点通信使用 A2A 协议
- 群组协议在 A2A 协议之上提供群组管理、任务拆分合并、共识投票等能力
- 群组消息同样使用统一消息信封(`UnifiedSynapseMessage`),通过 `group_id` 字段标识群组上下文

View File

@@ -0,0 +1,503 @@
# P2A 协议(Person/Program-to-Agent Protocol)
> 版本:1.0.0
> 状态:草案
> 类型:实现规范
> 基于:[灵隙原始协议(Synapse Raw Protocol) v1.0.0](./01-synapse-raw-protocol.md)
---
## 1. 概述
P2A(Person/Program-to-Agent)调用协议定义了**非 Agent 调用方**(如普通后端服务、前端应用、第三方系统、人类用户)如何调用 Agent 能力的规范。
本协议将 A2A 协议的复杂性封装起来,提供简洁的 RESTful API,使调用方无需了解 A2A 协议细节即可使用 Agent 能力。
### 1.1 适用场景
- 前端应用直接调用 Agent 能力
- 后端业务系统集成 Agent 服务
- 第三方开发者通过 API 接入
- 无需实现完整 A2A 协议的调用方
### 1.2 设计原则
- **简单易用**:标准 RESTful API,JSON 格式
- **两种模式**:回调模式 + 轮询模式,调用方按需选择
- **认证简单**:API Key 认证,无需 HMAC 签名
- **文件支持**:支持 multipart/form-data 上传文件
- **错误友好**:清晰的错误码和错误信息
### 1.3 与 A2A 协议的关系
```
外部调用方 ──P2A 协议──▶ External API Layer ──A2A 协议──▶ Agent
```
P2A 协议是 A2A 协议的"简化入口",所有请求最终会转换为 灵隙消息发送给 Agent。
---
## 2. 认证方式
### 2.1 API Key 认证
- 在 HTTP Header 中传递:`X-API-Key: {api_key}`
- 或在 URL Query 中传递:`?api_key={api_key}`
- 如果服务端未配置 API Key,则跳过认证
### 2.2 回调验证(可选)
如果调用方提供了 `callback_token`,则服务端在回调时会在 Header 中带上 `X-Callback-Token`,用于调用方验证回调来源。
---
## 3. API 端点
所有端点默认前缀:`/api/v1/synapse`
### 3.1 端点清单
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/task` | 提交任务(JSON) |
| POST | `/task/multipart` | 提交任务(带文件上传) |
| GET | `/task/{task_id}` | 获取任务详情 |
| GET | `/task/{task_id}/status` | 获取任务状态(简化版) |
| GET | `/tasks` | 获取任务列表 |
| POST | `/task/{task_id}/cancel` | 取消任务 |
| GET | `/capabilities` | 获取能力清单 |
| POST | `/callback` | 接收回调(调用方实现) |
---
## 4. 任务提交
### 4.1 提交任务(JSON)
**请求:**
```http
POST /api/v1/synapse/task
Content-Type: application/json
X-API-Key: your-api-key
{
"task_type": "text_generation",
"payload": {
"prompt": "写一首诗",
"style": "古典"
},
"external_task_id": "my-task-001",
"priority": "normal",
"callback_url": "https://your-domain.com/callback",
"callback_token": "your-callback-token"
}
```
**请求字段说明:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `task_type` | string | 是 | 任务类型,如 `video_generation`, `image_generation` |
| `payload` | object | 是 | 任务参数,具体结构取决于 task_type |
| `external_task_id` | string | 否 | 调用方的任务 ID,用于关联 |
| `priority` | string | 否 | 优先级:`low`, `normal`, `high`, `urgent`,默认 `normal` |
| `callback_url` | string | 否 | 回调 URL,不提供则需轮询 |
| `callback_token` | string | 否 | 回调验证 token |
**响应:**
```json
{
"success": true,
"task_id": "task-abc123",
"status": "received",
"message": "任务已接收"
}
```
**响应字段说明:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | bool | 是否成功 |
| `task_id` | string | 服务端生成的任务 ID |
| `status` | string | 初始状态,通常为 `received` |
| `message` | string | 提示信息 |
| `error` | string | 错误信息(失败时) |
### 4.2 提交任务(Multipart 带文件)
**请求:**
```http
POST /api/v1/synapse/task/multipart
Content-Type: multipart/form-data
X-API-Key: your-api-key
------WebKitFormBoundary
Content-Disposition: form-data; name="task_type"
image_generation
------WebKitFormBoundary
Content-Disposition: form-data; name="payload"
{"prompt":"一只猫","style":"写实"}
------WebKitFormBoundary
Content-Disposition: form-data; name="priority"
normal
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="reference.jpg"
Content-Type: image/jpeg
[二进制数据]
------WebKitFormBoundary--
```
**表单字段:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `task_type` | string | 是 | 任务类型 |
| `payload` | string(JSON) | 是 | 任务参数(JSON 字符串) |
| `external_task_id` | string | 否 | 外部任务 ID |
| `priority` | string | 否 | 优先级 |
| `callback_url` | string | 否 | 回调 URL |
| `callback_token` | string | 否 | 回调 token |
| `file` | file | 否 | 上传的文件(可多个) |
**响应:** 同 JSON 提交方式
---
## 5. 任务查询
### 5.1 获取任务详情
**请求:**
```http
GET /api/v1/synapse/task/{task_id}
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"task": {
"id": "task-abc123",
"type": "text_generation",
"status": "completed",
"progress": 100,
"message": "任务已完成",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:01:00Z",
"completed_at": "2026-01-01T00:01:00Z",
"eta": "2026-01-01T00:01:00Z",
"result": {
"text": "生成的文本...",
"metadata": { "tokens": 1024 }
},
"error": null,
"external_task_id": "my-task-001"
}
}
```
**任务状态:**
| 状态 | 说明 |
|------|------|
| `received` | 已接收 |
| `processing` | 处理中 |
| `completed` | 已完成 |
| `failed` | 已失败 |
| `cancelled` | 已取消 |
### 5.2 获取任务状态(简化版)
**请求:**
```http
GET /api/v1/synapse/task/{task_id}/status
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"task_id": "task-abc123",
"status": "processing",
"progress": 50
}
```
### 5.3 获取任务列表
**请求:**
```http
GET /api/v1/synapse/tasks?status=processing&limit=20&offset=0
X-API-Key: your-api-key
```
**查询参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `status` | string | 否 | 按状态过滤 |
| `limit` | int | 否 | 每页数量,默认 20 |
| `offset` | int | 否 | 偏移量,默认 0 |
**响应:**
```json
{
"success": true,
"tasks": [
{
"id": "task-abc123",
"type": "text_generation",
"status": "completed",
"progress": 100,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:01:00Z"
}
],
"total": 1,
"limit": 20,
"offset": 0,
"has_more": false
}
```
---
## 6. 任务取消
**请求:**
```http
POST /api/v1/synapse/task/{task_id}/cancel
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"task_id": "task-abc123",
"status": "cancelled",
"message": "任务已取消"
}
```
---
## 7. 能力查询
**请求:**
```http
GET /api/v1/synapse/capabilities
X-API-Key: your-api-key
```
**响应:**
```json
{
"success": true,
"capabilities": {
"agent_id": "my-agent",
"agent_name": "我的Agent",
"system_id": "my-system",
"protocol_version": "1.0.0",
"supported_task_types": [
{
"type": "text_generation",
"name": "文本生成",
"description": "生成各种类型的文本内容",
"estimated_time": "30s",
"input_schema": "...",
"output_schema": "..."
}
],
"supported_transport_modes": ["callback", "polling"],
"max_file_size": 104857600,
"max_concurrent_tasks": 10,
"support_progress_updates": true,
"support_streaming": false,
"support_hil": true,
"support_groups": true
}
}
```
---
## 8. 回调机制
如果调用方提供了 `callback_url`,服务端会在任务状态变化时主动回调。
### 8.1 回调请求
```http
POST {callback_url}
Content-Type: application/json
X-Callback-Token: {callback_token}
{
"task_id": "task-abc123",
"status": "completed",
"progress": 100,
"message": "任务已完成",
"result": {
"text": "生成的文本..."
},
"error": null,
"timestamp": "2026-01-01T00:01:00Z"
}
```
### 8.2 回调类型
| 触发时机 | 说明 |
|---------|------|
| 任务开始处理 | `status=processing`, `progress=0` |
| 进度更新 | `status=processing`, `progress=N` |
| 任务完成 | `status=completed`, `progress=100`, 包含 result |
| 任务失败 | `status=failed`, 包含 error |
| 任务取消 | `status=cancelled` |
### 8.3 回调响应
调用方应返回 2xx 状态码表示成功接收。如果返回非 2xx,服务端会重试(指数退避,最多 5 次)。
---
## 9. 错误处理
### 9.1 错误响应格式
```json
{
"success": false,
"error": "invalid_task_type",
"message": "不支持的任务类型"
}
```
### 9.2 错误码列表
| 错误码 | HTTP 状态码 | 说明 |
|--------|------------|------|
| `unauthorized` | 401 | API Key 无效或缺失 |
| `invalid_request` | 400 | 请求格式无效 |
| `invalid_task_type` | 400 | 任务类型不支持 |
| `invalid_params` | 400 | 参数无效 |
| `task_not_found` | 404 | 任务不存在 |
| `task_already_completed` | 409 | 任务已完成,无法取消 |
| `file_too_large` | 413 | 文件过大 |
| `rate_limited` | 429 | 请求过于频繁 |
| `internal_error` | 500 | 服务内部错误 |
| `service_unavailable` | 503 | 服务不可用 |
---
## 10. SDK 客户端接口
为方便调用方集成,提供 SDK 客户端。
### 10.1 SDK 初始化
```go
client := sdk.NewClient(sdk.ClientConfig{
BaseURL: "https://agent-api.example.com",
APIKey: "your-api-key",
Timeout: 30 * time.Second,
})
```
### 10.2 SDK 方法清单
| 方法 | 说明 |
|------|------|
| `SubmitTask(ctx, req)` | 提交任务(JSON) |
| `SubmitTaskWithFiles(ctx, req, files)` | 提交任务(带文件) |
| `GetTask(ctx, taskID)` | 获取任务详情 |
| `GetTaskStatus(ctx, taskID)` | 获取任务状态 |
| `ListTasks(ctx, status, limit, offset)` | 获取任务列表 |
| `CancelTask(ctx, taskID)` | 取消任务 |
| `GetCapabilities(ctx)` | 获取能力清单 |
| `WaitForTask(ctx, taskID, interval)` | 等待任务完成(轮询) |
### 10.3 数据结构
```go
type TaskRequest struct {
TaskType string
Payload map[string]any
ExternalTaskID string
Priority string
CallbackURL string
CallbackToken string
}
type TaskInfo struct {
ID string
Type string
Status string
Progress int
Message string
CreatedAt string
UpdatedAt string
CompletedAt string
ETA string
Result map[string]any
Error *TaskError
ExternalTaskID string
}
```
**完整 SDK 实现:** [sdk/client.go](../sdk/client.go)
---
## 11. 参考实现
本协议的参考实现:
| 模块 | 文件路径 | 说明 |
|------|---------|------|
| 服务端 | [externalapi/handler.go](../externalapi/handler.go) | External API Handler |
| 客户端 SDK | [sdk/client.go](../sdk/client.go) | Go 语言 SDK |
---
## 附录 A:调用模式选择
### 回调模式 vs 轮询模式
| 对比项 | 回调模式 | 轮询模式 |
|--------|---------|---------|
| 实时性 | 高,状态变化即时通知 | 低,取决于轮询间隔 |
| 实现复杂度 | 调用方需提供公网可访问的回调接口 | 简单,只需调用 GET 接口 |
| 网络要求 | 调用方需要公网可达 | 只需调用方能访问服务端 |
| 适用场景 | 生产环境、需要实时通知 | 开发测试、内网环境 |
### 选择建议
- **有公网服务器**:使用回调模式
- **本地开发 / 内网环境**:使用轮询模式
- **长时间任务**:建议回调模式,避免频繁轮询
- **短任务(<10s)**:可直接同步等待或高频轮询
---
## 附录 B:从 A2A 协议到 P2A 协议的映射
| 灵隙概念 | P2A 对应 | 说明 |
|---------|---------|------|
| `p2p:task_request` | `POST /task` | 任务提交 |
| `p2p:task_status_update` | 回调 / GET /task | 状态更新 |
| `p2p:task_complete` | 回调(status=completed) | 任务完成 |
| `p2p:task_failed` | 回调(status=failed) | 任务失败 |
| `p2p:task_cancel` | `POST /task/{id}/cancel` | 任务取消 |
| `p2p:capability_query` | `GET /capabilities` | 能力查询 |
| Agent 身份 | API Key | 简化认证 |
| HMAC 签名 | Callback Token | 简化验证 |

View File

@@ -0,0 +1,519 @@
# SDK 实现说明
> 版本:1.0.0
> 更新日期:2026-06-27
> 协议版本:1.0.0
> 关联协议:
> - [A2A 协议](./02-a2a-protocol.md)
> - [A2N 协议](./03-a2n-protocol.md)
> - [P2A 协议](./04-p2a-protocol.md)
---
## 1. 概述
SDK 按照灵隙协议族(Synapse Protocol Suite)的三个子协议进行了模块化设计,分为三个子包,命名与协议名保持一致:
```
┌─────────────────────────────────────────────────────────────┐
│ SDK (sdk/) │
├──────────────┬───────────────────┬───────────────────────────┤
│ sdk/p2a │ sdk/a2a │ sdk/a2n │
│ P2A 协议 │ A2A 协议 │ A2N 协议 │
│ (外部调用) │ (Agent间通信) │ (群组协作) │
│ ✅ 完整实现 │ ✅ 完整实现 │ ✅ 完整实现 │
└──────────────┴───────────────────┴───────────────────────────┘
```
---
## 2. SDK 目录结构
```
sdk/
├── doc.go # SDK 包文档(使用说明)
├── p2a/
│ └── client.go # P2A 协议 - 外部调用客户端
├── a2a/
│ └── client.go # A2A 协议 - Agent间通信客户端
└── a2n/
└── client.go # A2N 协议 - 群组协作客户端
```
---
## 3. 第一部分:sdk/p2a - P2A 协议
**协议对应**:[P2A 协议](./04-p2a-protocol.md)
**版本**:1.0.0
**用途**:非 Agent 调用方(前端、后端服务、第三方应用)通过 REST API 调用 Agent 能力
### 3.1 实现对应关系
| P2A 协议功能 | SDK 方法 | 位置 |
|-------------|---------|------|
| 认证方式 | `setHeaders()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 提交任务(JSON) | `SubmitTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 提交任务(Multipart) | `SubmitTaskWithFiles()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 获取任务详情 | `GetTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 获取任务状态(简化版) | `GetTaskStatus()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 获取任务列表 | `ListTasks()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 取消任务 | `CancelTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 能力查询 | `GetCapabilities()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 回调验证 | `VerifyCallback()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
| 轮询等待 | `WaitForTask()` | [sdk/p2a/client.go](../sdk/p2a/client.go) |
### 3.2 核心数据结构
```go
// Client 客户端
type Client struct {
baseURL string
apiKey string
httpClient *http.Client
}
// 配置
type ClientConfig struct {
BaseURL string
APIKey string
Timeout time.Duration
MaxRetries int
}
// 任务请求
type TaskRequest struct {
TaskType string
Payload map[string]any
ExternalTaskID string
Priority string
CallbackURL string
CallbackToken string
}
// 任务信息
type TaskInfo struct {
ID string
Type string
Status string
Progress int
Result map[string]any
Error *TaskError
}
// 文件上传参数
type FileParam struct {
FieldName string
Filename string
Data []byte
MimeType string
}
```
### 3.3 使用示例
```go
import "dp-headless/synapse/sdk/p2a"
client := p2a.NewClient(p2a.ClientConfig{
BaseURL: "https://agent-api.example.com",
APIKey: "your-api-key",
})
// 提交任务并等待完成
taskInfo, err := client.CreateAndWait(ctx, "text_generation", map[string]any{
"prompt": "写一首诗",
"style": "古典",
}, 5*time.Second)
// 使用回调模式
resp, err := client.CreateWithCallback(ctx, "image_generation", payload,
"https://your-domain.com/callback",
"callback-token",
)
```
---
## 4. 第二部分:sdk/a2a - A2A 协议
**协议对应**:[A2A 协议](./02-a2a-protocol.md)
**版本**:1.0.0
**用途**:Agent 之间的点对点通信,包含消息发送、签名验证、回调处理
### 4.1 实现对应关系
| A2A 协议功能 | SDK 方法 | 位置 |
|------------|---------|------|
| 发送任务请求 | `SendTaskRequest()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 发送任务取消 | `SendTaskCancel()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 发送人类决策 | `SendHumanDecision()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 查询能力 | `SendCapabilityQuery()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 消息签名 | `signMessage()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 签名验证 | `verifySignature()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 任务状态管理 | `GetTaskState()`, `ListTasks()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 等待任务完成 | `WaitForTask()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
| 回调服务 | `startCallbackServer()` | [sdk/a2a/client.go](../sdk/a2a/client.go) |
### 4.2 核心数据结构
```go
// Client A2A客户端
type Client struct {
agentEndpoint types.AgentEndpoint
authSecret string
callbackHandler MessageCallbackHandler
tasks map[string]*A2ATaskState
}
// 配置
type ClientConfig struct {
AgentEndpoint types.AgentEndpoint
AuthSecret string
CallbackPort int
Timeout time.Duration
MaxRetries int
}
// 消息回调处理器接口
type MessageCallbackHandler interface {
HandleTaskRequest(ctx context.Context, msg *types.UnifiedSynapseMessage) (*types.UnifiedSynapseMessage, error)
HandleTaskStatusUpdate(ctx context.Context, msg *types.UnifiedSynapseMessage) error
HandleTaskComplete(ctx context.Context, msg *types.UnifiedSynapseMessage) error
HandleTaskFailed(ctx context.Context, msg *types.UnifiedSynapseMessage) error
HandleNeedHuman(ctx context.Context, msg *types.UnifiedSynapseMessage) error
}
// 任务状态
type A2ATaskState struct {
TaskID string
Status types.P2PTaskStatus
Progress int
Message string
Result any
Error *types.SynapseError
}
```
### 4.3 使用示例
```go
import "dp-headless/synapse/sdk/a2a"
a2aClient, err := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: types.AgentEndpoint{
AgentID: "my-agent",
AgentName: "我的Agent",
SystemID: "my-system",
Endpoint: "http://localhost:8080/synapse/callback",
},
AuthSecret: "my-hmac-secret",
CallbackPort: 8080,
})
// 设置消息处理器
a2aClient.SetMessageHandler(&myHandler{})
// 发送任务请求给其他Agent
taskID, err := a2aClient.SendTaskRequest(ctx, targetAgent, "video_generation", map[string]any{
"prompt": "生成一段视频",
})
```
---
## 5. 第三部分:sdk/a2n - A2N 协议
**协议对应**:[A2N 协议](./03-a2n-protocol.md)
**版本**:1.0.0
**用途**:多 Agent 群组协作,包含群组管理、任务拆分合并、投票、升级
### 5.1 实现对应关系
| A2N 协议功能 | SDK 方法 | 位置 |
|------------|---------|------|
| 创建群组 | `CreateGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 加入群组 | `JoinGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 离开群组 | `LeaveGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 解散群组 | `DisbandGroup()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 提交群组任务 | `SubmitGroupTask()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 发送子任务结果 | `SendSubtaskResult()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 发起投票 | `InitiateVote()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 提交投票 | `SubmitVote()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 广播消息 | `Broadcast()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
| 升级到人类 | `EscalateToHuman()` | [sdk/a2n/client.go](../sdk/a2n/client.go) |
### 5.2 核心数据结构
```go
// Client A2N客户端
type Client struct {
a2aClient *a2a.Client
groups map[string]*GroupState
eventHandler GroupEventHandler
}
// 配置
type ClientConfig struct {
A2AClient *a2a.Client
EventHandler GroupEventHandler
}
// 群组状态
type GroupState struct {
GroupID string
Name string
Topology types.GroupTopology
Status types.GroupStatus
Members []types.GroupMember
Role string // "coordinator" | "worker"
}
// 群组事件处理器接口
type GroupEventHandler interface {
OnGroupCreated(ctx context.Context, groupID string) error
OnGroupDisbanded(ctx context.Context, groupID string) error
OnMemberJoined(ctx context.Context, groupID string, member types.GroupMember) error
OnMemberLeft(ctx context.Context, groupID string, agentID string) error
OnTaskSplit(ctx context.Context, groupID string, originalTaskID string, subtasks []types.Subtask) error
OnSubtaskProgress(ctx context.Context, groupID string, subtaskID string, progress int) error
OnTaskMerged(ctx context.Context, groupID string, originalTaskID string, result any) error
OnVoteInitiated(ctx context.Context, groupID string, vote types.GroupVotePayload) error
OnVoteResult(ctx context.Context, groupID string, voteID string, option string, passed bool) error
OnEscalation(ctx context.Context, groupID string, escalationID string, level int) error
OnBroadcast(ctx context.Context, groupID string, sender types.AgentEndpoint, message []byte) error
}
```
### 5.3 使用示例
```go
import (
"dp-headless/synapse/sdk/a2n"
"dp-headless/synapse/sdk/a2a"
)
// 创建A2A客户端
a2aClient, _ := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: myEndpoint,
})
// 创建A2N客户端
a2nClient, err := a2n.NewClient(&a2n.ClientConfig{
A2AClient: a2aClient,
EventHandler: &myGroupHandler{},
})
// 创建群组
group, err := a2nClient.CreateGroup(ctx, "视频制作组", types.GroupTopologyStar, nil, members)
// 作为Coordinator,提交任务
subtasks, err := a2nClient.SubmitGroupTask(ctx, group.GroupID, "video_generation",
map[string]any{"script": "..."},
types.MergeAllSuccess,
)
// 发起投票
voteID, err := a2nClient.InitiateVote(ctx, group.GroupID,
"选择哪个方案?",
[]string{"方案A", "方案B"},
voterIDs,
0.5,
5*time.Minute,
)
```
---
## 6. SDK 架构总览
```
┌─────────────────────────────────────────────────────────────┐
│ SDK (dp-headless/synapse/sdk) │
├─────────────────────────────────────────────────────────────┤
│ doc.go │
│ ├── 包文档和使用说明 │
│ └── 指向各子包的链接 │
├────────────────┬────────────────┬─────────────────────────┤
│ sdk/p2a │ sdk/a2a │ sdk/a2n │
│ │ │ │
│ P2A协议 │ 灵隙协议 │ A2N协议 │
│ │ │ │
│ ├── Client │ ├── Client │ ├── Client │
│ ├── 配置 │ ├── 配置 │ ├── 配置 │
│ ├── 任务操作 │ ├── 消息发送 │ ├── 群组管理 │
│ ├── 能力查询 │ ├── 任务状态 │ ├── 任务协作 │
│ └── 回调验证 │ ├── 回调服务 │ ├── 投票 │
│ │ └── 签名 │ └── 升级 │
└────────────────┴────────────────┴─────────────────────────┘
```
---
## 7. 快速开始
### 7.1 P2A 调用(外部调用者)
```go
package main
import (
"context"
"fmt"
"dp-headless/synapse/sdk/p2a"
)
func main() {
client := p2a.NewClient(p2a.ClientConfig{
BaseURL: "https://agent.example.com",
APIKey: "your-api-key",
})
ctx := context.Background()
// 方式1: 轮询等待
result, err := client.CreateAndWait(ctx, "text_generation", map[string]any{
"prompt": "写一首诗",
}, 5*time.Second)
if err != nil {
fmt.Println("Error:", err)
}
fmt.Printf("Result: %v\n", result.Result)
// 方式2: 回调模式
resp, err := client.CreateWithCallback(ctx, "image_generation",
map[string]any{"prompt": "一只猫"},
"https://your-domain.com/callback",
"token",
)
fmt.Printf("Task ID: %s\n", resp.TaskID)
}
```
### 7.2 灵隙 通信(Agent 间)
```go
package main
import (
"context"
"fmt"
"dp-headless/synapse/sdk/a2a"
"dp-headless/synapse/types"
)
func main() {
client, _ := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: types.AgentEndpoint{
AgentID: "agent-001",
AgentName: "Agent 001",
SystemID: "my-system",
Endpoint: "http://localhost:8080/synapse/callback",
},
AuthSecret: "secret-key",
CallbackPort: 8080,
})
ctx := context.Background()
target := types.AgentEndpoint{
AgentID: "agent-002",
Endpoint: "http://agent-002:8080/synapse/callback",
}
// 发送任务请求
taskID, err := client.SendTaskRequest(ctx, target, "data_analysis", map[string]any{
"dataset": "sales.csv",
})
fmt.Printf("Task ID: %s\n", taskID)
// 等待完成
state, err := client.WaitForTask(ctx, taskID, 5*time.Minute)
fmt.Printf("Status: %s\n", state.Status)
}
```
### 7.3 A2N 群组协作
```go
package main
import (
"context"
"fmt"
"dp-headless/synapse/sdk/a2n"
"dp-headless/synapse/sdk/a2a"
"dp-headless/synapse/types"
)
func main() {
a2aClient, _ := a2a.NewClient(&a2a.ClientConfig{
AgentEndpoint: myEndpoint,
})
a2nClient, _ := a2n.NewClient(&a2n.ClientConfig{
A2AClient: a2aClient,
})
ctx := context.Background()
// 创建群组
group, _ := a2nClient.CreateGroup(ctx, "协作组", types.GroupTopologyStar, nil, []types.AgentEndpoint{
{AgentID: "member-1", Endpoint: "http://member-1:8080"},
{AgentID: "member-2", Endpoint: "http://member-2:8080"},
})
fmt.Printf("Group ID: %s\n", group.GroupID)
// 提交任务
subtasks, _ := a2nClient.SubmitGroupTask(ctx, group.GroupID, "video_generation",
map[string]any{"script": "..."},
types.MergeAllSuccess,
)
fmt.Printf("Split into %d subtasks\n", len(subtasks))
}
```
---
## 8. 协议与包名对应关系
| 协议名称 | 协议文档 | SDK 包名 | 包路径 |
|---------|---------|---------|--------|
| 灵隙原始协议(Synapse Raw Protocol) | [01-synapse-raw-protocol.md](./01-synapse-raw-protocol.md) | - | 纲领性文档 |
| A2A 协议 | [02-a2a-protocol.md](./02-a2a-protocol.md) | `a2a` | `sdk/a2a/` |
| A2N 协议 | [03-a2n-protocol.md](./03-a2n-protocol.md) | `a2n` | `sdk/a2n/` |
| P2A 协议 | [04-p2a-protocol.md](./04-p2a-protocol.md) | `p2a` | `sdk/p2a/` |
**所有协议版本:1.0.0**
---
## 9. 扩展建议
| 优先级 | 扩展项 | 说明 |
|-------|-------|------|
| 高 | gRPC 传输 | 添加 gRPC 协议支持 |
| 高 | WebSocket | 添加 WebSocket 流式传输 |
| 中 | 批量操作 | 批量任务提交、批量查询 |
| 中 | 指标监控 | 内置指标收集 |
| 低 | 多语言 SDK | Python、JavaScript 等 |
---
## 附录:相关文件索引
| 文件路径 | 说明 |
|---------|------|
| [sdk/doc.go](../sdk/doc.go) | SDK 包文档 |
| [sdk/p2a/client.go](../sdk/p2a/client.go) | P2A 客户端实现 |
| [sdk/a2a/client.go](../sdk/a2a/client.go) | A2A 客户端实现 |
| [sdk/a2n/client.go](../sdk/a2n/client.go) | A2N 客户端实现 |
| [externalapi/handler.go](../externalapi/handler.go) | P2A 协议服务端实现 |
| [p2p/protocol.go](../p2p/protocol.go) | A2A 协议核心实现 |
| [group/protocol.go](../group/protocol.go) | A2N 协议核心实现 |
| [types/types.go](../types/types.go) | 公共类型定义 |
| [transport/transport.go](../transport/transport.go) | HTTP 传输层实现 |

View File

@@ -0,0 +1,777 @@
# 灵隙协议族(Synapse Protocol Suite) - 解决方案白皮书
> 版本:1.0.0
> 日期:2026-06-27
> 状态:草案
> 前置阅读:[灵隙原始协议(Synapse Raw Protocol)](./01-synapse-raw-protocol.md)
---
## 目录
1. [概述](#1-概述)
2. [方案一:Agent 发现与广播机制](#2-方案一agent-发现与广播机制)
3. [方案二:身份认证与信任体系](#3-方案二身份认证与信任体系)
4. [方案三:协议缺失内容补全](#4-方案三协议缺失内容补全)
5. [分阶段实施路线图](#5-分阶段实施路线图)
6. [技术选型建议](#6-技术选型建议)
---
## 1. 概述
### 1.1 背景
当前灵隙协议族(Synapse Protocol Suite)定义了 Agent 之间"怎么说话"(消息格式、任务流程),但缺少:
- **怎么找到对方** — Agent 发现与广播机制
- **怎么相信对方** — 身份认证与信任体系
- **怎么规模化协作** — 路由、调度、激励等基础设施
本文档针对上述问题给出完整的解决方案。
### 1.2 设计原则
| 原则 | 说明 |
|------|------|
| **渐进式** | 从简单到复杂,每阶段都可用 |
| **可插拔** | 组件可替换,不绑定具体实现 |
| **传输无关** | 不依赖特定底层网络 |
| **安全优先** | 身份认证是基础设施,不是可选功能 |
| **向后兼容** | 不破坏现有协议,增量扩展 |
---
## 2. 方案一:Agent 发现与广播机制
### 2.1 整体架构
```
┌─────────────────────────────────────────────────────────────────────┐
│ Agent 发现与广播层 │
├─────────────────────┬─────────────────────┬─────────────────────────┤
│ 集中式目录服务 │ 分布式 Gossip │ 组播/广播 │
│ (Directory) │ (Gossip) │ (Multicast) │
│ ┌───────────────┐ │ ┌───────────────┐ │ ┌────────────────┐ │
│ │ 注册/注销 │ │ │ 消息泛洪 │ │ │ 子网内广播 │ │
│ │ 能力索引 │ │ │ 状态同步 │ │ │ 邻居发现 │ │
│ │ 名字解析 │ │ │ 节点发现 │ │ │ 本地心跳 │ │
│ └───────────────┘ │ └───────────────┘ │ └────────────────┘ │
└─────────────────────┴─────────────────────┴─────────────────────────┘
```
### 2.2 模式一:集中式目录服务(推荐首选)
#### 2.2.1 架构说明
引入 **Agent Directory Service(ADS)** 作为中心化的 Agent 目录服务,类似 DNS。
```
Agent A ──注册──▶ ┌─────────────┐ ◀──注册── Agent B
│ Directory │
Agent C ──查询──▶ │ Service │ ◀──心跳── Agent D
└─────────────┘
│
▼
能力索引
在线状态
端点地址
```
#### 2.2.2 核心接口
**注册协议(Agent → Directory):**
| 操作 | 消息类型 | 说明 |
|------|---------|------|
| 注册 | `dir:register` | Agent 上线时注册自身信息 |
| 注销 | `dir:unregister` | Agent 下线时注销 |
| 心跳 | `dir:heartbeat` | 定期上报存活状态 |
| 更新能力 | `dir:update_capabilities` | 能力变化时更新 |
**查询协议(Agent → Directory):**
| 操作 | 消息类型 | 说明 |
|------|---------|------|
| 按 ID 查询 | `dir:query_by_id` | 根据 AgentID 查端点 |
| 按能力查询 | `dir:query_by_capability` | 找具备某能力的 Agent |
| 列表查询 | `dir:list` | 分页列出所有 Agent |
| 批量查询 | `dir:batch_query` | 批量查询多个 Agent |
#### 2.2.3 注册数据结构
```json
{
"agent_id": "agent-001",
"agent_name": "视频生成Agent",
"system_id": "video-system",
"endpoint": "https://agent-001.example.com/synapse/callback",
"version": "1.0.0",
"capabilities": [
{
"type": "video_generation",
"name": "视频生成",
"description": "根据脚本生成视频",
"max_concurrent": 10,
"avg_latency_ms": 5000
}
],
"transports": ["http", "grpc"],
"status": "online",
"last_heartbeat": "2026-06-27T10:00:00Z",
"trust_score": 85,
"public_key": "-----BEGIN PUBLIC KEY-----\n..."
}
```
#### 2.2.4 优缺点
| 优点 | 缺点 |
|------|------|
| 查询效率高(O(1)) | 单点故障风险 |
| 支持复杂查询(按能力、按系统等) | 中心节点需信任 |
| 易于管理和监控 | 扩展性有上限 |
| 实现简单 | 存在注册/注销时序问题 |
---
### 2.3 模式二:分布式 Gossip 协议(可选扩展)
#### 2.3.1 架构说明
无中心节点,Agent 之间通过 Gossip 协议(epidemic protocol)扩散状态信息,类似比特币网络的节点发现。
```
Agent A ──gossip──▶ Agent B ──gossip──▶ Agent D
│ │
└──gossip──▶ Agent C ──gossip──▶ Agent E
```
#### 2.3.2 核心机制
| 机制 | 说明 |
|------|------|
| **Peer Sampling** | 每个 Agent 维护一个部分视图(partial view) |
| **Active Push** | 定期随机选几个节点推送本地状态 |
| **Passive Pull** | 收到推送后,有概率回传自身状态 |
| **TTL** | 消息有存活跳数,防止无限扩散 |
| **Deduplication** | 消息 ID 去重,避免重复处理 |
#### 2.3.3 Gossip 消息格式
```json
{
"type": "gossip:state",
"from": "agent-001",
"view": [
{"agent_id": "agent-002", "endpoint": "...", "capabilities": [...], "timestamp": "..."},
{"agent_id": "agent-003", "endpoint": "...", "capabilities": [...], "timestamp": "..."},
{"agent_id": "agent-004", "endpoint": "...", "capabilities": [...], "timestamp": "..."}
],
"ttl": 5,
"msg_id": "msg-xxx"
}
```
#### 2.3.4 优缺点
| 优点 | 缺点 |
|------|------|
| 完全去中心化 | 查询效率低(需本地构建视图) |
| 容错性强 | 状态最终一致,有延迟 |
| 可无限扩展 | 实现复杂度高 |
| 无单点故障 | 网络流量大 |
---
### 2.4 模式三:本地组播/邻居发现(局域网场景)
适用于同一内网/集群内的 Agent 快速发现。
#### 2.4.1 机制
| 机制 | 说明 |
|------|------|
| **UDP 组播** | 定期在组播地址发送广播 |
| **mDNS/DNS-SD** | 类似 Apple Bonjour 的服务发现 |
| **ARP/NDP** | 链路层发现(不推荐) |
#### 2.4.2 适用场景
- 边缘计算集群
- 同一机房内的 Agent 集群
- 本地开发测试环境
---
### 2.5 广播机制设计
#### 2.5.1 广播类型
| 广播范围 | 消息类型前缀 | 说明 |
|---------|-------------|------|
| 全网广播 | `global:` | 所有在线 Agent |
| 系统广播 | `system:` | 同一 system_id 内 |
| 群组广播 | `group:` | 同一群组内(已有) |
| 能力组广播 | `cap:` | 具备某能力的 Agent |
#### 2.5.2 广播实现
**集中式模式下:**
- 由 Directory Service 转发广播消息
- 支持按条件过滤(按能力、按系统等)
**分布式模式下:**
- 通过 Gossip 协议泛洪
- TTL 控制扩散范围
- 消息 ID 去重
---
### 2.6 心跳与存活检测
#### 2.6.1 心跳机制
```
Agent ───heartbeat──▶ Directory
(每30秒)
Directory 判定离线:
- 连续 3 次未收到心跳 → 标记为 offline
- 离线 24 小时 → 自动注销
```
#### 2.6.2 主动探测
```
Directory ───ping──▶ Agent
◀──pong──
```
用于验证 Agent 是否真的在线(防止心跳伪造)。
---
## 3. 方案二:身份认证与信任体系
### 3.1 三层身份架构
```
┌─────────────────────────────────────────────────┐
│ Layer 3: 信任评分 (Trust Score) │
│ 声誉系统、历史行为、第三方评价 │
├─────────────────────────────────────────────────┤
│ Layer 2: 身份验证 (Authentication) │
│ 签名验证、证书校验、DID 验证 │
├─────────────────────────────────────────────────┤
│ Layer 1: 身份标识 (Identity) │
│ AgentID + 公钥 / 证书 / DID │
└─────────────────────────────────────────────────┘
```
### 3.2 Layer 1:身份标识方案
#### 3.2.1 方案 A:公钥身份(推荐基础版)
**核心思想**:AgentID = 公钥哈希(类似比特币地址)
```
AgentID = base58(sha256(public_key)[:20])
例:agent-1A2B3C4D5E6F...
```
**优点:**
- 自包含,不需要中心 CA
- 公钥可验证身份
- 实现简单
**缺点:**
- 身份无法撤销(私钥泄露 = 身份被盗)
- 没有人类可读的名字
- 无法关联真实世界身份
**适用场景**:内部系统、测试环境、信任度要求不高的场景
#### 3.2.2 方案 B:PKI 证书体系
**核心思想**:引入 CA(Certificate Authority)颁发证书
```
CA 根证书
├── 系统A证书
│ ├── Agent-001 证书
│ └── Agent-002 证书
└── 系统B证书
└── Agent-003 证书
```
**优点:**
- 成熟的工业标准
- 支持证书撤销(CRL/OCSP)
- 有层级信任关系
**缺点:**
- 需要信任中心化 CA
- 证书管理复杂
- 跨 CA 互操作需要桥接
**适用场景**:企业内部、有明确信任中心的场景
#### 3.2.3 方案 C:区块链 DID(去中心化身份)
**核心思想**:身份上链,链上存公钥和属性,类似 W3C DID 标准。
```
链上存储:
DID → {
"publicKey": "...",
"controller": "...",
"authentication": [...],
"service": [...],
"revoked": false,
"timestamp": "..."
}
```
**三种实现路径:**
| 路径 | 说明 | 适用场景 |
|------|------|---------|
| **公链 DID** | 使用以太坊/Solana 等公链 | 开放生态、跨组织协作 |
| **联盟链 DID** | 多机构共建联盟链 | 行业联盟、政企合作 |
| **私有链 DID** | 单组织内部链 | 企业内部、对性能要求高 |
**优点:**
- 去中心化,无单点故障
- 身份可验证、可追溯
- 支持跨系统互认
- 不可篡改的身份历史
**缺点:**
- 链上操作有延迟
- 需要支付 Gas 费(公链)
- 实现复杂度高
- 隐私保护需额外设计
---
### 3.3 Layer 2:身份验证方案
#### 3.3.1 消息签名验证
所有灵隙消息必须签名,签名方式:
```
签名内容 = SHA256(message_id + sender + receiver + payload + timestamp)
签名 = Sign(private_key, 签名内容)
验证 = Verify(public_key, 签名内容, 签名)
```
#### 3.3.2 签名放置位置
| 位置 | 说明 |
|------|------|
| 消息体内 | `signature` 字段 |
| HTTP Header | `X-Synapse-Signature` |
| gRPC Metadata | `x-synapse-signature` |
#### 3.3.3 防重放攻击
```
每个消息有唯一 message_id + timestamp
接收方维护已处理 message_id 缓存(如最近 1 小时)
timestamp 与本地时间差超过 5 分钟 → 拒绝
```
---
### 3.4 Layer 3:信任评分体系
#### 3.4.1 信任分维度
| 维度 | 权重 | 说明 |
|------|------|------|
| 任务完成率 | 30% | 历史任务成功 / 总任务数 |
| 任务质量 | 25% | 结果评分、返工率 |
| 响应速度 | 15% | 平均响应时间 |
| 在线稳定性 | 15% | 在线时长 / 总时长 |
| 历史信誉 | 15% | 其他 Agent 的评价 |
#### 3.4.2 信任分计算
```
trust_score = Σ(维度_i × 权重_i)
范围:0 - 100
等级:
90-100 S 级 高度可信
75-89 A 级 可信
60-74 B 级 一般可信
40-59 C 级 谨慎使用
0-39 D 级 不可信
```
#### 3.4.3 信任传递
```
A 信任 B(评分 90)
B 信任 C(评分 85)
A 对 C 的初始信任 = 90 × 85% × 衰减因子 = 约 68
```
**注意**:信任传递有衰减,防止信任污染。
---
### 3.5 区块链适用性决策树
```
是否需要跨组织协作?
├── 否 → 用 PKI 或公钥身份(简单高效)
└── 是 → 是否有共同信任的中心机构?
├── 是 → 用 PKI 体系(成熟可靠)
└── 否 → 是否需要不可篡改的审计?
├── 是 → 考虑联盟链 DID
└── 否 → 公钥身份 + 声誉系统可能够用
```
---
## 4. 方案三:协议缺失内容补全
### 4.1 第一优先级补全(核心基础设施)
#### 4.1.1 Agent 发现协议(ADS - Agent Discovery Service)
**新增消息类型:**
| 消息类型 | 方向 | 说明 |
|---------|------|------|
| `dir:register` | Agent → Directory | 注册 Agent |
| `dir:unregister` | Agent → Directory | 注销 Agent |
| `dir:heartbeat` | Agent → Directory | 心跳 |
| `dir:update_capabilities` | Agent → Directory | 更新能力 |
| `dir:query_by_id` | Agent → Directory | 按 ID 查询 |
| `dir:query_by_capability` | Agent → Directory | 按能力查询 |
| `dir:list` | Agent → Directory | 列表查询 |
| `dir:query_result` | Directory → Agent | 查询结果 |
**新增数据结构:** `AgentDirectoryEntry`, `CapabilityIndex`, `QueryFilter`
#### 4.1.2 身份认证规范
**新增消息类型:**
| 消息类型 | 说明 |
|---------|------|
| `auth:challenge` | 身份挑战 |
| `auth:response` | 挑战响应 |
| `auth:cert_exchange` | 证书交换 |
**新增内容:**
- 标准签名算法列表(Ed25519, ECDSA P-256, RSA 2048)
- 消息签名格式规范
- 防重放攻击机制
- 身份验证流程规范
#### 4.1.3 心跳与存活检测
**新增消息类型:**
| 消息类型 | 说明 |
|---------|------|
| `net:ping` | 存活探测 |
| `net:pong` | 存活响应 |
| `net:offline_notify` | 下线通知 |
**规范内容:**
- 心跳间隔(默认 30 秒)
- 超时判定(连续 3 次未收到 → 离线)
- 离线通知机制
#### 4.1.4 统一错误码体系
**错误分类:**
| 错误码前缀 | 类别 | 示例 |
|-----------|------|------|
| `msg_` | 消息层 | msg_invalid_format, msg_signature_invalid |
| `auth_` | 认证层 | auth_failed, auth_expired, auth_forbidden |
| `task_` | 任务层 | task_not_found, task_timeout, task_cancelled |
| `cap_` | 能力层 | cap_lacking, cap_busy, cap_not_supported |
| `group_` | 群组层 | group_not_found, group_full, group_permission_denied |
| `dir_` | 目录层 | dir_agent_not_found, dir_query_failed |
| `net_` | 网络层 | net_timeout, net_offline, net_unreachable |
| `sys_` | 系统层 | sys_internal_error, sys_not_implemented |
---
### 4.2 第二优先级补全(重要功能)
#### 4.2.1 能力匹配与任务路由
**功能:**
- 智能选择最合适的 Agent 执行任务
- 支持负载均衡(按并发数、响应速度、信任分)
- 支持任务失败自动重试/转发
**新增消息类型:**
- `router:submit_task` - 提交给路由器自动选择执行者
- `router:forward` - 任务转发
#### 4.2.2 全网广播协议
**新增消息类型:**
| 消息类型 | 说明 |
|---------|------|
| `global:broadcast` | 全网广播 |
| `system:broadcast` | 系统内广播 |
| `cap:broadcast` | 能力组广播 |
**规范内容:**
- 广播范围定义
- TTL 与去重机制
- 广播风暴防护
#### 4.2.3 版本协商机制
**功能:**
- Agent 之间协商使用的协议版本
- 向后兼容处理
- 能力降级策略
**新增消息类型:**
- `meta:version_query` - 查询支持的版本
- `meta:version_negotiate` - 版本协商
#### 4.2.4 幂等性保证
**规范内容:**
- 幂等键(Idempotency Key)机制
- 重复消息处理规范
- 任务级幂等 vs 消息级幂等
---
### 4.3 第三优先级补全(增强功能)
#### 4.3.1 流式输出协议
**功能:**
- 长任务实时进度推送
- 生成式 AI 的 token 流式输出
- WebSocket/SSE 传输支持
#### 4.3.2 批量任务协议
**功能:**
- 一次性提交多个同类型任务
- 批量状态查询
- 批量结果收集
#### 4.3.3 支付与激励机制
**功能:**
- 任务定价与结算
- Token 或积分系统
- 智能合约自动结算(区块链场景)
#### 4.3.4 群组发现协议
**功能:**
- 按主题/任务类型查找群组
- 推荐加入合适的群组
- 群组热度/活跃度排名
---
### 4.4 安全相关补全
#### 4.4.1 端到端加密
**规范内容:**
- 密钥协商(ECDH)
- 对称加密载荷(AES-GCM)
- 前向保密(PFS)
#### 4.4.2 权限委托
**功能:**
- Agent A 委托 Agent B 代表自己执行任务
- 委托令牌(Delegation Token)
- 委托范围与时效控制
#### 4.4.3 审计日志标准
**规范内容:**
- 统一审计日志格式
- 关键操作必须记录
- 日志完整性保护(哈希链)
---
## 5. 分阶段实施路线图
### 5.1 Phase 0:基础版(MVP) - 预计 2 周
**目标**:协议可用,能跑通基本流程
**内容:**
- [x] 灵隙点对点任务协议(已完成)
- [x] A2N 群组协作协议(已完成)
- [x] P2A 外部调用协议(已完成)
- [x] 三套协议的 SDK 实现(已完成)
---
### 5.2 Phase 1:发现与身份 - 预计 4 周
**目标**:Agent 能互相找到对方,能验证对方身份
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| Agent 目录服务协议 | 集中式 ADS | P0 |
| 公钥身份规范 | Ed25519 签名验证 | P0 |
| 心跳与存活检测 | 30 秒心跳,3 次超时判定离线 | P0 |
| 统一错误码体系 | 8 大类错误码 | P0 |
| 防重放攻击 | message_id + timestamp | P0 |
**输出物:**
- 新协议文档:`06-agent-discovery.md`
- 新协议文档:`07-authentication.md`
- 代码:`discovery/` 包(目录服务客户端)
- 代码:`auth/` 包(签名验证工具)
- 更新 types 包:新增消息类型和错误码
---
### 5.3 Phase 2:路由与广播 - 预计 4 周
**目标**:支持智能任务路由和全网广播
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| 任务路由协议 | 按能力 + 负载 + 信任分选择执行者 | P1 |
| 全网广播协议 | 基于目录服务的广播转发 | P1 |
| 版本协商机制 | 协议版本兼容 | P1 |
| 幂等性保证 | 幂等键 + 去重 | P1 |
**输出物:**
- 新协议文档:`08-routing-and-broadcast.md`
- 代码:`routing/` 包增强(增加能力路由)
- 更新目录服务:支持广播转发
---
### 5.4 Phase 3:信任与激励 - 预计 6 周
**目标**:建立信任体系,支持规模化协作
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| 信任评分体系 | 5 维度信任分计算 | P2 |
| 流式输出协议 | WebSocket/SSE 流式进度 | P2 |
| 批量任务协议 | 批量提交/查询/收集 | P2 |
| 群组发现协议 | 群组搜索与推荐 | P2 |
| SLA 协议 | 服务等级承诺 | P3 |
**输出物:**
- 新协议文档:`09-trust-and-reputation.md`
- 代码:`trust/` 包(信任评分引擎)
---
### 5.5 Phase 4:高级特性 - 预计 8 周+
**目标**:企业级、生产级完整功能
**新增协议模块:**
| 模块 | 说明 | 优先级 |
|------|------|--------|
| 端到端加密 | ECDH + AES-GCM | P3 |
| 权限委托 | 委托令牌机制 | P3 |
| 支付与激励 | Token/积分系统 | P3 |
| 区块链 DID 集成 | 可选模块 | P4 |
| 审计日志标准 | 统一审计格式 | P3 |
| Gossip 协议 | 分布式发现(可选) | P4 |
---
## 6. 技术选型建议
### 6.1 目录服务存储选型
| 方案 | 适用场景 | 优点 | 缺点 |
|------|---------|------|------|
| **Redis** | 中小规模 | 高性能,支持 TTL | 数据量受限 |
| **PostgreSQL** | 中大规模 | 复杂查询,持久化 | 需运维 |
| **Elasticsearch** | 大规模 + 全文检索 | 强大的搜索能力 | 运维复杂 |
**推荐**:先用 Redis(简单快),规模大了再迁 PostgreSQL。
---
### 6.2 签名算法选型
| 算法 | 安全性 | 性能 | 密钥长度 | 推荐度 |
|------|--------|------|---------|--------|
| Ed25519 | 高 | 快 | 256 位 | ⭐⭐⭐⭐⭐ 首选 |
| ECDSA P-256 | 高 | 中 | 256 位 | ⭐⭐⭐⭐ 兼容 |
| RSA 2048 | 中 | 慢 | 2048 位 | ⭐⭐ 仅兼容 |
**推荐**:默认 Ed25519,需要兼容旧系统时支持 ECDSA。
---
### 6.3 区块链选型(如果需要)
| 场景 | 推荐方案 | 说明 |
|------|---------|------|
| 企业内部 | **私有链(Hyperledger Fabric)** | 可控,高性能,无 Gas 费 |
| 行业联盟 | **联盟链(FISCO BCOS / ChainMaker)** | 多机构共建,合规 |
| 开放生态 | **公链(以太坊 / Solana)** | 完全开放,需 Gas 费 |
| 简单原型 | **模拟链(数据库模拟)** | 先验证逻辑,再上真链 |
**推荐**:90% 的场景用不到真区块链,先用数据库模拟验证业务逻辑。
---
## 7. 总结与建议
### 7.1 核心建议
1. **先集中后分布**:先用集中式目录服务,简单高效;规模大了再考虑分布式
2. **先公钥后区块链**:先用公钥身份解决 80% 的问题,真的需要了再上链
3. **先 MVP 后完善**:先跑通核心流程,再逐步加高级功能
4. **标准接口可替换**:目录服务、身份系统都做成可插拔的,方便未来升级
### 7.2 下一步行动
| 序号 | 行动 | 负责人 | 产出 |
|------|------|--------|------|
| 1 | 评审本方案,确认 Phase 1 范围 | 架构组 | 确认后的方案 |
| 2 | 设计目录服务 API 详细规范 | 协议组 | `06-agent-discovery.md` |
| 3 | 设计身份认证详细规范 | 协议组 | `07-authentication.md` |
| 4 | 实现目录服务原型 | 开发组 | 可运行的 ADS 服务 |
| 5 | 更新 SDK 支持发现和认证 | 开发组 | 新版 SDK |
---
## 附录:相关文档索引
| 文档 | 说明 |
|------|------|
| [01-synapse-raw-protocol.md](./01-synapse-raw-protocol.md) | 灵隙原始协议(Synapse Raw Protocol)(纲领) |
| [02-a2a-protocol.md](./02-a2a-protocol.md) | A2A 协议(P2P 任务) |
| [03-a2n-protocol.md](./03-a2n-protocol.md) | A2N 协议(群组协作) |
| [04-p2a-protocol.md](./04-p2a-protocol.md) | P2A 协议(外部调用) |
| [05-sdk-implementation.md](./05-sdk-implementation.md) | SDK 实现说明 |
| **本文档** | **解决方案白皮书** |

View File

@@ -0,0 +1,611 @@
package transport
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"sync"
"time"
"synapse/types"
)
// =============================================================================
// Multipart Transport - 混合文本+文件传输
// =============================================================================
// MultipartTransport handles multipart file transfers
type MultipartTransport struct {
mu sync.RWMutex
client *http.Client
timeout time.Duration
authSecret string
}
// NewMultipartTransport creates a new multipart transport
func NewMultipartTransport(timeout time.Duration, authSecret string) *MultipartTransport {
return &MultipartTransport{
client: &http.Client{
Timeout: timeout,
},
timeout: timeout,
authSecret: authSecret,
}
}
// =============================================================================
// Multipart Message Sending
// =============================================================================
// SendTaskWithFiles sends a task request with file attachments via multipart/form-data
func (m *MultipartTransport) SendTaskWithFiles(
ctx context.Context,
endpoint string,
msg *types.UnifiedSynapseMessage,
files []types.FileContent,
) error {
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
// 1. Write the main request JSON as the "request" part
requestPart, err := writer.CreateFormField("request")
if err != nil {
return fmt.Errorf("failed to create request part: %w", err)
}
if err := json.NewEncoder(requestPart).Encode(msg); err != nil {
return fmt.Errorf("failed to encode request: %w", err)
}
// 2. Write each file as a separate part
for _, file := range files {
if file.TransferMode != types.TransferInline {
continue // Skip URL-based files
}
part, err := writer.CreateFormFile(file.Name, file.Name)
if err != nil {
return fmt.Errorf("failed to create file part %s: %w", file.Name, err)
}
// For inline files, data is base64 encoded in file.Data
// In real implementation, decode base64 and write binary
// For now, just write the data as-is
if file.Data != "" {
part.Write([]byte(file.Data))
}
}
// Close the writer to finalize the boundary
if err := writer.Close(); err != nil {
return fmt.Errorf("failed to close writer: %w", err)
}
// 3. Create HTTP request
req, err := http.NewRequestWithContext(ctx, "POST", endpoint, &buf)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("X-Synapse-Version", "1.0.0")
req.Header.Set("X-Synapse-Agent", msg.Sender.AgentID)
req.Header.Set("X-Synapse-System", msg.Sender.SystemID)
// 4. Send
resp, err := m.client.Do(req)
if err != nil {
return fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}
log.Printf("[Multipart] Task sent with %d files to %s", len(files), endpoint)
return nil
}
// =============================================================================
// Multipart Message Parsing
// =============================================================================
// ParseMultipartRequest parses a multipart request with message + files
func (m *MultipartTransport) ParseMultipartRequest(r *http.Request) (
*types.UnifiedSynapseMessage,
[]types.FileContent,
error,
) {
// Limit max size (100MB default)
if err := r.ParseMultipartForm(100 << 20); err != nil {
return nil, nil, fmt.Errorf("failed to parse multipart: %w", err)
}
// 1. Parse the message JSON
requestValue := r.FormValue("request")
if requestValue == "" {
return nil, nil, fmt.Errorf("missing 'request' field")
}
var msg types.UnifiedSynapseMessage
if err := json.Unmarshal([]byte(requestValue), &msg); err != nil {
return nil, nil, fmt.Errorf("invalid request JSON: %w", err)
}
// 2. Parse file attachments
var files []types.FileContent
for _, fileHeaders := range r.MultipartForm.File {
for _, fh := range fileHeaders {
file, err := fh.Open()
if err != nil {
log.Printf("[Multipart] Failed to open file %s: %v", fh.Filename, err)
continue
}
data, err := io.ReadAll(file)
file.Close()
if err != nil {
log.Printf("[Multipart] Failed to read file %s: %v", fh.Filename, err)
continue
}
mimeType := fh.Header.Get("Content-Type")
if mimeType == "" {
mimeType = types.InferMimeType(fh.Filename)
}
fc := types.FileContent{
ContentBase: types.ContentBase{
MIMEType: mimeType,
Encoding: types.EncodingBase64,
Size: fh.Size,
TransferMode: types.TransferInline,
},
Name: fh.Filename,
Data: string(data), // In real impl: base64 encode
}
files = append(files, fc)
}
}
log.Printf("[Multipart] Parsed request: %d files received", len(files))
return &msg, files, nil
}
// =============================================================================
// Polling Client - 轮询客户端
// =============================================================================
// PollingClient handles task status polling
type PollingClient struct {
mu sync.RWMutex
client *http.Client
baseURL string
authSecret string
// Polling configuration
initialInterval time.Duration
maxInterval time.Duration
backoffFactor float64
maxDuration time.Duration
// Active polls
activePolls map[string]*pollContext
}
type pollContext struct {
taskID string
ctx context.Context
cancel context.CancelFunc
interval time.Duration
lastStatus string
lastProgress int
callback func(status *types.TaskInfo) bool // returns true to stop polling
}
// PollingClientConfig holds configuration for the polling client
type PollingClientConfig struct {
BaseURL string
AuthSecret string
InitialInterval time.Duration
MaxInterval time.Duration
BackoffFactor float64
MaxDuration time.Duration
Timeout time.Duration
}
// DefaultPollingClientConfig returns default polling configuration
func DefaultPollingClientConfig() *PollingClientConfig {
return &PollingClientConfig{
InitialInterval: 5 * time.Second,
MaxInterval: 60 * time.Second,
BackoffFactor: 2.0,
MaxDuration: 2 * time.Hour,
Timeout: 30 * time.Second,
}
}
// NewPollingClient creates a new polling client
func NewPollingClient(config *PollingClientConfig) *PollingClient {
if config == nil {
config = DefaultPollingClientConfig()
}
return &PollingClient{
client: &http.Client{
Timeout: config.Timeout,
},
baseURL: config.BaseURL,
authSecret: config.AuthSecret,
initialInterval: config.InitialInterval,
maxInterval: config.MaxInterval,
backoffFactor: config.BackoffFactor,
maxDuration: config.MaxDuration,
activePolls: make(map[string]*pollContext),
}
}
// =============================================================================
// Single Poll - 单次轮询
// =============================================================================
// GetTaskStatus polls for a task's current status
func (p *PollingClient) GetTaskStatus(ctx context.Context, taskID string) (*types.TaskInfo, error) {
url := fmt.Sprintf("%s/synapse/task/%s", p.baseURL, taskID)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Accept", "application/json")
req.Header.Set("X-Synapse-Version", "1.0.0")
resp, err := p.client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == 304 {
return nil, nil // Not modified
}
if resp.StatusCode >= 400 {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}
var result types.TaskStatusResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if !result.Success {
return nil, fmt.Errorf("API error: %s", result.Error.Message)
}
return result.Task, nil
}
// ListTasks lists tasks with filtering
func (p *PollingClient) ListTasks(ctx context.Context, status string, limit, offset int) (*types.TaskListResponse, error) {
url := fmt.Sprintf("%s/synapse/tasks?status=%s&limit=%d&offset=%d",
p.baseURL, status, limit, offset)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := p.client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
var result types.TaskListResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return &result, nil
}
// GetTaskEvents gets incremental task events
func (p *PollingClient) GetTaskEvents(ctx context.Context, taskID, since string) (*types.TaskEventsResponse, error) {
url := fmt.Sprintf("%s/synapse/task/%s/events", p.baseURL, taskID)
if since != "" {
url += fmt.Sprintf("?since=%s", since)
}
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
resp, err := p.client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
var result types.TaskEventsResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return &result, nil
}
// =============================================================================
// Continuous Polling - 持续轮询
// =============================================================================
// StartPolling starts continuous polling for a task
func (p *PollingClient) StartPolling(
ctx context.Context,
taskID string,
callback func(status *types.TaskInfo) bool,
) error {
p.mu.Lock()
if _, exists := p.activePolls[taskID]; exists {
p.mu.Unlock()
return fmt.Errorf("polling already active for task %s", taskID)
}
pollCtx, cancel := context.WithTimeout(ctx, p.maxDuration)
pc := &pollContext{
taskID: taskID,
ctx: pollCtx,
cancel: cancel,
interval: p.initialInterval,
callback: callback,
}
p.activePolls[taskID] = pc
p.mu.Unlock()
go p.pollLoop(pc)
return nil
}
// StopPolling stops polling for a task
func (p *PollingClient) StopPolling(taskID string) {
p.mu.Lock()
defer p.mu.Unlock()
if pc, exists := p.activePolls[taskID]; exists {
pc.cancel()
delete(p.activePolls, taskID)
}
}
// pollLoop is the main polling loop
func (p *PollingClient) pollLoop(pc *pollContext) {
defer func() {
p.mu.Lock()
delete(p.activePolls, pc.taskID)
p.mu.Unlock()
}()
ticker := time.NewTicker(pc.interval)
defer ticker.Stop()
consecutiveNoChange := 0
for {
select {
case <-pc.ctx.Done():
log.Printf("[Polling] Stopped for task %s: %v", pc.taskID, pc.ctx.Err())
return
case <-ticker.C:
status, err := p.GetTaskStatus(pc.ctx, pc.taskID)
if err != nil {
log.Printf("[Polling] Error for task %s: %v", pc.taskID, err)
continue
}
if status == nil {
continue
}
// Check if status or progress changed
changed := status.Status != pc.lastStatus || status.Progress != pc.lastProgress
if changed {
consecutiveNoChange = 0
pc.interval = p.initialInterval
ticker.Reset(pc.interval)
} else {
consecutiveNoChange++
// Apply backoff after 3 consecutive no-change polls
if consecutiveNoChange >= 3 {
newInterval := time.Duration(float64(pc.interval) * p.backoffFactor)
if newInterval > p.maxInterval {
newInterval = p.maxInterval
}
if newInterval != pc.interval {
pc.interval = newInterval
ticker.Reset(pc.interval)
log.Printf("[Polling] Backoff to %v for task %s", pc.interval, pc.taskID)
}
}
}
pc.lastStatus = status.Status
pc.lastProgress = status.Progress
// Call callback
if pc.callback(status) {
log.Printf("[Polling] Callback returned stop for task %s", pc.taskID)
return
}
// Check terminal states
if status.Status == "completed" || status.Status == "failed" ||
status.Status == "cancelled" || status.Status == "rejected" {
log.Printf("[Polling] Task %s reached terminal state: %s", pc.taskID, status.Status)
return
}
}
}
}
// =============================================================================
// Capability Query - 能力查询(同步)
// =============================================================================
// QueryCapabilities queries an agent's capabilities synchronously
func (p *PollingClient) QueryCapabilities(ctx context.Context, baseURL string) (*types.AgentCapabilities, error) {
url := fmt.Sprintf("%s/synapse/capabilities", baseURL)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := p.client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
var result types.CapabilitiesResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if !result.Success {
return nil, fmt.Errorf("API error: %s", result.Error.Message)
}
return result.Data, nil
}
// HealthCheck checks if an agent is reachable
func (p *PollingClient) HealthCheck(ctx context.Context, baseURL string) (bool, error) {
url := fmt.Sprintf("%s/synapse/health", baseURL)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return false, fmt.Errorf("failed to create request: %w", err)
}
resp, err := p.client.Do(req)
if err != nil {
return false, nil
}
defer resp.Body.Close()
return resp.StatusCode == 200, nil
}
// =============================================================================
// Hybrid Mode - 混合模式 (Callback + Polling Fallback)
// =============================================================================
// HybridTransport combines callback-based push with polling-based fallback
type HybridTransport struct {
callbackTransport *HTTPTransport
pollingClient *PollingClient
mu sync.RWMutex
pendingTasks map[string]*hybridTaskState
}
type hybridTaskState struct {
taskID string
callbackOK bool
pollingActive bool
lastStatus string
}
// NewHybridTransport creates a new hybrid transport
func NewHybridTransport(callback *HTTPTransport, polling *PollingClient) *HybridTransport {
return &HybridTransport{
callbackTransport: callback,
pollingClient: polling,
pendingTasks: make(map[string]*hybridTaskState),
}
}
// SendTaskWithHybridFallback sends a task request and sets up both callback and polling
func (h *HybridTransport) SendTaskWithHybridFallback(
ctx context.Context,
endpoint string,
msg *types.UnifiedSynapseMessage,
callback func(status *types.TaskInfo),
) error {
// 1. First, try callback-based approach
err := h.callbackTransport.Send(ctx, endpoint, msg)
if err != nil {
log.Printf("[Hybrid] Direct send failed, using polling only: %v", err)
}
// 2. Extract task ID from payload
var payload types.TaskRequestPayload
if err := json.Unmarshal(msg.Payload, &payload); err != nil {
return fmt.Errorf("failed to extract task ID: %w", err)
}
taskID := payload.Task.ID
// 3. Set up polling as fallback after 30s
h.mu.Lock()
h.pendingTasks[taskID] = &hybridTaskState{
taskID: taskID,
callbackOK: err == nil,
pollingActive: false,
}
h.mu.Unlock()
// Start polling after a delay if callback hasn't been received
go func() {
select {
case <-time.After(30 * time.Second):
h.mu.RLock()
state, exists := h.pendingTasks[taskID]
h.mu.RUnlock()
if exists && !state.callbackOK {
log.Printf("[Hybrid] Callback not received for %s, starting polling fallback", taskID)
h.pollingClient.StartPolling(ctx, taskID, func(status *types.TaskInfo) bool {
if callback != nil {
callback(status)
}
return false
})
}
case <-ctx.Done():
return
}
}()
return nil
}
// NotifyCallbackReceived notifies that a callback was received (stops polling fallback)
func (h *HybridTransport) NotifyCallbackReceived(taskID string) {
h.mu.Lock()
defer h.mu.Unlock()
if state, exists := h.pendingTasks[taskID]; exists {
state.callbackOK = true
if state.pollingActive {
h.pollingClient.StopPolling(taskID)
state.pollingActive = false
}
log.Printf("[Hybrid] Callback received for %s, polling disabled", taskID)
}
}

420
transport/transport.go Normal file
View File

@@ -0,0 +1,420 @@
package transport
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
"sync"
"time"
"synapse/types"
)
// =============================================================================
// HTTP Transport
// =============================================================================
// HTTPTransport implements HTTP-based transport for Synapse messages
type HTTPTransport struct {
mu sync.RWMutex
// Configuration
baseURL string
timeout time.Duration
maxRetries int
retryDelay time.Duration
// HTTP Client
client *http.Client
// Security
authSecret string
// Callbacks
callbackEndpoint string
callbackHandler MessageCallbackHandler
}
// HTTPTransportConfig holds HTTP transport configuration
type HTTPTransportConfig struct {
BaseURL string
Timeout time.Duration
MaxRetries int
RetryDelay time.Duration
AuthSecret string
}
// DefaultHTTPTransportConfig returns default HTTP transport configuration
func DefaultHTTPTransportConfig() *HTTPTransportConfig {
return &HTTPTransportConfig{
Timeout: 30 * time.Second,
MaxRetries: 5,
RetryDelay: 2 * time.Second,
}
}
// NewHTTPTransport creates a new HTTP transport
func NewHTTPTransport(config *HTTPTransportConfig) *HTTPTransport {
if config == nil {
config = DefaultHTTPTransportConfig()
}
t := &HTTPTransport{
baseURL: config.BaseURL,
timeout: config.Timeout,
maxRetries: config.MaxRetries,
retryDelay: config.RetryDelay,
authSecret: config.AuthSecret,
client: &http.Client{
Timeout: config.Timeout,
},
}
return t
}
// SetCallbackHandler sets the handler for incoming callbacks
func (t *HTTPTransport) SetCallbackHandler(handler MessageCallbackHandler) {
t.callbackHandler = handler
}
// SetCallbackEndpoint sets the endpoint for receiving callbacks
func (t *HTTPTransport) SetCallbackEndpoint(endpoint string) {
t.callbackEndpoint = endpoint
}
// =============================================================================
// Message Sending
// =============================================================================
// Send sends a message via HTTP POST
func (t *HTTPTransport) Send(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error {
if endpoint == "" {
return fmt.Errorf("endpoint is required")
}
// Add signature if secret is configured
if t.authSecret != "" {
t.signMessage(msg)
}
// Marshal message
body, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
// Create request
req, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(body))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
// Set headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Synapse-Version", "1.0.0")
req.Header.Set("X-Synapse-Agent", msg.Sender.AgentID)
req.Header.Set("X-Synapse-System", msg.Sender.SystemID)
if msg.Signature != "" {
req.Header.Set("X-Synapse-Signature", msg.Signature)
}
// Send with retries
var lastErr error
for i := 0; i < t.maxRetries; i++ {
if i > 0 {
delay := t.retryDelay * time.Duration(1<<uint(i-1))
log.Printf("[Transport] Retrying request (%d/%d) after %v", i, t.maxRetries, delay)
time.Sleep(delay)
}
resp, err := t.client.Do(req)
if err != nil {
lastErr = err
log.Printf("[Transport] Request failed: %v", err)
continue
}
// Read response
respBody, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
log.Printf("[Transport] Message sent successfully: %s to %s", msg.ID, endpoint)
return nil
}
lastErr = fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(respBody))
log.Printf("[Transport] Request failed: %v", lastErr)
}
return fmt.Errorf("failed to send message after %d retries: %w", t.maxRetries, lastErr)
}
// SendWithCallback sends a message and waits for callback response
func (t *HTTPTransport) SendWithCallback(
ctx context.Context,
endpoint string,
msg *types.UnifiedSynapseMessage,
timeout time.Duration,
) (*types.UnifiedSynapseMessage, error) {
// Create channel for response
respChan := make(chan *types.UnifiedSynapseMessage, 1)
errChan := make(chan error, 1)
// Register callback handler for this message
correlationID := msg.CorrelationID
if correlationID == "" {
correlationID = msg.ID
}
// Start timeout goroutine
go func() {
select {
case <-ctx.Done():
errChan <- ctx.Err()
case <-time.After(timeout):
errChan <- fmt.Errorf("timeout waiting for response")
}
}()
// Send message
if err := t.Send(ctx, endpoint, msg); err != nil {
return nil, err
}
// Wait for response
select {
case resp := <-respChan:
return resp, nil
case err := <-errChan:
return nil, err
}
}
// =============================================================================
// Message Reception
// =============================================================================
// MessageCallbackHandler is the interface for handling incoming messages
type MessageCallbackHandler interface {
HandleMessage(msg *types.UnifiedSynapseMessage) error
}
// HandleCallback handles an incoming callback message (HTTP POST)
func (t *HTTPTransport) HandleCallback(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// Read body
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
r.Body.Close()
// Parse message
var msg types.UnifiedSynapseMessage
if err := json.Unmarshal(body, &msg); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
// Verify signature if configured
if t.authSecret != "" && msg.Signature != "" {
if !t.verifySignature(body, msg.Signature) {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
}
// Handle message
if t.callbackHandler != nil {
if err := t.callbackHandler.HandleMessage(&msg); err != nil {
log.Printf("[Transport] Failed to handle message: %v", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
}
// Send success response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"message": "received",
})
}
// =============================================================================
// HTTP Server
// =============================================================================
// Server is an HTTP server for Synapse transport
type Server struct {
transport *HTTPTransport
mux *http.ServeMux
server *http.Server
}
// NewServer creates a new HTTP server for Synapse transport
func NewServer(addr string, transport *HTTPTransport) *Server {
mux := http.NewServeMux()
server := &Server{
transport: transport,
mux: mux,
server: &http.Server{
Addr: addr,
Handler: mux,
},
}
// Register callback endpoint
mux.HandleFunc("/synapse/callback", transport.HandleCallback)
mux.HandleFunc("/synapse/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
})
return server
}
// Start starts the HTTP server
func (s *Server) Start() error {
log.Printf("[Transport] Starting HTTP server on %s", s.server.Addr)
return s.server.ListenAndServe()
}
// Stop stops the HTTP server
func (s *Server) Stop(ctx context.Context) error {
return s.server.Shutdown(ctx)
}
// =============================================================================
// Security
// =============================================================================
func (t *HTTPTransport) signMessage(msg *types.UnifiedSynapseMessage) {
// Create canonical body (message without signature)
msgCopy := *msg
msgCopy.Signature = ""
body, _ := json.Marshal(msgCopy)
// Compute HMAC-SHA256
h := hmac.New(sha256.New, []byte(t.authSecret))
h.Write(body)
signature := hex.EncodeToString(h.Sum(nil))
msg.Signature = signature
}
func (t *HTTPTransport) verifySignature(body []byte, signature string) bool {
h := hmac.New(sha256.New, []byte(t.authSecret))
h.Write(body)
expected := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(strings.ToLower(signature)), []byte(strings.ToLower(expected)))
}
// =============================================================================
// Callback Sender (implements p2p.CallbackSender)
// =============================================================================
// HTTPCallbackSender implements p2p.CallbackSender using HTTP transport
type HTTPCallbackSender struct {
transport *HTTPTransport
}
// NewHTTPCallbackSender creates a new HTTP callback sender
func NewHTTPCallbackSender(t *HTTPTransport) *HTTPCallbackSender {
return &HTTPCallbackSender{transport: t}
}
// SendCallback sends a callback message
func (s *HTTPCallbackSender) SendCallback(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error {
if endpoint == "" && msg.Receiver != nil {
endpoint = msg.Receiver.Endpoint
}
return s.transport.Send(ctx, endpoint, msg)
}
// =============================================================================
// WebSocket Transport (for future extension)
// =============================================================================
// WebSocketConfig holds WebSocket transport configuration
type WebSocketConfig struct {
URL string
Timeout time.Duration
}
// WebSocketTransport implements WebSocket-based transport
type WebSocketTransport struct {
mu sync.RWMutex
config *WebSocketConfig
handler MessageCallbackHandler
}
// NewWebSocketTransport creates a new WebSocket transport
func NewWebSocketTransport(config *WebSocketConfig) *WebSocketTransport {
return &WebSocketTransport{
config: config,
}
}
// SetMessageHandler sets the message handler
func (t *WebSocketTransport) SetMessageHandler(handler MessageCallbackHandler) {
t.handler = handler
}
// Connect establishes a WebSocket connection
func (t *WebSocketTransport) Connect(ctx context.Context) error {
// Placeholder for WebSocket implementation
// In a real implementation, this would use gorilla/websocket
log.Printf("[Transport] WebSocket connect to %s (placeholder)", t.config.URL)
return nil
}
// Send sends a message via WebSocket
func (t *WebSocketTransport) Send(ctx context.Context, msg *types.UnifiedSynapseMessage) error {
// Placeholder for WebSocket send implementation
log.Printf("[Transport] WebSocket send (placeholder): %s", msg.ID)
return nil
}
// =============================================================================
// Message Utilities
// =============================================================================
// CreateSuccessResponse creates a success response
func CreateSuccessResponse(msgID string) map[string]interface{} {
return map[string]interface{}{
"success": true,
"message": "received",
"id": msgID,
}
}
// CreateErrorResponse creates an error response
func CreateErrorResponse(code int, message string) map[string]interface{} {
return map[string]interface{}{
"success": false,
"error": map[string]interface{}{
"code": code,
"message": message,
},
}
}

389
types/content.go Normal file
View File

@@ -0,0 +1,389 @@
package types
// =============================================================================
// Transport Mode Definitions
// =============================================================================
// TransportMode defines the transport mode for task communication
type TransportMode string
const (
TransportModeCallback TransportMode = "callback" // Async callback (webhook)
TransportModePolling TransportMode = "polling" // Async polling
TransportModeSync TransportMode = "sync" // Synchronous
TransportModeHybrid TransportMode = "hybrid" // Callback + Polling fallback
)
// TransportPreferences defines transport preferences for a task
type TransportPreferences struct {
Preferred TransportMode `json:"preferred"`
Fallback TransportMode `json:"fallback,omitempty"`
CallbackEndpoint string `json:"callback_endpoint,omitempty"`
PollingInterval int `json:"polling_interval,omitempty"` // seconds
ProgressUpdates bool `json:"progress_updates"`
ProgressInterval int `json:"progress_interval,omitempty"` // seconds
}
// DefaultTransportPreferences returns default transport preferences
func DefaultTransportPreferences() *TransportPreferences {
return &TransportPreferences{
Preferred: TransportModeCallback,
Fallback: TransportModePolling,
PollingInterval: 5,
ProgressUpdates: true,
ProgressInterval: 10,
}
}
// =============================================================================
// Content Type Definitions
// =============================================================================
// MIME type constants
const (
MIMEJSON = "application/json"
MIMETextPlain = "text/plain"
MIMETextMarkdown = "text/markdown"
MIMETextHTML = "text/html"
MIMETextCSV = "text/csv"
MIMEImageJPEG = "image/jpeg"
MIMEImagePNG = "image/png"
MIMEImageWebP = "image/webp"
MIMEImageGIF = "image/gif"
MIMEImageSVG = "image/svg+xml"
MIMEVideoMP4 = "video/mp4"
MIMEVideoWebM = "video/webm"
MIMEAudioMP3 = "audio/mpeg"
MIMEAudioWAV = "audio/wav"
MIMEAudioAAC = "audio/aac"
MIMEPDF = "application/pdf"
MIMEOctetStream = "application/octet-stream"
MIMEMultipartForm = "multipart/form-data"
)
// ContentEncoding defines content encoding methods
type ContentEncoding string
const (
EncodingNone ContentEncoding = "none"
EncodingBase64 ContentEncoding = "base64"
EncodingGzip ContentEncoding = "gzip"
EncodingDeflate ContentEncoding = "deflate"
)
// TransferMode defines how content is transferred
type TransferMode string
const (
TransferInline TransferMode = "inline" // Embedded in message (base64)
TransferURL TransferMode = "url" // Via URL reference
TransferStreaming TransferMode = "streaming" // Via streaming protocol
TransferMultipart TransferMode = "multipart" // Via multipart form
)
// =============================================================================
// Content Objects
// =============================================================================
// ContentBase is the base struct for all content types
type ContentBase struct {
MIMEType string `json:"mime_type"`
Encoding ContentEncoding `json:"encoding,omitempty"`
Size int64 `json:"size,omitempty"`
Checksum string `json:"checksum,omitempty"` // format: "sha256:xxx"
TransferMode TransferMode `json:"transfer_mode"`
}
// TextContent represents textual content
type TextContent struct {
ContentBase
Charset string `json:"charset"`
Language string `json:"language,omitempty"`
Data string `json:"data"`
}
// JSONContent represents JSON structured content
type JSONContent struct {
ContentBase
Schema string `json:"schema,omitempty"` // Schema identifier
SchemaURL string `json:"schema_url,omitempty"` // URL to schema definition
Data any `json:"data"`
}
// CodeContent represents source code content
type CodeContent struct {
ContentBase
Language string `json:"language"`
Version string `json:"version,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`
Data string `json:"data"`
}
// FileContent represents a file (image, video, document, etc.)
type FileContent struct {
ContentBase
Name string `json:"name"`
Role string `json:"role,omitempty"` // e.g., "reference", "result", "attachment"
Description string `json:"description,omitempty"`
// For inline transfer
Data string `json:"data,omitempty"` // base64 encoded if binary
// For URL transfer
URL string `json:"url,omitempty"`
URLExpiresAt *Timestamp `json:"url_expires_at,omitempty"`
DownloadMethod string `json:"download_method,omitempty"` // GET, POST, signed_url
RangeSupported bool `json:"range_supported,omitempty"`
// Metadata
Width int `json:"width,omitempty"` // images/videos
Height int `json:"height,omitempty"` // images/videos
Duration float64 `json:"duration,omitempty"` // audio/video (seconds)
Resolution string `json:"resolution,omitempty"` // videos
ThumbnailURL string `json:"thumbnail_url,omitempty"`
}
// =============================================================================
// Content Bundles (multiple files)
// =============================================================================
// ContentBundle represents a collection of content items
type ContentBundle struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Items []ContentBundleItem `json:"items"`
TotalSize int64 `json:"total_size,omitempty"`
CreatedAt Timestamp `json:"created_at"`
}
// ContentBundleItem is an item in a content bundle
type ContentBundleItem struct {
ID string `json:"id"`
Role string `json:"role"` // main, reference, attachment, thumbnail, etc.
Content any `json:"content"` // TextContent | FileContent | JSONContent | CodeContent
}
// =============================================================================
// File Transfer Helper Functions
// =============================================================================
// IsSmallFile checks if a file should be transferred inline
func IsSmallFile(size int64, threshold int64) bool {
if threshold <= 0 {
threshold = 1024 * 1024 // 1MB default
}
return size <= threshold
}
// InferMimeType infers MIME type from filename
func InferMimeType(filename string) string {
extMap := map[string]string{
".json": MIMEJSON,
".txt": MIMETextPlain,
".md": MIMETextMarkdown,
".html": MIMETextHTML,
".htm": MIMETextHTML,
".csv": MIMETextCSV,
".jpg": MIMEImageJPEG,
".jpeg": MIMEImageJPEG,
".png": MIMEImagePNG,
".webp": MIMEImageWebP,
".gif": MIMEImageGIF,
".svg": MIMEImageSVG,
".mp4": MIMEVideoMP4,
".webm": MIMEVideoWebM,
".mp3": MIMEAudioMP3,
".wav": MIMEAudioWAV,
".aac": MIMEAudioAAC,
".pdf": MIMEPDF,
".bin": MIMEOctetStream,
}
// Simple extension matching (lowercase would be better)
for ext, mime := range extMap {
if len(filename) > len(ext) && filename[len(filename)-len(ext):] == ext {
return mime
}
// Also check uppercase
upperExt := toUpper(ext)
if len(filename) > len(upperExt) && filename[len(filename)-len(upperExt):] == upperExt {
return mime
}
}
return MIMEOctetStream
}
func toUpper(s string) string {
result := make([]byte, len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if c >= 'a' && c <= 'z' {
c = c - 'a' + 'A'
}
result[i] = c
}
return string(result)
}
// NewInlineFileContent creates an inline file content (for small files)
func NewInlineFileContent(name, mimeType string, data []byte, role string) *FileContent {
// base64 encode would happen here in real implementation
return &FileContent{
ContentBase: ContentBase{
MIMEType: mimeType,
Encoding: EncodingBase64,
Size: int64(len(data)),
TransferMode: TransferInline,
},
Name: name,
Role: role,
Data: "", // base64.RawStdEncoding.EncodeToString(data)
}
}
// NewURLFileContent creates a URL-referenced file content (for large files)
func NewURLFileContent(name, mimeType string, size int64, url string, expiresAt *Timestamp, role string) *FileContent {
return &FileContent{
ContentBase: ContentBase{
MIMEType: mimeType,
Size: size,
TransferMode: TransferURL,
},
Name: name,
Role: role,
URL: url,
URLExpiresAt: expiresAt,
DownloadMethod: "GET",
RangeSupported: true,
}
}
// =============================================================================
// Polling API Types
// =============================================================================
// TaskStatusResponse is the response for task status polling
type TaskStatusResponse struct {
Success bool `json:"success"`
Task *TaskInfo `json:"task,omitempty"`
Error *SynapseError `json:"error,omitempty"`
}
// TaskInfo represents task status information
type TaskInfo struct {
ID string `json:"id"`
Type string `json:"type"`
Status string `json:"status"`
Progress int `json:"progress"`
Message string `json:"message,omitempty"`
CreatedAt Timestamp `json:"created_at"`
UpdatedAt Timestamp `json:"updated_at"`
ETA *Timestamp `json:"eta,omitempty"`
Result any `json:"result,omitempty"`
Error *SynapseError `json:"error,omitempty"`
ETag string `json:"etag,omitempty"`
}
// TaskListResponse is the response for listing tasks
type TaskListResponse struct {
Success bool `json:"success"`
Tasks []TaskInfo `json:"tasks"`
Total int `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
HasMore bool `json:"has_more"`
}
// TaskEvent represents a task event
type TaskEvent struct {
ID string `json:"id"`
Type string `json:"type"` // status_update, progress, log, error, complete
Timestamp Timestamp `json:"timestamp"`
Data any `json:"data"`
}
// TaskEventsResponse is the response for task events
type TaskEventsResponse struct {
Success bool `json:"success"`
Since string `json:"since"`
Events []TaskEvent `json:"events"`
HasMore bool `json:"has_more"`
Next string `json:"next_cursor,omitempty"`
}
// =============================================================================
// Streaming Types
// =============================================================================
// StreamChunk represents a chunk of streaming data
type StreamChunk struct {
TaskID string `json:"task_id"`
Sequence int64 `json:"sequence"`
ChunkType string `json:"chunk_type"` // text, binary, audio, video
Data string `json:"data"` // base64 for binary
Final bool `json:"final"`
Timestamp Timestamp `json:"timestamp"`
}
// StreamComplete is sent when streaming is complete
type StreamComplete struct {
TaskID string `json:"task_id"`
TotalChunks int64 `json:"total_chunks"`
FullContentChecksum string `json:"full_content_checksum"`
Result any `json:"result,omitempty"`
}
// =============================================================================
// Capabilities Query (Synchronous)
// =============================================================================
// CapabilitiesResponse is the response for capability query
type CapabilitiesResponse struct {
Success bool `json:"success"`
Data *AgentCapabilities `json:"data,omitempty"`
Error *SynapseError `json:"error,omitempty"`
}
// AgentCapabilities describes an agent's capabilities
type AgentCapabilities struct {
AgentID string `json:"agent_id"`
AgentName string `json:"agent_name"`
SystemID string `json:"system_id"`
ProtocolVersion string `json:"protocol_version"`
SupportedTaskTypes []SupportedTaskType `json:"supported_task_types"`
SupportedContentTypes []string `json:"supported_content_types"`
SupportedTransportModes []TransportMode `json:"supported_transport_modes"`
// Limits
MaxFileSize int64 `json:"max_file_size"`
MaxConcurrentTasks int `json:"max_concurrent_tasks"`
MaxTaskDuration int `json:"max_task_duration_seconds"`
// Features
SupportProgressUpdates bool `json:"support_progress_updates"`
SupportStreaming bool `json:"support_streaming"`
SupportHiL bool `json:"support_hil"`
SupportGroups bool `json:"support_groups"`
}
// SupportedTaskType describes a supported task type
type SupportedTaskType struct {
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
EstimatedTime string `json:"estimated_time"` // e.g., "30s-2min"
InputSchema string `json:"input_schema,omitempty"`
OutputSchema string `json:"output_schema,omitempty"`
Capabilities []string `json:"capabilities,omitempty"`
}
// NewCapabilitiesResponse creates a capabilities response
func NewCapabilitiesResponse(caps *AgentCapabilities) *CapabilitiesResponse {
return &CapabilitiesResponse{
Success: true,
Data: caps,
}
}

332
types/errors.go Normal file
View File

@@ -0,0 +1,332 @@
package types
// =============================================================================
// Synapse Error Codes
// =============================================================================
// SynapseErrorCode defines error code ranges
type SynapseErrorCode int
const (
// System errors (1000-1999)
ErrSystemError SynapseErrorCode = 1000
ErrInternalError SynapseErrorCode = 1001
ErrMaintenance SynapseErrorCode = 1002
// Task errors (2000-2999)
ErrTaskNotFound SynapseErrorCode = 2001
ErrTaskAlreadyCompleted SynapseErrorCode = 2002
ErrTaskCancelled SynapseErrorCode = 2003
ErrTaskTimeout SynapseErrorCode = 2004
ErrTaskInvalidParams SynapseErrorCode = 2005
// Information errors (3000-3999)
ErrRequiredFieldMissing SynapseErrorCode = 3001
ErrInvalidFieldFormat SynapseErrorCode = 3002
ErrInfoNotProvided SynapseErrorCode = 3003
// Capability errors (4000-4999)
ErrUnsupportedTaskType SynapseErrorCode = 4001
ErrCapabilityLacking SynapseErrorCode = 4002
ErrResourceBusy SynapseErrorCode = 4003
ErrQuotaExceeded SynapseErrorCode = 4004
// Security errors (5000-5999)
ErrAuthFailed SynapseErrorCode = 5001
ErrSignatureInvalid SynapseErrorCode = 5002
ErrPermissionDenied SynapseErrorCode = 5003
ErrPolicyViolation SynapseErrorCode = 5004
// Communication errors (6000-6999)
ErrEndpointUnreachable SynapseErrorCode = 6001
ErrCallbackFailed SynapseErrorCode = 6002
ErrMessageFormatInvalid SynapseErrorCode = 6003
ErrTTLExpired SynapseErrorCode = 6004
// Group errors (7000-7999)
ErrGroupNotFound SynapseErrorCode = 7001
ErrGroupNotActive SynapseErrorCode = 7002
ErrMemberNotFound SynapseErrorCode = 7003
ErrNotGroupMember SynapseErrorCode = 7004
ErrConsensusNotReached SynapseErrorCode = 7005
ErrRoleNotFound SynapseErrorCode = 7006
ErrAlreadyMember SynapseErrorCode = 7007
ErrGroupFull SynapseErrorCode = 7008
// HiL errors (8000-8999)
ErrHumanTimeout SynapseErrorCode = 8001
ErrHumanDeclined SynapseErrorCode = 8002
ErrEscalationLevel SynapseErrorCode = 8003
// Validation errors (9000-9999)
ErrInvalidParams SynapseErrorCode = 9001
)
// =============================================================================
// Error Categories
// =============================================================================
// ErrorCategory maps error codes to categories
func (c SynapseErrorCode) Category() string {
switch {
case c >= 1000 && c < 2000:
return "SYSTEM_ERROR"
case c >= 2000 && c < 3000:
return "TASK_ERROR"
case c >= 3000 && c < 4000:
return "INFO_ERROR"
case c >= 4000 && c < 5000:
return "CAPABILITY_ERROR"
case c >= 5000 && c < 6000:
return "SECURITY_ERROR"
case c >= 6000 && c < 7000:
return "COMMUNICATION_ERROR"
case c >= 7000 && c < 8000:
return "GROUP_ERROR"
case c >= 8000 && c < 9000:
return "HIL_ERROR"
case c >= 9000 && c < 10000:
return "VALIDATION_ERROR"
default:
return "UNKNOWN_ERROR"
}
}
// =============================================================================
// Synapse Error
// =============================================================================
// SynapseError represents a structured error response
type SynapseError struct {
Code SynapseErrorCode `json:"code"`
Category string `json:"category"`
Message string `json:"message"`
Details any `json:"details,omitempty"`
RetryAfter *int `json:"retry_after,omitempty"` // seconds
Timestamp Timestamp `json:"timestamp"`
}
// NewSynapseError creates a new Synapse error
func NewSynapseError(code SynapseErrorCode, message string, details any) *SynapseError {
return &SynapseError{
Code: code,
Category: code.Category(),
Message: message,
Details: details,
Timestamp: NowTimestamp(),
}
}
// NewSynapseErrorWithRetry creates a new Synapse error with retry hint
func NewSynapseErrorWithRetry(code SynapseErrorCode, message string, details any, retryAfter int) *SynapseError {
return &SynapseError{
Code: code,
Category: code.Category(),
Message: message,
Details: details,
RetryAfter: &retryAfter,
Timestamp: NowTimestamp(),
}
}
// Error implements the error interface
func (e *SynapseError) Error() string {
return e.Message
}
// ToMap converts error to map for JSON serialization
func (e *SynapseError) ToMap() map[string]any {
m := map[string]any{
"code": e.Code,
"category": e.Category,
"message": e.Message,
"timestamp": e.Timestamp,
}
if e.Details != nil {
m["details"] = e.Details
}
if e.RetryAfter != nil {
m["retry_after"] = *e.RetryAfter
}
return m
}
// =============================================================================
// Predefined Errors
// =============================================================================
var (
ErrTaskNotFoundError = NewSynapseError(
ErrTaskNotFound,
"Task not found",
nil,
)
ErrTaskAlreadyCompletedError = NewSynapseError(
ErrTaskAlreadyCompleted,
"Task has already been completed",
nil,
)
ErrTaskCancelledError = NewSynapseError(
ErrTaskCancelled,
"Task has been cancelled",
nil,
)
ErrTaskTimeoutError = NewSynapseError(
ErrTaskTimeout,
"Task execution timed out",
nil,
)
ErrRequiredFieldMissingError = NewSynapseError(
ErrRequiredFieldMissing,
"Required field is missing",
nil,
)
ErrUnsupportedTaskTypeError = NewSynapseError(
ErrUnsupportedTaskType,
"Task type is not supported by this agent",
nil,
)
ErrCapabilityLackingError = NewSynapseError(
ErrCapabilityLacking,
"Agent lacks the required capability",
nil,
)
ErrResourceBusyError = NewSynapseError(
ErrResourceBusy,
"Agent is currently busy processing other tasks",
nil,
)
ErrAuthFailedError = NewSynapseError(
ErrAuthFailed,
"Authentication failed",
nil,
)
ErrSignatureInvalidError = NewSynapseError(
ErrSignatureInvalid,
"Message signature verification failed",
nil,
)
ErrPermissionDeniedError = NewSynapseError(
ErrPermissionDenied,
"Permission denied",
nil,
)
ErrEndpointUnreachableError = NewSynapseError(
ErrEndpointUnreachable,
"Callback endpoint is unreachable",
nil,
)
ErrMessageFormatInvalidError = NewSynapseError(
ErrMessageFormatInvalid,
"Message format is invalid",
nil,
)
ErrGroupNotFoundError = NewSynapseError(
ErrGroupNotFound,
"Group not found",
nil,
)
ErrNotGroupMemberError = NewSynapseError(
ErrNotGroupMember,
"Agent is not a member of this group",
nil,
)
ErrGroupNotActiveError = NewSynapseError(
ErrGroupNotActive,
"Group is not active",
nil,
)
ErrAlreadyMemberError = NewSynapseError(
ErrAlreadyMember,
"Agent is already a member of this group",
nil,
)
ErrGroupFullError = NewSynapseError(
ErrGroupFull,
"Group has reached maximum capacity",
nil,
)
ErrMemberNotFoundError = NewSynapseError(
ErrMemberNotFound,
"Member not found in group",
nil,
)
ErrInvalidParamsError = NewSynapseError(
ErrInvalidParams,
"Invalid parameters",
nil,
)
ErrHumanTimeoutError = NewSynapseError(
ErrHumanTimeout,
"Human decision timed out",
nil,
)
)
// =============================================================================
// Error Response Builder
// =============================================================================
// ErrorResponse represents a standard error response
type ErrorResponse struct {
Success bool `json:"success"`
Error *SynapseError `json:"error"`
}
// NewErrorResponse creates an error response
func NewErrorResponse(err *SynapseError) *ErrorResponse {
return &ErrorResponse{
Success: false,
Error: err,
}
}
// SuccessResponse represents a standard success response
type SuccessResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data any `json:"data,omitempty"`
}
// NewSuccessResponse creates a success response
func NewSuccessResponse() *SuccessResponse {
return &SuccessResponse{
Success: true,
}
}
// NewSuccessResponseWithData creates a success response with data
func NewSuccessResponseWithData(data any) *SuccessResponse {
return &SuccessResponse{
Success: true,
Data: data,
}
}
// NewSuccessResponseWithMessage creates a success response with message
func NewSuccessResponseWithMessage(message string) *SuccessResponse {
return &SuccessResponse{
Success: true,
Message: message,
}
}

610
types/types.go Normal file
View File

@@ -0,0 +1,610 @@
package types
import (
"encoding/json"
"fmt"
"time"
)
// =============================================================================
// Message ID and Task ID generators
// =============================================================================
// GenerateMessageID generates a unique message ID
func GenerateMessageID() string {
return fmt.Sprintf("msg-%d-%d", time.Now().UnixNano(), time.Now().UnixMilli()%1000)
}
// GenerateTaskID generates a unique task ID
func GenerateTaskID() string {
return fmt.Sprintf("task-%d-%d", time.Now().UnixNano(), time.Now().UnixMilli()%1000)
}
// =============================================================================
// P2P Message Types
// =============================================================================
// P2PMessageType defines all point-to-point message types
type P2PMessageType string
const (
// Task lifecycle
P2PTaskRequest P2PMessageType = "p2p:task_request"
P2PTaskConfirm P2PMessageType = "p2p:task_confirm"
P2PTaskReject P2PMessageType = "p2p:task_reject"
P2PTaskNeedInfo P2PMessageType = "p2p:task_need_info"
P2PTaskInfoProvided P2PMessageType = "p2p:task_info_provided"
P2PTaskStatusUpdate P2PMessageType = "p2p:task_status_update"
P2PTaskComplete P2PMessageType = "p2p:task_complete"
P2PTaskFailed P2PMessageType = "p2p:task_failed"
P2PTaskCancel P2PMessageType = "p2p:task_cancel"
P2PTaskAck P2PMessageType = "p2p:task_ack"
// Communication
P2PAck P2PMessageType = "p2p:ack"
P2PNack P2PMessageType = "p2p:nack"
P2PHeartbeat P2PMessageType = "p2p:heartbeat"
P2PCapabilityQuery P2PMessageType = "p2p:capability_query"
P2PCapabilityResponse P2PMessageType = "p2p:capability_response"
// Human-in-the-Loop
P2PTaskNeedHuman P2PMessageType = "p2p:task_need_human"
P2PHumanDecision P2PMessageType = "p2p:human_decision"
)
// =============================================================================
// Group Message Types
// =============================================================================
// GroupMessageType defines all group message types
type GroupMessageType string
const (
// Group management
GroupCreate GroupMessageType = "group:create"
GroupDisband GroupMessageType = "group:disband"
GroupJoin GroupMessageType = "group:join"
GroupLeave GroupMessageType = "group:leave"
GroupInvite GroupMessageType = "group:invite"
GroupAcceptInvite GroupMessageType = "group:accept_invite"
GroupRejectInvite GroupMessageType = "group:reject_invite"
GroupMemberUpdate GroupMessageType = "group:member_update"
GroupRoleChange GroupMessageType = "group:role_change"
// Group communication
GroupBroadcast GroupMessageType = "group:broadcast"
GroupMulticast GroupMessageType = "group:multicast"
GroupDirect GroupMessageType = "group:direct"
GroupRelay GroupMessageType = "group:relay"
// Task collaboration
GroupTaskSplit GroupMessageType = "group:task_split"
GroupTaskAssign GroupMessageType = "group:task_assign"
GroupTaskMerge GroupMessageType = "group:task_merge"
GroupTaskProgress GroupMessageType = "group:task_progress"
// Consensus
GroupVote GroupMessageType = "group:vote"
GroupVoteSubmit GroupMessageType = "group:vote_submit"
GroupConsensus GroupMessageType = "group:consensus"
// Human-in-the-Loop
GroupConsensusEscalate GroupMessageType = "group:consensus_escalate"
GroupSubtaskEscalate GroupMessageType = "group:subtask_escalate"
GroupHumanDecision GroupMessageType = "group:human_decision"
InterGroupEscalate GroupMessageType = "inter_group:escalate"
)
// =============================================================================
// Agent Endpoint
// =============================================================================
// AgentEndpoint represents an agent's network endpoint
type AgentEndpoint struct {
AgentID string `json:"agent_id"`
AgentName string `json:"agent_name,omitempty"`
SystemID string `json:"system_id"`
SystemName string `json:"system_name,omitempty"`
Endpoint string `json:"endpoint"`
Version string `json:"version,omitempty"`
}
// =============================================================================
// P2P Task Status
// =============================================================================
// P2PTaskStatus defines task status for P2P operations
type P2PTaskStatus string
const (
// Basic states
P2PStatusIdle P2PTaskStatus = "idle"
P2PStatusPending P2PTaskStatus = "pending"
P2PStatusConfirmed P2PTaskStatus = "confirmed"
P2PStatusExecuting P2PTaskStatus = "executing"
// Information exchange
P2PStatusInfoNeeded P2PTaskStatus = "info_needed"
P2PStatusInfoProvided P2PTaskStatus = "info_provided"
// Human-in-the-Loop
P2PStatusNeedHuman P2PTaskStatus = "need_human"
P2PStatusHumanDeciding P2PTaskStatus = "human_deciding"
P2PStatusHumanApproved P2PTaskStatus = "human_approved"
P2PStatusHumanRejected P2PTaskStatus = "human_rejected"
// Terminal states
P2PStatusCompleted P2PTaskStatus = "completed"
P2PStatusFailed P2PTaskStatus = "failed"
P2PStatusCancelled P2PTaskStatus = "cancelled"
P2PStatusRejected P2PTaskStatus = "rejected"
)
// =============================================================================
// Group Status
// =============================================================================
// GroupStatus defines group state
type GroupStatus string
const (
GroupStatusNone GroupStatus = "none"
GroupStatusCreating GroupStatus = "creating"
GroupStatusActive GroupStatus = "active"
GroupStatusFrozen GroupStatus = "frozen"
GroupStatusDisbanded GroupStatus = "disbanded"
)
// GroupMemberStatus defines member state within a group
type GroupMemberStatus string
const (
GroupMemberActive GroupMemberStatus = "active"
GroupMemberAway GroupMemberStatus = "away"
GroupMemberBusy GroupMemberStatus = "busy"
)
// =============================================================================
// Priority
// =============================================================================
// Priority defines message priority
type Priority string
const (
PriorityHigh Priority = "high"
PriorityNormal Priority = "normal"
PriorityLow Priority = "low"
)
// =============================================================================
// Unified Synapse Message
// =============================================================================
// UnifiedSynapseMessage is the base message format for all Synapse communications
type UnifiedSynapseMessage struct {
// Message identification
ID string `json:"id"`
Version string `json:"version"`
Type string `json:"type"`
// Sender
Sender AgentEndpoint `json:"sender"`
// Receiver (for P2P)
Receiver *AgentEndpoint `json:"receiver,omitempty"`
// Group context (for Group)
GroupID string `json:"group_id,omitempty"`
TargetType string `json:"target_type,omitempty"` // "all", "role", "specific"
TargetIDs []string `json:"target_ids,omitempty"`
// Protocol control
CorrelationID string `json:"correlation_id,omitempty"`
ReplyTo string `json:"reply_to,omitempty"`
ConversationID string `json:"conversation_id,omitempty"`
// Payload
Payload json.RawMessage `json:"payload"`
// Transport control
TTL int `json:"ttl,omitempty"`
Priority Priority `json:"priority,omitempty"`
Broadcast bool `json:"broadcast,omitempty"`
// Security
Signature string `json:"signature,omitempty"`
Encrypt bool `json:"encrypt,omitempty"`
// Timestamps
Timestamp Timestamp `json:"timestamp"`
ExpiresAt Timestamp `json:"expires_at,omitempty"`
}
// Timestamp is an ISO 8601 formatted timestamp
type Timestamp string
func (t Timestamp) Time() (time.Time, error) {
return time.Parse(time.RFC3339, string(t))
}
func NowTimestamp() Timestamp {
return Timestamp(time.Now().UTC().Format(time.RFC3339))
}
// =============================================================================
// P2P Payloads
// =============================================================================
// TaskRequestPayload is the payload for TASK_REQUEST
type TaskRequestPayload struct {
Task struct {
ID string `json:"id"`
Type string `json:"type"`
Priority Priority `json:"priority"`
Deadline *Timestamp `json:"deadline,omitempty"`
Payload map[string]interface{} `json:"payload"`
} `json:"task"`
Context *struct {
SessionID string `json:"session_id,omitempty"`
ParentTaskID string `json:"parent_task_id,omitempty"`
CorrelationID string `json:"correlation_id,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
} `json:"context,omitempty"`
}
// TaskConfirmPayload is the payload for TASK_CONFIRM
type TaskConfirmPayload struct {
TaskID string `json:"task_id"`
Confirmed bool `json:"confirmed"`
EstimatedDuration int `json:"estimated_duration"` // seconds
ExpectedCompletion Timestamp `json:"expected_completion"`
CapabilitiesUsed []string `json:"capabilities_used"`
Constraints *struct {
MaxRetries int `json:"max_retries"`
FallbackEnabled bool `json:"fallback_enabled"`
} `json:"constraints,omitempty"`
}
// TaskRejectPayload is the payload for TASK_REJECT
type TaskRejectPayload struct {
TaskID string `json:"task_id"`
Reason struct {
Code string `json:"code"`
Message string `json:"message"`
Details any `json:"details,omitempty"`
} `json:"reason"`
Alternatives *struct {
SuggestedAgentID string `json:"suggested_agent_id,omitempty"`
RetryAfter int `json:"retry_after,omitempty"`
PartialSupport bool `json:"partial_support,omitempty"`
} `json:"alternatives,omitempty"`
}
// MissingField describes a missing information field
type MissingField struct {
Field string `json:"field"`
Reason string `json:"reason"`
SuggestedValue any `json:"suggested_value,omitempty"`
Alternatives []any `json:"alternatives,omitempty"`
}
// TaskNeedInfoPayload is the payload for TASK_NEED_INFO
type TaskNeedInfoPayload struct {
TaskID string `json:"task_id"`
MissingFields []MissingField `json:"missing_fields"`
Question string `json:"question,omitempty"`
RequiredBy *Timestamp `json:"required_by,omitempty"`
}
// TaskInfoProvidedPayload is the payload for TASK_INFO_PROVIDED
type TaskInfoProvidedPayload struct {
TaskID string `json:"task_id"`
ReplyTo string `json:"reply_to"`
ProvidedInfo map[string]interface{} `json:"provided_info"`
AdditionalContext string `json:"additional_context,omitempty"`
}
// TaskStatusUpdatePayload is the payload for TASK_STATUS_UPDATE
type TaskStatusUpdatePayload struct {
TaskID string `json:"task_id"`
Status string `json:"status"`
Progress int `json:"progress"` // 0-100
Message string `json:"message,omitempty"`
IntermediateResult any `json:"intermediate_result,omitempty"`
}
// TaskCompletePayload is the payload for TASK_COMPLETE
type TaskCompletePayload struct {
TaskID string `json:"task_id"`
Result struct {
OutputType string `json:"output_type"`
Output any `json:"output"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
} `json:"result"`
Summary string `json:"summary,omitempty"`
}
// TaskFailedPayload is the payload for TASK_FAILED
type TaskFailedPayload struct {
TaskID string `json:"task_id"`
Error struct {
Code string `json:"code"`
Message string `json:"message"`
Recoverable bool `json:"recoverable"`
Details any `json:"details,omitempty"`
} `json:"error"`
PartialResult any `json:"partial_result,omitempty"`
}
// TaskCancelPayload is the payload for TASK_CANCEL
type TaskCancelPayload struct {
TaskID string `json:"task_id"`
Reason string `json:"reason"`
CancelledBy string `json:"cancelled_by,omitempty"`
}
// TaskNeedHumanPayload is the payload for TASK_NEED_HUMAN
type TaskNeedHumanPayload struct {
TaskID string `json:"task_id"`
Reason struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"reason"`
Context struct {
TaskSummary string `json:"task_summary"`
CurrentProgress int `json:"current_progress"`
BlockedReason string `json:"blocked_reason"`
RelevantHistory []any `json:"relevant_history,omitempty"`
} `json:"context"`
Options []struct {
Field string `json:"field"`
Value any `json:"value"`
ProsCons string `json:"pros_cons,omitempty"`
Confidence int `json:"confidence,omitempty"`
} `json:"options,omitempty"`
SuggestedValue any `json:"suggested_value,omitempty"`
EscalationLevel int `json:"escalation_level"`
Deadline *Timestamp `json:"deadline,omitempty"`
}
// HumanDecisionPayload is the payload for HUMAN_DECISION
type HumanDecisionPayload struct {
TaskID string `json:"task_id"`
DecisionID string `json:"decision_id"`
ReplyTo string `json:"reply_to"`
DecidedBy struct {
UserID string `json:"user_id"`
UserName string `json:"user_name"`
Role string `json:"role"`
} `json:"decided_by"`
Decision struct {
Action string `json:"action"` // approve, reject, modify, select
ApprovedFields map[string]interface{} `json:"approved_fields,omitempty"`
ModifiedFields map[string]interface{} `json:"modified_fields,omitempty"`
SelectedOption any `json:"selected_option,omitempty"`
RejectionReason string `json:"rejection_reason,omitempty"`
} `json:"decision"`
Notes string `json:"notes,omitempty"`
Timestamp Timestamp `json:"timestamp"`
}
// =============================================================================
// Group Payloads
// =============================================================================
// GroupTopology defines group topology types
type GroupTopology string
const (
GroupTopologyStar GroupTopology = "star"
GroupTopologyMesh GroupTopology = "mesh"
GroupTopologyChain GroupTopology = "chain"
GroupTopologyHierarchical GroupTopology = "hierarchical"
GroupTopologyFullMesh GroupTopology = "fullmesh"
)
// GroupRole defines a group role
type GroupRole struct {
ID string `json:"id"`
Name string `json:"name"`
Permissions []string `json:"permissions"`
MaxTasks int `json:"max_tasks"`
}
// GroupMember represents a member in a group
type GroupMember struct {
AgentID string `json:"agent_id"`
AgentName string `json:"agent_name"`
Role string `json:"role"`
JoinedAt Timestamp `json:"joined_at"`
Status GroupMemberStatus `json:"status"`
Capabilities []string `json:"capabilities"`
LoadFactor float64 `json:"load_factor"` // 0-1, for task assignment
}
// AgentGroup represents a group of agents
type AgentGroup struct {
ID string `json:"id"`
Name string `json:"name"`
Topology GroupTopology `json:"topology"`
CreatedBy string `json:"created_by"`
CreatedAt Timestamp `json:"created_at"`
Config GroupConfig `json:"config"`
Members []GroupMember `json:"members"`
Roles []GroupRole `json:"roles"`
Status GroupStatus `json:"status"`
}
// GroupConfig defines group configuration
type GroupConfig struct {
AllowSelfLeave bool `json:"allow_self_leave"`
RequiresApproval bool `json:"requires_approval"`
ConsensusRequired bool `json:"consensus_required"`
ConsensusThreshold float64 `json:"consensus_threshold"` // 0-1
BroadcastMode string `json:"broadcast_mode"` // "all" or "smart"
}
// GroupCreatePayload is the payload for GROUP_CREATE
type GroupCreatePayload struct {
Group struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Topology GroupTopology `json:"topology"`
Config GroupConfig `json:"config"`
InitialMembers []AgentEndpoint `json:"initial_members,omitempty"`
Roles []GroupRole `json:"roles,omitempty"`
} `json:"group"`
}
// GroupInvitePayload is the payload for GROUP_INVITE
type GroupInvitePayload struct {
GroupID string `json:"group_id"`
Invitee AgentEndpoint `json:"invitee"`
Inviter AgentEndpoint `json:"inviter"`
Message string `json:"message,omitempty"`
RequiresApproval bool `json:"requires_approval"`
}
// Subtask describes a subtask in task splitting
type Subtask struct {
ID string `json:"id"`
Description string `json:"description"`
AssignedTo string `json:"assigned_to"`
Dependencies []string `json:"dependencies"`
EstimatedDuration int `json:"estimated_duration"` // seconds
Priority int `json:"priority"`
}
// MergeStrategy defines how subtask results are merged
type MergeStrategy string
const (
MergeAllSuccess MergeStrategy = "all_success"
MergeMajority MergeStrategy = "majority"
MergeAnyComplete MergeStrategy = "any_complete"
MergePriority MergeStrategy = "priority"
)
// GroupTaskSplitPayload is the payload for GROUP_TASK_SPLIT
type GroupTaskSplitPayload struct {
OriginalTask struct {
ID string `json:"id"`
Type string `json:"type"`
Requirements map[string]interface{} `json:"requirements"`
Deadline *Timestamp `json:"deadline,omitempty"`
} `json:"original_task"`
Subtasks []Subtask `json:"subtasks"`
MergeStrategy MergeStrategy `json:"merge_strategy"`
}
// SubtaskResult describes a single subtask result
type SubtaskResult struct {
SubtaskID string `json:"subtask_id"`
AssignedTo string `json:"assigned_to"`
Status string `json:"status"` // success, failed, partial
Result any `json:"result,omitempty"`
Error string `json:"error,omitempty"`
CompletedAt *Timestamp `json:"completed_at,omitempty"`
}
// GroupTaskMergePayload is the payload for GROUP_TASK_MERGE
type GroupTaskMergePayload struct {
OriginalTaskID string `json:"original_task_id"`
SubtaskResults []SubtaskResult `json:"subtask_results"`
MergeStrategyUsed string `json:"merge_strategy_used"`
MergedResult any `json:"merged_result"`
ConsensusAchieved bool `json:"consensus_achieved"`
}
// GroupVoteOption describes a vote option
type GroupVoteOption struct {
VoterID string `json:"voter_id"`
Option string `json:"option"`
Reason string `json:"reason,omitempty"`
Timestamp Timestamp `json:"timestamp"`
}
// GroupVotePayload is the payload for GROUP_VOTE
type GroupVotePayload struct {
VoteID string `json:"vote_id"`
Question string `json:"question"`
Options []string `json:"options"`
Voters []string `json:"voters"`
Deadline Timestamp `json:"deadline"`
Threshold float64 `json:"threshold"` // 0-1
Status string `json:"status"` // open, closed
Votes []GroupVoteOption `json:"votes,omitempty"`
}
// GroupConsensusEscalatePayload is the payload for GROUP_CONSENSUS_ESCALATE
type GroupConsensusEscalatePayload struct {
VoteID string `json:"vote_id"`
EscalationID string `json:"escalation_id"`
Question string `json:"question"`
Votes []struct {
VoterID string `json:"voter_id"`
VoterRole string `json:"voter_role"`
Option string `json:"option"`
Reason string `json:"reason,omitempty"`
} `json:"votes"`
ConsensusStatus struct {
RequiredThreshold float64 `json:"required_threshold"`
ActualThreshold float64 `json:"actual_threshold"`
LeadingOption string `json:"leading_option,omitempty"`
OptionsTied bool `json:"options_tied"`
} `json:"consensus_status"`
EscalateReason struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"escalate_reason"`
EscalationLevel int `json:"escalation_level"`
DecisionOptions []struct {
Option string `json:"option"`
SupportCount int `json:"support_count"`
SupportAgents []string `json:"support_agents"`
} `json:"decision_options"`
Deadline Timestamp `json:"deadline"`
Context struct {
TaskID string `json:"task_id"`
OriginalTaskRequirements map[string]interface{} `json:"original_task_requirements"`
AgentRecommendations []struct {
AgentID string `json:"agent_id"`
SuggestedOption string `json:"suggested_option"`
} `json:"agent_recommendations"`
} `json:"context"`
}
// GroupSubtaskEscalatePayload is the payload for GROUP_SUBTASK_ESCALATE
type GroupSubtaskEscalatePayload struct {
OriginalTask struct {
ID string `json:"id"`
Type string `json:"type"`
Description string `json:"description"`
} `json:"original_task"`
Subtask struct {
ID string `json:"id"`
Description string `json:"description"`
AssignedTo string `json:"assigned_to"`
BlockedAt string `json:"blocked_at"`
BlockedReason struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"blocked_reason"`
AttemptedSolutions []string `json:"attempted_solutions"`
} `json:"subtask"`
RequiredDecision struct {
FieldsNeeded []struct {
Field string `json:"field"`
CurrentValue any `json:"current_value,omitempty"`
SuggestedValue any `json:"suggested_value,omitempty"`
Alternatives []any `json:"alternatives,omitempty"`
ValidationRules string `json:"validation_rules,omitempty"`
} `json:"fields_needed"`
DecisionType string `json:"decision_type"` // approve, select, provide_info
AffectsOtherSubtasks bool `json:"affects_other_subtasks"`
RollbackPossible bool `json:"rollback_possible"`
} `json:"required_decision"`
EscalationLevel int `json:"escalation_level"`
Deadline *Timestamp `json:"deadline,omitempty"`
}