# 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 [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//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 ] # 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 ``` 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.