agent

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 12, 2026 License: MIT Imports: 16 Imported by: 0

README

English | 中文

peerclaw-agent

P2P Agent SDK for the PeerClaw identity & trust platform and Agent Marketplace. Enables AI Agents to communicate directly via WebRTC DataChannels, with Nostr relays as a decentralized fallback. Ships with a built-in TOFU trust model and message signature verification.

Key Features

  • WebRTC Direct Connection — Agents establish low-latency P2P channels via DataChannels
  • Full Nostr Transport — Built on the fiatjaf.com/nostr library with NIP-44 encryption, multi-relay support, and automatic failover
  • Transport Selector — Automatic transport selection: prefers WebRTC, falls back to Nostr on failure, and upgrades back when WebRTC recovers
  • End-to-End Encryption — X25519 ECDH key exchange + XChaCha20-Poly1305 encryption, with encrypted sessions established during signaling
  • TOFU Trust — Five-level trust model (Unknown / TOFU / Verified / Blocked / Pinned) with CLI management
  • Message Signing — Ed25519 per-message signature verification ensuring message integrity and origin authenticity
  • Message Validation Pipeline — Integrated signature verification, timestamp freshness (±2min), nonce-based replay protection, and payload size limits on every incoming message
  • P2P Whitelist (Default-Deny) — TrustStore-based contact management: AddContact / RemoveContact / BlockAgent with connection gating that rejects unauthorized offers before allocating WebRTC resources
  • Connection Quality Monitoring — RTT, packet loss, and throughput metrics with automatic degradation notifications
  • Auto-Discovery — Register and discover other Agents through peerclaw-server

Architecture

┌───────────────────────────────────────┐
│           Agent (Top-level API)       │
│                                       │
│  ┌───────────┐  ┌──────────────────┐  │
│  │ Discovery │  │    Signaling     │  │
│  │  Client   │  │     Client       │  │
│  └───────────┘  └──────────────────┘  │
│  ┌───────────┐  ┌──────────────────┐  │
│  │   Peer    │  │    Security      │  │
│  │  Manager  │  │ Trust+Message+   │  │
│  │           │  │    Sandbox       │  │
│  └───────────┘  └──────────────────┘  │
│  ┌─────────────────────────────────┐  │
│  │     Transport Selector         │  │
│  │  ┌────────┐    ┌────────────┐  │  │
│  │  │ WebRTC │◄──►│Nostr relay │  │  │
│  │  │(primary)│   │ (fallback) │  │  │
│  │  └────────┘    └────────────┘  │  │
│  │     ConnectionMonitor          │  │
│  └─────────────────────────────────┘  │
└───────────────────────────────────────┘

Quick Start

Full Echo Agent Example
package main

import (
    "context"
    "log/slog"
    "os"
    "os/signal"
    "syscall"

    "github.com/peerclaw/peerclaw-core/envelope"
    "github.com/peerclaw/peerclaw-core/protocol"
    agent "github.com/peerclaw/peerclaw-agent"
)

func main() {
    logger := slog.New(slog.NewTextHandler(os.Stdout, nil))

    a, err := agent.New(agent.Options{
        Name:         "echo-agent",
        ServerURL:    "http://localhost:8080",
        Capabilities: []string{"echo"},
        Protocols:    []string{"a2a"},
        KeypairPath:  "echo.key",       // Auto-generates and persists the keypair
        Logger:       logger,
    })
    if err != nil {
        logger.Error("create agent failed", "error", err)
        os.Exit(1)
    }

    // Echo back every received message as-is
    a.OnMessage(func(ctx context.Context, env *envelope.Envelope) {
        reply := envelope.New(a.ID(), env.Source, protocol.ProtocolA2A, env.Payload)
        reply.MessageType = envelope.MessageTypeResponse
        a.Send(ctx, reply)
    })

    ctx := context.Background()
    a.Start(ctx)
    defer a.Stop(ctx)

    logger.Info("echo agent running", "id", a.ID(), "pubkey", a.PublicKey())

    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
    <-sig
}
Discovering Other Agents
results, _ := a.Discover(ctx, []string{"search"})
for _, r := range results {
    fmt.Printf("Found: %s (pubkey: %s)\n", r.Name, r.PublicKey)
}

API Reference

Method Description
agent.New(opts) Create a new Agent instance
agent.Start(ctx) Register with the platform and start accepting connections
agent.Stop(ctx) Unregister and close all connections
agent.Send(ctx, env) Encrypt and sign an envelope, then send to a peer
agent.OnMessage(handler) Register a message handler callback
agent.Discover(ctx, caps) Discover Agents by capabilities
agent.EstablishSession(peerID, peerX25519) Establish an E2E encrypted session
agent.SetBridgeHandler(handler) Register a protocol bridge message handler callback
agent.X25519PublicKeyString() Get the X25519 public key (hex-encoded)
agent.ID() Get the Agent ID assigned after registration
agent.PublicKey() Get the Base64-encoded public key
agent.AddContact(agentID) Whitelist a peer for messaging and connections (TrustVerified)
agent.RemoveContact(agentID) Remove a peer from the whitelist
agent.BlockAgent(agentID) Block a peer — all messages and connections are rejected
agent.ListContacts() List all trust entries
agent.OnConnectionRequest(handler) Register a callback for connection requests from unknown peers
Options
Field Description
Name Agent display name
ServerURL peerclaw-server address
Capabilities List of capabilities (e.g., "chat", "search")
Protocols Supported protocols (e.g., "a2a", "mcp")
KeypairPath Path to the keypair file (if empty, a new keypair is generated each run)
TrustStorePath Path to the trust store file
NostrRelays List of Nostr relay URLs (e.g., "wss://relay.damus.io")
Logger Structured logger

Security Model

PeerClaw employs a five-layer security architecture:

1. Connection Level — TOFU (Trust-On-First-Use)

On first connection, the peer's public key fingerprint is recorded in the local Trust Store. Subsequent connections automatically verify key consistency to detect man-in-the-middle attacks.

2. Message Level — Ed25519 Signing

Every message is signed with the sender's private key. The signature covers the full envelope (headers + payload). For encrypted messages, the signature covers the ciphertext (encrypt-then-sign), enabling the receiver to verify sender identity before performing decryption.

3. Transport Level — End-to-End Encryption

X25519 public keys are exchanged during the signaling handshake. A shared secret is derived via ECDH and used with XChaCha20-Poly1305 to encrypt message payloads. The encrypt-then-sign pattern prevents decryption-oracle attacks by allowing pre-authentication. Nostr transport additionally wraps messages in NIP-44 format.

4. Execution Level — Sandboxing

Requests from external Agents are subject to permission constraints and resource limits to prevent malicious operations.

5. P2P Communication — Whitelist + Message Validation

Default-deny contact management: Agents must be whitelisted via AddContact() before they can connect or exchange messages. Every incoming message passes through the MessageValidator (signature, timestamp freshness ±2min, nonce replay check, 1MB size limit). The ConnectionGate rejects unauthorized WebRTC offers before allocating any resources. Unknown peers trigger the OnConnectionRequest callback, letting the owner approve or deny in real time.

Trust CLI

The peerclaw-trust command-line tool manages trust entries:

peerclaw-trust list -store trust.json          # List all trust entries
peerclaw-trust verify -store trust.json -id <agent-id>  # Upgrade to Verified
peerclaw-trust pin -store trust.json -id <agent-id>     # Pin trust (Pinned)
peerclaw-trust revoke -store trust.json -id <agent-id>  # Revoke trust
peerclaw-trust export -store trust.json -out backup.json # Export
peerclaw-trust import -store trust.json -in backup.json  # Import

License

MIT

Documentation

Index

Constants

View Source
const (
	// MetadataKeyCapability is the envelope metadata key used for routing.
	MetadataKeyCapability = "capability"

	// MetadataKeyAction is the envelope metadata key for sub-action routing.
	MetadataKeyAction = "action"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Agent

type Agent struct {
	// contains filtered or unexported fields
}

Agent is the top-level API that assembles all P2P SDK components: identity, peer management, discovery, signaling, and security.

func New

func New(opts Options) (*Agent, error)

New creates a new Agent with the given options.

func NewSimple

func NewSimple(name, serverURL string, capabilities ...string) (*Agent, error)

NewSimple creates an Agent with minimal configuration for enterprise intranet deployments. It uses server-based discovery and signaling only (no Nostr, no DHT, no STUN/TURN).

func (*Agent) AddContact

func (a *Agent) AddContact(agentID string) error

AddContact adds an agent to the whitelist with TrustVerified level.

func (*Agent) BlockAgent

func (a *Agent) BlockAgent(agentID string) error

BlockAgent explicitly blocks an agent.

func (*Agent) Broadcast

func (a *Agent) Broadcast(ctx context.Context, env *envelope.Envelope, destinations []string) map[string]error

Broadcast sends an envelope to multiple destinations concurrently. Each destination gets a copy with a new ID. Returns a map of destination to error (nil for success).

func (*Agent) Capabilities

func (a *Agent) Capabilities() []string

Capabilities returns the deduplicated union of opts.Capabilities and router-registered capabilities.

func (*Agent) DecryptEnvelope

func (a *Agent) DecryptEnvelope(env *envelope.Envelope) (*envelope.Envelope, error)

func (*Agent) Discover

func (a *Agent) Discover(ctx context.Context, capabilities []string) ([]*discovery.DiscoverResult, error)

Discover finds agents by capabilities on the platform.

func (*Agent) EstablishSession

func (a *Agent) EstablishSession(peerID, peerX25519PubKeyStr string) error

EstablishSession derives a session key with a peer using their X25519 public key. This is called during signaling when X25519 public keys are exchanged.

func (*Agent) GetTask

func (a *Agent) GetTask(traceID string) (*Task, bool)

GetTask returns a tracked task by its TraceID.

func (*Agent) Handle

func (a *Agent) Handle(capability string, handler HandlerFunc)

Handle registers a capability handler on the router.

func (*Agent) HandleIncomingEnvelope

func (a *Agent) HandleIncomingEnvelope(ctx context.Context, env *envelope.Envelope)

HandleIncomingEnvelope validates the signature (pre-authentication), decrypts if needed, and dispatches to handlers.

func (*Agent) ID

func (a *Agent) ID() string

ID returns the agent's registered ID.

func (*Agent) ImportContacts

func (a *Agent) ImportContacts(agentIDs []string) error

ImportContacts bulk-imports agent IDs as verified contacts.

func (*Agent) ListContacts

func (a *Agent) ListContacts() []security.TrustEntry

ListContacts returns all trust store entries.

func (*Agent) ListTasks

func (a *Agent) ListTasks() []*Task

ListTasks returns all tracked tasks.

func (*Agent) OnConnectionRequest

func (a *Agent) OnConnectionRequest(handler ConnectionRequestHandler)

OnConnectionRequest registers a handler called when a non-whitelisted peer requests a connection. The handler returns true to allow, false to deny.

func (*Agent) OnMessage

func (a *Agent) OnMessage(handler MessageHandler)

OnMessage registers a handler for incoming messages.

func (*Agent) PublicKey

func (a *Agent) PublicKey() string

PublicKey returns the agent's public key string.

func (*Agent) RemoveContact

func (a *Agent) RemoveContact(agentID string)

RemoveContact removes an agent from the whitelist.

func (*Agent) Send

func (a *Agent) Send(ctx context.Context, env *envelope.Envelope) error

Send sends an envelope to a peer using P2P (preferred) or signaling relay (fallback). The payload is encrypted (if a session key exists), then the envelope is signed covering the ciphertext + headers (encrypt-then-sign for pre-authentication).

func (*Agent) SendRequest

func (a *Agent) SendRequest(ctx context.Context, env *envelope.Envelope, timeout time.Duration) (*envelope.Envelope, error)

SendRequest sends an envelope and waits for a response with the same TraceID. It returns the response envelope or an error on timeout/context cancellation.

func (*Agent) Start

func (a *Agent) Start(ctx context.Context) error

Start registers the agent with the platform and begins accepting connections.

func (*Agent) Stop

func (a *Agent) Stop(ctx context.Context) error

Stop deregisters the agent and closes all connections.

func (*Agent) Use

func (a *Agent) Use(mw ...Middleware)

Use adds global middleware to the router.

func (*Agent) X25519PublicKeyString

func (a *Agent) X25519PublicKeyString() (string, error)

X25519PublicKeyString returns the agent's X25519 public key for key exchange.

type ConnectionRequest

type ConnectionRequest struct {
	FromAgentID string
	Timestamp   time.Time
}

ConnectionRequest describes a pending connection from an unknown peer.

type ConnectionRequestHandler

type ConnectionRequestHandler func(ctx context.Context, req *ConnectionRequest) bool

ConnectionRequestHandler is called when a non-whitelisted peer requests a connection. Return true to allow, false to deny.

type HandlerFunc

type HandlerFunc func(ctx context.Context, env *envelope.Envelope) (*envelope.Envelope, error)

HandlerFunc processes an inbound envelope and optionally returns a response envelope.

func Chain

func Chain(handler HandlerFunc, mws ...Middleware) HandlerFunc

Chain wraps a handler with the given middlewares. Middlewares are applied in order: the first middleware is the outermost wrapper.

type MessageHandler

type MessageHandler func(ctx context.Context, env *envelope.Envelope)

MessageHandler is called when an incoming envelope is received.

type Middleware

type Middleware func(HandlerFunc) HandlerFunc

Middleware wraps a HandlerFunc to add cross-cutting behavior.

func LoggingMiddleware

func LoggingMiddleware(logger *slog.Logger) Middleware

LoggingMiddleware logs capability, source, and duration for each dispatched request.

func RecoveryMiddleware

func RecoveryMiddleware(logger *slog.Logger) Middleware

RecoveryMiddleware recovers from panics in handlers and converts them to errors.

type Options

type Options struct {
	// Name is the agent's display name.
	Name string

	// ServerURL is the peerclaw-server base URL (e.g., "http://localhost:8080").
	ServerURL string

	// Capabilities lists what this agent can do (e.g., "chat", "search").
	Capabilities []string

	// Protocols lists supported communication protocols (e.g., "a2a", "mcp").
	Protocols []string

	// KeypairPath is the path to the Ed25519 keypair seed file.
	// If empty or not found, a new keypair will be generated.
	KeypairPath string

	// TrustStorePath is the path to the trust store file.
	TrustStorePath string

	// NostrRelays is a list of Nostr relay WebSocket URLs for fallback transport.
	// If non-empty, a transport Selector will be created wrapping WebRTC + Nostr.
	NostrRelays []string

	// Discovery is an optional custom discovery implementation.
	// If nil, a RegistryClient is created using ServerURL.
	Discovery discovery.Discovery

	// Signaling is an optional custom signaling client implementation.
	// If nil, a WebSocket Client is created using ServerURL.
	Signaling pcsignaling.SignalingClient

	// MessageCachePath is the file path for persisting offline message cache.
	MessageCachePath string

	// ClaimToken is a one-time pairing code (e.g., "PCW-XXXX-XXXX") obtained from
	// the platform. When set, the agent uses the claim flow instead of direct
	// registration, binding itself to the user who generated the token.
	ClaimToken string

	// InboxRelays is a list of Nostr relay URLs for the offline mailbox.
	// When non-empty, a Mailbox will be created for offline message delivery.
	InboxRelays []string

	// MailboxTTL is how long mailbox messages are retained (default 7 days).
	MailboxTTL time.Duration

	// InboxSyncInterval is how often the inbox is polled for new messages (default 5 minutes).
	InboxSyncInterval time.Duration

	// OutboxStatePath is the file path for persisting the outbox queue.
	OutboxStatePath string

	// LastSyncPath is the file path for persisting the last inbox sync timestamp.
	LastSyncPath string

	// Logger is the structured logger. Uses slog.Default() if nil.
	Logger *slog.Logger
}

Options configures an Agent.

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router dispatches inbound envelopes to capability-specific handlers.

func NewRouter

func NewRouter(logger *slog.Logger) *Router

NewRouter creates a new Router.

func (*Router) Capabilities

func (r *Router) Capabilities() []string

Capabilities returns the list of registered capability names.

func (*Router) Dispatch

func (r *Router) Dispatch(ctx context.Context, env *envelope.Envelope) (matched bool, resp *envelope.Envelope, err error)

Dispatch routes an envelope to the matching capability handler. Returns (false, nil, nil) if no capability metadata or no matching handler (fallthrough). Returns (true, resp, err) when a handler is matched and executed.

func (*Router) Handle

func (r *Router) Handle(capability string, handler HandlerFunc)

Handle registers a handler for the given capability.

func (*Router) Use

func (r *Router) Use(mw ...Middleware)

Use appends global middlewares that wrap every handler on dispatch.

type Task

type Task struct {
	ID        string
	TraceID   string
	AgentID   string // destination agent
	State     TaskState
	Request   *envelope.Envelope
	Response  *envelope.Envelope // terminal response (nil until completed/failed)
	CreatedAt time.Time
	UpdatedAt time.Time
}

Task maps an Envelope request-response exchange to an A2A task lifecycle.

type TaskState

type TaskState string

TaskState represents the lifecycle state of an A2A task.

const (
	TaskSubmitted     TaskState = "submitted"
	TaskWorking       TaskState = "working"
	TaskCompleted     TaskState = "completed"
	TaskFailed        TaskState = "failed"
	TaskCanceled      TaskState = "canceled"
	TaskInputRequired TaskState = "input_required"
)

type TaskTracker

type TaskTracker struct {
	// contains filtered or unexported fields
}

TaskTracker manages the lifecycle of tasks keyed by TraceID.

func NewTaskTracker

func NewTaskTracker() *TaskTracker

NewTaskTracker creates an empty TaskTracker.

func (*TaskTracker) Get

func (tt *TaskTracker) Get(traceID string) (*Task, bool)

Get returns a task by its TraceID.

func (*TaskTracker) List

func (tt *TaskTracker) List() []*Task

List returns all tracked tasks.

func (*TaskTracker) Remove

func (tt *TaskTracker) Remove(traceID string)

Remove deletes a task by its TraceID.

func (*TaskTracker) Submit

func (tt *TaskTracker) Submit(env *envelope.Envelope) *Task

Submit creates a new task in Submitted state from an outgoing request envelope.

func (*TaskTracker) Update

func (tt *TaskTracker) Update(traceID string, state TaskState, resp *envelope.Envelope)

Update transitions a task to a new state with an optional response envelope.

Directories

Path Synopsis
cmd
peerclaw-trust command
examples
echo command
enterprise command
Enterprise example: minimal intranet agent setup using NewSimple + ImportContacts.
Enterprise example: minimal intranet agent setup using NewSimple + ImportContacts.
llm-tools command
Package main demonstrates how to expose PeerClaw agent capabilities as MCP-compatible tools for LLM-driven agents.
Package main demonstrates how to expose PeerClaw agent capabilities as MCP-compatible tools for LLM-driven agents.
p2p-chat command
Package main demonstrates a minimal P2P chat between two agents.
Package main demonstrates a minimal P2P chat between two agents.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL