Go SDK
The Layr8 Go SDK is a minimal, idiomatic Go library for building DIDComm agents. It handles WebSocket connections, Phoenix Channel protocol, DIDComm message formatting, and thread correlation. You write domain logic.
A basic agent is ~50 lines of Go. The SDK manages everything between your handler and the network.
Install
go get github.com/layr8/go-sdkCore Concepts
Config
client, err := layr8.NewClient(layr8.Config{ NodeURL: "wss://your-node.layr8.cloud/plugin_socket/websocket", APIKey: "your-api-key", AgentDID: "did:web:your-node.layr8.cloud:my-agent",}, layr8.LogErrors(log.Default()))if err != nil { log.Fatal(err)}NewClient takes a Config and a required ErrorHandler. The error handler is called for SDK-level errors that cannot be returned to a direct caller (unparseable messages, missing handlers, handler panics, server rejections). LogErrors is a built-in helper that logs all errors via a standard logger.
All config fields fall back to environment variables if empty: LAYR8_NODE_URL, LAYR8_API_KEY, LAYR8_AGENT_DID. If AgentDID is omitted, the node assigns an ephemeral DID on connect.
Handlers
Register handlers before calling Connect. The handler’s return value determines what happens:
client.Handle("https://layr8.io/protocols/echo/1.0/request", func(msg *layr8.Message) (*layr8.Message, error) { // Return (*Message, nil) → send response to sender // Return (nil, error) → send problem report to sender // Return (nil, nil) → no response (fire-and-forget) },)The protocol base URI is derived automatically from the message type and registered with the node on connect.
Send
err := client.Send(ctx, &layr8.Message{ Type: "https://didcomm.org/basicmessage/2.0/message", To: []string{"did:web:partner-node:agent"}, Body: map[string]string{"content": "Hello!"},})By default, Send waits for the server to acknowledge the message and returns an error if the server rejects it (e.g., authorization failure). For true fire-and-forget behavior, use WithFireAndForget():
err := client.Send(ctx, &layr8.Message{ Type: "https://didcomm.org/basicmessage/2.0/message", To: []string{"did:web:partner-node:agent"}, Body: map[string]string{"content": "Hello!"},}, layr8.WithFireAndForget())Request (Request/Response)
resp, err := client.Request(ctx, &layr8.Message{ Type: "https://layr8.io/protocols/echo/1.0/request", To: []string{"did:web:partner-node:echo-agent"}, Body: EchoRequest{Message: "ping"},})// resp is the correlated response, matched by thread IDMessages
type Message struct { ID string // auto-generated if empty Type string // DIDComm message type URI From string // auto-filled with agent DID To []string // recipient DIDs ThreadID string // auto-generated for Request Body any // serialized to JSON Context *MessageContext // populated on inbound messages}Use msg.UnmarshalBody(&target) to deserialize the body. On inbound messages, msg.Context provides node metadata: Authorized, Recipient, and SenderCredentials.
Error Handling
The SDK requires an ErrorHandler at construction time. This ensures SDK-level errors are never silently swallowed.
ErrorHandler
ErrorHandler is called for errors that cannot be returned to a direct caller:
type ErrorHandler func(SDKError)
type SDKError struct { Kind ErrorKind // category of error MessageID string // DIDComm message ID (if available) Type string // message type URI From string // sender DID Cause error // underlying error Raw []byte // raw message bytes (for parse failures) Timestamp time.Time}Error kinds:
| Kind | When |
|---|---|
ErrParseFailure | Inbound message could not be parsed as a DIDComm envelope |
ErrNoHandler | No handler registered for the message type |
ErrHandlerPanic | A handler goroutine panicked (recovered automatically) |
ErrServerReject | The server rejected a message (e.g., authorization failure) |
ErrTransportWrite | Failed to write to the WebSocket connection |
Built-in Helper
LogErrors logs all SDK errors via a standard log.Logger:
client, err := layr8.NewClient(cfg, layr8.LogErrors(log.Default()))Custom Handler
For production agents, route errors to your observability stack:
client, err := layr8.NewClient(cfg, func(e layr8.SDKError) { slog.Error("sdk error", "kind", e.Kind, "message_id", e.MessageID, "type", e.Type, "from", e.From, "error", e.Cause, ) metrics.IncrCounter("layr8.sdk.errors", 1)})Verifiable Credentials
The SDK provides methods for signing, verifying, storing, and retrieving W3C Verifiable Credentials through the cloud-node’s REST API.
Sign a Credential
cred := layr8.Credential{ Context: []string{"https://www.w3.org/ns/credentials/v2"}, Type: []string{"VerifiableCredential"}, CredentialSubject: map[string]any{"id": subjectDID, "name": "Acme Corp"},}
signedJWT, err := client.SignCredential(ctx, cred)The issuer DID defaults to client.DID(). Override with layr8.WithIssuerDID(did). Output format defaults to compact_jwt; use layr8.WithCredentialFormat(layr8.FormatJSON) for JSON-LD.
Verify a Credential
result, err := client.VerifyCredential(ctx, signedJWT)// result.Credential — decoded credential claims// result.Headers — JWT headersStore, List, and Get
stored, err := client.StoreCredential(ctx, signedJWT)// stored.ID — unique credential ID
creds, err := client.ListCredentials(ctx)// []StoredCredential
fetched, err := client.GetCredential(ctx, stored.ID)// fetched.CredentialJWT — the original signed JWTSign and Verify a Presentation
presJWT, err := client.SignPresentation(ctx, []string{signedJWT})
result, err := client.VerifyPresentation(ctx, presJWT)// result.Presentation — decoded presentation// result.Headers — JWT headersSee Verifiable Credentials for background on the W3C standard and trust model.
Durable Handlers (Persist-then-Ack)
For messages that must not be lost, use manual ack to persist before acknowledging:
client.Handle("https://layr8.io/protocols/order/1.0/created", func(msg *layr8.Message) (*layr8.Message, error) { var body any msg.UnmarshalBody(&body)
// Write to disk first — if this fails, the message is // NOT acked and the cloud-node will redeliver it. if err := persistToDisk(msg.ID, body); err != nil { return nil, err }
msg.Ack() // safe to ack now return nil, nil }, layr8.WithManualAck(),)See examples/durable-handler for a complete working example.
Connection Resilience
The SDK automatically reconnects when the WebSocket connection drops (e.g., node restart, network interruption). Reconnection uses exponential backoff starting at 1 second, capped at 30 seconds.
During reconnection:
Send(),Request(), and other operations returnErrNotConnectedimmediately — the SDK does not queue messages- The
OnDisconnectcallback fires when the connection drops - The
OnReconnectcallback fires when the connection is restored Close()stops the reconnect loop
Claude Code Skill
The SDK includes a Claude Code skill that teaches Claude the full API. Install it into any agent project:
mkdir -p .claude/skillscp /path/to/go-sdk/.claude/skills/build-layr8-agent.md .claude/skills/Once installed, Claude Code knows how to build Layr8 agents in every session. Example prompts:
- “Build me a postgres query agent that forwards SQL queries to a partner’s database”
- “Add a handler for order.created messages that validates the sender’s credentials”
- “Convert this REST endpoint into a DIDComm agent”
Links
- Go SDK on GitHub — Full README, source code, and examples
- Example Agents — Working demo agents from beginner to advanced
- DIDComm Reference — Protocol details and message patterns
- Getting Started — End-to-end setup walkthrough