Machine-translated draft — terminology + flow still being reviewed.

Keel — Architecture

Keel splits like Expo into two tiers: the framework (SDK + CLI) and the cloud tier — KAS (Keel Application Services), the analog of Expo’s EAS. KAS is the umbrella over the paid cloud services — KAS Build / Update / Submit / Credentials (one-to-one with EAS Build/Update/Submit/credentials) — plus the companion Keel Go (free tester app) and Dev Client (custom tester). Each layer is independently usable and independently billed, but together they give China-based RN developers a complete workflow.

Naming: KAS = Keel Application Services is the cloud umbrella (≡ EAS), NOT just the credential vault — that’s KAS Credentials. CLI verbs stay keel build / submit / publish / rollback / credentials; the cloud processes stay keel-{build,update,push,submit} + credentials-server (KAS Credentials).

LayerExpo analogStatus
Keel SDK (framework)Expo SDKShipped (KeelModuleCore.xcframework + keel-module-core.aar + KeelRuntimeHost)
Keel CLI (framework)Expo CLIShipped (@keel-ai/cli)
KAS BuildEAS BuildShipped (metro + Hermes bytecode + Docker pipeline; keel build --local signs .aab/.ipa; iOS/Android via a Mac agent)
KAS UpdateEAS UpdateShipped (manifest + bsdiff + client SDK + LocalFS BlobStore); production-grade (CDN / custom domains / staged rollout / monitoring) planned
KAS SubmitEAS SubmitDesign ✓ + driver scaffolds (see submit.md); driver impl in progress
KAS CredentialsEAS credentialsShipped (AES-256-GCM vault + server-side keystore generation + Postgres persistence; verified staging/prod)

Keel focuses on the iOS + Android mobile target. Web apps use a separate Next.js / Vite stack; the WeChat Mini Program is on the roadmap (a separate WXML pipeline, not reusing RN code).

Keel’s relationship to Mortar: fully decoupled, no dependency. Keel works with any backend; wiring up Mortar is the same as wiring up Supabase / Firebase / your own backend (the standard Mortar SDK, no Keel-specific plugin).


The 5-layer architecture

                    ┌──────────────────────────────────────────────┐
                    │  Developer machine                            │
                    │  ┌──────────┐   ┌────────────┐                │
                    │  │ Keel CLI │   │ Keel Go    │ ← scan QR     │
                    │  └────┬─────┘   └────────────┘    paste URL  │
                    │       │                                       │
                    │       │  push tarball                         │
                    └───────┼───────────────────────────────────────┘


              ┌────────────────────────────────────────┐
              │  KAS Build (cloud build)                │
              │   metro → Hermes byte-compile          │
              │   → ipa / apk                          │
              │   [build host: Aliyun ECI / FC]        │
              └────┬─────────────────────────┬─────────┘
                   │                         │
          artifact → Keel OSS           metadata
                   │                         │
                   ▼                         ▼
            ┌─────────────────┐      ┌─────────────────┐
            │  KAS Update     │      │  KAS Submit     │
            │  (manifest +    │      │  (App Store +   │
            │   bsdiff + CDN  │      │   Huawei/Xiaomi/│
            │   + rollout)    │      │   OPPO/vivo/    │
            │                 │      │   Tencent MyApp)│
            └────────┬────────┘      └─────────────────┘

              client pulls bundle


       ┌─────────────────────────────────────────────┐
       │  Keel SDK (inside the user's app)            │
       │                                              │
       │  iOS: KeelModuleCore.xcframework                      │
       │  Android: keel-module-core.aar                      │
       │                                              │
       │  Optional plugins:                          │
       │   import { mortar } from '@mortar/client'    ← if using the Mortar backend │
       │   import { Stack } from '@keel-ai/router'     ← if using App Router │
       │   import { wechat } from '@keel-ai/wechat'    ← China-first first-class module │
       └──────────────────────────────────────────────┘

Layer 1: Native SDK

The core runtime you embed into an existing RN app or a new app.

The two artifacts

PlatformArtifactPath
iOSKeelModuleCore.xcframework (fat binary)keel/build/ios/KeelModuleCore.xcframework
Androidkeel-module-core.aarkeel/build/android/keel-module-core.aar

Distribution channels (iOS)

iOS ships on two tracks, SPM by default, CocoaPods as the compatibility path:

ChannelScopePosition
Swift Package ManagerKeelModuleCore.xcframework (core) + most official @keel-ai/* modules✅ default
CocoaPodsthe few official modules whose low-level deps only publish a podspec🔁 compat

SPM is Apple’s officially recommended dependency manager — natively supported in new iOS projects, zero config. Keel defaults to SPM, aligning with where the iOS ecosystem is heading; the few modules whose third-party SDKs don’t yet ship a Package.swift keep a CocoaPods integration path and fold back into SPM as those upstreams migrate. Integrators never have to choose between the two tools — the core goes through SPM, and you pull in whichever pod-only module you need on the side, with no conflict.

The iOS SPM entry point lives in keel/modules-core/. keel/sdk/ is now a pure-JS umbrella — the native (foundation + substrate) on BOTH platforms folded into modules-core (iOS 2026-05-29, Android in the Z restructure), delivered symmetrically as KeelModuleCore.xcframework (SPM) + keel-module-core.aar (maven com.appunvs:keel-module-core). So keel/sdk/ has no android/ / ios/ / Package.swift / Keel.podspec:

  • keel/modules-core/Package.swift — the only iOS SPM package: a .binaryTarget pointing at keel/build/ios/KeelModuleCore.xcframework, plus the KeelMacros source plugin. The host consumes it directly by path:.
  • keel/modules-core/KeelModuleCore.podspec — a build-time recipe only (react-native.config.js sets ios:null, so it is never autolinked; it is used once when baking the xcframework).
  • On the npm side, @keel-ai/keel (the umbrella) lists @keel-ai/modules-core directly in its dependencies, avoiding reliance on leaf peer-dep transitivity (same pattern as Expo’s expo umbrella).

iOS distribution shape — varies by layer (in transition)

The presence of Package.swift vs *.podspec at each layer:

LayerPackage.swiftpodspecMeaning
modules-core✅ (build-time only)the podspec is used once by modules-core/packaging/build-ios.sh when xcodebuild bakes KeelModuleCore.xcframework; consumers only ever see the SPM .binaryTarget
packages/* (kernel leaves + 2 signin)SPM-only: all are React-free SPM source targets, the podspec removed; includes the kernel leaves + apple-signin / google-signin (moved from modules/ after going React-free on 2026-06-03; google pulls GoogleSignIn-iOS transitively via SPM); the host pulls them in by path:
modules/* (vendor)the 5 vendor modules (alipay/amap/push/umeng/wechat): React-free SPM source targets (they have a Package.swift, no podspec)

modules-autolinking implements both paths: link / install emit a per-module Podfile + Gradle snippet (on iOS there is now no internal consumer — signin also moved to SPM on 2026-06-03; the snippet is only for external standalone distribution); autolink scans <host>/node_modules and generates KeelModulesProvider.{swift,kt}, registering each module with KeelModuleRegistrar. The React-free SPM modules are still declared by hand via path: in the host’s Package.swift / project.yml, but their registration entries are generated by autolink.

See keel/packaging/release.sh for the release flow (a one-shot local release-snippet generator; zero CI); for private / enterprise channels see enterprise-distribution.md.

Android has a single channel: keel-module-core.aar distributed via Maven (Maven Central planned), no multi-channel needed.

Public API (at the native layer)

Class / interfacePlatformPurpose
KeelRuntimeHostiOS + Androidthe substrate that mounts an AI bundle; loadBundle produces an RN rootView the host embeds via SwiftUI UIViewRepresentable / Compose AndroidView
KeelSDKiOS + Androidglobal init (inject license / endpoint)

Class names: KeelRuntimeHost / KeelSDK. The module / framework names (KeelModuleCore.xcframework / import KeelModuleCore / keel-module-core.aar) have always been Keel-branded.

Public API (at the JS layer)

// Backend — optional plugin (@mortar/client is not in the default bundle)
import { mortar } from '@mortar/client';
mortar.from('todos').select(...);
mortar.auth.signIn(...);

// File-based routing — optional plugin (added)
import { Stack, Tabs } from '@keel-ai/router';
// or keep using react-navigation's imperative style (compatible)

// China-first first-class modules — optional (npm install on demand)
import { wechat } from '@keel-ai/wechat';
import { alipay } from '@keel-ai/alipay';

New Architecture on by default

The Keel SDK tracks RN 0.85.2 and enables the New Architecture by default:

  • Fabric (new renderer) — async, concurrent rendering
  • TurboModules (new native modules) — lazy loading, 30%+ lower startup latency
  • Bridgeless (no RN bridge) — no serialization overhead between JS and native

Unlike Expo, which conservatively waits for users to opt in, Keel treats the New Architecture as a product feature — bundle startup speed / performance is 30%+ better than an Old Architecture RN bundle, one of Keel’s selling points over a plain RN project.

Internal technical detail (invisible to developers)

Each KeelRuntimeHost carries its own Hermes runtime; JS state does not leak across instances. This opens the door to super-app / multi-tenant / multi-bundle scenarios in higher-level apps. A typical RN developer is fine with a single instance, matching Expo’s behavior.


Layer 2: CLI (@keel-ai/cli)

Analogous to npx expo / npx eas, but on China npm mirrors with a Chinese-language UI by default.

npm install -g @keel-ai/cli

keel create my-app                  # new project (pick one of 5 templates)
keel start                          # local dev server (metro + Keel Go pairing)
keel doctor                         # check the environment (Node / Java / Xcode / iOS SDK version compat)
keel install <pkg>                  # RN-aware dependency install (version-compat checks)
keel build ios                      # trigger a cloud iOS build
keel build android                  # Android
keel build dev-client               # build the project's Dev Client
keel publish <bundle>               # push a new bundle (OTA: upload + promote)
keel submit ios --to=appstore       # auto-submit to the iOS App Store (planned)
keel submit android --to=huawei     # Huawei AppGallery (planned)

Implementation: a Node CLI (distributed on npm) that calls the Keel backend’s HTTP API. It does no building locally — it only packs a tarball and uploads it.

Note: there is no keel build web / keel deploy --target=web — Keel does not do the web target (see the note at the top).


Layer 3: Build

Analogous to EAS Build. A cloud build service. keel build ios triggers:

  1. The CLI packs the project source (with package.json) into a tarball and uploads it to the Build endpoint
  2. The backend spins up an ephemeral container on Aliyun ECI / FC, pulling an image (pre-installed metro + Hermes + iOS / Android toolchain)
  3. The container runs metro bundlehermesc → the platform’s packaging script
  4. The artifact (.ipa / .apk) lands in Keel’s own OSS bucket
  5. The CLI gets back a download URL; it pulls it locally or feeds straight into the Update / Submit flow

Code paths: keel/cloud/build-image/ (metro + Dockerfile) + keel/cloud/internal/cloudbuild/ (the Go service) + keel/cloud/cmd/build-server/ (the HTTP entry). The external product name is KAS Build (KAS = Keel Application Services, the cloud umbrella).


Layer 4: Update

Analogous to EAS Update. The OTA protocol layer plus production-grade distribution infrastructure in one. It includes:

  • Protocol: the manifest API + client SDKs (TS / iOS Swift / Android Kotlin)
  • Transfer optimization: bsdiff4 incremental patch updates
  • Storage: a bundle blob store (LocalFS today, Aliyun OSS / S3 planned)
  • Distribution: CDN edge cache (planned, Aliyun CDN) + custom domains + HTTPS
  • Release control: channels (production / staging / development) + staged rollout (10%→50%→100%, planned) + one-click rollback (planned)
  • Monitoring: pull success rate / latency / error rate (planned, Feishu / DingTalk alerts)
  • Optional push (@keel-ai/push): JPush / GeTui, triggering clients to pull a new bundle immediately

Self-operated, decoupled from Mortar: KAS Update manages its own manifest DB (PG / SQLite) + its own Aliyun OSS + its own CDN. It does not depend on Mortar primitives. Running KAS Update incurs no Mortar bill.

Code paths: keel/cloud/internal/update/ (the Go service) + keel/cloud/cmd/update-server/ (the HTTP entry) + keel/packages/updates/ (the @keel-ai/updates npm package: three parallel client implementations in TS + Swift + Kotlin).

See update.md for details.


Layer 5: Submit

Analogous to EAS Submit. Auto-uploads the ipa / apk produced by keel build to app stores for review. Planned.

Supported targets:

  • iOS: the App Store Connect API
  • China’s 5 major Android stores: Huawei / Xiaomi / OPPO / vivo / Tencent MyApp (each has a different API; implemented in priority order by market share)
  • International Android: the Google Play Console API (optional, for overseas users)

Each store has a different review mechanism; the CLI helps users fill in metadata (screenshots / description / privacy policy), monitors review status, and gives feedback when a review fails.

First lands the iOS App Store + Huawei + Xiaomi + OPPO; vivo / Tencent MyApp later.

See the Submit section of roadmap.md for details.


Modules API (horizontal extension, mapping to the Expo Modules API)

A horizontal extension above the 5 layers — @keel-ai/modules-core lets the Keel team (or any RN module author) write a Keel module, publish it to npm, and have a consumer project npm install + autolink it. It maps to the Expo Modules API (a declarative spec + native iOS/Android + TS bindings packaged together), but without depending on the expo runtime.

Flat distribution model

Like Expo, there are no tier levels. Two kinds of artifact come out of this repo:

ArtifactContentsIntegration
SDK runtimeKeelModuleCore.xcframework + keel-module-core.aar (just the KeelRuntimeHost mount + the Hermes runtime)the host app links the SDK binary via pod / gradle
Keel npm packagesindividual npm packages under keel/packages/<name>/the host app npm install @keel-ai/<name> + standard RN autolink

Whether it’s Keel’s own Keel Go tester, Fabric’s host shell, or a third-party RN project, they all go through the same autolink path — the dependencies declared in the host’s package.json decide which native modules go into the binary. There are no “special modules embedded in the SDK” — apart from the one core runtime KeelRuntimeHost, every capability is its own npm package (including OTA Update, see @keel-ai/updates).

If a downstream app wants to gate which modules an AI bundle may import, it maintains an allowlist on its own side — Keel does not bake in tiers to support that kind of gating.

The module.yml declaration

name: keel-module-wechat
platforms: [ios, android]
ios:    { pod: KeelModuleWeChat,    min_ios: "13.0" }
android: { aar: keel-module-wechat,  min_sdk: 23 }
js:     { entry: ./src/index.ts }
peer_runtime: "^1.0.0"

The current official Keel modules

Spread across three top-level directories by role: feature packages in keel/packages/, vendor modules in keel/modules/, dev tooling in keel/tools/.

Platform utilitieskeel/packages/ (mapping to expo-* platform metadata + OTA):

  • @keel-ai/application — app metadata (bundle ID / name / version / build number)
  • @keel-ai/constants — runtime constants (status bar height / platform identity / simulator detection)
  • @keel-ai/device — device info (model / manufacturer / OS name + version)
  • @keel-ai/asset — bundle asset resolution + remote asset cache download
  • @keel-ai/font — runtime custom-font registration
  • @keel-ai/updates — the OTA bundle distribution client (manifest + bsdiff + channel + rollback), mapping to expo-updates. Three parallel implementations: TS (JS-side self-update), Swift (the iOS host shell’s native launcher), Kotlin (Android)

China-first vendor integrationskeel/modules/ (the gap in domestic RN modules, scaffolded out by create-keel-module):

  • keel-module-wechat — WeChat pay / login / share / Mini Program jump
  • keel-module-alipay — Alipay pay / real-name / Mini Program jump
  • keel-module-amap — AMap maps / location / route planning
  • keel-module-push — a push abstraction layer (JPush + GeTui backends)
  • keel-module-umeng — Umeng analytics

Auth providers@keel-ai/apple-signin / @keel-ai/google-signin (native, React-free SPM, under keel/packages/, pulled by @keel-ai/keel as default kernel; moved from modules/ on 2026-06-03) / @keel-ai/auth-session (pure TS, under keel/packages/)

App frameworkkeel/packages/: @keel-ai/router (file-system routing, mapping to expo-router). Its companion compiler plugin router-metro-plugin is in keel/tools/

Dev toolingkeel/tools/: bundle-atlas (bundle content auditing) / create-keel-project / create-keel-module

CLI

# Scaffolding lives in the standalone create-keel-module package (Expo pattern)
npm create keel-module <name>

# modules-core's keel-module CLI only does daily-dev
keel-module validate          # validate module.yml + sources
keel-module link              # add to the host app's package.json + emit autolink snippet
keel-module install           # write the snippet into Podfile / settings.gradle.kts (idempotent)
keel-module test              # run module tests in a minimal host-app context
keel-module publish           # validate + bump version + npm publish

See modules-core/README.md for details.


Companions: Keel Go + Dev Client

Keel Go

Same usage as Expo Go: developers download the free Keel Go app from the App Store / China’s 5 major stores; scan a QR / paste a URL to load any Keel bundle.

  • What it can run: whichever Keel modules Keel Go declares at compile time in its own package.json are in the binary; a bundle that only uses those works out of the box
  • Upside: zero config, see results on a real device within 5 minutes

Dev Client

keel build dev-client auto-generates a sideloadable tester app for the project:

  • includes all of the project’s native modules (the ones in package.json + the autolinked ones)
  • iOS: sideload to a test device / TestFlight distribution
  • Android: sideload the .apk / enterprise distribution on domestic stores

Analogous to the Expo Dev Client. Purpose: when a project’s native modules exceed what’s already linked into the Keel Go binary, the Dev Client re-links a tester that includes exactly the native modules this project needs.


Relationship to Mortar (fully decoupled, no dependency)

Keel has no special integration with Mortar — Mortar is just one of many BaaS options:

  • Not bundled by default: the Keel SDK does not auto-inject a Mortar client
  • No @keel-ai/mortar plugin package: users wire up Mortar through the standard Mortar SDK (npm install @mortar/client), the same way they’d use @supabase/supabase-js for Supabase or firebase for Firebase
  • Usage:
// Your own backend:
fetch('/api/todos');

// Mortar:
import { mortar } from '@mortar/client';
mortar.from('todos').select(...);

// Supabase / Firebase / ...:
import { createClient } from '@supabase/supabase-js';
// all three are fine, no conflict

Why fully decoupled:

  • so Keel users aren’t forced onto Mortar (Supabase / your own backend / Firebase all work)
  • so Mortar users aren’t forced onto Keel (any RN / web / native works, including Expo)
  • so each side can be sold independently to non-overlapping customers, with no reverse references
  • Mortar’s docs never mention Keel; Keel’s docs only mention Mortar in passing as “one of many BaaS options”
  • internal dogfood: appunvs uses the standard integration path, with no back doors

Internal tech (not on the product marketing surface)

  • per-instance Hermes: one independent Hermes runtime per KeelRuntimeHost (not a shared runtime with isolated contexts; a genuinely separate runtime). Opens the door to super-app / multi-bundle coexistence scenarios
  • SDK ABI version: the sdk_version field in keel/version.json; adding / removing / re-signing a native public API = a bump

Evolution path

See roadmap.md. Current progress:

  • Shipped: SDK + Build ✅ shipped
  • In progress: CLI + docs site + Update (OTA protocol + client SDKs on all three platforms) ✅ done; productionizing Update (CDN / custom domains / staged rollout / monitoring) + App Router + Keel Go + Open Governance to do
  • Planned: Modules API + Dev Client + the 5 China modules + @keel-ai/push + Submit (incl. China’s 5 major Android stores)
  • Later: Bundle Atlas + VS Code extension + monitoring dashboard and other developer tools
  • Later: enterprise edition (cross-cutting: private deployment / SSO / audit log / SLA)

Governance + open source

Keel is an Apache 2.0 open-source project. See governance.md.

  • public GitHub repo + RFC process
  • public roadmap
  • commercial services (Pro / Team / Enterprise) sell hosting + SLA + first-class operations, and do not cripple open-source features