Guidance for AI coding assistants working in this repository.
AGENTS.mdis the real file; eachCLAUDE.mdis a committed symlink to theAGENTS.mdbeside it. Editing either name edits the same bytes. If you create a newAGENTS.mdin a directory that doesn't have one, runbash scripts/validation/gates/check_agents_claude_sync.sh --fixfrom the repo root to create itsCLAUDE.mdsymlink —pr-build.ymlfails any trackedAGENTS.mdmissing one. Never hand-create aCLAUDE.mdas a real file.
- Focus on simplicity. Clean, SOLID-ish separation of concerns; reuse over reinvention. No mock implementations and no new unit tests unless the task explicitly asks for them.
- Plan first for non-trivial work. Write the plan to
thoughts/shared/plans/{descriptive_name}.md, get it approved before implementing, and keep it updated (append what changed) as you go so the next engineer can pick it up. - Structured types over raw strings. Enums/sealed classes/data classes for configuration and results — this is enforced per-SDK by lint (ktlint/detekt, SwiftLint, ESLint) so ad hoc strings tend to get caught anyway.
- Read files fully before editing them; only use offset/limit on files too large to read at once.
- Use full local build parallelism (
-j "$(sysctl -n hw.logicalcpu)"/ equivalent), scaling down only under real memory/thermal pressure — not because load average looks high.
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. If an example app builds a multi-step bootstrap 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.
- C++ commons (
core/) — cross-platform, non-I/O logic: model lifecycle, registry, download orchestration, RAG session management, inference routing. All 5 SDKs get the fix for free. - Platform SDK layer — platform-specific I/O or runtime bridging only (Web OPFS, iOS Keychain, Android Keystore, WASM MEMFS mirroring).
- Example apps — UI rendering, navigation, thin SDK calls. No business logic, no internal-SDK knowledge (path patterns, framework→directory mappings), no workarounds.
iOS is the source of truth. When behavior is ambiguous in any other SDK, check the iOS Swift implementation first and copy the logic exactly, adapting only syntax.
Cross-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). 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.
Current version: 0.20.24 (canonical source: core/VERSION)
| SDK | Path | Bridge mechanism | Platforms |
|---|---|---|---|
| Swift | bindings/swift/ |
XCFramework + CRACommons module map | iOS 17.5+, macOS 14.5+ |
| Kotlin | bindings/kotlin/ |
JNI (librunanywhere_jni.so) |
Android (min API 24) |
| Flutter | bindings/flutter/ |
Dart FFI | iOS, Android |
| React Native | bindings/react-native/ |
NitroModules (JSI HybridObject) | iOS 17.5+, Android arm64 |
| Web | bindings/web/ |
Emscripten WASM + TypeScript | Chrome, Safari, Firefox |
| Electron | bindings/electron/ |
Node-API addon | Windows x64 (preview) |
| Python | bindings/python/ |
pybind11 extension (runanywhere._core) |
Windows, macOS, Linux (alpha) |
Each has its own AGENTS.md with full architecture, build commands, and conventions —
read it before working in that SDK.
| Native dir | Contents |
|---|---|
core/ |
C/C++ core library: all AI logic, plugin registry, event system |
engines/ |
7 backend plugins: llamacpp, sherpa, onnx, cloud, mlx, qhexrt, neurt |
runtimes/ |
3 runtime adapters: cpu (always), onnxrt, coreml |
idl/ |
39 Protobuf schemas + per-language codegen scripts |
The four full consumer apps were extracted into standalone repos (history preserved); PR against them there, not here: runanywhere-ios, runanywhere-android, runanywhere-web, runanywhere-electron.
Two example apps remain in-tree (bindings/flutter/example/, bindings/react-native/example/).
All example apps share brand orange #FF6900, documented in docs/DESIGN_GUIDELINE.md.
Each in-repo example consumes its SDK from local source (no staging/publishing needed to
see an edit): Swift via RUNANYWHERE_USE_LOCAL_NATIVES=1, Kotlin via a Gradle composite
build (includeBuild + dependencySubstitution), Web via Vite aliases into
packages/*/src. They're deliberately minimal (one prompt in, one streamed completion
out) — feature-complete UI belongs in the consumer repos above.
idl/*.proto is the schema root; idl/codegen/generate_all.sh emits per-language
bindings (Swift, Kotlin/Wire, TS/ts-proto, Dart), all generated and none committed — see
docs/reference/generated-code-contract.md
for the full "what generates what, when" contract before touching idl/.
All SDKs reach runanywhere-commons through the rac_* C API. Commons holds the
component layer (lifecycle), the service layer (dispatch), and the plugin registry, and
reaches engines through rac_engine_vtable_t.
| Engine | Primitives |
|---|---|
| llamacpp | LLM, VLM |
| sherpa-onnx | STT, TTS, VAD |
| onnx | Embed, Segment |
| qhexrt | Hexagon NPU |
| neurt, cloud | Apple Neural Engine, HTTP |
Backend base priorities (highest wins per primitive via rac_plugin_find(), no
runtime/format scoring): qhexrt=150 (QNN-context models only), mlx=110 (Apple),
llamacpp=100, sherpa=90, onnx/cloud=50. An explicit engine name is honored through
rac_plugin_find_for_engine() regardless of priority.
- Platform adapter IoC:
rac_platform_adapter_tis a flat C struct of function pointers populated by each SDK beforerac_init(). C++ never calls platform APIs directly — file I/O, HTTP, Keychain, logging, memory queries all pass through it. - Two-phase init: every SDK does Phase 1 (sync: register adapter, load native libs, configure logging) then Phase 2 (async: authenticate, register device, fetch model assignments, discover downloaded models).
- Plugin ABI:
RAC_PLUGIN_API_VERSION = 9u; a version mismatch is an immediate rejection. Each vtable has 10 active primitive slots and 7 reserved. NULL slot = not supported.rerank_ops(wire value 11) was promoted fromreserved_slot_2in ABI v8; wire value 6, retired in ABI v4, stays permanently retired — never reuse it. Seecore/AGENTS.mdfor the full ABI history and how to add a primitive. - Static vs. dynamic plugins: iOS and WASM force
RAC_STATIC_PLUGINS=ON(nodlopen), registered viaRAC_STATIC_PLUGIN_REGISTER(name)+-force_load/--whole-archive. Android/Linux/macOS default to dynamic loading viarac_registry_load_plugin(). - Streaming fan-out: C++ allows only one proto-byte callback per component handle.
Each SDK implements its own fan-out to multiplex that into multiple subscribers (Swift
AsyncStream, KotlinFlow, DartStreamController, TSAsyncIterable). - Proto types are canonical: never hand-write enum values or structured types that
exist in
idl/*.proto— use the generated types/typealiases and regenerate instead. - HTTP is platform-provided: no libcurl. Each SDK registers a
rac_http_transport_ops_t(URLSession on Apple platforms, OkHttp on Android,emscripten_fetchon Web).
A side-by-side comparison of entry point / bridge / streaming / events / storage / HTTP
per SDK — useful when porting a fix across SDKs — lives in
docs/reference/cross-sdk-parity.md.
The root CMakeLists.txt (version from core/VERSION) is the single entry point for
native builds; CMakePresets.json defines macos-{debug,release}, linux-{debug,release,asan},
ios-{device,simulator}, android-arm64, wasm, and the rcli-*/windows-* presets.
cmake --preset macos-debug && cmake --build build/macos-debug && ctest --preset macos-debug./run <group> [subcommand] is the unified wrapper for everything else — build,
lint, and run any SDK or example app from one CLI (./run --help for the full menu):
./run doctor # scan host toolchains, show what's buildable here
./run setup # provision env for every host-buildable target
./run sdk commons build-android # build C++ commons for all Android ABIs, stage .so files
./run sdk kotlin build # (etc. — see per-SDK AGENTS.md for direct tool commands)
./run example android install # build + install + launch the Kotlin minimal example
./run codegen # regenerate every language binding from idl/For the direct swift build / ./gradlew / melos / yarn / npm commands each SDK
uses under the hood, and its test/lint/publish targets, see that SDK's own AGENTS.md
(bindings/<name>/AGENTS.md) — the commands are kept there, not duplicated here, so they
can't drift.
Cross-platform build scripts worth knowing about at the root level:
./bindings/swift/scripts/build-core-xcframework.sh # also syncs into RN/Flutter plugin dirs
./scripts/build/build-core-android.sh # .so for all ABIs → every SDK's jniLibs/
./bindings/web/scripts/build-core-wasm.sh
./scripts/setup/setup-toolchain.sh # protoc/wire/ts-proto toolchain
./idl/codegen/generate_all.sh [--only <lang>] # swift|kotlin|dart|ts|cpp|pythonCanonical version: core/VERSION (single-line semver). Bump everywhere with:
./scripts/release/sync-versions.sh <version>The full release runbook (version bump → PR → tag → GitHub Release → npm/Maven/pub.dev/
SwiftPM publishing → cutting the runanywhere-swift SPM distribution repo → starter-app
smoke tests) is a multi-step process captured in three skills — use them rather than
improvising the steps: sdk-release (version bump through published GitHub Release),
sdk-publish (registry publishing + the runanywhere-swift dist repo cut), and
sdk-test-starters (post-release device/app smoke tests).
| Workflow | Trigger | Purpose |
|---|---|---|
pr-build.yml |
PR to main, push to main/feat branch | Parallel native builds (macOS/Linux/iOS/Android) + per-SDK typecheck + centralization/coherence gates |
release.yml |
Tag v*.*.* or manual |
Full artifact build matrix, SDK packaging, consumer validation, draft Release |
auto-tag.yml |
PR merged to main with release:* label |
Verifies the reviewed semver bump, pushes that exact git tag |
idl-drift-check.yml |
Changes to idl/ or generated files |
Regenerates protos, verifies schema lock + untracked generated trees |
legacy-files-blocklist.yml |
All PRs/pushes | Prevents specific deleted files from being re-introduced |
secret-scan.yml |
PRs and pushes to main | Incremental gitleaks scan on diff range |
check-no-pii-logging.yml |
PRs/pushes to main/master/feat-branch | Guards against logging signed URLs alongside active-download destination paths |
| Platform | Min version | Build tool | Key versions |
|---|---|---|---|
| iOS / macOS | 17.5 / 14.5 | Xcode 26+ | Swift 6.2 |
| Kotlin SDK | Android API 24 | AGP 9.2.1 / Gradle 9.5.0 | Kotlin 2.4.0, NDK 27.3.13750724 |
| Flutter | 3.44.6 | Melos / AGP 9.0.1 / Gradle 9.1.0 | Dart 3.12.2+, NDK 28.2.13676358 |
| React Native | 0.85.3 (min 0.83.1) | Yarn Berry 3.6.1 | NitroModules, Hermes |
| Web | Chrome 86+ | Vite | Emscripten 6.0.2, Node 24 LTS |
| C++ core | N/A | CMake 3.24+ | C++20, Ninja |
- NDK pin:
core/VERSIONS::NDK_VERSION(27.3.13750724) is the single source of truth, mirrored intobindings/kotlin/gradle.properties. NDK 27 (r27d) gives 16 KB page-alignment required by Android 15+ — NDK 25.x's 4 KB-alignedlibc++_shared.so/libomp.sotrips Android 16's page-size enforcement. Mirror this pin whenever bumping it; Flutter/RN Android build files carry their own fallback literals that can drift. useLocalNatives(Kotlingradle.properties, similarly named flags elsewhere):truebuilds native libs from source locally; CI and most non-local runs set itfalseto download prebuilt.so/.xcframework/.wasmfrom GitHub Releases instead.- Web SDK has several hard-won WASM/browser runtime workarounds (VLM worker crash
recovery, a Qwen2-VL WebGPU NaN bug, cross-origin isolation requirements) — see
docs/reference/web-runtime-gotchas.md. - Cloud/Linux dev VM (e.g. Cursor Cloud): what's buildable there and the
clang/NDK/KVM gotchas are in
docs/reference/cursor-cloud-environment.md.
pre-commit run --all-files # everything
pre-commit run ios-sdk-swiftlint --all-files # SwiftLint onlyConfigured 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 Swift code detection).
thoughts/shared/plans/ holds active/completed execution plans (including any in-flight
SDK reorg or migration briefs — e.g. the Electron reorganization plan); thoughts/shared/issues/
holds tracked bug write-ups. Check these directories directly rather than trusting a
status summary written into this file — both change too often for a static snapshot here
to stay accurate.