diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 778814576534e00e449e40e3be69ba7685e4044e..dcd586c641bf1a82c0506b93e7cff9c077910199 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,7 +8,9 @@ ksp = "2.1.10-1.0.31"
 kotlinx-serialization = "1.8.0"
 ktor = "3.1.1"
 cache4k = "0.14.0"
-statelyConcurrentCollections = "2.1.0"
+# Pinned. Do not upgrade from 2.0.5 until Koin migrates to using a newer version of Stately >= 2.1.0
+# See for more info: https://github.com/sqldelight/sqldelight/issues/4357#issuecomment-1839905700
+stately = "2.0.5"
 slf4j = "2.0.17"
 kotlinx-io = "0.7.0"
 
@@ -18,8 +20,8 @@ datetime = "0.6.2"
 sandwich = "2.1.0"
 arrow = "2.0.0"
 # Main Koin version - for core, android deps
-koin = "4.0.2"
-koinTest = "4.0.2"
+koin = "4.1.0-Beta5"
+koinTest = "4.1.0-Beta5"
 gradleBuildConfigPlugin = "5.5.4"
 akkurate = "0.11.0"
 exposed = "0.56.0"
@@ -54,12 +56,14 @@ compileSdk = "34"
 ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
 ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" }
 ktor-client-serialization = { group = "io.ktor", name = "ktor-client-serialization", version.ref = "ktor" }
+ktor-client-encoding = { group = "io.ktor", name = "ktor-client-encoding", version.ref = "ktor" }
 ktor-client-java = { group = "io.ktor", name = "ktor-client-java", version.ref = "ktor" }
 ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version.ref = "ktor" }
 ktor-test = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" }
 cache4k = { group = "io.github.reactivecircus.cache4k", name = "cache4k", version.ref = "cache4k" }
-statelyConcurrentCollections = { group = "co.touchlab", name = "stately-concurrent-collections", version.ref = "statelyConcurrentCollections" }
-slf4j = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
+statelyCommon = { module = "co.touchlab:stately-common", version.ref = "stately" }
+statelyIsolate = { module = "co.touchlab:stately-isolate", version.ref = "stately" }
+statelyIsoCollections = { module = "co.touchlab:stately-iso-collections", version.ref = "stately" }
 slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
 
 
@@ -119,7 +123,7 @@ klaxon = { module = "com.beust:klaxon", version.ref = "klaxon" }
 konsist = { module = "com.lemonappdev:konsist", version.ref = "konsist" }
 
 [bundles]
-ktor = ["ktor-client-logging", "ktor-client-serialization", "kotlinx-serialization-json"]
+ktor = ["ktor-client-logging", "ktor-client-serialization", "ktor-client-encoding", "kotlinx-serialization-json"]
 sandwich = ["sandwich", "sandwich-ktor", "sandwich-ktorfit"]
 
 [plugins]
diff --git a/temerity/build.gradle.kts b/temerity/build.gradle.kts
index 0a4a0f62af75b1a686de1f479579e2cb8e7e0fe2..6d69a307da2b2084c212ff4b5cd2594f055cf26a 100644
--- a/temerity/build.gradle.kts
+++ b/temerity/build.gradle.kts
@@ -106,8 +106,8 @@ kotlin {
                 implementation(libs.ktorfit.lib)
                 implementation(libs.kotlinx.coroutines.core)
                 api(libs.kermit)
-                // TODO: Re-add cache4k when updated for Kotlin 2.0.21
-                implementation(libs.statelyConcurrentCollections)
+                implementation(libs.cache4k)
+                implementation(libs.statelyCommon)
                 implementation(libs.kmpIo)
                 implementation(libs.kotlinx.io)
                 implementation(libs.kotlinSemver)
@@ -127,12 +127,18 @@ kotlin {
                 implementation(libs.ktor.client.java)
                 implementation(libs.slf4j.api)
                 implementation(libs.jansi)
+                // See https://github.com/sqldelight/sqldelight/issues/4357#issuecomment-1839905700
+                implementation(libs.statelyIsolate)
+                implementation(libs.statelyIsoCollections)
             }
         }
         val androidMain by getting {
             dependencies {
                 implementation(libs.ktor.client.android)
                 // TODO: Add logging for Android
+                // See https://github.com/sqldelight/sqldelight/issues/4357#issuecomment-1839905700
+                implementation(libs.statelyIsolate)
+                implementation(libs.statelyIsoCollections)
             }
         }
         val jvmTest by getting {
diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/TemClientConfig.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/TemClientConfig.kt
index 4b2ef17f062902a894325c97f44f6ee2fdd809fa..a31ac918f9c15e6c42b80a57783b7388dd3ec99d 100644
--- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/TemClientConfig.kt
+++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/TemClientConfig.kt
@@ -30,13 +30,16 @@ import kotlin.time.Duration
  *
  * @property serviceUrl Specifies URL of platform instance to make API calls to
  * @property serviceToken Specifies the authorization token to use for API calls
- * @property optDebugEnabled Specifies whether debug logging should be enabled
+ * @property optLoggingEnabled Specifies whether logging should be enabled
+ * @property optDebugEnabled Specifies whether debugging mode should be enabled
+ * @property optSupportKtxNotebook Specifies whether Kotlin notebook support should be enabled
  *
  * @property optExpectSuccess Specifies whether to expect successful responses by default
  * @property optUseWebTimeout Specifies whether to use a timeout  other than default for web requests
  * @property optWebTimeoutDuration Specifies the timeout [kotlin.time.Duration] to use when making web requests
  * @property optCacheTimeoutDuration Specifies the timeout [kotlin.time.Duration] to use when storing cache entries. Affects keep-alive time for cached instance data like user role type fields.
  * @property optThreadCount Specifies the number of threads to use for each client instance. Defaults to 2 at a minimum
+ * @property optEnableCompression Specifies whether to turn on compression for HTTP requests. Enabled by default.
  */
 @TemDsl
 public class TemClientConfig {
@@ -59,6 +62,8 @@ public class TemClientConfig {
 
   public var optThreadCount: Int? = null
 
+  public var optEnableCompression: Boolean = true
+
   /**
    * Configures the HttpClient with the provided block.
    * @param () -> HttpClient The block to configure the HttpClient.
@@ -80,7 +85,7 @@ public class TemClientConfig {
 
   /**
    * Creates an custom [HttpClient] with the specified [io.ktor.client.engine.HttpClientEngineFactory] and optional [block] configuration.
-   * Note that the Temerity config will be added afterwards.
+   * Note that the Temerity config will be added afterward.
    */
   public fun <T : HttpClientEngineConfig> httpClient(
     engineFactory: HttpClientEngineFactory<T>,
diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt
index 685947d248b9561c27d7318e8bffebc1369dbe59..422a74f29750e060454fd5e6a3a6898b67b901bc 100644
--- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt
+++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt
@@ -17,7 +17,6 @@
  */
 package edu.ucsc.its.temerity.core
 
-import co.touchlab.stately.collections.ConcurrentMutableMap
 import com.skydoves.sandwich.ApiResponse
 import com.skydoves.sandwich.StatusCode.NoContent
 import com.skydoves.sandwich.getOrThrow
@@ -54,10 +53,13 @@ import edu.ucsc.its.temerity.model.User
 import edu.ucsc.its.temerity.model.UserGroup
 import edu.ucsc.its.temerity.model.UserRecordingSession
 import edu.ucsc.its.temerity.model.UserUpdate
+import io.github.reactivecircus.cache4k.Cache
 import io.ktor.client.HttpClient
 import io.ktor.client.HttpClientConfig
 import io.ktor.client.engine.HttpClientEngine
 import io.ktor.client.plugins.HttpTimeout
+import io.ktor.client.plugins.compression.ContentEncoding
+import io.ktor.client.plugins.compression.ContentEncodingConfig
 import io.ktor.client.plugins.defaultRequest
 import io.ktor.client.plugins.logging.LogLevel.ALL
 import io.ktor.client.plugins.logging.Logging
@@ -216,7 +218,7 @@ public class Temerity internal constructor(
 
   private var libraryLogger: KermitLogger
   private var platformApi: PlatformApi
-  private var cachedUserRoleList: ConcurrentMutableMap<Int, String>
+  private var cachedUserRoleList: Cache<Int, String>
   private var libraryCoroutineDispatcher: CoroutineDispatcher
   private var libraryCoroutineScope: CoroutineScope
   private var jsonProcessingDispatcher: CoroutineDispatcher
@@ -251,7 +253,9 @@ public class Temerity internal constructor(
       parametersOf(config, webRequestDispatcher)
     }
 
-    cachedUserRoleList = ConcurrentMutableMap()
+    cachedUserRoleList = Cache.Builder<Int, String>()
+      .expireAfterWrite(config.optCacheTimeoutDuration ?: 15.minutes)
+      .build()
   }
 
   /**
@@ -344,7 +348,7 @@ public class Temerity internal constructor(
       json.decodeFromString<List<User>>(returnedUsersResponse)
     }
     val roleTypes = returnedUserList.map { it.userType }.distinct()
-    cachedUserRoleList.clear()
+    cachedUserRoleList.invalidateAll()
     roleTypes.forEach {
       cachedUserRoleList.put(it.hashCode(), it)
     }
@@ -355,7 +359,7 @@ public class Temerity internal constructor(
     if (refresh) {
       refreshCachedUserRoles()
     }
-    val returnedUserRoles = cachedUserRoleList.values.toList()
+    val returnedUserRoles = cachedUserRoleList.asMap().values.toList()
     returnedUserRoles.ifEmpty {
       refreshCachedUserRoles()
     }
@@ -693,8 +697,17 @@ internal fun buildHttpClient(
       }
     }
 
+    if (config.optEnableCompression) {
+      install(ContentEncoding) {
+        mode = ContentEncodingConfig.Mode.CompressRequest
+        deflate(1.0F)
+        gzip(1.0F)
+      }
+    }
+
     config.optHttpClientConfigBlock?.invoke(this)
   }
+
   return config.optHttpClientBuilder?.invoke()?.config(defaultConfig) ?: HttpClient(httpClientEngine, defaultConfig)
 }
 
diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdReportTests.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdReportTests.kt
index 5f61a923904ca0d4e6f143dc254f76f62181308d..c62a65f86cbb33dfbc95c6da26bb5f4f2df2a5eb 100644
--- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdReportTests.kt
+++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdReportTests.kt
@@ -18,6 +18,7 @@
 package edu.ucsc.its.temerity.test
 
 import edu.ucsc.its.temerity.model.AuditLogSortOrder.NEW_FIRST
+import edu.ucsc.its.temerity.model.DeviceRecordingSession
 import edu.ucsc.its.temerity.model.EventType.AUTOMATED_SESSION_FAILED_TO_START
 import edu.ucsc.its.temerity.model.EventType.AUTOMATED_SESSION_MONITOR
 import edu.ucsc.its.temerity.model.EventType.CAPTURE_ERROR