Repository: open-telemetry/opamp-go
Pull request #272 (2024-04-17 17:40:19)

Use UUID instead of ULID
Updates OpAMP spec to 0.9.0 and implements spec change https://github.com/open-telemetry/opamp-spec/pull/186

The example server still accepts old-style ULID agent instances to
demonstrate how this change can be handled in a backward compatible way.
Replace ULIDs by 16 byte ids and recommend UUID v7 (#186)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/272?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) Report
Attention: Patch coverage is `77.77778%` with `2 lines` in your changes are missing coverage. Please review.
> Project coverage is 75.18%. Comparing base [(`359e77a`)](https://app.codecov.io/gh/open-telemetry/opamp-go/commit/359e77ace7bcb8542be8d5726942222230d41de2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) to head [(`d44e1d5`)](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/272?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).

| [Files](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/272?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | Patch % | Lines |
|---|---|---|
| [server/serverimpl.go](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/272?src=pr&el=tree&filepath=server%2Fserverimpl.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-c2VydmVyL3NlcnZlcmltcGwuZ28=) | 0.00% | [0 Missing and 2 partials :warning: ](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/272?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #272      +/-   ##
==========================================
+ Coverage   75.07%   75.18%   +0.10%     
==========================================
  Files          25       25              
  Lines        1657     1656       -1     
==========================================
+ Hits         1244     1245       +1     
+ Misses        300      299       -1     
+ Partials      113      112       -1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/open-telemetry/opamp-go/pull/272?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).

tigrannajaryan: @open-telemetry/opamp-go-approvers please review.

Patch:
diff --git a/client/internal/receivedprocessor.go b/client/internal/receivedprocessor.go
index af87920..736073d 100644
--- a/client/internal/receivedprocessor.go
+++ b/client/internal/receivedprocessor.go
@@ -2,7 +2,7 @@ package internal
 
 import (
 	"context"
-	"errors"
+	"fmt"
 
 	"github.com/open-telemetry/opamp-go/client/types"
 	"github.com/open-telemetry/opamp-go/protobufs"
@@ -225,13 +225,13 @@ func (r *receivedProcessor) processErrorResponse(ctx context.Context, body *prot
 }
 
 func (r *receivedProcessor) rcvAgentIdentification(ctx context.Context, agentId *protobufs.AgentIdentification) error {
-	if agentId.NewInstanceUid == "" {
-		err := errors.New("empty instance uid is not allowed")
+	if len(agentId.NewInstanceUid) != 16 {
+		err := fmt.Errorf("instance uid must be 16 bytes but is %d bytes long", len(agentId.NewInstanceUid))
 		r.logger.Debugf(ctx, err.Error())
 		return err
 	}
 
-	err := r.sender.SetInstanceUid(agentId.NewInstanceUid)
+	err := r.sender.SetInstanceUid(types.InstanceUid(agentId.NewInstanceUid))
 	if err != nil {
 		r.logger.Errorf(ctx, "Error while setting instance uid: %v", err)
 		return err
diff --git a/client/internal/sender.go b/client/internal/sender.go
index f418b4c..b6338b1 100644
--- a/client/internal/sender.go
+++ b/client/internal/sender.go
@@ -3,7 +3,7 @@ package internal
 import (
 	"errors"
 
-	"github.com/oklog/ulid/v2"
+	"github.com/open-telemetry/opamp-go/client/types"
 	"github.com/open-telemetry/opamp-go/protobufs"
 )
 
@@ -21,7 +21,7 @@ type Sender interface {
 	ScheduleSend()
 
 	// SetInstanceUid sets a new instanceUid to be used for all subsequent messages to be sent.
-	SetInstanceUid(instanceUid string) error
+	SetInstanceUid(instanceUid types.InstanceUid) error
 }
 
 // SenderCommon is partial Sender implementation that is common between WebSocket and plain
@@ -65,18 +65,15 @@ func (h *SenderCommon) NextMessage() *NextMessage {
 // SetInstanceUid sets a new instanceUid to be used for all subsequent messages to be sent.
 // Can be called concurrently, normally is called when a message is received from the
 // Server that instructs us to change our instance UID.
-func (h *SenderCommon) SetInstanceUid(instanceUid string) error {
-	if instanceUid == "" {
+func (h *SenderCommon) SetInstanceUid(instanceUid types.InstanceUid) error {
+	var emptyUid types.InstanceUid
+	if instanceUid == emptyUid {
 		return errors.New("cannot set instance uid to empty value")
 	}
 
-	if _, err := ulid.ParseStrict(instanceUid); err != nil {
-		return err
-	}
-
 	h.nextMessage.Update(
 		func(msg *protobufs.AgentToServer) {
-			msg.InstanceUid = instanceUid
+			msg.InstanceUid = instanceUid[:]
 		})
 
 	return nil
diff --git a/client/types/instanceid.go b/client/types/instanceid.go
new file mode 100644
index 0000000..47901ad
--- /dev/null
+++ b/client/types/instanceid.go
@@ -0,0 +1,3 @@
+package types
+
+type InstanceUid [16]byte
diff --git a/client/types/startsettings.go b/client/types/startsettings.go
index b395afd..857fd2a 100644
--- a/client/types/startsettings.go
+++ b/client/types/startsettings.go
@@ -21,7 +21,7 @@ type StartSettings struct {
 	TLSConfig *tls.Config
 
 	// Agent information.
-	InstanceUid string
+	InstanceUid InstanceUid
 
 	// Callbacks that the client will call after Start() returns nil.
 	Callbacks Callbacks
diff --git a/go.mod b/go.mod
index 125f76b..01c3eac 100644
--- a/go.mod
+++ b/go.mod
@@ -4,8 +4,8 @@ go 1.20
 
 require (
 	github.com/cenkalti/backoff/v4 v4.2.1
+	github.com/google/uuid v1.6.0
 	github.com/gorilla/websocket v1.5.1
-	github.com/oklog/ulid/v2 v2.1.0
 	github.com/stretchr/testify v1.9.0
 	google.golang.org/protobuf v1.33.0
 )
diff --git a/go.sum b/go.sum
index c53b66a..0492792 100644
--- a/go.sum
+++ b/go.sum
@@ -4,11 +4,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
 github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
-github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
-github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
-github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
diff --git a/internal/examples/agent/agent/agent.go b/internal/examples/agent/agent/agent.go
index bc89c3b..71e9dd4 100644
--- a/internal/examples/agent/agent/agent.go
+++ b/internal/examples/agent/agent/agent.go
@@ -10,16 +10,14 @@ import (
 	"crypto/x509/pkix"
 	"encoding/pem"
 	"fmt"
-	"math/rand"
 	"os"
 	"runtime"
 	"sort"
-	"time"
 
+	"github.com/google/uuid"
 	"github.com/knadh/koanf"
 	"github.com/knadh/koanf/parsers/yaml"
 	"github.com/knadh/koanf/providers/rawbytes"
-	"github.com/oklog/ulid/v2"
 
 	"github.com/open-telemetry/opamp-go/client"
 	"github.com/open-telemetry/opamp-go/client/types"
@@ -54,7 +52,7 @@ type Agent struct {
 
 	effectiveConfig string
 
-	instanceId ulid.ULID
+	instanceId uuid.UUID
 
 	agentDescription *protobufs.AgentDescription
 
@@ -82,7 +80,7 @@ func NewAgent(logger types.Logger, agentType string, agentVersion string) *Agent
 
 	agent.createAgentIdentity()
 	agent.logger.Debugf(context.Background(), "Agent starting, id=%v, type=%s, version=%s.",
-		agent.instanceId.String(), agentType, agentVersion)
+		agent.instanceId, agentType, agentVersion)
 
 	agent.loadLocalConfig()
 	if err := agent.connect(); err != nil {
@@ -107,7 +105,7 @@ func (agent *Agent) connect() error {
 	settings := types.StartSettings{
 		OpAMPServerURL: "wss://127.0.0.1:4320/v1/opamp",
 		TLSConfig:      tlsConfig,
-		InstanceUid:    agent.instanceId.String(),
+		InstanceUid:    types.InstanceUid(agent.instanceId),
 		Callbacks: types.CallbacksStruct{
 			OnConnectFunc: func(ctx context.Context) {
 				agent.logger.Debugf(ctx, "Connected to the server.")
@@ -167,8 +165,11 @@ func (agent *Agent) disconnect(ctx context.Context) {
 
 func (agent *Agent) createAgentIdentity() {
 	// Generate instance id.
-	entropy := ulid.Monotonic(rand.New(rand.NewSource(0)), 0)
-	agent.instanceId = ulid.MustNew(ulid.Timestamp(time.Now()), entropy)
+	uid, err := uuid.NewV7()
+	if err != nil {
+		panic(err)
+	}
+	agent.instanceId = uid
 
 	hostname, _ := os.Hostname()
 
@@ -209,10 +210,10 @@ func (agent *Agent) createAgentIdentity() {
 	}
 }
 
-func (agent *Agent) updateAgentIdentity(ctx context.Context, instanceId ulid.ULID) {
+func (agent *Agent) updateAgentIdentity(ctx context.Context, instanceId uuid.UUID) {
 	agent.logger.Debugf(ctx, "Agent identify is being changed from id=%v to id=%v",
-		agent.instanceId.String(),
-		instanceId.String())
+		agent.instanceId,
+		instanceId)
 	agent.instanceId = instanceId
 
 	if agent.metricReporter != nil {
@@ -459,11 +460,12 @@ func (agent *Agent) onMessage(ctx context.Context, msg *types.MessageData) {
 	}
 
 	if msg.AgentIdentification != nil {
-		newInstanceId, err := ulid.Parse(msg.AgentIdentification.NewInstanceUid)
+		uid, err := uuid.FromBytes(msg.AgentIdentification.NewInstanceUid)
 		if err != nil {
-			agent.logger.Errorf(ctx, err.Error())
+			agent.logger.Errorf(ctx, "invalid NewInstanceUid: %v", err)
+			return
 		}
-		agent.updateAgentIdentity(ctx, newInstanceId)
+		agent.updateAgentIdentity(ctx, uid)
 	}
 
 	if configChanged {
diff --git a/internal/examples/agent/agent/metricreporter.go b/internal/examples/agent/agent/metricreporter.go
index 754f063..0963985 100644
--- a/internal/examples/agent/agent/metricreporter.go
+++ b/internal/examples/agent/agent/metricreporter.go
@@ -9,7 +9,7 @@ import (
 	"os"
 	"time"
 
-	"github.com/oklog/ulid/v2"
+	"github.com/google/uuid"
 	"github.com/shirou/gopsutil/process"
 	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
@@ -46,7 +46,7 @@ func NewMetricReporter(
 	dest *protobufs.TelemetryConnectionSettings,
 	agentType string,
 	agentVersion string,
-	instanceId ulid.ULID,
+	instanceId uuid.UUID,
 ) (*MetricReporter, error) {
 
 	// Check the destination credentials to make sure they look like a valid OTLP/HTTP
diff --git a/internal/examples/go.mod b/internal/examples/go.mod
index 875097c..8734b6b 100644
--- a/internal/examples/go.mod
+++ b/internal/examples/go.mod
@@ -4,6 +4,7 @@ go 1.20
 
 require (
 	github.com/cenkalti/backoff/v4 v4.2.1
+	github.com/google/uuid v1.6.0
 	github.com/knadh/koanf v1.3.3
 	github.com/oklog/ulid/v2 v2.1.0
 	github.com/open-telemetry/opamp-go v0.1.0
diff --git a/internal/examples/go.sum b/internal/examples/go.sum
index 3fdd46a..d38b555 100644
--- a/internal/examples/go.sum
+++ b/internal/examples/go.sum
@@ -47,6 +47,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
 github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
diff --git a/internal/examples/server/data/agent.go b/internal/examples/server/data/agent.go
index a28295c..bbefaac 100644
--- a/internal/examples/server/data/agent.go
+++ b/internal/examples/server/data/agent.go
@@ -11,6 +11,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/google/uuid"
 	"google.golang.org/protobuf/proto"
 
 	"github.com/open-telemetry/opamp-go/internal/examples/server/certman"
@@ -23,7 +24,8 @@ type Agent struct {
 	// Some fields in this struct are exported so that we can render them in the UI.
 
 	// Agent's instance id. This is an immutable field.
-	InstanceId InstanceId
+	InstanceId    InstanceId
+	InstanceIdStr string
 
 	// Connection to the Agent.
 	conn types.Connection
@@ -60,7 +62,7 @@ func NewAgent(
 	instanceId InstanceId,
 	conn types.Connection,
 ) *Agent {
-	agent := &Agent{InstanceId: instanceId, conn: conn}
+	agent := &Agent{InstanceId: instanceId, InstanceIdStr: uuid.UUID(instanceId).String(), conn: conn}
 	tslConn, ok := conn.Connection().(*tls.Conn)
 	if ok {
 		// Client is using TLS connection.
@@ -84,6 +86,7 @@ func (agent *Agent) CloneReadonly() *Agent {
 	defer agent.mux.RUnlock()
 	return &Agent{
 		InstanceId:                  agent.InstanceId,
+		InstanceIdStr:               uuid.UUID(agent.InstanceId).String(),
 		Status:                      proto.Clone(agent.Status).(*protobufs.AgentToServer),
 		EffectiveConfig:             agent.EffectiveConfig,
 		CustomInstanceConfig:        agent.CustomInstanceConfig,
diff --git a/internal/examples/server/data/instanceid.go b/internal/examples/server/data/instanceid.go
index debb0ef..b40ae23 100644
--- a/internal/examples/server/data/instanceid.go
+++ b/internal/examples/server/data/instanceid.go
@@ -1,3 +1,5 @@
 package data
 
-type InstanceId string
+import "github.com/google/uuid"
+
+type InstanceId uuid.UUID
diff --git a/internal/examples/server/opampsrv/opampsrv.go b/internal/examples/server/opampsrv/opampsrv.go
index 84bb201..ea19d91 100644
--- a/internal/examples/server/opampsrv/opampsrv.go
+++ b/internal/examples/server/opampsrv/opampsrv.go
@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"os"
 
+	"github.com/oklog/ulid/v2"
 	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
 
 	"github.com/open-telemetry/opamp-go/internal"
@@ -83,13 +84,28 @@ func (srv *Server) onDisconnect(conn types.Connection) {
 }
 
 func (srv *Server) onMessage(ctx context.Context, conn types.Connection, msg *protobufs.AgentToServer) *protobufs.ServerToAgent {
-	instanceId := data.InstanceId(msg.InstanceUid)
-
-	agent := srv.agents.FindOrCreateAgent(instanceId, conn)
-
 	// Start building the response.
 	response := &protobufs.ServerToAgent{}
 
+	var instanceId data.InstanceId
+	if len(msg.InstanceUid) == 26 {
+		// This is an old-style ULID.
+		u, err := ulid.Parse(string(msg.InstanceUid))
+		if err != nil {
+			srv.logger.Errorf(ctx, "Cannot parse ULID %s: %v", string(msg.InstanceUid), err)
+			return response
+		}
+		instanceId = data.InstanceId(u.Bytes())
+	} else if len(msg.InstanceUid) == 16 {
+		// This is a 16 byte, new style UID.
+		instanceId = data.InstanceId(msg.InstanceUid)
+	} else {
+		srv.logger.Errorf(ctx, "Invalid length of msg.InstanceUid")
+		return response
+	}
+
+	agent := srv.agents.FindOrCreateAgent(instanceId, conn)
+
 	// Process the status report and continue building the response.
 	agent.UpdateStatus(msg, response)
 
diff --git a/internal/examples/server/uisrv/html/agent.html b/internal/examples/server/uisrv/html/agent.html
index af8f42d..3a6a9d8 100644
--- a/internal/examples/server/uisrv/html/agent.html
+++ b/internal/examples/server/uisrv/html/agent.html
@@ -21,7 +21,7 @@
             <h3>Agent</h3>
             <table border="1" style="border-collapse: collapse">
                 <tr>
-                    <td>Instance ID:</td><td>{{ .InstanceId }}</td>
+                    <td>Instance ID:</td><td>{{ .InstanceIdStr }}</td>
                 </tr>
                 {{if .Status.Health }}
                 <tr>
@@ -70,7 +70,7 @@
         <td valign="top">
             Additional Configuration:<br/>
             <form action="/save_config" method="post">
-            <input type="hidden" name="instanceid" value="{{ .InstanceId }}"/>
+            <input type="hidden" name="instanceid" value="{{ .InstanceIdStr }}"/>
             <textarea cols="40" rows="20" name="config">{{ .CustomInstanceConfig }}</textarea><br/>
              {{if .Status.RemoteConfigStatus }}
                 {{if .Status.RemoteConfigStatus.ErrorMessage }}
@@ -116,7 +116,7 @@
 
 <br/>
 <form action="/rotate_client_cert" method="post">
-    <input type="hidden" name="instanceid" value="{{ .InstanceId }}"/>
+    <input type="hidden" name="instanceid" value="{{ .InstanceIdStr }}"/>
     {{if .ClientCert}}
     <input type="submit" value="Rotate Client Certificate"/>
     {{else}}
diff --git a/internal/examples/server/uisrv/html/root.html b/internal/examples/server/uisrv/html/root.html
index 1121edf..ec7bb68 100644
--- a/internal/examples/server/uisrv/html/root.html
+++ b/internal/examples/server/uisrv/html/root.html
@@ -14,7 +14,7 @@
     </tr>
 {{ range . }}
     <tr>
-        <td><a href="agent?instanceid={{ .InstanceId }}">{{ .InstanceId }}</a></td>
+        <td><a href="agent?instanceid={{ .InstanceIdStr }}">{{ .InstanceIdStr }}</a></td>
     </tr>
 {{ end }}
 </table>
diff --git a/internal/examples/server/uisrv/ui.go b/internal/examples/server/uisrv/ui.go
index 4e11b7f..f766b03 100644
--- a/internal/examples/server/uisrv/ui.go
+++ b/internal/examples/server/uisrv/ui.go
@@ -8,6 +8,8 @@ import (
 	"text/template"
 	"time"
 
+	"github.com/google/uuid"
+
 	"github.com/open-telemetry/opamp-go/internal"
 	"github.com/open-telemetry/opamp-go/internal/examples/server/data"
 	"github.com/open-telemetry/opamp-go/protobufs"
@@ -63,7 +65,13 @@ func renderRoot(w http.ResponseWriter, r *http.Request) {
 }
 
 func renderAgent(w http.ResponseWriter, r *http.Request) {
-	agent := data.AllAgents.GetAgentReadonlyClone(data.InstanceId(r.URL.Query().Get("instanceid")))
+	uid, err := uuid.Parse(r.URL.Query().Get("instanceid"))
+	if err != nil {
+		w.WriteHeader(http.StatusNotFound)
+		return
+	}
+
+	agent := data.AllAgents.GetAgentReadonlyClone(data.InstanceId(uid))
 	if agent == nil {
 		w.WriteHeader(http.StatusNotFound)
 		return
@@ -77,7 +85,13 @@ func saveCustomConfigForInstance(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	instanceId := data.InstanceId(r.Form.Get("instanceid"))
+	uid, err := uuid.Parse(r.Form.Get("instanceid"))
+	if err != nil {
+		w.WriteHeader(http.StatusNotFound)
+		return
+	}
+
+	instanceId := data.InstanceId(uid)
 	agent := data.AllAgents.GetAgentReadonlyClone(instanceId)
 	if agent == nil {
 		w.WriteHeader(http.StatusNotFound)
@@ -103,7 +117,7 @@ func saveCustomConfigForInstance(w http.ResponseWriter, r *http.Request) {
 	case <-timer.C:
 	}
 
-	http.Redirect(w, r, "/agent?instanceid="+string(instanceId), http.StatusSeeOther)
+	http.Redirect(w, r, "/agent?instanceid="+uid.String(), http.StatusSeeOther)
 }
 
 func rotateInstanceClientCert(w http.ResponseWriter, r *http.Request) {
@@ -113,7 +127,13 @@ func rotateInstanceClientCert(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// Find the agent instance.
-	instanceId := data.InstanceId(r.Form.Get("instanceid"))
+	uid, err := uuid.Parse(r.Form.Get("instanceid"))
+	if err != nil {
+		w.WriteHeader(http.StatusNotFound)
+		return
+	}
+
+	instanceId := data.InstanceId(uid)
 	agent := data.AllAgents.GetAgentReadonlyClone(instanceId)
 	if agent == nil {
 		w.WriteHeader(http.StatusNotFound)
@@ -151,5 +171,5 @@ func rotateInstanceClientCert(w http.ResponseWriter, r *http.Request) {
 		logger.Printf("Time out waiting for agent %s to reconnect\n", instanceId)
 	}
 
-	http.Redirect(w, r, "/agent?instanceid="+string(instanceId), http.StatusSeeOther)
+	http.Redirect(w, r, "/agent?instanceid="+uid.String(), http.StatusSeeOther)
 }
diff --git a/internal/examples/supervisor/supervisor/supervisor.go b/internal/examples/supervisor/supervisor/supervisor.go
index c47d912..1d794b1 100644
--- a/internal/examples/supervisor/supervisor/supervisor.go
+++ b/internal/examples/supervisor/supervisor/supervisor.go
@@ -5,7 +5,6 @@ import (
 	"crypto/tls"
 	"errors"
 	"fmt"
-	"math/rand"
 	"os"
 	"runtime"
 	"sort"
@@ -13,11 +12,11 @@ import (
 	"time"
 
 	"github.com/cenkalti/backoff/v4"
+	"github.com/google/uuid"
 	"github.com/knadh/koanf"
 	"github.com/knadh/koanf/parsers/yaml"
 	"github.com/knadh/koanf/providers/file"
 	"github.com/knadh/koanf/providers/rawbytes"
-	"github.com/oklog/ulid/v2"
 
 	"github.com/open-telemetry/opamp-go/client"
 	"github.com/open-telemetry/opamp-go/client/types"
@@ -54,7 +53,7 @@ type Supervisor struct {
 	agentVersion string
 
 	// Agent's instance id.
-	instanceId ulid.ULID
+	instanceId uuid.UUID
 
 	// A config section to be added to the Collector's config to fetch its own metrics.
 	// TODO: store this persistently so that when starting we can compose the effective
@@ -91,7 +90,7 @@ func NewSupervisor(logger types.Logger) (*Supervisor, error) {
 
 	s.createInstanceId()
 	logger.Debugf(context.Background(), "Supervisor starting, id=%v, type=%s, version=%s.",
-		s.instanceId.String(), agentType, agentVersion)
+		s.instanceId, agentType, agentVersion)
 
 	s.loadAgentEffectiveConfig()
 
@@ -138,7 +137,7 @@ func (s *Supervisor) startOpAMP() error {
 		TLSConfig: &tls.Config{
 			InsecureSkipVerify: true,
 		},
-		InstanceUid: s.instanceId.String(),
+		InstanceUid: types.InstanceUid(s.instanceId),
 		Callbacks: types.CallbacksStruct{
 			OnConnectFunc: func(ctx context.Context) {
 				s.logger.Debugf(ctx, "Connected to the server.")
@@ -184,8 +183,12 @@ func (s *Supervisor) startOpAMP() error {
 
 func (s *Supervisor) createInstanceId() {
 	// Generate instance id.
-	entropy := ulid.Monotonic(rand.New(rand.NewSource(0)), 0)
-	s.instanceId = ulid.MustNew(ulid.Timestamp(time.Now()), entropy)
+
+	uid, err := uuid.NewV7()
+	if err != nil {
+		panic(err)
+	}
+	s.instanceId = uid
 
 	// TODO: set instanceId in the Collector config.
 }
@@ -573,9 +576,10 @@ func (s *Supervisor) onMessage(ctx context.Context, msg *types.MessageData) {
 	}
 
 	if msg.AgentIdentification != nil {
-		newInstanceId, err := ulid.Parse(msg.AgentIdentification.NewInstanceUid)
+		newInstanceId, err := uuid.FromBytes(msg.AgentIdentification.NewInstanceUid)
 		if err != nil {
-			s.logger.Errorf(ctx, err.Error())
+			s.logger.Errorf(ctx, "invalid NewInstanceUid: %v", err)
+			return
 		}
 
 		s.logger.Debugf(ctx, "Agent identify is being changed from id=%v to id=%v",
diff --git a/internal/opamp-spec b/internal/opamp-spec
index 0a32d93..f1f80ae 160000
--- a/internal/opamp-spec
+++ b/internal/opamp-spec
@@ -1,1 +1,1 @@
-Subproject commit 0a32d9365032a4d74a878656414c54c5c274623d
+Subproject commit f1f80aeb72df7e97e0bdc01809436a4d36ea5c73
diff --git a/protobufs/anyvalue.pb.go b/protobufs/anyvalue.pb.go
index 3f33ba5..697b805 100644
--- a/protobufs/anyvalue.pb.go
+++ b/protobufs/anyvalue.pb.go
@@ -20,8 +20,8 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.31.0
-// 	protoc        v4.24.3
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.17.3
 // source: anyvalue.proto
 
 package protobufs
@@ -52,7 +52,6 @@ type AnyValue struct {
 	// in which case this AnyValue is considered to be "null".
 	//
 	// Types that are assignable to Value:
-	//
 	//	*AnyValue_StringValue
 	//	*AnyValue_BoolValue
 	//	*AnyValue_IntValue
diff --git a/protobufs/opamp.pb.go b/protobufs/opamp.pb.go
index 5c908c1..6e6cd1b 100644
--- a/protobufs/opamp.pb.go
+++ b/protobufs/opamp.pb.go
@@ -16,8 +16,8 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.31.0
-// 	protoc        v4.24.3
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.17.3
 // source: opamp.proto
 
 package protobufs
@@ -602,8 +602,8 @@ type AgentToServer struct {
 
 	// Globally unique identifier of the running instance of the Agent. SHOULD remain
 	// unchanged for the lifetime of the Agent process.
-	// Recommended format: https://github.com/ulid/spec
-	InstanceUid string `protobuf:"bytes,1,opt,name=instance_uid,json=instanceUid,proto3" json:"instance_uid,omitempty"`
+	// MUST be 16 bytes long and SHOULD be generated using the UUID v7 spec.
+	InstanceUid []byte `protobuf:"bytes,1,opt,name=instance_uid,json=instanceUid,proto3" json:"instance_uid,omitempty"`
 	// The sequence number is incremented by 1 for every AgentToServer sent
 	// by the Agent. This allows the Server to detect that it missed a message when
 	// it notices that the sequence_num is not exactly by 1 greater than the previously
@@ -690,11 +690,11 @@ func (*AgentToServer) Descriptor() ([]byte, []int) {
 	return file_opamp_proto_rawDescGZIP(), []int{0}
 }
 
-func (x *AgentToServer) GetInstanceUid() string {
+func (x *AgentToServer) GetInstanceUid() []byte {
 	if x != nil {
 		return x.InstanceUid
 	}
-	return ""
+	return nil
 }
 
 func (x *AgentToServer) GetSequenceNum() uint64 {
@@ -991,7 +991,7 @@ type ServerToAgent struct {
 
 	// Agent instance uid. MUST match the instance_uid field in AgentToServer message.
 	// Used for multiplexing messages from/to multiple agents using one message stream.
-	InstanceUid string `protobuf:"bytes,1,opt,name=instance_uid,json=instanceUid,proto3" json:"instance_uid,omitempty"`
+	InstanceUid []byte `protobuf:"bytes,1,opt,name=instance_uid,json=instanceUid,proto3" json:"instance_uid,omitempty"`
 	// error_response is set if the Server wants to indicate that something went wrong
 	// during processing of an AgentToServer message. If error_response is set then
 	// all other fields below must be unset and vice versa, if any of the fields below is
@@ -1065,11 +1065,11 @@ func (*ServerToAgent) Descriptor() ([]byte, []int) {
 	return file_opamp_proto_rawDescGZIP(), []int{5}
 }
 
-func (x *ServerToAgent) GetInstanceUid() string {
+func (x *ServerToAgent) GetInstanceUid() []byte {
 	if x != nil {
 		return x.InstanceUid
 	}
-	return ""
+	return nil
 }
 
 func (x *ServerToAgent) GetErrorResponse() *ServerErrorResponse {
@@ -1951,7 +1951,6 @@ type ServerErrorResponse struct {
 	// Error message in the string form, typically human readable.
 	ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"`
 	// Types that are assignable to Details:
-	//
 	//	*ServerErrorResponse_RetryInfo
 	Details isServerErrorResponse_Details `protobuf_oneof:"Details"`
 }
@@ -2135,15 +2134,15 @@ type AgentDescription struct {
 	//
 	// For standalone running Agents (such as OpenTelemetry Collector) the following
 	// attributes SHOULD be specified:
-	//   - service.name should be set to a reverse FQDN that uniquely identifies the
-	//     Agent type, e.g. "io.opentelemetry.collector"
-	//   - service.namespace if it is used in the environment where the Agent runs.
-	//   - service.version should be set to version number of the Agent build.
-	//   - service.instance.id should be set. It may be be set equal to the Agent's
-	//     instance uid (equal to ServerToAgent.instance_uid field) or any other value
-	//     that uniquely identifies the Agent in combination with other attributes.
-	//   - any other attributes that are necessary for uniquely identifying the Agent's
-	//     own telemetry.
+	// - service.name should be set to a reverse FQDN that uniquely identifies the
+	//   Agent type, e.g. "io.opentelemetry.collector"
+	// - service.namespace if it is used in the environment where the Agent runs.
+	// - service.version should be set to version number of the Agent build.
+	// - service.instance.id should be set. It may be be set equal to the Agent's
+	//   instance uid (equal to ServerToAgent.instance_uid field) or any other value
+	//   that uniquely identifies the Agent in combination with other attributes.
+	// - any other attributes that are necessary for uniquely identifying the Agent's
+	//   own telemetry.
 	//
 	// The Agent SHOULD also include these attributes in the Resource of its own
 	// telemetry. The combination of identifying attributes SHOULD be sufficient to
@@ -2153,13 +2152,13 @@ type AgentDescription struct {
 	// Attributes that do not necessarily identify the Agent but help describe
 	// where it runs.
 	// The following attributes SHOULD be included:
-	//   - os.type, os.version - to describe where the Agent runs.
-	//   - host.* to describe the host the Agent runs on.
-	//   - cloud.* to describe the cloud where the host is located.
-	//   - any other relevant Resource attributes that describe this Agent and the
-	//     environment it runs in.
-	//   - any user-defined attributes that the end user would like to associate
-	//     with this Agent.
+	// - os.type, os.version - to describe where the Agent runs.
+	// - host.* to describe the host the Agent runs on.
+	// - cloud.* to describe the cloud where the host is located.
+	// - any other relevant Resource attributes that describe this Agent and the
+	//   environment it runs in.
+	// - any user-defined attributes that the end user would like to associate
+	//   with this Agent.
 	NonIdentifyingAttributes []*KeyValue `protobuf:"bytes,2,rep,name=non_identifying_attributes,json=nonIdentifyingAttributes,proto3" json:"non_identifying_attributes,omitempty"`
 }
 
@@ -2646,7 +2645,8 @@ type AgentIdentification struct {
 
 	// When new_instance_uid is set, Agent MUST update instance_uid
 	// to the value provided and use it for all further communication.
-	NewInstanceUid string `protobuf:"bytes,1,opt,name=new_instance_uid,json=newInstanceUid,proto3" json:"new_instance_uid,omitempty"`
+	// MUST be 16 bytes long and SHOULD be generated using the UUID v7 spec.
+	NewInstanceUid []byte `protobuf:"bytes,1,opt,name=new_instance_uid,json=newInstanceUid,proto3" json:"new_instance_uid,omitempty"`
 }
 
 func (x *AgentIdentification) Reset() {
@@ -2681,11 +2681,11 @@ func (*AgentIdentification) Descriptor() ([]byte, []int) {
 	return file_opamp_proto_rawDescGZIP(), []int{25}
 }
 
-func (x *AgentIdentification) GetNewInstanceUid() string {
+func (x *AgentIdentification) GetNewInstanceUid() []byte {
 	if x != nil {
 		return x.NewInstanceUid
 	}
-	return ""
+	return nil
 }
 
 type AgentRemoteConfig struct {
@@ -2999,7 +2999,7 @@ var file_opamp_proto_rawDesc = []byte{
 	0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbc, 0x06, 0x0a, 0x0d, 0x41,
 	0x67, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c,
 	0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x55, 0x69, 0x64, 0x12,
+	0x28, 0x0c, 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x55, 0x69, 0x64, 0x12,
 	0x21, 0x0a, 0x0c, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x18,
 	0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4e,
 	0x75, 0x6d, 0x12, 0x4a, 0x0a, 0x11, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x63,
@@ -3067,7 +3067,7 @@ var file_opamp_proto_rawDesc = []byte{
 	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x73, 0x72, 0x18, 0x01, 0x20,
 	0x01, 0x28, 0x0c, 0x52, 0x03, 0x63, 0x73, 0x72, 0x22, 0xc8, 0x05, 0x0a, 0x0d, 0x53, 0x65, 0x72,
 	0x76, 0x65, 0x72, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e,
-	0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
 	0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x55, 0x69, 0x64, 0x12, 0x47, 0x0a,
 	0x0e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18,
 	0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72,
@@ -3350,7 +3350,7 @@ var file_opamp_proto_rawDesc = []byte{
 	0x73, 0x61, 0x67, 0x65, 0x22, 0x3f, 0x0a, 0x13, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x65,
 	0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6e,
 	0x65, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x75, 0x69, 0x64, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x65, 0x77, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e,
+	0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x6e, 0x65, 0x77, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e,
 	0x63, 0x65, 0x55, 0x69, 0x64, 0x22, 0x69, 0x0a, 0x11, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65,
 	0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x33, 0x0a, 0x06, 0x63, 0x6f,
 	0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x61,
diff --git a/server/serverimpl.go b/server/serverimpl.go
index 2f5965a..815c052 100644
--- a/server/serverimpl.go
+++ b/server/serverimpl.go
@@ -256,7 +256,7 @@ func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Co
 
 		if connectionCallbacks != nil {
 			response := connectionCallbacks.OnMessage(msgContext, agentConn, &request)
-			if response.InstanceUid == "" {
+			if len(response.InstanceUid) == 0 {
 				response.InstanceUid = request.InstanceUid
 			}
 			if !sentCustomCapabilities {
@@ -349,7 +349,7 @@ func (s *server) handlePlainHTTPRequest(req *http.Request, w http.ResponseWriter
 	response := connectionCallbacks.OnMessage(req.Context(), agentConn, &request)
 
 	// Set the InstanceUid if it is not set by the callback.
-	if response.InstanceUid == "" {
+	if len(response.InstanceUid) == 0 {
 		response.InstanceUid = request.InstanceUid
 	}
 


Test patch:
diff --git a/client/clientimpl_test.go b/client/clientimpl_test.go
index 994ad4b..288c754 100644
--- a/client/clientimpl_test.go
+++ b/client/clientimpl_test.go
@@ -5,7 +5,6 @@ import (
 	"crypto/tls"
 	"crypto/x509"
 	"fmt"
-	"math/rand"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -13,7 +12,7 @@ import (
 	"testing"
 	"time"
 
-	ulid "github.com/oklog/ulid/v2"
+	"github.com/google/uuid"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"google.golang.org/protobuf/proto"
@@ -94,10 +93,17 @@ func eventually(t *testing.T, f func() bool) {
 	assert.Eventually(t, f, 5*time.Second, 10*time.Millisecond)
 }
 
+func newInstanceUid(t *testing.T) types.InstanceUid {
+	uid, err := uuid.NewV7()
+	require.NoError(t, err)
+	b, err := uid.MarshalBinary()
+	require.NoError(t, err)
+	return types.InstanceUid(b)
+}
+
 func prepareSettings(t *testing.T, settings *types.StartSettings, c OpAMPClient) {
 	// Autogenerate instance id.
-	entropy := ulid.Monotonic(rand.New(rand.NewSource(99)), 0)
-	settings.InstanceUid = ulid.MustNew(ulid.Timestamp(time.Now()), entropy).String()
+	settings.InstanceUid = newInstanceUid(t)
 
 	// Make sure correct URL scheme is used, based on the type of the OpAMP client.
 	u, err := url.Parse(settings.OpAMPServerURL)
@@ -150,7 +156,7 @@ func TestInvalidInstanceId(t *testing.T) {
 	testClients(t, func(t *testing.T, client OpAMPClient) {
 		settings := createNoServerSettings()
 		prepareClient(t, &settings, client)
-		settings.InstanceUid = "invalidid"
+		settings.InstanceUid = types.InstanceUid{}
 
 		err := client.Start(context.Background(), settings)
 		assert.Error(t, err)
@@ -624,9 +630,7 @@ func TestAgentIdentification(t *testing.T) {
 	testClients(t, func(t *testing.T, client OpAMPClient) {
 		// Start a server.
 		srv := internal.StartMockServer(t)
-		newInstanceUid := ulid.MustNew(
-			ulid.Timestamp(time.Now()), ulid.Monotonic(rand.New(rand.NewSource(0)), 0),
-		)
+		newInstanceUid := newInstanceUid(t)
 		var rcvAgentInstanceUid atomic.Value
 		var sentInvalidId atomic.Bool
 		srv.OnMessage = func(msg *protobufs.AgentToServer) *protobufs.ServerToAgent {
@@ -636,7 +640,7 @@ func TestAgentIdentification(t *testing.T) {
 					InstanceUid: msg.InstanceUid,
 					AgentIdentification: &protobufs.AgentIdentification{
 						// If we sent the invalid one first, send a valid one now
-						NewInstanceUid: newInstanceUid.String(),
+						NewInstanceUid: newInstanceUid[:],
 					},
 				}
 			}
@@ -645,7 +649,7 @@ func TestAgentIdentification(t *testing.T) {
 				InstanceUid: msg.InstanceUid,
 				AgentIdentification: &protobufs.AgentIdentification{
 					// Start by sending an invalid id forcing an error.
-					NewInstanceUid: "",
+					NewInstanceUid: nil,
 				},
 			}
 		}
@@ -662,11 +666,11 @@ func TestAgentIdentification(t *testing.T) {
 		eventually(
 			t,
 			func() bool {
-				instanceUid, ok := rcvAgentInstanceUid.Load().(string)
+				instanceUid, ok := rcvAgentInstanceUid.Load().([]byte)
 				if !ok {
 					return false
 				}
-				return instanceUid == oldInstanceUid
+				return types.InstanceUid(instanceUid) == oldInstanceUid
 			},
 		)
 
@@ -677,11 +681,11 @@ func TestAgentIdentification(t *testing.T) {
 		eventually(
 			t,
 			func() bool {
-				instanceUid, ok := rcvAgentInstanceUid.Load().(string)
+				instanceUid, ok := rcvAgentInstanceUid.Load().([]byte)
 				if !ok {
 					return false
 				}
-				return instanceUid == oldInstanceUid
+				return types.InstanceUid(instanceUid) == oldInstanceUid
 			},
 		)
 
@@ -693,11 +697,11 @@ func TestAgentIdentification(t *testing.T) {
 		eventually(
 			t,
 			func() bool {
-				instanceUid, ok := rcvAgentInstanceUid.Load().(string)
+				instanceUid, ok := rcvAgentInstanceUid.Load().([]byte)
 				if !ok {
 					return false
 				}
-				return instanceUid == newInstanceUid.String()
+				return types.InstanceUid(instanceUid) == newInstanceUid
 			},
 		)
 
diff --git a/client/internal/wsreceiver_test.go b/client/internal/wsreceiver_test.go
index c929f8f..6b2cca7 100644
--- a/client/internal/wsreceiver_test.go
+++ b/client/internal/wsreceiver_test.go
@@ -157,7 +157,7 @@ func TestDecodeMessage(t *testing.T) {
 	msgsToTest := []*protobufs.ServerToAgent{
 		{}, // Empty message
 		{
-			InstanceUid: "abcd",
+			InstanceUid: []byte("0123456789123456"),
 		},
 	}
 
diff --git a/server/serverimpl_test.go b/server/serverimpl_test.go
index f17263f..55ad5ba 100644
--- a/server/serverimpl_test.go
+++ b/server/serverimpl_test.go
@@ -247,6 +247,8 @@ func TestDisconnectWSConnection(t *testing.T) {
 	})
 }
 
+var testInstanceUid = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}
+
 func TestServerReceiveSendMessage(t *testing.T) {
 	var rcvMsg atomic.Value
 	callbacks := CallbacksStruct{
@@ -282,7 +284,7 @@ func TestServerReceiveSendMessage(t *testing.T) {
 
 	// Send a message to the Server.
 	sendMsg := protobufs.AgentToServer{
-		InstanceUid: "12345678",
+		InstanceUid: testInstanceUid,
 	}
 	bytes, err := proto.Marshal(&sendMsg)
 	require.NoError(t, err)
@@ -364,7 +366,7 @@ func TestServerReceiveSendMessageWithCompression(t *testing.T) {
 
 			// Send a message to the Server.
 			sendMsg := protobufs.AgentToServer{
-				InstanceUid: "10000000",
+				InstanceUid: testInstanceUid,
 				EffectiveConfig: &protobufs.EffectiveConfig{
 					ConfigMap: &protobufs.AgentConfigMap{
 						ConfigMap: map[string]*protobufs.AgentConfigFile{
@@ -452,7 +454,7 @@ func TestServerReceiveSendMessagePlainHTTP(t *testing.T) {
 
 	// Send a message to the Server.
 	sendMsg := protobufs.AgentToServer{
-		InstanceUid: "12345678",
+		InstanceUid: testInstanceUid,
 	}
 	b, err := proto.Marshal(&sendMsg)
 	require.NoError(t, err)
@@ -584,7 +586,7 @@ func TestServerAttachSendMessagePlainHTTP(t *testing.T) {
 
 	// Send a message to the Server.
 	sendMsg := protobufs.AgentToServer{
-		InstanceUid: "12345678",
+		InstanceUid: testInstanceUid,
 	}
 	b, err := proto.Marshal(&sendMsg)
 	require.NoError(t, err)
@@ -653,7 +655,7 @@ func TestServerHonoursClientRequestContentEncoding(t *testing.T) {
 
 	// Send a message to the Server.
 	sendMsg := protobufs.AgentToServer{
-		InstanceUid: "12345678",
+		InstanceUid: testInstanceUid,
 	}
 	b, err := proto.Marshal(&sendMsg)
 	require.NoError(t, err)
@@ -731,7 +733,7 @@ func TestServerHonoursAcceptEncoding(t *testing.T) {
 
 	// Send a message to the Server.
 	sendMsg := protobufs.AgentToServer{
-		InstanceUid: "12345678",
+		InstanceUid: testInstanceUid,
 	}
 	b, err := proto.Marshal(&sendMsg)
 	require.NoError(t, err)
@@ -775,7 +777,7 @@ func TestDecodeMessage(t *testing.T) {
 	msgsToTest := []*protobufs.AgentToServer{
 		{}, // Empty message
 		{
-			InstanceUid: "abcd",
+			InstanceUid: testInstanceUid,
 			SequenceNum: 123,
 		},
 	}
@@ -934,7 +936,7 @@ func TestServerCallsHTTPMiddlewareOverHTTP(t *testing.T) {
 	}()
 
 	// Send an AgentToServer message to the Server
-	sendMsg1 := protobufs.AgentToServer{InstanceUid: "01BX5ZZKBKACTAV9WEVGEMMVS1"}
+	sendMsg1 := protobufs.AgentToServer{InstanceUid: []byte("0123456789123456")}
 	serializedProtoBytes1, err := proto.Marshal(&sendMsg1)
 	require.NoError(t, err)
 	_, err = http.Post(
@@ -945,7 +947,7 @@ func TestServerCallsHTTPMiddlewareOverHTTP(t *testing.T) {
 	require.NoError(t, err)
 
 	// Send another AgentToServer message to the Server
-	sendMsg2 := protobufs.AgentToServer{InstanceUid: "01BX5ZZKBKACTAV9WEVGEMMVRZ"}
+	sendMsg2 := protobufs.AgentToServer{InstanceUid: []byte("0123456789000000")}
 	serializedProtoBytes2, err := proto.Marshal(&sendMsg2)
 	require.NoError(t, err)
 	_, err = http.Post(