Creating a New Engine Plugin
Relevant source files
The following files were used as context for generating this wiki page:
An engine in the RunAnywhere architecture acts as an op-table adapter for specific modalities. It implements the rac_engine_vtable_t interface to provide capabilities like LLM, STT, or TTS by wrapping external libraries or internal codebase units. Engines register with the central plugin registry, allowing the C++ commons core to dispatch tasks based on priority and capability.
Sources: engines/AGENTS.md:14-22, core/AGENTS.md:73-77
Architecture and Dispatch Flow
The plugin registry manages engine discovery and selection. Callers in the commons layer use rac_plugin_find(primitive) to find the highest-priority engine that serves a specific primitive. If a specific engine is required, rac_plugin_find_for_engine(primitive, engine_name) pins the selection.
The diagram shows how requests flow from the SDK through the commons layer to the specific engine vtable.
Sources: engines/AGENTS.md:23-27, core/AGENTS.md:73-77
The Engine Contract
To be valid, an engine must provide five core components:
rac_engine_manifest_t: Defines the identity, priority, and supported primitives.rac_engine_vtable_t: Contains function pointers for served primitives; all other slots must be NULL.- Model Lifecycle Implementation: Implements
create,initialize,cleanup, anddestroy. capability_check: A helper function that returns the engine's availability status for the current platform.- Registration Logic: Uses
RAC_PLUGIN_ENTRY_DEFto expose the entry symbol.
Sources: engines/AGENTS.md:104-135
Core Components and Data Structures
Engine Manifest
The manifest describes the engine's capabilities and metadata. The name must reflect the library or identity of the engine, not the modality it serves.
| Field | Description |
|---|---|
name | Snake_case identity (e.g., llamacpp, sherpa). |
primitives[] | List of supported wire values (e.g., LLM=1, STT=2). |
runtimes[] | Declared runtimes if execution depends on a specific device. |
priority | Integer used for dispatch selection (higher wins). |
availability | PUBLIC or PRIVATE designation. |
Sources: engines/AGENTS.md:29-37, engines/AGENTS.md:106-113
The Vtable and Modalities
The rac_engine_vtable_t currently supports 10 active primitive slots. If an engine does not serve a primitive, its corresponding slot must be set to NULL.
| Slot | Modality | Typical Backends |
|---|---|---|
llm_ops | Text Generation | llamacpp, qhexrt, neurt |
stt_ops | Speech-to-Text | sherpa, qhexrt |
tts_ops | Text-to-Speech | sherpa, platform |
vad_ops | Voice Activity Detection | sherpa |
embedding_ops | Vector Embeddings | llamacpp, onnx |
Sources: engines/AGENTS.md:14-22, core/AGENTS.md:106-121
Implementation Details
The 4-File Skeleton
Every engine implementation follows a standard file layout to ensure compatibility with the build system and registry.
| File | Purpose |
|---|---|
rac_plugin_entry_<name>.cpp | The source of truth containing manifest and vtable definitions. |
rac_backend_<name>_register.cpp | Provides idempotent registration/unregistration for dynamic-link hosts. |
rac_static_register_<name>.cpp | A static-init shim for platforms like iOS and WASM. |
<name>_backend.cpp | The actual implementation and native-lib glue code. |
Sources: engines/AGENTS.md:145-156
Model Lifecycle
Engines must adhere to a strict lifecycle flow for model management. The config_json provided during create is advisory and should be ignored if keys are unrecognized.
The sequence shows the standard transitions from allocation to destruction required for all engine plugins.
Sources: engines/AGENTS.md:118-129
Build and Registration Modes
Engines support two primary link modes, controlled by the RAC_STATIC_PLUGINS CMake variable.
Static Registration
Used primarily for iOS and WASM. The engine sources are folded directly into rac_commons. Registration occurs via RAC_STATIC_PLUGIN_REGISTER before main() executes. The linker must use -force_load (Apple) or --whole-archive to prevent stripping the unreferenced registrar.
Sources: engines/AGENTS.md:214-222, core/AGENTS.md:80-82
Dynamic Loading
Default for Android, Linux, macOS, and Windows. Engines build as librunanywhere_<name>.so. The host uses dlopen via rac_registry_load_plugin(). On Android, the SDK calls registration through a JNI bridge after System.loadLibrary.
Sources: engines/AGENTS.md:223-228, bindings/kotlin/AGENTS.md:46-52
Shared Helpers and Macros
The engines/common/ directory provides utilities to reduce boilerplate and handle platform-specific complexities.
rac_engine_unavailable.h: ProvidesRAC_ENGINE_UNAVAILABLE_PLUGINto emit a "not-routable" shell for engines missing private artifacts.rac_engine_jni_bridge.h: Macros likeRAC_DEFINE_ENGINE_JNI_BRIDGEemit the standard JNI quartet for Android.rac_engine_sibling_loader.h: Handles cross-registration of sibling engines in separate.sofiles on Android.RAC_DEFINE_CREATE_ADAPTER: Generates boilerplatecreateadapters for primitives like STT, TTS, and VAD.
Sources: engines/AGENTS.md:158-185, engines/common/rac_engine_unavailable.h:1-10
Summary
Creating a new engine requires implementing the rac_engine_vtable_t and rac_manifest_t according to the ABI v9 specification. Developers must ensure the engine identity remains stable, handle model lifecycle transitions correctly, and provide both static and dynamic registration paths to support the cross-platform nature of the RunAnywhere SDK.