Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5ad4eba
feat(gap01-phase1): IDL + codegen infrastructure
sanchitmonga22 Apr 21, 2026
68265d4
feat(gap01-phase2): Swift rollout — consume generated enums
sanchitmonga22 Apr 21, 2026
6a34618
feat(gap01-phase3): Kotlin rollout — one AudioFormat, one SDKEnvironment
sanchitmonga22 Apr 21, 2026
db897b8
feat(gap01-phase4): Dart rollout — proto bridges on every enum
sanchitmonga22 Apr 21, 2026
7566810
feat(gap01-phase5): TS rollout — proto bridges on RN + Web enums
sanchitmonga22 Apr 21, 2026
f506d64
feat(gap01-phase6): VoiceEvent handoff to GAP 09
sanchitmonga22 Apr 21, 2026
5ce9048
docs(gap01-final-gate): Success Criteria verification report
sanchitmonga22 Apr 21, 2026
e3ad196
feat(gap02-phase7): unified engine plugin ABI + registry
sanchitmonga22 Apr 21, 2026
079315e
feat(gap02-phase8): llama.cpp plugin entry points
sanchitmonga22 Apr 21, 2026
6648db3
feat(gap02-phase9): ONNX + whispercpp + whisperkit_coreml + metalrt e…
sanchitmonga22 Apr 21, 2026
21c13f1
feat(gap02-phase10): plugin registry tests + authoring doc
sanchitmonga22 Apr 21, 2026
3187219
docs(gap02-final-gate): Success Criteria verification report
sanchitmonga22 Apr 21, 2026
c6aa710
feat(gap03-phase1-2-3): dynamic plugin loader + CMake mode split
sanchitmonga22 Apr 21, 2026
7e93d0f
feat(gap03-phase4-5-6): static-macro polish + llama.cpp dual-mode + t…
sanchitmonga22 Apr 21, 2026
d598960
docs(gap03-phase7): authoring guide + final gate report
sanchitmonga22 Apr 21, 2026
f2efc81
feat(gap04-phase8-9-10-11): engine router + ABI v2 metadata extension
sanchitmonga22 Apr 21, 2026
b5a14b3
feat(gap04-phase12): rac_plugin_route C ABI + router tests + final gate
sanchitmonga22 Apr 22, 2026
0a2dba6
docs(wave-b-c-d-e-outline): post-Wave-A roadmap
sanchitmonga22 Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Mark every IDL-generated tree as generated so GitHub's diff viewer collapses
# it by default. Anyone auditing a PR sees only the hand-written changes + the
# set of files that rotated after regen. The CI drift check is the actual
# gatekeeper; this attribute is purely cosmetic.
#
# Paths match the output directories from idl/codegen/generate_*.sh.

# Swift
sdk/runanywhere-swift/Sources/RunAnywhere/Generated/** linguist-generated=true

# Kotlin
sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/generated/** linguist-generated=true

# Dart
sdk/runanywhere-flutter/packages/runanywhere/lib/generated/** linguist-generated=true

# TypeScript (RN + Web)
sdk/runanywhere-react-native/packages/core/src/generated/** linguist-generated=true
sdk/runanywhere-web/packages/core/src/generated/** linguist-generated=true

# Python (future SDK; directory exists once generate_python.sh runs)
sdk/runanywhere-python/src/runanywhere/generated/** linguist-generated=true

# C++
sdk/runanywhere-commons/src/generated/proto/** linguist-generated=true

# Mirror Nitrogen's existing output tree for consistency.
sdk/runanywhere-react-native/packages/core/nitrogen/generated/** linguist-generated=true
sdk/runanywhere-react-native/packages/llamacpp/nitrogen/generated/** linguist-generated=true
sdk/runanywhere-react-native/packages/onnx/nitrogen/generated/** linguist-generated=true
100 changes: 100 additions & 0 deletions .github/workflows/idl-drift-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: IDL drift check

# Runs on every PR and every push to `main`. If any .proto under idl/ is edited
# without regenerating the committed language bindings, or if anyone hand-edits
# a generated file, this job fails with ::error::IDL-generated code is out of
# sync with .proto sources. Fixing the PR is a one-liner locally:
#
# ./idl/codegen/generate_all.sh && git add -A && git commit --amend --no-edit

on:
pull_request:
paths:
- 'idl/**'
- 'sdk/runanywhere-swift/Sources/RunAnywhere/Generated/**'
- 'sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/generated/**'
- 'sdk/runanywhere-flutter/packages/runanywhere/lib/generated/**'
- 'sdk/runanywhere-react-native/packages/core/src/generated/**'
- 'sdk/runanywhere-web/packages/core/src/generated/**'
- 'sdk/runanywhere-commons/src/generated/proto/**'
- 'scripts/setup-toolchain.sh'
- '.github/workflows/idl-drift-check.yml'
push:
branches: [main]
paths:
- 'idl/**'
- 'sdk/runanywhere-swift/Sources/RunAnywhere/Generated/**'
- 'sdk/runanywhere-kotlin/src/commonMain/kotlin/com/runanywhere/sdk/generated/**'
- 'sdk/runanywhere-flutter/packages/runanywhere/lib/generated/**'
- 'sdk/runanywhere-react-native/packages/core/src/generated/**'
- 'sdk/runanywhere-web/packages/core/src/generated/**'
- 'sdk/runanywhere-commons/src/generated/proto/**'
- 'scripts/setup-toolchain.sh'
- '.github/workflows/idl-drift-check.yml'

jobs:
check:
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."

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +37 to +100
9 changes: 9 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ let package = Package(
.package(url: "https://github.com/apple/ml-stable-diffusion.git", from: "1.1.0"),
// WhisperKit for Neural Engine STT
.package(url: "https://github.com/argmaxinc/WhisperKit.git", from: "0.9.0"),
// swift-protobuf for idl/*.proto generated types consumed by
// sdk/runanywhere-swift/Sources/RunAnywhere/Generated/*.pb.swift
// (see v2_gap_specs/GAP_01_IDL_AND_CODEGEN.md for rationale)
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.27.0"),
],
targets: [
// =================================================================
Expand Down Expand Up @@ -156,6 +160,7 @@ let package = Package(
.product(name: "DeviceKit", package: "DeviceKit"),
.product(name: "Sentry", package: "sentry-cocoa"),
.product(name: "StableDiffusion", package: "ml-stable-diffusion"),
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
"CRACommons",
"RACommonsBinary",
],
Expand Down
213 changes: 213 additions & 0 deletions docs/engine_plugin_authoring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# Engine Plugin Authoring Guide

_Closes GAP 02 Phase 10. The definitive "how do I add a new engine to RunAnywhere?" reference._

Use this guide when you want RunAnywhere to route a new primitive (LLM, STT, TTS, VAD, embedding, reranker, VLM, diffusion) through your engine. After Phase 10 of
[`v2_gap_specs/GAP_02_UNIFIED_ENGINE_PLUGIN_ABI.md`](../v2_gap_specs/GAP_02_UNIFIED_ENGINE_PLUGIN_ABI.md) there are **two** registration paths. Most authors should pick the unified path; the legacy path only stays around for binary-compatibility with releases ≤ v0.19.

## Which path should I pick?

```
Are you adding a brand-new engine?
├─ Yes ────────────────────────────────────── Unified path (this guide).
└─ No (you're modifying an existing backend)
├─ Add a NEW primitive to an existing backend?
│ (e.g. add `embed` to ONNX)
│ ────────────────────────────────────── Edit the existing
│ rac_plugin_entry_<name>.cpp.
├─ Fix a bug in existing ops?
│ ────────────────────────────────────── Edit the existing
│ rac_backend_<name>_register.cpp.
│ Both registration paths share
│ the same ops-struct; fixing
│ there fixes both.
└─ Deprecate an engine?
─────────────────────────────────────── Add `on_unload` hook in the
rac_plugin_entry_<name>.cpp
for cleanup, then drop the
rac_plugin_register() call at
consumer sites.
```

## Unified path — 4 steps

### 1. Fill in a `rac_engine_vtable_t`

Reserve a short stable name (e.g. `mlx`). Put the vtable in a new
`src/backends/<name>/rac_plugin_entry_<name>.cpp`:

```cpp
#include "rac/plugin/rac_engine_vtable.h"
#include "rac/plugin/rac_plugin_entry.h"
#include "rac/features/llm/rac_llm_service.h"

extern "C" {
extern const rac_llm_service_ops_t g_mlx_ops; // <- your ops struct

static const rac_engine_vtable_t g_mlx_engine_vtable = {
/* metadata */ {
.abi_version = RAC_PLUGIN_API_VERSION,
.name = "mlx",
.display_name = "Apple MLX",
.engine_version = "0.1.0",
.priority = 95, // higher wins for same primitive
.capability_flags = 0,
.reserved_0 = 0,
.reserved_1 = 0,
},
/* capability_check */ [](){
#if defined(__APPLE__)
return RAC_SUCCESS;
#else
return RAC_ERROR_CAPABILITY_UNSUPPORTED; // silent reject
#endif
},
/* on_unload */ nullptr,

/* llm_ops */ &g_mlx_ops,
/* other slots */ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,

/* reserved_slot_0..9 */
nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr,
};

RAC_PLUGIN_ENTRY_DEF(mlx) {
return &g_mlx_engine_vtable;
}
} // extern "C"
```

Rules:
- `metadata.abi_version` MUST equal `RAC_PLUGIN_API_VERSION` (currently 1).
- `metadata.name` MUST be unique across all registered engines.
- Fill exactly the primitive slots you serve; leave everything else NULL.
- `capability_check` returning non-zero rejects the plugin silently (no error log).

### 2. Declare the entry in a public header

`sdk/runanywhere-commons/include/rac/plugin/rac_plugin_entry_mlx.h`:

```c
#ifndef RAC_PLUGIN_ENTRY_MLX_H
#define RAC_PLUGIN_ENTRY_MLX_H
#include "rac/plugin/rac_plugin_entry.h"
#ifdef __cplusplus
extern "C" {
#endif
RAC_PLUGIN_ENTRY_DECL(mlx);
#ifdef __cplusplus
}
#endif
#endif
```

The install rule already picks it up via `install(DIRECTORY include/)`.

### 3. Hook CMake

In `sdk/runanywhere-commons/src/backends/mlx/CMakeLists.txt`:

```cmake
set(MLX_BACKEND_SOURCES
rac_llm_mlx.cpp
rac_backend_mlx_register.cpp # optional — legacy path
rac_plugin_entry_mlx.cpp # unified path
)
```

### 4. Register at startup

Pick the simplest of:

```cpp
// C++ app or library: uses static-init.
#include "rac/plugin/rac_plugin_entry_mlx.h"
RAC_STATIC_PLUGIN_REGISTER(mlx);
```

```c
// C app or explicit ordering: call manually.
#include "rac/plugin/rac_plugin_entry_mlx.h"
int main(void) {
rac_plugin_register(rac_plugin_entry_mlx());
// ... your code ...
}
```

```c
// Dynamic plugin (dlopen): load then call by symbol name.
void* h = dlopen("libmlx.so", RTLD_NOW);
rac_plugin_entry_fn entry = (rac_plugin_entry_fn)dlsym(h, "rac_plugin_entry_mlx");
rac_plugin_register(entry());
```

## Testing your plugin

```cpp
// test_plugin_entry_mlx.cpp
#include "rac/plugin/rac_plugin_entry_mlx.h"
int main() {
const rac_engine_vtable_t* vt = rac_plugin_entry_mlx();
assert(vt->metadata.abi_version == RAC_PLUGIN_API_VERSION);
assert(vt->llm_ops != nullptr);
rac_plugin_register(vt);
assert(rac_plugin_find(RAC_PRIMITIVE_GENERATE_TEXT) == vt);
rac_plugin_unregister("mlx");
}
```

Hook it into `sdk/runanywhere-commons/tests/CMakeLists.txt` following the
pattern established by `test_plugin_entry_llamacpp` and
`test_plugin_entry_onnx` in Phase 10.

## Priority ladder (as of GAP 02 Phase 9)

| Priority | Name | Primitives served | Platforms |
|----------|-------------------|-----------------------------------|------------|
| 120 | metalrt | LLM + STT + TTS + VLM | Apple |
| 110 | whisperkit_coreml | STT | Apple |
| 100 | llamacpp | LLM (vlm via llamacpp_vlm) | All |
| 100 | llamacpp_vlm | VLM | All |
| 90 | whispercpp | STT | All |
| 80 | onnx | STT + TTS + VAD | All |
| 95 | mlx (example) | LLM | Apple only |

Pick your priority within the existing range. Reserve 0–40 for
experimental / CPU fallback engines, 40–80 for standard CPU
implementations, 80–110 for optimized / hardware-accelerated
implementations, 110+ for Apple-specific hardware paths.

## Bumping the plugin API version

Bump `RAC_PLUGIN_API_VERSION` in
`sdk/runanywhere-commons/include/rac/plugin/rac_plugin_entry.h` when any of:

- `rac_engine_vtable_t` field layout changes (reserved slot promotion, new primitive).
- A new primitive lands in `rac_primitive.h`.
- Any per-domain ops struct (`rac_llm_service_ops_t`, …) grows or shrinks.

Old plugins loaded against a newer host will fail the ABI check and be
rejected with `RAC_ERROR_ABI_VERSION_MISMATCH` — a safe outcome. Do **not**
bump for additive metadata fields (new `capability_flags` bits).

## Relationship to the legacy path

Every existing backend (`llamacpp`, `onnx`, `whispercpp`, `whisperkit_coreml`,
`metalrt`) now exposes BOTH:

- `rac_backend_<name>_register()` — registers via the legacy per-domain
`rac_service_register_provider()` path used by the C ABI + Swift /
Kotlin / Dart bridges pre-GAP-02.
- `rac_plugin_entry_<name>()` — returns a `const rac_engine_vtable_t*` for
the unified registry.

Both paths share the same ops-struct (e.g. `g_llamacpp_ops`); a bug fix in
that struct shows up in both registries automatically. The legacy path will
stay around for two release cycles; GAP 06 deprecates it and GAP 11 deletes
it once every SDK frontend has cut over.
Loading
Loading