Ketoy
v0.3is live

AI-native app updates for Android.

Make your Android apps update themselves in under 60 seconds - UI changes, features, fixes, experiments. Fully within Play Store policies. Plus AI workflows that let you ship most changes by prompt instead of spinning up a release train for every tiny edit.

CheckoutScreen.kt@KetoyEntryPoint
@KetoyEntryPoint @Composable
fun CheckoutScreen() {
    val vm    = ketoyViewModel<CheckoutViewModel>()
    val state by vm.state.collectAsState()

    Scaffold(topBar = { TopAppBar(title = { Text("Checkout") }) }) { p ->
        LazyColumn(modifier = Modifier.padding(p)) {
            items(state.cartItems) { item ->
                ProductCard(name = item.name, price = item.price)
            }
        }
        Button(onClick = { vm.dispatch("place_order") }) {
            Text("Place Order - $" + state.total)
        }
    }
}
// $ ./gradlew ketoyBundle  →  checkout.ktx  ·  3.4 KB
< 50ms
Bundle load + verify + parse
< 100ms
To first frame, cold
~4KB
Full checkout flow bundle
20×
Smaller than JSON SDUI
The Play Store math · brace yourself

How many days has your team spent waiting for review this year?

Update tested. Tagged. Signed. Uploaded. Now you wait. The marketing team waits. The PM refreshes the dashboard. Customer support copy-pastes “fix is coming soon.” Drag the sliders - be honest - and find out exactly how much of your life Play Store review has eaten.

Releases per month (across all your Android apps)2
Average review wait - days per release1.5
Engineers blocked on a release going out3
Loaded engineer cost per day, USD$500
Yes, of course this is back-of-the-napkin. Use real numbers from play.google.com if you want a worse number.
Total this year
36days lost waiting for review
3 engineers blocked for 36 days

That’s a quarter of a year, gone. Refreshing the Play Console. We’re sure your team enjoyed it.

Same updates, shipped via Ketoy~ 96 min
Annual cost of waiting: $54,000
AI-native, by design

Ship most updates by prompt. Skip the release train for every tiny edit.

Ketoy isn’t just a runtime - it’s an update infrastructure. Most product changes (copy tweaks, layout shuffles, A/B variants, hotfixes) don’t need a four-person standup. Describe them. Ketoy plans the change, generates the bundle, signs it, and stages it for review - one workflow, no separate dev / release / ops coordination.

Prompt-driven update workflows

"Move the trust badges above the price on Checkout for India users." Ketoy plans the diff, regenerates the bundle, and queues it behind your approval gate.

Experiments without the orchestration tax

Roll a variant to 5% of users, watch the metrics roll in, ramp or revert from the same console. No PM-eng-design ping-pong for every percentage bump.

Humans still own the merge

AI proposes, you approve. Every bundle still gets Ed25519-signed by your release key. The autonomy lives in the planning, not in the deploy.

One workflow, instead of four

Dev, release, ops and ticketing for a copy change is six tickets, two reviews, and a calendar event. Ketoy collapses it into a single prompt + approval.

Ketoy in one paragraph

Your app, but it can update itself. Like a website. But still your Android app.

You add one annotation to a screen. You run ./gradlew ketoyBundle. You upload a tiny .ktx file to your CDN. Every user gets the change within 60 seconds. The app on their phone doesn’t update. The flow inside it does.

01

Apps that self-update

Push a fix at 3pm. Every user has it by 3:01. No Play Store release. No staged rollout timeline. No "we’ll get to it next sprint."

How the bytecode flow works
02

Totally legal

Ketoy doesn’t download executable DEX. The JIT generates it on-device from KBC - exactly like ART does with bytecode. Same lane as Lua in a game engine. Play Store-compliant.

Read the security model
03

Your existing Kotlin. Untouched.

No DSL to learn. No RemoteText(config = ...) replacing your Text(). No JSON schema. Just Compose. The way you already write it.

See what's supported
We hate heavy migrations at Ketoy

Things you have to learn don’t exist.

Server-driven UI tools love giving you homework. New DSLs. New schemas. New component libraries that look like Compose but aren’t. Ketoy has none of that. Here’s the list of things you don’t have to do, in order of how much we don’t want you to do them:

No new DSLIt’s just Kotlin. Same one you’ve been using since college.
No JSON schemaNo mapping keys to Compose params. No "fontWeight": "bold". Ever.
No heavy migrationYour existing CheckoutScreen.kt works as-is. Add one annotation.
No component re-implementationYou still call Button(). We render the real Material3 button.
No serialization codeNo type converters. No adapter glue. We snapshot the IR.
No new ViewModel patternReal androidx.lifecycle.ViewModel. Real viewModelScope. SavedStateHandle still works.
The diff between an in-APK screen and a Ketoy screen is one annotation and one Gradle task. That’s it. We checked twice.
What works · what’s in the box

If it’s in Jetpack Compose, it’s probably in Ketoy.

Ketoy isn’t a curated subset of “the easy parts of Compose.” It’s a code generator that walks your Compose classpath and writes adapters for everything it finds. When Compose adds a parameter, you run one command. Done.

UI

Jetpack Compose & Material 3

All 35 Material3 composables with every parameter. Text, TextField, Button, Scaffold, LazyColumn - the works.

ScaffoldLazyColumnOutlinedTextFieldButtonTopAppBarAlertDialog+29 more
Language

Plain Kotlin

K2-compatible. Sealed classes, data classes, extension functions, lambdas with captures, top-level functions - all of it, compiled to KBC bytecode.

data classsealedwhenextensionsclosures
Async

Coroutines & Flow

First-class. launch, async, withContext, structured concurrency, 9 Flow operators. GlobalScope stays banned - you’re welcome.

launchasyncStateFlowcollectAsStatedebounce
State

ViewModel

Real Android ViewModel. Real viewModelScope. Survives config changes. SavedStateHandle for process death. Hilt-friendly.

viewModelScopeSavedStateHandle@HiltViewModeldispatch()
I/O

Networking, Storage, Navigation

HTTP (Ktor/OkHttp), DataStore KV, Room queries, NavController push/pop/deep-link. 67 built-in capabilities. Register your own for app-specific stuff.

HTTPDataStoreRoomNavController+ your SDKs
DI

Hilt

Drop-in. ketoy-hilt wires everything. @Inject in your ViewModels exactly like before. We added a debug overlay too - you’ll like it.

@Inject@HiltViewModel@Moduledev overlay
See the full supported-features matrix →
Security · the bit you’ll have to defend to your CTO

Three things, plus a footnote for Play Store policy.

Server-driven UI gets the security question approximately once per conversation. Here is the answer, in three parts. You can copy-paste this into Slack.

1 · Every bundle is signed

Ed25519, 64-byte signature, verified before a single instruction is decoded. The public key ships in your APK. The private key stays on your signing server. Neither we nor Google can produce a valid bundle for your app. That’s the design.

2 · The sandbox is a build error

The K2 plugin rejects java.io.*, kotlin.reflect, GlobalScope, and any unregistered Android API. If a malicious bundle could exist, it couldn’t compile. Forbidden calls are build errors, not surprises on a user’s phone.

3 · Capabilities are the only door

KBC code can’t touch an Android API directly. Every HTTP call, every DataStore read, every NavController.navigate() goes through a registered capability. Not registered, not callable. Not callable, not exploitable.

Quick start · the whole tutorial fits on a screen

Three blocks. One Gradle task. One CDN upload.

Add the plugin to your app module. Write your screen the way you’d write any other Compose screen. Run ketoyBundle. Upload. Look up at the clock - yep, that fast.

build.gradle.kts
plugins {
  id("dev.ketoy.compiler") version "0.3.4-alpha"
}

dependencies {
  implementation(platform("dev.ketoy.vm:ketoy-bom:0.3.4-alpha"))
  implementation("dev.ketoy.vm:ketoy-runtime")
  implementation("dev.ketoy.vm:ketoy-hilt")
  implementation("dev.ketoy.vm:ketoy-adapters-material3")
  ksp("dev.ketoy.vm:ketoy-ksp-processor")
}
HomeScreen.kt
// Look familiar? It should.
@KetoyEntryPoint
@Composable
fun HomeScreen() {
  val vm    = ketoyViewModel<HomeViewModel>()
  val state by vm.state.collectAsState()

  Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
    Text("Welcome, " + state["name"])
    Button(onClick = { vm.dispatch("get_started") }) {
      Text("Get Started")
    }
  }
}
terminal~60s rollout
# Compile to KBC
$ ./gradlew :ketoy-screens:ketoyBundle

> Task :ketoy-screens:ketoyBundle
  HomeScreen.kt        → home.ktx        2.1 KB
  CheckoutScreen.kt    → checkout.ktx    3.4 KB
  Signed:    Ed25519
  Compressed:Brotli q=11

# Upload. That's the whole release process.
$ aws s3 cp build/ketoy-bundles/ s3://cdn/ketoy/ \
    --recursive --content-encoding br

# Total review time: 0 days. (You're welcome.)

Write Kotlin. Ship the screen. Reclaim your week.

Available now on Maven Central. Hilt integration, dev overlay, and the full Material3 adapter catalog included. No credit card, no waitlist, no sales call - it’s a Gradle plugin.

Stuck on something, evaluating Ketoy for your team, or wondering if your weird in-house SDK is supported? We answer every email. Usually within the same day. Sometimes faster than your Play Store review.