Kotlin Multiplatform · Android · iOS · Desktop

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

FlightRecorder · search REC
    Deterministic by design

    Four guarantees. Not conventions — guarantees.

    Most MVI libraries describe a pattern. Kide's engine enforces one, and the test suite proves it.

    01

    No intent is ever dropped

    Intents queue on a lossless FIFO channel and process strictly in dispatch order — even under load, even mid-navigation.

    02

    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.

    03

    Side effects fire exactly once

    Buffered until collected, delivered once, never lost across configuration changes. No hot-flow footguns.

    04

    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.

    One map. Every behavior.

    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.

    SearchContract.kt
    @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"
    }
    A serious architecture first

    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.

    Agent-native debugging

    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.

    claude · connected to kide (localhost:8765)
    1
    Record. A FlightRecorder keeps the causal trace: intent → action → state diff → effect → error.
    2
    Connect. Debug builds serve MCP on loopback. adb forward, then claude mcp add. Release builds refuse to start it.
    3
    Interrogate. The agent reads live state and traces, injects intents into the running app, and exports a bug session as a regression-test scaffold.
    $ 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.

    demo · "Why is the search screen stuck?"

    Watch the agent read a live FlightRecorder trace, pinpoint the missing reduction, and generate a Kotest replay — against the running app.

    Batteries, modular

    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 else
    kide-navigationNavigation 3 integration & ViewState persistence
    kide-clean-architectureDomain / adapter / framework layer vocabulary
    kide-devtoolsFlightRecorder, MCP agent port, event streaming
    kide-koinKoin dependency-injection helpers
    kide-decomposeDecompose host adapter + StateKeeper persistence
    kide-voyagerVoyager ScreenModel host adapter