diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,13 @@
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
+## 0.3.1.0 - 2020-04-21
+
+### Added
+
+ - Attach user-defined labels to agent
+ - Attach Nix protocol versions to logs and agent
+
 ## 0.3.0.0 - 2020-03-07
 
 ### Added
@@ -34,3 +41,4 @@
 ## 0.2.0.0
 
 Start of changelog
+
diff --git a/hercules-ci-api-agent.cabal b/hercules-ci-api-agent.cabal
--- a/hercules-ci-api-agent.cabal
+++ b/hercules-ci-api-agent.cabal
@@ -1,7 +1,7 @@
 cabal-version: 1.12
 
 name:           hercules-ci-api-agent
-version:        0.3.0.0
+version:        0.3.1.0
 synopsis:       API definition for Hercules CI Agent to talk to hercules-ci.com or Hercules CI Enterprise
 category:       API, CI, Testing, DevOps, Nix
 homepage:       https://github.com/hercules-ci/hercules-ci-agent#readme
@@ -51,6 +51,7 @@
       Hercules.API.Agent
       Hercules.API.Logs
       Hercules.API.Logs.LogEntry
+      Hercules.API.Logs.LogHello
       Hercules.API.Logs.LogMessage
       Hercules.API.TaskStatus
       Hercules.API.Task
diff --git a/src/Hercules/API/Agent/LifeCycle/AgentInfo.hs b/src/Hercules/API/Agent/LifeCycle/AgentInfo.hs
--- a/src/Hercules/API/Agent/LifeCycle/AgentInfo.hs
+++ b/src/Hercules/API/Agent/LifeCycle/AgentInfo.hs
@@ -13,12 +13,15 @@
   { hostname :: Text,
     agentVersion :: Text,
     nixVersion :: Text,
+    nixClientProtocolVersion :: Int,
+    nixDaemonProtocolVersion :: Int,
     platforms :: [Text],
     systemFeatures :: [Text],
     cachixPushCaches :: [Text],
     pushCaches :: [Text],
     substituters :: [Text],
-    concurrentTasks :: Int
+    concurrentTasks :: Int,
+    labels :: Map Text A.Value
   }
   deriving (Generic, Show, Eq, NFData, ToJSON)
 
@@ -30,3 +33,6 @@
         v
           & _Object . at "concurrentTasks" %~ (<|> Just (A.Number 2))
           & _Object . at "pushCaches" %~ (<|> Just (A.Array mempty))
+          & _Object . at "nixClientProtocolVersion" %~ (<|> Just (A.Number 0))
+          & _Object . at "nixDaemonProtocolVersion" %~ (<|> Just (A.Number 0))
+          & _Object . at "labels" %~ (<|> Just (A.Object mempty))
diff --git a/src/Hercules/API/Logs/LogHello.hs b/src/Hercules/API/Logs/LogHello.hs
new file mode 100644
--- /dev/null
+++ b/src/Hercules/API/Logs/LogHello.hs
@@ -0,0 +1,8 @@
+{-# LANGUAGE DeriveAnyClass #-}
+
+module Hercules.API.Logs.LogHello where
+
+import Hercules.API.Prelude
+
+data LogHello = LogHello {storeProtocolVersion :: !Int, clientProtocolVersion :: !Int}
+  deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON)
diff --git a/src/Hercules/API/Logs/LogMessage.hs b/src/Hercules/API/Logs/LogMessage.hs
--- a/src/Hercules/API/Logs/LogMessage.hs
+++ b/src/Hercules/API/Logs/LogMessage.hs
@@ -5,9 +5,11 @@
 import Data.Vector
 import Data.Word (Word64)
 import Hercules.API.Logs.LogEntry
+import Hercules.API.Logs.LogHello (LogHello)
 import Hercules.API.Prelude
 
 data LogMessage
-  = LogEntries (Vector LogEntry)
+  = Hello LogHello
+  | LogEntries (Vector LogEntry)
   | End {i :: !Word64, ms :: !Word64}
   deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON)
diff --git a/test/Hercules/API/Agent/LifeCycle/AgentInfoSpec.hs b/test/Hercules/API/Agent/LifeCycle/AgentInfoSpec.hs
--- a/test/Hercules/API/Agent/LifeCycle/AgentInfoSpec.hs
+++ b/test/Hercules/API/Agent/LifeCycle/AgentInfoSpec.hs
@@ -1,25 +1,31 @@
 module Hercules.API.Agent.LifeCycle.AgentInfoSpec where
 
-import Data.Aeson (eitherDecode, encode)
+import Data.Aeson (eitherDecode, encode, (.=))
+import qualified Data.Aeson as A
 import qualified Data.ByteString.Lazy as BL
+import qualified Data.Map as M
+import Data.Text (Text)
 import Hercules.API.Agent.LifeCycle.AgentInfo
 import Test.Hspec
 import Prelude
 
-objectV1, objectV2, objectV3 :: AgentInfo
-jsonV1, jsonV2, jsonV3 :: BL.ByteString
+objectV1, objectV2, objectV3, objectV4 :: AgentInfo
+jsonV1, jsonV2, jsonV3, jsonV4 :: BL.ByteString
 jsonV1 = "{\"platforms\":[\"riscv-sel4\"],\"hostname\":\"a\",\"nixVersion\":\"nv\",\"agentVersion\":\"v\",\"cachixPushCaches\":[\"c1\",\"c2\"],\"systemFeatures\":[\"f1\",\"f2\"],\"substituters\":[\"s1\",\"s2\"]}"
 objectV1 =
   AgentInfo
     { hostname = "a",
       agentVersion = "v",
       nixVersion = "nv",
+      nixClientProtocolVersion = 0,
+      nixDaemonProtocolVersion = 0,
       platforms = ["riscv-sel4"],
       systemFeatures = ["f1", "f2"],
       cachixPushCaches = ["c1", "c2"],
       pushCaches = [],
       substituters = ["s1", "s2"],
-      concurrentTasks = 2 -- the hardcoded default
+      concurrentTasks = 2, -- the hardcoded default
+      labels = mempty
     }
 
 jsonV2 = "{\"platforms\":[\"riscv-sel4\"],\"hostname\":\"a\",\"nixVersion\":\"nv\",\"agentVersion\":\"v\",\"cachixPushCaches\":[\"c1\",\"c2\"],\"systemFeatures\":[\"f1\",\"f2\"],\"substituters\":[\"s1\",\"s2\"], \"concurrentTasks\": 19}"
@@ -29,12 +35,15 @@
     { hostname = "a",
       agentVersion = "v",
       nixVersion = "nv",
+      nixClientProtocolVersion = 0,
+      nixDaemonProtocolVersion = 0,
       platforms = ["riscv-sel4"],
       systemFeatures = ["f1", "f2"],
       cachixPushCaches = ["c1", "c2"],
       pushCaches = [],
       substituters = ["s1", "s2"],
-      concurrentTasks = 19 -- something else
+      concurrentTasks = 19,
+      labels = mempty
     }
 
 jsonV3 = "{\"platforms\":[\"riscv-sel4\"],\"hostname\":\"a\",\"nixVersion\":\"nv\",\"agentVersion\":\"v\",\"cachixPushCaches\":[\"c1\",\"c2\"],\"pushCaches\":[\"pc1\",\"pc2\"],\"systemFeatures\":[\"f1\",\"f2\"],\"substituters\":[\"s1\",\"s2\"], \"concurrentTasks\": 19}"
@@ -44,25 +53,58 @@
     { hostname = "a",
       agentVersion = "v",
       nixVersion = "nv",
+      nixClientProtocolVersion = 0,
+      nixDaemonProtocolVersion = 0,
       platforms = ["riscv-sel4"],
       systemFeatures = ["f1", "f2"],
       cachixPushCaches = ["c1", "c2"],
       pushCaches = ["pc1", "pc2"],
       substituters = ["s1", "s2"],
-      concurrentTasks = 19 -- something else
+      concurrentTasks = 19,
+      labels = mempty
     }
 
+jsonV4 = "{\"platforms\":[\"riscv-sel4\"],\"hostname\":\"a\",\"nixVersion\":\"nv\",\"concurrentTasks\":19,\"agentVersion\":\"v\",\"labels\":{\"an object\":{\"hi\":42},\"drinks menu\":[\"goat milk\",\"mate\",\"matcha\"],\"nix is cool\":true,\"revision\":\"deadbeef\"},\"cachixPushCaches\":[\"c1\",\"c2\"],\"pushCaches\":[\"pc1\",\"pc2\"],\"nixDaemonProtocolVersion\":274,\"systemFeatures\":[\"f1\",\"f2\"],\"nixClientProtocolVersion\":270,\"substituters\":[\"s1\",\"s2\"]}"
+
+objectV4 =
+  AgentInfo
+    { hostname = "a",
+      agentVersion = "v",
+      nixVersion = "nv",
+      nixClientProtocolVersion = 270,
+      nixDaemonProtocolVersion = 274,
+      platforms = ["riscv-sel4"],
+      systemFeatures = ["f1", "f2"],
+      cachixPushCaches = ["c1", "c2"],
+      pushCaches = ["pc1", "pc2"],
+      substituters = ["s1", "s2"],
+      concurrentTasks = 19,
+      labels =
+        M.fromList
+          [ "nix is cool" .= True,
+            "revision" .= ("deadbeef" :: Text),
+            "an object"
+              .= A.object
+                [ "hi" .= (42 :: Int)
+                ],
+            "drinks menu" .= ["goat milk", "mate", "matcha" :: Text]
+          ]
+    }
+
 spec :: Spec
 spec = describe "AgentInfo" $ do
   describe "FromJSON" $ do
     it "parses v1 correctly" $ eitherDecode jsonV1 `shouldBe` Right objectV1
     it "parses v2 correctly" $ eitherDecode jsonV2 `shouldBe` Right objectV2
     it "parses v3 correctly" $ eitherDecode jsonV3 `shouldBe` Right objectV3
-  describe "ToJSON" $
+    it "parses v4 correctly" $ eitherDecode jsonV4 `shouldBe` Right objectV4
+  describe "ToJSON" $ do
     it "encodes v2 correctly" $
       eitherDecode (encode objectV2)
         `shouldBe` Right objectV2
-  describe "ToJSON" $
     it "encodes v3 correctly" $
+      eitherDecode (encode objectV3)
+        `shouldBe` Right objectV3
+    it "encodes v4 correctly" $
       eitherDecode (encode objectV3)
         `shouldBe` Right objectV3
