Getting Started
This guide provides the essential steps to integrate Project KARL into your Kotlin application. We will cover setting up your project, adding the necessary dependencies, and creating a minimal runnable example to demonstrate a basic KARL integration.
Prerequisites
Before integrating Project KARL, please ensure your development environment and project meet the following prerequisites:
-
Kotlin Version: Project KARL's core and implementation modules are compatible with Kotlin
1.9.23
or higher. Ensure your project is configured to use a compatible Kotlin version. -
Java Development Kit (JDK): A JDK version of 11 or later is required to compile and run KARL, especially for its JVM-targeted modules (e.g., Desktop examples, KotlinDL backend).
-
Gradle: Your project should be using Gradle
7.3
or a more recent version for dependency management and building. -
Integrated Development Environment (IDE): IntelliJ IDEA or Android Studio with the latest Kotlin plugin is highly recommended for the best development experience.
-
For UI Integration (
:karl-compose-ui
&:karl-example-desktop
):-
Jetpack Compose Multiplatform Plugin: Version
1.6.10
(compatible with Kotlin1.9.23
) or a compatible successor. -
Relevant Jetpack Compose library dependencies (runtime, UI, foundation, material/material3).
-
-
For Specific Implementations:
-
:karl-room
: Requires KSP (Kotlin Symbol Processing) plugin (e.g., version1.9.23-1.0.19
) and AndroidX Room dependencies (e.g., version2.6.1
). -
:karl-kldl
: Requires KotlinDL dependencies (e.g., version0.5.2
for Kotlin1.9.23
, or a0.6.0-alpha-x
for Kotlin 2.0.x).
-
It is assumed that you are familiar with Kotlin programming and Gradle build system basics.
Adding KARL to Your Project
To integrate Project KARL, you'll need to configure your Gradle build scripts to include KARL's modules and their dependencies. KARL's modular design allows you to select only the components necessary for your application.
Step 1: Configure Repositories
First, ensure your project can resolve artifacts from standard repositories. This is typically configured
in your root settings.gradle.kts
file. Your setup should include repositories like
mavenCentral()
, google()
, and the JetBrains Compose repository if you plan to
use KARL's UI components or Jetpack Compose in general.
Example snippet for settings.gradle.kts
:
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven("https.maven.pkg.jetbrains.space/public/p/compose/dev")
// TODO: Add repository for KARL artifacts once published
}
}
For the most up-to-date repository configuration, please refer to the settings.gradle.kts
Step 2: Add Dependencies using Version Catalog (Recommended)
Project KARL utilizes Gradle's Version Catalog feature (a libs.versions.toml
file) for
consistent and centralized dependency management. This is the recommended approach.
You will need to define versions and aliases for KARL modules (e.g., karl-core
,
karl-kldl
, karl-room
) and their transitive dependencies (like Room, KotlinDL,
Coroutines) in your project's gradle/libs.versions.toml
file.
Key entries to add/ensure in your gradle/libs.versions.toml
:
In the [versions]
section, define a version for KARL and its key dependencies:
karl = "x.y.z" # Replace with the latest KARL release version
room = "2.6.1" # Example Room version
kotlinDL = "0.5.2" # Example KotlinDL version (ensure compatibility with Kotlin)
In the [libraries]
section, define aliases for KARL modules and their dependencies:
karl-core = {group = "com.your-group-id", name ="karl-core", version.ref ="karl"}
karl-kldl = {group = "com.your-group-id", name ="karl-kldl", version.ref ="karl"}
karl-room = {group = "com.your-group-id", name ="karl-room", version.ref ="karl"}
androidx-room-runtime = {group = "androidx.room", name ="room-runtime", version.ref ="room"}
androidx-room-compiler = {group = "androidx.room", name ="room-compiler", version.ref ="room"}
# ... and other necessary libraries like coroutines, serialization, etc.
In the [plugins]
section, define aliases for necessary Gradle plugins:
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
# Ensure ksp version is defined in [versions]
For a complete reference of required versions and aliases, please consult the authoritative libs.versions.toml
Then, in your application module's build.gradle.kts
(e.g., :app
or
:karl-example-desktop
):
Apply necessary plugins:
plugins
alias(libs.plugins.kotlinMultiplatform) // Or kotlinJvm for a pure JVM module
alias(libs.plugins.ksp) // If using the :karl-room module
// Add Compose plugins if your application or KARL UI components are used
alias(libs.plugins.jetbrainsCompose)
alias(libs.plugins.kotlinComposeCompiler) // If required by your Kotlin/Compose versions
Add KARL dependencies to the appropriate source set:
// ... your target configurations (jvm, androidTarget, etc.) ...
sourceSets {
val main by getting { // Adjust to your module's main source set (e.g., commonMain, jvmMain)
dependencies {
implementation(libs.karl.core)
implementation(libs.karl.kldl) // Your chosen LearningEngine implementation
implementation(libs.karl.room) // Your chosen DataStorage implementation
// implementation(libs.karl.composeUi) // If using KARL's UI components
// Transitive dependencies for implementations (e.g., Room runtime)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
}
}
// KSP configuration for specific targets if applicable (e.g., jvmMain for Room)
val jvmMain by getting {
dependencies {
kspJvm(libs.androidx.room.compiler)
}
}
}
For a fully configured example, please view the build.gradle.kts
Step 3: Sync Gradle Project
After configuring your build scripts, synchronize your project with Gradle. In IntelliJ IDEA or Android
Studio, a Sync Now
prompt typically appears, or you can trigger this action manually (e.g.,
via File >
Sync Project with Gradle Files or the Gradle tool window).
Your First KARL Integration
This section outlines the core steps to integrate KARL into your application, initialize a
KarlContainer
, and perform a basic interaction. It assumes you have already set up your
project dependencies as described above.
For a complete, runnable code example demonstrating these steps, please refer to our Example
Desktop Application
Core Steps:
Step 1: Implement Your Application's DataSource
KARL learns from user interactions that your application provides. To do this, you must implement the
com.karl.core.data.DataSource
interface. This implementation will be specific to your
application, observing relevant user actions (e.g., button clicks, commands executed, settings changed)
and transforming them into InteractionData
objects for KARL.
Conceptual Structure of a DataSource
:
// In your application's codebase
import com.karl.core.data.DataSource
import com.karl.core.models.InteractionData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
// ... other necessary imports for your event source (e.g., Flow)
class AppSpecificDataSource(private val userId: String /*... other dependencies ...*/) : DataSource {
override fun observeInteractionData(
onNewData: suspend (InteractionData) -> Unit,
coroutineScope: CoroutineScope
): Job {
println("DataSource: Starting observation for user $userId...")
// 1. Subscribe to your application's internal event stream(s) (e.g., a Flow, listeners).
// 2. When a relevant event occurs, transform it into an InteractionData object:
// val interaction = InteractionData(type = "event_type", details = mapOf(...), ...)
// 3. Call the onNewData callback:
// coroutineScope.launch { onNewData(interaction) }
// 4. Return the Job associated with the observation.
TODO("Implement actual observation logic and return Job")
}
}
See a concrete implementation in the ExampleDataSource.kt
file
within our example application
Step 2: Instantiate KARL Dependencies
In your application's setup logic (e.g., when a user session starts, or in your main application initialization), you'll need to create instances of:
-
Your chosen
LearningEngine
implementation (e.g., an instance ofKLDLLearningEngine
from the:karl-kldl
module). -
Your chosen
DataStorage
implementation (e.g., an instance ofRoomDataStorage
from the:karl-room
module, which itself requires an initialized Room database instance). -
Your custom
DataSource
implementation (from Step 1). -
A
CoroutineScope
that is tied to the lifecycle of the user session or application component that will manage KARL. This scope is crucial for KARL's asynchronous operations and proper cleanup.
Conceptual Snippet:
// In your application's setup code
val userId = "currentUser123"
val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
// Manage this scope's lifecycle!
// Note: Actual instantiation will involve
// Constructors and Configuration specific to each implementation.
val learningEngine: LearningEngine = KLDLLearningEngineImpl(/* ...config... */)
val dataStorage: DataStorage = RoomDataStorageImpl(/* ...database instance, DAO... */)
val dataSource: DataSource = AppSpecificDataSource(userId /* ...other app dependencies... */)
For details on instantiating specific implementations like KLDLLearningEngine
and setting
up RoomDataStorage
(including database creation), refer to the dependency setup
section in our Example Application
Step 3: Build and Initialize the KarlContainer
With the dependencies ready, use the KarlAPI
builder to construct and then initialize your
KarlContainer
instance. Initialization is an asynchronous operation and should typically be
launched within your application-managed CoroutineScope
.
Conceptual Snippet:
val karlContainer = Karl.forUser(userId)
.withLearningEngine(learningEngine)
.withDataStorage(dataStorage)
.withDataSource(dataSource)
.withCoroutineScope(applicationScope)
// .withInstructions(listOf(KarlInstruction.MinConfidence(0.7f))) // Optional
.buil
applicationScope.launch {
try {
karlContainer.initialize(learningEngine, dataStorage, dataSource, emptyList(), applicationScope)
println("KARL Initialized for $userId.")
// KARL is now ready and observing data from your DataSource.
} catch (e: Exception) {
println("Error initializing KARL: ${e.message}")
}
}
Step 4: Getting Predictions and Managing Lifecycle
Once initialized, you can request predictions using karlContainer.getPrediction()
. Remember
to manage the container's lifecycle by calling karlContainer.saveState()
(e.g., on app exit)
and karlContainer.release()
(when the container is no longer needed and its scope is about to
be cancelled).
Conceptual Snippet:
// To get a prediction:
applicationScope.launch {
val prediction = karlContainer.getPrediction()
if (prediction != null) { /* Use the prediction */ }
}
// During application shutdown or when the user session ends:
fun onAppShutdown() {
applicationScope.launch {
karlContainer.saveState().join() // Ensure state is saved
karlContainer.release()
}
applicationScope.cancel() // Cancel the main scope
}
This minimal overview focuses on the setup and core interaction loop. The TODO
placeholders
from the
original example are now addressed by guiding the user to your full example application code for the
concrete implementation details of the LearningEngine
and DataStorage
.
The full lifecycle management and
interaction patterns are best understood by examining our Example Desktop Application
Running the Example
To run the example above (once the TODO's
are filled):
-
Ensure all KARL module dependencies are correctly added to your application module.
-
Place the
KarlManager
andAppSpecificDataSource
(or similar logic) in your application's startup sequence (e.g.,main
function for a simple JVM app,Application
class orViewModel
for Android,App
composable for Jetpack Compose Desktop). -
Call
karlManager.initializeKarl()
at an appropriate point (e.g., application start). -
Call
karlManager.cleanup()
when your application is shutting down to ensure KARL's state is saved and resources are released. -
Compile and run your application. Observe the console output for logs from KARL components.
The :karl-example-desktop
module in the Project KARL repository provides a more complete,
runnable Jetpack Compose Desktop application demonstrating these steps. It is recommended to explore its
source code for a practical reference.