Skip to content

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

Terminal window
go get github.com/layr8/go-sdk

Core 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 ID

Messages

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:

KindWhen
ErrParseFailureInbound message could not be parsed as a DIDComm envelope
ErrNoHandlerNo handler registered for the message type
ErrHandlerPanicA handler goroutine panicked (recovered automatically)
ErrServerRejectThe server rejected a message (e.g., authorization failure)
ErrTransportWriteFailed 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 headers

Store, 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 JWT

Sign and Verify a Presentation

presJWT, err := client.SignPresentation(ctx, []string{signedJWT})
result, err := client.VerifyPresentation(ctx, presJWT)
// result.Presentation — decoded presentation
// result.Headers — JWT headers

See 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 return ErrNotConnected immediately — the SDK does not queue messages
  • The OnDisconnect callback fires when the connection drops
  • The OnReconnect callback 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:

Terminal window
mkdir -p .claude/skills
cp /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”