Quickstart
Prerequisites: JDK 25, the lertha CLI, and a running orchestrator. For a laptop, the
fastest orchestrator is the throwaway one the CLI ships:
lertha dev
That gives you an orchestrator on localhost:7233 and a UI on localhost:8233. In production the
address is the Lertha orchestrator's; see Configuration.
1. Define a task
data class GreetingInput(val greeting: String, val name: String)
data class Greeting(val text: String)
object GreetingTask : TaskHandler<GreetingInput, Greeting> {
override val type = taskType<GreetingInput, Greeting>("ComposeGreeting")
override suspend fun handle(input: GreetingInput, ctx: TaskContext): Greeting =
Greeting("${input.greeting}, ${input.name}!")
}
No annotations, no engine imports. The type name is what the UI and the CLI show.
2. Run a worker
fun main() {
EngineConnection.fromEnvironment().use { connection ->
connection.runner("lab").use { runner ->
runner.register(GreetingTask)
runner.start()
Thread.currentThread().join()
}
}
}
3. Start the task from application code
fun main(args: Array<String>) = runBlocking {
EngineConnection.fromEnvironment().use { connection ->
val orchestrator = connection.orchestrator()
val options = RunOptions(
id = "greeting-${UUID.randomUUID()}",
queue = "lab",
attemptTimeout = 10.seconds,
retry = RetryPolicy(maxAttempts = 5, initialInterval = 500.milliseconds),
)
val greeting = orchestrator.run(GreetingTask.type, GreetingInput("Hello", "World"), options)
println(greeting.text)
}
}
run is start followed by result(). Use start when the caller should not wait.
4. Reach the same worker from the CLI
Because the handler registers under its plain type name:
lertha task run --type ComposeGreeting --id cli-1 --queue lab --timeout 10s \
--input '{"greeting":"Hello","name":"CLI"}'