Files
correx/docs/decisions/adr-0002-kotlin-choice.md

3.1 KiB
Raw Permalink Blame History

name, description, depth, links
name description depth links
Adr 0002 Kotlin Choice Decision to implement in Kotlin 2
../index.md
./adr-0000-invariants.md

ADR 0002: use kotlin as the primary implementation language

status: accepted
date: 07.05.2026 (May) deciders: Kami


context

Correx is a stateheavy, eventheavy, concurrencyheavy orchestration runtime. The language choice affects maintainability, type safety, concurrency model, and ecosystem compatibility for a localfirst tool.

decision

We implement the core, orchestration, and infrastructure layers in Kotlin (JVM), using the following primary libraries:

  • coroutines structured concurrency
  • kotlinx.serialization event/artifact serialization
  • Exposed typesafe SQL for persistence
  • Ktor web/API server
  • Hoplite / Typesafe Config configuration

Frontend interface uses SvelteKit; CLI uses Clikt.

rationale

  1. Sealed hierarchies and data classes map naturally to event types, artifact schemas, and validation outcomes.
  2. Coroutines provide safe, cancellable concurrency that aligns with the session lifecycle (cancellation tokens, explicit scopes).
  3. Immutability by default reduces accidental state mutation critical for event sourcing.
  4. Typesafe DSLs enable clean definition of transitions, conditions, and tool contracts directly in config or code.
  5. The existing team knows Java/Kotlin, reducing rampup.
  6. Libraries like kotlinx.serialization give deterministic, reflectionfree serialization.
  7. Strong typing prevents many categories of bugs that would be hard to catch in a dynamic language when dealing with complex state machines.

alternatives considered

  • Python: excellent for glue and rapid prototyping, but dynamic typing and global interpreter lock make deterministic concurrency, state integrity, and longterm maintenance riskier. Python was used in the failed llm-pipeline prototype and contributed to hidden mutable state issues.
  • TypeScript (Node.js): singlethreaded event loop simplifies some concurrency but lacks the typesafety and structured concurrency needed for complex orchestration state machines. Validation and serialization would rely on thirdparty libraries with varying degrees of strictness.
  • Rust: performance and safety are excellent, but the borrow checker and ecosystem immaturity for LLM tooling would slow development significantly.

consequences

  • JVM startup overhead; acceptable for a tool that runs as a persistent server or longlived CLI.
  • Requires Kotlin knowledge; mitigated by good documentation and a growing community.
  • Interop with native libraries (e.g., llama.cpp) will be via JNI or external processes; we accept that complexity and encapsulate it in infrastructure providers.
  • We must enforce module dependency rules (with tools like ArchUnit) to prevent core from depending on infrastructure.

status

This decision is foundational and aligns with the architectural need for a “workflow engine” rather than a “scripting environment”.