runanywhere-sdks Agent Skills & AI Rules

GitHub

Production ready toolkit to run AI locally

RAW Rules
5 Discovered Rules β€’ ~14,310 Estimated Tokens
Quick:
Loading skill content...

AI Agent Protocol & Instructions

AGENTS.md root-instruction ~6,705 tokens
Raw
# AGENTS.md

Guidance for AI coding assistants working in this repository.

> **`AGENTS.md` is the real file; each `CLAUDE.md` is a committed symlink to the
> `AGENTS.md` beside it.** Editing either name edits the same bytes. If you create a new
> `AGENTS.md` in a directory that doesn't have one, run
> `bash scripts/validation/gates/check_agents_claude_sync.sh --fix` from the repo root to
> create its `CLAUDE.md` symlink β€” `pr-build.yml` fails any tracked `AGENTS.md` missing one.
> Never hand-create a `CLAUDE.md` as a real file.

## Working conventions

- **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.
- **Multi-agent workflows run on Sonnet.** When fanning work out to subagents (the
  `Workflow` tool, or several parallel `Agent` calls), pass `model: 'sonnet'` on every
  `agent(...)` call rather than letting them inherit the session model. A fan-out of
  8-10 probes reading large files is exactly the shape where Sonnet is the right
  cost/latency trade, and these agents are doing bounded, well-specified retrieval and
  analysis rather than open-ended reasoning. Reserve the session's stronger model for
  the synthesis step that consumes their reports, and for anything where a wrong answer
  is expensive and hard to detect β€” a final design decision, an adversarial verification
  pass, or a security-relevant judgement.

  ```js
  // in a Workflow script
  const findings = await parallel(LENSES.map((l) => () =>
    agent(l.prompt, { label: `probe:${l.key}`, schema: FINDINGS, model: 'sonnet' })))

  // the synthesis that reads them can stay on the session model
  const plan = await agent(synthesisPrompt, { schema: PLAN, effort: 'high' })
  ```

## The most important architectural rule: logic lives at the lowest layer that serves everyone

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.

1. **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.
2. **Platform SDK layer** β€” platform-specific I/O or runtime bridging only (Web OPFS,
   iOS Keychain, Android Keystore, WASM MEMFS mirroring).
3. **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.

---

## Repository overview

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.37` (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 |

### Consumer apps live in separate repos

The four full consumer apps were extracted into standalone repos (history preserved); PR
against them there, not here: [runanywhere-ios](https://github.com/RunanywhereAI/runanywhere-ios),
[runanywhere-android](https://github.com/RunanywhereAI/runanywhere-android),
[runanywhere-web](https://github.com/RunanywhereAI/runanywhere-web),
[runanywhere-electron](https://github.com/RunanywhereAI/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`.

### Minimal examples are how you verify an SDK change locally

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.

---

## Cross-platform architecture

`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`](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.

### Non-obvious invariants that span every SDK

- **Platform adapter IoC**: `rac_platform_adapter_t` is a flat C struct of function
  pointers populated by each SDK before `rac_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 from `reserved_slot_2` in ABI v8;
  wire value 6, retired in ABI v4, stays permanently retired β€” never reuse it. See
  `core/AGENTS.md` for the full ABI history and how to add a primitive.
- **Static vs. dynamic plugins**: iOS and WASM force `RAC_STATIC_PLUGINS=ON` (no
  `dlopen`), registered via `RAC_STATIC_PLUGIN_REGISTER(name)` + `-force_load` /
  `--whole-archive`. Android/Linux/macOS default to dynamic loading via
  `rac_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`, Kotlin `Flow`, Dart `StreamController`, TS `AsyncIterable`).
- **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_fetch` on Web).
- **NeuRT crosses a repo boundary as BYTES, not source**: the Apple engine is built and
  published by the private `neurun` repo and consumed here as prebuilt archives, pinned by
  tag + SHA-256 in `core/VERSIONS` (`NEURT_*`). Fetch with `scripts/build/download-neurt.sh`.
  Each archive carries a receipt naming the `RAC_PLUGIN_API_VERSION` it was built against,
  and both the downloader and CMake refuse a mismatch β€” a vtable-layout change relinks
  cleanly and corrupts dispatch at runtime, so nothing else would catch it.

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`](docs/reference/cross-sdk-parity.md).

---

## Building and running

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`, `cpp-desktop-*`, and the `windows-*` presets.

```bash
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):

```bash
./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:

```bash
./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|python
```

---

## Version management and releases

Canonical version: `core/VERSION` (single-line semver). Bump everywhere with:

```bash
./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).

### Release pipeline lessons (v0.20.29 through v0.20.33 β€” read before cutting a release by hand)

- **`native_ios` and `native_android` always rebuild fresh archives on every `release.yml`
  run β€” there is no reuse/skip mechanism for either job** (unlike `native_web`'s
  `reuse_native_web_run_id` or the cpp-desktop kits' `reuse_cpp_desktop_run_id`). This makes
  the Apple/Android TEMP-pin convention (Package.swift's `sdkVersion`, the Flutter podspecs'
  `asset_version`, Kotlin's `nativeLibVersion` pinned to an old version "because this release
  ships no new iOS/Android archives") **structurally unsound for any release where those two
  jobs actually run β€” which is every release.** It surfaced as three separate bugs
  (`auto-tag.yml`'s coherence check, `package-sdk.sh`'s podspec check, and `sync-checksums.sh`'s
  publish-time verification) before the pattern was recognized. If a release genuinely ships
  no new Apple/Android bytes, that has to be proven by diffing the actual built artifacts
  against the previous release, not assumed from what changed in the diff.
- **`RunAnywhereMLXRuntime`'s stripped Mach-O is not byte-reproducible across builds**
  (`RAC_CHECKSUM_SKIP=RunAnywhereMLXRuntime` in `release.yml`'s publish job exists because of
  this, and is the ONLY archive exempted from the publish-time checksum gate). The corollary
  that bit v0.20.29: **the checksum committed to `Package.swift` / the Flutter MLX podspec
  can only be computed from a build that already happened, but the tag that freezes those
  files has to be pushed before `native_ios` (which produces that exact artifact) even runs**
  β€” so re-syncing checksums against an EARLIER, superseded build (e.g. after a tag had to be
  moved and re-dispatched to fix an unrelated bug) commits a value that does not match what
  the FINAL successful run actually publishes, and the skip means nothing catches it. v0.20.29
  shipped this way (committed `d942788f...`, actually published `fc4b23a0...`) β€” caught only
  because the Flutter `runanywhere_mlx` package hadn't been published to pub.dev yet for that
  version. Before publishing any package that ships `RunAnywhereMLXRuntime`, re-run
  `bindings/swift/scripts/sync-checksums.sh` against the artifacts from the SPECIFIC run whose
  tag is being published β€” never reuse a checksum computed against an earlier attempt.
- **The Electron QHexRT backend never set `ADSP_LIBRARY_PATH`, and one comment in the
  codebase confidently asserted Windows doesn't need it.** It does, for exactly one decode
  path: `QHexRT/src/bonsai/fastrpc_win.cpp`'s custom FastRPC skel (the ternary/Bonsai
  decoder, e.g. `qwen3.8-27b-1bit-npu`) resolves through `ADSP_LIBRARY_PATH βˆͺ cwd`, separately
  from the standard QNN HTP graph path's `LoadLibraryW` + PATH search that `bridge.ts`'s
  `addSidecarDirToDllSearch` already handled. Every OTHER QHexRT model worked fine in the
  packaged app, which is exactly what let this ship undetected β€” validate the Bonsai/ternary
  path specifically, not just "some NPU model works." Fixed in `bridge.ts`'s
  `addSidecarDirToDspSearchPath` (#803).
- **`package-private-engine-overlay.sh`'s QAIRT-runtime copy step filtered on `{".dll",
  ".lib"}` only**, silently dropping every `.so`/`.cat` skel file (both the standard HTP skel
  and, since it never read `engines/qhexrt/prebuilt/current/dsp/win-arm64/` at all, the
  Bonsai skel too). This meant **RCLI could not have run any QHexRT model** β€” standard or
  ternary β€” from a fresh private overlay, a gap invisible until someone actually tried to run
  inference rather than just building. `bindings/electron/scripts/bundle-native.ts` already
  had the correct pattern (copy `dsp/win-arm64/` flat, no extension filter); the overlay
  script just never matched it. Fixed in #804.
- **A new NeuRT archive must be added in FOUR places, and three of them only fail in a build
  configuration your Mac never produces.** v0.20.33 added `libneurt_rac_tts_ops.a` and took two
  wasted release candidates to land, because each site fails somewhere different and late:
  `engines/neurt/CMakeLists.txt` (probe list + imported target + `_neurt_extra_links`),
  `build-core-xcframework.sh` (pre-flight guard AND `_NEURT_ROUTABLE_ARCHIVES` β€” two lists in one
  file), `package-private-engine-overlay.sh`, and the `#if RAC_NEURT_ROUTABLE` guard around the
  vtable entry in `rac_plugin_entry_neurt.cpp`. Miss the force-load lists and every native job goes
  green while `validate_consumer_flutter` dies an hour later on `Undefined symbol:
  _g_neurt_tts_ops` β€” the engine registers, all CMake targets compile, and only a CONSUMER's link
  fails. Miss the vtable guard and the routable build is fine while every PR job WITHOUT
  `NEURUN_TOKEN` fails, because a non-routable shell still references a symbol nothing defines.
  A dev machine with the payload staged is routable, so it reproduces neither. `libneurt_rac_*.a`
  archives now fail packaging by name if they are not in the list
  (`neurt_assert_no_unlisted_archives`); the vtable guard has no such mechanism β€” audit all ten
  slots when adding one. Reproduce a non-routable build by moving `core/third_party/neurt` aside.
- **`reuse_native_web_run_id` is SAME-VERSION-ONLY, whatever the diff looks like.** `racommons.wasm`
  embeds `RAC_VERSION_STRING` and `validate_public_packages.py` asserts it against the version being
  packaged, so reusing ANY prior release's run fails `sdk_web` with *"does not embed
  RAC_VERSION_STRING"*. Worse than a red job: `native_web` is skipped, so that candidate never
  uploads a web artifact and can never serve as `publish_from_run_id`. Only a re-dispatch at the
  same version qualifies.
- **Verify checksums by NAME as well as by URL β€” the manifests use two different shapes.** The root
  `Package.swift` interpolates `\(sdkVersion)` into each URL; the Flutter manifests bind a checksum
  to a `name:` through a helper with no URL at the call site at all. A URL-only cross-check silently
  validates 9 of 15 sites and reports success. Re-hash every site against the zip it names, and
  remember `RAC_CHECKSUM_SKIP` hides `RunAnywhereMLXRuntime` from `publish` entirely, so the Flutter
  MLX podspec is verifiable only by hand.
- **A green `rcli backends --json` proves nothing against the OSS kit.** NeuRT ships as a separate
  private pack, so the public bottle lists no `neurt` at all β€” a routability check there passes
  cleanly and means nothing. Apply the private overlay
  (`cpp-desktop-macos-arm64-neurt-private` workflow artifact) before believing the result.
- **General pattern across all four of the above**: each one was invisible to the checks that
  existed (a version-coherence check, a checksum-skip escape hatch, a "the model loaded"
  smoke test, a "the build succeeded" CI job) because none of those checks actually ran the
  specific thing that was broken. When validating a release, run the SPECIFIC flagship/hardest
  model end-to-end on-device, not just confirm the pipeline completed.
- **The RACommons/RunAnywhereMLXRuntime checksum-staleness bug above recurred on v0.20.30 AND
  v0.20.31** β€” same `mismatch: RACommonsBinary` failure in `publish`, same root cause both
  times: the version-bump PR ran `sync-versions.sh` and merged/tagged directly, skipping the
  **mandatory** "dispatch a release candidate off the unmerged branch, sync checksums from
  its real build, THEN merge" sequence that the **`sdk-release` skill's step 2-3 already
  document in detail** (including this exact failure signature). Documenting the lesson in
  prose here was not enough to stop it recurring twice more β€” **if you are about to run
  `scripts/release/sync-versions.sh` for ANY reason, including as one step of a larger
  cross-repo release cascade, invoke the `sdk-release` skill and follow it from step 1**,
  rather than reconstructing the sequence from memory or treating a "small" version bump as
  exempt. v0.20.31 additionally caught `RunAnywhereMLXRuntime` in the Flutter podspec
  independently stale in the SAME commit β€” `RAC_CHECKSUM_SKIP` exempts it from `publish`'s
  check, so it would have shipped broken to Flutter consumers with no CI signal at all; check
  every row of the checksum-location table, not only the one the CI error names.
- **QHexRT's `qhx_runtime_device()` (arch/soc lookup, needed to pick the `v75`/`v79`/`v81`
  manifest directory in `session_open()`) shared its live-device cost with the standard QNN
  graph path**, so a `host_only` model with ZERO QNN graphs β€” the Bonsai/Maple ternary decoder
  (`qwen3.8-27b-1bit-npu`) β€” paid for a live QNN HTP device anyway, which then contended with
  the decoder's own direct FastRPC session for the same Hexagon cDSP and made every generation
  fail with a bare `HostOpFailed`. This shipped in v0.20.30 (the SAME release that fixed the
  `ADSP_LIBRARY_PATH` bug for the same model) and was found only by running that SPECIFIC model
  on real Windows-ARM64 hardware after the "fix" β€” reinforcing the point above: a green
  `HostOpFailed`-free run of a DIFFERENT QHexRT model proves nothing about this one. Two red
  herrings were chased and ruled out with live device tracing before the real cause: neither
  `ADSP_LIBRARY_PATH` (confirmed correctly set) nor a QAIRT-version mismatch between the
  shipped host DLLs and the on-device skel (confirmed present, but fixing it alone did not fix
  the failure) was the actual cause. Fixed in `neurun` v0.20.31 (`Backend::profile()` now uses
  a separate, device-handle-free `deviceGetPlatformInfo` query instead of sharing
  `ensure_device()` with `device()`) + this repo's `qhexrt_session.cpp` (#810, a complementary
  fix removing a second, redundant eager device query used only for a log line).

---

## CI/CD workflows (`.github/workflows/`)

| 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 requirements

| 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 |

---

## Non-obvious configuration details

- **NDK pin**: `core/VERSIONS::NDK_VERSION` (27.3.13750724) is the single source of truth,
  mirrored into `bindings/kotlin/gradle.properties`. NDK 27 (r27d) gives 16 KB
  page-alignment required by Android 15+ β€” NDK 25.x's 4 KB-aligned `libc++_shared.so` /
  `libomp.so` trips 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`** (Kotlin `gradle.properties`, similarly named flags elsewhere):
  `true` builds native libs from source locally; CI and most non-local runs set it
  `false` to download prebuilt `.so`/`.xcframework`/`.wasm` from 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`](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`](docs/reference/cursor-cloud-environment.md).

---

## Pre-commit hooks

```bash
pre-commit run --all-files                     # everything
pre-commit run ios-sdk-swiftlint --all-files   # SwiftLint only
```

Configured 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).

---

## Where to look for current in-flight work

`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.