State management,
crystallized.
Kide — Finnish for crystal — is a strict MVI architecture for Kotlin Multiplatform. Lossless intents, exactly-once side effects, process-death persistence, and something no other library has: your debug app becomes an MCP server your AI assistant can interrogate.
Apache 2.0 · coroutines-only core · explicit API & ABI-validated
Four guarantees. Not conventions — guarantees.
Most MVI libraries describe a pattern. Kide's engine enforces one, and the test suite proves it.
No intent is ever dropped
Intents queue on a lossless FIFO channel and process strictly in dispatch order — even under load, even mid-navigation.
Reduction order is dispatch order
Synchronous reducers run inline on the intent loop. Async work runs off it, with cancellationKey restarts built into the DSL.
Side effects fire exactly once
Buffered until collected, delivered once, never lost across configuration changes. No hot-flow footguns.
Errors never kill the loop
A throwing reducer or use case is caught, logged, reported to interceptors and your onError hook — and the loop keeps running.
Every intent maps to an action — explicitly.
One exhaustive when over a sealed intent hierarchy, returning action values: reduce, sideEffect, async, composite. The compiler tells you when a new intent is unhandled. Screens become deterministic — and trivially testable.
@Serializable // opt-in: survives process death
data class SearchViewState(
val query: String = "",
@Transient val results: List<Project> = emptyList(),
@Transient val isLoading: Boolean = false,
) : ViewState
sealed interface SearchIntent : ViewIntent {
data class UpdateQuery(val query: String) : SearchIntent
data object TriggerSearch : SearchIntent
}
sealed interface SearchSideEffect : SideEffect {
data class ShowToast(val message: String) : SearchSideEffect
}
class SearchProcessor(
private val searchUseCase: SearchUseCase,
) : PresentationProcessor<SearchIntent, SearchViewState, SearchSideEffect>(
SearchViewState()
) {
override suspend fun map(intent: SearchIntent) =
when (intent) {
is UpdateQuery -> reduce { copy(query = intent.query) }
TriggerSearch -> composite(
reduce { copy(isLoading = true) },
async(cancellationKey = "search") { // same key ⇒ restarts
val result = searchUseCase.execute(state.query)
reduce { copy(results = result, isLoading = false) }
},
)
} // sealed + exhaustive: a new intent won't compile unhandled
}
beforeSpec { Dispatchers.setMain(UnconfinedTestDispatcher()) }
it("updates the query") {
// Under the test dispatcher, dispatch is synchronous.
val processor = SearchProcessor(FakeSearchUseCase())
processor.dispatch(UpdateQuery("kotlin"))
processor.state.query shouldBe "kotlin"
}
it("keeps processing after a use case throws") {
// Guarantee 04: errors never kill the intent loop.
val processor = SearchProcessor(FailingSearchUseCase())
processor.dispatch(TriggerSearch)
processor.dispatch(UpdateQuery("still alive"))
processor.state.query shouldBe "still alive"
}
The AI tooling only matters because the core is rock-solid.
Kide is the first AI-agent-native MVI architecture for Kotlin Multiplatform — but agent-native debugging is only trustworthy on top of a deterministic core. Replay works because the intent loop is lossless and reduces in strict dispatch order, so bugs reproduce deterministically.
◆ Bulletproof execution
No dropped intents, deterministic synchronous reduction, exactly-once side-effect delivery. Errors are caught and reported; your intent loop never crashes.
◆ Pure Kotlin Multiplatform
100% logic sharing across Android, JVM (desktop), and iOS — with no Android UI dependencies in the core.
◆ Batteries included
Opt-in ViewState persistence across process death (plain kotlinx-serialization, no custom savers), interceptors for analytics and logging, and optional Clean Architecture scaffolding.
◆ Plays well with your stack
Compose Multiplatform, Navigation 3, Koin, Decompose, and Voyager — integrate one screen at a time, with your existing DI and navigation.
Your app is an MCP server.
Every other MVI library built debug tooling for human eyes. Kide's kide-devtools exposes the running app to the entity that increasingly does the debugging: your AI coding assistant.
FlightRecorder keeps the causal trace: intent → action → state diff → effect → error.adb forward, then claude mcp add. Release builds refuse to start it.$ adb forward tcp:8765 tcp:8765
$ claude mcp add --transport http kide \
http://localhost:8765/mcp
Sound replay isn't luck — it falls out of guarantees 01 & 02. Concurrent-intent architectures can't promise it.
Adopt one screen. Or an architecture.
The core is a single artifact with one dependency: coroutines. Everything else is opt-in.
◆ Process-death persistence
Mark your state @Serializable, expose a serializer on the nav key — done. Lazy snapshots, restore-before-composition, graceful schema evolution. No custom saver abstractions.
◆ Navigation 3, typed end-to-end
First-mover integration with Jetpack Navigation 3 for Compose Multiplatform: ScreenNavKey<T> to ScreenContext<T> to processor — with back-stack persistence.
◆ Host independence
One processor, three retention ecosystems: AndroidX/JetBrains ViewModel, Decompose InstanceKeeper, Voyager ScreenModel. Ten-line adapters each.
◆ Observability built in
Interceptors see every intent, action, state change, effect, and error. KideLog adds severity levels, class-derived tags, and lazy messages — zero dependencies.
Modules
kideMVI core — depends on kotlinx-coroutines, nothing elsekide-navigationNavigation 3 integration & ViewState persistencekide-clean-architectureDomain / adapter / framework layer vocabularykide-devtoolsFlightRecorder, MCP agent port, event streamingkide-koinKoin dependency-injection helperskide-decomposeDecompose host adapter + StateKeeper persistencekide-voyagerVoyager ScreenModel host adapter