Skip to content

Repository files navigation

s2s-host

Host / plugin-management foundation for the Jarvis ecosystem (speech-to-speech-mobile, s2s-llm, s2s-context, s2s-tools, s2s-agent).

Replaces hardcoded LlamaLanguageModel(...)/SqliteContextEngine(...) construction in host apps with a plugin registry + typed provider/factory model + composition resolver. Neither s2s-agent nor speech-to-speech-mobile depend on this module or know it exists — they consume LanguageModel/ContextEngine/Tools exactly as before.

What this is NOT

  • Not a plugin marketplace, no remote catalog, no payments/ratings.
  • Not a runtime arbitrary-code-installation mechanism — Android/Play Store don't allow that, and this module doesn't pretend otherwise. Every plugin registered here is compiled into the host app (PluginSource.BUILT_IN/BUNDLED).
  • Not sandboxing — PluginSource.EXTERNAL/MCP exist as enum values for a future phase; no discovery, IPC, or isolation mechanism is implemented.
  • Not MCP — same as above, a future Tools provider, not built here.
  • Not another agent — zero orchestration/execution logic; AgentRuntime (in s2s-agent) remains the only execution loop.

Core types

  • PluginDescriptor / PluginType / PluginSource / PluginAvailability — inspectable metadata.
  • PluginProvider<T> — typed factory (PluginProvider<LanguageModel>, etc.), no Any/casts.
  • PluginStateDISCOVERED → CONFIGURED → ENABLED/DISABLED, FAILED on incompatible version, missing permission, unavailable implementation, or a creation error.
  • PluginConfigStoreInMemoryPluginConfigStore (tests) / SharedPreferencesPluginConfigStore (persists across restarts).
  • PluginRegistry — register/unregister/find/list/enable/disable/select, idempotent registration.
  • HostComposer — resolves the currently enabled+selected plugins into a ComposedCapabilities(languageModel, contextEngine, tools).

Five distinct plugin concepts

PluginRegistry deliberately keeps these separate — a plugin can be any combination of them, and conflating any two produces wrong UI/composition behavior:

Concept How to check Independent of
Known registry.find(id) != null everything else
Available descriptor.availability != UNAVAILABLE enabled/selected — an unavailable plugin can still be "enabled" in config, it just can never compose
Enabled registry.isEnabled(id) selection — two plugins of the same PluginType can both be enabled with only one selected
Selected registry.getSelected(type) == id enabled — selecting a disabled plugin doesn't implicitly enable it
Composable right now registry.canCompose(id, grantedPermissions) doesn't instantiate anything — checks known+available+enabled+permissions without calling PluginProvider.create()

PluginRegistry.state(id) folds all of these into one PluginState for simple UI display; canCompose() is the non-instantiating check to use when you need a yes/no answer without side effects (e.g. before showing an "Enable" toggle as available).

Usage

val registry = PluginRegistry(SharedPreferencesPluginConfigStore(context))

registry.register(
    PluginDescriptor("llama-cpp", PluginType.LANGUAGE_MODEL, "Llama.cpp", "0.2.0"),
    PluginProvider<LanguageModel> { config -> LlamaLanguageModel(LlamaConfig(), config["modelPath"]!!) },
)
registry.setEnabled("llama-cpp", true)
registry.select("llama-cpp", PluginType.LANGUAGE_MODEL)
registry.setConfig("llama-cpp", PluginConfig(mapOf("modelPath" to "/data/model.gguf")))

// ... register CONTEXT_ENGINE and TOOLS plugins the same way ...

val composed = HostComposer(registry).resolve().getOrThrow()
val engine = S2SEngine(context, config, languageModel = composed.languageModel, history = composed.contextEngine, sessionId = sessionId)

Switching implementations later needs no code change downstream — only registry.select("remote", PluginType.LANGUAGE_MODEL) followed by a fresh HostComposer(registry).resolve() call.

Known limitations

  • Disabling the currently-active plugin does not reach into an already-running AgentRuntime/S2SEngine — an in-flight instance keeps running against whatever it was constructed with; only the next resolve() call sees the change. No live-swap mechanism exists.
  • PluginRegistry.select/setEnabled/setConfig all require()-throw on an unknown pluginId rather than returning a Result — acceptable for a host's own programming error, not meant to handle a plugin registered by someone else at a different point in the app's lifecycle.
  • No signature/identity verification for PluginSource.EXTERNAL — not needed yet since nothing external is discoverable in this phase, but a real implementation must add it before that source type does anything.

Published to JitPack. Note: due to an indexing quirk on first publish, s2s-host resolves under com.github.Loyality7:s2s-host (capitalized) for some earlier versions — check which coordinate actually resolves for the version you're pinning (https://jitpack.io/com/github/<casing>/s2s-host/<version>/) before depending on it.

About

Plugin management / host composition foundation for the Jarvis ecosystem

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages