Skip to content

feat(v2): v2 architecture migration — single long-lived branch (GAP 01-04 done; 05-09 to come)#494

Open
sanchitmonga22 wants to merge 481 commits into
devfrom
feat/v2-architecture
Open

feat(v2): v2 architecture migration — single long-lived branch (GAP 01-04 done; 05-09 to come)#494
sanchitmonga22 wants to merge 481 commits into
devfrom
feat/v2-architecture

Conversation

@sanchitmonga22
Copy link
Copy Markdown
Contributor

@sanchitmonga22 sanchitmonga22 commented Apr 22, 2026

Replaces the (auto-closed during branch rename) PR #493. Same 18 commits, no diff change — only the branch was renamed from feat/v2-architecture-gaps-01-04 to feat/v2-architecture so future v2 work (Waves B-E per docs/wave_roadmap.md) lands on this single long-lived branch instead of fragmenting into per-wave branches.

Workflow contract for this branch

  • This is the single working branch for the entire v2 architecture migration on main.
  • Every future wave (B / C / D / E) commits directly to feat/v2-architecture — no feat/v2-gap0X sub-branches.
  • The PR stays open and grows as each wave merges. Reviewers see the full diff in one place.
  • Per-wave final-gate reports continue to land under docs/gap0X_final_gate_report.md to make the merge-time review easier.
  • When the entire migration is ready to ship to main, this PR squash-merges (or merge-commits, depending on team preference) the whole thing.

What's in this PR today

GAP 01-04 already implemented (Wave A). Per-gap breakdown below.

Gap Title Status
01 IDL + Codegen Infrastructure done
02 Unified Engine Plugin ABI done
03 Dynamic Plugin Loading + ABI Version Check done
04 Engine Router + Hardware Profile done
06 Engines top-level reorg next (Wave B)
07 Single root CMake + presets next (Wave B)
09 Streaming consistency Wave C
08 Delete duplicated frontend logic Wave D
05 DAG runtime primitives (optional) Wave E

18 commits, 202 files changed, +62,471 / −589 LOC (most additions are committed proto-generated code across 6 languages).

GAP 01 — IDL + Codegen

  • idl/ directory with 4 proto schemas (model_types, voice_events, pipeline, solutions) + 7 codegen scripts under idl/codegen/.
  • CI drift-check workflow (.github/workflows/idl-drift-check.yml) that fails any PR where committed generated code drifts from .proto sources.
  • All 5 SDKs migrated to consume the generated types via typealiases (Swift) or thin toProto()/fromProto() bridges (Kotlin / Dart / TS RN / TS Web).
  • Kotlin SDK now has exactly 1 AudioFormat and 1 SDKEnvironment (the duplicates were the original motivation for GAP 01).
  • Final gate: docs/gap01_final_gate_report.md.

GAP 02 — Unified Engine Plugin ABI

  • New rac/plugin/ headers: rac_primitive.h, rac_engine_vtable.h (8 active + 10 reserved primitive slots), rac_plugin_entry.h (with RAC_PLUGIN_API_VERSION + RAC_STATIC_PLUGIN_REGISTER macro).
  • src/plugin/rac_plugin_registry.cpp — ABI validation + capability_check + dedup-by-name + priority sort.
  • 6 new in-tree plugin entry points across llamacpp, llamacpp_vlm, onnx, whispercpp, whisperkit_coreml, metalrt.
  • 4 new tests + docs/engine_plugin_authoring.md.
  • Final gate: docs/gap02_final_gate_report.md.

GAP 03 — Dynamic Plugin Loading

  • rac_plugin_loader.h + plugin_loader.cpp — POSIX (dlopen | RTLD_NOW | RTLD_LOCAL) + Win32 (LoadLibraryA) loader. Symbol resolution: librunanywhere_<name>.sorac_plugin_entry_<name>.
  • RAC_STATIC_PLUGINS CMake option — forced ON for iOS + Emscripten, default OFF elsewhere. Static path uses RAC_STATIC_PLUGIN_REGISTER with __attribute__((used)) + per-plugin extern marker so Apple's linker keeps the TU.
  • llama.cpp dual-mode: same TU compiles into either the static rac_commons or the standalone librunanywhere_llamacpp.so.
  • 4 new tests + docs/plugin_loader_authoring.md.
  • Final gate: docs/gap03_final_gate_report.md.

GAP 04 — Engine Router + Hardware Profile

  • rac_runtime_id_t enum (CPU / Metal / CoreML / ANE / CUDA / Vulkan / QNN / NNAPI / WebGPU / WASM_SIMD + 7 reserved).
  • rac::router::HardwareProfile with per-platform probes (Apple chip-gen via sysctl, Android ro.hardware + QNN/NNAPI dlopen, Linux CUDA/Vulkan dlopen). Honors RAC_FORCE_RUNTIME=cpu env override.
  • rac::router::EngineRouter with deterministic scoring: hard rejects + pinned-name (+10000) + priority + +30 runtime match + +10 format match + tiebreak by name.
  • rac_plugin_route() C ABI wrapper for non-C++ frontends.
  • ABI bump 1u → 2u: rac_engine_metadata_t extended with runtimes[] + formats[] arrays; all 6 in-tree backends updated.
  • 7 router test scenarios + hardware-profile invariant tests.
  • Final gate: docs/gap04_final_gate_report.md.

Forward roadmap

docs/wave_roadmap.md outlines Waves B-E with scope, expected deliverables, dependencies, and likely todo decomposition so the next batch of work starts from a known baseline.

Commit log (18 commits, designed for per-phase review)

0a2dba6f docs(wave-b-c-d-e-outline): post-Wave-A roadmap
b5a14b3d feat(gap04-phase12): rac_plugin_route C ABI + router tests + final gate
f2efc81d feat(gap04-phase8-9-10-11): engine router + ABI v2 metadata extension
d5989608 docs(gap03-phase7): authoring guide + final gate report
7e93d0fe feat(gap03-phase4-5-6): static-macro polish + llama.cpp dual-mode + tests
c6aa7109 feat(gap03-phase1-2-3): dynamic plugin loader + CMake mode split
31872199 docs(gap02-final-gate): Success Criteria verification report
21c13f1c feat(gap02-phase10): plugin registry tests + authoring doc
6648db38 feat(gap02-phase9): ONNX + whispercpp + whisperkit_coreml + metalrt entries
079315e7 feat(gap02-phase8): llama.cpp plugin entry points
e3ad196b feat(gap02-phase7): unified engine plugin ABI + registry
5ce9048a docs(gap01-final-gate): Success Criteria verification report
f506d64f feat(gap01-phase6): VoiceEvent handoff to GAP 09
7566810e feat(gap01-phase5): TS rollout — proto bridges on RN + Web enums
db897b8e feat(gap01-phase4): Dart rollout — proto bridges on every enum
6a34618c feat(gap01-phase3): Kotlin rollout — one AudioFormat, one SDKEnvironment
68265d43 feat(gap01-phase2): Swift rollout — consume generated enums
5ad4ebaa feat(gap01-phase1): IDL + codegen infrastructure

Backwards compatibility

  • Every legacy ABI symbol preserved. rac_service_register_provider() + rac_service_create() continue to work for unmigrated callers.
  • New rac_plugin_* and rac_router_* APIs are parallel surfaces; sample apps + frontend SDKs see no public-API change.
  • RAC_PLUGIN_API_VERSION bumps are explicit (1u in GAP 02, 2u in GAP 04). Plugins compiled against an older version are rejected at register time with RAC_ERROR_ABI_VERSION_MISMATCH + a single specific log line.

Test plan

  • CI drift-check (idl-drift-check.yml) green on Ubuntu 22.04 + macOS 14.
  • swift build --target RunAnywhere green (verified locally).
  • ./gradlew :runanywhere-kotlin:compileKotlinJvm + compileDebugKotlinAndroid green (verified locally).
  • dart analyze sdk/runanywhere-flutter/packages/runanywhere/lib clean (verified locally).
  • tsc --noEmit green on both sdk/runanywhere-react-native/packages/core and sdk/runanywhere-web/packages/core (verified locally).
  • CTest matrix runs every new test (test_engine_vtable, test_plugin_entry_*, test_legacy_coexistence, test_static_registration, test_plugin_loader{,_abi_mismatch,_double_load}, test_engine_router, test_hardware_profile).
  • iOS sample app builds with RAC_STATIC_PLUGINS=ON and rac_registry_plugin_count() > 0 at launch.
  • Linux build produces standalone librunanywhere_llamacpp.so; loading via rac_registry_load_plugin() round-trips clean.
  • All 4 final-gate reports' Success Criteria check out under CI.

Risks

  • GAP 04 ABI bump (1u → 2u) rebuilds every in-tree backend in the same commit; out-of-tree plugins compiled against the older header would be rejected. Safe outcome by design.
  • iOS dead-code stripping of static-registered plugins requires hosts to use -force_load / --whole-archive. The cmake/plugins.cmake helper that wraps these flags lands in Wave B (GAP 07).
  • Pre-existing LlamaCPPRuntime Swift target header drift between the binary RACommons.xcframework and the committed CRACommons headers is unrelated to this PR (confirmed by building pristine main).

Source-of-truth specs

Made with Cursor

Summary by CodeRabbit

Release Notes

  • New Features

    • Added unified plugin system with dynamic engine loading, registration, and hardware-aware routing
    • Added protobuf-based IDL definitions for voice events, model metadata, pipelines, and solutions
    • Added code generation toolchain supporting Swift, Kotlin, Dart, TypeScript, Python, and C++
  • Documentation

    • Added comprehensive architecture guides for plugin authoring, engine routing, and IDL migration
    • Added GAP final gate reports documenting completion of design phases
  • Build & Infrastructure

    • Added GitHub Actions workflow for IDL drift detection and code generation validation
    • Added setup script for code generation toolchain
    • Added CMake configuration for protobuf-based IDL compilation
  • Tests

    • Added comprehensive test suite for plugin registry, dynamic loading, and engine routing

Note

Medium Risk
Moderate risk because it replaces the PR CI build workflow and introduces a new root CMake/preset-based build entrypoint that could break cross-platform builds if presets or helper macros diverge from existing scripts.

Overview
Build/CI overhaul for the v2 migration. Adds a root CMakeLists.txt + CMakePresets.json as the single native build entrypoint, plus new shared CMake helpers (cmake/platform.cmake, cmake/plugins.cmake, cmake/protobuf.cmake, cmake/sanitizers.cmake) to standardize platform detection, plugin target creation/force-load, protobuf detection/codegen, and sanitizer flags.

GitHub Actions changes. Replaces the previous path-filtered, script-driven pr-build.yml with a smaller preset-based matrix (macOS/Linux/iOS/Android + per-SDK wrapper checks), adds idl-drift-check.yml to regenerate bindings and fail on drift, and adds streaming-perf.yml to build/run streaming parity/perf fixtures and upload artifacts.

SDK/tooling + docs updates. Marks generated binding trees as linguist-generated in .gitattributes, updates Swift SPM to depend on swift-protobuf and exclude unused generated *.grpc.swift stubs (plus flips useLocalNatives to true), makes Android NDK path configurable via racNdkVersion, and adds/updates several architecture/migration/release documents and SDK docs to reflect proto-stream voice agent usage and current package versions.

Reviewed by Cursor Bugbot for commit 801cac4. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 22, 2026

Too many files changed for review. (202 files found, 100 file limit)

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a71cda93-fa00-4514-b18f-71df88a14cb3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements a unified engine plugin system architecture with protocol-buffer based IDL schemas and multi-language code generation. It introduces plugin registration/discovery, hardware-aware engine routing, dynamic/static plugin loading, CI drift-checking for generated artifacts, and corresponding language SDK updates to bridge proto-generated types.

Changes

Cohort / File(s) Summary
IDL Protocol Schemas
idl/*.proto
Added four proto3 schema files (model_types.proto, voice_events.proto, pipeline.proto, solutions.proto) defining cross-SDK enumerations, message types for streaming voice events, pipeline DAG configurations, and solution templates with language-specific codegen directives.
IDL Codegen & Toolchain
idl/codegen/*.sh, scripts/setup-toolchain.sh, idl/README.md
Created per-language protobuf code generation scripts (Swift, Kotlin, Dart, TypeScript, Python, C++), a combined entrypoint generator script, and a toolchain setup/verification utility. Added README documenting IDL compatibility policy and CI drift prevention.
CI/CD & Git Configuration
.gitattributes, .github/workflows/idl-drift-check.yml
Added GitHub Linguist metadata for generated directories across SDKs and a new macOS-based GitHub Actions workflow that regenerates all language bindings and fails if uncommitted drift is detected.
Plugin System Core
sdk/runanywhere-commons/include/rac/plugin/*.h, sdk/runanywhere-commons/src/plugin/*.cpp
Implemented unified engine plugin ABI: vtable structures with ABI versioning, plugin entry/registration macros, registry with priority-based lookup, and dynamic loader supporting both dlopen/dlsym shared loading and static initialization modes.
Hardware-Aware Router
sdk/runanywhere-commons/include/rac/router/*.h, sdk/runanywhere-commons/src/router/*.cpp
Added hardware capability detection (CPU/GPU vendors, platform-specific runtimes), engine router with scoring/tiebreak logic, and C ABI wrapper for frontend plugin selection by primitive and preferred runtime.
Backend Plugin Entry Points
sdk/runanywhere-commons/src/backends/*/rac_plugin_entry*.cpp, *_register.cpp linkage changes
Added unified-ABI plugin entry implementations for LlamaCPP (LLM/VLM), ONNX (STT/TTS/VAD), WhisperCPP (STT), WhisperKit CoreML (STT), and MetalRT (multi-ops). Changed backend ops symbols from static const to const for cross-TU visibility.
Build Configuration
Package.swift, gradle/libs.versions.toml, sdk/runanywhere-commons/CMakeLists.txt, sdk/runanywhere-commons/src/backends/*/CMakeLists.txt, idl/CMakeLists.txt
Added Swift Package dependencies (swift-protobuf), Gradle Wire library/plugin entries, CMake static/dynamic plugin mode selection, IDL C++ generation target, and per-backend plugin entry compilation units.
Testing Infrastructure
sdk/runanywhere-commons/tests/*.cpp, sdk/runanywhere-commons/tests/fixtures/*.cpp, sdk/runanywhere-commons/tests/CMakeLists.txt
Added comprehensive unit tests for vtable ABI, legacy coexistence, hardware profiling, engine router scoring, static/dynamic plugin loading, and per-backend plugin entry validation. Included test fixture libraries with ABI mismatch variants.
Documentation
docs/gap0*_final_gate_report.md, docs/*_authoring.md, docs/wave_roadmap.md, docs/voice_event_proto_handoff.md
Added four final gate reports (GAP 01–04), third-party plugin authoring guides, voice event streaming migration handoff spec, and wave roadmap for remaining architecture phases.
Flutter/Dart SDK Updates
sdk/runanywhere-flutter/packages/runanywhere/lib/**/*.dart, sdk/runanywhere-flutter/packages/runanywhere/pubspec.yaml
Added proto bridging methods (toProto/fromProto) to enums in audio_format.dart, model_types.dart, and sdk_environment.dart. Added protobuf and fixnum runtime dependencies to pubspec.yaml.
Kotlin SDK Updates
sdk/runanywhere-kotlin/build.gradle.kts, sdk/runanywhere-kotlin/src/commonMain/kotlin/**/*.kt
Removed local AudioFormat enum from AudioTypes.kt, moved/extended it in ComponentTypes.kt with proto bridging. Updated InferenceFramework with proto conversion. Added api(libs.wire.runtime) dependency. Unified SDKEnvironment import in SDKLogger.kt.
Swift Package Dependency
Package.resolved
Pinned swift-protobuf to version 1.37.0 with remote source reference.

Sequence Diagram(s)

sequenceDiagram
    participant Frontend as Frontend/App
    participant Router as EngineRouter<br/>(CPU: Intel, GPU: Metal)
    participant Registry as Plugin Registry
    participant Backend as Engine Backend<br/>(e.g., LLama.cpp)

    Frontend->>Router: route(primitive=GENERATE_TEXT,<br/>preferred_runtime=Metal)
    activate Router
    Router->>Router: score(LlamaCPP vtable)<br/>priority=50, Metal support=false<br/>score=-1000
    Router->>Router: score(MetalRT vtable)<br/>priority=60, Metal support=true<br/>score=70 (60+Metal bonus)
    Router->>Registry: find(GENERATE_TEXT)
    activate Registry
    Registry-->>Router: [MetalRT, LlamaCPP] (sorted by score)
    deactivate Registry
    Router-->>Frontend: RouteResult(vtable=MetalRT,<br/>score=70)
    deactivate Router

    Frontend->>Backend: llm_ops->generate(...)
    activate Backend
    Backend-->>Frontend: result
    deactivate Backend
Loading
sequenceDiagram
    participant Loader as rac_registry_load_plugin()
    participant SO as Shared Library<br/>(dlopen/LoadLibrary)
    participant Entry as Plugin Entry Point<br/>(rac_plugin_entry_*)
    participant Registry as Plugin Registry<br/>(rac_plugin_register)
    participant App as App Runtime

    Loader->>SO: dlopen("/path/to/librunanywhere_onnx.so")
    activate SO
    SO-->>Loader: handle
    deactivate SO
    Loader->>Entry: dlsym(handle, "rac_plugin_entry_onnx")
    activate Entry
    Entry-->>Loader: function pointer
    deactivate Entry
    Loader->>Entry: rac_plugin_entry_onnx()
    activate Entry
    Entry-->>Loader: rac_engine_vtable_t*<br/>(metadata.abi_version=2,<br/>stt_ops, tts_ops, vad_ops)
    deactivate Entry
    
    Loader->>Registry: rac_plugin_register(vtable)
    activate Registry
    Registry->>Registry: validate ABI version<br/>matches RAC_PLUGIN_API_VERSION
    Registry->>Registry: insert into primitive buckets<br/>(TRANSCRIBE, SYNTHESIZE,<br/>DETECT_VOICE)
    Registry-->>Loader: RAC_SUCCESS
    deactivate Registry
    
    Loader->>SO: store dl handle
    Loader-->>App: RAC_SUCCESS

    App->>Registry: rac_plugin_find(TRANSCRIBE)
    Registry-->>App: onnx vtable (priority-sorted)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~90 minutes

Possibly related PRs

  • #459: Modifies MetalRT backend C++ sources, CMake wiring, and Swift/Package integrations alongside unified plugin entry point additions in this PR.
  • #469: Updates LlamaCPP backend vtable/ops (g_llamacpp_ops linkage and function pointer assignments) that are also refactored in this PR for unified plugin entry points.
  • #344: Adds VLM (Vision Language Model) support across C/C++ public APIs, plugin vtable entries, and rac_vlm_component wiring that overlaps with this PR's plugin infrastructure.

Suggested labels

enhancement, architecture, idl-codegen, plugin-system, multi-language-sdk

Suggested reviewers

  • Siddhesh2377

Poem

🐰 A cottontail hops through the unified gates,
Plugins now register without hesitates,
From Swift to Dart to Kotlin's embrace,
Proto-bound schemas keep drifting at bay!
🔌✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v2-architecture

sanchitmonga22 added a commit that referenced this pull request Apr 22, 2026
Per review request — all v2 architecture work lives on the one
`feat/v2-architecture` branch tracked by PR #494, instead of fragmenting
into per-wave sub-branches. Updates `docs/wave_roadmap.md` to encode
this contract for future contributors:

- Branch: `feat/v2-architecture` (single, long-lived).
- PR:     #494 (stays open and grows commit-by-commit).
- Cadence: one commit per phase, message prefix `feat(gapXX-phaseN)`.
- Per-wave milestone: checked-in `docs/gap0X_final_gate_report.md`.
- Merge to main: only when GAP 01-08 are all done (GAP 05 opt-in).

Refresh the title from "Post-Wave-A roadmap" to "v2 architecture
roadmap" to match the broader scope. Note Wave A is now MERGED INTO
the branch (not "this branch").

No code changes.

Made-with: Cursor
Comment on lines +37 to +100
name: Verify generated code matches IDL
runs-on: macos-14
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- name: Cache Homebrew
uses: actions/cache@v4
with:
path: |
/usr/local/Homebrew
/opt/homebrew
~/Library/Caches/Homebrew
key: ${{ runner.os }}-brew-protoc-${{ hashFiles('scripts/setup-toolchain.sh') }}

- name: Install protoc + swift-protobuf (Homebrew)
run: |
brew install protobuf swift-protobuf

- name: Install wire-compiler (best-effort — Gradle Wire plugin is the fallback)
run: |
brew install wire || echo "wire bottle unavailable; Gradle Wire plugin will handle Kotlin codegen"

- name: Install Dart plugin (protoc-gen-dart)
run: |
if command -v dart >/dev/null 2>&1; then
dart pub global activate protoc_plugin 21.1.2
echo "$HOME/.pub-cache/bin" >> "$GITHUB_PATH"
else
echo "::warning::dart not found on macos-14 runner; Dart codegen skipped"
fi

- name: Install ts-proto (npm)
run: |
npm install -g ts-proto@1.181.1 protobufjs

- name: Install Python protobuf
run: |
python3 -m pip install --upgrade "protobuf>=4.25,<5" grpcio-tools

- name: Dump toolchain versions (debug)
run: |
echo "protoc: $(protoc --version)"
echo "protoc-gen-swift: $(protoc-gen-swift --version 2>/dev/null || echo 'not present')"
echo "wire-compiler: $(wire-compiler --version 2>/dev/null || echo 'not present')"
echo "protoc-gen-dart: $(protoc-gen-dart --version 2>/dev/null || echo 'present or skipped')"
echo "node: $(node --version)"
echo "python3: $(python3 --version)"

- name: Regenerate all bindings
run: ./idl/codegen/generate_all.sh

- name: Fail on drift
run: |
if ! git diff --exit-code --stat; then
echo "::error::IDL-generated code is out of sync with .proto sources."
echo ""
echo "To fix locally:"
echo " ./scripts/setup-toolchain.sh"
echo " ./idl/codegen/generate_all.sh"
echo " git add -A && git commit -m 'chore(codegen): regenerate bindings'"
exit 1
fi
echo "✓ No drift detected."
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
sdk/runanywhere-commons/src/backends/whisperkit_coreml/CMakeLists.txt (1)

15-25: ⚠️ Potential issue | 🟡 Minor

Add RAC_WHISPERKIT_COREML_BUILDING compile definition to match peer backends.

The WhisperKit CMakeLists.txt does not define a backend-specific RAC_WHISPERKIT_COREML_BUILDING macro, unlike ONNX, LlamaCPP, and MetalRT. While the public callback functions use RAC_API (which has unconditional visibility("default")), the plugin entry point rac_plugin_entry_whisperkit_coreml has no explicit visibility attribute and relies on default behavior. Add the definition to maintain consistency and ensure robust symbol visibility:

target_compile_definitions(rac_backend_whisperkit_coreml PRIVATE RAC_WHISPERKIT_COREML_BUILDING)

Then create rac_backend_whisperkit_coreml.h with the visibility wrapper pattern used by peer backends, or annotate the entry symbol explicitly if it needs special handling in shared builds.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/src/backends/whisperkit_coreml/CMakeLists.txt` around
lines 15 - 25, Add a compile definition and visibility wrapper for the
WhisperKit backend: update the CMake target rac_backend_whisperkit_coreml to
call target_compile_definitions(... PRIVATE RAC_WHISPERKIT_COREML_BUILDING) so
the backend-specific macro is defined for shared builds, and add a new header
rac_backend_whisperkit_coreml.h that mirrors the visibility wrapper pattern used
by ONNX/LlamaCPP/MetalRT (define RAC_WHISPERKIT_COREML_BUILDING to export
symbols via RAC_API and annotate the plugin entry function
rac_plugin_entry_whisperkit_coreml or include the header in that source to
ensure the entry symbol has the correct visibility in shared builds).
sdk/runanywhere-commons/src/backends/whisperkit_coreml/rac_backend_whisperkit_coreml_register.cpp (1)

91-98: ⚠️ Potential issue | 🔴 Critical

g_whisperkit_coreml_stt_ops has internal linkage and cannot be accessed via extern from another translation unit.

The symbol is defined inside the unnamed namespace { block (opened at line 24, closed at line 174) at line 91. Names declared in an unnamed namespace have internal linkage per C++ [basic.link], so the extern declaration in rac_plugin_entry_whisperkit_coreml.cpp line 19 cannot resolve to this symbol at link time.

Move the definition outside the anonymous namespace with extern "C":

Fix
 namespace {
 
 const char* LOG_CAT = "WhisperKitCoreML";
 
 // ... vtable functions ...
 
+}  // namespace
+
+extern "C" const rac_stt_service_ops_t g_whisperkit_coreml_stt_ops = {
-const rac_stt_service_ops_t g_whisperkit_coreml_stt_ops = {
     .initialize = whisperkit_coreml_stt_vtable_initialize,
     .transcribe = whisperkit_coreml_stt_vtable_transcribe,
     .transcribe_stream = whisperkit_coreml_stt_vtable_transcribe_stream,
     .get_info = whisperkit_coreml_stt_vtable_get_info,
     .cleanup = whisperkit_coreml_stt_vtable_cleanup,
     .destroy = whisperkit_coreml_stt_vtable_destroy,
 };
+
+namespace {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-commons/src/backends/whisperkit_coreml/rac_backend_whisperkit_coreml_register.cpp`
around lines 91 - 98, The symbol g_whisperkit_coreml_stt_ops is defined inside
an unnamed namespace so it has internal linkage and cannot satisfy the extern in
rac_plugin_entry_whisperkit_coreml.cpp; move the definition of
g_whisperkit_coreml_stt_ops out of the anonymous namespace to global scope and
give it external C linkage (e.g., declare/define it as extern "C" const
rac_stt_service_ops_t g_whisperkit_coreml_stt_ops) so the extern in the other TU
can link to it, keeping the existing initializer and references to
whisperkit_coreml_stt_vtable_* functions unchanged.
sdk/runanywhere-commons/src/backends/llamacpp/rac_backend_llamacpp_register.cpp (1)

156-179: ⚠️ Potential issue | 🔴 Critical

Move g_llamacpp_ops outside the anonymous namespace — currently it cannot be resolved by plugin entry extern declarations.

g_llamacpp_ops is defined at line 162 inside the namespace { block (opened at line 27, closed at line 291), yet rac_plugin_entry_llamacpp.cpp attempts to extern it. Per C++ [basic.link], names in an unnamed namespace have internal linkage regardless of whether static is used, so the extern declaration will fail to link.

Similarly, all five backend register files have identical issues:

  • rac_backend_whisperkit_coreml_register.cpp: namespace 24–174, g_whisperkit_coreml_stt_ops at line 91
  • rac_backend_whispercpp_register.cpp: namespace 23–188, g_whispercpp_stt_ops at line 106
  • rac_backend_onnx_register.cpp: namespace 39–538, multiple ops structs inside
  • rac_backend_metalrt_register.cpp: namespace 79–499, g_metalrt_llm_ops at line 159

Move each ops struct (and referenced vtable functions, or forward-declare them) outside its anonymous namespace, or define the unified plugin entry in the same TU.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-commons/src/backends/llamacpp/rac_backend_llamacpp_register.cpp`
around lines 156 - 179, The ops struct g_llamacpp_ops is inside an unnamed
namespace so it has internal linkage and cannot be extern'd by
rac_plugin_entry_llamacpp.cpp; move the declaration/definition of g_llamacpp_ops
out of the anonymous namespace (or remove the extern use by placing the plugin
entry in the same TU), and ensure any vtable functions it references
(llamacpp_vtable_initialize, llamacpp_vtable_generate, etc.) are either
forward-declared at namespace-scope or also defined outside the anonymous
namespace; apply the same fix for the other backends' ops structs
(g_whisperkit_coreml_stt_ops, g_whispercpp_stt_ops, the ops in
rac_backend_onnx_register.cpp, g_metalrt_llm_ops) so the plugin entry externs
can link them.
sdk/runanywhere-commons/src/backends/whispercpp/rac_backend_whispercpp_register.cpp (1)

23-188: ⚠️ Potential issue | 🔴 Critical

Critical: g_whispercpp_stt_ops has internal linkage and cannot be referenced externally.

The vtable definition at line 106 sits inside the anonymous namespace (namespace {}, lines 23–188). Per C++ [basic.link], names in unnamed namespaces have internal linkage regardless of the static keyword. The extern declaration in rac_plugin_entry_whispercpp.cpp:14 will fail to link.

Move g_whispercpp_stt_ops outside the anonymous namespace. Keep helper functions (convert_int16_to_float32, vtable implementations) inside namespace {}.

Proposed fix
namespace {

const char* LOG_CAT = "WhisperCPP";

/**
 * Convert Int16 PCM audio to Float32 normalized to [-1.0, 1.0].
 */
static std::vector<float> convert_int16_to_float32(const void* int16_data, size_t byte_count) {
    // ... implementation ...
}

// Vtable function implementations
static rac_result_t whispercpp_stt_vtable_initialize(void* impl, const char* model_path) { /* ... */ }
static rac_result_t whispercpp_stt_vtable_transcribe(void* impl, const void* audio_data, /* ... */ ) { /* ... */ }
static rac_result_t whispercpp_stt_vtable_transcribe_stream(void* impl, /* ... */ ) { /* ... */ }
static rac_result_t whispercpp_stt_vtable_get_info(void* impl, rac_stt_info_t* out_info) { /* ... */ }
static rac_result_t whispercpp_stt_vtable_cleanup(void* impl) { /* ... */ }
static void whispercpp_stt_vtable_destroy(void* impl) { /* ... */ }

const char* const MODULE_ID = "whispercpp";
const char* const STT_PROVIDER_NAME = "WhisperCPPSTTService";

rac_bool_t whispercpp_stt_can_handle(const rac_service_request_t* request, void* user_data) { /* ... */ }
rac_handle_t whispercpp_stt_create(const rac_service_request_t* request, void* user_data) { /* ... */ }

bool g_registered = false;

}  // namespace

// Externally-visible vtable
extern "C" const rac_stt_service_ops_t g_whispercpp_stt_ops = {
    .initialize = whispercpp_stt_vtable_initialize,
    .transcribe = whispercpp_stt_vtable_transcribe,
    .transcribe_stream = whispercpp_stt_vtable_transcribe_stream,
    .get_info = whispercpp_stt_vtable_get_info,
    .cleanup = whispercpp_stt_vtable_cleanup,
    .destroy = whispercpp_stt_vtable_destroy,
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-commons/src/backends/whispercpp/rac_backend_whispercpp_register.cpp`
around lines 23 - 188, The g_whispercpp_stt_ops vtable is defined inside the
anonymous namespace so it has internal linkage and cannot be referenced from
rac_plugin_entry_whispercpp.cpp; move the definition of g_whispercpp_stt_ops out
of the anonymous namespace (leaving helper functions like
convert_int16_to_float32 and the vtable implementation functions
whispercpp_stt_vtable_initialize/transcribe/transcribe_stream/get_info/cleanup/destroy
inside the anonymous namespace) so the symbol has external linkage, and ensure
its declaration matches the extern usage in rac_plugin_entry_whispercpp.cpp.
sdk/runanywhere-commons/src/backends/llamacpp/rac_backend_llamacpp_vlm_register.cpp (1)

25-240: ⚠️ Potential issue | 🔴 Critical

Critical: g_llamacpp_vlm_ops remains internally-linked — unnamed namespace prevents external linkage.

The definition at lines 114–124 is enclosed by the anonymous namespace (opened line 25, closed line 240). Per C++ [basic.link], names in an unnamed namespace have internal linkage; removing the static keyword does not change this. The comment on lines 114–115 is incorrect: simply making the variable non-static does not allow external linkage from within an unnamed namespace.

The plugin entry TU (rac_plugin_entry_llamacpp_vlm.cpp line 19) declares extern const rac_vlm_service_ops_t g_llamacpp_vlm_ops;, which will not resolve to this definition and will cause a linker error.

Hoist g_llamacpp_vlm_ops and its vtable function pointers out of the anonymous namespace to give them external linkage. (Same issue and fix pattern as rac_backend_whispercpp_register.cpp.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-commons/src/backends/llamacpp/rac_backend_llamacpp_vlm_register.cpp`
around lines 25 - 240, The exported vtable g_llamacpp_vlm_ops is currently
inside an unnamed namespace so it has internal linkage and cannot satisfy the
extern in rac_plugin_entry_llamacpp_vlm.cpp; move the vtable and its related
vtable functions out of the anonymous namespace to give them external linkage.
Specifically, take the const rac_vlm_service_ops_t g_llamacpp_vlm_ops definition
and the functions it references (llamacpp_vlm_vtable_initialize,
llamacpp_vlm_vtable_process, llamacpp_vlm_vtable_process_stream,
llamacpp_vlm_vtable_get_info, llamacpp_vlm_vtable_cancel,
llamacpp_vlm_vtable_cleanup, llamacpp_vlm_vtable_destroy) out of the anonymous
namespace (keep other helper types like VLMStreamAdapter or registry state
inside if desired), ensure the symbol names remain unchanged and visible at
global scope, and keep the signature matching the extern declaration so the
linker can resolve g_llamacpp_vlm_ops.
sdk/runanywhere-commons/src/backends/metalrt/rac_backend_metalrt_register.cpp (1)

159-322: ⚠️ Potential issue | 🔴 Critical

The vtable symbols cannot be referenced via extern declarations while inside an anonymous namespace.

g_metalrt_llm_ops (line 159), g_metalrt_stt_ops (line 209), g_metalrt_tts_ops (line 254), and g_metalrt_vlm_ops (line 314) are all defined within the anonymous namespace (lines 79–499). Per the C++ standard, names in an unnamed namespace have internal linkage—extern declarations in rac_plugin_entry_metalrt.cpp (lines 22–25) cannot bind to these definitions. This will produce either a linker error (unresolved symbol) or silent dispatch to the wrong definition.

To export these vtables so rac_plugin_entry_metalrt.cpp can reference them, move the four g_metalrt_*_ops definitions outside the anonymous namespace, or expose them via accessor functions that reside outside the namespace.

Note: The ONNX backend (rac_backend_onnx_register.cpp) exhibits the same pattern (ops inside anonymous namespace at lines 39–538, referenced via extern in rac_plugin_entry_onnx.cpp), which suggests this issue may be systemic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-commons/src/backends/metalrt/rac_backend_metalrt_register.cpp`
around lines 159 - 322, The four vtable symbols g_metalrt_llm_ops,
g_metalrt_stt_ops, g_metalrt_tts_ops, and g_metalrt_vlm_ops are currently
defined inside an anonymous namespace so external extern declarations cannot
bind to them; fix by moving each of those const rac_*_service_ops_t definitions
out of the unnamed namespace (place them at namespace scope with external
linkage) or alternatively add and export simple accessor functions (e.g.,
get_metalrt_llm_ops(), get_metalrt_stt_ops(), get_metalrt_tts_ops(),
get_metalrt_vlm_ops()) defined outside the anonymous namespace that return
pointers/references to the corresponding ops, and update
rac_plugin_entry_metalrt.cpp to use those accessors instead of extern symbols.
sdk/runanywhere-commons/src/backends/onnx/rac_backend_onnx_register.cpp (1)

147-384: ⚠️ Potential issue | 🔴 Critical

Linkage error: service ops defined in anonymous namespace cannot be externally linked.

g_onnx_stt_ops (line ~147), g_onnx_tts_ops (line ~213), and g_onnx_vad_ops (line ~376) are defined inside the anonymous namespace (lines 39–538). By C++ standard, symbols in unnamed namespaces have internal linkage. When rac_plugin_entry_onnx.cpp declares extern const rac_stt_service_ops_t g_onnx_stt_ops; etc., the linker cannot resolve these symbols because they are not visible outside their translation unit.

Removing static alone will not help—the anonymous namespace already enforces internal linkage. Move the three definitions outside the anonymous namespace, or expose them via accessor functions in the extern "C" block below.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/src/backends/onnx/rac_backend_onnx_register.cpp`
around lines 147 - 384, The service ops objects g_onnx_stt_ops, g_onnx_tts_ops,
and g_onnx_vad_ops are currently defined inside an unnamed (anonymous) namespace
which gives them internal linkage, so extern declarations in
rac_plugin_entry_onnx.cpp cannot link to them; fix this by moving the three
definitions (g_onnx_stt_ops, g_onnx_tts_ops, g_onnx_vad_ops) out of the
anonymous namespace into global scope (or alternatively add extern "C" accessor
functions that return pointers to these objects and call those from
rac_plugin_entry_onnx.cpp), ensuring the objects remain non-static and globally
visible.
♻️ Duplicate comments (1)
.github/workflows/idl-drift-check.yml (1)

35-40: ⚠️ Potential issue | 🟡 Minor

Add an explicit permissions: block.

CodeQL has already flagged this. A contents: read default is sufficient for a drift check that only reads the repo.

🔒 Suggested change
 jobs:
   check:
     name: Verify generated code matches IDL
     runs-on: macos-14
     timeout-minutes: 15
+    permissions:
+      contents: read
     steps:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/idl-drift-check.yml around lines 35 - 40, Add an explicit
permissions block to the workflow so the job has only the repo read permission;
update the workflow (job "check" in .github/workflows/idl-drift-check.yml) to
include a top-level permissions: entry with contents: read to satisfy CodeQL and
limit token scope for the verify generated code job.
🟡 Minor comments (18)
idl/codegen/ci-drift-check.sh-24-31 (1)

24-31: ⚠️ Potential issue | 🟡 Minor

Drift check misses newly generated (untracked) files.

git diff --exit-code --stat only reports modifications to tracked files. If generate_all.sh creates a brand-new output file (e.g., when a new .proto is added and its first-time generated binding isn't committed yet), the file shows up as untracked and the drift check passes silently.

Consider staging everything first, or explicitly checking for untracked files:

🔧 Proposed fix
-# Fail loud on any drift.
-if ! git diff --exit-code --stat; then
+# Fail loud on any drift (modifications or new untracked outputs).
+git add -A -N .  # intent-to-add so untracked files show up in diff
+if ! git diff --exit-code --stat; then
     echo "" >&2
     echo "::error::IDL-generated code is out of sync with .proto sources." >&2

Or equivalently, assert git status --porcelain is empty.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@idl/codegen/ci-drift-check.sh` around lines 24 - 31, The current drift check
uses "git diff --exit-code --stat" which ignores untracked files so newly
generated files (from generate_all.sh) can be missed; modify the script to first
run a check for any workspace changes including untracked files (for example by
running "git status --porcelain" and failing if its output is non-empty) or
alternatively stage all changes and compare the index (e.g., "git add -A" then
"git diff --cached --exit-code --stat"); update the block that currently runs
"git diff --exit-code --stat" to use one of these approaches so untracked
generated files cause the check to fail.
docs/gap04_final_gate_report.md-41-49 (1)

41-49: ⚠️ Potential issue | 🟡 Minor

Broken placeholder link in a gate-closure document.

Line 44 points the "execution wave plan" reference to https://example.invalid/plan, which is not a real target. Since example.invalid is the reserved RFC 2606 TLD, this is clearly a placeholder that slipped through. Either link to the actual file in-repo (e.g., a relative path under v2_gap_specs/ or docs/) or remove the hyperlink.

Minor nit on line 9: "iOS17 ANE run" reads better as "iOS 17 ANE run".

✍️ Proposed fix
-Wave A (GAP 03 + GAP 04) ships the dynamic-loader + hardware-aware router on top of the GAP 02 plugin ABI. Subsequent waves per
-[`gap03_gap04_execution_wave_08047ae8.plan.md`](https://example.invalid/plan):
+Wave A (GAP 03 + GAP 04) ships the dynamic-loader + hardware-aware router on top of the GAP 02 plugin ABI. Subsequent waves per
+[`gap03_gap04_execution_wave_08047ae8.plan.md`](../path/to/gap03_gap04_execution_wave_08047ae8.plan.md):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/gap04_final_gate_report.md` around lines 41 - 49, The placeholder link
to https://example.invalid/plan (referenced as
`gap03_gap04_execution_wave_08047ae8.plan.md`) in
docs/gap04_final_gate_report.md is invalid; replace the hyperlink with either
the correct in-repo relative path (e.g., the actual file under v2_gap_specs/ or
docs/) or remove the link and keep plain text, ensuring the reference text
`gap03_gap04_execution_wave_08047ae8.plan.md` matches the real filename; also
fix the minor typo by changing the phrase "iOS17 ANE run" to "iOS 17 ANE run".
idl/codegen/generate_kotlin.sh-21-29 (1)

21-29: ⚠️ Potential issue | 🟡 Minor

Fix the Wire output root to align directory structure with package paths.

The current configuration generates files at .../com/runanywhere/sdk/generated/ai/runanywhere/proto/v1/ with package declaration ai.runanywhere.proto.v1. This creates a mismatch: the directory path includes com/runanywhere/sdk/generated but the package does not.

Wire treats --kotlin_out as a source root and appends the package directory structure from the proto java_package option. Since the proto files specify option java_package = "ai.runanywhere.proto.v1", change the output root to sdk/runanywhere-kotlin/src/commonMain/kotlin so files are generated at the correct structure matching their package names.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@idl/codegen/generate_kotlin.sh` around lines 21 - 29, Update the OUT_DIR used
in generate_kotlin.sh so the Wire compiler's --kotlin_out points to the Kotlin
source root instead of embedding "com/runanywhere/sdk/generated"; change the
OUT_DIR variable (and the mkdir -p target) from the current
"${REPO_ROOT}/sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/generated"
to "${REPO_ROOT}/sdk/runanywhere-kotlin/src/commonMain/kotlin" and ensure the
wire-compiler invocation continues to use "--kotlin_out=\"${OUT_DIR}\"" so
generated files follow the package path from the proto java_package option.
sdk/runanywhere-commons/tests/test_static_registration.cpp-27-29 (1)

27-29: ⚠️ Potential issue | 🟡 Minor

Narrowing: 0xFEEDFACE does not fit in int.

0xFEEDFACE = 4,276,993,774, which exceeds INT_MAX (2,147,483,647) on all common platforms. Initializing const int from it is a narrowing/implementation-defined conversion and will warn (or fail under -Wnarrowing/-Werror). Use an unsigned or wider type — it's just a sentinel pointer value, so unsigned is fine.

🛡️ Proposed fix
 namespace {
-const int k_sentinel_static = 0xFEEDFACE;
+const unsigned int k_sentinel_static = 0xFEEDFACEu;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/tests/test_static_registration.cpp` around lines 27 -
29, k_sentinel_static is declared as const int but initialized with 0xFEEDFACE
which exceeds INT_MAX and causes a narrowing/implementation-defined conversion;
change its type to an unsigned or wider integer type (e.g., constexpr unsigned
int, uint32_t, or uintptr_t) and use an unsigned literal (0xFEEDFACEu) so the
sentinel value is represented without narrowing in the anonymous namespace.
sdk/runanywhere-commons/src/backends/whisperkit_coreml/rac_plugin_entry_whisperkit_coreml.cpp-34-37 (1)

34-37: ⚠️ Potential issue | 🟡 Minor

Use protobuf enum symbols instead of magic numbers for model formats.

The hardcoded values 6 and 8 will silently drift if new enum values are inserted before MODEL_FORMAT_COREML or MODEL_FORMAT_MLPACKAGE in idl/model_types.proto. Include the generated protobuf header and reference the enum symbols directly.

Proposed fix
+#include "rac/plugin/rac_engine_vtable.h"
+#include "rac/plugin/rac_plugin_entry.h"
+#include "rac/features/stt/rac_stt_service.h"
+#include "rac/core/rac_error.h"
+#include "rac/generated/proto/model_types.pb.h"
 
 extern "C" {
 
 extern const rac_stt_service_ops_t g_whisperkit_coreml_stt_ops;
 
 static rac_result_t whisperkit_coreml_capability_check(void) {
 `#if` defined(__APPLE__)
     return RAC_SUCCESS;
 `#else`
     return RAC_ERROR_CAPABILITY_UNSUPPORTED;
 `#endif`
 }
 
 static const rac_runtime_id_t k_whisperkit_coreml_runtimes[] = {
     RAC_RUNTIME_COREML,
     RAC_RUNTIME_ANE,
 };
 
 static const uint32_t k_whisperkit_coreml_formats[] = {
-    6,  /* MODEL_FORMAT_COREML    */
-    8,  /* MODEL_FORMAT_MLPACKAGE */
+    static_cast<uint32_t>(MODEL_FORMAT_COREML),
+    static_cast<uint32_t>(MODEL_FORMAT_MLPACKAGE),
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-commons/src/backends/whisperkit_coreml/rac_plugin_entry_whisperkit_coreml.cpp`
around lines 34 - 37, Replace the magic numeric literals in
k_whisperkit_coreml_formats with the generated protobuf enum symbols and include
the generated protobuf header: add an `#include` for the model_types protobuf
header (e.g., the generated idl/model_types.pb.h) at the top of the file and
change the array entries to use MODEL_FORMAT_COREML and MODEL_FORMAT_MLPACKAGE
(the protobuf enum symbols referenced in idl/model_types.proto) so the code uses
the canonical enum values instead of hardcoded numbers.
idl/CMakeLists.txt-26-43 (1)

26-43: ⚠️ Potential issue | 🟡 Minor

Remove dead _RAC_IDL_GEN_DIR variable and dead include directive.

protobuf_generate_cpp() emits files directly to ${CMAKE_CURRENT_BINARY_DIR}, not to ${_RAC_IDL_GEN_DIR}. The file(MAKE_DIRECTORY) call and the second target_include_directories() targeting ${_RAC_IDL_GEN_DIR} are unused. Also, the comment on lines 39–40 incorrectly claims consumers will include "runanywhere/idl/model_types.pb.h" — they will actually include "model_types.pb.h" (no prefix) because the include root is the binary dir.

Simplest fix: delete lines 27–29 and lines 39–42, and wrap the first target_include_directories() argument in $<BUILD_INTERFACE:>.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@idl/CMakeLists.txt` around lines 26 - 43, Remove the dead _RAC_IDL_GEN_DIR
setup and the unused include directive: delete the file(MAKE_DIRECTORY
${_RAC_IDL_GEN_DIR}) and the _RAC_IDL_GEN_DIR variable usage plus the second
target_include_directories(...) that references it; keep
protobuf_generate_cpp(...) as-is (it emits into ${CMAKE_CURRENT_BINARY_DIR}),
and change the existing target_include_directories(rac_idl PUBLIC
${CMAKE_CURRENT_BINARY_DIR}) to wrap the include in $<BUILD_INTERFACE:...> so it
reads target_include_directories(rac_idl PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>); leave
target_link_libraries(rac_idl PUBLIC ${Protobuf_LIBRARIES}) and the
add_library(rac_idl STATIC ...) intact.
sdk/runanywhere-commons/src/plugin/plugin_registry_internal.h-40-46 (1)

40-46: ⚠️ Potential issue | 🟡 Minor

Docstring doesn't match the signature of rac_plugin_registry_snapshot_names.

The comment says "Returns the count via out_count" and "Caller passes the desired count cap; the registry truncates if it has more", but the declared signature has neither an out_count parameter nor a cap input — it returns size_t directly and takes only out_names. Either the doc is stale or the signature is missing parameters; whichever is intended, they disagree, and the loader TU will be coded against one or the other.

🛠️ If the return-value form is the intended one
 /**
  * Snapshot the names of every currently-registered plugin into `out_names`
  * (heap-allocated `strdup`s, caller frees with `free()` per entry + `free()`
- * on the array). Returns the count via `out_count`. Caller passes the desired
- * count cap; the registry truncates if it has more.
+ * on the array). Returns the number of entries written to `*out_names`.
  */
 size_t rac_plugin_registry_snapshot_names(const char*** out_names);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/src/plugin/plugin_registry_internal.h` around lines
40 - 46, The docstring and the declaration for
rac_plugin_registry_snapshot_names disagree: either update the comment to match
the current signature or change the function signature/implementation to match
the documented API. Fix option A (preferred if return-value style is intended):
change the comment on rac_plugin_registry_snapshot_names to state that the
function returns the count as its size_t return value, that it allocates an
array of strdup'd C-strings into the out_names pointer (caller must free each
entry and the array), and remove references to out_count and a caller-provided
cap. Fix option B (if the doc is correct): change the declaration/implementation
of rac_plugin_registry_snapshot_names to accept a size_t cap and a size_t*
out_count (e.g., size_t rac_plugin_registry_snapshot_names(const char***
out_names, size_t cap, size_t* out_count)), and update all callers to pass a cap
and receive out_count; preserve the strdup/ownership semantics noted in the
comment.
docs/engine_plugin_authoring.md-86-90 (1)

86-90: ⚠️ Potential issue | 🟡 Minor

Update RAC_PLUGIN_API_VERSION version number in documentation from "1" to "2".

Lines 86–90 document RAC_PLUGIN_API_VERSION as "currently 1", but the actual definition in sdk/runanywhere-commons/include/rac/plugin/rac_plugin_entry.h:58 is 2u. Plugin authors following this outdated documentation will hardcode the wrong version and encounter RAC_ERROR_ABI_VERSION_MISMATCH at runtime.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/engine_plugin_authoring.md` around lines 86 - 90, The doc text
incorrectly states RAC_PLUGIN_API_VERSION is "currently 1"; update the
documentation so it reflects the actual ABI value 2 (i.e., change the phrase
"currently 1" to "currently 2" or, better, reference the constant symbol
RAC_PLUGIN_API_VERSION directly), ensuring the rule describing
metadata.abi_version explicitly requires equality with RAC_PLUGIN_API_VERSION
(now 2) to prevent authors from hardcoding the wrong value and triggering
RAC_ERROR_ABI_VERSION_MISMATCH.
sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/core/types/ComponentTypes.kt-82-95 (1)

82-95: ⚠️ Potential issue | 🟡 Minor

Add else → null fallback to handle forward-compatibility as new proto enum values are added.

The when expression covers all current enum values but lacks an explicit fallback. Unlike InferenceFramework.fromProto (line 248), which uses else → UNKNOWN, this function implicitly returns null for unknown values. Make this intent explicit by adding else → null to match the pattern in the generated proto's fromValue helper and improve clarity for future maintainers.

Suggested fix
 fun audioFormatFromProto(proto: ai.runanywhere.proto.v1.AudioFormat): AudioFormat? =
     when (proto) {
         ai.runanywhere.proto.v1.AudioFormat.AUDIO_FORMAT_PCM        -> AudioFormat.PCM
         ai.runanywhere.proto.v1.AudioFormat.AUDIO_FORMAT_WAV        -> AudioFormat.WAV
         ai.runanywhere.proto.v1.AudioFormat.AUDIO_FORMAT_MP3        -> AudioFormat.MP3
         ai.runanywhere.proto.v1.AudioFormat.AUDIO_FORMAT_OPUS       -> AudioFormat.OPUS
         ai.runanywhere.proto.v1.AudioFormat.AUDIO_FORMAT_AAC        -> AudioFormat.AAC
         ai.runanywhere.proto.v1.AudioFormat.AUDIO_FORMAT_FLAC       -> AudioFormat.FLAC
         ai.runanywhere.proto.v1.AudioFormat.AUDIO_FORMAT_OGG        -> AudioFormat.OGG
         ai.runanywhere.proto.v1.AudioFormat.AUDIO_FORMAT_PCM_S16LE  -> AudioFormat.PCM_16BIT
         ai.runanywhere.proto.v1.AudioFormat.AUDIO_FORMAT_M4A        -> null
         ai.runanywhere.proto.v1.AudioFormat.AUDIO_FORMAT_UNSPECIFIED -> null
+        else                                                         -> null
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/core/types/ComponentTypes.kt`
around lines 82 - 95, The when-expression in audioFormatFromProto currently
lists all known ai.runanywhere.proto.v1.AudioFormat cases but lacks an explicit
fallback; update the audioFormatFromProto function to include an else → null
branch so any future/unknown ai.runanywhere.proto.v1.AudioFormat values are
handled explicitly and return null (matching the intended forward-compatibility
behavior).
sdk/runanywhere-commons/src/backends/llamacpp/rac_plugin_entry_llamacpp_vlm.cpp-28-44 (1)

28-44: ⚠️ Potential issue | 🟡 Minor

Replace magic format numbers with proto enum constants to prevent silent drift.

The vtable architecture explicitly documents that format values must be proto-encoded runanywhere.v1.ModelFormat values. The current hardcoded values (1, 5) are correct, but lack abstraction—if the proto enum reorders or renumbers, they will silently mismatch. Use the named constants from the generated header:

♻️ Suggested change
+#include "rac/infrastructure/proto_wrapper.h"  // or appropriate proto header path
+
 static const uint32_t k_llamacpp_vlm_formats[] = {
-    1,  /* MODEL_FORMAT_GGUF */
-    5,  /* MODEL_FORMAT_BIN  — vision projector / mmproj files */
+    static_cast<uint32_t>(runanywhere::v1::MODEL_FORMAT_GGUF),
+    static_cast<uint32_t>(runanywhere::v1::MODEL_FORMAT_BIN),
 };

(Adjust include path to match your proto header location.)

This pattern affects all backend plugins (whispercpp, llamacpp, onnx, whisperkit_coreml, metalrt); consider applying uniformly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-commons/src/backends/llamacpp/rac_plugin_entry_llamacpp_vlm.cpp`
around lines 28 - 44, The static array k_llamacpp_vlm_formats currently uses
magic numbers (1, 5); replace those numeric literals with the proto enum
constants from the generated runanywhere v1 header (e.g., MODEL_FORMAT_GGUF and
MODEL_FORMAT_BIN from the runanywhere::v1 proto enum) and add the appropriate
`#include` for that generated header; update g_llamacpp_vlm_engine_vtable
(formats/formats_count) only by changing k_llamacpp_vlm_formats contents so
semantics remain the same and compile-time enum names prevent future drift.
sdk/runanywhere-commons/tests/test_engine_vtable.cpp-161-167 (1)

161-167: ⚠️ Potential issue | 🟡 Minor

Scenario (9) does not actually exercise RAC_STATIC_PLUGIN_REGISTER.

The file header and scenario list both promise a static-registration smoke check, but this block only asserts rac_plugin_count() == 0. Either invoke RAC_STATIC_PLUGIN_REGISTER in this TU (or verify a statically-registered plugin from another TU is present before the test-local cleanups) to match the documented contract, or update the comment/header to stop advertising that coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/tests/test_engine_vtable.cpp` around lines 161 - 167,
The test block claims to exercise static-registration but never uses
RAC_STATIC_PLUGIN_REGISTER; update the test to actually invoke the macro in this
translation unit and verify its effect: call RAC_STATIC_PLUGIN_REGISTER(...)
with a simple test plugin identifier at the start of the scenario, assert
rac_plugin_count() increases (e.g., >0) to show the static registration was
observed, then perform the existing cleanup and assert rac_plugin_count() == 0
afterward; locate the checks around rac_plugin_count() in the same test block
and add the macro invocation and the intermediate assertion there (or
alternatively, remove/adjust the comment if you prefer not to exercise the
macro).
sdk/runanywhere-commons/src/backends/onnx/rac_plugin_entry_onnx.cpp-50-50 (1)

50-50: ⚠️ Potential issue | 🟡 Minor

engine_version set to nullptr.

Other plugins (e.g., the test fixture) set a version string here. If any consumer (logs, router telemetry, display_name formatting) calls strlen/printf("%s", …) on engine_version without a null check, this will crash. Recommend populating with the ONNX Runtime version (or "unknown") for safety and parity with other backends.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/src/backends/onnx/rac_plugin_entry_onnx.cpp` at line
50, Replace the null engine_version in the plugin descriptor (.engine_version =
nullptr) with a stable C-string containing the ONNX Runtime version (or a
fallback like "unknown") so callers can safely call strlen/printf without null
checks; ensure you use a statically-allocated string or a string with process
lifetime (e.g., a literal or the result of the runtime/version API) when setting
engine_version in the rac_plugin_entry_onnx plugin descriptor.
docs/plugin_loader_authoring.md-46-69 (1)

46-69: ⚠️ Potential issue | 🟡 Minor

Example vtable metadata doesn't match the actual struct layout.

The example initializes .reserved_0 / .reserved_1 but omits .runtimes, .runtimes_count, .formats, .formats_count — the opposite of what the real rac_engine_metadata_t exposes in rac_test_plugin.cpp (lines 45-48) and rac_plugin_entry_onnx.cpp (lines 53-56). A copy-paste of this snippet won't compile. Please sync the example with the current metadata struct (drop reserved_*, add the runtimes/formats fields).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/plugin_loader_authoring.md` around lines 46 - 69, The g_myonnx_vtable
metadata block does not match the current rac_engine_metadata_t layout; update
the static const rac_engine_vtable_t g_myonnx_vtable initialization to remove
the obsolete .reserved_0/.reserved_1 fields and instead include the current
fields .runtimes, .runtimes_count, .formats, and .formats_count in the metadata
sub-struct (and ensure their order/presence matches rac_engine_metadata_t as
used in rac_test_plugin.cpp and rac_plugin_entry_onnx.cpp); leave other vtable
members (capability_check, on_unload, g_myonnx_llm_ops, etc.) as-is.
sdk/runanywhere-commons/tests/CMakeLists.txt-82-97 (1)

82-97: ⚠️ Potential issue | 🟡 Minor

Plugin entry symbol won't export on MSVC due to CMake visibility preset.

The fixture manually adds __attribute__((visibility("default"))) before RAC_PLUGIN_ENTRY_DEF(test_plugin), but RAC_PLUGIN_ENTRY_DEF expands to just a function declaration with no visibility attribute. With C_VISIBILITY_PRESET hidden and CXX_VISIBILITY_PRESET hidden, MSVC will hide the symbol (the GCC/Clang visibility attribute is ignored). dlsym() will fail to find rac_plugin_entry_test_plugin on Windows, causing the loader tests to fail.

Update RAC_PLUGIN_ENTRY_DEF in rac_plugin_entry.h to use a portable export macro (following the pattern of RAC_API in rac_types.h: __declspec(dllexport) on MSVC, __attribute__((visibility("default"))) on GCC/Clang), then remove the manual visibility attribute from the fixture.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/tests/CMakeLists.txt` around lines 82 - 97, The
plugin entry symbol is hidden on MSVC because
C_VISIBILITY_PRESET/CXX_VISIBILITY_PRESET hide symbols and the fixture's GCC
visibility attribute is ignored; update rac_plugin_entry.h so
RAC_PLUGIN_ENTRY_DEF uses a portable export macro (follow RAC_API in
rac_types.h) that expands to __declspec(dllexport) on MSVC and
__attribute__((visibility("default"))) on GCC/Clang, then apply that macro to
the RAC_PLUGIN_ENTRY_DEF declaration (so rac_plugin_entry_test_plugin is
exported) and remove the manual __attribute__((visibility("default"))) from the
test fixture.
sdk/runanywhere-commons/src/router/rac_hardware_profile.cpp-94-108 (1)

94-108: ⚠️ Potential issue | 🟡 Minor

Probe vs. documented contract drift: CUDA/Vulkan only check that the loader is present, not that a device exists.

The header contract for these flags reads:

  • has_cuda → "NVIDIA CUDA driver + at least 1 device node."
  • has_vulkan → "Vulkan loader + at least 1 physical device."

detect_cuda_linux does gate on /dev/nvidiactl existing, which approximates the "device node" claim, but detect_vulkan_linux only calls dlopen("libvulkan.so.1", ...) — a present loader does not imply a usable physical device (common on CI containers and headless VMs shipping the Vulkan loader but zero adapters). The "conservative, prefer false-negative" philosophy in the file header is violated here: a box with only the loader will report has_vulkan=true and the router will cheerfully route Vulkan-preferring plugins to it.

Two low-cost options:

  1. Weaken the header doc to match the probe ("Vulkan loader present" only), or
  2. Extend the probe: after dlopen, dlsym vkCreateInstance / vkEnumeratePhysicalDevices, create a throwaway instance, and verify physicalDeviceCount > 0 before returning true.

Either is fine; keeping the header contract authoritative makes (2) the preferable fix. Same consideration applies to the NNAPI / QNN dlopen-only probes in the Android block — those at least combine a device-node stat for QNN, but NNAPI is loader-only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/src/router/rac_hardware_profile.cpp` around lines 94
- 108, The current detect_vulkan_linux() only checks for the Vulkan loader via
dlopen which violates the header contract that requires "Vulkan loader + at
least 1 physical device"; update detect_vulkan_linux() to, after
dlopen("libvulkan.so.1"), use dlsym to load vkCreateInstance and
vkEnumeratePhysicalDevices, create a temporary VkInstance (use minimal
VkApplicationInfo/VkInstanceCreateInfo), call vkEnumeratePhysicalDevices to get
the device count, and only return true if count > 0; ensure proper cleanup
(destroy instance if created, dlclose the library) and treat any failure or
missing symbols as false. Also review detect_cuda_linux() for consistency (it
already stats /dev/nvidiactl but ensure it still returns false on dlopen/dlsym
failures) so both functions match the documented "loader + device" semantics.
sdk/runanywhere-flutter/packages/runanywhere/lib/core/types/model_types.dart-166-190 (1)

166-190: ⚠️ Potential issue | 🟡 Minor

ModelCategory.fromProto silently coerces UNSPECIFIED and future proto cases to audio.

The fallback after the MODEL_CATEGORY_EMBEDDING check returns ModelCategory.audio for any value that didn't match above. The comment documents the AUDIO+VAD collapse, but the same branch is also hit by:

  • MODEL_CATEGORY_UNSPECIFIED (proto3 default for unset fields) — an un-initialized category field on the wire becomes "Audio Processing", which is misleading (and likely undesirable for a language/vision catalog row).
  • Any future ModelCategory value added to model_types.proto before the Dart enum catches up.

The Dart ModelCategory enum has no unknown case (unlike ModelFormat/InferenceFramework), so pick a safer default and handle UNSPECIFIED explicitly, e.g.:

🩹 Proposed fix
   static ModelCategory fromProto(pb.ModelCategory proto) {
+    if (proto == pb.ModelCategory.MODEL_CATEGORY_UNSPECIFIED) {
+      // Proto default / unset — fall back to the most common category rather
+      // than silently labeling the row as audio.
+      return ModelCategory.language;
+    }
     if (proto == pb.ModelCategory.MODEL_CATEGORY_LANGUAGE) {
       return ModelCategory.language;
     }
     ...
-    // AUDIO + VAD both map to the Dart audio case
+    // AUDIO + VAD both map to the Dart audio case; any future proto case
+    // added upstream also lands here until this bridge is updated.
     return ModelCategory.audio;
   }

Long-term: consider adding a ModelCategory.unknown case for symmetry with the other bridges — that would also remove the need to pick an arbitrary fallback here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-flutter/packages/runanywhere/lib/core/types/model_types.dart`
around lines 166 - 190, ModelCategory.fromProto currently falls through to
ModelCategory.audio for any unmatched proto value, causing
MODEL_CATEGORY_UNSPECIFIED and future proto additions to be misclassified;
update the mapping to explicitly handle
pb.ModelCategory.MODEL_CATEGORY_UNSPECIFIED (return a new Dart enum case
ModelCategory.unknown) and map only pb.ModelCategory.MODEL_CATEGORY_AUDIO and
pb.ModelCategory.MODEL_CATEGORY_VAD to ModelCategory.audio, then add
ModelCategory.unknown to the Dart ModelCategory enum so unmatched/future proto
values map to unknown instead of audio; adjust any callers/serializers that
assume the old enum shape accordingly.
sdk/runanywhere-commons/src/plugin/plugin_loader.cpp-74-88 (1)

74-88: ⚠️ Potential issue | 🟡 Minor

entry_symbol_from_path uses find('.') — breaks on versioned dylibs and dotted plugin names.

After last_sep, s is just the basename (no directory), but the extension strip uses the first dot, not the last. That gives the wrong symbol whenever the basename contains more than one dot:

Input basename Current result Expected
libfoo.so rac_plugin_entry_foo rac_plugin_entry_foo
libfoo.1.dylib rac_plugin_entry_foo rac_plugin_entry_foo.1 ❌ (should strip only .dylib)
libfoo.1.2.3.dylib rac_plugin_entry_foo rac_plugin_entry_foo.1.2.3
libruntime.plugin.so rac_plugin_entry_runtime rac_plugin_entry_runtime.plugin

macOS in particular ships versioned dylibs with this exact layout (libllama.1.0.dylib), and Linux symlinked .so.N variants are common. Either switch to stripping by the well-known extension set, or use the last dot:

🩹 Quick fix
-    // Drop file extension.
-    auto dot = s.find('.');
-    if (dot != std::string::npos) s.erase(dot);
+    // Drop file extension — use the last dot so versioned names like
+    // "libfoo.1.0.dylib" strip only ".dylib".
+    auto dot = s.rfind('.');
+    if (dot != std::string::npos) s.erase(dot);

For full robustness against libfoo.so.1 (trailing version after the extension on Linux SONAMEs), consider a small loop / a known-suffix list (.so, .dylib, .dll, .so.<N>).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/src/plugin/plugin_loader.cpp` around lines 74 - 88,
The basename-to-symbol logic in entry_symbol_from_path incorrectly strips at the
first dot (variable 'dot'), which drops version segments and dotted plugin
names; change the extension removal to either find the last dot (use
s.find_last_of('.') instead of s.find('.')) or implement suffix-aware stripping
that removes known extensions (e.g., ".so", ".dylib", ".dll") and optional
trailing version components (like ".so.1" or multiple ".N" segments) while
preserving any prior dot-separated parts (so s retains "foo.1.2.3" for
"libfoo.1.2.3.dylib"); update the code around variables s, last_sep and dot (or
replace 'dot' logic) accordingly and ensure tests cover names like
"libfoo.1.dylib", "libfoo.so.1", and "libruntime.plugin.so".
sdk/runanywhere-commons/include/rac/plugin/rac_plugin_entry.h-123-166 (1)

123-166: ⚠️ Potential issue | 🟡 Minor

Fix MSVC linker symbol name in documentation to match macro export.

Line 125 instructs users to use /INCLUDE:_g_rac_plugin_autoreg_<name>, but the macro on line 166 exports rac_plugin_static_marker_##name. Users following the current documentation on MSVC would fail to prevent static plugin TUs from being stripped.

Documentation fix
- *        - MSVC:          add `/INCLUDE:_g_rac_plugin_autoreg_<name>` per plugin
+ *        - MSVC:          add `/INCLUDE:rac_plugin_static_marker_<name>` per plugin
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/include/rac/plugin/rac_plugin_entry.h` around lines
123 - 166, Update the MSVC linker instruction to reference the actual exported
symbol from the macro: replace `/INCLUDE:_g_rac_plugin_autoreg_<name>` with
`/INCLUDE:rac_plugin_static_marker_<name>` (matching the extern "C" symbol
produced by the RAC_STATIC_PLUGIN_REGISTER macro, i.e.,
rac_plugin_static_marker_##name). Ensure the documentation text around
RAC_STATIC_PLUGIN_REGISTER and the example uses the corrected symbol name so
MSVC users can force-include the TU.

Comment thread .github/workflows/idl-drift-check.yml Outdated
Comment on lines +60 to +67
- name: Install Dart plugin (protoc-gen-dart)
run: |
if command -v dart >/dev/null 2>&1; then
dart pub global activate protoc_plugin 21.1.2
echo "$HOME/.pub-cache/bin" >> "$GITHUB_PATH"
else
echo "::warning::dart not found on macos-14 runner; Dart codegen skipped"
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Drift check silently passes when Dart is unavailable.

macos-14 runners do not ship with dart preinstalled, so this step emits a warning and generate_dart.sh is never invoked by generate_all.sh. Because the committed Dart bindings under sdk/runanywhere-flutter/packages/runanywhere/lib/generated/** are not regenerated, git diff --exit-code on line 91 reports no drift even when a contributor edits idl/*.proto without regenerating Dart (or hand-edits a generated Dart file). The gate advertised in the workflow header ("any .proto … without regenerating the committed language bindings … this job fails") does not hold for Dart.

Either install Dart unconditionally (e.g., dart-lang/setup-dart@v1) or fail the job when dart is missing rather than warning — the drift guarantee is only as strong as its weakest language.

🛡️ Suggested change
-      - name: Install Dart plugin (protoc-gen-dart)
-        run: |
-          if command -v dart >/dev/null 2>&1; then
-            dart pub global activate protoc_plugin 21.1.2
-            echo "$HOME/.pub-cache/bin" >> "$GITHUB_PATH"
-          else
-            echo "::warning::dart not found on macos-14 runner; Dart codegen skipped"
-          fi
+      - uses: dart-lang/setup-dart@v1
+        with:
+          sdk: stable
+      - name: Install Dart plugin (protoc-gen-dart)
+        run: |
+          dart pub global activate protoc_plugin 21.1.2
+          echo "$HOME/.pub-cache/bin" >> "$GITHUB_PATH"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/idl-drift-check.yml around lines 60 - 67, The macos step
that conditionally skips Dart makes the drift check unsound; modify the "Install
Dart plugin (protoc-gen-dart)" step so Dart is guaranteed (either install Dart
using dart-lang/setup-dart@v1 before activating protoc_plugin, or replace the
current else warning with an explicit exit 1), ensuring generate_all.sh will
invoke generate_dart.sh and that protoc_plugin (protoc-gen-dart) activation runs
on macos; update the run block that currently checks `command -v dart` to
perform a setup action or fail the job when dart is missing.

Comment thread docs/engine_plugin_authoring.md Outdated
Comment on lines +13 to +16
# The generated headers live inside sdk/runanywhere-commons so the C ABI shim
# layer can `#include "runanywhere/idl/model_types.pb.h"` for
# proto-encoded wire conversions. CMake's `idl/CMakeLists.txt` generates the
# same files at build time for the `rac_idl` library; this script keeps a
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether committed/generated headers and consumers agree on the include path.
set -euo pipefail

echo "Generated C++ proto headers:"
fd '.*\.pb\.h$' sdk/runanywhere-commons/src/generated/proto || true

echo
echo "C++ proto include sites:"
rg -n '#include\s+["<].*\.pb\.h[">]' sdk/runanywhere-commons || true

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 728


🏁 Script executed:

cat -n idl/codegen/generate_cpp.sh | head -50

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1587


🏁 Script executed:

# Check the protoc command invocations to understand output path structure
rg -A 5 'protoc.*--cpp_out' idl/codegen/generate_cpp.sh

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 268


🏁 Script executed:

# Find C ABI shim layer and files referencing the documented include path
rg -r '#include.*runanywhere/idl' --type cpp --type c || true

echo "---"

# Also check for any usage of the documented include path
rg 'runanywhere/idl/.*\.pb\.h' || true

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 649


🏁 Script executed:

# Look for C ABI shim layer files
fd -i 'abi|shim' sdk/runanywhere-commons --type f | head -20

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 139


🏁 Script executed:

# Check CMakeLists.txt for include path configuration
find . -name CMakeLists.txt -exec grep -l 'runanywhere-commons\|proto\|idl' {} \; | head -5

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 522


🏁 Script executed:

cat idl/CMakeLists.txt

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1819


🏁 Script executed:

cat sdk/runanywhere-commons/tests/test_plugin_loader_abi_mismatch.cpp

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1837


🏁 Script executed:

# Search for actual consumer code that links against rac_idl or uses the proto headers
rg 'target_link_libraries.*rac_idl|link_libraries.*rac_idl' --type cmake

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 152


🏁 Script executed:

# Check test CMakeLists.txt to see how tests consume the proto headers
head -100 sdk/runanywhere-commons/tests/CMakeLists.txt | grep -A 5 -B 5 'rac_idl\|model_types'

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 56


Align the generated C++ include path with the documented consumer interface.

The script documents that consumers use #include "runanywhere/idl/model_types.pb.h", but the protoc invocation with --proto_path="${REPO_ROOT}/idl" and --cpp_out="${OUT_DIR}" (where ${OUT_DIR} is sdk/runanywhere-commons/src/generated/proto) generates headers directly at that output directory without the runanywhere/idl/ prefix. The CMakeLists.txt target_include_directories() configuration only exposes the bare filenames (e.g., #include "model_types.pb.h"), not the documented path. Any consumer following the documented include path will fail to compile.

Either adjust the protoc invocation to generate files under a runanywhere/idl/ subdirectory, or update the documentation to reflect the actual include paths used in the build.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@idl/codegen/generate_cpp.sh` around lines 13 - 16, The generated C++ headers
are emitted directly into ${OUT_DIR} but the docs and consumers expect `#include`
"runanywhere/idl/model_types.pb.h"; update the protoc invocation in
generate_cpp.sh (the line invoking protoc with --proto_path="${REPO_ROOT}/idl"
and --cpp_out="${OUT_DIR}") to emit files under a runanywhere/idl/ subdirectory
(so generated headers match the documented include path), or alternatively
update the documentation/CMake target_include_directories() notes to document
the bare include names (e.g., "model_types.pb.h"); modify whichever is simpler
to keep the protoc/OUT_DIR behavior and documented include path consistent.

Comment thread idl/solutions.proto
Comment on lines +49 to +51
// Barge-in behavior.
bool enable_barge_in = 8; // default true
int32 barge_in_threshold_ms = 9; // default 200
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'enable_barge_in|optional bool enable_barge_in|disable_barge_in' idl
rg -n 'proto3_optional|has_.*enable_barge_in|hasEnableBargeIn|enableBargeIn.*case' .

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 273


🏁 Script executed:

cat -n idl/solutions.proto | sed -n '40,60p'

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 902


🏁 Script executed:

# Check if the field is already optional
rg -A 3 -B 3 'enable_barge_in' idl/solutions.proto

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 258


🏁 Script executed:

# Check if other generated files also have presence tracking
find . -name "*.pb.*" -type f | head -20 | xargs grep -l "hasEnableBargeIn\|EnableBargeIn" 2>/dev/null | head -10

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 213


🏁 Script executed:

# Check C++ generated code
rg -A 5 -B 5 'enable_barge_in' ./sdk/runanywhere-commons/src/generated/proto/solutions.pb.h | head -40

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1197


🏁 Script executed:

# Check Dart generated code more thoroughly
rg -A 10 -B 10 'hasEnableBargeIn' ./sdk/runanywhere-flutter/packages/runanywhere/lib/generated/solutions.pb.dart

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 725


🏁 Script executed:

# Check if other bool fields also have has methods in Dart
rg 'has[A-Z][a-zA-Z]*\(\) => \$_has' ./sdk/runanywhere-flutter/packages/runanywhere/lib/generated/solutions.pb.dart | head -20

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 935


Make the default-true barge-in flag representable across all generators.

A plain proto3 bool defaults to false and lacks presence semantics, preventing the core (C++) from distinguishing "unset, use default true" from "caller explicitly set false." Although Dart's protoc plugin auto-generates presence tracking, C++ does not, creating inconsistency. Use optional bool to ensure all generators properly preserve presence.

Proposed proto change
-    bool   enable_barge_in = 8;  // default true
+    optional bool enable_barge_in = 8;  // default true when unset
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Barge-in behavior.
bool enable_barge_in = 8; // default true
int32 barge_in_threshold_ms = 9; // default 200
// Barge-in behavior.
optional bool enable_barge_in = 8; // default true when unset
int32 barge_in_threshold_ms = 9; // default 200
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@idl/solutions.proto` around lines 49 - 51, Change the plain proto3 bool field
enable_barge_in in idl/solutions.proto to an optional bool so presence is
preserved across all language generators (allowing the C++ core to distinguish
"unset (use default true)" from "explicitly false"); keep the semantic default
(documented comment "default true" / leave barge_in_threshold_ms as-is) and
update any server/client code that reads enable_barge_in to check presence
before applying the default behavior.

Comment thread scripts/setup-toolchain.sh Outdated
Comment on lines +38 to +43
PROTOC_EXPECTED_MAJOR="25"
SWIFT_PROTOBUF_EXPECTED="1.27"
WIRE_EXPECTED="4.9"
PROTOC_PLUGIN_DART_EXPECTED="21.1.2"
TS_PROTO_EXPECTED="1.181"
PYTHON_PROTOBUF_EXPECTED="4.25"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show declared expected versions and whether they are actually compared.
rg -n -C2 'EXPECTED|npm install -g|pip install|rc=1|--version|protoc-gen' scripts/setup-toolchain.sh

# ShellCheck already points at the same symptom for several expected-version vars.
# This command is read-only and only reports if shellcheck is available.
if command -v shellcheck >/dev/null 2>&1; then
  shellcheck scripts/setup-toolchain.sh || true
fi

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 3564


🏁 Script executed:

# First, let's examine the complete setup-toolchain.sh file to understand its structure
wc -l scripts/setup-toolchain.sh && echo "---" && head -50 scripts/setup-toolchain.sh

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1577


🏁 Script executed:

# Search for all references to the expected version variables to confirm usage
rg -n 'PROTOC_EXPECTED_MAJOR|SWIFT_PROTOBUF_EXPECTED|WIRE_EXPECTED' scripts/setup-toolchain.sh

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 160


🏁 Script executed:

# Check if these variables are exported or used by external scripts
git ls-files | xargs rg -l 'PROTOC_EXPECTED_MAJOR|SWIFT_PROTOBUF_EXPECTED|WIRE_EXPECTED'

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 283


🏁 Script executed:

# Examine the check_versions() function more carefully to see exit code behavior
sed -n '130,170p' scripts/setup-toolchain.sh

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1375


🏁 Script executed:

# Check install functions to verify version pinning behavior
sed -n '57,128p' scripts/setup-toolchain.sh

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 2318


🏁 Script executed:

# Verify the exact versions being installed vs expected
rg -A2 'npm install|pip install|brew install|dart pub' scripts/setup-toolchain.sh

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 861


Enforce the pinned tool versions instead of only declaring them.

The script claims byte-identical codegen, but three expected versions (PROTOC_EXPECTED_MAJOR, SWIFT_PROTOBUF_EXPECTED, WIRE_EXPECTED) are unused, and remaining pinned versions allow floating resolution instead of exact matches:

  • brew/apt install unpinned (protoc, swift-protobuf, wire-compiler)
  • npm install "ts-proto@^${TS_PROTO_EXPECTED}" uses caret range, not exact version
  • pip install "protobuf>=${PYTHON_PROTOBUF_EXPECTED},<5" allows 4.25–4.99.x range, not exact version
  • check_versions() only fails on missing protoc, not on missing or mismatched protoc-gen-swift, protoc-gen-dart, or ts-proto

Local regeneration can silently drift from CI. --check should validate exact versions and fail consistently on any missing codegen tool.

Suggested direction
+PROTOC_EXPECTED_MAJOR="25"
+SWIFT_PROTOBUF_EXPECTED="1.27"
+WIRE_EXPECTED="4.9"
 PROTOC_PLUGIN_DART_EXPECTED="21.1.2"
 TS_PROTO_EXPECTED="1.181"
 PYTHON_PROTOBUF_EXPECTED="4.25"
+
+version_has_prefix() {
+    case "$1" in
+        "$2"*) return 0 ;;
+        *) return 1 ;;
+    esac
+}
+
+mark_version_mismatch() {
+    echo "$1: expected $2.x, got $3" >&2
+    return 1
+}
@@
-    npm install -g "ts-proto@^${TS_PROTO_EXPECTED}" protobufjs
+    npm install -g "ts-proto@${TS_PROTO_EXPECTED}" protobufjs
@@
-        python3 -m pip install --user --upgrade "protobuf>=${PYTHON_PROTOBUF_EXPECTED},<5" grpcio-tools
+        python3 -m pip install --user --upgrade "protobuf>=${PYTHON_PROTOBUF_EXPECTED},<4.26" grpcio-tools
@@
     if have protoc; then
-        echo "protoc:            $(protoc --version)"
+        local protoc_version
+        protoc_version="$(protoc --version | awk '{print $2}')"
+        echo "protoc:            ${protoc_version}"
+        version_has_prefix "${protoc_version}" "${PROTOC_EXPECTED_MAJOR}." || {
+            mark_version_mismatch "protoc" "${PROTOC_EXPECTED_MAJOR}" "${protoc_version}"
+            rc=1
+        }
@@
     if have protoc-gen-swift; then
         echo "protoc-gen-swift:  $(protoc-gen-swift --version 2>/dev/null || echo 'present')"
     else
         echo "protoc-gen-swift:  MISSING (Swift codegen will fail)" >&2
+        rc=1
@@
     if have protoc-gen-dart; then
         echo "protoc-gen-dart:   present"
     else
         echo "protoc-gen-dart:   MISSING (Dart codegen will fail)" >&2
+        rc=1
@@
     if have npm && [ -x "$(npm root -g 2>/dev/null)/ts-proto/protoc-gen-ts_proto" ]; then
         echo "ts-proto:          present"
     else
         echo "ts-proto:          MISSING (TS codegen will fail)" >&2
+        rc=1
@@
     if have python3 && python3 -c "import google.protobuf" >/dev/null 2>&1; then
         echo "python-protobuf:   present"
     else
         echo "python-protobuf:   MISSING (Python codegen will fail)" >&2
+        rc=1
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 38-38: PROTOC_EXPECTED_MAJOR appears unused. Verify use (or export if used externally).

(SC2034)


[warning] 39-39: SWIFT_PROTOBUF_EXPECTED appears unused. Verify use (or export if used externally).

(SC2034)


[warning] 40-40: WIRE_EXPECTED appears unused. Verify use (or export if used externally).

(SC2034)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/setup-toolchain.sh` around lines 38 - 43, The script currently only
declares expected versions (PROTOC_EXPECTED_MAJOR, SWIFT_PROTOBUF_EXPECTED,
WIRE_EXPECTED, TS_PROTO_EXPECTED, PYTHON_PROTOBUF_EXPECTED) and uses loose
install specifiers and incomplete checks; update it to enforce exact pinned
versions: use the variables (PROTOC_EXPECTED_MAJOR, SWIFT_PROTOBUF_EXPECTED,
WIRE_EXPECTED, PROTOC_PLUGIN_DART_EXPECTED, TS_PROTO_EXPECTED,
PYTHON_PROTOBUF_EXPECTED) in installer commands so package managers install
exact versions (avoid caret/ranges, e.g. install ts‑proto@<exact> and pip
install protobuf==<exact>), change any apt/brew install invocations to target
specific releases or download exact binaries, and extend check_versions() to
verify exact versions for protoc (major match and ideally exact if available),
protoc-gen-swift, protoc-gen-dart, protoc-gen-dart plugin name
(protoc-gen-dart), ts-proto (ts-proto), swift-protobuf (swift-protobuf/wire
compiler as WIRE_EXPECTED) and fail with non-zero exit when any tool is missing
or version-mismatched; also ensure the previously unused constants
(PROTOC_EXPECTED_MAJOR, SWIFT_PROTOBUF_EXPECTED, WIRE_EXPECTED) are referenced
by these checks/installs so the script enforces byte-identical codegen.

Comment on lines +259 to +267
auto* arr = static_cast<const char**>(std::malloc(n * sizeof(const char*)));
if (arr == nullptr) {
*out_names = nullptr;
return 0;
}
size_t i = 0;
for (auto& kv : s.by_name) {
arr[i++] = strdup(kv.first.c_str());
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP '\bstrdup\s*\(' --glob '*.{c,cc,cpp,cxx,h,hpp}' sdk/runanywhere-commons
rg -n 'MSVC|WIN32|_WIN32|_strdup' sdk/runanywhere-commons -C 2

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 50386


🏁 Script executed:

head -n 280 sdk/runanywhere-commons/src/plugin/rac_plugin_registry.cpp | tail -n 30

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1065


🏁 Script executed:

rg -n 'strdup' sdk/runanywhere-commons/src/plugin/rac_plugin_registry.cpp -B 2 -A 2

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 217


🏁 Script executed:

grep -n 'rac_strdup' sdk/runanywhere-commons/include/rac/core/rac_types.h -A 5

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 170


🏁 Script executed:

rg -n 'rac_strdup' sdk/runanywhere-commons/src -A 3 | head -50

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 4397


🏁 Script executed:

find sdk/runanywhere-commons -name "rac_memory.cpp" -o -name "*memory*" | grep -E '\.(cpp|c)$'

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 121


🏁 Script executed:

grep -r 'char\*\*' sdk/runanywhere-commons/src/plugin/rac_plugin_registry.cpp -B 5 -A 10 | head -60

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 724


🏁 Script executed:

cat sdk/runanywhere-commons/src/core/rac_memory.cpp | head -100

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1035


🏁 Script executed:

grep -n 'rac_strdup' sdk/runanywhere-commons/src/core/rac_memory.cpp -A 8

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 336


🏁 Script executed:

grep -n 'for.*strdup\|for.*malloc' sdk/runanywhere-commons/src/plugin/rac_plugin_registry.cpp -B 3 -A 5

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 56


🏁 Script executed:

rg 'strdup' sdk/runanywhere-commons/src/plugin/ -l

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 194


🏁 Script executed:

rg 'strdup' sdk/runanywhere-commons/src/plugin/plugin_registry_internal.h -B 2 -A 2

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 363


🏁 Script executed:

sed -n '250,280p' sdk/runanywhere-commons/src/plugin/rac_plugin_registry.cpp

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1134


🏁 Script executed:

grep -n 'snapshot_names' sdk/runanywhere-commons/include -r -A 5 -B 2

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 56


🏁 Script executed:

find sdk/runanywhere-commons/include -name "*.h" | xargs grep -l 'plugin_registry'

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 135


🏁 Script executed:

grep -n 'snapshot_names\|plugin_registry' sdk/runanywhere-commons/include/rac/plugin/*.h 2>/dev/null | head -30

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 214


🏁 Script executed:

grep -n 'snapshot_names' sdk/runanywhere-commons/include/rac/plugin/rac_plugin_entry.h -B 3 -A 8

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 56


🏁 Script executed:

grep -n 'free\|strdup' sdk/runanywhere-commons/include/rac/plugin/rac_plugin_entry.h | head -20

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 56


🏁 Script executed:

grep -n 'rac_plugin_registry_snapshot_names' sdk/runanywhere-commons/include/rac/plugin/rac_plugin_entry.h -B 3 -A 10

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 56


🏁 Script executed:

grep -n 'rac_plugin_registry_snapshot_names' sdk/runanywhere-commons/src/plugin/rac_plugin_registry.cpp -B 5 | head -20

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 237


🏁 Script executed:

sed -n '240,275p' sdk/runanywhere-commons/src/plugin/rac_plugin_registry.cpp

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1141


🏁 Script executed:

rg 'extern.*snapshot_names|RAC_API.*snapshot_names' sdk/runanywhere-commons -A 2

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 56


Replace POSIX strdup with portable malloc+memcpy and add cleanup on allocation failure.

strdup is not available on MSVC (Windows), and the current code has no error handling if allocation fails mid-loop—it would return a partially-invalid snapshot as if all names were copied. Use the proposed portable approach with proper cleanup.

Portable allocation fix
     size_t i = 0;
     for (auto& kv : s.by_name) {
-        arr[i++] = strdup(kv.first.c_str());
+        const std::string& name = kv.first;
+        auto* copy = static_cast<char*>(std::malloc(name.size() + 1));
+        if (copy == nullptr) {
+            for (size_t j = 0; j < i; ++j) {
+                std::free(const_cast<char*>(arr[j]));
+            }
+            std::free(arr);
+            *out_names = nullptr;
+            return 0;
+        }
+        std::memcpy(copy, name.c_str(), name.size() + 1);
+        arr[i++] = copy;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/src/plugin/rac_plugin_registry.cpp` around lines 259
- 267, The loop that uses strdup to copy keys from s.by_name into arr must be
made portable and robust: replace strdup(kv.first.c_str()) with allocating len =
kv.first.size() + 1 bytes via malloc, memcpy (or memcpy/memmove) the bytes
including the terminating NUL, then assign to arr[i]; after each malloc check
for NULL and on any failure free all previously allocated arr[j] strings and
free arr, set *out_names = nullptr and return 0; on success set *out_names = arr
and return the count. Ensure you reference and update arr, s.by_name, out_names
and the loop that currently uses strdup so no partial snapshot is returned and
code is MSVC-portable.

Comment on lines +92 to +134
RouteResult EngineRouter::route(const RouteRequest& req) const {
auto candidates = snapshot_for_primitive(req.primitive);
if (candidates.empty()) {
return RouteResult{nullptr, -1, "no plugin serves this primitive"};
}

/* Score every candidate. */
struct Scored {
int score;
const rac_engine_vtable_t* vt;
};
std::vector<Scored> scored;
scored.reserve(candidates.size());
for (auto* vt : candidates) {
if (vt == nullptr) continue;
int s = score(*vt, req);
if (s > -1000) {
scored.push_back({s, vt});
}
}
if (scored.empty()) {
if (!req.pinned_engine.empty() && req.no_fallback) {
return RouteResult{nullptr, -1,
std::string("pinned engine '") +
std::string(req.pinned_engine) +
"' not registered; no_fallback=true"};
}
return RouteResult{nullptr, -1, "no eligible plugin (all hard-rejected)"};
}

/* Stable sort: score desc, priority desc (tiebreak), name asc (final tiebreak).
* Determinism is required by the spec — same RouteRequest in same process
* MUST yield same winner across 1000 calls. */
std::sort(scored.begin(), scored.end(),
[](const Scored& a, const Scored& b) {
if (a.score != b.score) return a.score > b.score;
if (a.vt->metadata.priority != b.vt->metadata.priority) {
return a.vt->metadata.priority > b.vt->metadata.priority;
}
return std::strcmp(a.vt->metadata.name, b.vt->metadata.name) < 0;
});

return RouteResult{scored.front().vt, scored.front().score, {}};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Pin plugin lifetime while routing.

route() snapshots raw vtable pointers, then dereferences them after the registry lock is gone. A concurrent unregister/dynamic unload can invalidate vt->metadata while scoring or tie-breaking. Hold a registry read lock through scoring, or return a snapshot that ref-counts/pins the plugin handle until routing completes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/src/router/rac_engine_router.cpp` around lines 92 -
134, EngineRouter::route currently grabs raw vtable pointers from
snapshot_for_primitive and then dereferences vt->metadata after the registry
lock may have been released, risking use-after-unload; fix by ensuring the
plugin lifetime is pinned while scoring/sorting: either have
snapshot_for_primitive return a snapshot of ref-counted/pinned plugin handles
(not raw rac_engine_vtable_t*), or acquire and hold the registry read-lock for
the entire scoring and std::sort phase inside EngineRouter::route; update the
loop that builds scored (and the comparator that reads vt->metadata) to use the
pinned handle type or to run while holding the lock so vt->metadata cannot be
invalidated concurrently.

Comment on lines +53 to +68
/* (4) RAC_FORCE_RUNTIME=cpu zeroes every has_* flag. */
setenv("RAC_FORCE_RUNTIME", "cpu", 1);
HardwareProfile::refresh();
const HardwareProfile& d = HardwareProfile::cached();
bool any_accel = d.has_metal || d.has_ane || d.has_coreml || d.has_cuda ||
d.has_vulkan || d.has_qnn || d.has_nnapi || d.has_webgpu;
if (any_accel) {
std::fprintf(stderr, " FAIL: RAC_FORCE_RUNTIME=cpu but accelerators detected\n"); ++fails;
} else {
std::fprintf(stdout, " ok: RAC_FORCE_RUNTIME=cpu disables every accelerator\n");
}
if (!d.supports_runtime(RAC_RUNTIME_CPU)) {
std::fprintf(stderr, " FAIL: CPU still not supported under FORCE\n"); ++fails;
}
unsetenv("RAC_FORCE_RUNTIME");
HardwareProfile::refresh(); /* leave cache in normal state for any later tests */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether this test is registered for all platforms or platform-gated.
set -euo pipefail
rg -n -C3 'test_hardware_profile|add_executable|add_test' sdk/runanywhere-commons/tests

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 24754


🏁 Script executed:

# Check if Commons supports Windows builds and look for WIN32 conditionals
rg -n "WIN32|_WIN32|MSVC" sdk/runanywhere-commons/tests/CMakeLists.txt

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 430


🏁 Script executed:

# Check the actual test file to confirm setenv/unsetenv usage
head -70 sdk/runanywhere-commons/tests/test_hardware_profile.cpp | tail -30

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 1498


🏁 Script executed:

# Search for existing environment variable portability patterns in the codebase
rg -n "setenv|unsetenv|_putenv" sdk/runanywhere-commons --type cpp --type h

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 269


Guard environment variable access with platform-specific wrapper.

setenv/unsetenv are POSIX-only APIs. This test is marked as "always built" (line 43 of CMakeLists.txt) without WIN32 guards, so it will fail to compile under the Windows/MSVC Commons build. Wrap the environment variable access in a small platform-conditional helper function.

Portable test helper
+#if defined(_WIN32)
+#include <cstdlib>
+static void set_env(const char* name, const char* value) {
+    _putenv_s(name, value);
+}
+static void unset_env(const char* name) {
+    _putenv_s(name, "");
+}
+#else
+static void set_env(const char* name, const char* value) {
+    setenv(name, value, 1);
+}
+static void unset_env(const char* name) {
+    unsetenv(name);
+}
+#endif
+
     /* (4) RAC_FORCE_RUNTIME=cpu zeroes every has_* flag. */
-    setenv("RAC_FORCE_RUNTIME", "cpu", 1);
+    set_env("RAC_FORCE_RUNTIME", "cpu");
     HardwareProfile::refresh();
@@
-    unsetenv("RAC_FORCE_RUNTIME");
+    unset_env("RAC_FORCE_RUNTIME");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* (4) RAC_FORCE_RUNTIME=cpu zeroes every has_* flag. */
setenv("RAC_FORCE_RUNTIME", "cpu", 1);
HardwareProfile::refresh();
const HardwareProfile& d = HardwareProfile::cached();
bool any_accel = d.has_metal || d.has_ane || d.has_coreml || d.has_cuda ||
d.has_vulkan || d.has_qnn || d.has_nnapi || d.has_webgpu;
if (any_accel) {
std::fprintf(stderr, " FAIL: RAC_FORCE_RUNTIME=cpu but accelerators detected\n"); ++fails;
} else {
std::fprintf(stdout, " ok: RAC_FORCE_RUNTIME=cpu disables every accelerator\n");
}
if (!d.supports_runtime(RAC_RUNTIME_CPU)) {
std::fprintf(stderr, " FAIL: CPU still not supported under FORCE\n"); ++fails;
}
unsetenv("RAC_FORCE_RUNTIME");
HardwareProfile::refresh(); /* leave cache in normal state for any later tests */
`#if` defined(_WIN32)
`#include` <cstdlib>
static void set_env(const char* name, const char* value) {
_putenv_s(name, value);
}
static void unset_env(const char* name) {
_putenv_s(name, "");
}
`#else`
static void set_env(const char* name, const char* value) {
setenv(name, value, 1);
}
static void unset_env(const char* name) {
unsetenv(name);
}
`#endif`
/* (4) RAC_FORCE_RUNTIME=cpu zeroes every has_* flag. */
set_env("RAC_FORCE_RUNTIME", "cpu");
HardwareProfile::refresh();
const HardwareProfile& d = HardwareProfile::cached();
bool any_accel = d.has_metal || d.has_ane || d.has_coreml || d.has_cuda ||
d.has_vulkan || d.has_qnn || d.has_nnapi || d.has_webgpu;
if (any_accel) {
std::fprintf(stderr, " FAIL: RAC_FORCE_RUNTIME=cpu but accelerators detected\n"); ++fails;
} else {
std::fprintf(stdout, " ok: RAC_FORCE_RUNTIME=cpu disables every accelerator\n");
}
if (!d.supports_runtime(RAC_RUNTIME_CPU)) {
std::fprintf(stderr, " FAIL: CPU still not supported under FORCE\n"); ++fails;
}
unset_env("RAC_FORCE_RUNTIME");
HardwareProfile::refresh(); /* leave cache in normal state for any later tests */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/runanywhere-commons/tests/test_hardware_profile.cpp` around lines 53 -
68, The test uses POSIX setenv/unsetenv directly (lines calling
setenv("RAC_FORCE_RUNTIME", ...) and unsetenv(...)), which breaks MSVC/Windows
builds; add a small platform-conditional helper (e.g., SetTestEnv(const char*
name, const char* value) and UnsetTestEnv(const char* name)) that on POSIX calls
setenv/unsetenv and on Windows calls _putenv_s (or _putenv/_putenv_s semantics)
and then update the test to call SetTestEnv("RAC_FORCE_RUNTIME","cpu") and
UnsetTestEnv("RAC_FORCE_RUNTIME") around
HardwareProfile::refresh()/HardwareProfile::cached() usage so the test builds on
both platforms.

Comment thread sdk/runanywhere-commons/tests/test_static_registration.cpp
Comment on lines +33 to +42
/// Decode from the IDL-generated Wire enum. Unknown → development.
static SDKEnvironment fromProto(pb.SDKEnvironment proto) {
if (proto == pb.SDKEnvironment.SDK_ENVIRONMENT_STAGING) {
return SDKEnvironment.staging;
}
if (proto == pb.SDKEnvironment.SDK_ENVIRONMENT_PRODUCTION) {
return SDKEnvironment.production;
}
return SDKEnvironment.development;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use a safe fallback for unknown proto environments.

Mapping unknown or unspecified wire values to development can disable auth/sync and enable dev behavior in production flows. Prefer an explicit development match and default unknowns to production or throw.

Safer fallback
   static SDKEnvironment fromProto(pb.SDKEnvironment proto) {
+    if (proto == pb.SDKEnvironment.SDK_ENVIRONMENT_DEVELOPMENT) {
+      return SDKEnvironment.development;
+    }
     if (proto == pb.SDKEnvironment.SDK_ENVIRONMENT_STAGING) {
       return SDKEnvironment.staging;
     }
     if (proto == pb.SDKEnvironment.SDK_ENVIRONMENT_PRODUCTION) {
       return SDKEnvironment.production;
     }
-    return SDKEnvironment.development;
+    return SDKEnvironment.production;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Decode from the IDL-generated Wire enum. Unknown → development.
static SDKEnvironment fromProto(pb.SDKEnvironment proto) {
if (proto == pb.SDKEnvironment.SDK_ENVIRONMENT_STAGING) {
return SDKEnvironment.staging;
}
if (proto == pb.SDKEnvironment.SDK_ENVIRONMENT_PRODUCTION) {
return SDKEnvironment.production;
}
return SDKEnvironment.development;
}
/// Decode from the IDL-generated Wire enum. Unknown → production.
static SDKEnvironment fromProto(pb.SDKEnvironment proto) {
if (proto == pb.SDKEnvironment.SDK_ENVIRONMENT_DEVELOPMENT) {
return SDKEnvironment.development;
}
if (proto == pb.SDKEnvironment.SDK_ENVIRONMENT_STAGING) {
return SDKEnvironment.staging;
}
if (proto == pb.SDKEnvironment.SDK_ENVIRONMENT_PRODUCTION) {
return SDKEnvironment.production;
}
return SDKEnvironment.production;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@sdk/runanywhere-flutter/packages/runanywhere/lib/public/configuration/sdk_environment.dart`
around lines 33 - 42, The current SDKEnvironment.fromProto maps any
non-staging/non-production proto to development, which can enable dev behavior
in real deployments; change fromProto to explicitly check for
pb.SDKEnvironment.SDK_ENVIRONMENT_DEVELOPMENT and return
SDKEnvironment.development only in that case, return SDKEnvironment.production
for any unknown/unspecified values (or alternatively throw) so unknown wire
values do not default to development; update the function handling in
SDKEnvironment.fromProto accordingly, referencing
pb.SDKEnvironment.SDK_ENVIRONMENT_DEVELOPMENT, SDKEnvironment.development, and
SDKEnvironment.production.

sanchitmonga22 added a commit that referenced this pull request Apr 23, 2026
Closes Wave F (GAP 11 legacy cleanup) — the final wave of the v2
architecture migration program. PR #494 ready to merge to main after
this commit.

Phase 29 — deprecate the legacy service registry surface:
- sdk/runanywhere-commons/include/rac/core/rac_core.h: 4 entry points
  (rac_service_register_provider, rac_service_unregister_provider,
  rac_service_create, rac_service_list_providers) annotated with the
  new `RAC_DEPRECATED_LEGACY_SVC` macro. Macro expands to C++14
  `[[deprecated]]` for clang/g++/MSVC with appropriate fallbacks.
- sdk/runanywhere-commons/src/infrastructure/registry/service_registry.cpp:
  added `rac_legacy_warn_once(fn_name)` helper that emits a single
  RAC_LOG_WARNING per entry point on first invocation, thread-safe via
  std::atomic<bool> in a guarded map. Inserted into all 4 entry points.
- docs/engine_plugin_authoring.md: new §"Migrating off the legacy
  service registry (GAP 11 Phase 29)" with the call-site translation
  table — every legacy call has its `rac_plugin_*` replacement.

Phase 30 — audit the residue:
- docs/gap11_audit_repoint.md NEW. `rg -c rac_service_*` across the
  monorepo found 88 references in 30 files: 4 in SDK frontends (Swift
  bridge + Flutter FFI), 56 in commons (incl. the 21 in the impl file
  itself, scheduled for `git rm` in v3), and 22 in engine plugins
  (dual-path bridges + JNI thunks). Per-call-site mechanical
  translation documented; physical repoint scheduled for v3 because
  each caller needs per-platform behavioral verification.

Phase 31 — final v2 gate:
- docs/gap11_final_gate_report.md NEW. All 7 spec Success Criteria
  addressed. 5 OK, 2 OK partial (the actual `git rm` +
  RAC_PLUGIN_API_VERSION bump are layout-incompatible struct removals
  by convention deferred to v3 — same pattern as Square Wire 3.x→4.x
  and gRPC Server→aio.server migrations).
- docs/v2_migration_complete.md NEW. Comprehensive post-mortem covering
  all 33 phases across 6 waves. Includes the full architecture-as-built
  tree diagram, wave/GAP scoreboard with LOC delta per gap, "what v2
  unlocks" summary, and the v3 deferral list.

Verified:
- service_registry.cpp compiles clean with -Wno-deprecated-declarations.
- All 4 rac_service_* entry points reachable both at compile-time (with
  warning) and at runtime (with one-time log).
- Migration table in engine_plugin_authoring.md cross-checked against
  the actual API in rac/plugin/rac_plugin_entry.h + rac/router/rac_route.h.

Wave F complete. v2 architecture migration: done. Ready to merge to main
as PR #494.

What's deferred to v3 (per gap11_final_gate_report.md):
- git rm service_registry.cpp + rac_capability_t / rac_service_provider_t
- RAC_PLUGIN_API_VERSION 2u → 3u
- Physical delete of Wave D orchestration bodies (~3,040 LOC)
- Per-call-site repoint of 30 files (per gap11_audit_repoint.md)
- Wave E (GAP 05 DAG runtime primitives) — gated on a second pipeline

Made-with: Cursor
sanchitmonga22 added a commit that referenced this pull request Apr 23, 2026
Three independent agents audited the branch on Wave-F-final HEAD,
cross-referencing v2_gap_specs/* with docs/gap0*_final_gate_report.md
and the actual code state. Findings converged on:

- Waves A, B, C are substantively shipped: IDL + codegen, plugin ABI
  + dynamic loading + router, root CMake + presets + engines/ reorg,
  streaming adapter contracts in all 5 SDKs.

- Wave D (GAP 08 frontend deletion) and Wave F (GAP 11 legacy removal)
  shipped *deprecation pressure*, not the *physical deletes* the gate
  prose initially described. The orchestration bodies marked
  @deprecated still execute; service_registry.cpp is still on disk;
  RAC_PLUGIN_API_VERSION is still 2u. The Kotlin streamVoiceSession
  function (the largest single Wave D target) lacks even the marker.

- Two GAP 08 LOC ceilings are violated today:
    runanywhere.dart        : 2,688 LOC  (spec ≤ 500)
    VoiceSessionHandle.ts   :   636 LOC  (spec ≤ 250)

- Aggregate diff vs branch start (8d1f851): 127 files,
  +3,845 / −6,095, net −2,250 LOC. The deletes are the 10 legacy
  build-*.sh scripts from GAP 07 Phase 7, not Wave D's ~3,040 LOC of
  orchestration (which is scheduled, not shipped).

- No v2_gap_specs/GAP_11_*.md exists in the repo — gate report cites
  a missing spec; criteria reverse-engineered from the gate.

- GAP 05 deferred per Wave E (no second pipeline yet).

This commit:

1. wave_roadmap.md
   - Replaces the "all five waves done" framing with a per-wave
     spec-vs-reality table.
   - Adds an "Audit snapshot" section near the top so reviewers
     don't miss the deferral story.
   - Points at the new v2_remaining_work.md for the action list.

2. v2_remaining_work.md NEW
   - Single prioritized list (P0 build sanity → P1 streaming E2E →
     P2 Wave D physical deletes (10 platform-specific PRs) →
     P3 v3 cut-over → P4 spec-drift cleanups → P5 optional Wave E).
   - Each item cites the spec criterion + files + effort estimate
     pulled from the original plan budget.
   - Includes the audit methodology so the report is reproducible.

3. v2_migration_complete.md
   - Reframed from "Complete" tagline to "Status & Post-Mortem"
     ("not yet shippable as v2").
   - Adds an "Audit reality check" section with a what-is-real vs
     what-is-deferred table.
   - Adds release-planning guidance: PR #494 = v2-foundation PR,
     v2 ship tag waits on Priority 2, v3 tag is Priority 3.

Why now: the user asked for a current-state assessment + an updated
view of remaining work. This commit is doc-only; no code changes.

Audit transcript: 3 agents on feat/v2-architecture HEAD, cross-checking
spec criteria against gate reports against actual files. All three
converged on the same picture.

Made-with: Cursor
sanchitmonga22 added a commit that referenced this pull request Apr 23, 2026
…s flips

Phase 16. Closes the v2 close-out program. All 16 phases complete on
the single feat/v2-architecture branch (PR #494).

What ships in this commit:

1. docs/v2_closeout_results.md NEW — comprehensive results report:
   - Per-file LOC delta table (10 in-place targets + 3 git-rm'd files)
   - Branch diff vs Phase 0 baseline e81fae3: 66 files,
     +6,028 / -6,772, net -744 LOC
   - Wave D total delete: -6,247 LOC across 13 files (3 full delete +
     10 in-place shrink). 22% over the spec's 5,100±500 target,
     driven by the 3 zero-caller Kotlin files that the audit
     underestimated.
   - GAP 09 spec-criteria status flips: #1, #3, #4, #5, #6, #7, #8,
     #9 all moved from MISSING/PARTIAL to OK.
   - GAP 08 spec-criteria status flips: #1, #3, #5, #6, #7, #10
     moved to OK; #4 (runanywhere.dart ≤500) stays DEFERRED with
     explicit rationale.
   - 8 bugs found + fixed during execution (CMakePresets schema,
     5min vs 60s auth drift, grpc-swift v2 plugin name, grpc-swift
     v2 flag rename, 88-vs-131 orphan count, 3 zero-caller Kotlin
     files, AlamofireDownloadService already thin, EventBus no
     legacy block).
   - Tests passing: 9 + 10 + 8 across the 3 v2-closeout test suites.

2. docs/gap08_final_gate_report.md updated:
   - Added "POST-CLOSE-OUT UPDATE" callout pointing at
     v2_closeout_results.md.
   - Marker-only criteria are now backed by real deletes.

3. docs/gap09_final_gate_report.md updated:
   - Added "POST-CLOSE-OUT UPDATE" callout listing the specific
     phase-by-phase work that closed the previously-PARTIAL/MISSING
     criteria.

4. docs/wave_roadmap.md "Audit snapshot" table updated:
   - GAP 08 row: "Markers + audits only" → "−6,247 LOC actually
     deleted in v2 close-out Phases 6-14".
   - GAP 09 row: "Shipped partial" → "Shipped. All 9 spec criteria
     OK or OK-by-design".

5. docs/v2_remaining_work.md status banner:
   - "STATUS UPDATE (post-v2-closeout): P0, P1, P2 are DONE. What
     remains: P3 (v3 cut-over), P4 (spec-drift cleanups),
     P5 (Wave E — optional)."

6. docs/v2_migration_complete.md tagline flipped:
   - "READY TO SHIP as v2" (was "not yet shippable as v2" per the
     post-Wave-F audit).

What's deferred to follow-up PRs (acknowledged honestly):

  - Dart runanywhere.dart (2,688 → ≤500): per-feature extension
    extraction across 79 methods. Multi-day Dart-specific refactor.
  - Kotlin CppBridgeAuth.kt (181 LOC remain) → 0: needs rac_auth_*
    JNI thunks; sequenced for safety per
    docs/v2_closeout_phase5_cabis.md.
  - Kotlin orphan natives (~95 declarations across 21 surviving
    CppBridge*.kt files): per-bridge cleanup PRs.
  - Per-platform behavioral verification: 60-sec auth refresh
    (Android), voice barge-in latency (iOS), download resume
    smoke check. All scheduled in
    docs/v2_closeout_device_verification.md.
  - Sample-app smoke automation: separate v2.x workstream.

Verified locally:
  - All 3 v2-closeout test suites green:
      test_proto_event_dispatch (9/9), test_llm_thinking (10/10),
      parity_test_cpp (8/8 events match golden).
  - cmake --preset macos-release configures clean.
  - cmake --build --preset macos-release --target rac_commons builds
    clean (only intentional GAP 11 deprecated-warnings).

The v2 architecture program is closed.

Total v2-closeout commit count: 17 commits across 16 phases on
feat/v2-architecture, all on a single branch / single PR.

Made-with: Cursor
Comment thread .github/workflows/pr-build.yml Outdated
Comment on lines +54 to +63
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
with:
platform: sdk-only
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Gradle build (no test)
working-directory: sdk/runanywhere-kotlin
run: ./gradlew build -x test --no-daemon || echo "::warning::Kotlin gradle build failed — non-blocking on PR"
- name: Install ninja + protobuf
run: sudo apt-get update && sudo apt-get install -y ninja-build libprotobuf-dev protobuf-compiler
- run: cmake --preset linux-debug
- run: cmake --build --preset linux-debug
- run: ctest --preset linux-debug

sdk_web:
needs: [detect, native_web]
if: |
always() && (
needs.detect.outputs.sdk_web == 'true' ||
needs.native_web.result == 'success'
)
runs-on: ubuntu-latest
timeout-minutes: 20
linux-asan:
Comment thread .github/workflows/pr-build.yml Outdated
Comment on lines +64 to +72
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
with:
platform: sdk-only
- name: Install web deps
working-directory: sdk/runanywhere-web
run: npm install
- name: Build TS (core then llamacpp then onnx)
working-directory: sdk/runanywhere-web
# build:ts must run before typecheck because llamacpp/onnx packages
# import from `@runanywhere/web`; tsc can only resolve those imports
# once the core package has produced its `dist/` (with .d.ts files).
run: npm run build:ts
- name: Typecheck (now that dist/ exists)
working-directory: sdk/runanywhere-web
run: npm run typecheck
- run: sudo apt-get update && sudo apt-get install -y ninja-build libprotobuf-dev protobuf-compiler
- run: cmake --preset linux-asan
- run: cmake --build --preset linux-asan
- run: ctest --preset linux-asan

sdk_flutter:
# Flutter consumes BOTH iOS and Android natives, so fire whenever either
# native-build job succeeded on this run, OR when Flutter SDK paths changed.
needs: [detect, native_ios, native_android]
if: |
always() && (
needs.detect.outputs.sdk_flutter == 'true' ||
needs.native_ios.result == 'success' ||
needs.native_android.result == 'success'
)
runs-on: ubuntu-latest
timeout-minutes: 25
ios-device:
Comment thread .github/workflows/pr-build.yml Outdated
Comment on lines +73 to +80
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
- name: Flutter analyze (each package)
working-directory: sdk/runanywhere-flutter
run: |
for pkg in packages/*/; do
if [ -f "$pkg/pubspec.yaml" ]; then
echo "=== Analyzing $pkg ==="
(cd "$pkg" && flutter pub get && flutter analyze) || echo "::warning::flutter analyze failed in $pkg"
fi
done
- run: brew install protobuf
- run: cmake --preset ios-device
- run: cmake --build --preset ios-device --config Release

sdk_react_native:
# React Native consumes BOTH iOS and Android natives, so fire whenever either
# native-build job succeeded on this run, OR when RN SDK paths changed.
needs: [detect, native_ios, native_android]
if: |
always() && (
needs.detect.outputs.sdk_react_native == 'true' ||
needs.native_ios.result == 'success' ||
needs.native_android.result == 'success'
)
runs-on: ubuntu-latest
timeout-minutes: 25
android-arm64:
Comment on lines +81 to +99
runs-on: ubuntu-22.04
env:
ANDROID_NDK_HOME: ${{ github.workspace }}/android-ndk-r27c
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
with:
platform: sdk-only
- name: Enable Corepack (for yarn@3.6.1 declared in packageManager)
run: corepack enable
- name: Install RN workspace
working-directory: sdk/runanywhere-react-native
- name: Install Android NDK
run: |
# The RN SDK uses yarn workspaces + yarn@3.6.1 via Corepack; fall
# back to npm install only as a last resort for older checkouts.
if [ -f yarn.lock ] || grep -q '"packageManager": "yarn@' package.json 2>/dev/null; then
yarn install --immutable || yarn install
else
npm install --legacy-peer-deps
fi
- name: Typecheck packages
working-directory: sdk/runanywhere-react-native
run: |
for pkg in packages/*/; do
if [ -f "$pkg/tsconfig.json" ]; then
echo "=== Typechecking $pkg ==="
(cd "$pkg" && npx tsc --noEmit) || echo "::warning::tsc failed in $pkg"
fi
done

# ---------------------------------------------------------------------------
# Lint jobs — fast, parallel, symmetric across all 5 SDKs.
# Each runs independently of the native builds and can fail a PR on its own.
# Path-filtered so a Swift-only PR doesn't run Kotlin/Web/Flutter/RN lints.
# ---------------------------------------------------------------------------

lint_swift:
needs: detect
if: needs.detect.outputs.sdk_swift == 'true' || needs.detect.outputs.workflows == 'true'
curl -sSL -o ndk.zip https://dl.google.com/android/repository/android-ndk-r27c-linux.zip
unzip -q ndk.zip
mv android-ndk-r27c "${ANDROID_NDK_HOME}"
- run: sudo apt-get update && sudo apt-get install -y ninja-build
- run: cmake --preset android-arm64
- run: cmake --build --preset android-arm64

# =============================================================================
# Per-SDK frontend builds (call wrapper scripts, which internally drive cmake)
# =============================================================================

swift-spm:
Comment on lines +111 to +127
runs-on: ubuntu-22.04
needs: [android-arm64]
env:
ANDROID_NDK_HOME: ${{ github.workspace }}/android-ndk-r27c
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
with:
platform: sdk-only
- name: Lint Kotlin SDK (detekt + ktlint)
working-directory: sdk/runanywhere-kotlin
run: ./gradlew detekt ktlintCheck --no-daemon
- name: Lint Android example (detekt + ktlint)
working-directory: examples/android/RunAnywhereAI
run: ./gradlew detekt ktlintCheck --no-daemon
- run: |
curl -sSL -o ndk.zip https://dl.google.com/android/repository/android-ndk-r27c-linux.zip
unzip -q ndk.zip
mv android-ndk-r27c "${ANDROID_NDK_HOME}"
- run: sudo apt-get update && sudo apt-get install -y ninja-build
- name: Build Android JNI libs
run: ./scripts/build-core-android.sh
- name: gradle assembleDebug
run: ./gradlew :runanywhere-kotlin:assembleDebug

lint_web:
needs: detect
if: needs.detect.outputs.sdk_web == 'true' || needs.detect.outputs.workflows == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
flutter-pubget:
Comment on lines +128 to +135
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
with:
platform: sdk-only
- name: Install deps
working-directory: sdk/runanywhere-web
run: npm install
- name: Build core (so llamacpp/onnx can resolve types)
working-directory: sdk/runanywhere-web
run: npm run build -w packages/core
- name: Lint
working-directory: sdk/runanywhere-web
run: npm run lint
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.38.x' }
- run: cd sdk/runanywhere-flutter/packages/runanywhere && flutter pub get && flutter analyze --no-fatal-infos lib

lint_rn:
needs: detect
if: needs.detect.outputs.sdk_react_native == 'true' || needs.detect.outputs.workflows == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
rn-typecheck:
Comment on lines +136 to +144
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-toolchain
with:
platform: sdk-only
- name: Enable Corepack (yarn@3.6.1)
run: corepack enable
- name: Install RN SDK deps
working-directory: sdk/runanywhere-react-native
run: yarn install --immutable || yarn install
- name: Prepare core (nitrogen + build for llamacpp/onnx)
working-directory: sdk/runanywhere-react-native
run: yarn core:prepare || true
- name: Lint RN SDK
working-directory: sdk/runanywhere-react-native
run: yarn lint
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: cd sdk/runanywhere-react-native && yarn install --immutable
- run: cd sdk/runanywhere-react-native/packages/core && yarn typecheck

lint_flutter:
needs: detect
if: needs.detect.outputs.sdk_flutter == 'true' || needs.detect.outputs.workflows == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
web-typecheck:
Comment thread .github/workflows/pr-build.yml Outdated
Comment on lines +145 to +150
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
- name: Flutter analyze — SDK packages
working-directory: sdk/runanywhere-flutter
run: |
set -e
# Generate pubspec_overrides.yaml for each sub-package so that
# `flutter pub get` resolves `runanywhere` from the local monorepo
# instead of the published version on pub.dev (which lags behind).
for pkg in packages/runanywhere_genie packages/runanywhere_llamacpp packages/runanywhere_onnx; do
if [ -f "$pkg/pubspec.yaml" ]; then
printf 'dependency_overrides:\n runanywhere:\n path: ../runanywhere\n' \
> "$pkg/pubspec_overrides.yaml"
fi
done
for pkg in packages/runanywhere packages/runanywhere_genie packages/runanywhere_llamacpp packages/runanywhere_onnx; do
if [ -f "$pkg/pubspec.yaml" ]; then
echo "=== Analyzing $pkg ==="
(cd "$pkg" && flutter pub get && flutter analyze)
fi
done
- name: Flutter analyze — example app
working-directory: examples/flutter/RunAnywhereAI
run: flutter pub get && flutter analyze

# ---------------------------------------------------------------------------
# summary: depends on every other job so the PR check shows an aggregate result
# ---------------------------------------------------------------------------
summary:
if: always()
needs:
- detect
- native_ios
- native_android
- native_linux
- native_windows
- native_web
- sdk_swift
- sdk_kotlin
- sdk_web
- sdk_flutter
- sdk_react_native
- lint_swift
- lint_kotlin
- lint_web
- lint_rn
- lint_flutter
runs-on: ubuntu-latest
steps:
- name: Print job results
run: |
echo "::group::Job results"
echo "native_ios = ${{ needs.native_ios.result }}"
echo "native_android = ${{ needs.native_android.result }}"
echo "native_linux = ${{ needs.native_linux.result }}"
echo "native_windows = ${{ needs.native_windows.result }}"
echo "native_web = ${{ needs.native_web.result }}"
echo "sdk_swift = ${{ needs.sdk_swift.result }}"
echo "sdk_kotlin = ${{ needs.sdk_kotlin.result }}"
echo "sdk_web = ${{ needs.sdk_web.result }}"
echo "sdk_flutter = ${{ needs.sdk_flutter.result }}"
echo "sdk_react_native = ${{ needs.sdk_react_native.result }}"
echo "lint_swift = ${{ needs.lint_swift.result }}"
echo "lint_kotlin = ${{ needs.lint_kotlin.result }}"
echo "lint_web = ${{ needs.lint_web.result }}"
echo "lint_rn = ${{ needs.lint_rn.result }}"
echo "lint_flutter = ${{ needs.lint_flutter.result }}"
echo "::endgroup::"
- name: Fail on hard failures
run: |
# 'failure' = job ran and failed. 'cancelled'/'skipped' don't fail the PR.
for r in \
"${{ needs.native_ios.result }}" \
"${{ needs.native_android.result }}" \
"${{ needs.native_linux.result }}" \
"${{ needs.native_windows.result }}" \
"${{ needs.native_web.result }}" \
"${{ needs.lint_swift.result }}" \
"${{ needs.lint_kotlin.result }}" \
"${{ needs.lint_web.result }}" \
"${{ needs.lint_rn.result }}" \
"${{ needs.lint_flutter.result }}"; do
if [ "$r" = "failure" ]; then
echo "::error::A build or lint job failed."
exit 1
fi
done
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: cd sdk/runanywhere-web/packages/core && npm install --no-audit --no-fund && npx tsc --noEmit
Comment thread .github/workflows/streaming-perf.yml Outdated
Comment on lines +32 to +75
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y ninja-build cmake libprotobuf-dev protobuf-compiler libabsl-dev python3

- name: Configure
run: cmake --preset linux-debug -DRAC_BUILD_TESTS=ON

- name: Build streaming targets
run: |
cmake --build --preset linux-debug --target parity_test_cpp
cmake --build --preset linux-debug --target perf_producer
cmake --build --preset linux-debug --target cancel_producer

- name: Run parity check (C++ golden producer)
run: ctest --test-dir build/linux-debug -R parity_test_cpp_check --output-on-failure

- name: Run perf producer
run: ctest --test-dir build/linux-debug -R perf_producer_cpp --output-on-failure

- name: Run cancel producer
run: ctest --test-dir build/linux-debug -R cancel_producer_cpp --output-on-failure

- name: Aggregate perf percentiles (no per-SDK logs => harness sanity only)
run: ctest --test-dir build/linux-debug -R perf_aggregate --output-on-failure || true

- name: Aggregate cancel traces
run: ctest --test-dir build/linux-debug -R cancel_aggregate --output-on-failure || true

- name: Upload perf-bench fixture
uses: actions/upload-artifact@v4
with:
name: perf-bench-fixture
path: build/linux-debug/tests/streaming/perf_bench/

- name: Upload cancel-parity fixture
uses: actions/upload-artifact@v4
with:
name: cancel-parity-fixture
path: build/linux-debug/tests/streaming/cancel_parity/
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

Bugbot Autofix prepared fixes for all 3 issues found in the latest run.

  • ✅ Fixed: Dev toggle useLocalNatives = true committed to repo
    • Set useLocalNatives back to false in Package.swift so external SPM consumers use release binaries by default.
  • ✅ Fixed: NDK mv fails: source and destination are identical
    • Removed the redundant mv android-ndk-r27c "${ANDROID_NDK_HOME}" lines from both Android workflow jobs so NDK install no longer fails on self-move.
  • ✅ Fixed: Generator expressions in message() print literal strings
    • Replaced generator expressions in the CMake summary with configure-time if()-computed variables so message() prints real mode values.

Create PR

Or push these changes by commenting:

@cursor push f30a17ede9
Preview (f30a17ede9)
diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml
--- a/.github/workflows/pr-build.yml
+++ b/.github/workflows/pr-build.yml
@@ -87,7 +87,6 @@
         run: |
           curl -sSL -o ndk.zip https://dl.google.com/android/repository/android-ndk-r27c-linux.zip
           unzip -q ndk.zip
-          mv android-ndk-r27c "${ANDROID_NDK_HOME}"
       - run: sudo apt-get update && sudo apt-get install -y ninja-build
       - run: cmake --preset android-arm64
       - run: cmake --build --preset android-arm64
@@ -117,7 +116,6 @@
       - run: |
           curl -sSL -o ndk.zip https://dl.google.com/android/repository/android-ndk-r27c-linux.zip
           unzip -q ndk.zip
-          mv android-ndk-r27c "${ANDROID_NDK_HOME}"
       - run: sudo apt-get update && sudo apt-get install -y ninja-build
       - name: Build Android JNI libs
         run: ./scripts/build-core-android.sh

diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -152,8 +152,18 @@
 message(STATUS "RunAnywhere SDKs — root CMake configured")
 message(STATUS "============================================")
 message(STATUS "Version          : ${RAC_VERSION}")
-message(STATUS "Build mode       : $<IF:$<BOOL:${RAC_BUILD_SHARED}>,SHARED,STATIC>")
-message(STATUS "Plugin mode      : $<IF:$<BOOL:${RAC_STATIC_PLUGINS}>,STATIC (linked into commons),SHARED (dlopen-loaded)>")
+if(RAC_BUILD_SHARED)
+    set(RAC_BUILD_MODE_SUMMARY "SHARED")
+else()
+    set(RAC_BUILD_MODE_SUMMARY "STATIC")
+endif()
+if(RAC_STATIC_PLUGINS)
+    set(RAC_PLUGIN_MODE_SUMMARY "STATIC (linked into commons)")
+else()
+    set(RAC_PLUGIN_MODE_SUMMARY "SHARED (dlopen-loaded)")
+endif()
+message(STATUS "Build mode       : ${RAC_BUILD_MODE_SUMMARY}")
+message(STATUS "Plugin mode      : ${RAC_PLUGIN_MODE_SUMMARY}")
 message(STATUS "Sanitizer        : ${RAC_SANITIZER}")
 message(STATUS "Tests            : ${RAC_BUILD_TESTS}")
 message(STATUS "Server (HTTP)    : ${RAC_BUILD_SERVER}")

diff --git a/Package.swift b/Package.swift
--- a/Package.swift
+++ b/Package.swift
@@ -40,7 +40,7 @@
 // the same — it's been renamed to `useLocalNatives` for consistency with the
 // equivalent toggle in the other client SDKs (Kotlin, Flutter, React Native).
 // =============================================================================
-let useLocalNatives = true //  Toggle: true for local dev, false for release
+let useLocalNatives = false //  Toggle: true for local dev, false for release
 
 // Version for remote XCFrameworks (used when useLocalNatives = false)
 // Updated automatically by CI/CD during releases.

You can send follow-ups to the cloud agent here.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 801cac4. Configure here.

Comment thread Package.swift
// equivalent toggle in the other client SDKs (Kotlin, Flutter, React Native).
// =============================================================================
let useLocalNatives = false // Toggle: true for local dev, false for release
let useLocalNatives = true // Toggle: true for local dev, false for release
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dev toggle useLocalNatives = true committed to repo

High Severity

useLocalNatives was flipped from false to true. The inline comment says "true for local dev, false for release" and the full documentation block (lines 28-37) explicitly marks false as PRODUCTION. When this merges to main, external SPM consumers will get the local-dev path that expects XCFrameworks at sdk/runanywhere-swift/Binaries/ — which don't exist in a fresh clone — instead of downloading from GitHub Releases. This breaks all external Swift Package Manager resolution.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 801cac4. Configure here.

Comment thread .github/workflows/pr-build.yml Outdated
if: needs.detect.outputs.sdk_swift == 'true' || needs.detect.outputs.workflows == 'true'
curl -sSL -o ndk.zip https://dl.google.com/android/repository/android-ndk-r27c-linux.zip
unzip -q ndk.zip
mv android-ndk-r27c "${ANDROID_NDK_HOME}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NDK mv fails: source and destination are identical

High Severity

ANDROID_NDK_HOME is set to ${{ github.workspace }}/android-ndk-r27c. The unzip extracts the NDK into the default working directory, which is also github.workspace. The subsequent mv android-ndk-r27c "${ANDROID_NDK_HOME}" resolves to moving a directory onto itself, which fails with exit code 1 and breaks both the android-arm64 and kotlin-android CI jobs. The mv line is unnecessary since the NDK is already at the correct path after unzip.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 801cac4. Configure here.

Comment thread CMakeLists.txt
message(STATUS "============================================")
message(STATUS "Version : ${RAC_VERSION}")
message(STATUS "Build mode : $<IF:$<BOOL:${RAC_BUILD_SHARED}>,SHARED,STATIC>")
message(STATUS "Plugin mode : $<IF:$<BOOL:${RAC_STATIC_PLUGINS}>,STATIC (linked into commons),SHARED (dlopen-loaded)>")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generator expressions in message() print literal strings

Low Severity

The configuration summary uses CMake generator expressions ($<IF:$<BOOL:...>,...,...>) inside message(STATUS ...). Generator expressions are only evaluated during build-system generation for target-related commands — message() runs at configure time and prints the literal unexpanded string. The "Build mode" and "Plugin mode" lines will display something like $<IF:$<BOOL:OFF>,SHARED,STATIC> instead of the intended STATIC or SHARED.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 801cac4. Configure here.

Comment on lines +13 to +38
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Fail if removed legacy files reappear
shell: bash
run: |
set -euo pipefail
blocked=(
"sdk/runanywhere-react-native/packages/core/src/Features/VoiceSession/VoiceSessionHandle.ts"
"sdk/runanywhere-react-native/packages/core/src/Public/Extensions/RunAnywhere+VoiceSession.ts"
"sdk/runanywhere-react-native/scripts/build-react-native.sh"
"sdk/runanywhere-web/packages/core/src/Public/Extensions/RunAnywhere+VoiceAgent.ts"
"sdk/runanywhere-web/scripts/build-web.sh"
)

found=0
for path in "${blocked[@]}"; do
if [[ -e "$path" ]]; then
echo "Blocked legacy file reintroduced: $path"
found=1
fi
done

if [[ "$found" -ne 0 ]]; then
exit 1
fi

const canRemoveDocuments = RAG.capabilities().documentRemoval;
listEl.innerHTML = documents.map((doc) => `
<li class="docs-item" data-id="${escapeHtml(doc.id)}">
<div class="docs-item-title">${escapeHtml(doc.name)}</div>
<div class="docs-item-meta">${doc.chunkCount} chunk${doc.chunkCount === 1 ? '' : 's'}</div>
</div>
${canRemoveDocuments ? `<button class="btn btn-icon docs-item-delete" data-id="${escapeHtml(doc.id)}" aria-label="Remove">
sanchitmonga22 and others added 15 commits May 5, 2026 13:34
…nalize helpers (CPP-04)

Construct RAGBackend directly from proto bytes in rac_rag_proto_abi.cpp;
hoist rac_rag_session_*_proto / rac_rag_ingest_proto / rac_rag_query_proto
/ rac_rag_clear_proto / rac_rag_stats_proto declarations to rac_rag.h.

Deleted (commons):
- include/rac/features/rag/rac_rag_pipeline.h (~314 LOC)
- src/features/rag/rac_rag_pipeline.cpp (~340 LOC)
- rac_rag_config_to/from_proto, rac_rag_query_to/from_proto,
  rac_search_result_to/from_proto, rac_rag_result_to/from_proto
  (unused after proto ABI refactor; ~143 LOC in proto_adapters.cpp + 25 decls in .h)

Deleted (RN):
- packages/core/cpp/bridges/RAGBridge.cpp (~298 LOC)
- packages/core/cpp/bridges/RAGBridge.hpp (~40 LOC)

ModelRegistryBridge + CompatibilityBridge retained (comment blocks
mark getHandle/checkCompatibility as internal adapter glue still
reached from Registry/Storage/Lifecycle extensions; blocker:
rac_model_compatibility_*_proto ABI does not yet exist on commons;
tracked under react-native.md CPP-RN-RAG-BRIDGE-PURGE).

Validation:
- cmake --preset macos-debug -DRAC_BUILD_BACKENDS=ON configures clean
- cmake --build build/macos-debug --target rac_commons succeeds
- cmake --build build/macos-debug --target runanywhere_commons_jni succeeds
- ctest -R "advanced_modality_proto_abi_tests|rag_service_proto_abi_tests"
  both pass (100%, 2/2)
- cd sdk/runanywhere-react-native && yarn typecheck passes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…EB-01)

Flips the Emscripten force-disable gates on engines/onnx, engines/sherpa,
and runtimes/onnxrt to EXISTS-guarded disables keyed on the vendored WASM
static archives under sdk/runanywhere-commons/third_party/. Once the
archives land (CPP-13-VENDOR / WEB-01-VENDOR), the engines turn on
automatically and commons WASM ships _rac_backend_onnx_register +
_rac_backend_sherpa_register.

Changes:
- engines/onnx/CMakeLists.txt: EXISTS gate for onnxruntime-wasm static lib.
- engines/sherpa/CMakeLists.txt: EXISTS gate + new elseif(EMSCRIPTEN)
  prebuilt-discovery branch creating STATIC IMPORTED targets for every
  libsherpa-onnx-*.a + libkaldi-*.a + libkissfft-*.a (+ optional Piper
  TTS deps) under third_party/sherpa-onnx-wasm/lib/.
- runtimes/onnxrt/CMakeLists.txt: mirror the same EXISTS gate.
- sdk/runanywhere-commons/cmake/FetchONNXRuntime.cmake: Emscripten branch
  now creates a STATIC IMPORTED onnxruntime target when the archive is
  present; falls back to INTERFACE-only otherwise.
- sdk/runanywhere-web/wasm/CMakeLists.txt: adds
  _rac_backend_sherpa_register / _unregister to RAC_EXPORTED_FUNCTIONS and
  links rac_backend_sherpa into runanywhere_wasm when the target exists.
  Updated FATAL_ERROR explains the vendoring dependency; sherpa target
  absence downgrades to STATUS warning instead of fail-fast.

Residual (follow-up row): staging the ONNX Runtime WASM + Sherpa-ONNX WASM
static archives under sdk/runanywhere-commons/third_party/. Build recipes
live in the banners at the top of engines/onnx and engines/sherpa
CMakeLists.txt plus the .gitkeep sentinels under the vendored directories
(directories themselves are gitignored by sdk/runanywhere-commons/.gitignore:44).

Validation:
- macOS reconfigure green (cmake --preset macos-debug): Configuring ONNX
  backend + Configuring Sherpa-ONNX backend both still logged; no
  non-EMSCRIPTEN path regressed.
- rac_commons target builds clean (no changes affected macOS code).
- Existing racommons-llamacpp.wasm + racommons-llamacpp-webgpu.wasm
  artifacts untouched (vendored archives not yet present → engines still
  skip on Emscripten; pre-existing unrelated RAG header break in
  wasm_exports.cpp is separate, tracked elsewhere).

Cross-links:
- thoughts/shared/plans/wasm_onnx_unblock.md (9-step plan, Steps 2-4+6
  landed here; Steps 1+5 are CPP-13-VENDOR follow-up).
- gaps/gaps/inconsistencies/cpp-layer.md § CPP-13
- gaps/gaps/inconsistencies/web.md § WEB-01

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…s (CPP-04 follow-up)

CPP-04 (commit 8958b8e) deleted rac_rag_pipeline.{h,cpp} but the WASM
entry point at sdk/runanywhere-web/wasm/src/wasm_exports.cpp:83 still
included the deleted header. Remove the include; no RAG functions are
actually referenced in wasm_exports.cpp so no replacement is needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…TE-LLAMA-load_model)

The rac_llm_llamacpp_load_model function was a documented no-op since
LlamaCPP loads the model during rac_llm_llamacpp_create(). It was called
in only two places:

- The llamacpp_vtable_initialize callback (inlined to RAC_SUCCESS return)
- A reload test path (removed — reload via destroy/recreate instead)

Delete the function definition, public header declaration, Swift-side
runtime shim declaration, and the WASM exports list entry. Update the
test to stop attempting a reload through this removed API.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…a (ENG-LLAMA-04)

Both engines declared their own `enum class DeviceType` in their
backend headers with overlapping numeric codes and divergent value
sets. Consolidate into a single shared internal header under
engines/common/ — union of all values (CPU/GPU/NEURAL_ENGINE/
METAL/CUDA/WEBGPU) in a single `runanywhere::DeviceType`.

- New: engines/common/rac_engine_device_type.h
- Update llamacpp_backend.h + sherpa_backend.h to include the shared
  header and remove their local duplicates.
- Add `${CMAKE_CURRENT_SOURCE_DIR}/..` (engines/) to each backend's
  include path so the shared header resolves via `common/*.h`.

Closes ENG-LLAMA-04 / DUP-04 (active-backend slice).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ter + static shims (ENG-SHERPA-03)

Unify the plugin-registration contract across the three active backends so all
of llamacpp / sherpa / onnx ship the same two entry points:

  1. Explicit `rac_backend_<name>_register()` for dynamic-link hosts
     (Android / Linux / macOS dev).
  2. `rac_static_register_<name>.cpp` shim expanding
     `RAC_STATIC_PLUGIN_REGISTER(<name>)` for static-link hosts (iOS / WASM,
     `RAC_STATIC_PLUGINS=ON`).

Changes:
- Delete the ELF `__attribute__((constructor))` auto-register block at the
  bottom of `engines/sherpa/rac_plugin_entry_sherpa.cpp`.
- Add `rac_backend_sherpa_register()` / `rac_backend_sherpa_unregister()` to
  `engines/sherpa/rac_backend_sherpa_register.cpp` (mirrors the onnx shape).
- Publish `sdk/runanywhere-commons/include/rac/plugin/rac_plugin_entry_sherpa.h`
  declaring the entry point + register functions (parallel to llamacpp / onnx).
- Add `engines/sherpa/rac_static_register_sherpa.cpp` +
  `engines/onnx/rac_static_register_onnx.cpp` shims; wire them into each
  engine's CMakeLists with the same `if(RAC_STATIC_PLUGINS)` branching as
  `engines/llamacpp/CMakeLists.txt`.
- Teach the ONNX JNI bridge to dlsym `rac_backend_sherpa_register` at
  `nativeRegister` time so Android / JVM hosts that dlopen
  `librac_backend_sherpa.so` keep registering sherpa without a dedicated
  sherpa JNI bridge.

Validation: macos-debug rebuilds `rac_commons`, `rac_backend_sherpa`,
`rac_backend_onnx`, `runanywhere_sherpa`, `runanywhere_onnx` clean. The
pre-existing ggml-metal upstream compile failure on `rac_backend_llamacpp` is
unrelated to this refactor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-ONNX-02)

Relocate onnx_embedding_provider.{h,cpp} and rac_onnx_embeddings_register.cpp
from sdk/runanywhere-commons/src/features/rag/ into engines/onnx/ so the ONNX
engine plugin fully owns its embedding primitive. Replaces the cross-module
CMake RAG_DIR tweak (engines/onnx/CMakeLists.txt) with plain local sources
and updates comment references in the embeddings service header, RAG
CMakeLists, plugin entry, and placeholder tokenizer test. The files are
still gated by RAC_BACKEND_RAG because the RAG pipeline is the sole
consumer of the embeddings primitive today.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tions from onnx backend (ENG-ONNX-05)

Audited commons for consumers of rac_storage_strategy_register /
rac_download_strategy_register: no live caller exists. The wrappers
rac_model_strategy_find_path / _detect / _is_valid / _prepare_download
/ _get_download_dest / _post_process all fall through to the default
path-resolution behavior (rac_model_paths_resolve_artifact + default
extraction) when no per-framework strategy is registered. Framework
gating for ONNX is already carried by g_onnx_engine_vtable.metadata
per the plugin-entry TU, making RAC_FRAMEWORK_ONNX strategy
registration redundant pre-v3 machinery.

Deleted from engines/onnx/rac_backend_onnx_register.cpp:
- rac_storage_strategy_register(RAC_FRAMEWORK_ONNX, ...)
- rac_download_strategy_register(RAC_FRAMEWORK_ONNX, ...)
- rac_model_strategy_unregister(RAC_FRAMEWORK_ONNX)
- 8 dead strategy callbacks (onnx_storage_find_model_path,
  onnx_storage_detect_model, onnx_storage_is_valid,
  onnx_storage_get_patterns, onnx_download_prepare,
  onnx_download_get_dest, onnx_download_post_process,
  onnx_download_cleanup) + their static strategy structs
- Unused includes: rac_model_strategy.h, rac_model_types.h, cstdint,
  cstdio, cstdlib, cstring, vector

The entire strategy-registry machinery in commons now has zero live
registrants and is dead code - tracked as CPP-STRATEGY-CLEANUP in
gaps/gaps/inconsistencies/cpp-layer.md for a follow-up row removing
the register/lookup/wrapper APIs and the rac_model_strategy.h header
entirely.

Validation:
- cmake --preset macos-debug (green)
- cmake --build build/macos-debug --target rac_backend_onnx (green)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…herpa copy (DUP-01)

Sherpa's `convert_int16_to_float32` wrapper now routes through the shared
`rac::audio::rac_audio_pcm16_to_float32` helper in
`sdk/runanywhere-commons/include/rac/audio/rac_audio_convert.h`, eliminating
the duplicated inner loop. The sherpa-only `std::vector<float>`-returning
wrapper is kept as a thin adapter since both transcribe paths still require
the allocate-and-return shape.

Validated: `cmake --build build/macos-debug --target rac_backend_sherpa` green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…gnment (DUP-07)

Extract the duplicated `target_link_options(... --gc-sections, max-page-size=16384,
common-page-size=16384)` block into a shared `rac_apply_android_page_alignment()`
helper in `cmake/plugins.cmake`. Replace 8 copy-pasted occurrences across the 3
active engines (llamacpp, sherpa, onnx) with single-line calls. The helper is a
no-op on non-Android platforms so callers don't need a RAC_PLATFORM_ANDROID guard
at the call site. Deferred engines (whispercpp, genie) keep the raw block until
they are un-parked.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e scaffolds (DUP-03)

The per-primitive `<name>_<primitive>_create_impl` functions on each backend
were 7-line copy-pasted forwards onto `rac_<primitive>_<name>_create`. Factor
the scaffold into a single macro `RAC_DEFINE_CREATE_ADAPTER(primitive, name)`
in `rac_plugin_entry.h`.

Migrated: sherpa STT, sherpa TTS, sherpa VAD (net -32 LOC in
`engines/sherpa/rac_backend_sherpa_register.cpp`). Sherpa VAD loses a dead
RAC_LOG_ERROR + null-handle recovery branch that `rac_vad_sherpa_create`
never actually triggers — it asserts out_handle internally on failure.

Not migrated (scoped out with note in the tracked gap doc):
  - llamacpp LLM `create_impl`: 45-line body wraps `LlamaCppRuntimeImpl`
    around a CPU-runtime session — cannot be expressed as a 7-line forward.
  - onnx embeddings `create_impl`: try/catch + std::make_unique +
    is_ready()-after-init check diverges from the scaffold shape.

Build verified: `cmake --build build/macos-debug --target rac_backend_sherpa`
links cleanly. The llamacpp register.cpp.o also builds; the pre-existing
vendored ggml-metal .m compile failure against current Xcode SDK is
unrelated to this change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ates (DUP-05)

Factor the duplicated {callback, user_data} stream adapter struct used by
engines/llamacpp's LLM and VLM vtable TUs into a shared template header at
sdk/runanywhere-commons/include/rac/plugin/rac_stream_adapter.h.

Both llamacpp TUs now use `rac::plugin::StreamAdapter<CallbackT>` via a
`using` alias parameterized on their respective `rac_*_stream_callback_fn`
typedef. Per-primitive C trampolines remain intentionally per-TU because
each still needs to discard its own `is_final` arg; the win is the
removed struct body.

Validation: `cmake --build build/macos-debug --target rac_backend_llamacpp`
recompiles `rac_backend_llamacpp_register.cpp.o` and
`rac_backend_llamacpp_vlm_register.cpp.o` clean. The pre-existing
ggml-metal obj-c compile errors in _deps/llamacpp-src are unrelated.

Updated gaps/gaps/inconsistencies/engines.md: DUP-05 marked resolved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…lete per-backend duplicates (DUP-06)

Lifted rac_event_track("*.backend.created", ...) emission into the commons
service layer so future backends inherit it automatically and can't silently
drop the event.

Commons emits (each reads backend name from vt->metadata.name, so the JSON
payload is correct regardless of which plugin the router picked):
- sdk/runanywhere-commons/src/features/llm/rac_llm_service.cpp      (rac_llm_create)
- sdk/runanywhere-commons/src/features/stt/rac_stt_service.cpp      (rac_stt_create)
- sdk/runanywhere-commons/src/features/tts/rac_tts_service.cpp      (rac_tts_create)
- sdk/runanywhere-commons/src/features/vad/vad_component.cpp        (rac_vad_component_load_model)

Deleted per-backend firings that are now duplicates:
- engines/llamacpp/rac_llm_llamacpp.cpp     (was line 114)
- engines/sherpa/rac_stt_sherpa.cpp         (was line 97)
- engines/sherpa/rac_tts_sherpa.cpp         (was line 74)
- engines/sherpa/rac_vad_sherpa.cpp         (was line 78)

Validation: rac_commons + rac_backend_sherpa compile clean under
'cmake --build build/macos-debug'. engines/whispercpp retains its own emit
(deferred to Iteration-I; will be cleaned up when whispercpp is reactivated).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… + ONNXRT (DUP-RT-01)

Replace inlined release_tensor implementations in CPU and ONNXRT runtimes
with calls to the shared rac::runtime::rac_runtime_release_tensor helper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ONNXRT (DUP-RT-02)

Replaces the inlined offset/bounds check + memmove in cpu_copy_buffer
and onnxrt_copy_buffer with a call into the shared
rac::runtime::rac_runtime_copy_buffer helper declared in
sdk/runanywhere-commons/include/rac/runtime/rac_runtime_helpers.h.

Both runtimes already include the helpers header (DUP-RT-01 wave);
this migration removes the remaining duplicated body.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
sanchitmonga22 and others added 30 commits May 16, 2026 15:38
After adding the `protobuf` pip dep, the next generator in the chain
(`generate_swift_modality_abi.py`) bailed with:

  error: PyYAML not installed.
  Run `python3 -m pip install pyyaml` or use the toolchain installed
  by scripts/setup-toolchain.sh.

Bundle pyyaml into the same install step so both groups of generators
have their Python deps ready.

Co-authored-by: Cursor <cursoragent@cursor.com>
idl-drift-check finally got through every codegen step (mint, dart,
python protobuf+pyyaml, ts-proto, generate_all.sh) and bailed at
the proto-ts build:

  npm error code EUNSUPPORTEDPROTOCOL
  npm error Unsupported URL Type "workspace:": workspace:*

Same root cause we already fixed for pr-build.yml web-typecheck
(commit 086170b): npm walks up from sdk/shared/proto-ts, hits
the root Yarn workspace package.json with `workspace:*` deps it
can't parse, and bails. Add `--workspaces=false` so npm treats
this directory as standalone for the install.

Co-authored-by: Cursor <cursoragent@cursor.com>
…cframework script

- .github/workflows/pr-build.yml (kotlin-android): explicit
  `actions/setup-java@v4` step pinned to Temurin 17. The ubuntu-22.04
  runner default JDK is 11, which AGP 8.13.0 rejects:
    > Android Gradle plugin requires Java 17 to run.
    > You are currently using Java 11.

- scripts/build-core-xcframework.sh: rename the expected static
  archive from `libcommon.a` → `libllama-common.a` to match the
  llama.cpp b9180 target rename that we already fixed in
  engines/llamacpp/CMakeLists.txt's LINK_LIBRARIES list (commit
  6d24629). Without this swift-spm fails the final libtool merge
  with:
    error: required archive not found:
      build/ios-device/_deps/llamacpp-build/common/Release-iphoneos/libcommon.a

Co-authored-by: Cursor <cursoragent@cursor.com>
…stacks

Closes out the second-pass review of PR #494 (v2 architecture migration).
50 review agents staged 124 deduped findings (3 blocker, 42 major, 69 minor,
10 nit) against current HEAD; 15 cross-SDK follow-ups were spawned during the
fix waves. Final state: 139/139 records FIXED with real code changes, zero
FIXED-PARTIAL or RESOLVED-NO-CODE-CHANGE remaining.

Blockers fixed
- Cross-SDK UAF window in modality stream dispatchers (LLM/STT/TTS/VAD/VLM/
  Diffusion): lifted voice_agent's in_flight atomic counter + spinwait quiesce
  pattern to all six dispatchers; new rac_vlm_proto_quiesce() public symbol
  and per-modality unset/destroy spinwait close the Swift/Flutter/RN UAF
- Apple RAC_API exports completed: 479 missing core symbols added,
  RACommons.exports split into core + 3 backend-conditional sibling files
  (rag / onnx_embeddings / whisperkit_coreml) concatenated at CMake configure
  time, stale rac_vad_{start,stop,reset} decls removed (1003/1003 covered)
- Web @runanywhere/web-onnx package no longer auto-installs the standalone
  Sherpa provider whose wasm/ glob was excluded from published files

Major fixes — Commons + IDL
- Tool-calling: tool_choice/forced_tool_name (proto fields 7/8) added to
  ToolCallingSessionCreateRequest, plumbed through build_options_snapshot,
  run-loop and session paths. New cancel ABI: rac_tool_calling_session_cancel
  _proto, rac_tool_calling_run_loop_with_handle_proto, rac_tool_calling_run_
  loop_cancel_proto + JNI shim + Swift/Kotlin/Flutter/RN/Web adapter plumbing
- LLMStreamEvent.tool_call (proto field 18): producer wired for both
  libprotobuf and WASM hand-encoded paths in rac_llm_stream.cpp; consumer
  wiring in Swift/Kotlin/Flutter/RN/Web event types
- structured_output: typed validation failures (INVALID_FORMAT / VALIDATION_
  FAILED) now surface through StructuredOutputResult instead of transport err
- Runtime registry: rac_runtime_register eviction now single-critical-section;
  destroy/dlclose run on locally-captured locals after lock release
- Tool-calling tool_choice=NONE follow-up turn now preserves tool_results
- Cross-thread error propagation in SolutionRunner (mutex-guarded operator-
  error slot drained on wait()); built-in 'retrieve' operator stand-in;
  download orchestrator UB guard on empty-plan path

Major fixes — Swift SDK
- HandleStreamAdapter.tearDown rewritten lock-then-release (no recursive-lock
  deadlock); new tests for terminal-event teardown + sync-unregister-during-
  teardown invariants
- Generated ModalityProtoABI streams now pass onCancel: through to native
  cancel (LLM + StructuredOutput); generator updated to emit it
- RunAnywhere+ToolCalling: toolChoice/forcedToolName/validateCalls public
  params; withTaskCancellationHandler wires AbortSignal-equivalent cancel
- SDKException.fieldPath + validationFailed(fieldPath:message:underlying:)
  factory (cross-SDK ValidationError parity)
- Real SHA-256 checksums for all 4 Package.swift remote binaryTargets
  (RACommons, LlamaCPP, ONNX, Sherpa) + Binaries/checksums.txt sidecar
- CRACommons LoRA 4-arg prototypes stripped; bundled xcframework Headers/
  copies synced with current commons ABI
- README v2 migration; CppBridge+StructuredOutput.generate dead-code removed

Major fixes — Kotlin SDK
- HandleStreamAdapter: Channel.UNLIMITED (Swift AsyncStream parity); 2-phase
  install state machine appends collector before native register, calls
  register outside per-fan-out lock (no lost-first-event)
- ComponentActor unit tests (5 cases); fan-out tests for cancel-one-of-N
  mid-stream + post-detach delivery
- RAConvenience.kt regenerated: zero-literal-by-type (Long==0L etc.),
  else->"" branches for fully-annotated enums
- README/Documentation/ARCHITECTURE/CLAUDE.md migrated from v1 to v2 API
- SDKException.validationFailed(fieldPath, message, cause) factory
- Tool-calling cancel via Job.invokeOnCompletion → racToolCallingSessionCancel

Major fixes — Flutter SDK
- Real bridge stream-drain test (injection seam on DartBridgeTTS, fake FFI
  override) replaces the previous generic StreamController contract test
- kStreamDrainMaxMicrotasks shared constant + drainPendingStreamCallbacks
  helper (deduplicates 4 magic-number sites across TTS/VLM/STT/LLM)
- ToolCallingOptions.toolChoice / forcedToolName plumbed through proto request
- LLMStreamEvent.toolCall consumed end-to-end (Wire decode auto-forwards)
- ONNX bindings: hardcoded lookupFunction<Concrete,Concrete> per symbol
  (replaces unsafe generic _lookup helper)
- VAD callback API removal documented in CHANGELOG; reset() docstring fixed
- Centralization gate parser tolerates pubspec-driven registry post-syn-121

Major fixes — React Native SDK
- HybridRunAnywhereCore.resetAllGlobalComponentHandles: documented destruction
  ordering invariant tying voice_agent destroy-before-leaves to owns_components
  =false contract
- RunAnywhere+ToolCalling: toolChoice/forcedToolName/validateCalls + Abort-
  Signal cancel + validation-failure logger.warning
- RunAnywhere+TextGeneration: toolCall accumulator on LLMGenerationResult
- proto-ts dist regenerated (was stale; root cause of pass-2 typecheck failure)
- nitro peer ranges aligned to 0.33.9 (matches versions.json SoT)
- App.tsx awaits + boolean-gates ONNX.register() (parity with LlamaCPP path)

Major fixes — Web SDK
- TS tool-calling run loop REPLACED with WASM session-driver: calls _rac_tool_
  calling_session_{create,step_with_result,destroy,cancel}_proto via Module.
  addFunction trampoline; loop iteration now lives in commons; addFunction
  trampoline handles executor callback; cancel wiring via AbortSignal
- T6.1 Worker pipeline: @internal @experimental contract; shutdown tears it
  down (OffscreenRuntimeBridge.disposeShared + setStreamWorkerFactory(null) +
  setStreamWorkerInit(null)); 10s handshake timeout; modality-aware cancel
  routing; structured-clone → transferable on init + per-token paths
- EventBus.onCategory/eventsFor APIs (Swift/Kotlin/RN/Flutter parity)
- StandaloneSherpa: VAD reset/drainSegments per call (Silero state leak fixed);
  stageDirectoryFromCommons no longer creates phantom directory at basename;
  STT/TTS destroy capture-into-local before async free (UAF + heap leak fixes)
- RACErrors.ts canonical RAC error-code module (no more -401 dup)
- Hardcoded /Users/sanchitmonga absolute paths replaced with REPO_ROOT-relative
  in 7 browser specs; RA_RUN_SPEECH_E2E gate added to 4 specs
- LifecycleVLMProvider category guard (no LLM-as-VLM routing)

Major fixes — IDL + codegen
- Kotlin convenience generator: zero_literal_for_required helper fixes
  compile-breaking `Long == 0` comparisons (INT64/FLOAT/DOUBLE/INT32 typed
  zeros); else->"" emitted on all enum accessors; _convenience_common shared
  helpers imported (deletes ~120 LOC of dup in Dart + TS generators)
- TS convenience generator: emits only annotated fields (matches Kotlin/Dart/
  Swift); BOOL rac_required behavior aligned; LangProfile extended with
  DART_PROFILE / TS_PROFILE + int64_wrap callable
- generate_swift_modality_abi.py emits onCancel: closures; swift-modality-abi
  .yaml LoRA modality-level handle key removed
- generate_streams.sh source_proto derivation (njk template parameterized);
  stale attribution to deleted generate_{rn,web}_streams.sh removed
- idl/codegen/tests/ smoke harness: test_options.proto fixture + runner +
  swift/kotlin/dart/ts golden files

Major fixes — Native engines + runtimes
- WhisperKit-CoreML + MetalRT static-register shims (engines/*/rac_static_
  register_*.cpp): plugin registers now actually run at static-init time
- Sherpa VAD configure rebuilds with full VADConfig snapshot (not just
  threshold); TTS synthesize() mutex serializes concurrent calls; STT
  per-language recognizer cache (LRU cap 3) eliminates the rebuild cliff
- llamacpp VLM plugin entry annotated with RAC_API for explicit visibility
- Diffusion CoreML loadable registration path (mirrors llamacpp pattern)

Major fixes — Example apps + build/CI
- Web example: Voice Agent preflight, RAG bootstrap clearer-error wrapping,
  Documents/Vision provider activation, web-sdk-test-suite.md realignment
- iOS example CLAUDE.md scrubbed of deferred WhisperKit/Diffusion sections;
  Package.swift platform floor bumped to .iOS("17.5")/.macOS("14.5")
- Android example System TTS registration documented; ModelBootstrap full
  baseline catalog parity with iOS/Flutter
- RN example: scripts/pod-install.sh runs `bundle install` before
  `bundle exec pod install` (no chicken-and-egg)
- release.yml: publish gate flipped to == 'success', if-no-files-found:
  error, manifest-content assertion; validate_consumer_* jobs added to
  publish.needs with continue-on-error removed; validate_consumer_flutter
  gets actions/checkout + native artifact download
- pr-build.yml + release.yml Flutter version centralized via VERSIONS +
  setup-toolchain (was hardcoded 3.27.0 in 3 places)
- scripts/sync-versions.sh extended to bump proto-ts/package.json,
  @runanywhere/proto-ts caret deps in RN+Web, per-SDK SDKConstants
- scripts/setup-toolchain.sh sources VERSIONS for codegen pins (Wire,
  swift-protobuf, ts-proto) — closes the 3-way version fork
- .syncpackrc.json nitro aligned to 0.33.9 (matches versions.json)
- New centralization gates wired to pr-build.yml (T4 premise now defended)
- Brewfile + requirements-codegen.txt pin codegen toolchain externals
- renovate.json customManagers cover dependencies/versions.json + VERSIONS

Regression tests + guards
- Voice agent pipeline-mutex concurrency stress (4 writers + 2 readers +
  2 resetters under TSAN-friendly conditions)
- HTTP-200-ignore-Range resume fallback test (mid-stream failure + retry)
- LoRA proto-ABI negative-input cases (malformed buffer, NULL pointer,
  missing vtable slot, double-clear idempotency, unknown-id remove)
- Download orchestrator path-traversal rejection (8 malicious paths +
  hand-built plan validation)
- HandleStreamAdapter Swift tests: terminal-event teardown + sync-unregister
- ComponentActor Kotlin tests: atomic loadModel critical section, concurrent
  serialization, destroy-during-load ordering
- OkHttpHttpTransport Kotlin resume contract test (Range header + marker)
- check_no_pii_logging.sh CI guard (URL+path INFO logging fingerprint)
- no-llm-stream-adapter regression guard (fs.existsSync)

Verification — all 6 stacks PASS
- C++ Commons: cmake configure + 442/442 build targets + 135/135 ctest +
  clang-format clean + RAC_API exports 1003/1003 covered
- Swift: swift build + 24/24 swift test + swiftlint 0 violations + smoke OK
- Kotlin: assemble + 39 jvmTest + 25 testDebugUnitTest + ktlint + detekt +
  Android example assembleDebug
- Flutter: dart analyze (0 err/warn) + 48/48 flutter test + centralization
  gate + example pub get + analyze
- React Native: typecheck across 4 packages + lint + nitrogen current +
  example typecheck
- Web SDK: npm typecheck + lint + 48/48 Vitest + vite build + WASM rebuilt
  with the 7 new tool-calling session exports embedded + example vite build
- IDL: toolchain check + regen idempotent + convenience generator goldens
  match across swift/kotlin/dart/ts + all 3 centralization gates + PII gate
  + deprecated-surface gate

WASM artifacts
- racommons-llamacpp.{js,wasm} rebuilt; new exports: _rac_tool_calling_session
  _{create,step_with_result,destroy,cancel}_proto + _rac_tool_calling_run_loop
  _{with_handle,cancel,proto} (7 new symbols total)
- wasm/CMakeLists.txt POST_BUILD made tolerant of Emscripten 5.x bundled-
  worker output (no separate .worker.js)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… improved dependency management

- Updated the label for local workspace packages in .syncpackrc.json to clarify the handling of @runanywhere/proto-ts.
- Added new pinned dependencies for @runanywhere/proto-ts, react-native, react, @eslint/js, and tsd to ensure consistent versioning across SDKs.
- Adjusted the default and exact range labels in .syncpackrc.json to better reflect dependency management strategies.
- Enhanced the descriptions in versions.json for react-native-nitro-modules and nitrogen to clarify their interdependencies and versioning strategy.

These changes aim to streamline dependency management and improve clarity for future contributors.
Updates stale `CLAUDE.md` references in source/docs/config to point at
the renamed `AGENTS.md` files. Follow-up to the bulk rename commit that
moved each CLAUDE.md to AGENTS.md across the monorepo.

Per-file changes:
- .gitleaks.toml: allowlist regex `CLAUDE\.md$` → `AGENTS\.md$`
- Package.swift / sdk/runanywhere-swift/Package.swift: comment refs
- sdk/runanywhere-{commons,swift}/ARCHITECTURE.md: doc cross-refs
- sdk/runanywhere-commons/src/core/internal/platform_compat.h
- sdk/runanywhere-swift/Sources/.../{CppBridge*,HandleStreamAdapter,
  RunAnywhere+Solutions}.swift: NSLock policy comments
- sdk/runanywhere-flutter/packages/runanywhere_genie/lib/genie.dart
- scripts/validation/check_gradle_centralization.sh
- 4 AGENTS.md title lines (commons, web sdk, flutter sdk, web example)

Vendored CLAUDE.md files in build-*/ and SPM checkouts are left
untouched (not project-owned).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…restore

Recovers RN ProtoWire/Hermes proto helpers, Metro bufbuild wiring, Flutter
registry fixes, iOS xcframework packaging flags, web lane Playwright scripts,
and pod-install Xcode settings that were discarded by git restore.

Co-authored-by: Cursor <cursoragent@cursor.com>
Unblock Flutter lanes, harden commons for RAG-off builds and WASM tar.gz extraction, and fix Web/RN model-load and HTTP paths identified by the 20260520 matrix run.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ate, device noise)

- Commons download_orchestrator: idempotent rac_download_start_proto rejects duplicate
  starts for an in-flight model and returns the existing task instead of cancelling
  the first one (CLUSTER-02 / SWIFT-IOS-001 double-tap-Get bug).
- Commons device manager: downgrade noisy LOGE to LOGD when callbacks bind after
  the first register_if_needed call — Web SDK init order makes this benign
  (WEB-DEVICE-CB-NOISE-001).
- Web RunAnywhere.hydrateModelRegistry: emit models.hydrated event on every
  patch (WEB-OPFS-HYDRATE-UI-001), and reconcile registry when OPFS bytes are
  missing so re-download is triggered instead of no-op (WEB-REDOWNLOAD-WAIT-001).

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants