This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
- Focus on SIMPLICITY, and following Clean SOLID principles when writing code. Reusability, Clean architecture(not strictly) style, clear separation of concerns.
- Do NOT write ANY MOCK IMPLEMENTATION unless specified otherwise.
- DO NOT PLAN or WRITE any unit tests unless specified otherwise.
- Always in plan mode to make a plan refer to
thoughts/shared/plans/{descriptive_name}.md. - After get the plan, make sure you Write the plan to the appropriate file as mentioned in the guide that you referred to.
- If the task require external knowledge or certain package, also research to get latest knowledge (Use Task tool for research)
- Don't over plan it, always think MVP.
- Once you write the plan, firstly ask me to review it. Do not continue until I approve the plan.
- You should update the plan as you work - check
thoughts/shared/plans/{descriptive_name}.mdif you're running an already created plan viathoughts/shared/plans/{descriptive_name}.md - After you complete tasks in the plan, you should update and append detailed descriptions of the changes you made, so following tasks can be easily hand over to other engineers.
- Always make sure that you're using structured types, never use strings directly so that we can keep things consistent and scalable and not make mistakes.
- Read files FULLY to understand the FULL context. Only use offset/limit when the file is large and you are short on context.
- When fixing issues focus on SIMPLICITY, and following Clean SOLID principles, do not add complicated logic unless necessary!
- When looking up something: It's December 2025 FYI
- Use the latest Swift 6 APIs always.
- Do not use NSLock as it is outdated.
This repository contains cross-platform SDKs for the RunAnywhere on-device AI platform. The platform provides intelligent routing between on-device and cloud AI models to optimize for cost and privacy.
- Kotlin Multiplatform SDK (
sdk/runanywhere-kotlin/) - Cross-platform SDK supporting JVM, Android, and Native platforms - Android SDK (
sdk/runanywhere-android/) - Kotlin-based SDK for Android - iOS SDK (
sdk/runanywhere-swift/) - Swift Package Manager-based SDK for iOS/macOS/tvOS/watchOS - Web SDK (
sdk/runanywhere-web/) - TypeScript/WASM SDK for browsers via Emscripten
- Android Demo (
examples/android/RunAnywhereAI/) - Sample Android app demonstrating SDK usage - iOS Demo (
examples/ios/RunAnywhereAI/) - Sample iOS app demonstrating SDK usage - Web Demo (
examples/web/RunAnywhereAI/) - Sample web app demonstrating SDK usage - IntelliJ Plugin Demo (
examples/intellij-plugin-demo/) - IntelliJ/Android Studio plugin for voice features
# Navigate to Kotlin SDK
cd sdk/runanywhere-kotlin/
# Build Commands (using scripts/sdk.sh)
./scripts/sdk.sh build # Build all platforms (JVM and Android)
./scripts/sdk.sh build-all # Same as 'build' - builds all targets
./scripts/sdk.sh build-all --clean # Clean before building (removes build directories)
./scripts/sdk.sh build-all --deep-clean # Deep clean including Gradle caches
./scripts/sdk.sh build-all --no-clean # Build without any cleanup (default)
# Individual Platform Builds
./scripts/sdk.sh jvm # Build JVM JAR only
./scripts/sdk.sh android # Build Android AAR only
./scripts/sdk.sh common # Compile common module only
# Testing
./scripts/sdk.sh test # Run all tests
./scripts/sdk.sh test-jvm # Run JVM tests
./scripts/sdk.sh test-android # Run Android tests
# Publishing
./scripts/sdk.sh publish # Publish to Maven Local (~/.m2/repository)
./scripts/sdk.sh publish-local # Same as 'publish'
# Cleanup Options
./scripts/sdk.sh clean # Clean build directories
./scripts/sdk.sh deep-clean # Clean build dirs and Gradle caches
# Help and Info
./scripts/sdk.sh help # Show all available commands
./scripts/sdk.sh --help # Same as 'help'
# Direct Gradle Commands (Alternative)
./gradlew build # Build all targets
./gradlew jvmJar # Build JVM JAR
./gradlew assembleDebug # Build Android Debug AAR
./gradlew assembleRelease # Build Android Release AAR
./gradlew clean # Clean build directories
./gradlew publishToMavenLocal # Publish to local MavenThe scripts/sdk.sh script provides:
- Automatic cleanup options:
--clean,--deep-clean,--no-cleanflags - Build verification: Checks for successful JAR and AAR creation
- Error handling: Continues building other targets if one fails
- Progress indicators: Clear output showing build status
- Flexible commands: Support for multiple build scenarios
After a successful build:
- JVM JAR:
build/libs/RunAnywhereKotlinSDK-jvm-0.1.0.jar - Android AAR:
build/outputs/aar/RunAnywhereKotlinSDK-debug.aar - Maven Local:
~/.m2/repository/com/runanywhere/sdk/
# Navigate to Android SDK
cd sdk/runanywhere-android/
# Build the SDK
./gradlew build
# Run lint checks
./gradlew lint
# Run tests
./gradlew test
# Clean build
./gradlew clean
# Build release AAR
./gradlew assembleRelease# Navigate to iOS SDK
cd sdk/runanywhere-swift/
# Build the SDK
swift build
# Run tests
swift test
# Run tests with coverage
swift test --enable-code-coverage
# Run SwiftLint
swiftlint
# Build for specific platform
xcodebuild build -scheme RunAnywhere -destination 'platform=iOS Simulator,name=iPhone 15'# Navigate to Android example
cd examples/android/RunAnywhereAI/
# Build the app
./gradlew build
# Run lint
./gradlew :app:lint
# Install on device/emulator
./gradlew installDebug
# Run tests
./gradlew testTo get logs for sample app and sdk use this in another terminal:
log stream --predicate 'subsystem CONTAINS "com.runanywhere"' --info --debugFor physical device:
idevicesyslog | grep "com.runanywhere"# Navigate to iOS example
cd examples/ios/RunAnywhereAI/
# Build and run on simulator (handles dependencies automatically)
./scripts/build_and_run.sh simulator "iPhone 16 Pro" --build-sdk
# Build and run on connected device
./scripts/build_and_run.sh device
# Clean build artifacts
./scripts/clean_build_and_run.sh# Install CocoaPods dependencies (required for TensorFlow Lite and ZIPFoundation)
pod install
# Fix Xcode 16 sandbox issues (required after pod install)
./fix_pods_sandbox.sh
# After pod install, always open the .xcworkspace file
open RunAnywhereAI.xcworkspace
# Run SwiftLint
./swiftlint.sh
# Verify model download URLs
./scripts/verify_urls.shError: Sandbox: rsync deny(1) file-write-create
Fix: After pod install, run ./fix_pods_sandbox.sh
# Navigate to Web SDK
cd sdk/runanywhere-web/
# First-time setup (installs emsdk, npm deps, builds WASM + TypeScript)
./scripts/build-web.sh --setup
# Build WASM + TypeScript (all backends, default)
./scripts/build-web.sh
# Build WASM with specific backends
./scripts/build-web.sh --build-wasm --llamacpp --onnx
./scripts/build-web.sh --build-wasm --all-backends
./scripts/build-web.sh --build-wasm --llamacpp --vlm --webgpu
# Build TypeScript only (after WASM is already built)
./scripts/build-web.sh --build-ts
# Build sherpa-onnx WASM module (TTS/VAD)
./scripts/build-web.sh --build-sherpa
# Debug build with assertions
./scripts/build-web.sh --debug --llamacpp
# Clean all build artifacts
./scripts/build-web.sh --clean
# Direct npm commands (alternative)
npm run build:wasm # WASM build (core only, no backends)
npm run build:ts # TypeScript compilation
npm run build # TypeScript only (default)
npm run dev # TypeScript watch mode
npm run typecheck # Type-check without emitting
npm run clean # Remove all build outputsAfter a successful build:
- WASM module:
packages/core/wasm/racommons.wasm+racommons.js - WebGPU variant:
packages/core/wasm/racommons-webgpu.wasm(when --webgpu is used) - Sherpa-ONNX:
packages/core/wasm/sherpa/sherpa-onnx.wasm - TypeScript:
packages/core/dist/
- Emscripten SDK: v5.0.0+ (installed automatically by
--setup) - CMake: 3.22+
- Node.js: 18+
# Navigate to web example
cd examples/web/RunAnywhereAI/
# Install dependencies and run dev server
npm install
npm run dev# Run all pre-commit checks
pre-commit run --all-files
# Run specific checks
pre-commit run android-sdk-lint --all-files
pre-commit run ios-sdk-swiftlint --all-filesThe SDK uses Kotlin Multiplatform to share code across JVM, Android, and Native platforms:
-
Common Module (
commonMain/) - Platform-agnostic business logic- Core services and interfaces
- Data models and repositories
- Network and authentication logic
- Model management abstractions
-
Platform-Specific Implementations:
- JVM (
jvmMain/) - Desktop/IntelliJ plugin support - Android (
androidMain/) - Android-specific implementations with Room DB - Native (
nativeMain/) - Linux, macOS, Windows support
- JVM (
-
Key Components:
RunAnywhere.kt- Main SDK entry point (platform-specific implementations)Services.kt- Service container and dependency injectionSTTComponent- Speech-to-text with Whisper integrationVADComponent- Voice activity detectionLLMComponent- Large language model inferenceTTSComponent- Text-to-speech synthesisVLMComponent- Vision-language model inferenceVoiceAgentComponent- Complete voice AI pipeline orchestrationSpeakerDiarizationComponent- Multi-speaker identificationWakeWordComponent- Wake word detectionModelManager- Model downloading and lifecycleConfigurationService- Environment-specific configuration
- Repository Pattern: Data access abstraction with platform-specific implementations
- Service Container: Centralized dependency injection
- Event Bus: Reactive communication between components
- Provider Pattern: Platform-specific service providers (STT, VAD)
Kotlin Multiplatform SDK:
- Kotlin: 2.1.21 (upgraded from 2.0.21 to fix compiler issues)
- Gradle: 8.11.1
- JVM Target: 17
- Android Min SDK: 24
- Android Target SDK: 36
iOS SDK:
- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+
- Swift: 5.9+
- Xcode: 15.0+
For IntelliJ/JetBrains plugin development:
dependencies {
implementation("com.runanywhere.sdk:RunAnywhereKotlinSDK-jvm:0.1.0")
}Location after local publish: ~/.m2/repository/com/runanywhere/sdk/
GitHub Actions workflows are configured for automated testing and building:
- Path-based triggers: Workflows only run when relevant files change
- Platform-specific runners: Ubuntu for Android, macOS for iOS
- Artifact uploads: Build outputs and test results are preserved
- Lint enforcement: Lint errors fail the build
Workflows are located in .github/workflows/:
android-sdk.yml- Android SDK CIios-sdk.yml- iOS SDK CIandroid-app.yml- Android example app CIios-app.yml- iOS example app CIweb-sdk-release.yml- Web SDK release
NEVER make assumptions when implementing KMP code. ALWAYS refer to the iOS implementation as the definitive source of truth.
-
iOS First: When encountering missing logic, unimplemented features, or unclear requirements in KMP, ALWAYS:
- Check the corresponding iOS implementation
- Copy the iOS logic exactly (head-to-head translation)
- Adapt only for Kotlin syntax, not business logic
-
commonMain First: ALL business logic, protocols, interfaces, and structures MUST be defined in
commonMain/:- Interfaces and abstract classes
- Data models and enums
- Business logic and algorithms
- Service contracts and protocols
- Component definitions
- Even platform-specific service interfaces
-
Platform Implementation Naming Convention: Platform-specific implementations MUST use clear prefixes:
AndroidTTSService.kt(not justTTSService.kt)JvmTTSService.kt(not justTTSServiceImpl.kt)IosTTSService.kt(for any iOS-specific bridges)WindowsTTSService.kt,LinuxTTSService.kt, etc.
// Step 1: Check iOS implementation (e.g., TTSService.swift)
// Step 2: Define interface in commonMain matching iOS exactly
// commonMain/kotlin/com/runanywhere/sdk/services/tts/TTSService.kt
interface TTSService {
// Match iOS protocol exactly
suspend fun synthesize(text: String, options: TTSOptions): ByteArray
val availableVoices: List<String>
}
// Step 3: Implement platform-specific versions with clear names
// androidMain/kotlin/com/runanywhere/sdk/services/tts/AndroidTTSService.kt
class AndroidTTSService : TTSService {
// Android-specific implementation
}
// jvmMain/kotlin/com/runanywhere/sdk/services/tts/JvmTTSService.kt
class JvmTTSService : TTSService {
// JVM-specific implementation
}❌ DON'T invent your own logic when something is unclear
❌ DON'T put business logic in platform-specific modules
❌ DON'T name platform files generically (e.g., TTSServiceImpl.kt)
❌ DON'T assume behavior - check iOS implementation
✅ DO check iOS implementation for every feature ✅ DO keep all logic in commonMain ✅ DO use platform prefixes for all platform files ✅ DO translate iOS logic exactly, adapting only syntax
// KMP has this incomplete method:
fun processAudio(data: ByteArray): String {
// TODO: implement
return ""
}
// WRONG approach:
fun processAudio(data: ByteArray): String {
// Making assumptions about what it should do
return data.toString()
}
// CORRECT approach:
// 1. Find iOS AudioProcessor.swift
// 2. Find processAudio method
// 3. Copy exact logic:
fun processAudio(data: ByteArray): String {
// Exact translation of iOS logic
val rms = calculateRMS(data) // If iOS does this
val normalized = normalizeAudio(data, rms) // If iOS does this
return encodeToBase64(normalized) // If iOS does this
}The Kotlin Multiplatform SDK has been aligned with iOS architecture patterns while leveraging Kotlin's strengths. These best practices ensure consistency, maintainability, and cross-platform compatibility.
Follow the iOS component pattern but adapted to KMP idioms:
// Base component with lifecycle management
abstract class BaseComponent<TService : Any>(
protected val configuration: ComponentConfiguration,
serviceContainer: ServiceContainer? = null
) : Component {
// Component state tracking
override var state: ComponentState = ComponentState.NOT_INITIALIZED
protected set
// Service creation (platform-specific via providers)
protected abstract suspend fun createService(): TService
// Lifecycle methods
suspend fun initialize() { /* ... */ }
override suspend fun cleanup() { /* ... */ }
override suspend fun healthCheck(): ComponentHealth { /* ... */ }
}Use Flow instead of AsyncSequence for reactive streams:
// Central event bus with typed events
object EventBus {
private val _componentEvents = MutableSharedFlow<ComponentEvent>()
val componentEvents: SharedFlow<ComponentEvent> = _componentEvents.asSharedFlow()
fun publish(event: ComponentEvent) {
_componentEvents.tryEmit(event)
}
}
// Usage: Listen to component state changes
EventBus.componentEvents
.filterIsInstance<ComponentInitializationEvent.ComponentReady>()
.collect { event ->
println("Component ${event.component} is ready")
}Centralized dependency injection with lazy initialization:
class ServiceContainer {
companion object {
val shared = ServiceContainer()
}
// Platform abstractions via expect/actual
private val fileSystem by lazy { createFileSystem() }
private val httpClient by lazy { createHttpClient() }
// Service dependencies
val modelManager: ModelManager by lazy {
ModelManager(fileSystem, downloadService)
}
// Platform-specific initialization
fun initialize(platformContext: PlatformContext) {
platformContext.initialize()
}
}Keep all business logic, interfaces, and data models in commonMain/:
commonMain/
├── components/ # Component implementations
│ ├── base/ # Base component classes
│ ├── stt/ # Speech-to-text components
│ ├── vad/ # Voice activity detection
│ ├── llm/ # LLM inference components
│ ├── tts/ # Text-to-speech components
│ └── speakerdiarization/ # Speaker diarization
├── data/ # Data layer
│ ├── models/ # Data classes and enums
│ ├── network/ # Network services
│ └── repositories/ # Repository interfaces
├── events/ # Event definitions
├── foundation/ # Core infrastructure
│ ├── ServiceContainer.kt
│ └── SDKLogger.kt
├── models/ # Model management
│ ├── ModelManager.kt
│ └── ModelDownloader.kt
├── memory/ # Memory management
└── generation/ # Text generation services
Use expect/actual only for platform-specific implementations:
// commonMain - Interface only
expect class PlatformContext {
fun initialize()
}
expect fun createFileSystem(): FileSystem
expect fun createHttpClient(): HttpClient
// androidMain - Android implementation
actual class PlatformContext(private val context: Context) {
actual fun initialize() {
// Android-specific setup
}
}
actual fun createFileSystem(): FileSystem = AndroidFileSystem()Core SDK vs Feature Modules:
- Core SDK (
commonMain): Essential services, base components - Feature modules: Optional capabilities (WhisperKit, external AI providers)
- Plugin architecture:
ModuleRegistryfor runtime registration
// Plugin registration pattern
object ModuleRegistry {
fun registerSTT(provider: STTServiceProvider) {
sttProviders.add(provider)
}
fun sttProvider(modelId: String? = null): STTServiceProvider? {
return sttProviders.firstOrNull { it.canHandle(modelId) }
}
}
// External module registration
// In WhisperKit module:
ModuleRegistry.shared.registerSTT(WhisperSTTProvider())Flow for Reactive Streams:
// Instead of AsyncSequence, use Flow
fun transcribeStream(audioFlow: Flow<ByteArray>): Flow<TranscriptionUpdate> {
return audioFlow.map { audioData ->
// Process audio chunk
TranscriptionUpdate(text = processAudio(audioData), isFinal = false)
}
}Coroutines for Async Operations:
// Instead of async/await, use suspend functions
suspend fun loadModel(modelId: String): ModelLoadResult {
return withContext(Dispatchers.IO) {
modelRepository.loadModel(modelId)
}
}Use sealed classes for type-safe error handling:
sealed class SDKError : Exception() {
data class InvalidApiKey(override val message: String) : SDKError()
data class NetworkError(override val cause: Throwable?) : SDKError()
data class ComponentNotReady(override val message: String) : SDKError()
data class InvalidState(override val message: String) : SDKError()
// Result wrapper for operations
sealed class Result<out T> {
data class Success<T>(val value: T) : Result<T>()
data class Failure(val error: SDKError) : Result<Nothing>()
}
}Always use structured types instead of strings:
// Component configuration
data class STTConfiguration(
val modelId: String,
val language: Language = Language.EN,
val enableVAD: Boolean = true,
val audioFormat: AudioFormat = AudioFormat.PCM_16BIT
) : ComponentConfiguration {
override fun validate() {
require(modelId.isNotBlank()) { "Model ID cannot be blank" }
}
}
// Enum for type safety
enum class Language(val code: String) {
EN("en"), ES("es"), FR("fr"), DE("de"), JA("ja")
}
enum class AudioFormat { PCM_16BIT, PCM_24BIT, FLAC, MP3 }Provider Pattern with Type Safety:
interface STTServiceProvider {
suspend fun createSTTService(configuration: STTConfiguration): STTService
fun canHandle(modelId: String?): Boolean
val name: String
}
// Registration in app initialization:
ModuleRegistry.registerSTT(WhisperSTTProvider())
ModuleRegistry.registerLLM(LlamaProvider())Centralized Event System:
// Component publishes events
eventBus.publish(ComponentInitializationEvent.ComponentReady(
component = SDKComponent.STT,
modelId = "whisper-base"
))
// Other components subscribe to events
EventBus.componentEvents
.filterIsInstance<ComponentInitializationEvent.ComponentReady>()
.filter { it.component == SDKComponent.STT }
.collect { handleSTTReady(it) }Service Creation with Fallbacks:
class STTComponent(configuration: STTConfiguration) : BaseComponent<STTService>(configuration) {
override suspend fun createService(): STTService {
// Try external providers first
val provider = ModuleRegistry.sttProvider(configuration.modelId)
return provider?.createSTTService(configuration)
?: throw SDKError.ComponentNotAvailable("No STT provider available for model: ${configuration.modelId}")
}
}Component Lifecycle:
abstract class BaseComponent<TService : Any> {
override suspend fun cleanup() {
// Proper resource cleanup
performCleanup()
service = null
serviceContainer = null // Allow GC
currentStage = null
}
protected open suspend fun performCleanup() {
// Override for component-specific cleanup
}
}Service Container Memory Management:
class ServiceContainer {
// Use lazy initialization to avoid memory pressure
val modelManager: ModelManager by lazy {
ModelManager(fileSystem, downloadService)
}
suspend fun cleanup() {
// Cleanup components in reverse dependency order
sttComponent.cleanup()
vadComponent.cleanup()
}
}Android optimizations in androidMain:
actual fun createFileSystem(): FileSystem = AndroidFileSystem().apply {
// Configure for Android-specific optimizations
enableFileWatcher = false // Reduce battery usage
cacheStrategy = CacheStrategy.MEMORY_FIRST
}JVM optimizations in jvmMain:
actual fun createHttpClient(): HttpClient = HttpClient {
engine {
// JVM-specific HTTP client configuration
threadsCount = 4
pipelining = true
}
}class STTComponentTest {
@Test
fun `should initialize successfully with valid configuration`() = runTest {
val config = STTConfiguration(modelId = "whisper-base")
val component = STTComponent(config)
component.initialize()
assertEquals(ComponentState.READY, component.state)
assertTrue(component.isReady)
}
@Test
fun `should emit events during initialization`() = runTest {
val events = mutableListOf<ComponentEvent>()
val job = launch {
EventBus.componentEvents.collect { events.add(it) }
}
val component = STTComponent(STTConfiguration(modelId = "whisper-base"))
component.initialize()
assertTrue(events.any { it is ComponentInitializationEvent.ComponentReady })
job.cancel()
}
}class MockSTTProvider : STTServiceProvider {
override val name = "MockSTT"
override suspend fun createSTTService(configuration: STTConfiguration): STTService {
return MockSTTService()
}
override fun canHandle(modelId: String?): Boolean = true
}
// In test setup:
ModuleRegistry.clear()
ModuleRegistry.registerSTT(MockSTTProvider())- Business Logic in commonMain: Keep all core logic platform-agnostic
- expect/actual for Platform APIs: Only use for truly platform-specific code
- Flow over AsyncSequence: Use Kotlin's reactive streams
- Coroutines over async/await: Leverage structured concurrency
- Sealed Classes for Errors: Type-safe error handling
- Data Classes for Models: Strong typing throughout
- ModuleRegistry for Plugins: Extensible architecture
- EventBus for Communication: Decoupled component communication
- Service Container for DI: Centralized dependency management
- Component Lifecycle: Proper initialization and cleanup
These patterns ensure the Kotlin Multiplatform SDK maintains architectural consistency with the iOS implementation while leveraging Kotlin's strengths for cross-platform development.
- The Kotlin Multiplatform SDK is the primary SDK implementation
- Use
./scripts/sdk.shfor all SDK operations - it handles configuration and build complexity - Configuration files (
dev.json,staging.json,prod.json) are git-ignored - use example files as templates - Both SDKs focus on privacy-first, on-device AI with intelligent routing
- Cost optimization is a key feature with real-time tracking
- Pre-commit hooks are configured for code quality enforcement