Skip to content

Commit d803fb3

Browse files
gcmsgclaude
andcommitted
feat: add Agent Identity & Trust Platform (Phase 6)
Transform PeerClaw from protocol gateway to identity & trust platform: - EWMA reputation engine (internal/reputation/) with 10 event types - Endpoint verification via challenge-response (internal/verification/) - Public API: /api/v1/directory for unauthenticated agent browsing - Reputation integration in register/heartbeat/bridge handlers - Heartbeat timeout checker goroutine (60s interval, 5m timeout) - Frontend: rename web/dashboard to web/app, add public pages (Landing, Directory, Profile), admin routes moved to /admin/* - New components: ReputationMeter, VerifiedBadge, ReputationChart - PublicEndpoint opt-in field for endpoint URL visibility - Updated docs to reflect identity & trust positioning Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d957821 commit d803fb3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1999
-77
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ GO := go
66
GOFLAGS := -v
77

88
dashboard:
9-
cd web/dashboard && npm install && npm run build
9+
cd web/app && npm install && npm run build
1010

1111
build: dashboard
1212
CGO_ENABLED=1 $(GO) build $(GOFLAGS) -o $(BUILD_DIR)/$(BINARY) ./cmd/peerclawd

README.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
# peerclaw-server
44

5-
**AI Agent Gateway — register, discover, connect, and bridge across protocols.**
5+
**AI Agent Identity & Trust Platform — verifiable identity, reputation scoring, endpoint verification, and cross-protocol bridging.**
66

7-
peerclaw-server is the coordination hub for AI agents. Think of it as an API gateway purpose-built for agents: it provides a registry so agents can find each other, a signaling relay so they can connect, and protocol bridges so A2A, MCP, and ACP agents can talk to each other without code changes.
7+
peerclaw-server is the trust infrastructure for AI agents. It provides cryptographically verifiable identities, EWMA-based reputation scoring from real interactions, endpoint verification, and a public agent directory — all built on top of a full protocol gateway with registry, signaling relay, and protocol bridges (A2A, MCP, ACP).
88

99
Start it with one command. No external dependencies required.
1010

@@ -17,6 +17,9 @@ Start it with one command. No external dependencies required.
1717

1818
| Capability | What it means for you |
1919
|-----------|----------------------|
20+
| **Reputation Engine** | EWMA scoring from real events (registration, heartbeat, bridge, verification). Trust that's earned, not claimed. |
21+
| **Endpoint Verification** | Challenge-response proof that an agent controls its URL. Ed25519 signed. |
22+
| **Public Directory** | Browse agents by reputation, capability, verification status. No auth required. |
2023
| **Agent Registry** | Agents register their capabilities. Anyone can discover them. Like DNS for agents. |
2124
| **Protocol Bridging** | An MCP agent can call an A2A agent. The gateway translates automatically. |
2225
| **Signaling Relay** | Agents establish direct P2P connections via WebSocket signaling. |
@@ -96,6 +99,8 @@ curl http://localhost:8080/api/v1/health
9699
| **Bridge** | `internal/bridge/` | Protocol adapters (A2A, MCP, ACP) + negotiator |
97100
| **Router** | `internal/router/` | Capability-based message routing |
98101
| **Federation** | `internal/federation/` | Multi-server signal relay, DNS SRV discovery |
102+
| **Reputation** | `internal/reputation/` | EWMA reputation engine, event recording, score computation |
103+
| **Verification** | `internal/verification/` | Challenge-response endpoint verification (SSRF-safe) |
99104
| **Security** | `internal/security/` | URL validation (SSRF protection), safe HTTP client |
100105
| **Config** | `internal/config/` | YAML config with `${ENV_VAR}` secret substitution |
101106
| **Observability** | `internal/observability/` | OpenTelemetry provider setup |
@@ -192,6 +197,20 @@ Applies to: `redis.password`, `database.dsn`, `signaling.turn.credential`, `fede
192197
| `DELETE` | `/api/v1/agents/{id}` | Deregister an agent (owner only) |
193198
| `POST` | `/api/v1/agents/{id}/heartbeat` | Report heartbeat (owner only) |
194199

200+
### Public Directory (no auth required)
201+
202+
| Method | Path | Description |
203+
|--------|------|-------------|
204+
| `GET` | `/api/v1/directory` | Browse agent directory (filter: `capability`, `protocol`, `status`, `verified`, `min_score`, `search`; sort: `reputation`, `name`, `registered_at`) |
205+
| `GET` | `/api/v1/directory/{id}` | Public agent profile (sanitized, no auth params) |
206+
| `GET` | `/api/v1/directory/{id}/reputation` | Reputation event history |
207+
208+
### Verification
209+
210+
| Method | Path | Description |
211+
|--------|------|-------------|
212+
| `POST` | `/api/v1/agents/{id}/verify` | Initiate endpoint verification (owner only) |
213+
195214
### Discovery & Routing
196215

197216
| Method | Path | Description |
@@ -214,7 +233,7 @@ When `auth.required: true`, all non-public endpoints require one of:
214233
- **Bearer token**: `Authorization: Bearer <api-key>`
215234
- **Ed25519 signature**: `X-PeerClaw-PublicKey` + `X-PeerClaw-Signature` headers
216235

217-
Public endpoints (no auth): `GET /api/v1/health`, `GET /.well-known/agent.json`, `GET /acp/ping`
236+
Public endpoints (no auth): `GET /api/v1/health`, `GET /api/v1/directory`, `GET /api/v1/directory/{id}`, `GET /api/v1/directory/{id}/reputation`, `GET /.well-known/agent.json`, `GET /acp/ping`
218237

219238
## Protocol Gateway Endpoints
220239

README_zh.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
# peerclaw-server
44

5-
**AI Agent 网关注册、发现、连接、跨协议通信**
5+
**AI Agent 身份与信任平台可验证身份、声誉评分、端点验证、跨协议桥接**
66

7-
peerclaw-server 是 AI Agent 的协调中心。你可以把它理解为专为 Agent 设计的 API 网关:提供注册中心让 Agent 互相发现,提供信令中转让它们建立连接,提供协议桥接让 A2A、MCP、ACP 的 Agent 无需改代码就能互相通信
7+
peerclaw-server 是 AI Agent 的信任基础设施。它提供密码学可验证身份、基于真实交互的 EWMA 声誉评分、端点验证、以及公开的 Agent 目录 — 一切都构建在完整的协议网关之上,包括注册中心、信令中转和协议桥接(A2A、MCP、ACP
88

99
一行命令启动,零外部依赖。
1010

@@ -17,6 +17,9 @@ peerclaw-server 是 AI Agent 的协调中心。你可以把它理解为专为 Ag
1717

1818
| 能力 | 对你意味着什么 |
1919
|------|--------------|
20+
| **声誉引擎** | 基于真实事件(注册、心跳、桥接、验证)的 EWMA 评分。信任是赢得的,不是声称的。 |
21+
| **端点验证** | Challenge-Response 证明 Agent 控制其 URL,Ed25519 签名。 |
22+
| **公开目录** | 按声誉、能力、验证状态浏览 Agent,无需认证。 |
2023
| **Agent 注册中心** | Agent 注册自己的能力,任何人都能发现它。像 Agent 的 DNS。 |
2124
| **协议桥接** | MCP Agent 可以调用 A2A Agent,网关自动翻译。 |
2225
| **信令中转** | Agent 通过 WebSocket 信令建立 P2P 直连。 |
@@ -96,6 +99,8 @@ curl http://localhost:8080/api/v1/health
9699
| **桥接** | `internal/bridge/` | 协议适配器(A2A、MCP、ACP)+ 协商器 |
97100
| **路由** | `internal/router/` | 基于能力的消息路由 |
98101
| **联邦** | `internal/federation/` | 多服务器信令中转、DNS SRV 发现 |
102+
| **声誉** | `internal/reputation/` | EWMA 声誉引擎、事件记录、分数计算 |
103+
| **验证** | `internal/verification/` | Challenge-Response 端点验证(SSRF 安全) |
99104
| **安全** | `internal/security/` | URL 校验(SSRF 防护)、安全 HTTP 客户端 |
100105
| **配置** | `internal/config/` | YAML 配置 + `${ENV_VAR}` 密钥替换 |
101106
| **可观测** | `internal/observability/` | OpenTelemetry Provider 初始化 |
@@ -192,6 +197,20 @@ redis:
192197
| `DELETE` | `/api/v1/agents/{id}` | 注销 Agent(仅所有者) |
193198
| `POST` | `/api/v1/agents/{id}/heartbeat` | 上报心跳(仅所有者) |
194199

200+
### 公开目录(免认证)
201+
202+
| 方法 | 路径 | 说明 |
203+
|------|------|------|
204+
| `GET` | `/api/v1/directory` | 浏览 Agent 目录(过滤:`capability`、`protocol`、`status`、`verified`、`min_score`、`search`;排序:`reputation`、`name`、`registered_at`) |
205+
| `GET` | `/api/v1/directory/{id}` | Agent 公开档案(脱敏,不含认证参数) |
206+
| `GET` | `/api/v1/directory/{id}/reputation` | 声誉事件历史 |
207+
208+
### 端点验证
209+
210+
| 方法 | 路径 | 说明 |
211+
|------|------|------|
212+
| `POST` | `/api/v1/agents/{id}/verify` | 发起端点验证(仅所有者) |
213+
195214
### 发现与路由
196215

197216
| 方法 | 路径 | 说明 |
@@ -214,7 +233,7 @@ redis:
214233
- **Bearer Token**:`Authorization: Bearer <api-key>`
215234
- **Ed25519 签名**:`X-PeerClaw-PublicKey` + `X-PeerClaw-Signature` 请求头
216235

217-
公开端点(免认证):`GET /api/v1/health`、`GET /.well-known/agent.json`、`GET /acp/ping`
236+
公开端点(免认证):`GET /api/v1/health`、`GET /api/v1/directory`、`GET /api/v1/directory/{id}`、`GET /api/v1/directory/{id}/reputation`、`GET /.well-known/agent.json`、`GET /acp/ping`
218237

219238
## 协议网关端点
220239

cmd/peerclawd/main.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"syscall"
1111
"time"
1212

13+
"database/sql"
14+
1315
"github.com/peerclaw/peerclaw-server/internal/audit"
1416
"github.com/peerclaw/peerclaw-server/internal/bridge"
1517
"github.com/peerclaw/peerclaw-server/internal/bridge/a2a"
@@ -19,9 +21,11 @@ import (
1921
"github.com/peerclaw/peerclaw-server/internal/federation"
2022
"github.com/peerclaw/peerclaw-server/internal/observability"
2123
"github.com/peerclaw/peerclaw-server/internal/registry"
24+
"github.com/peerclaw/peerclaw-server/internal/reputation"
2225
"github.com/peerclaw/peerclaw-server/internal/router"
2326
"github.com/peerclaw/peerclaw-server/internal/server"
2427
"github.com/peerclaw/peerclaw-server/internal/signaling"
28+
"github.com/peerclaw/peerclaw-server/internal/verification"
2529
goredis "github.com/redis/go-redis/v9"
2630
)
2731

@@ -77,6 +81,33 @@ func main() {
7781
}
7882
defer store.Close()
7983

84+
// Extract the underlying *sql.DB for shared use by reputation and verification modules.
85+
sqlDB, _ := store.GetDB().(*sql.DB)
86+
87+
// Initialize reputation engine.
88+
var repEngine *reputation.Engine
89+
if sqlDB != nil {
90+
repStore := reputation.NewSQLiteStore(sqlDB)
91+
if err := repStore.Migrate(context.Background()); err != nil {
92+
logger.Error("failed to migrate reputation tables", "error", err)
93+
os.Exit(1)
94+
}
95+
repEngine = reputation.NewEngine(repStore, logger)
96+
logger.Info("reputation engine initialized")
97+
}
98+
99+
// Initialize verification challenger.
100+
var verifyChallenger *verification.Challenger
101+
if sqlDB != nil {
102+
verifyStore := verification.NewSQLiteStore(sqlDB)
103+
if err := verifyStore.Migrate(context.Background()); err != nil {
104+
logger.Error("failed to migrate verification tables", "error", err)
105+
os.Exit(1)
106+
}
107+
verifyChallenger = verification.NewChallenger(verifyStore, logger)
108+
logger.Info("endpoint verification initialized")
109+
}
110+
80111
// Initialize services.
81112
regService := registry.NewService(store, logger)
82113
routeTable := router.NewTable()
@@ -169,6 +200,12 @@ func main() {
169200
httpServer.SetStore(store)
170201
httpServer.SetAudit(auditLogger)
171202
httpServer.SetMetrics(otelMetrics)
203+
if repEngine != nil {
204+
httpServer.SetReputation(repEngine)
205+
}
206+
if verifyChallenger != nil {
207+
httpServer.SetVerificationChallenger(verifyChallenger)
208+
}
172209
if fedService != nil {
173210
httpServer.SetFederation(fedService)
174211
}
@@ -202,6 +239,40 @@ func main() {
202239
sigHub.SetBroker(signaling.NewLocalBroker(sigHub))
203240
}
204241
}
242+
// Start heartbeat timeout checker goroutine.
243+
if repEngine != nil && sqlDB != nil {
244+
go func() {
245+
ticker := time.NewTicker(60 * time.Second)
246+
defer ticker.Stop()
247+
for {
248+
select {
249+
case <-ctx.Done():
250+
return
251+
case <-ticker.C:
252+
// Find agents whose last heartbeat is older than 5 minutes and status is online.
253+
rows, err := sqlDB.QueryContext(ctx,
254+
`SELECT id FROM agents WHERE status = 'online' AND last_heartbeat < ?`,
255+
time.Now().UTC().Add(-5*time.Minute).Format(time.RFC3339),
256+
)
257+
if err != nil {
258+
logger.Warn("heartbeat check query failed", "error", err)
259+
continue
260+
}
261+
for rows.Next() {
262+
var agentID string
263+
if err := rows.Scan(&agentID); err != nil {
264+
continue
265+
}
266+
repEngine.RecordEvent(ctx, agentID, "heartbeat_miss", "")
267+
logger.Debug("heartbeat miss recorded", "agent_id", agentID)
268+
}
269+
rows.Close()
270+
}
271+
}
272+
}()
273+
logger.Info("heartbeat timeout checker started", "interval", "60s", "timeout", "5m")
274+
}
275+
205276
grpcServer := server.NewGRPCServer(cfg.Server.GRPCAddr, logger)
206277

207278
var wg sync.WaitGroup

internal/registry/postgres.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,10 @@ func (s *PostgresStore) FindByCapabilities(ctx context.Context, capabilities []s
300300
return agents, rows.Err()
301301
}
302302

303+
func (s *PostgresStore) GetDB() interface{} {
304+
return s.db
305+
}
306+
303307
func (s *PostgresStore) Close() error {
304308
return s.db.Close()
305309
}

internal/registry/sqlite.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,17 @@ func (s *SQLiteStore) List(ctx context.Context, filter ListFilter) (*ListResult,
153153
conditions = append(conditions, "status = ?")
154154
args = append(args, string(filter.Status))
155155
}
156+
if filter.Verified {
157+
conditions = append(conditions, "verified = 1")
158+
}
159+
if filter.MinScore > 0 {
160+
conditions = append(conditions, "COALESCE(reputation_score, 0.5) >= ?")
161+
args = append(args, filter.MinScore)
162+
}
163+
if filter.Search != "" {
164+
conditions = append(conditions, "(name LIKE ? OR description LIKE ?)")
165+
args = append(args, "%"+filter.Search+"%", "%"+filter.Search+"%")
166+
}
156167

157168
where := ""
158169
if len(conditions) > 0 {
@@ -176,14 +187,24 @@ func (s *SQLiteStore) List(ctx context.Context, filter ListFilter) (*ListResult,
176187
fmt.Sscanf(filter.PageToken, "%d", &offset)
177188
}
178189

190+
orderBy := "registered_at DESC"
191+
switch filter.SortBy {
192+
case "reputation":
193+
orderBy = "COALESCE(reputation_score, 0.5) DESC"
194+
case "name":
195+
orderBy = "name ASC"
196+
case "registered_at":
197+
orderBy = "registered_at DESC"
198+
}
199+
179200
query := fmt.Sprintf(`SELECT
180201
id, name, description, version, COALESCE(public_key, ''), capabilities,
181202
endpoint_url, endpoint_host, endpoint_port, endpoint_transport,
182203
protocols, auth_type, auth_params, metadata,
183204
peerclaw_nat, peerclaw_relay, peerclaw_priority, peerclaw_tags,
184205
COALESCE(skills, '[]'), COALESCE(tools, '[]'),
185206
status, registered_at, last_heartbeat
186-
FROM agents %s ORDER BY registered_at DESC LIMIT ? OFFSET ?`, where)
207+
FROM agents %s ORDER BY %s LIMIT ? OFFSET ?`, where, orderBy)
187208

188209
args = append(args, pageSize, offset)
189210
rows, err := s.db.QueryContext(ctx, query, args...)
@@ -274,6 +295,10 @@ func (s *SQLiteStore) FindByCapabilities(ctx context.Context, capabilities []str
274295
return agents, rows.Err()
275296
}
276297

298+
func (s *SQLiteStore) GetDB() interface{} {
299+
return s.db
300+
}
301+
277302
func (s *SQLiteStore) Close() error {
278303
return s.db.Close()
279304
}

internal/registry/store.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ type ListFilter struct {
1313
Status agentcard.AgentStatus
1414
PageSize int
1515
PageToken string
16+
// Public directory filters.
17+
Verified bool
18+
MinScore float64
19+
Search string
20+
SortBy string // "reputation", "name", "registered_at"
1621
}
1722

1823
// ListResult holds a page of agents and pagination info.
@@ -42,6 +47,9 @@ type Store interface {
4247
// FindByCapabilities returns agents that match any of the given capabilities.
4348
FindByCapabilities(ctx context.Context, capabilities []string, protocol string, maxResults int) ([]*agentcard.Card, error)
4449

50+
// GetDB returns the underlying *sql.DB for shared use by other modules.
51+
GetDB() interface{}
52+
4553
// Close releases resources.
4654
Close() error
4755
}

0 commit comments

Comments
 (0)