DID-Authenticated HTTP Transport for A2A Protocol
sage-a2a-go provides DID-authenticated HTTP/JSON-RPC 2.0 transport for a2a-go, enabling secure agent-to-agent communication with blockchain-anchored identity.
Note: This project uses a SAGE-X fork of a2a-go with critical bug fixes for Message Parts marshaling. See Bug Fix section below.
- ✅ HTTP/JSON-RPC 2.0 Transport: Required by A2A spec (a2a-go only has gRPC)
- ✅ Automatic DID Signing: All HTTP requests signed with RFC 9421
- ✅ Blockchain Identity: SAGE DIDs anchored on Ethereum/Solana/Kaia
- ✅ Drop-in for a2a-go: Use standard a2a-go Client with DID auth
- ✅ Zero Code Duplication: Wraps a2a-go, doesn't reimplement
- ✅ Bug Fixes: Includes critical Message Parts marshaling fix
| Feature | a2a-go | sage-a2a-go |
|---|---|---|
| A2A Client SDK | ✅ | ✅ (uses a2a-go) |
| gRPC Transport | ✅ | ✅ (from a2a-go) |
| HTTP/JSON-RPC Transport | ❌ | ✅ |
| DID Authentication | ❌ | ✅ |
| RFC 9421 Signatures | ❌ | ✅ |
| Blockchain Identity | ❌ | ✅ |
sage-a2a-go = a2a-go + HTTP Transport + DID Auth
Build SAGE protocol agents with 83% less code and zero direct sage imports:
import "github.com/sage-x-project/sage-a2a-go/pkg/agent/framework"
// Initialize agent in just 4 lines (replaces 165 lines of boilerplate)
agent, err := framework.NewAgentFromEnv(
"payment", // agent name
"PAYMENT", // environment variable prefix
true, // enable HPKE encryption
true, // require signature verification
)
if err != nil {
log.Fatal(err)
}
// Use the agent's HTTP server for receiving messages
httpServer := agent.GetHTTPServer()
// Create HPKE client for sending encrypted messages
transport := prototx.NewA2ATransport(...)
hpkeClient, err := agent.CreateHPKEClient(transport)Code Reduction:
- Initialization: 165 lines → 10 lines (94% reduction)
- Full Agent: ~686 lines → ~150 lines (78% reduction)
- Direct sage imports: 7 → 0 (100% elimination)
Benefits:
- ✅ Zero direct sage imports - Framework handles all complexity
- ✅ Environment-based config - Easy deployment with env vars
- ✅ Built-in HPKE - Automatic encryption for A2A communication
- ✅ DID authentication - Signature verification included
- ✅ Pure business logic - Focus on what matters, not crypto details
- ✅ Comprehensive tests - 23 test cases covering all functionality
Package Structure:
pkg/agent/framework/
├── agent.go - Main framework (NewAgent, NewAgentFromEnv)
├── keys/ - Cryptographic key management
├── session/ - HPKE session management
├── did/ - DID resolution and verification
├── middleware/ - HTTP DID authentication
└── hpke/ - HPKE client/server abstractions
Documentation:
- 📖 Framework Guide - Complete API reference
- 💡 Examples - Working payment agent example
- 📋 Migration Guide - Migrating from sage-multi-agent
- ✅ Verification Report - Migration verification
Quick Links:
┌──────────────────────────────────────────────────┐
│ Your Application │
│ (uses a2aclient.Client from a2a-go) │
└──────────────────┬───────────────────────────────┘
│
┌──────────────────▼───────────────────────────────┐
│ a2a-go Client │
│ - GetTask, SendMessage, etc. │
│ - CallInterceptors │
│ - Config management │
└──────────┬──────────────────┬────────────────────┘
│ │
┌──────▼──────┐ ┌──────▼──────────────────┐
│ gRPC │ │ DIDHTTPTransport │
│ Transport │ │ (sage-a2a-go) │
│ (a2a-go) │ │ - HTTP/JSON-RPC 2.0 │
└─────────────┘ │ - DID Signatures │
│ - RFC 9421 │
└──────┬──────────────────┘
│
┌──────▼──────────────────┐
│ SAGE DID Auth │
│ - DIDVerifier │
│ - A2ASigner │
│ - KeySelector │
└─────────────────────────┘
go get github.com/sage-x-project/sage-a2a-goThe project automatically uses the SAGE-X fork of a2a-go via replace directive in go.mod:
// Use SAGE-X fork with bug fixes
replace github.com/a2aproject/a2a-go => github.com/SAGE-X-project/a2a-go v0.0.0-20251026124015-70634d9eddaeThis ensures you get critical bug fixes for Message Parts marshaling automatically.
package main
import (
"context"
"log"
"github.com/a2aproject/a2a-go/a2a"
"github.com/sage-x-project/sage-a2a-go/pkg/agent"
"github.com/sage-x-project/sage-a2a-go/pkg/crypto"
"github.com/sage-x-project/sage-a2a-go/pkg/identity"
)
func main() {
ctx := context.Background()
// Generate key pair using sage-a2a-go crypto package
myKeyPair, _ := crypto.GenerateSecp256k1KeyPair()
// Your agent's identity
myDID := identity.AgentDID("did:sage:ethereum:0x...")
// Target agent's card
targetCard := &a2a.AgentCard{
Name: "Assistant Agent",
URL: "https://agent.example.com",
// ...
}
// Create agent with DID authentication (unified API)
myAgent, err := agent.NewAgent(ctx, myDID, myKeyPair, targetCard)
if err != nil {
log.Fatal(err)
}
defer myAgent.A2AClient.Destroy()
// Send message (automatically signed with DID)
message := &a2a.MessageSendParams{
Message: &a2a.Message{
Role: a2a.RoleUser,
Parts: []a2a.Part{
&a2a.TextPart{Text: "Hello!"},
},
},
}
task, err := myAgent.A2AClient.SendMessage(ctx, message)
if err != nil {
log.Fatal(err)
}
log.Printf("Task: %+v", task)
}import (
"github.com/a2aproject/a2a-go/a2aclient"
"github.com/sage-x-project/sage-a2a-go/pkg/crypto"
"github.com/sage-x-project/sage-a2a-go/pkg/identity"
"github.com/sage-x-project/sage-a2a-go/pkg/transport"
)
// Generate key pair
myKeyPair, _ := crypto.GenerateSecp256k1KeyPair()
myDID := identity.AgentDID("did:sage:ethereum:0x...")
// Use a2a-go's factory with DID HTTP transport
client, err := a2aclient.NewFromCard(
ctx,
agentCard,
transport.WithDIDHTTPTransport(myDID, myKeyPair, nil),
a2aclient.WithConfig(a2aclient.Config{
AcceptedOutputModes: []string{"application/json"},
}),
a2aclient.WithInterceptors(loggingInterceptor),
)// Task management
task, err := client.GetTask(ctx, &a2a.TaskQueryParams{ID: "task-123"})
task, err := client.CancelTask(ctx, &a2a.TaskIDParams{ID: "task-123"})
// Messaging
result, err := client.SendMessage(ctx, messageParams)
// Streaming (via Server-Sent Events)
for event, err := range client.SendStreamingMessage(ctx, messageParams) {
// handle event
}
// Push notifications
config, err := client.GetTaskPushConfig(ctx, params)
configs, err := client.ListTaskPushConfig(ctx, params)
config, err := client.SetTaskPushConfig(ctx, config)
err := client.DeleteTaskPushConfig(ctx, params)
// Agent discovery
card, err := client.GetAgentCard(ctx)sage-a2a-go implements the a2aclient.Transport interface:
type Transport interface {
GetTask(ctx, query) (*Task, error)
CancelTask(ctx, id) (*Task, error)
SendMessage(ctx, msg) (SendMessageResult, error)
SendStreamingMessage(ctx, msg) iter.Seq2[Event, error]
// ... all A2A methods
}DIDHTTPTransport adds:
- HTTP/JSON-RPC 2.0 protocol support
- Automatic DID signature on every request
- RFC 9421 HTTP Message Signatures
1. Client calls: client.SendMessage(ctx, message)
↓
2. a2a-go calls: transport.SendMessage(ctx, message)
↓
3. DIDHTTPTransport:
- Marshals to JSON-RPC 2.0 request
- Creates HTTP POST to /rpc endpoint
- Signs with DID (RFC 9421)
- Sends HTTP request
- Parses JSON-RPC 2.0 response
↓
4. Returns result to client
Every HTTP request includes:
POST /rpc HTTP/1.1
Host: agent.example.com
Content-Type: application/json
Signature-Input: sig1=("@method" "@target-uri" "content-type");created=1234567890;keyid="did:sage:ethereum:0x...";alg="ecdsa-p256-sha256"
Signature: sig1=:base64signature:
{"jsonrpc":"2.0","method":"message/send","params":{...},"id":1}The official a2a-go library had a critical bug in message unmarshaling where Message.Parts would unmarshal as value types instead of pointer types, causing message transmission failures in agent-to-agent communication.
Problem:
// After unmarshaling with official a2a-go
msg.Parts[0] // Type: a2a.TextPart (value) ❌Solution:
The SAGE-X fork fixes this in a2a/core.go:304-332:
// Fixed UnmarshalJSON implementation
case "text":
var part TextPart
if err := json.Unmarshal(rawMsg, &part); err != nil {
return err
}
result[i] = &part // Return pointer type ✅Impact:
- ✅ Messages now transmit correctly between agents
- ✅ All 173 tests passing with strict pointer type validation
- ✅ E2E tests verify correct behavior
This project automatically uses the fixed fork, so you don't need to worry about this issue.
sage-a2a-go provides a complete unified API so you only need to import sage-a2a-go packages:
Wraps SAGE crypto functionality with a simple interface:
import "github.com/sage-x-project/sage-a2a-go/pkg/crypto"
// Generate key pairs
keyPair, _ := crypto.GenerateSecp256k1KeyPair() // Ethereum
keyPair, _ := crypto.GenerateEd25519KeyPair() // Solana
keyPair, _ := crypto.GenerateX25519KeyPair() // HPKE/encryption
// Key types
crypto.KeyTypeSecp256k1
crypto.KeyTypeEd25519
crypto.KeyTypeX25519Wraps SAGE DID functionality:
import "github.com/sage-x-project/sage-a2a-go/pkg/identity"
// Work with DIDs
did := identity.AgentDID("did:sage:ethereum:0x...")
chain, address, _ := identity.ParseDID(did)
// Validate DIDs
err := identity.ValidateDID(string(did))
// Marshal/unmarshal public keys
keyData, _ := identity.MarshalPublicKey(pubKey)
pubKey, _ := identity.UnmarshalPublicKey(keyData, "secp256k1")Combines SAGE identity with A2A communication:
import "github.com/sage-x-project/sage-a2a-go/pkg/agent"
// Build complete agent with one function
agent, err := agent.NewAgent(ctx, myDID, myKeyPair, targetCard)
// Access A2A client
task, _ := agent.A2AClient.SendMessage(ctx, message)
// Agent structure
type Agent struct {
DID identity.AgentDID
KeyPair crypto.KeyPair
A2AClient *a2aclient.Client
Card *a2a.AgentCard
}Simplified HPKE (Hybrid Public Key Encryption) client for end-to-end encryption:
import "github.com/sage-x-project/sage-a2a-go/pkg/hpke"
// Create HPKE client
hpkeClient, err := hpke.NewClient(
clientDID,
keyPair,
transport,
resolver,
sessionMgr,
nil, // optional ClientOptions
)
// Initialize encrypted session
sessionID, err := hpkeClient.InitializeSession(ctx, "context-123", peerDID)
// Access session manager
sessionMgr := hpkeClient.GetSessionManager()Three-phase registration wrapper for agent registration:
import "github.com/sage-x-project/sage-a2a-go/pkg/registry"
// Create registration client
client, err := registry.NewRegistrationClient(®istry.ClientConfig{
RPCURL: "https://ethereum-rpc.example.com",
RegistryAddress: "0x1234...",
PrivateKey: "your-private-key",
})
// Phase 1: Commit (wait 1-60 minutes)
status, err := client.CommitRegistration(ctx, params)
// Phase 2: Register (wait 1 hour)
status, err = client.RegisterAgent(ctx, status)
// Phase 3: Activate
err = client.ActivateAgent(ctx, status)Advanced session management for multi-peer scenarios:
import "github.com/sage-x-project/sage-a2a-go/pkg/session"
sessionMgr := session.NewManager()
// List sessions by peer DID
sessions := sessionMgr.ListByRemoteDID(ctx, peerDID)
// Get session count
count := sessionMgr.Count()
// Session metadata
err := sessionMgr.SetMetadata(ctx, sessionID, "request_id", "req-123")
value, err := sessionMgr.GetMetadata(ctx, sessionID, "request_id")
// Cleanup sessions
deleted := sessionMgr.DeleteByRemoteDID(ctx, peerDID)Philosophy: Users should build agents using only sage-a2a-go imports. No need to directly import SAGE or A2A packages.
Implements HTTP/JSON-RPC 2.0 with DID signatures:
DIDHTTPTransport- Main transport implementationWithDIDHTTPTransport()- Factory option for a2a-goNewDIDAuthenticatedClient()- Convenience function
Verify HTTP signatures using DIDs:
DIDVerifier- Verify HTTP signatures using DIDsKeySelector- Protocol-aware key selection (Ethereum/Solana/HPKE)RFC9421Verifier- RFC 9421 implementation
Sign HTTP requests with DID:
A2ASigner- Sign HTTP requests with DIDDefaultA2ASigner- RFC 9421 implementation with security hardening
DID authentication for HTTP servers:
DIDAuthMiddleware- Middleware for verifying incoming requests- Extracts and validates DID signatures
- Adds verified DID to request context
Agent metadata and verification:
AgentCard- Agent metadataAgentCardSigner- Sign/verify cards with JWS
All 10 client methods from A2A v0.4.0 specification with 92.1% test coverage and comprehensive E2E tests.
DIDs stored on:
- Ethereum (ECDSA/secp256k1)
- Solana (Ed25519)
- Kaia
- Signature-Input header
- Signature header
- DID as keyid parameter
- Timestamp for replay protection
- Protocol-aware key selection
- Up to 10 keys per agent
- Key rotation support
- Wraps a2a-go instead of reimplementing
- Automatic updates when a2a-go updates
- Clean separation of concerns
The project maintains 92.1% average test coverage across all packages:
| Package | Coverage | Tests |
|---|---|---|
pkg/server |
100.0% | 🏆 Full coverage |
pkg/session |
100.0% | 🏆 Full coverage (20 tests) |
pkg/client |
92.3% | Unit + integration |
pkg/signer |
92.2% | HTTP signing tests |
pkg/protocol |
91.2% | Card validation |
pkg/verifier |
88.0% | DID verification |
pkg/transport |
87.2% | HTTP transport |
pkg/hpke |
85.0% | HPKE client (9 tests) |
pkg/registry |
83.0% | Registry client (6 tests) |
| Total | 92.1% | 208 tests |
Comprehensive E2E tests in test/e2e/ verify real-world scenarios:
# Run E2E tests
go test ./test/e2e/... -vTest Coverage:
- ✅ Full HTTP request/response cycle
- ✅ DID signature verification
- ✅ SSE streaming with multiple messages
- ✅ Task operations (get, list, cancel)
- ✅ Timeout handling
- ✅ Error propagation
- ✅ Message Parts pointer type validation
9 test cases covering:
SendMessage_Success- Complete message flowGetAgentCard_Success- Agent card retrievalTimeout_HandledCorrectly- Timeout scenariosStreamMessage_Success- SSE streaming with 3 messagesGetTask_Success- Task retrievalListTasks_Success- Task listing with paginationCancelTask_Success- Task cancellation
# All tests
make test-all
# Unit tests only
make test
# With coverage report
make test-coverage
# E2E tests
go test ./test/e2e/... -v- Integration Guide - Complete integration tutorial
- SSE Streaming Guide - Real-time streaming with SSE
- API Reference - Complete API documentation
- Architecture & Design - Comprehensive system architecture, design principles, and implementation details
- GoDoc - Generated API documentation
- Changelog - Version history and upgrade guide
Complete examples in cmd/examples/:
1. Simple Client (README)
Basic DID-authenticated A2A client.
go run ./cmd/examples/simple-client/main.go2. Simple Agent (README)
Create an agent with DID authentication.
go run ./cmd/examples/simple-agent/main.go3. Chat Demo (README)
Interactive chat application with SSE streaming.
go run ./cmd/examples/chat-demo/main.go4. SSE Streaming (README)
Real-time message streaming with Server-Sent Events.
go run ./cmd/examples/sse-streaming/main.go5. Agent Communication (README)
Agent-to-agent communication example.
go run ./cmd/examples/agent-communication/main.go6. Multi-Key Agent (README)
Multi-protocol key management.
go run ./cmd/examples/multi-key-agent/main.goThis project uses Make for build automation. Run make help to see all available commands.
# Display all available commands
make help
# Build the library
make build
# Run tests
make test
# Run tests with coverage report
make test-coverage
# Format code
make fmt
# Run linter
make lint
# Quick development cycle (fmt + vet + test)
make dev# Build library
make build
# Build example programs
make build-examples
# Install library locally
make install# Run unit tests
make test
# Run tests with verbose output
make test-verbose
# Generate coverage report (HTML)
make test-coverage
# Run integration tests
make test-integration
# Run all tests (unit + integration)
make test-all
# Run benchmarks
make bench# Format code
make fmt
# Check formatting
make fmt-check
# Run go vet
make vet
# Run linter
make lint
# Auto-fix linter issues
make lint-fix
# Run all quality checks
make check# Download dependencies
make deps
# Tidy go.mod and go.sum
make tidy
# Verify dependencies
make verify
# Update all dependencies
make deps-update# Clean build artifacts and test cache
make clean
# Clean only build artifacts
make clean-build
# Clean coverage reports
make clean-coverage# Run CI checks (format, vet, lint, test)
make ci
# Run full CI suite with coverage
make ci-full
# Run pre-commit checks
make pre-commitIf you prefer not to use Make:
# Build
go build ./...
# Test
go test ./...
# Coverage
go test -cover -coverprofile=coverage.out ./...
go tool cover -html=coverage.outsage-a2a-go tracks versions of its dependencies:
import "github.com/sage-x-project/sage-a2a-go/pkg/version"
info := version.Get()
// info.SageA2AVersion = "1.0.0-dev"
// info.A2AProtocolVersion = "0.4.0"
// info.SAGEVersion = "1.5.2"
// info.A2AGoForkVersion = "v0.0.0-20251026124015-70634d9eddae"When updating a2a-go (using SAGE-X fork):
# Update to latest fork version
go get -u github.com/SAGE-X-project/a2a-go
go mod tidy
go test ./...The project uses the SAGE-X fork to ensure critical bug fixes are included. Monitor both repositories:
- Official: a2aproject/a2a-go
- Fork (used): SAGE-X-project/a2a-go
- ✅ HTTP/JSON-RPC 2.0 transport
- ✅ DID signatures (RFC 9421)
- ✅ A2A v0.4.0 protocol support
- ✅ Server-Sent Events (SSE) for streaming
- ✅ All core protocol methods (GetTask, SendMessage, ListTasks, etc.)
- ✅ DID authentication middleware for servers
- ✅ Unified API architecture (v1.5.2)
- ✅ HPKE Client wrapper (v1.6.0)
- ✅ Registry Client wrapper (v1.6.0)
- ✅ Enhanced Session Manager (v1.6.0)
- ✅ 92.1% test coverage (208 tests: Unit + Integration + E2E)
- ✅ 6 complete example programs
- ✅ Comprehensive documentation
- Enhanced error handling with custom error types
- Retry logic for network operations
- Connection pooling for HPKE sessions
- Metrics and observability hooks
- WebSocket transport
- HTTP/2 and HTTP/3 support
- Plugin architecture for custom transports
- GraphQL support for registry queries
- Multi-chain registry support (Solana, Polygon)
- WebAssembly compilation target
- Rate limiting and quota management
We welcome contributions! Please see CONTRIBUTING.md for detailed guidelines.
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Write tests (TDD approach - maintain 90%+ coverage)
- Implement feature
- Run tests:
make test-all - Run linter:
make lint - Submit Pull Request
<type>: <subject>
Types: feat, fix, test, refactor, docs, chore
Example: feat: add SSE streaming support
This project uses a SAGE-X fork of a2a-go. If you need to modify a2a-go:
- Fork is at: https://github.com/SAGE-X-project/a2a-go
- Local development: Use
replacedirective ingo.mod - Submit fixes to the fork first
- Update the fork version in this project
See CONTRIBUTING.md for full details.
- DID Spoofing: Prevented by blockchain anchoring
- Replay Attacks: Timestamp validation
- MITM: TLS + HTTP signatures
- Key Compromise: Multi-key rotation
- Use HTTPS/TLS 1.2+
- Validate signature timestamps
- Rotate keys regularly
- Use mTLS in production
LGPL-3.0 - see LICENSE
Dependencies:
- a2a-go (Apache 2.0)
- SAGE (LGPL-3.0)
- A2A Protocol - v0.4.0 specification
- a2a-go (Official) - Go SDK
- a2a-go (SAGE-X Fork) - Fork with bug fixes (used by this project)
- SAGE - DID infrastructure
- RFC 9421 - HTTP Signatures
- DID Core - W3C Specification
- GitHub Issues: Report bugs
- Documentation: Complete guides
- Examples:
cmd/examples/
sage-a2a-go: Bringing DID authentication to A2A Protocol 🔐