|
| 1 | +# Post-Wave-A roadmap |
| 2 | + |
| 3 | +_Last updated: at the close of Wave A (GAP 03 + GAP 04), branch `feat/v2-gap03-gap04`._ |
| 4 | + |
| 5 | +The seven-gap migration on `runanywhere-sdks-main` is structured as five waves. Two are done; four remain. This doc captures the **scope, dependency, and rationale** for each remaining wave at a level deep enough to start a detailed plan; full per-phase plans get written one wave at a time as the prior wave merges. |
| 6 | + |
| 7 | +| Wave | Gaps | Status | Estimate (single eng) | |
| 8 | +|------|---------------------------------------|---------------|-----------------------| |
| 9 | +| Done | GAP 01 + GAP 02 | merged | (history) | |
| 10 | +| A | GAP 03 + GAP 04 | this branch | ~4–6 wk | |
| 11 | +| **B**| **GAP 07 + GAP 06** | next | ~2–4 wk | |
| 12 | +| **C**| **GAP 09** | after B | ~3–4 wk | |
| 13 | +| **D**| **GAP 08** | after C | ~6–10 wk (parallel) | |
| 14 | +| **E**| **GAP 05** (optional) | deferred | ~6–8 wk | |
| 15 | + |
| 16 | +```mermaid |
| 17 | +flowchart LR |
| 18 | + A[Wave A done] --> B[Wave B: GAP 07 + 06] |
| 19 | + B --> C[Wave C: GAP 09] |
| 20 | + C --> D[Wave D: GAP 08] |
| 21 | + A --> C |
| 22 | + A --> E[Wave E: GAP 05 optional] |
| 23 | +``` |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +## Wave B — Build-system + engines/ reorg (~2–4 weeks) |
| 28 | + |
| 29 | +**Goal.** Collapse the 11 `build-*.sh` scripts + scattered `CMakeLists.txt` into a single root `CMakeLists.txt` + `CMakePresets.json` (GAP 07), then move every backend out of `sdk/runanywhere-commons/src/backends/` into a top-level `engines/` directory (GAP 06). Both wholly internal — no public API or behavior change. |
| 30 | + |
| 31 | +### GAP 07 — Single root CMake (~1–2 weeks) |
| 32 | + |
| 33 | +**Source spec:** [v2_gap_specs/GAP_07_SINGLE_ROOT_CMAKE.md](../v2_gap_specs/GAP_07_SINGLE_ROOT_CMAKE.md). |
| 34 | + |
| 35 | +**Expected deliverables:** |
| 36 | +- `runanywhere-sdks-main/CMakeLists.txt` (~180 LOC) — top-level project + subdirectory orchestration; replaces the implicit `sdk/runanywhere-commons/CMakeLists.txt`-as-top. |
| 37 | +- `runanywhere-sdks-main/CMakePresets.json` (~145 LOC) — 9 preset families covering host (Debug / Release / RelWithDebInfo) × platform (macOS / Linux / Android / iOS / WebAssembly). |
| 38 | +- `runanywhere-sdks-main/cmake/platform.cmake` — platform-detection (`RAC_PLATFORM_*` vars) hoisted out of commons. |
| 39 | +- `runanywhere-sdks-main/cmake/plugins.cmake` — `rac_add_engine_plugin(name SOURCES ...)` helper that hides static-vs-shared decision behind one call. Also `rac_force_load(target PLUGINS ...)` wrapping `-Wl,-force_load` / `--whole-archive` / `/INCLUDE:`. |
| 40 | +- `runanywhere-sdks-main/cmake/sanitizers.cmake` — `RAC_SANITIZER=asan|ubsan|tsan` switch. |
| 41 | +- `runanywhere-sdks-main/cmake/protobuf.cmake` — wraps `find_package(Protobuf)` + the `rac_idl` build (currently lives inside `idl/CMakeLists.txt`). |
| 42 | +- `scripts/build-core-android.sh`, `scripts/build-core-xcframework.sh`, `scripts/build-core-wasm.sh` — three new scripts collapsed from the 11 existing per-SDK ones; each ~150 LOC, all wrapping `cmake --preset` + artifact-copy. |
| 43 | +- Slim down `.github/workflows/pr-build.yml` from 601 lines / 17 jobs to ~250 lines once steps become `cmake --preset … && cmake --build --preset … && ctest --preset …`. |
| 44 | + |
| 45 | +**Effort estimate (from spec):** 1–2 weeks (5–10 engineer-days), 1 engineer. |
| 46 | + |
| 47 | +**Blockers / dependencies:** |
| 48 | +- Independent of every prior gap. Touches only build configuration; no runtime code. |
| 49 | +- Must land BEFORE GAP 06 because GAP 06 uses the new `cmake/plugins.cmake` `rac_add_engine_plugin()` helper. |
| 50 | + |
| 51 | +**Likely todo decomposition (~6 todos when fully planned):** |
| 52 | +1. Root `CMakeLists.txt` + project declaration + subdirectory routing. |
| 53 | +2. `CMakePresets.json` — host + cross-compile families. |
| 54 | +3. Four shared `cmake/*.cmake` helpers. |
| 55 | +4. Three `scripts/build-core-*.sh` wrappers. |
| 56 | +5. Slim `pr-build.yml` to use the presets. |
| 57 | +6. Final gate: collapse the per-SDK `gradle.properties` + `Package.swift` build hooks to call the new scripts; verify `ctest --preset all` passes on a hosted runner. |
| 58 | + |
| 59 | +### GAP 06 — engines/ top-level reorg (~1–2 weeks) |
| 60 | + |
| 61 | +**Source spec:** [v2_gap_specs/GAP_06_ENGINES_TOPLEVEL_REORG.md](../v2_gap_specs/GAP_06_ENGINES_TOPLEVEL_REORG.md). |
| 62 | + |
| 63 | +**Expected deliverables:** |
| 64 | +- New top-level `engines/` directory. |
| 65 | +- `git mv sdk/runanywhere-commons/src/backends/{llamacpp,onnx,whispercpp,whisperkit_coreml,metalrt} engines/<name>/` for each. |
| 66 | +- Each `engines/<name>/CMakeLists.txt` becomes a one-liner: |
| 67 | + ```cmake |
| 68 | + rac_add_engine_plugin(llamacpp |
| 69 | + SOURCES llamacpp_backend.cpp rac_llm_llamacpp.cpp ... |
| 70 | + RUNTIMES CPU METAL CUDA # populates the metadata array we wrote in GAP 04 |
| 71 | + ) |
| 72 | + ``` |
| 73 | +- Public engine headers stay where they are (`include/rac/backends/`) so frontend SDKs see no API change. |
| 74 | +- `tools/plugin-loader-smoke/main.cpp` — tiny CLI that dlopen-loads every `engines/<name>/librunanywhere_<name>.so` to prove the reorg didn't break anything. |
| 75 | + |
| 76 | +**Effort estimate (from spec):** 1–2 weeks (5–9 engineer-days). |
| 77 | + |
| 78 | +**Blockers / dependencies:** |
| 79 | +- Requires GAP 02 + GAP 07 (`cmake/plugins.cmake`). |
| 80 | +- Required by future third-party engine ecosystem work. |
| 81 | + |
| 82 | +**Likely todo decomposition (~5 todos when fully planned):** |
| 83 | +1. `git mv` for each of the 5 backends; rewrite path references (`#include` paths in non-engine TUs that referenced `src/backends/*`). |
| 84 | +2. Replace each engine's CMakeLists.txt with the `rac_add_engine_plugin()` one-liner. |
| 85 | +3. Drop the `add_subdirectory(src/backends/...)` block from `sdk/runanywhere-commons/CMakeLists.txt`; replace with `add_subdirectory(${CMAKE_SOURCE_DIR}/engines/...)`. |
| 86 | +4. `tools/plugin-loader-smoke/` smoke binary. |
| 87 | +5. Final gate: every CI matrix job (Linux dlopen, iOS static-link, Android static-link) green; `librunanywhere_*.so` filenames unchanged from GAP 03. |
| 88 | + |
| 89 | +--- |
| 90 | + |
| 91 | +## Wave C — Streaming consistency (~3–4 weeks) |
| 92 | + |
| 93 | +**Goal.** Replace the 6 hand-written streaming implementations across Swift / Kotlin / Dart / RN / Web with codegen'd idiomatic streaming types. Built on top of `idl/voice_events.proto` already shipped in GAP 01. |
| 94 | + |
| 95 | +**Source spec:** [v2_gap_specs/GAP_09_STREAMING_CONSISTENCY.md](../v2_gap_specs/GAP_09_STREAMING_CONSISTENCY.md). |
| 96 | + |
| 97 | +**Expected deliverables:** |
| 98 | +- `idl/voice_agent_service.proto` — gRPC-style service definition (single `stream` rpc returning `VoiceEvent`). |
| 99 | +- Swift: `Sources/RunAnywhere/Generated/voice_agent_service.grpc.swift` (`AsyncStream<VoiceEvent>` from `grpc-swift`). |
| 100 | +- Kotlin: `commonMain/.../generated/voice_agent_service.grpc.kt` (`Flow<VoiceEvent>` from `grpc-kotlin`). |
| 101 | +- Dart: `lib/generated/voice_agent_service.pbgrpc.dart` (`Stream<VoiceEvent>` from `grpc-dart`). |
| 102 | +- TS (RN + Web): hand-written template emitting `AsyncIterable<VoiceEvent>` (no official gRPC streaming generator); ~200 LOC of template wired into `idl/codegen/generate_ts.sh`. |
| 103 | +- Per-SDK adapter (~100–200 LOC each) wiring the generated client stub to the in-process C callback (no actual gRPC transport — just shared types + iteration semantics). |
| 104 | +- Delete ≥1,500 LOC of hand-written `VoiceSessionEvent` / `LiveTranscriptionSession` / `tokenQueue` plumbing. |
| 105 | +- `idl/codegen/check-drift.sh` extended to gate the new generated files alongside the GAP 01 ones. |
| 106 | + |
| 107 | +**Effort estimate (from spec):** 3–4 weeks single engineer; 4–5 weeks if the per-SDK adapters parallelize. |
| 108 | + |
| 109 | +**Blockers / dependencies:** |
| 110 | +- **Requires GAP 01** (the IDL + codegen toolchain — already done). |
| 111 | +- Benefits from GAP 08 because the deletion sweep there removes the hand-written event types this gap replaces. |
| 112 | +- Independent of GAP 02–07. |
| 113 | + |
| 114 | +**Likely todo decomposition (~7 todos when fully planned):** |
| 115 | +1. Add `idl/voice_agent_service.proto` + extend codegen scripts to emit gRPC stubs. |
| 116 | +2. Per-SDK adapter (Swift) — wire C callback → `AsyncStream`. |
| 117 | +3. Per-SDK adapter (Kotlin) — wire C callback → `Flow`. |
| 118 | +4. Per-SDK adapter (Dart) — wire C callback → `Stream`. |
| 119 | +5. Per-SDK adapter (TS RN + Web) — `AsyncIterable` template + emitter. |
| 120 | +6. Delete hand-written `VoiceSessionEvent` / `tokenQueue` types in each SDK; verify sample apps build unchanged. |
| 121 | +7. Final gate: 5 SDKs use generated streaming types; CI drift gate green; `wc -l` confirms ≥1,500 LOC deletion. |
| 122 | + |
| 123 | +--- |
| 124 | + |
| 125 | +## Wave D — Frontend deletion sweep (~6–10 weeks parallel) |
| 126 | + |
| 127 | +**Goal.** Delete ~5,100 LOC of duplicated business logic across Swift / Kotlin / Dart / RN / Web that re-implements C APIs already exposed by `runanywhere-commons`. |
| 128 | + |
| 129 | +**Source spec:** [v2_gap_specs/GAP_08_FRONTEND_LOGIC_DUPLICATION.md](../v2_gap_specs/GAP_08_FRONTEND_LOGIC_DUPLICATION.md). |
| 130 | + |
| 131 | +**Expected deliverables:** |
| 132 | +- Map every Swift / Kotlin / Dart / RN / Web hand-written orchestration file to its existing C API counterpart (`rac_voice_agent_*`, `rac_auth_*`, `rac_download_*`, `rac_http_execute`). |
| 133 | +- Phase-by-phase deletion (per the spec's 6 phases): voice (3–4 wk), auth (2–3 wk), download (2–3 wk), HTTP (1–2 wk), error handling (1 wk), then cleanup of dead `external fun` declarations. |
| 134 | +- Behavioral fixes folded in (e.g. Kotlin's 5-min token refresh window → C's 60-sec window). |
| 135 | +- Each per-SDK phase ends with sample-app smoke runs to prove parity. |
| 136 | + |
| 137 | +**Effort estimate (from spec):** 6–10 weeks wall-clock with 2–3 engineers in parallel; 12–18 weeks single engineer. |
| 138 | + |
| 139 | +**Blockers / dependencies:** |
| 140 | +- Requires the C ABI surface to be feature-complete + behaviorally correct (post-GAP-09 voice work especially). |
| 141 | +- Independent of GAP 02–07 (those make the engine layer cleaner; GAP 08 is about the frontend layer). |
| 142 | + |
| 143 | +**Likely todo decomposition (~12 todos when fully planned, one per [SDK × domain] cell):** |
| 144 | +1. Voice: Swift, Kotlin, Dart, RN, Web (5 todos). |
| 145 | +2. Auth: Kotlin (the worst offender), Dart (2 todos). |
| 146 | +3. Download: Kotlin, Dart (2 todos). |
| 147 | +4. HTTP: Kotlin, Dart, RN (3 todos). |
| 148 | +5. Final gate: `wc -l` of frontend src dirs shows ≥5,100 LOC deletion; sample apps green; behavioral parity tests pass. |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +## Wave E — DAG runtime primitives (optional, ~6–8 weeks) |
| 153 | + |
| 154 | +**Goal.** Land the `StreamEdge<T>`, `GraphScheduler`, `PipelineNode`, `CancelToken`, `RingBuffer<T>`, `MemoryPool` primitives the v2 branch ships in `core/Core/Graph/`. |
| 155 | + |
| 156 | +**Source spec:** [v2_gap_specs/GAP_05_DAG_RUNTIME.md](../v2_gap_specs/GAP_05_DAG_RUNTIME.md). |
| 157 | + |
| 158 | +**Why optional.** The spec itself flags this as deferrable: today's `voice_agent.cpp` is a single-threaded mutex-guarded orchestrator that works fine without DAG primitives. v2's own `voice_pipeline.cpp` does NOT use `GraphScheduler` either — it spawns six named worker threads manually. The primitives only earn their keep when a SECOND pipeline (e.g. multi-modal RAG, computer-use agent) needs to share scheduling logic with voice. |
| 159 | + |
| 160 | +**When to do this:** Only after committing to ship a second pipeline that would otherwise duplicate voice's threading code. |
| 161 | + |
| 162 | +**Likely todo decomposition (~8 todos when fully planned):** |
| 163 | +1. `StreamEdge<T>` (typed bounded queue + 3 overflow policies). |
| 164 | +2. `CancelToken` (hierarchical cancellation). |
| 165 | +3. `RingBuffer<T>` (lock-free single-producer/single-consumer). |
| 166 | +4. `MemoryPool` (per-node arena allocator). |
| 167 | +5. `PipelineNode` (one-thread-per-node base class). |
| 168 | +6. `GraphScheduler` (DAG topological sort + worker pool). |
| 169 | +7. Refactor `voice_agent.cpp` to use the primitives (proves they earn their keep). |
| 170 | +8. Final gate: no behavior change in voice agent; CPU + memory profile within 5% of baseline; second pipeline (whichever motivates the work) builds on the primitives. |
| 171 | + |
| 172 | +--- |
| 173 | + |
| 174 | +## Cross-wave constraints |
| 175 | + |
| 176 | +- Every wave preserves backwards compatibility: legacy `rac_service_create` + `rac_service_register_provider` continue to work through Waves B–D. Removal would be a separate "GAP 11" cleanup that runs after every SDK has cut over (post-Wave-D). |
| 177 | +- ABI version bumps are cumulative: GAP 02 set `RAC_PLUGIN_API_VERSION=1`, GAP 04 (in this wave A) bumped to `2`. Any future field added to `rac_engine_metadata_t` or `rac_engine_vtable_t` bumps to `3` and rejects all v2 plugins. |
| 178 | +- The CI drift-check (GAP 01) gates all generated code; every wave that touches `idl/*.proto` (Wave C) must regenerate cleanly. |
0 commit comments