{"owner":"RunanywhereAI","repo":"runanywhere-sdks","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# AGENTS.md\n\nThis file provides guidance to AI coding assistants (Claude Code, Cursor, etc.) when working with code in this repository.\n- Focus on SIMPLICITY, and following Clean SOLID principles when writing code. Reusability, Clean architecture(not strictly) style, clear separation of concerns.\n\n> **`AGENTS.md` is the real file; each `CLAUDE.md` is a symlink to the `AGENTS.md` beside it.**\n> Editing either name edits the same bytes, so the two can never drift and Claude Code, Cursor, and every\n> other assistant read identical guidance. The symlinks are committed, so a fresh clone recreates them\n> automatically on macOS/Linux, and `scripts/setup/setup.sh` plus the post-checkout/post-merge git hooks\n> re-create any missing link (e.g. on Windows). To add the symlink in a new directory (or repair a broken\n> one), run `bash scripts/validation/gates/check_agents_claude_sync.sh --fix`; a pre-commit hook and the\n> `pr-build.yml` gate fail if any tracked `AGENTS.md` is missing its committed `CLAUDE.md` symlink.\n\n### Resource discipline\nUse the machine's available capacity for local builds and verification instead of defaulting to low worker caps:\n- Use full local capacity by default. Prefer explicit worker counts based on the host CPU count for reproducibility, e.g. `cmake --build <dir> -j \"$(sysctl -n hw.logicalcpu)\"`, `make -j\"$(sysctl -n hw.logicalcpu)\"`, `ninja -j \"$(sysctl -n hw.logicalcpu)\"`, Gradle `--max-workers=\"$(sysctl -n hw.logicalcpu)\"`, and Xcode `-jobs \"$(sysctl -n hw.logicalcpu)\"`.\n- Lower the cap only under real pressure. Scale down if the machine is memory constrained, swapping, thermally throttling, or a build is failing because of resource exhaustion; do not wait solely because load average is above an arbitrary threshold.\n- Parallelize with intent. Running independent light checks or agents in parallel is fine. Avoid uncontrolled process storms, repeated repo-wide scans, or multiple native rebuilds that compete for the same memory-heavy toolchain without a clear benefit.\n- Check `uptime` before a heavy step for situational awareness, then proceed with the worker count that fits the current machine state and user urgency.\n\n### Before starting work.\n- Do NOT write ANY MOCK IMPLEMENTATION unless specified otherwise.\n- DO NOT PLAN or WRITE any unit tests unless specified otherwise.\n- Always in plan mode to make a plan refer to `thoughts/shared/plans/{descriptive_name}.md`.\n- After get the plan, make sure you Write the plan to the appropriate file as mentioned in the guide that you referred to.\n- If the task require external knowledge or certain package, also research to get latest knowledge (Use Task tool for research)\n- Don't over plan it, always think MVP.\n- Once you write the plan, firstly ask me to review it. Do not continue until I approve the plan.\n### While implementing\n- You should update the plan as you work - check `thoughts/shared/plans/{descriptive_name}.md` if you're running an already created plan via `thoughts/shared/plans/{descriptive_name}.md`\n- 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.\n- 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.\n- Read files FULLY to understand the FULL context. Only use offset/limit when the file is large and you are short on context.\n- When fixing issues focus on SIMPLICITY, and following Clean SOLID principles, do not add complicated logic unless necessary!\n\n## Swift specific rules:\n- Use the latest Swift 6 APIs always.\n- Do not use NSLock as it is outdated.\n\n## Business logic layering rules\n\nThe most important architectural rule in this repo: logic lives at the lowest layer that can serve all consumers.\n\n> Corollary: the SDK must be seamless inside every example app. Each feature/modality (LLM, STT, TTS, VAD, VLM, RAG, LoRA, Voice) is invoked through **one** SDK entry point; the SDK, and below it C++ commons, does all the heavy lifting: segmentation, derivation, download, orchestration, prompt control. If an example app builds a multi-step sequence, hardcodes a model/engine constant, or post-processes model output, that is a bug in the SDK, not the app. Fix it down a layer.\n\n### Decision hierarchy (top = preferred)\n\n1. C++ commons (`core/`). If logic is cross-platform and not I/O-specific, it belongs here. All 5 SDKs get the fix for free. Examples: model lifecycle, registry management, download orchestration, RAG session management, inference routing.\n\n2. Platform SDK layer. If logic is platform-specific I/O or runtime bridging (e.g. Web OPFS persistence, iOS Keychain, Android Keystore, WASM MEMFS mirroring), it belongs in the platform SDK, not the example app. Examples: `OPFSBridge`, platform adapter registration, WASM module broadcast, MEMFS hydration.\n\n3. Example apps. Only UI rendering, tab navigation, and thin SDK API calls. No business logic, no workarounds, no internal SDK knowledge. If you find yourself writing multi-step bootstrap sequences, duplicating internal constants (e.g. filesystem path patterns), or routing around SDK limitations inside an example, stop and fix the SDK instead.\n\n### Concrete rules\n\n- Example apps call SDK APIs directly. `downloadModel()`, `loadModel()`, `ragIngest()` are the right entry points. The SDK handles everything beneath.\n- Never duplicate SDK-internal knowledge in example apps. Framework→directory mappings, OPFS path patterns, MEMFS write helpers, WASM module iteration all belong in the SDK.\n- Never add workaround logic to example apps. If a download path is broken for multi-file models, fix `downloadModel()` in the SDK. If OPFS state needs cold-start hydration, add `hydrateModelRegistry()` to the SDK. Don't paper over SDK bugs in example code.\n- Never add multi-step bootstrap in example views. If a view needs to call `register()` + `reRegisterCatalog()` + `downloadDependency()` + `createPipeline()` before it can work, those steps belong in the SDK's single entry point (e.g. `createPipeline()` should handle its own prerequisites or surface a clear error).\n- When fixing a bug, ask whether it can be fixed at the C++ level. A C++ fix benefits iOS, Android, Flutter, React Native, and Web simultaneously. A TS/Swift/Kotlin fix only helps one SDK. Only go to the platform layer when the fix is genuinely platform-specific.\n\n### iOS SDK as source of truth\n\nWhen the correct behavior is ambiguous, check the iOS Swift implementation first. iOS is the canonical reference for all business logic patterns. Copy the logic exactly and adapt only syntax.\n\n---\n\n## Repository overview\n\nCross-platform on-device AI SDK monorepo. A single C/C++ core (`runanywhere-commons`, ~118K first-party LOC plus ~420K generated proto bindings) implements all AI business logic behind a pure C ABI (`rac_*` prefix). Five platform SDKs are thin bridges that supply platform services (file I/O, HTTP, Keychain, audio) via an inversion-of-control struct and call into the C core for all inference. Protobuf IDL schemas generate type-safe bindings for every language.\n\n**Current version**: `0.20.22` (canonical source: `core/VERSION`)\n\n### SDK implementations\n| SDK | Path | Bridge Mechanism | Platforms |\n|-----|------|-----------------|-----------|\n| Swift | `bindings/swift/` | XCFramework + CRACommons module map | iOS 17.5+, macOS 14.5+ |\n| Kotlin (Android library) | `bindings/kotlin/` | JNI (`librunanywhere_jni.so`) | Android (min 24) |\n| Flutter | `bindings/flutter/` | Dart FFI (`ffi` package) | iOS, Android |\n| React Native | `bindings/react-native/` | NitroModules (JSI HybridObject) | iOS 17.5+, Android arm64 |\n| Web | `bindings/web/` | Emscripten WASM + TypeScript | Browsers (Chrome, Safari, Firefox) |\n\n### Native core\n| Directory | Contents |\n|-----------|----------|\n| `core/` | C/C++ core library: all AI logic, plugin registry, event system |\n| `engines/` | 7 backend plugins: llamacpp, sherpa, onnx, cloud, mlx, qhexrt, neurt |\n| `runtimes/` | 3 runtime adapters: cpu (always), onnxrt, coreml |\n| `idl/` | 23 Protobuf schemas + per-language codegen scripts |\n\n### Consumer applications\nThe four full consumer apps were extracted into standalone repositories (history preserved). They are not in this tree; open PRs against them there.\n\n| App | Repository | Build System |\n|-----|-----------|-------------|\n| iOS | [RunanywhereAI/runanywhere-ios](https://github.com/RunanywhereAI/runanywhere-ios) | SwiftUI + SPM |\n| Android | [RunanywhereAI/runanywhere-android](https://github.com/RunanywhereAI/runanywhere-android) | Gradle/Compose |\n| Web | [RunanywhereAI/runanywhere-web](https://github.com/RunanywhereAI/runanywhere-web) | Vanilla TS + Vite |\n| Electron | [RunanywhereAI/runanywhere-electron](https://github.com/RunanywhereAI/runanywhere-electron) | TS + electron-builder |\n\nTwo example apps remain in-tree:\n\n| App | Path | Build System |\n|-----|------|-------------|\n| Flutter | `bindings/flutter/example/` | Flutter + Dart FFI |\n| React Native | `bindings/react-native/example/` | RN 0.85 + NitroModules |\n\nAll example apps share one visual identity, brand orange `#FF6900` (the logo primary, not the legacy `#FF5500`), documented in `docs/DESIGN_GUIDELINE.md`. Each app hand-maintains a small theme file that mirrors that doc; see the \"Design System\" section in each app's `AGENTS.md`.\n\n### Minimal examples (in-repo harnesses)\nThese are how you verify an SDK change locally, and what monorepo CI builds. Each consumes the SDK from local source, so an edit is visible without staging or publishing anything.\n\n| SDK | Path | How it consumes the SDK |\n|-----|------|-------------------------|\n| Swift | `bindings/swift/example/` | SwiftPM package depending on the repo-root manifest (`RUNANYWHERE_USE_LOCAL_NATIVES=1`) |\n| Kotlin | `bindings/kotlin/example/` | Gradle composite build (`includeBuild` + `dependencySubstitution`), no AAR staging |\n| Web | `bindings/web/example/` | Vite aliases + `tsconfig` paths into `packages/*/src`; `RAC_USE_INSTALLED_SDK=1` switches to installed tarballs |\n\nEach is deliberately small: one prompt in, one streamed completion out. They are contributor harnesses, not showcases: feature-complete UI belongs in the consumer repos above.\n\n---\n\n## Cross-platform architecture\n\nFour layers, top to bottom.\n\n`idl/*.proto` is the schema root. `idl/codegen/generate_all.sh` emits `*.pb.swift`, Wire\nKotlin, and ts-proto / protoc-gen-dart output, all committed.\n\nPlatform SDKs are thin bridges: they supply platform services and call the C ABI.\n\n| SDK | Bridge |\n|---|---|\n| Swift | XCFramework |\n| Kotlin | JNI |\n| Flutter | Dart FFI |\n| React Native | NitroModules |\n| Web | WASM |\n\nAll five reach `runanywhere-commons` through the `rac_*` C API. Commons holds the component\nlayer (lifecycle), the service layer (dispatch), and the plugin registry, and reaches engines\nthrough `rac_engine_vtable_t` v9.\n\n| Engine | Primitives |\n|---|---|\n| llamacpp | LLM, VLM |\n| sherpa-onnx | STT, TTS, VAD |\n| onnx | Embed, Segment |\n| qhexrt | Hexagon NPU |\n| neurt, cloud | Apple Neural Engine, HTTP |\n\n### Key architectural patterns\n\nPlatform adapter IoC: `rac_platform_adapter_t` is a flat C struct of function pointers populated by each SDK before calling `rac_init()`. C++ never calls platform APIs directly: all file I/O, HTTP, Keychain, logging, and memory queries pass through this struct.\n\nTwo-phase SDK initialization: All SDKs follow the same pattern: Phase 1 (synchronous: register platform adapter, load native libs, configure logging) then Phase 2 (async: authenticate, register device, fetch model assignments, discover downloaded models).\n\nPlugin ABI v9: Every backend publishes a `rac_engine_vtable_t` with 10 active primitive slots (`llm_ops`, `stt_ops`, `tts_ops`, `vad_ops`, `embedding_ops`, `vlm_ops`, `diffusion_ops`, `diarization_ops`, `segmentation_ops`, `rerank_ops`) and 7 reserved slots. LLM publishers may implement `get_stream_token_counts` on `rac_llm_service_ops_t`; when it is NULL, commons estimates counts and marks them as estimated. NULL primitive slot = not supported. `RAC_PLUGIN_API_VERSION = 9u`, and a version mismatch causes immediate rejection. (`rerank_ops`/`RAC_PRIMITIVE_RERANK` was revived as a first-class cross-encoder reranking primitive in ABI v8 at **wire value 11**, promoted from `reserved_slot_2` at the same binary offset; the original wire value 6, retired in ABI v4, stays permanently retired.)\n\nStatic and dynamic plugins: iOS and WASM force `RAC_STATIC_PLUGINS=ON` (no `dlopen`). Android/Linux/macOS default to dynamic loading via `rac_registry_load_plugin()`. Static registration uses `RAC_STATIC_PLUGIN_REGISTER(name)` macro with `-force_load` / `--whole-archive` linker flags.\n\nStreaming fan-out: C++ allows only one proto-byte callback per component handle. Each SDK implements a `HandleFanOut` that multiplexes one C callback to multiple subscribers (Swift `AsyncStream`, Kotlin `Flow`, Dart `StreamController`, TS `AsyncIterable`).\n\nProto types are canonical: All structured types (environments, model formats, error codes, voice events, LLM stream events) are defined in `idl/*.proto` and code-generated per SDK. Never hand-write enum values; use the generated types and typealiases.\n\n---\n\n## Building the native core\n\nThe root `CMakeLists.txt` is the single entry point for all native builds. Version is read from `core/VERSION`.\n\n### CMake presets (`CMakePresets.json`)\n\n```bash\n# macOS (development)\ncmake --preset macos-debug && cmake --build build/macos-debug\nctest --preset macos-debug\n\n# macOS release\ncmake --preset macos-release && cmake --build build/macos-release\n\n# Linux (with sanitizer)\ncmake --preset linux-asan && cmake --build build/linux-asan\n\n# iOS (device + simulator)\ncmake --preset ios-device && cmake --build build/ios-device --config Release\ncmake --preset ios-simulator && cmake --build build/ios-simulator --config Release\n\n# Android (requires ANDROID_NDK_HOME)\ncmake --preset android-arm64 && cmake --build build/android-arm64\n\n# WASM (requires EMSDK)\ncmake --preset wasm && cmake --build build/wasm\n```\n\n### Cross-platform build scripts\n\n```bash\n# iOS: Build XCFrameworks for all slices → bindings/swift/Binaries/\n./bindings/swift/scripts/build-core-xcframework.sh\n# Also syncs XCFrameworks into React Native and Flutter SDK plugin dirs\n\n# Android: Build .so for all ABIs → copies into all SDK jniLibs/ dirs\n./scripts/build/build-core-android.sh\n\n# WASM: Build racommons-llamacpp.wasm → bindings/web/packages/llamacpp/wasm/\n./bindings/web/scripts/build-core-wasm.sh\n\n# Version bump across all manifests\n./scripts/release/sync-versions.sh <version>\n\n# Update Package.swift checksums after building release zips\n./bindings/swift/scripts/sync-checksums.sh <zip_dir>\n\n# Cut the runanywhere-swift SPM distribution repo at the current version\n./bindings/swift/scripts/sync-dist-repo.sh --zips <zip_dir> --tag <checkout>\n\n# Full IDL codegen (requires protoc toolchain; see scripts/setup/setup-toolchain.sh)\n./idl/codegen/generate_all.sh\n```\n\n### Native build outputs\n\n| Platform | Output | Consumed by |\n|----------|--------|------------|\n| iOS | `bindings/swift/Binaries/*.xcframework` | Swift SPM, Flutter iOS, RN iOS |\n| Android | `*/jniLibs/{abi}/*.so` | Kotlin, Flutter Android, RN Android |\n| WASM | `bindings/web/packages/llamacpp/wasm/*.wasm` | Web SDK |\n| macOS/Linux | `build/<preset>/librac_commons.a` or `.so` | Local dev/testing |\n\n---\n\n## SDK development commands\n\n### C++ core (`core/`)\n\nSee `core/AGENTS.md` for detailed architecture and C++ conventions.\n\n```bash\n# Build with backends + tests\ncmake -B build -DRAC_BUILD_TESTS=ON -DRAC_BUILD_BACKENDS=ON -DCMAKE_BUILD_TYPE=Debug\ncmake --build build\nctest --test-dir build --output-on-failure\n\n# Lint C++\ncore/scripts/lint-cpp.sh          # Check formatting\ncore/scripts/lint-cpp.sh --fix    # Auto-fix\n```\n\n### Swift SDK (`bindings/swift/`)\n\n```bash\n# Build (requires XCFrameworks in bindings/swift/Binaries/)\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift build\n\n# Run tests\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift test\n\n# Build for specific platform\nxcodebuild build -scheme RunAnywhere -destination 'platform=iOS Simulator,name=iPhone 16 Pro'\n\n# Run SwiftLint\nswiftlint\n```\n\n### Kotlin SDK (`bindings/kotlin/`)\n\n```bash\ncd bindings/kotlin/\n\n# Build (Android library)\n./gradlew build\n\n# Individual targets\n./gradlew assembleDebug        # Android Debug AAR\n./gradlew assembleRelease      # Android Release AAR\n\n# Test\n./gradlew testDebugUnitTest    # Android unit tests\n./gradlew test                 # All unit tests (debug + release variants)\n\n# Publish to Maven Local\n./gradlew publishToMavenLocal\n\n# Native library management (C++ JNI)\n./gradlew setupLocalDevelopment   # First-time: builds C++ JNI libs (runs scripts/build/build-core-android.sh)\n./gradlew rebuildCommons          # Rebuild C++ after source changes\n./gradlew downloadJniLibs         # Download pre-built .so from GitHub Releases\n```\n\nBuild outputs: `build/outputs/aar/runanywhere-kotlin-{debug,release}.aar` (plus sub-module AARs under `modules/runanywhere-core-{llamacpp,onnx}/build/outputs/aar/`).\n\nBackend modules at `modules/runanywhere-core-llamacpp/` and `modules/runanywhere-core-onnx/`.\n\n### Flutter SDK (`bindings/flutter/`)\n\nManaged by Melos. Four packages: `runanywhere` (core), `runanywhere_llamacpp`, `runanywhere_onnx`, `runanywhere_qhexrt`.\n\n```bash\ncd bindings/flutter/\nmelos bootstrap         # Install deps across all packages\nmelos run analyze       # Dart analysis\n```\n\n### React Native SDK (`bindings/react-native/`)\n\nManaged by Yarn Berry 3.6.1. Three packages: `@runanywhere/core`, `@runanywhere/llamacpp`, `@runanywhere/onnx`.\n\n```bash\ncd bindings/react-native/\nyarn install\nyarn typecheck          # Primary verification gate\n```\n\nNitroModules specs in `packages/core/src/specs/*.nitro.ts`. After spec changes, run `nitrogen` to regenerate C++ bridge code, then `scripts/fix-nitrogen-output.js`.\n\n### Web SDK (`bindings/web/`)\n\nThree npm packages: `@runanywhere/web` (core TS), `@runanywhere/web-llamacpp` (WASM), `@runanywhere/web-onnx` (Sherpa WASM).\n\n```bash\ncd bindings/web/\n\n# Build WASM (requires Emscripten SDK)\nnpm run build:wasm -- --core\nnpm run build:wasm -- --llamacpp                 # CPU variant\nnpm run build:wasm -- --webgpu                   # WebGPU variant\nnpm run build:wasm -- --onnx                     # ONNX + Sherpa artifact\n\n# Build TypeScript\nnpm run build\n\n# Type-check\nnpm run typecheck\n```\n\nThe current artifact and deployment contract is maintained in\n`bindings/web/AGENTS.md`; it supersedes historical standalone\n`wasm/sherpa/` paths. Do not use or recreate those removed paths.\n\n### IDL codegen\n\n```bash\n# Install toolchain (protoc, protoc-gen-swift, wire-compiler, ts-proto, etc.)\n./scripts/setup/setup-toolchain.sh\n\n# Regenerate all language bindings\n./idl/codegen/generate_all.sh\n\n# One language (also: kotlin, dart, ts, cpp, python)\n./idl/codegen/generate_all.sh --only swift\n```\n\n### Generated code — what is committed and what is not\n\n**Nothing generated is tracked.** A fresh clone has no C++, Kotlin, Swift,\nTypeScript, Dart, React Native or Python bindings until codegen runs.\n`./scripts/setup/setup.sh` runs it first for exactly that reason; `./run codegen`\nruns it on demand. The three hooks below mean almost nobody has to know that.\n\n| tree | who generates it | when |\n|---|---|---|\n| `core/src/generated/proto/` (76 files, ~336k lines) | `core/CMakeLists.txt`, at **configure** time when the files are absent | every `cmake --preset …`, i.e. all ~29 native CI runner instances, the Electron addon, the Python wheel, rcli and WASM |\n| `core/include/rac/rac_defaults_generated.h` | same block | same. A SHIPPED public header: `install(DIRECTORY include/)` puts it in the XCFramework `Headers/` and the Linux/Windows dist, and five shipped `rac_{llm,stt,tts,vad,vlm}_types.h` `#include` it — so it must exist before packaging, which configure time guarantees |\n| `bindings/kotlin/.../sdk/generated/` (373 files) | the `generateIdlKotlinBindings` Gradle task, wired into `preBuild` | every `assemble*` / `compile*Kotlin` / `test*` / ktlint / detekt, including JitPack |\n| `bindings/swift/Sources/RunAnywhere/Generated/` | `sync-dist-repo.sh` | ships in the SwiftPM tag |\n| `bindings/proto-ts/src/` and `dist/` | each `package-sdk.sh` | `dist` ships in 7 npm packages |\n| `bindings/flutter/packages/runanywhere/lib/generated/` | `bindings/flutter/scripts/package-sdk.sh` | ships in the pub package |\n| the two `RADefaultsPool.kt` under flutter/ and react-native/ | the same packaging scripts | ship inside the pub / npm packages |\n| `bindings/python/runanywhere/_proto/`, `_generated_{errors,defaults}.py` | the in-tree PEP 517 backend | ship in the sdist + wheel |\n\nTwo CI jobs read generated C/C++ **without** configuring CMake and therefore carry an\nexplicit `generate-idl` step with `cpp`: `pr-build.rn-typecheck` (`-fsyntax-only` over\n`core/include`) and `release.native_rcli_macos` (`swift build` over the root\n`Package.swift`).\n\n`idl/codegen/generated_trees.txt` is the machine-readable version of that table, plus\nthe eight hand-written files that live *inside* those trees and stay tracked (the\n`.gitignore` negations exist for them, and `check_generated_trees.sh` fails if one\never stops being tracked — and fails the other way if a generated file becomes tracked).\n\n**The toolchain is downloaded, not assumed.** protoc stamps its own patch version into\nevery C++ header (`#if PROTOBUF_VERSION != 7035001`) and every ts-proto banner, and Wire\nrenames files between releases, so the output is a function of the tool versions and not\nonly of the schemas. The package managers this repo would otherwise reach for do not offer\nthat guarantee — `brew install protobuf` gives whatever is current, `apt-get install\nprotobuf-compiler` gives whatever the distro froze, neither selects a per-platform archive\nby checksum, and Homebrew's `wire` is a different product entirely. protobuf and Maven\nCentral both publish immutable per-platform archives, so the pins are *obtainable*:\n\n| script | resolves | pinned by | verified against |\n|---|---|---|---|\n| `idl/codegen/bootstrap_protoc.sh` | protoc | `core/VERSIONS::PROTOC_VERSION` | `idl/codegen/protoc.sha256` |\n| `idl/codegen/bootstrap_wire.sh` | wire-compiler | `core/VERSIONS::WIRE_VERSION` | `idl/codegen/wire.sha256` |\n| `idl/codegen/bootstrap_pyproto.sh` | a python3 with `google.protobuf` + `yaml` | `core/VERSIONS::PYTHON_PROTOBUF_VERSION` | pip, into a cached venv |\n\nEach prints one path on stdout, uses a matching tool already on `PATH` when there is one,\ncaches under `${XDG_CACHE_HOME:-~/.cache}/runanywhere/`, and refuses to install anything\nwhose checksum is not recorded — so bumping a pin without refreshing the `.sha256` file is\na hard error rather than an unverified download. `RAC_PROTOC` / `RAC_WIRE_COMPILER` /\n`RAC_PYTHON` override; `RAC_PROTOC_NO_DOWNLOAD=1`, `RAC_WIRE_NO_DOWNLOAD=1` and\n`RAC_PY_NO_INSTALL=1` make an air-gapped host fail loudly instead of reaching out.\n\n**Every publish path generates before packaging.** A de-committed tree that ships\ninside an artifact must exist at pack time or the published package is broken in a\nway that no build step notices — `npm pack` packs an empty `dist/`, `flutter pub\npublish --dry-run` validates a package with no `lib/generated/`, and a Python wheel\ninstalls fine and fails at `import`. So each packaging script calls\n`idl/codegen/ensure_generated.sh --only <lang>` first, and the Python SDK carries an\nin-tree PEP 517 backend (`bindings/python/_build/`) so even a bare `pip install`\ncannot skip it.\n\n**Schema version.** `idl/VERSION` is hand-maintained semver for the `.proto` surface;\n`idl/SCHEMA_LOCK` is machine-written by `generate_all.sh` and records a digest of\nevery `idl/*.proto`. Because it is tracked and the bindings are not, the lock is the\ndrift signal: editing a schema without re-running codegen leaves it stale, and CI\nfails. Changing the schema without bumping `idl/VERSION` also fails.\n\n```bash\n./idl/codegen/schema_lock.sh --print   # which IDL is this checkout?\n./idl/codegen/ci-drift-check.sh        # the whole gate, exactly as CI runs it\n```\n\nCI `idl-drift-check.yml` is **generate, then verify** — not \"regenerate and diff\",\nwhich cannot fail for an ignored file.\n\n---\n\n## Example app commands\n\n### Swift minimal example\n\n```bash\ncd bindings/swift/example/\n\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift build\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift run\n```\n\nRequires the XCFrameworks in `bindings/swift/Binaries/` (`RACommons`, `RABackendLLAMACPP`, `RABackendONNX`, `RABackendSherpa`). Build them with `./bindings/swift/scripts/build-core-xcframework.sh`. `./run example ios {build|run|clean}` wraps this.\n\nSDK logs (in a separate terminal):\n\n```bash\nlog stream --predicate 'subsystem CONTAINS \"com.runanywhere\"' --info --debug\n```\n\n### Kotlin minimal example\n\n```bash\ncd bindings/kotlin/example/\n\n./gradlew :app:assembleDebug   # Build\n./gradlew :app:installDebug    # Install on device/emulator\n```\n\n`settings.gradle.kts` pulls `bindings/kotlin` in as a **composite build** with `dependencySubstitution`, so Gradle recompiles the SDK from source on every app build and its transitive runtime deps (coroutines, OkHttp, Wire) come along automatically. There is no AAR staging step.\n\n- `./run sdk commons build-android` builds the commons `.so` for all Android ABIs (needed once, and after any C++ change; `runanywhere.useLocalNatives=true` expects them under `src/main/jniLibs/`).\n- `./run example android build` runs `:app:assembleDebug`.\n- `./run example android install` runs `:app:installDebug` and launches.\n\n### Web minimal example\n\n```bash\ncd bindings/web/example/\n\nnpm install\nnpm run typecheck\nnpm run dev          # Vite dev server at port 3000 (COOP/COEP set by vite.config.ts)\nnpm run build        # Production bundle in dist/\nnpm run preview      # Serve dist/ on port 3000\n```\n\nRequires the four canonical WASM pairs (`npm run build:wasm:all` from `bindings/web/`); the build fails naming the missing files rather than emitting a broken bundle. `SharedArrayBuffer` needs cross-origin isolation (COOP + COEP).\n\nThe example publishes `window.__RUNANYWHERE_SDK__` and `window.__RUNANYWHERE_AI_READY__`, the readiness contract `bindings/web/tests/browser/` probes. `RA_E2E_APP_DIR` points Playwright at a different app (e.g. a checkout of `RunanywhereAI/runanywhere-web` for the full release journey).\n\n### Flutter example\n\n```bash\ncd bindings/flutter/example/\n\nflutter pub get\nflutter run\nflutter run -d \"iPhone 16 Pro\"\n./scripts/verify.sh            # pub get + analyze + APK build\nRUN_IOS=1 ./scripts/verify.sh  # Also builds iOS\n```\n\n### React Native example\n\n```bash\ncd bindings/react-native/example/\n\nyarn install\nyarn start          # Metro bundler\nyarn ios            # iOS simulator\nyarn android        # Android device\nyarn typecheck      # Primary verification gate\n./scripts/verify.sh # typecheck + optional builds\n```\n\nHermes caveat: Does not support `for await...of` with NitroModules async iterables. Use manual `iterator.next()` loops.\n\n---\n\n## Version management\n\nCanonical version: `core/VERSION` (single-line file, e.g. `0.20.0`).\n\n```bash\n# Bump everywhere: VERSION, Package.swift, gradle.properties, package.json, pubspec.yaml\n./scripts/release/sync-versions.sh 0.20.0\n```\n\nRelease lifecycle: `sync-versions.sh` → PR with `release:minor` label → merge → `auto-tag.yml` pushes `v0.20.0` tag → `release.yml` builds all artifacts and creates draft GitHub Release → cut the Swift distribution repo, below.\n\n### Cutting `runanywhere-swift` (required, every release)\n\n[`RunanywhereAI/runanywhere-swift`](https://github.com/RunanywhereAI/runanywhere-swift)\nis a generated, Swift-only SPM distribution of `bindings/swift` (Package.swift +\nSources/ + LICENSE + README). It exists so Swift consumers clone ~3 MB instead of\nthe ~340 MB monorepo. Its manifest declares the same remote binaryTargets\nagainst the same release assets on `runanywhere-sdks`, with the same\nchecksums, so the XCFrameworks are never re-uploaded.\n\nIts tag must track every release. Publish `v<version>` here without cutting it\nand `from: \"<version>\"` resolves to nothing for every Swift consumer.\n\n```bash\ngit clone https://github.com/RunanywhereAI/runanywhere-swift.git /tmp/ra-swift\n\n# Regenerate Sources/ + bump sdkVersion/README, sync this release's checksums,\n# commit, and tag (bare semver, no 'v' prefix; SwiftPM `from:` needs that).\n./bindings/swift/scripts/sync-dist-repo.sh \\\n    --zips release-artifacts/native-ios-macos --tag /tmp/ra-swift\n\n# Prove both manifests agree before pushing.\nRUNANYWHERE_SWIFT_DIST_REPO=/tmp/ra-swift \\\n    bash scripts/validation/gates/check_swift_dist_repo_sync.sh\n\ngit -C /tmp/ra-swift push origin main --follow-tags\n```\n\nThis is enforced, not merely documented: once `v<version>` is tagged here,\n`gates/check_swift_dist_repo_sync.sh` fails every PR until `runanywhere-swift`\ncarries the matching tag.\n\n---\n\n## CI/CD Workflows (`.github/workflows/`)\n\n| Workflow | Trigger | Purpose |\n|----------|---------|---------|\n| `pr-build.yml` | PR to main, push to main/feat branch | Parallel native builds (macOS/Linux/iOS/Android) + per-SDK typecheck |\n| `release.yml` | Tag `v*.*.*` or manual | Full artifact build matrix, SDK packaging, consumer validation, draft Release |\n| `auto-tag.yml` | PR merged to main with `release:*` label | Verifies the reviewed semver bump, then pushes that exact git tag |\n| `idl-drift-check.yml` | Changes to `idl/` or generated files | Regenerates protos, fails if `git diff` is non-empty |\n| `legacy-files-blocklist.yml` | All PRs/pushes | Prevents 5 specific deleted files from being re-introduced |\n| `secret-scan.yml` | PRs and pushes to main | Incremental gitleaks scan on diff range |\n| `check-no-pii-logging.yml` | All PRs/pushes to main, master, feat-branch | Regression guard against Android logcat / RAC_LOG_INFO calls that emit signed URLs alongside active-download destination paths |\n\n---\n\n## Key architectural decisions\n\n### iOS SDK is the source of truth\nWhen implementing features in any other SDK (especially Kotlin), always check the iOS Swift implementation first. Copy logic exactly, adapting only for language syntax, not business logic.\n\n### All business logic in C++ commons (or the SDK shared layer)\nPlatform-specific code should only handle: native library loading, platform adapter registration, audio capture/playback, secure storage, and UI. All AI inference, model management, event routing, and pipeline orchestration live in C++ (`runanywhere-commons`) or, when intentionally Kotlin-side, under the Kotlin SDK's shared `src/main/kotlin/com/runanywhere/sdk/` tree.\n\n### Backend registration pattern\nAll SDKs follow the same pattern:\n1. Load the backend native library\n2. Call `rac_backend_*_register()` (which registers the engine's vtable with the plugin registry)\n3. The registry orders registered plugins by base priority, per primitive\n4. On inference, the highest-priority plugin that serves the primitive is selected via `rac_plugin_find()` (or `rac_plugin_find_for_engine()` for a name-pinned engine)\n\nBackend base priorities: qhexrt=150 (QNN-context models only), mlx=110 (Apple), llamacpp=100, sherpa=90, onnx/cloud=50. Selection is plain priority order, with no runtime/format scoring or pinned-engine bonus; an explicit engine name is honored through `rac_plugin_find_for_engine()`.\n\n### HTTP transport is platform-provided\nlibcurl was removed. Each SDK registers a `rac_http_transport_ops_t` vtable: Swift uses URLSession, Kotlin/Flutter/RN use OkHttp (Android) or URLSession (iOS), Web uses `emscripten_fetch`.\n\n### Proto-generated types replace hand-written enums\nAll cross-platform types are defined in `idl/*.proto`. SDKs use typealiases to the generated types (e.g., `typealias SDKEnvironment = RASDKEnvironment` in Swift, `typealias SDKEnvironment = ai.runanywhere.proto.v1.SDKEnvironment` in Kotlin). Never add enum values by hand; modify the `.proto` file and regenerate.\n\n---\n\n## Platform requirements\n\n| Platform | Min Version | Build Tool | Key Versions |\n|----------|------------|------------|--------------|\n| iOS | 17.5 | Xcode 26+ | Swift 6.2 |\n| macOS | 14.5 | Xcode 26+ | Swift 6.2 |\n| Kotlin SDK | Android API 24 | AGP 9.2.1 / Gradle 9.5.0 | Kotlin 2.4.0, NDK 27.3.13750724 |\n| Android example | Android API 24 | AGP 9.2.1 / Gradle 9.6.0 | Kotlin 2.4.0, compile/target SDK 37 |\n| Flutter | 3.44.6 | Melos / AGP 9.0.1 / Gradle 9.1.0 | Dart 3.12.2+, compile/target SDK 36, NDK 28.2.13676358 |\n| React Native | 0.85.3 (min 0.83.1) | Yarn Berry 3.6.1 | NitroModules, Hermes |\n| Web | Chrome 86+ | Vite | Emscripten 6.0.2, Node 24 LTS |\n| C++ Core | N/A | CMake 3.24+ (upstream 4.2+ for the VS 2026 preset) | C++20, Ninja |\n\n---\n\n## Kotlin SDK: critical implementation rules\n\nThe Kotlin SDK (`bindings/kotlin/`) ships as an Android library (`alias(libs.plugins.android.library)` in `bindings/kotlin/build.gradle.kts`), not as a Kotlin Multiplatform module. It targets Android only and consumes the C++ commons core through JNI (`librunanywhere_jni.so`). JVM 17 is the toolchain for the Gradle build itself, not a published target.\n\n### iOS as the source of truth\n**NEVER make assumptions when implementing the Kotlin SDK. ALWAYS refer to the iOS implementation as the definitive source of truth.**\n\n1. **iOS First**: When encountering missing logic or unclear requirements in the Kotlin SDK, check the corresponding iOS implementation, copy the logic exactly, adapt only for Kotlin syntax.\n\n2. **Public API symmetry**: The Kotlin SDK mirrors the Swift `RunAnywhere` surface as an `object RunAnywhere` singleton with extension functions one-per-feature in `src/main/kotlin/com/runanywhere/sdk/public/extensions/`. Add new public API only after the Swift facade has landed.\n\n3. **Platform naming convention**: Android-only adapters keep an explicit `Android` prefix (e.g. `AndroidTTSService.kt`) so file naming makes the target unambiguous if a JVM-only or KMP variant is ever reintroduced.\n\n### Source set layout\n\n```\nbindings/kotlin/\n    src/main/kotlin/        (all Kotlin sources: public API, JNI bridges, generated Wire proto types)\n    src/main/jniLibs/       (prebuilt .so files staged by build-core-android.sh)\n    src/test/kotlin/        (unit tests, no JNI required)\n    modules/runanywhere-core-{llamacpp,onnx}/  (Android library sub-modules that register C++ backends)\n```\n\nStandard Android library layout. There is no `commonMain`/`jvmAndroidMain`/`androidMain`/`jvmMain` hierarchy at this level (the SDK was migrated away from KMP). Any `expect`/`actual` pairs you see in legacy documentation describe the previous topology; the current build is single-target Android. Reviewer-area names like `A-kotlin-common-domain` in `test_workflows/.../SCOPE_MANIFEST.json` are kept for historical filtering and do not imply KMP source sets exist today.\n\n### Cross-SDK alignment\n\n| Concern | iOS Swift | Kotlin (Android) | Flutter | React Native | Web |\n|---------|-----------|------------------|---------|-------------|-----|\n| Entry point | `enum RunAnywhere` | `object RunAnywhere` | `RunAnywhere` (abstract final class with static members) | `RunAnywhere` object | `RunAnywhere` object |\n| Two-phase init | `initialize()` + `completeServicesInitialization()` | Same | Same | Same | Same |\n| Bridge layer | `CppBridge` enum + extensions | `CppBridge` object + extensions | `DartBridge` + `DartBridge*.dart` | `HybridRunAnywhereCore` (Nitro) | `LlamaCppBridge` + `SherpaONNXBridge` |\n| Streaming | `AsyncStream` | `Flow` | `Stream` (via `StreamController`) | `AsyncIterable` (manual iteration) | `AsyncIterable` |\n| Events | `EventBus` (Combine) | `EventBus` (SharedFlow) | `EventBus` (custom pub/sub via dart:async broadcast StreamController) | `EventBus` (NativeEventEmitter) | `EventBus` (custom pub/sub) |\n| Error type | `SDKException` (proto-backed) | `SDKException` (proto-backed) | `SDKException` | `SDKException` | `SDKException` |\n| Secure storage | Keychain | Android Keystore | Keychain (iOS), Android Keystore + atomic no-backup ciphertext files | Keychain (iOS), Android Keystore | localStorage |\n| HTTP transport | URLSession | OkHttp | OkHttp (Android), URLSession (iOS) | OkHttp (Android), URLSession (iOS) | emscripten_fetch / fetch() |\n\n---\n\n## Non-obvious configuration details\n\n`Package.swift`: remote release artifacts are the fail-closed default. Local builds opt into staged XCFrameworks with `RUNANYWHERE_USE_LOCAL_NATIVES=1`; scripts set this explicitly and never rewrite the manifest.\n\n`Package.swift:186-191`: three `.grpc.swift` files are excluded from compilation. They require iOS 18 / macOS 15, above the SDK's minimums. In-process C callback path replaces gRPC.\n\n`gradle.properties`: `runanywhere.useLocalNatives=true` means local `.so` files. CI overrides with `-Prunanywhere.useLocalNatives=false` to download from GitHub Releases.\n\nNDK version: `racNdkVersion=27.3.13750724` (matches `core/VERSIONS::NDK_VERSION`, the single source of truth) is the pin for the Kotlin SDK in `bindings/kotlin/gradle.properties`. NDK 27 is the current LTS line (r27d) and provides 16 KB page-alignment required by Android 15+ (NDK 25.x's 4 KB-aligned `libc++_shared.so` / `libomp.so` would trip Android 16's 16 KB page-size enforcement). Flutter/RN Android build files carry their own `?: \"...\"` fallback literals but the canonical version lives in `VERSIONS`; mirror it whenever bumping.\n\nWeb cross-origin isolation: `SharedArrayBuffer` requires COOP/COEP headers. Safari needs `coi-serviceworker.js` polyfill.\n\nWeb VLM Worker crash recovery: if `rac_vlm_component_process` causes WASM OOM (`\"memory access out of bounds\"`), the Worker auto-recovers by creating a fresh WASM instance on the next `process()` call.\n\nWeb Qwen2-VL WebGPU workaround: Qwen2-VL models produce NaN logits on WebGPU due to f16 M-RoPE overflow. VLM Worker forces CPU WASM for Qwen2-VL even when WebGPU is active.\n\nWeb struct offsets: TypeScript never hard-codes C struct field offsets. `wasm_exports.cpp` exposes `EMSCRIPTEN_KEEPALIVE` offset functions; the `Offsets` proxy reads them at runtime from the WASM module.\n\n---\n\n## Pre-commit hooks\n\n```bash\npre-commit run --all-files        # Run all checks\npre-commit run ios-sdk-swiftlint --all-files  # SwiftLint only\n```\n\nConfigured hooks: gitleaks (secrets), trailing-whitespace, end-of-file-fixer, check-yaml, check-added-large-files (1000 KB max), check-merge-conflict, object file detection, SwiftLint (SDK + example app), periphery (unused code detection).\n\n---\n\n## Active issues\n\n> The old `thoughts/shared/issues/` directory (regressions 001/002/003/005 on\n> `feat/v2-architecture`) does not exist in this tree. Those bullets claimed\n> Swift/Kotlin/Web had collapsed backends into monoliths; those SDKs already ship\n> split backend packages. Do not revive that section from memory.\n\n### Electron (`smonga/electron_upgrade`), in progress\n\nWork is active on branch `smonga/electron_upgrade`. Do not claim packaging or\nper-backend Electron packages are done. Entry points:\n\n- [`thoughts/shared/plans/electron_HANDOFF.md`](thoughts/shared/plans/electron_HANDOFF.md): master state\n- [`thoughts/shared/plans/electron_takeover.md`](thoughts/shared/plans/electron_takeover.md): the remaining executable plan\n\nCurrent shape (honest): TypeScript SDK + example shell are far along; feature\nviews, visual gate, electron-builder packaging, and the backend packaging split\n(#9 / `RAC_HAVE_BACKEND_*` fat addon → runtime plugins) remain open. Parallel\nTracks A/B/C plus Phase 0 commits may be in flight, so check the HANDOFF status\npointer before assuming anything landed.\n\n---\n\n## Cursor Cloud specific instructions\n\n### Environment overview\n\nThis is a cross-platform SDK monorepo. On a Linux cloud VM, the buildable services are:\n\n| Component | Build | Test | Lint | Notes |\n|-----------|-------|------|------|-------|\n| Kotlin SDK (Android target) | `cd bindings/kotlin && ./gradlew compileDebugKotlin -Prunanywhere.useLocalNatives=false` | Android unit tests require device/emulator | `cd bindings/kotlin && ./gradlew ktlintCheck` | Single-target Android library (no KMP). `androidx.annotation` is always available because the build only targets Android. |\n| Web SDK (TypeScript) | `npm run build -w packages/core` (from `bindings/web/`) | N/A | Prefer workspace `npm run typecheck` (builds core `dist/` before backends). Isolated `npm run typecheck -w packages/{llamacpp,onnx}` needs a fresh `npm run build -w packages/core` first, backends resolve `@runanywhere/web/backend` through the gitignored `packages/core/dist` types |\n| Web minimal example | `npm run dev` (from `bindings/web/example/`) | Manual browser testing at `localhost:3000` | N/A | Streams one completion; needs the WASM pairs built |\n| C++ Commons (core) | `cmake -B build ... && cmake --build build` (from `core/`) | `./build/tests/test_core --run-all` (13 tests, no models needed) | N/A | Must use `gcc`/`g++` via `CC=gcc CXX=g++` (clang lacks C++ stdlib headers). Pass `-DRAC_BUILD_PLATFORM=OFF` on Linux |\n| C++ Commons (full backends) | `CC=gcc CXX=g++ ./scripts/build-linux.sh` | Backend tests need downloaded models | N/A | Builds the canonical Linux release preset and packages the staged shared libraries and public headers. |\n| iOS/Swift SDK | Not buildable | Not buildable | Not available | Requires macOS + Xcode |\n| Android emulator | Not runnable | Not runnable | N/A | No KVM support in cloud VM |\n\n### Key gotchas\n\n- **Android SDK**: Installed at `/opt/android-sdk`. `ANDROID_HOME` and `JAVA_HOME` are set in `~/.bashrc`.\n- **JDK 17**: Required by Gradle JVM toolchain. Both JDK 17 and JDK 21 are installed.\n- **`useLocalNatives` flag**: Set to `true` in `gradle.properties`. Pass `-Prunanywhere.useLocalNatives=false` to Gradle to avoid needing Android NDK (downloads pre-built JNI libs from GitHub releases instead of building locally).\n- **C++ compiler**: Default clang on this VM lacks `libc++` headers. Use `gcc`/`g++` via `-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++`.\n- **`local.properties`**: Auto-created at root, `bindings/kotlin/`, and `bindings/kotlin/example/` with `sdk.dir=/opt/android-sdk`.\n- **pre-commit hooks**: Installed via `pre-commit install`. Requires `git config --unset-all core.hooksPath` first if `core.hooksPath` is set.\n\n### Standard commands\n\nSee the rest of this file for comprehensive build/test/lint commands for all SDK platforms. See `CONTRIBUTING.md` for contributor setup flow.\n"},"files":{"AGENTS.md":"# AGENTS.md\n\nThis file provides guidance to AI coding assistants (Claude Code, Cursor, etc.) when working with code in this repository.\n- Focus on SIMPLICITY, and following Clean SOLID principles when writing code. Reusability, Clean architecture(not strictly) style, clear separation of concerns.\n\n> **`AGENTS.md` is the real file; each `CLAUDE.md` is a symlink to the `AGENTS.md` beside it.**\n> Editing either name edits the same bytes, so the two can never drift and Claude Code, Cursor, and every\n> other assistant read identical guidance. The symlinks are committed, so a fresh clone recreates them\n> automatically on macOS/Linux, and `scripts/setup/setup.sh` plus the post-checkout/post-merge git hooks\n> re-create any missing link (e.g. on Windows). To add the symlink in a new directory (or repair a broken\n> one), run `bash scripts/validation/gates/check_agents_claude_sync.sh --fix`; a pre-commit hook and the\n> `pr-build.yml` gate fail if any tracked `AGENTS.md` is missing its committed `CLAUDE.md` symlink.\n\n### Resource discipline\nUse the machine's available capacity for local builds and verification instead of defaulting to low worker caps:\n- Use full local capacity by default. Prefer explicit worker counts based on the host CPU count for reproducibility, e.g. `cmake --build <dir> -j \"$(sysctl -n hw.logicalcpu)\"`, `make -j\"$(sysctl -n hw.logicalcpu)\"`, `ninja -j \"$(sysctl -n hw.logicalcpu)\"`, Gradle `--max-workers=\"$(sysctl -n hw.logicalcpu)\"`, and Xcode `-jobs \"$(sysctl -n hw.logicalcpu)\"`.\n- Lower the cap only under real pressure. Scale down if the machine is memory constrained, swapping, thermally throttling, or a build is failing because of resource exhaustion; do not wait solely because load average is above an arbitrary threshold.\n- Parallelize with intent. Running independent light checks or agents in parallel is fine. Avoid uncontrolled process storms, repeated repo-wide scans, or multiple native rebuilds that compete for the same memory-heavy toolchain without a clear benefit.\n- Check `uptime` before a heavy step for situational awareness, then proceed with the worker count that fits the current machine state and user urgency.\n\n### Before starting work.\n- Do NOT write ANY MOCK IMPLEMENTATION unless specified otherwise.\n- DO NOT PLAN or WRITE any unit tests unless specified otherwise.\n- Always in plan mode to make a plan refer to `thoughts/shared/plans/{descriptive_name}.md`.\n- After get the plan, make sure you Write the plan to the appropriate file as mentioned in the guide that you referred to.\n- If the task require external knowledge or certain package, also research to get latest knowledge (Use Task tool for research)\n- Don't over plan it, always think MVP.\n- Once you write the plan, firstly ask me to review it. Do not continue until I approve the plan.\n### While implementing\n- You should update the plan as you work - check `thoughts/shared/plans/{descriptive_name}.md` if you're running an already created plan via `thoughts/shared/plans/{descriptive_name}.md`\n- 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.\n- 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.\n- Read files FULLY to understand the FULL context. Only use offset/limit when the file is large and you are short on context.\n- When fixing issues focus on SIMPLICITY, and following Clean SOLID principles, do not add complicated logic unless necessary!\n\n## Swift specific rules:\n- Use the latest Swift 6 APIs always.\n- Do not use NSLock as it is outdated.\n\n## Business logic layering rules\n\nThe most important architectural rule in this repo: logic lives at the lowest layer that can serve all consumers.\n\n> Corollary: the SDK must be seamless inside every example app. Each feature/modality (LLM, STT, TTS, VAD, VLM, RAG, LoRA, Voice) is invoked through **one** SDK entry point; the SDK, and below it C++ commons, does all the heavy lifting: segmentation, derivation, download, orchestration, prompt control. If an example app builds a multi-step sequence, hardcodes a model/engine constant, or post-processes model output, that is a bug in the SDK, not the app. Fix it down a layer.\n\n### Decision hierarchy (top = preferred)\n\n1. C++ commons (`core/`). If logic is cross-platform and not I/O-specific, it belongs here. All 5 SDKs get the fix for free. Examples: model lifecycle, registry management, download orchestration, RAG session management, inference routing.\n\n2. Platform SDK layer. If logic is platform-specific I/O or runtime bridging (e.g. Web OPFS persistence, iOS Keychain, Android Keystore, WASM MEMFS mirroring), it belongs in the platform SDK, not the example app. Examples: `OPFSBridge`, platform adapter registration, WASM module broadcast, MEMFS hydration.\n\n3. Example apps. Only UI rendering, tab navigation, and thin SDK API calls. No business logic, no workarounds, no internal SDK knowledge. If you find yourself writing multi-step bootstrap sequences, duplicating internal constants (e.g. filesystem path patterns), or routing around SDK limitations inside an example, stop and fix the SDK instead.\n\n### Concrete rules\n\n- Example apps call SDK APIs directly. `downloadModel()`, `loadModel()`, `ragIngest()` are the right entry points. The SDK handles everything beneath.\n- Never duplicate SDK-internal knowledge in example apps. Framework→directory mappings, OPFS path patterns, MEMFS write helpers, WASM module iteration all belong in the SDK.\n- Never add workaround logic to example apps. If a download path is broken for multi-file models, fix `downloadModel()` in the SDK. If OPFS state needs cold-start hydration, add `hydrateModelRegistry()` to the SDK. Don't paper over SDK bugs in example code.\n- Never add multi-step bootstrap in example views. If a view needs to call `register()` + `reRegisterCatalog()` + `downloadDependency()` + `createPipeline()` before it can work, those steps belong in the SDK's single entry point (e.g. `createPipeline()` should handle its own prerequisites or surface a clear error).\n- When fixing a bug, ask whether it can be fixed at the C++ level. A C++ fix benefits iOS, Android, Flutter, React Native, and Web simultaneously. A TS/Swift/Kotlin fix only helps one SDK. Only go to the platform layer when the fix is genuinely platform-specific.\n\n### iOS SDK as source of truth\n\nWhen the correct behavior is ambiguous, check the iOS Swift implementation first. iOS is the canonical reference for all business logic patterns. Copy the logic exactly and adapt only syntax.\n\n---\n\n## Repository overview\n\nCross-platform on-device AI SDK monorepo. A single C/C++ core (`runanywhere-commons`, ~118K first-party LOC plus ~420K generated proto bindings) implements all AI business logic behind a pure C ABI (`rac_*` prefix). Five platform SDKs are thin bridges that supply platform services (file I/O, HTTP, Keychain, audio) via an inversion-of-control struct and call into the C core for all inference. Protobuf IDL schemas generate type-safe bindings for every language.\n\n**Current version**: `0.20.22` (canonical source: `core/VERSION`)\n\n### SDK implementations\n| SDK | Path | Bridge Mechanism | Platforms |\n|-----|------|-----------------|-----------|\n| Swift | `bindings/swift/` | XCFramework + CRACommons module map | iOS 17.5+, macOS 14.5+ |\n| Kotlin (Android library) | `bindings/kotlin/` | JNI (`librunanywhere_jni.so`) | Android (min 24) |\n| Flutter | `bindings/flutter/` | Dart FFI (`ffi` package) | iOS, Android |\n| React Native | `bindings/react-native/` | NitroModules (JSI HybridObject) | iOS 17.5+, Android arm64 |\n| Web | `bindings/web/` | Emscripten WASM + TypeScript | Browsers (Chrome, Safari, Firefox) |\n\n### Native core\n| Directory | Contents |\n|-----------|----------|\n| `core/` | C/C++ core library: all AI logic, plugin registry, event system |\n| `engines/` | 7 backend plugins: llamacpp, sherpa, onnx, cloud, mlx, qhexrt, neurt |\n| `runtimes/` | 3 runtime adapters: cpu (always), onnxrt, coreml |\n| `idl/` | 23 Protobuf schemas + per-language codegen scripts |\n\n### Consumer applications\nThe four full consumer apps were extracted into standalone repositories (history preserved). They are not in this tree; open PRs against them there.\n\n| App | Repository | Build System |\n|-----|-----------|-------------|\n| iOS | [RunanywhereAI/runanywhere-ios](https://github.com/RunanywhereAI/runanywhere-ios) | SwiftUI + SPM |\n| Android | [RunanywhereAI/runanywhere-android](https://github.com/RunanywhereAI/runanywhere-android) | Gradle/Compose |\n| Web | [RunanywhereAI/runanywhere-web](https://github.com/RunanywhereAI/runanywhere-web) | Vanilla TS + Vite |\n| Electron | [RunanywhereAI/runanywhere-electron](https://github.com/RunanywhereAI/runanywhere-electron) | TS + electron-builder |\n\nTwo example apps remain in-tree:\n\n| App | Path | Build System |\n|-----|------|-------------|\n| Flutter | `bindings/flutter/example/` | Flutter + Dart FFI |\n| React Native | `bindings/react-native/example/` | RN 0.85 + NitroModules |\n\nAll example apps share one visual identity, brand orange `#FF6900` (the logo primary, not the legacy `#FF5500`), documented in `docs/DESIGN_GUIDELINE.md`. Each app hand-maintains a small theme file that mirrors that doc; see the \"Design System\" section in each app's `AGENTS.md`.\n\n### Minimal examples (in-repo harnesses)\nThese are how you verify an SDK change locally, and what monorepo CI builds. Each consumes the SDK from local source, so an edit is visible without staging or publishing anything.\n\n| SDK | Path | How it consumes the SDK |\n|-----|------|-------------------------|\n| Swift | `bindings/swift/example/` | SwiftPM package depending on the repo-root manifest (`RUNANYWHERE_USE_LOCAL_NATIVES=1`) |\n| Kotlin | `bindings/kotlin/example/` | Gradle composite build (`includeBuild` + `dependencySubstitution`), no AAR staging |\n| Web | `bindings/web/example/` | Vite aliases + `tsconfig` paths into `packages/*/src`; `RAC_USE_INSTALLED_SDK=1` switches to installed tarballs |\n\nEach is deliberately small: one prompt in, one streamed completion out. They are contributor harnesses, not showcases: feature-complete UI belongs in the consumer repos above.\n\n---\n\n## Cross-platform architecture\n\nFour layers, top to bottom.\n\n`idl/*.proto` is the schema root. `idl/codegen/generate_all.sh` emits `*.pb.swift`, Wire\nKotlin, and ts-proto / protoc-gen-dart output, all committed.\n\nPlatform SDKs are thin bridges: they supply platform services and call the C ABI.\n\n| SDK | Bridge |\n|---|---|\n| Swift | XCFramework |\n| Kotlin | JNI |\n| Flutter | Dart FFI |\n| React Native | NitroModules |\n| Web | WASM |\n\nAll five reach `runanywhere-commons` through the `rac_*` C API. Commons holds the component\nlayer (lifecycle), the service layer (dispatch), and the plugin registry, and reaches engines\nthrough `rac_engine_vtable_t` v9.\n\n| Engine | Primitives |\n|---|---|\n| llamacpp | LLM, VLM |\n| sherpa-onnx | STT, TTS, VAD |\n| onnx | Embed, Segment |\n| qhexrt | Hexagon NPU |\n| neurt, cloud | Apple Neural Engine, HTTP |\n\n### Key architectural patterns\n\nPlatform adapter IoC: `rac_platform_adapter_t` is a flat C struct of function pointers populated by each SDK before calling `rac_init()`. C++ never calls platform APIs directly: all file I/O, HTTP, Keychain, logging, and memory queries pass through this struct.\n\nTwo-phase SDK initialization: All SDKs follow the same pattern: Phase 1 (synchronous: register platform adapter, load native libs, configure logging) then Phase 2 (async: authenticate, register device, fetch model assignments, discover downloaded models).\n\nPlugin ABI v9: Every backend publishes a `rac_engine_vtable_t` with 10 active primitive slots (`llm_ops`, `stt_ops`, `tts_ops`, `vad_ops`, `embedding_ops`, `vlm_ops`, `diffusion_ops`, `diarization_ops`, `segmentation_ops`, `rerank_ops`) and 7 reserved slots. LLM publishers may implement `get_stream_token_counts` on `rac_llm_service_ops_t`; when it is NULL, commons estimates counts and marks them as estimated. NULL primitive slot = not supported. `RAC_PLUGIN_API_VERSION = 9u`, and a version mismatch causes immediate rejection. (`rerank_ops`/`RAC_PRIMITIVE_RERANK` was revived as a first-class cross-encoder reranking primitive in ABI v8 at **wire value 11**, promoted from `reserved_slot_2` at the same binary offset; the original wire value 6, retired in ABI v4, stays permanently retired.)\n\nStatic and dynamic plugins: iOS and WASM force `RAC_STATIC_PLUGINS=ON` (no `dlopen`). Android/Linux/macOS default to dynamic loading via `rac_registry_load_plugin()`. Static registration uses `RAC_STATIC_PLUGIN_REGISTER(name)` macro with `-force_load` / `--whole-archive` linker flags.\n\nStreaming fan-out: C++ allows only one proto-byte callback per component handle. Each SDK implements a `HandleFanOut` that multiplexes one C callback to multiple subscribers (Swift `AsyncStream`, Kotlin `Flow`, Dart `StreamController`, TS `AsyncIterable`).\n\nProto types are canonical: All structured types (environments, model formats, error codes, voice events, LLM stream events) are defined in `idl/*.proto` and code-generated per SDK. Never hand-write enum values; use the generated types and typealiases.\n\n---\n\n## Building the native core\n\nThe root `CMakeLists.txt` is the single entry point for all native builds. Version is read from `core/VERSION`.\n\n### CMake presets (`CMakePresets.json`)\n\n```bash\n# macOS (development)\ncmake --preset macos-debug && cmake --build build/macos-debug\nctest --preset macos-debug\n\n# macOS release\ncmake --preset macos-release && cmake --build build/macos-release\n\n# Linux (with sanitizer)\ncmake --preset linux-asan && cmake --build build/linux-asan\n\n# iOS (device + simulator)\ncmake --preset ios-device && cmake --build build/ios-device --config Release\ncmake --preset ios-simulator && cmake --build build/ios-simulator --config Release\n\n# Android (requires ANDROID_NDK_HOME)\ncmake --preset android-arm64 && cmake --build build/android-arm64\n\n# WASM (requires EMSDK)\ncmake --preset wasm && cmake --build build/wasm\n```\n\n### Cross-platform build scripts\n\n```bash\n# iOS: Build XCFrameworks for all slices → bindings/swift/Binaries/\n./bindings/swift/scripts/build-core-xcframework.sh\n# Also syncs XCFrameworks into React Native and Flutter SDK plugin dirs\n\n# Android: Build .so for all ABIs → copies into all SDK jniLibs/ dirs\n./scripts/build/build-core-android.sh\n\n# WASM: Build racommons-llamacpp.wasm → bindings/web/packages/llamacpp/wasm/\n./bindings/web/scripts/build-core-wasm.sh\n\n# Version bump across all manifests\n./scripts/release/sync-versions.sh <version>\n\n# Update Package.swift checksums after building release zips\n./bindings/swift/scripts/sync-checksums.sh <zip_dir>\n\n# Cut the runanywhere-swift SPM distribution repo at the current version\n./bindings/swift/scripts/sync-dist-repo.sh --zips <zip_dir> --tag <checkout>\n\n# Full IDL codegen (requires protoc toolchain; see scripts/setup/setup-toolchain.sh)\n./idl/codegen/generate_all.sh\n```\n\n### Native build outputs\n\n| Platform | Output | Consumed by |\n|----------|--------|------------|\n| iOS | `bindings/swift/Binaries/*.xcframework` | Swift SPM, Flutter iOS, RN iOS |\n| Android | `*/jniLibs/{abi}/*.so` | Kotlin, Flutter Android, RN Android |\n| WASM | `bindings/web/packages/llamacpp/wasm/*.wasm` | Web SDK |\n| macOS/Linux | `build/<preset>/librac_commons.a` or `.so` | Local dev/testing |\n\n---\n\n## SDK development commands\n\n### C++ core (`core/`)\n\nSee `core/AGENTS.md` for detailed architecture and C++ conventions.\n\n```bash\n# Build with backends + tests\ncmake -B build -DRAC_BUILD_TESTS=ON -DRAC_BUILD_BACKENDS=ON -DCMAKE_BUILD_TYPE=Debug\ncmake --build build\nctest --test-dir build --output-on-failure\n\n# Lint C++\ncore/scripts/lint-cpp.sh          # Check formatting\ncore/scripts/lint-cpp.sh --fix    # Auto-fix\n```\n\n### Swift SDK (`bindings/swift/`)\n\n```bash\n# Build (requires XCFrameworks in bindings/swift/Binaries/)\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift build\n\n# Run tests\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift test\n\n# Build for specific platform\nxcodebuild build -scheme RunAnywhere -destination 'platform=iOS Simulator,name=iPhone 16 Pro'\n\n# Run SwiftLint\nswiftlint\n```\n\n### Kotlin SDK (`bindings/kotlin/`)\n\n```bash\ncd bindings/kotlin/\n\n# Build (Android library)\n./gradlew build\n\n# Individual targets\n./gradlew assembleDebug        # Android Debug AAR\n./gradlew assembleRelease      # Android Release AAR\n\n# Test\n./gradlew testDebugUnitTest    # Android unit tests\n./gradlew test                 # All unit tests (debug + release variants)\n\n# Publish to Maven Local\n./gradlew publishToMavenLocal\n\n# Native library management (C++ JNI)\n./gradlew setupLocalDevelopment   # First-time: builds C++ JNI libs (runs scripts/build/build-core-android.sh)\n./gradlew rebuildCommons          # Rebuild C++ after source changes\n./gradlew downloadJniLibs         # Download pre-built .so from GitHub Releases\n```\n\nBuild outputs: `build/outputs/aar/runanywhere-kotlin-{debug,release}.aar` (plus sub-module AARs under `modules/runanywhere-core-{llamacpp,onnx}/build/outputs/aar/`).\n\nBackend modules at `modules/runanywhere-core-llamacpp/` and `modules/runanywhere-core-onnx/`.\n\n### Flutter SDK (`bindings/flutter/`)\n\nManaged by Melos. Four packages: `runanywhere` (core), `runanywhere_llamacpp`, `runanywhere_onnx`, `runanywhere_qhexrt`.\n\n```bash\ncd bindings/flutter/\nmelos bootstrap         # Install deps across all packages\nmelos run analyze       # Dart analysis\n```\n\n### React Native SDK (`bindings/react-native/`)\n\nManaged by Yarn Berry 3.6.1. Three packages: `@runanywhere/core`, `@runanywhere/llamacpp`, `@runanywhere/onnx`.\n\n```bash\ncd bindings/react-native/\nyarn install\nyarn typecheck          # Primary verification gate\n```\n\nNitroModules specs in `packages/core/src/specs/*.nitro.ts`. After spec changes, run `nitrogen` to regenerate C++ bridge code, then `scripts/fix-nitrogen-output.js`.\n\n### Web SDK (`bindings/web/`)\n\nThree npm packages: `@runanywhere/web` (core TS), `@runanywhere/web-llamacpp` (WASM), `@runanywhere/web-onnx` (Sherpa WASM).\n\n```bash\ncd bindings/web/\n\n# Build WASM (requires Emscripten SDK)\nnpm run build:wasm -- --core\nnpm run build:wasm -- --llamacpp                 # CPU variant\nnpm run build:wasm -- --webgpu                   # WebGPU variant\nnpm run build:wasm -- --onnx                     # ONNX + Sherpa artifact\n\n# Build TypeScript\nnpm run build\n\n# Type-check\nnpm run typecheck\n```\n\nThe current artifact and deployment contract is maintained in\n`bindings/web/AGENTS.md`; it supersedes historical standalone\n`wasm/sherpa/` paths. Do not use or recreate those removed paths.\n\n### IDL codegen\n\n```bash\n# Install toolchain (protoc, protoc-gen-swift, wire-compiler, ts-proto, etc.)\n./scripts/setup/setup-toolchain.sh\n\n# Regenerate all language bindings\n./idl/codegen/generate_all.sh\n\n# One language (also: kotlin, dart, ts, cpp, python)\n./idl/codegen/generate_all.sh --only swift\n```\n\n### Generated code — what is committed and what is not\n\n**Nothing generated is tracked.** A fresh clone has no C++, Kotlin, Swift,\nTypeScript, Dart, React Native or Python bindings until codegen runs.\n`./scripts/setup/setup.sh` runs it first for exactly that reason; `./run codegen`\nruns it on demand. The three hooks below mean almost nobody has to know that.\n\n| tree | who generates it | when |\n|---|---|---|\n| `core/src/generated/proto/` (76 files, ~336k lines) | `core/CMakeLists.txt`, at **configure** time when the files are absent | every `cmake --preset …`, i.e. all ~29 native CI runner instances, the Electron addon, the Python wheel, rcli and WASM |\n| `core/include/rac/rac_defaults_generated.h` | same block | same. A SHIPPED public header: `install(DIRECTORY include/)` puts it in the XCFramework `Headers/` and the Linux/Windows dist, and five shipped `rac_{llm,stt,tts,vad,vlm}_types.h` `#include` it — so it must exist before packaging, which configure time guarantees |\n| `bindings/kotlin/.../sdk/generated/` (373 files) | the `generateIdlKotlinBindings` Gradle task, wired into `preBuild` | every `assemble*` / `compile*Kotlin` / `test*` / ktlint / detekt, including JitPack |\n| `bindings/swift/Sources/RunAnywhere/Generated/` | `sync-dist-repo.sh` | ships in the SwiftPM tag |\n| `bindings/proto-ts/src/` and `dist/` | each `package-sdk.sh` | `dist` ships in 7 npm packages |\n| `bindings/flutter/packages/runanywhere/lib/generated/` | `bindings/flutter/scripts/package-sdk.sh` | ships in the pub package |\n| the two `RADefaultsPool.kt` under flutter/ and react-native/ | the same packaging scripts | ship inside the pub / npm packages |\n| `bindings/python/runanywhere/_proto/`, `_generated_{errors,defaults}.py` | the in-tree PEP 517 backend | ship in the sdist + wheel |\n\nTwo CI jobs read generated C/C++ **without** configuring CMake and therefore carry an\nexplicit `generate-idl` step with `cpp`: `pr-build.rn-typecheck` (`-fsyntax-only` over\n`core/include`) and `release.native_rcli_macos` (`swift build` over the root\n`Package.swift`).\n\n`idl/codegen/generated_trees.txt` is the machine-readable version of that table, plus\nthe eight hand-written files that live *inside* those trees and stay tracked (the\n`.gitignore` negations exist for them, and `check_generated_trees.sh` fails if one\never stops being tracked — and fails the other way if a generated file becomes tracked).\n\n**The toolchain is downloaded, not assumed.** protoc stamps its own patch version into\nevery C++ header (`#if PROTOBUF_VERSION != 7035001`) and every ts-proto banner, and Wire\nrenames files between releases, so the output is a function of the tool versions and not\nonly of the schemas. The package managers this repo would otherwise reach for do not offer\nthat guarantee — `brew install protobuf` gives whatever is current, `apt-get install\nprotobuf-compiler` gives whatever the distro froze, neither selects a per-platform archive\nby checksum, and Homebrew's `wire` is a different product entirely. protobuf and Maven\nCentral both publish immutable per-platform archives, so the pins are *obtainable*:\n\n| script | resolves | pinned by | verified against |\n|---|---|---|---|\n| `idl/codegen/bootstrap_protoc.sh` | protoc | `core/VERSIONS::PROTOC_VERSION` | `idl/codegen/protoc.sha256` |\n| `idl/codegen/bootstrap_wire.sh` | wire-compiler | `core/VERSIONS::WIRE_VERSION` | `idl/codegen/wire.sha256` |\n| `idl/codegen/bootstrap_pyproto.sh` | a python3 with `google.protobuf` + `yaml` | `core/VERSIONS::PYTHON_PROTOBUF_VERSION` | pip, into a cached venv |\n\nEach prints one path on stdout, uses a matching tool already on `PATH` when there is one,\ncaches under `${XDG_CACHE_HOME:-~/.cache}/runanywhere/`, and refuses to install anything\nwhose checksum is not recorded — so bumping a pin without refreshing the `.sha256` file is\na hard error rather than an unverified download. `RAC_PROTOC` / `RAC_WIRE_COMPILER` /\n`RAC_PYTHON` override; `RAC_PROTOC_NO_DOWNLOAD=1`, `RAC_WIRE_NO_DOWNLOAD=1` and\n`RAC_PY_NO_INSTALL=1` make an air-gapped host fail loudly instead of reaching out.\n\n**Every publish path generates before packaging.** A de-committed tree that ships\ninside an artifact must exist at pack time or the published package is broken in a\nway that no build step notices — `npm pack` packs an empty `dist/`, `flutter pub\npublish --dry-run` validates a package with no `lib/generated/`, and a Python wheel\ninstalls fine and fails at `import`. So each packaging script calls\n`idl/codegen/ensure_generated.sh --only <lang>` first, and the Python SDK carries an\nin-tree PEP 517 backend (`bindings/python/_build/`) so even a bare `pip install`\ncannot skip it.\n\n**Schema version.** `idl/VERSION` is hand-maintained semver for the `.proto` surface;\n`idl/SCHEMA_LOCK` is machine-written by `generate_all.sh` and records a digest of\nevery `idl/*.proto`. Because it is tracked and the bindings are not, the lock is the\ndrift signal: editing a schema without re-running codegen leaves it stale, and CI\nfails. Changing the schema without bumping `idl/VERSION` also fails.\n\n```bash\n./idl/codegen/schema_lock.sh --print   # which IDL is this checkout?\n./idl/codegen/ci-drift-check.sh        # the whole gate, exactly as CI runs it\n```\n\nCI `idl-drift-check.yml` is **generate, then verify** — not \"regenerate and diff\",\nwhich cannot fail for an ignored file.\n\n---\n\n## Example app commands\n\n### Swift minimal example\n\n```bash\ncd bindings/swift/example/\n\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift build\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift run\n```\n\nRequires the XCFrameworks in `bindings/swift/Binaries/` (`RACommons`, `RABackendLLAMACPP`, `RABackendONNX`, `RABackendSherpa`). Build them with `./bindings/swift/scripts/build-core-xcframework.sh`. `./run example ios {build|run|clean}` wraps this.\n\nSDK logs (in a separate terminal):\n\n```bash\nlog stream --predicate 'subsystem CONTAINS \"com.runanywhere\"' --info --debug\n```\n\n### Kotlin minimal example\n\n```bash\ncd bindings/kotlin/example/\n\n./gradlew :app:assembleDebug   # Build\n./gradlew :app:installDebug    # Install on device/emulator\n```\n\n`settings.gradle.kts` pulls `bindings/kotlin` in as a **composite build** with `dependencySubstitution`, so Gradle recompiles the SDK from source on every app build and its transitive runtime deps (coroutines, OkHttp, Wire) come along automatically. There is no AAR staging step.\n\n- `./run sdk commons build-android` builds the commons `.so` for all Android ABIs (needed once, and after any C++ change; `runanywhere.useLocalNatives=true` expects them under `src/main/jniLibs/`).\n- `./run example android build` runs `:app:assembleDebug`.\n- `./run example android install` runs `:app:installDebug` and launches.\n\n### Web minimal example\n\n```bash\ncd bindings/web/example/\n\nnpm install\nnpm run typecheck\nnpm run dev          # Vite dev server at port 3000 (COOP/COEP set by vite.config.ts)\nnpm run build        # Production bundle in dist/\nnpm run preview      # Serve dist/ on port 3000\n```\n\nRequires the four canonical WASM pairs (`npm run build:wasm:all` from `bindings/web/`); the build fails naming the missing files rather than emitting a broken bundle. `SharedArrayBuffer` needs cross-origin isolation (COOP + COEP).\n\nThe example publishes `window.__RUNANYWHERE_SDK__` and `window.__RUNANYWHERE_AI_READY__`, the readiness contract `bindings/web/tests/browser/` probes. `RA_E2E_APP_DIR` points Playwright at a different app (e.g. a checkout of `RunanywhereAI/runanywhere-web` for the full release journey).\n\n### Flutter example\n\n```bash\ncd bindings/flutter/example/\n\nflutter pub get\nflutter run\nflutter run -d \"iPhone 16 Pro\"\n./scripts/verify.sh            # pub get + analyze + APK build\nRUN_IOS=1 ./scripts/verify.sh  # Also builds iOS\n```\n\n### React Native example\n\n```bash\ncd bindings/react-native/example/\n\nyarn install\nyarn start          # Metro bundler\nyarn ios            # iOS simulator\nyarn android        # Android device\nyarn typecheck      # Primary verification gate\n./scripts/verify.sh # typecheck + optional builds\n```\n\nHermes caveat: Does not support `for await...of` with NitroModules async iterables. Use manual `iterator.next()` loops.\n\n---\n\n## Version management\n\nCanonical version: `core/VERSION` (single-line file, e.g. `0.20.0`).\n\n```bash\n# Bump everywhere: VERSION, Package.swift, gradle.properties, package.json, pubspec.yaml\n./scripts/release/sync-versions.sh 0.20.0\n```\n\nRelease lifecycle: `sync-versions.sh` → PR with `release:minor` label → merge → `auto-tag.yml` pushes `v0.20.0` tag → `release.yml` builds all artifacts and creates draft GitHub Release → cut the Swift distribution repo, below.\n\n### Cutting `runanywhere-swift` (required, every release)\n\n[`RunanywhereAI/runanywhere-swift`](https://github.com/RunanywhereAI/runanywhere-swift)\nis a generated, Swift-only SPM distribution of `bindings/swift` (Package.swift +\nSources/ + LICENSE + README). It exists so Swift consumers clone ~3 MB instead of\nthe ~340 MB monorepo. Its manifest declares the same remote binaryTargets\nagainst the same release assets on `runanywhere-sdks`, with the same\nchecksums, so the XCFrameworks are never re-uploaded.\n\nIts tag must track every release. Publish `v<version>` here without cutting it\nand `from: \"<version>\"` resolves to nothing for every Swift consumer.\n\n```bash\ngit clone https://github.com/RunanywhereAI/runanywhere-swift.git /tmp/ra-swift\n\n# Regenerate Sources/ + bump sdkVersion/README, sync this release's checksums,\n# commit, and tag (bare semver, no 'v' prefix; SwiftPM `from:` needs that).\n./bindings/swift/scripts/sync-dist-repo.sh \\\n    --zips release-artifacts/native-ios-macos --tag /tmp/ra-swift\n\n# Prove both manifests agree before pushing.\nRUNANYWHERE_SWIFT_DIST_REPO=/tmp/ra-swift \\\n    bash scripts/validation/gates/check_swift_dist_repo_sync.sh\n\ngit -C /tmp/ra-swift push origin main --follow-tags\n```\n\nThis is enforced, not merely documented: once `v<version>` is tagged here,\n`gates/check_swift_dist_repo_sync.sh` fails every PR until `runanywhere-swift`\ncarries the matching tag.\n\n---\n\n## CI/CD Workflows (`.github/workflows/`)\n\n| Workflow | Trigger | Purpose |\n|----------|---------|---------|\n| `pr-build.yml` | PR to main, push to main/feat branch | Parallel native builds (macOS/Linux/iOS/Android) + per-SDK typecheck |\n| `release.yml` | Tag `v*.*.*` or manual | Full artifact build matrix, SDK packaging, consumer validation, draft Release |\n| `auto-tag.yml` | PR merged to main with `release:*` label | Verifies the reviewed semver bump, then pushes that exact git tag |\n| `idl-drift-check.yml` | Changes to `idl/` or generated files | Regenerates protos, fails if `git diff` is non-empty |\n| `legacy-files-blocklist.yml` | All PRs/pushes | Prevents 5 specific deleted files from being re-introduced |\n| `secret-scan.yml` | PRs and pushes to main | Incremental gitleaks scan on diff range |\n| `check-no-pii-logging.yml` | All PRs/pushes to main, master, feat-branch | Regression guard against Android logcat / RAC_LOG_INFO calls that emit signed URLs alongside active-download destination paths |\n\n---\n\n## Key architectural decisions\n\n### iOS SDK is the source of truth\nWhen implementing features in any other SDK (especially Kotlin), always check the iOS Swift implementation first. Copy logic exactly, adapting only for language syntax, not business logic.\n\n### All business logic in C++ commons (or the SDK shared layer)\nPlatform-specific code should only handle: native library loading, platform adapter registration, audio capture/playback, secure storage, and UI. All AI inference, model management, event routing, and pipeline orchestration live in C++ (`runanywhere-commons`) or, when intentionally Kotlin-side, under the Kotlin SDK's shared `src/main/kotlin/com/runanywhere/sdk/` tree.\n\n### Backend registration pattern\nAll SDKs follow the same pattern:\n1. Load the backend native library\n2. Call `rac_backend_*_register()` (which registers the engine's vtable with the plugin registry)\n3. The registry orders registered plugins by base priority, per primitive\n4. On inference, the highest-priority plugin that serves the primitive is selected via `rac_plugin_find()` (or `rac_plugin_find_for_engine()` for a name-pinned engine)\n\nBackend base priorities: qhexrt=150 (QNN-context models only), mlx=110 (Apple), llamacpp=100, sherpa=90, onnx/cloud=50. Selection is plain priority order, with no runtime/format scoring or pinned-engine bonus; an explicit engine name is honored through `rac_plugin_find_for_engine()`.\n\n### HTTP transport is platform-provided\nlibcurl was removed. Each SDK registers a `rac_http_transport_ops_t` vtable: Swift uses URLSession, Kotlin/Flutter/RN use OkHttp (Android) or URLSession (iOS), Web uses `emscripten_fetch`.\n\n### Proto-generated types replace hand-written enums\nAll cross-platform types are defined in `idl/*.proto`. SDKs use typealiases to the generated types (e.g., `typealias SDKEnvironment = RASDKEnvironment` in Swift, `typealias SDKEnvironment = ai.runanywhere.proto.v1.SDKEnvironment` in Kotlin). Never add enum values by hand; modify the `.proto` file and regenerate.\n\n---\n\n## Platform requirements\n\n| Platform | Min Version | Build Tool | Key Versions |\n|----------|------------|------------|--------------|\n| iOS | 17.5 | Xcode 26+ | Swift 6.2 |\n| macOS | 14.5 | Xcode 26+ | Swift 6.2 |\n| Kotlin SDK | Android API 24 | AGP 9.2.1 / Gradle 9.5.0 | Kotlin 2.4.0, NDK 27.3.13750724 |\n| Android example | Android API 24 | AGP 9.2.1 / Gradle 9.6.0 | Kotlin 2.4.0, compile/target SDK 37 |\n| Flutter | 3.44.6 | Melos / AGP 9.0.1 / Gradle 9.1.0 | Dart 3.12.2+, compile/target SDK 36, NDK 28.2.13676358 |\n| React Native | 0.85.3 (min 0.83.1) | Yarn Berry 3.6.1 | NitroModules, Hermes |\n| Web | Chrome 86+ | Vite | Emscripten 6.0.2, Node 24 LTS |\n| C++ Core | N/A | CMake 3.24+ (upstream 4.2+ for the VS 2026 preset) | C++20, Ninja |\n\n---\n\n## Kotlin SDK: critical implementation rules\n\nThe Kotlin SDK (`bindings/kotlin/`) ships as an Android library (`alias(libs.plugins.android.library)` in `bindings/kotlin/build.gradle.kts`), not as a Kotlin Multiplatform module. It targets Android only and consumes the C++ commons core through JNI (`librunanywhere_jni.so`). JVM 17 is the toolchain for the Gradle build itself, not a published target.\n\n### iOS as the source of truth\n**NEVER make assumptions when implementing the Kotlin SDK. ALWAYS refer to the iOS implementation as the definitive source of truth.**\n\n1. **iOS First**: When encountering missing logic or unclear requirements in the Kotlin SDK, check the corresponding iOS implementation, copy the logic exactly, adapt only for Kotlin syntax.\n\n2. **Public API symmetry**: The Kotlin SDK mirrors the Swift `RunAnywhere` surface as an `object RunAnywhere` singleton with extension functions one-per-feature in `src/main/kotlin/com/runanywhere/sdk/public/extensions/`. Add new public API only after the Swift facade has landed.\n\n3. **Platform naming convention**: Android-only adapters keep an explicit `Android` prefix (e.g. `AndroidTTSService.kt`) so file naming makes the target unambiguous if a JVM-only or KMP variant is ever reintroduced.\n\n### Source set layout\n\n```\nbindings/kotlin/\n    src/main/kotlin/        (all Kotlin sources: public API, JNI bridges, generated Wire proto types)\n    src/main/jniLibs/       (prebuilt .so files staged by build-core-android.sh)\n    src/test/kotlin/        (unit tests, no JNI required)\n    modules/runanywhere-core-{llamacpp,onnx}/  (Android library sub-modules that register C++ backends)\n```\n\nStandard Android library layout. There is no `commonMain`/`jvmAndroidMain`/`androidMain`/`jvmMain` hierarchy at this level (the SDK was migrated away from KMP). Any `expect`/`actual` pairs you see in legacy documentation describe the previous topology; the current build is single-target Android. Reviewer-area names like `A-kotlin-common-domain` in `test_workflows/.../SCOPE_MANIFEST.json` are kept for historical filtering and do not imply KMP source sets exist today.\n\n### Cross-SDK alignment\n\n| Concern | iOS Swift | Kotlin (Android) | Flutter | React Native | Web |\n|---------|-----------|------------------|---------|-------------|-----|\n| Entry point | `enum RunAnywhere` | `object RunAnywhere` | `RunAnywhere` (abstract final class with static members) | `RunAnywhere` object | `RunAnywhere` object |\n| Two-phase init | `initialize()` + `completeServicesInitialization()` | Same | Same | Same | Same |\n| Bridge layer | `CppBridge` enum + extensions | `CppBridge` object + extensions | `DartBridge` + `DartBridge*.dart` | `HybridRunAnywhereCore` (Nitro) | `LlamaCppBridge` + `SherpaONNXBridge` |\n| Streaming | `AsyncStream` | `Flow` | `Stream` (via `StreamController`) | `AsyncIterable` (manual iteration) | `AsyncIterable` |\n| Events | `EventBus` (Combine) | `EventBus` (SharedFlow) | `EventBus` (custom pub/sub via dart:async broadcast StreamController) | `EventBus` (NativeEventEmitter) | `EventBus` (custom pub/sub) |\n| Error type | `SDKException` (proto-backed) | `SDKException` (proto-backed) | `SDKException` | `SDKException` | `SDKException` |\n| Secure storage | Keychain | Android Keystore | Keychain (iOS), Android Keystore + atomic no-backup ciphertext files | Keychain (iOS), Android Keystore | localStorage |\n| HTTP transport | URLSession | OkHttp | OkHttp (Android), URLSession (iOS) | OkHttp (Android), URLSession (iOS) | emscripten_fetch / fetch() |\n\n---\n\n## Non-obvious configuration details\n\n`Package.swift`: remote release artifacts are the fail-closed default. Local builds opt into staged XCFrameworks with `RUNANYWHERE_USE_LOCAL_NATIVES=1`; scripts set this explicitly and never rewrite the manifest.\n\n`Package.swift:186-191`: three `.grpc.swift` files are excluded from compilation. They require iOS 18 / macOS 15, above the SDK's minimums. In-process C callback path replaces gRPC.\n\n`gradle.properties`: `runanywhere.useLocalNatives=true` means local `.so` files. CI overrides with `-Prunanywhere.useLocalNatives=false` to download from GitHub Releases.\n\nNDK version: `racNdkVersion=27.3.13750724` (matches `core/VERSIONS::NDK_VERSION`, the single source of truth) is the pin for the Kotlin SDK in `bindings/kotlin/gradle.properties`. NDK 27 is the current LTS line (r27d) and provides 16 KB page-alignment required by Android 15+ (NDK 25.x's 4 KB-aligned `libc++_shared.so` / `libomp.so` would trip Android 16's 16 KB page-size enforcement). Flutter/RN Android build files carry their own `?: \"...\"` fallback literals but the canonical version lives in `VERSIONS`; mirror it whenever bumping.\n\nWeb cross-origin isolation: `SharedArrayBuffer` requires COOP/COEP headers. Safari needs `coi-serviceworker.js` polyfill.\n\nWeb VLM Worker crash recovery: if `rac_vlm_component_process` causes WASM OOM (`\"memory access out of bounds\"`), the Worker auto-recovers by creating a fresh WASM instance on the next `process()` call.\n\nWeb Qwen2-VL WebGPU workaround: Qwen2-VL models produce NaN logits on WebGPU due to f16 M-RoPE overflow. VLM Worker forces CPU WASM for Qwen2-VL even when WebGPU is active.\n\nWeb struct offsets: TypeScript never hard-codes C struct field offsets. `wasm_exports.cpp` exposes `EMSCRIPTEN_KEEPALIVE` offset functions; the `Offsets` proxy reads them at runtime from the WASM module.\n\n---\n\n## Pre-commit hooks\n\n```bash\npre-commit run --all-files        # Run all checks\npre-commit run ios-sdk-swiftlint --all-files  # SwiftLint only\n```\n\nConfigured hooks: gitleaks (secrets), trailing-whitespace, end-of-file-fixer, check-yaml, check-added-large-files (1000 KB max), check-merge-conflict, object file detection, SwiftLint (SDK + example app), periphery (unused code detection).\n\n---\n\n## Active issues\n\n> The old `thoughts/shared/issues/` directory (regressions 001/002/003/005 on\n> `feat/v2-architecture`) does not exist in this tree. Those bullets claimed\n> Swift/Kotlin/Web had collapsed backends into monoliths; those SDKs already ship\n> split backend packages. Do not revive that section from memory.\n\n### Electron (`smonga/electron_upgrade`), in progress\n\nWork is active on branch `smonga/electron_upgrade`. Do not claim packaging or\nper-backend Electron packages are done. Entry points:\n\n- [`thoughts/shared/plans/electron_HANDOFF.md`](thoughts/shared/plans/electron_HANDOFF.md): master state\n- [`thoughts/shared/plans/electron_takeover.md`](thoughts/shared/plans/electron_takeover.md): the remaining executable plan\n\nCurrent shape (honest): TypeScript SDK + example shell are far along; feature\nviews, visual gate, electron-builder packaging, and the backend packaging split\n(#9 / `RAC_HAVE_BACKEND_*` fat addon → runtime plugins) remain open. Parallel\nTracks A/B/C plus Phase 0 commits may be in flight, so check the HANDOFF status\npointer before assuming anything landed.\n\n---\n\n## Cursor Cloud specific instructions\n\n### Environment overview\n\nThis is a cross-platform SDK monorepo. On a Linux cloud VM, the buildable services are:\n\n| Component | Build | Test | Lint | Notes |\n|-----------|-------|------|------|-------|\n| Kotlin SDK (Android target) | `cd bindings/kotlin && ./gradlew compileDebugKotlin -Prunanywhere.useLocalNatives=false` | Android unit tests require device/emulator | `cd bindings/kotlin && ./gradlew ktlintCheck` | Single-target Android library (no KMP). `androidx.annotation` is always available because the build only targets Android. |\n| Web SDK (TypeScript) | `npm run build -w packages/core` (from `bindings/web/`) | N/A | Prefer workspace `npm run typecheck` (builds core `dist/` before backends). Isolated `npm run typecheck -w packages/{llamacpp,onnx}` needs a fresh `npm run build -w packages/core` first, backends resolve `@runanywhere/web/backend` through the gitignored `packages/core/dist` types |\n| Web minimal example | `npm run dev` (from `bindings/web/example/`) | Manual browser testing at `localhost:3000` | N/A | Streams one completion; needs the WASM pairs built |\n| C++ Commons (core) | `cmake -B build ... && cmake --build build` (from `core/`) | `./build/tests/test_core --run-all` (13 tests, no models needed) | N/A | Must use `gcc`/`g++` via `CC=gcc CXX=g++` (clang lacks C++ stdlib headers). Pass `-DRAC_BUILD_PLATFORM=OFF` on Linux |\n| C++ Commons (full backends) | `CC=gcc CXX=g++ ./scripts/build-linux.sh` | Backend tests need downloaded models | N/A | Builds the canonical Linux release preset and packages the staged shared libraries and public headers. |\n| iOS/Swift SDK | Not buildable | Not buildable | Not available | Requires macOS + Xcode |\n| Android emulator | Not runnable | Not runnable | N/A | No KVM support in cloud VM |\n\n### Key gotchas\n\n- **Android SDK**: Installed at `/opt/android-sdk`. `ANDROID_HOME` and `JAVA_HOME` are set in `~/.bashrc`.\n- **JDK 17**: Required by Gradle JVM toolchain. Both JDK 17 and JDK 21 are installed.\n- **`useLocalNatives` flag**: Set to `true` in `gradle.properties`. Pass `-Prunanywhere.useLocalNatives=false` to Gradle to avoid needing Android NDK (downloads pre-built JNI libs from GitHub releases instead of building locally).\n- **C++ compiler**: Default clang on this VM lacks `libc++` headers. Use `gcc`/`g++` via `-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++`.\n- **`local.properties`**: Auto-created at root, `bindings/kotlin/`, and `bindings/kotlin/example/` with `sdk.dir=/opt/android-sdk`.\n- **pre-commit hooks**: Installed via `pre-commit install`. Requires `git config --unset-all core.hooksPath` first if `core.hooksPath` is set.\n\n### Standard commands\n\nSee the rest of this file for comprehensive build/test/lint commands for all SDK platforms. See `CONTRIBUTING.md` for contributor setup flow.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# AGENTS.md\n\nThis file provides guidance to AI coding assistants (Claude Code, Cursor, etc.) when working with code in this repository.\n- Focus on SIMPLICITY, and following Clean SOLID principles when writing code. Reusability, Clean architecture(not strictly) style, clear separation of concerns.\n\n> **`AGENTS.md` is the real file; each `CLAUDE.md` is a symlink to the `AGENTS.md` beside it.**\n> Editing either name edits the same bytes, so the two can never drift and Claude Code, Cursor, and every\n> other assistant read identical guidance. The symlinks are committed, so a fresh clone recreates them\n> automatically on macOS/Linux, and `scripts/setup/setup.sh` plus the post-checkout/post-merge git hooks\n> re-create any missing link (e.g. on Windows). To add the symlink in a new directory (or repair a broken\n> one), run `bash scripts/validation/gates/check_agents_claude_sync.sh --fix`; a pre-commit hook and the\n> `pr-build.yml` gate fail if any tracked `AGENTS.md` is missing its committed `CLAUDE.md` symlink.\n\n### Resource discipline\nUse the machine's available capacity for local builds and verification instead of defaulting to low worker caps:\n- Use full local capacity by default. Prefer explicit worker counts based on the host CPU count for reproducibility, e.g. `cmake --build <dir> -j \"$(sysctl -n hw.logicalcpu)\"`, `make -j\"$(sysctl -n hw.logicalcpu)\"`, `ninja -j \"$(sysctl -n hw.logicalcpu)\"`, Gradle `--max-workers=\"$(sysctl -n hw.logicalcpu)\"`, and Xcode `-jobs \"$(sysctl -n hw.logicalcpu)\"`.\n- Lower the cap only under real pressure. Scale down if the machine is memory constrained, swapping, thermally throttling, or a build is failing because of resource exhaustion; do not wait solely because load average is above an arbitrary threshold.\n- Parallelize with intent. Running independent light checks or agents in parallel is fine. Avoid uncontrolled process storms, repeated repo-wide scans, or multiple native rebuilds that compete for the same memory-heavy toolchain without a clear benefit.\n- Check `uptime` before a heavy step for situational awareness, then proceed with the worker count that fits the current machine state and user urgency.\n\n### Before starting work.\n- Do NOT write ANY MOCK IMPLEMENTATION unless specified otherwise.\n- DO NOT PLAN or WRITE any unit tests unless specified otherwise.\n- Always in plan mode to make a plan refer to `thoughts/shared/plans/{descriptive_name}.md`.\n- After get the plan, make sure you Write the plan to the appropriate file as mentioned in the guide that you referred to.\n- If the task require external knowledge or certain package, also research to get latest knowledge (Use Task tool for research)\n- Don't over plan it, always think MVP.\n- Once you write the plan, firstly ask me to review it. Do not continue until I approve the plan.\n### While implementing\n- You should update the plan as you work - check `thoughts/shared/plans/{descriptive_name}.md` if you're running an already created plan via `thoughts/shared/plans/{descriptive_name}.md`\n- 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.\n- 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.\n- Read files FULLY to understand the FULL context. Only use offset/limit when the file is large and you are short on context.\n- When fixing issues focus on SIMPLICITY, and following Clean SOLID principles, do not add complicated logic unless necessary!\n\n## Swift specific rules:\n- Use the latest Swift 6 APIs always.\n- Do not use NSLock as it is outdated.\n\n## Business logic layering rules\n\nThe most important architectural rule in this repo: logic lives at the lowest layer that can serve all consumers.\n\n> Corollary: the SDK must be seamless inside every example app. Each feature/modality (LLM, STT, TTS, VAD, VLM, RAG, LoRA, Voice) is invoked through **one** SDK entry point; the SDK, and below it C++ commons, does all the heavy lifting: segmentation, derivation, download, orchestration, prompt control. If an example app builds a multi-step sequence, hardcodes a model/engine constant, or post-processes model output, that is a bug in the SDK, not the app. Fix it down a layer.\n\n### Decision hierarchy (top = preferred)\n\n1. C++ commons (`core/`). If logic is cross-platform and not I/O-specific, it belongs here. All 5 SDKs get the fix for free. Examples: model lifecycle, registry management, download orchestration, RAG session management, inference routing.\n\n2. Platform SDK layer. If logic is platform-specific I/O or runtime bridging (e.g. Web OPFS persistence, iOS Keychain, Android Keystore, WASM MEMFS mirroring), it belongs in the platform SDK, not the example app. Examples: `OPFSBridge`, platform adapter registration, WASM module broadcast, MEMFS hydration.\n\n3. Example apps. Only UI rendering, tab navigation, and thin SDK API calls. No business logic, no workarounds, no internal SDK knowledge. If you find yourself writing multi-step bootstrap sequences, duplicating internal constants (e.g. filesystem path patterns), or routing around SDK limitations inside an example, stop and fix the SDK instead.\n\n### Concrete rules\n\n- Example apps call SDK APIs directly. `downloadModel()`, `loadModel()`, `ragIngest()` are the right entry points. The SDK handles everything beneath.\n- Never duplicate SDK-internal knowledge in example apps. Framework→directory mappings, OPFS path patterns, MEMFS write helpers, WASM module iteration all belong in the SDK.\n- Never add workaround logic to example apps. If a download path is broken for multi-file models, fix `downloadModel()` in the SDK. If OPFS state needs cold-start hydration, add `hydrateModelRegistry()` to the SDK. Don't paper over SDK bugs in example code.\n- Never add multi-step bootstrap in example views. If a view needs to call `register()` + `reRegisterCatalog()` + `downloadDependency()` + `createPipeline()` before it can work, those steps belong in the SDK's single entry point (e.g. `createPipeline()` should handle its own prerequisites or surface a clear error).\n- When fixing a bug, ask whether it can be fixed at the C++ level. A C++ fix benefits iOS, Android, Flutter, React Native, and Web simultaneously. A TS/Swift/Kotlin fix only helps one SDK. Only go to the platform layer when the fix is genuinely platform-specific.\n\n### iOS SDK as source of truth\n\nWhen the correct behavior is ambiguous, check the iOS Swift implementation first. iOS is the canonical reference for all business logic patterns. Copy the logic exactly and adapt only syntax.\n\n---\n\n## Repository overview\n\nCross-platform on-device AI SDK monorepo. A single C/C++ core (`runanywhere-commons`, ~118K first-party LOC plus ~420K generated proto bindings) implements all AI business logic behind a pure C ABI (`rac_*` prefix). Five platform SDKs are thin bridges that supply platform services (file I/O, HTTP, Keychain, audio) via an inversion-of-control struct and call into the C core for all inference. Protobuf IDL schemas generate type-safe bindings for every language.\n\n**Current version**: `0.20.22` (canonical source: `core/VERSION`)\n\n### SDK implementations\n| SDK | Path | Bridge Mechanism | Platforms |\n|-----|------|-----------------|-----------|\n| Swift | `bindings/swift/` | XCFramework + CRACommons module map | iOS 17.5+, macOS 14.5+ |\n| Kotlin (Android library) | `bindings/kotlin/` | JNI (`librunanywhere_jni.so`) | Android (min 24) |\n| Flutter | `bindings/flutter/` | Dart FFI (`ffi` package) | iOS, Android |\n| React Native | `bindings/react-native/` | NitroModules (JSI HybridObject) | iOS 17.5+, Android arm64 |\n| Web | `bindings/web/` | Emscripten WASM + TypeScript | Browsers (Chrome, Safari, Firefox) |\n\n### Native core\n| Directory | Contents |\n|-----------|----------|\n| `core/` | C/C++ core library: all AI logic, plugin registry, event system |\n| `engines/` | 7 backend plugins: llamacpp, sherpa, onnx, cloud, mlx, qhexrt, neurt |\n| `runtimes/` | 3 runtime adapters: cpu (always), onnxrt, coreml |\n| `idl/` | 23 Protobuf schemas + per-language codegen scripts |\n\n### Consumer applications\nThe four full consumer apps were extracted into standalone repositories (history preserved). They are not in this tree; open PRs against them there.\n\n| App | Repository | Build System |\n|-----|-----------|-------------|\n| iOS | [RunanywhereAI/runanywhere-ios](https://github.com/RunanywhereAI/runanywhere-ios) | SwiftUI + SPM |\n| Android | [RunanywhereAI/runanywhere-android](https://github.com/RunanywhereAI/runanywhere-android) | Gradle/Compose |\n| Web | [RunanywhereAI/runanywhere-web](https://github.com/RunanywhereAI/runanywhere-web) | Vanilla TS + Vite |\n| Electron | [RunanywhereAI/runanywhere-electron](https://github.com/RunanywhereAI/runanywhere-electron) | TS + electron-builder |\n\nTwo example apps remain in-tree:\n\n| App | Path | Build System |\n|-----|------|-------------|\n| Flutter | `bindings/flutter/example/` | Flutter + Dart FFI |\n| React Native | `bindings/react-native/example/` | RN 0.85 + NitroModules |\n\nAll example apps share one visual identity, brand orange `#FF6900` (the logo primary, not the legacy `#FF5500`), documented in `docs/DESIGN_GUIDELINE.md`. Each app hand-maintains a small theme file that mirrors that doc; see the \"Design System\" section in each app's `AGENTS.md`.\n\n### Minimal examples (in-repo harnesses)\nThese are how you verify an SDK change locally, and what monorepo CI builds. Each consumes the SDK from local source, so an edit is visible without staging or publishing anything.\n\n| SDK | Path | How it consumes the SDK |\n|-----|------|-------------------------|\n| Swift | `bindings/swift/example/` | SwiftPM package depending on the repo-root manifest (`RUNANYWHERE_USE_LOCAL_NATIVES=1`) |\n| Kotlin | `bindings/kotlin/example/` | Gradle composite build (`includeBuild` + `dependencySubstitution`), no AAR staging |\n| Web | `bindings/web/example/` | Vite aliases + `tsconfig` paths into `packages/*/src`; `RAC_USE_INSTALLED_SDK=1` switches to installed tarballs |\n\nEach is deliberately small: one prompt in, one streamed completion out. They are contributor harnesses, not showcases: feature-complete UI belongs in the consumer repos above.\n\n---\n\n## Cross-platform architecture\n\nFour layers, top to bottom.\n\n`idl/*.proto` is the schema root. `idl/codegen/generate_all.sh` emits `*.pb.swift`, Wire\nKotlin, and ts-proto / protoc-gen-dart output, all committed.\n\nPlatform SDKs are thin bridges: they supply platform services and call the C ABI.\n\n| SDK | Bridge |\n|---|---|\n| Swift | XCFramework |\n| Kotlin | JNI |\n| Flutter | Dart FFI |\n| React Native | NitroModules |\n| Web | WASM |\n\nAll five reach `runanywhere-commons` through the `rac_*` C API. Commons holds the component\nlayer (lifecycle), the service layer (dispatch), and the plugin registry, and reaches engines\nthrough `rac_engine_vtable_t` v9.\n\n| Engine | Primitives |\n|---|---|\n| llamacpp | LLM, VLM |\n| sherpa-onnx | STT, TTS, VAD |\n| onnx | Embed, Segment |\n| qhexrt | Hexagon NPU |\n| neurt, cloud | Apple Neural Engine, HTTP |\n\n### Key architectural patterns\n\nPlatform adapter IoC: `rac_platform_adapter_t` is a flat C struct of function pointers populated by each SDK before calling `rac_init()`. C++ never calls platform APIs directly: all file I/O, HTTP, Keychain, logging, and memory queries pass through this struct.\n\nTwo-phase SDK initialization: All SDKs follow the same pattern: Phase 1 (synchronous: register platform adapter, load native libs, configure logging) then Phase 2 (async: authenticate, register device, fetch model assignments, discover downloaded models).\n\nPlugin ABI v9: Every backend publishes a `rac_engine_vtable_t` with 10 active primitive slots (`llm_ops`, `stt_ops`, `tts_ops`, `vad_ops`, `embedding_ops`, `vlm_ops`, `diffusion_ops`, `diarization_ops`, `segmentation_ops`, `rerank_ops`) and 7 reserved slots. LLM publishers may implement `get_stream_token_counts` on `rac_llm_service_ops_t`; when it is NULL, commons estimates counts and marks them as estimated. NULL primitive slot = not supported. `RAC_PLUGIN_API_VERSION = 9u`, and a version mismatch causes immediate rejection. (`rerank_ops`/`RAC_PRIMITIVE_RERANK` was revived as a first-class cross-encoder reranking primitive in ABI v8 at **wire value 11**, promoted from `reserved_slot_2` at the same binary offset; the original wire value 6, retired in ABI v4, stays permanently retired.)\n\nStatic and dynamic plugins: iOS and WASM force `RAC_STATIC_PLUGINS=ON` (no `dlopen`). Android/Linux/macOS default to dynamic loading via `rac_registry_load_plugin()`. Static registration uses `RAC_STATIC_PLUGIN_REGISTER(name)` macro with `-force_load` / `--whole-archive` linker flags.\n\nStreaming fan-out: C++ allows only one proto-byte callback per component handle. Each SDK implements a `HandleFanOut` that multiplexes one C callback to multiple subscribers (Swift `AsyncStream`, Kotlin `Flow`, Dart `StreamController`, TS `AsyncIterable`).\n\nProto types are canonical: All structured types (environments, model formats, error codes, voice events, LLM stream events) are defined in `idl/*.proto` and code-generated per SDK. Never hand-write enum values; use the generated types and typealiases.\n\n---\n\n## Building the native core\n\nThe root `CMakeLists.txt` is the single entry point for all native builds. Version is read from `core/VERSION`.\n\n### CMake presets (`CMakePresets.json`)\n\n```bash\n# macOS (development)\ncmake --preset macos-debug && cmake --build build/macos-debug\nctest --preset macos-debug\n\n# macOS release\ncmake --preset macos-release && cmake --build build/macos-release\n\n# Linux (with sanitizer)\ncmake --preset linux-asan && cmake --build build/linux-asan\n\n# iOS (device + simulator)\ncmake --preset ios-device && cmake --build build/ios-device --config Release\ncmake --preset ios-simulator && cmake --build build/ios-simulator --config Release\n\n# Android (requires ANDROID_NDK_HOME)\ncmake --preset android-arm64 && cmake --build build/android-arm64\n\n# WASM (requires EMSDK)\ncmake --preset wasm && cmake --build build/wasm\n```\n\n### Cross-platform build scripts\n\n```bash\n# iOS: Build XCFrameworks for all slices → bindings/swift/Binaries/\n./bindings/swift/scripts/build-core-xcframework.sh\n# Also syncs XCFrameworks into React Native and Flutter SDK plugin dirs\n\n# Android: Build .so for all ABIs → copies into all SDK jniLibs/ dirs\n./scripts/build/build-core-android.sh\n\n# WASM: Build racommons-llamacpp.wasm → bindings/web/packages/llamacpp/wasm/\n./bindings/web/scripts/build-core-wasm.sh\n\n# Version bump across all manifests\n./scripts/release/sync-versions.sh <version>\n\n# Update Package.swift checksums after building release zips\n./bindings/swift/scripts/sync-checksums.sh <zip_dir>\n\n# Cut the runanywhere-swift SPM distribution repo at the current version\n./bindings/swift/scripts/sync-dist-repo.sh --zips <zip_dir> --tag <checkout>\n\n# Full IDL codegen (requires protoc toolchain; see scripts/setup/setup-toolchain.sh)\n./idl/codegen/generate_all.sh\n```\n\n### Native build outputs\n\n| Platform | Output | Consumed by |\n|----------|--------|------------|\n| iOS | `bindings/swift/Binaries/*.xcframework` | Swift SPM, Flutter iOS, RN iOS |\n| Android | `*/jniLibs/{abi}/*.so` | Kotlin, Flutter Android, RN Android |\n| WASM | `bindings/web/packages/llamacpp/wasm/*.wasm` | Web SDK |\n| macOS/Linux | `build/<preset>/librac_commons.a` or `.so` | Local dev/testing |\n\n---\n\n## SDK development commands\n\n### C++ core (`core/`)\n\nSee `core/AGENTS.md` for detailed architecture and C++ conventions.\n\n```bash\n# Build with backends + tests\ncmake -B build -DRAC_BUILD_TESTS=ON -DRAC_BUILD_BACKENDS=ON -DCMAKE_BUILD_TYPE=Debug\ncmake --build build\nctest --test-dir build --output-on-failure\n\n# Lint C++\ncore/scripts/lint-cpp.sh          # Check formatting\ncore/scripts/lint-cpp.sh --fix    # Auto-fix\n```\n\n### Swift SDK (`bindings/swift/`)\n\n```bash\n# Build (requires XCFrameworks in bindings/swift/Binaries/)\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift build\n\n# Run tests\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift test\n\n# Build for specific platform\nxcodebuild build -scheme RunAnywhere -destination 'platform=iOS Simulator,name=iPhone 16 Pro'\n\n# Run SwiftLint\nswiftlint\n```\n\n### Kotlin SDK (`bindings/kotlin/`)\n\n```bash\ncd bindings/kotlin/\n\n# Build (Android library)\n./gradlew build\n\n# Individual targets\n./gradlew assembleDebug        # Android Debug AAR\n./gradlew assembleRelease      # Android Release AAR\n\n# Test\n./gradlew testDebugUnitTest    # Android unit tests\n./gradlew test                 # All unit tests (debug + release variants)\n\n# Publish to Maven Local\n./gradlew publishToMavenLocal\n\n# Native library management (C++ JNI)\n./gradlew setupLocalDevelopment   # First-time: builds C++ JNI libs (runs scripts/build/build-core-android.sh)\n./gradlew rebuildCommons          # Rebuild C++ after source changes\n./gradlew downloadJniLibs         # Download pre-built .so from GitHub Releases\n```\n\nBuild outputs: `build/outputs/aar/runanywhere-kotlin-{debug,release}.aar` (plus sub-module AARs under `modules/runanywhere-core-{llamacpp,onnx}/build/outputs/aar/`).\n\nBackend modules at `modules/runanywhere-core-llamacpp/` and `modules/runanywhere-core-onnx/`.\n\n### Flutter SDK (`bindings/flutter/`)\n\nManaged by Melos. Four packages: `runanywhere` (core), `runanywhere_llamacpp`, `runanywhere_onnx`, `runanywhere_qhexrt`.\n\n```bash\ncd bindings/flutter/\nmelos bootstrap         # Install deps across all packages\nmelos run analyze       # Dart analysis\n```\n\n### React Native SDK (`bindings/react-native/`)\n\nManaged by Yarn Berry 3.6.1. Three packages: `@runanywhere/core`, `@runanywhere/llamacpp`, `@runanywhere/onnx`.\n\n```bash\ncd bindings/react-native/\nyarn install\nyarn typecheck          # Primary verification gate\n```\n\nNitroModules specs in `packages/core/src/specs/*.nitro.ts`. After spec changes, run `nitrogen` to regenerate C++ bridge code, then `scripts/fix-nitrogen-output.js`.\n\n### Web SDK (`bindings/web/`)\n\nThree npm packages: `@runanywhere/web` (core TS), `@runanywhere/web-llamacpp` (WASM), `@runanywhere/web-onnx` (Sherpa WASM).\n\n```bash\ncd bindings/web/\n\n# Build WASM (requires Emscripten SDK)\nnpm run build:wasm -- --core\nnpm run build:wasm -- --llamacpp                 # CPU variant\nnpm run build:wasm -- --webgpu                   # WebGPU variant\nnpm run build:wasm -- --onnx                     # ONNX + Sherpa artifact\n\n# Build TypeScript\nnpm run build\n\n# Type-check\nnpm run typecheck\n```\n\nThe current artifact and deployment contract is maintained in\n`bindings/web/AGENTS.md`; it supersedes historical standalone\n`wasm/sherpa/` paths. Do not use or recreate those removed paths.\n\n### IDL codegen\n\n```bash\n# Install toolchain (protoc, protoc-gen-swift, wire-compiler, ts-proto, etc.)\n./scripts/setup/setup-toolchain.sh\n\n# Regenerate all language bindings\n./idl/codegen/generate_all.sh\n\n# One language (also: kotlin, dart, ts, cpp, python)\n./idl/codegen/generate_all.sh --only swift\n```\n\n### Generated code — what is committed and what is not\n\n**Nothing generated is tracked.** A fresh clone has no C++, Kotlin, Swift,\nTypeScript, Dart, React Native or Python bindings until codegen runs.\n`./scripts/setup/setup.sh` runs it first for exactly that reason; `./run codegen`\nruns it on demand. The three hooks below mean almost nobody has to know that.\n\n| tree | who generates it | when |\n|---|---|---|\n| `core/src/generated/proto/` (76 files, ~336k lines) | `core/CMakeLists.txt`, at **configure** time when the files are absent | every `cmake --preset …`, i.e. all ~29 native CI runner instances, the Electron addon, the Python wheel, rcli and WASM |\n| `core/include/rac/rac_defaults_generated.h` | same block | same. A SHIPPED public header: `install(DIRECTORY include/)` puts it in the XCFramework `Headers/` and the Linux/Windows dist, and five shipped `rac_{llm,stt,tts,vad,vlm}_types.h` `#include` it — so it must exist before packaging, which configure time guarantees |\n| `bindings/kotlin/.../sdk/generated/` (373 files) | the `generateIdlKotlinBindings` Gradle task, wired into `preBuild` | every `assemble*` / `compile*Kotlin` / `test*` / ktlint / detekt, including JitPack |\n| `bindings/swift/Sources/RunAnywhere/Generated/` | `sync-dist-repo.sh` | ships in the SwiftPM tag |\n| `bindings/proto-ts/src/` and `dist/` | each `package-sdk.sh` | `dist` ships in 7 npm packages |\n| `bindings/flutter/packages/runanywhere/lib/generated/` | `bindings/flutter/scripts/package-sdk.sh` | ships in the pub package |\n| the two `RADefaultsPool.kt` under flutter/ and react-native/ | the same packaging scripts | ship inside the pub / npm packages |\n| `bindings/python/runanywhere/_proto/`, `_generated_{errors,defaults}.py` | the in-tree PEP 517 backend | ship in the sdist + wheel |\n\nTwo CI jobs read generated C/C++ **without** configuring CMake and therefore carry an\nexplicit `generate-idl` step with `cpp`: `pr-build.rn-typecheck` (`-fsyntax-only` over\n`core/include`) and `release.native_rcli_macos` (`swift build` over the root\n`Package.swift`).\n\n`idl/codegen/generated_trees.txt` is the machine-readable version of that table, plus\nthe eight hand-written files that live *inside* those trees and stay tracked (the\n`.gitignore` negations exist for them, and `check_generated_trees.sh` fails if one\never stops being tracked — and fails the other way if a generated file becomes tracked).\n\n**The toolchain is downloaded, not assumed.** protoc stamps its own patch version into\nevery C++ header (`#if PROTOBUF_VERSION != 7035001`) and every ts-proto banner, and Wire\nrenames files between releases, so the output is a function of the tool versions and not\nonly of the schemas. The package managers this repo would otherwise reach for do not offer\nthat guarantee — `brew install protobuf` gives whatever is current, `apt-get install\nprotobuf-compiler` gives whatever the distro froze, neither selects a per-platform archive\nby checksum, and Homebrew's `wire` is a different product entirely. protobuf and Maven\nCentral both publish immutable per-platform archives, so the pins are *obtainable*:\n\n| script | resolves | pinned by | verified against |\n|---|---|---|---|\n| `idl/codegen/bootstrap_protoc.sh` | protoc | `core/VERSIONS::PROTOC_VERSION` | `idl/codegen/protoc.sha256` |\n| `idl/codegen/bootstrap_wire.sh` | wire-compiler | `core/VERSIONS::WIRE_VERSION` | `idl/codegen/wire.sha256` |\n| `idl/codegen/bootstrap_pyproto.sh` | a python3 with `google.protobuf` + `yaml` | `core/VERSIONS::PYTHON_PROTOBUF_VERSION` | pip, into a cached venv |\n\nEach prints one path on stdout, uses a matching tool already on `PATH` when there is one,\ncaches under `${XDG_CACHE_HOME:-~/.cache}/runanywhere/`, and refuses to install anything\nwhose checksum is not recorded — so bumping a pin without refreshing the `.sha256` file is\na hard error rather than an unverified download. `RAC_PROTOC` / `RAC_WIRE_COMPILER` /\n`RAC_PYTHON` override; `RAC_PROTOC_NO_DOWNLOAD=1`, `RAC_WIRE_NO_DOWNLOAD=1` and\n`RAC_PY_NO_INSTALL=1` make an air-gapped host fail loudly instead of reaching out.\n\n**Every publish path generates before packaging.** A de-committed tree that ships\ninside an artifact must exist at pack time or the published package is broken in a\nway that no build step notices — `npm pack` packs an empty `dist/`, `flutter pub\npublish --dry-run` validates a package with no `lib/generated/`, and a Python wheel\ninstalls fine and fails at `import`. So each packaging script calls\n`idl/codegen/ensure_generated.sh --only <lang>` first, and the Python SDK carries an\nin-tree PEP 517 backend (`bindings/python/_build/`) so even a bare `pip install`\ncannot skip it.\n\n**Schema version.** `idl/VERSION` is hand-maintained semver for the `.proto` surface;\n`idl/SCHEMA_LOCK` is machine-written by `generate_all.sh` and records a digest of\nevery `idl/*.proto`. Because it is tracked and the bindings are not, the lock is the\ndrift signal: editing a schema without re-running codegen leaves it stale, and CI\nfails. Changing the schema without bumping `idl/VERSION` also fails.\n\n```bash\n./idl/codegen/schema_lock.sh --print   # which IDL is this checkout?\n./idl/codegen/ci-drift-check.sh        # the whole gate, exactly as CI runs it\n```\n\nCI `idl-drift-check.yml` is **generate, then verify** — not \"regenerate and diff\",\nwhich cannot fail for an ignored file.\n\n---\n\n## Example app commands\n\n### Swift minimal example\n\n```bash\ncd bindings/swift/example/\n\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift build\nRUNANYWHERE_USE_LOCAL_NATIVES=1 swift run\n```\n\nRequires the XCFrameworks in `bindings/swift/Binaries/` (`RACommons`, `RABackendLLAMACPP`, `RABackendONNX`, `RABackendSherpa`). Build them with `./bindings/swift/scripts/build-core-xcframework.sh`. `./run example ios {build|run|clean}` wraps this.\n\nSDK logs (in a separate terminal):\n\n```bash\nlog stream --predicate 'subsystem CONTAINS \"com.runanywhere\"' --info --debug\n```\n\n### Kotlin minimal example\n\n```bash\ncd bindings/kotlin/example/\n\n./gradlew :app:assembleDebug   # Build\n./gradlew :app:installDebug    # Install on device/emulator\n```\n\n`settings.gradle.kts` pulls `bindings/kotlin` in as a **composite build** with `dependencySubstitution`, so Gradle recompiles the SDK from source on every app build and its transitive runtime deps (coroutines, OkHttp, Wire) come along automatically. There is no AAR staging step.\n\n- `./run sdk commons build-android` builds the commons `.so` for all Android ABIs (needed once, and after any C++ change; `runanywhere.useLocalNatives=true` expects them under `src/main/jniLibs/`).\n- `./run example android build` runs `:app:assembleDebug`.\n- `./run example android install` runs `:app:installDebug` and launches.\n\n### Web minimal example\n\n```bash\ncd bindings/web/example/\n\nnpm install\nnpm run typecheck\nnpm run dev          # Vite dev server at port 3000 (COOP/COEP set by vite.config.ts)\nnpm run build        # Production bundle in dist/\nnpm run preview      # Serve dist/ on port 3000\n```\n\nRequires the four canonical WASM pairs (`npm run build:wasm:all` from `bindings/web/`); the build fails naming the missing files rather than emitting a broken bundle. `SharedArrayBuffer` needs cross-origin isolation (COOP + COEP).\n\nThe example publishes `window.__RUNANYWHERE_SDK__` and `window.__RUNANYWHERE_AI_READY__`, the readiness contract `bindings/web/tests/browser/` probes. `RA_E2E_APP_DIR` points Playwright at a different app (e.g. a checkout of `RunanywhereAI/runanywhere-web` for the full release journey).\n\n### Flutter example\n\n```bash\ncd bindings/flutter/example/\n\nflutter pub get\nflutter run\nflutter run -d \"iPhone 16 Pro\"\n./scripts/verify.sh            # pub get + analyze + APK build\nRUN_IOS=1 ./scripts/verify.sh  # Also builds iOS\n```\n\n### React Native example\n\n```bash\ncd bindings/react-native/example/\n\nyarn install\nyarn start          # Metro bundler\nyarn ios            # iOS simulator\nyarn android        # Android device\nyarn typecheck      # Primary verification gate\n./scripts/verify.sh # typecheck + optional builds\n```\n\nHermes caveat: Does not support `for await...of` with NitroModules async iterables. Use manual `iterator.next()` loops.\n\n---\n\n## Version management\n\nCanonical version: `core/VERSION` (single-line file, e.g. `0.20.0`).\n\n```bash\n# Bump everywhere: VERSION, Package.swift, gradle.properties, package.json, pubspec.yaml\n./scripts/release/sync-versions.sh 0.20.0\n```\n\nRelease lifecycle: `sync-versions.sh` → PR with `release:minor` label → merge → `auto-tag.yml` pushes `v0.20.0` tag → `release.yml` builds all artifacts and creates draft GitHub Release → cut the Swift distribution repo, below.\n\n### Cutting `runanywhere-swift` (required, every release)\n\n[`RunanywhereAI/runanywhere-swift`](https://github.com/RunanywhereAI/runanywhere-swift)\nis a generated, Swift-only SPM distribution of `bindings/swift` (Package.swift +\nSources/ + LICENSE + README). It exists so Swift consumers clone ~3 MB instead of\nthe ~340 MB monorepo. Its manifest declares the same remote binaryTargets\nagainst the same release assets on `runanywhere-sdks`, with the same\nchecksums, so the XCFrameworks are never re-uploaded.\n\nIts tag must track every release. Publish `v<version>` here without cutting it\nand `from: \"<version>\"` resolves to nothing for every Swift consumer.\n\n```bash\ngit clone https://github.com/RunanywhereAI/runanywhere-swift.git /tmp/ra-swift\n\n# Regenerate Sources/ + bump sdkVersion/README, sync this release's checksums,\n# commit, and tag (bare semver, no 'v' prefix; SwiftPM `from:` needs that).\n./bindings/swift/scripts/sync-dist-repo.sh \\\n    --zips release-artifacts/native-ios-macos --tag /tmp/ra-swift\n\n# Prove both manifests agree before pushing.\nRUNANYWHERE_SWIFT_DIST_REPO=/tmp/ra-swift \\\n    bash scripts/validation/gates/check_swift_dist_repo_sync.sh\n\ngit -C /tmp/ra-swift push origin main --follow-tags\n```\n\nThis is enforced, not merely documented: once `v<version>` is tagged here,\n`gates/check_swift_dist_repo_sync.sh` fails every PR until `runanywhere-swift`\ncarries the matching tag.\n\n---\n\n## CI/CD Workflows (`.github/workflows/`)\n\n| Workflow | Trigger | Purpose |\n|----------|---------|---------|\n| `pr-build.yml` | PR to main, push to main/feat branch | Parallel native builds (macOS/Linux/iOS/Android) + per-SDK typecheck |\n| `release.yml` | Tag `v*.*.*` or manual | Full artifact build matrix, SDK packaging, consumer validation, draft Release |\n| `auto-tag.yml` | PR merged to main with `release:*` label | Verifies the reviewed semver bump, then pushes that exact git tag |\n| `idl-drift-check.yml` | Changes to `idl/` or generated files | Regenerates protos, fails if `git diff` is non-empty |\n| `legacy-files-blocklist.yml` | All PRs/pushes | Prevents 5 specific deleted files from being re-introduced |\n| `secret-scan.yml` | PRs and pushes to main | Incremental gitleaks scan on diff range |\n| `check-no-pii-logging.yml` | All PRs/pushes to main, master, feat-branch | Regression guard against Android logcat / RAC_LOG_INFO calls that emit signed URLs alongside active-download destination paths |\n\n---\n\n## Key architectural decisions\n\n### iOS SDK is the source of truth\nWhen implementing features in any other SDK (especially Kotlin), always check the iOS Swift implementation first. Copy logic exactly, adapting only for language syntax, not business logic.\n\n### All business logic in C++ commons (or the SDK shared layer)\nPlatform-specific code should only handle: native library loading, platform adapter registration, audio capture/playback, secure storage, and UI. All AI inference, model management, event routing, and pipeline orchestration live in C++ (`runanywhere-commons`) or, when intentionally Kotlin-side, under the Kotlin SDK's shared `src/main/kotlin/com/runanywhere/sdk/` tree.\n\n### Backend registration pattern\nAll SDKs follow the same pattern:\n1. Load the backend native library\n2. Call `rac_backend_*_register()` (which registers the engine's vtable with the plugin registry)\n3. The registry orders registered plugins by base priority, per primitive\n4. On inference, the highest-priority plugin that serves the primitive is selected via `rac_plugin_find()` (or `rac_plugin_find_for_engine()` for a name-pinned engine)\n\nBackend base priorities: qhexrt=150 (QNN-context models only), mlx=110 (Apple), llamacpp=100, sherpa=90, onnx/cloud=50. Selection is plain priority order, with no runtime/format scoring or pinned-engine bonus; an explicit engine name is honored through `rac_plugin_find_for_engine()`.\n\n### HTTP transport is platform-provided\nlibcurl was removed. Each SDK registers a `rac_http_transport_ops_t` vtable: Swift uses URLSession, Kotlin/Flutter/RN use OkHttp (Android) or URLSession (iOS), Web uses `emscripten_fetch`.\n\n### Proto-generated types replace hand-written enums\nAll cross-platform types are defined in `idl/*.proto`. SDKs use typealiases to the generated types (e.g., `typealias SDKEnvironment = RASDKEnvironment` in Swift, `typealias SDKEnvironment = ai.runanywhere.proto.v1.SDKEnvironment` in Kotlin). Never add enum values by hand; modify the `.proto` file and regenerate.\n\n---\n\n## Platform requirements\n\n| Platform | Min Version | Build Tool | Key Versions |\n|----------|------------|------------|--------------|\n| iOS | 17.5 | Xcode 26+ | Swift 6.2 |\n| macOS | 14.5 | Xcode 26+ | Swift 6.2 |\n| Kotlin SDK | Android API 24 | AGP 9.2.1 / Gradle 9.5.0 | Kotlin 2.4.0, NDK 27.3.13750724 |\n| Android example | Android API 24 | AGP 9.2.1 / Gradle 9.6.0 | Kotlin 2.4.0, compile/target SDK 37 |\n| Flutter | 3.44.6 | Melos / AGP 9.0.1 / Gradle 9.1.0 | Dart 3.12.2+, compile/target SDK 36, NDK 28.2.13676358 |\n| React Native | 0.85.3 (min 0.83.1) | Yarn Berry 3.6.1 | NitroModules, Hermes |\n| Web | Chrome 86+ | Vite | Emscripten 6.0.2, Node 24 LTS |\n| C++ Core | N/A | CMake 3.24+ (upstream 4.2+ for the VS 2026 preset) | C++20, Ninja |\n\n---\n\n## Kotlin SDK: critical implementation rules\n\nThe Kotlin SDK (`bindings/kotlin/`) ships as an Android library (`alias(libs.plugins.android.library)` in `bindings/kotlin/build.gradle.kts`), not as a Kotlin Multiplatform module. It targets Android only and consumes the C++ commons core through JNI (`librunanywhere_jni.so`). JVM 17 is the toolchain for the Gradle build itself, not a published target.\n\n### iOS as the source of truth\n**NEVER make assumptions when implementing the Kotlin SDK. ALWAYS refer to the iOS implementation as the definitive source of truth.**\n\n1. **iOS First**: When encountering missing logic or unclear requirements in the Kotlin SDK, check the corresponding iOS implementation, copy the logic exactly, adapt only for Kotlin syntax.\n\n2. **Public API symmetry**: The Kotlin SDK mirrors the Swift `RunAnywhere` surface as an `object RunAnywhere` singleton with extension functions one-per-feature in `src/main/kotlin/com/runanywhere/sdk/public/extensions/`. Add new public API only after the Swift facade has landed.\n\n3. **Platform naming convention**: Android-only adapters keep an explicit `Android` prefix (e.g. `AndroidTTSService.kt`) so file naming makes the target unambiguous if a JVM-only or KMP variant is ever reintroduced.\n\n### Source set layout\n\n```\nbindings/kotlin/\n    src/main/kotlin/        (all Kotlin sources: public API, JNI bridges, generated Wire proto types)\n    src/main/jniLibs/       (prebuilt .so files staged by build-core-android.sh)\n    src/test/kotlin/        (unit tests, no JNI required)\n    modules/runanywhere-core-{llamacpp,onnx}/  (Android library sub-modules that register C++ backends)\n```\n\nStandard Android library layout. There is no `commonMain`/`jvmAndroidMain`/`androidMain`/`jvmMain` hierarchy at this level (the SDK was migrated away from KMP). Any `expect`/`actual` pairs you see in legacy documentation describe the previous topology; the current build is single-target Android. Reviewer-area names like `A-kotlin-common-domain` in `test_workflows/.../SCOPE_MANIFEST.json` are kept for historical filtering and do not imply KMP source sets exist today.\n\n### Cross-SDK alignment\n\n| Concern | iOS Swift | Kotlin (Android) | Flutter | React Native | Web |\n|---------|-----------|------------------|---------|-------------|-----|\n| Entry point | `enum RunAnywhere` | `object RunAnywhere` | `RunAnywhere` (abstract final class with static members) | `RunAnywhere` object | `RunAnywhere` object |\n| Two-phase init | `initialize()` + `completeServicesInitialization()` | Same | Same | Same | Same |\n| Bridge layer | `CppBridge` enum + extensions | `CppBridge` object + extensions | `DartBridge` + `DartBridge*.dart` | `HybridRunAnywhereCore` (Nitro) | `LlamaCppBridge` + `SherpaONNXBridge` |\n| Streaming | `AsyncStream` | `Flow` | `Stream` (via `StreamController`) | `AsyncIterable` (manual iteration) | `AsyncIterable` |\n| Events | `EventBus` (Combine) | `EventBus` (SharedFlow) | `EventBus` (custom pub/sub via dart:async broadcast StreamController) | `EventBus` (NativeEventEmitter) | `EventBus` (custom pub/sub) |\n| Error type | `SDKException` (proto-backed) | `SDKException` (proto-backed) | `SDKException` | `SDKException` | `SDKException` |\n| Secure storage | Keychain | Android Keystore | Keychain (iOS), Android Keystore + atomic no-backup ciphertext files | Keychain (iOS), Android Keystore | localStorage |\n| HTTP transport | URLSession | OkHttp | OkHttp (Android), URLSession (iOS) | OkHttp (Android), URLSession (iOS) | emscripten_fetch / fetch() |\n\n---\n\n## Non-obvious configuration details\n\n`Package.swift`: remote release artifacts are the fail-closed default. Local builds opt into staged XCFrameworks with `RUNANYWHERE_USE_LOCAL_NATIVES=1`; scripts set this explicitly and never rewrite the manifest.\n\n`Package.swift:186-191`: three `.grpc.swift` files are excluded from compilation. They require iOS 18 / macOS 15, above the SDK's minimums. In-process C callback path replaces gRPC.\n\n`gradle.properties`: `runanywhere.useLocalNatives=true` means local `.so` files. CI overrides with `-Prunanywhere.useLocalNatives=false` to download from GitHub Releases.\n\nNDK version: `racNdkVersion=27.3.13750724` (matches `core/VERSIONS::NDK_VERSION`, the single source of truth) is the pin for the Kotlin SDK in `bindings/kotlin/gradle.properties`. NDK 27 is the current LTS line (r27d) and provides 16 KB page-alignment required by Android 15+ (NDK 25.x's 4 KB-aligned `libc++_shared.so` / `libomp.so` would trip Android 16's 16 KB page-size enforcement). Flutter/RN Android build files carry their own `?: \"...\"` fallback literals but the canonical version lives in `VERSIONS`; mirror it whenever bumping.\n\nWeb cross-origin isolation: `SharedArrayBuffer` requires COOP/COEP headers. Safari needs `coi-serviceworker.js` polyfill.\n\nWeb VLM Worker crash recovery: if `rac_vlm_component_process` causes WASM OOM (`\"memory access out of bounds\"`), the Worker auto-recovers by creating a fresh WASM instance on the next `process()` call.\n\nWeb Qwen2-VL WebGPU workaround: Qwen2-VL models produce NaN logits on WebGPU due to f16 M-RoPE overflow. VLM Worker forces CPU WASM for Qwen2-VL even when WebGPU is active.\n\nWeb struct offsets: TypeScript never hard-codes C struct field offsets. `wasm_exports.cpp` exposes `EMSCRIPTEN_KEEPALIVE` offset functions; the `Offsets` proxy reads them at runtime from the WASM module.\n\n---\n\n## Pre-commit hooks\n\n```bash\npre-commit run --all-files        # Run all checks\npre-commit run ios-sdk-swiftlint --all-files  # SwiftLint only\n```\n\nConfigured hooks: gitleaks (secrets), trailing-whitespace, end-of-file-fixer, check-yaml, check-added-large-files (1000 KB max), check-merge-conflict, object file detection, SwiftLint (SDK + example app), periphery (unused code detection).\n\n---\n\n## Active issues\n\n> The old `thoughts/shared/issues/` directory (regressions 001/002/003/005 on\n> `feat/v2-architecture`) does not exist in this tree. Those bullets claimed\n> Swift/Kotlin/Web had collapsed backends into monoliths; those SDKs already ship\n> split backend packages. Do not revive that section from memory.\n\n### Electron (`smonga/electron_upgrade`), in progress\n\nWork is active on branch `smonga/electron_upgrade`. Do not claim packaging or\nper-backend Electron packages are done. Entry points:\n\n- [`thoughts/shared/plans/electron_HANDOFF.md`](thoughts/shared/plans/electron_HANDOFF.md): master state\n- [`thoughts/shared/plans/electron_takeover.md`](thoughts/shared/plans/electron_takeover.md): the remaining executable plan\n\nCurrent shape (honest): TypeScript SDK + example shell are far along; feature\nviews, visual gate, electron-builder packaging, and the backend packaging split\n(#9 / `RAC_HAVE_BACKEND_*` fat addon → runtime plugins) remain open. Parallel\nTracks A/B/C plus Phase 0 commits may be in flight, so check the HANDOFF status\npointer before assuming anything landed.\n\n---\n\n## Cursor Cloud specific instructions\n\n### Environment overview\n\nThis is a cross-platform SDK monorepo. On a Linux cloud VM, the buildable services are:\n\n| Component | Build | Test | Lint | Notes |\n|-----------|-------|------|------|-------|\n| Kotlin SDK (Android target) | `cd bindings/kotlin && ./gradlew compileDebugKotlin -Prunanywhere.useLocalNatives=false` | Android unit tests require device/emulator | `cd bindings/kotlin && ./gradlew ktlintCheck` | Single-target Android library (no KMP). `androidx.annotation` is always available because the build only targets Android. |\n| Web SDK (TypeScript) | `npm run build -w packages/core` (from `bindings/web/`) | N/A | Prefer workspace `npm run typecheck` (builds core `dist/` before backends). Isolated `npm run typecheck -w packages/{llamacpp,onnx}` needs a fresh `npm run build -w packages/core` first, backends resolve `@runanywhere/web/backend` through the gitignored `packages/core/dist` types |\n| Web minimal example | `npm run dev` (from `bindings/web/example/`) | Manual browser testing at `localhost:3000` | N/A | Streams one completion; needs the WASM pairs built |\n| C++ Commons (core) | `cmake -B build ... && cmake --build build` (from `core/`) | `./build/tests/test_core --run-all` (13 tests, no models needed) | N/A | Must use `gcc`/`g++` via `CC=gcc CXX=g++` (clang lacks C++ stdlib headers). Pass `-DRAC_BUILD_PLATFORM=OFF` on Linux |\n| C++ Commons (full backends) | `CC=gcc CXX=g++ ./scripts/build-linux.sh` | Backend tests need downloaded models | N/A | Builds the canonical Linux release preset and packages the staged shared libraries and public headers. |\n| iOS/Swift SDK | Not buildable | Not buildable | Not available | Requires macOS + Xcode |\n| Android emulator | Not runnable | Not runnable | N/A | No KVM support in cloud VM |\n\n### Key gotchas\n\n- **Android SDK**: Installed at `/opt/android-sdk`. `ANDROID_HOME` and `JAVA_HOME` are set in `~/.bashrc`.\n- **JDK 17**: Required by Gradle JVM toolchain. Both JDK 17 and JDK 21 are installed.\n- **`useLocalNatives` flag**: Set to `true` in `gradle.properties`. Pass `-Prunanywhere.useLocalNatives=false` to Gradle to avoid needing Android NDK (downloads pre-built JNI libs from GitHub releases instead of building locally).\n- **C++ compiler**: Default clang on this VM lacks `libc++` headers. Use `gcc`/`g++` via `-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++`.\n- **`local.properties`**: Auto-created at root, `bindings/kotlin/`, and `bindings/kotlin/example/` with `sdk.dir=/opt/android-sdk`.\n- **pre-commit hooks**: Installed via `pre-commit install`. Requires `git config --unset-all core.hooksPath` first if `core.hooksPath` is set.\n\n### Standard commands\n\nSee the rest of this file for comprehensive build/test/lint commands for all SDK platforms. See `CONTRIBUTING.md` for contributor setup flow.\n","category":"root","tokens":10640}]}