Plugins
Plugin Types¶
Plugins come in two flavors:
Service plugins expose named services that apps access explicitly via self.require("name") or self.service("name").
Enhancer plugins dynamically inject providers into capabilities at startup. If the plugin is absent or offline, the capability falls back to the next provider. No app code changes needed.
This is the Graceful Enhancement pattern: capabilities degrade silently when a plugin goes offline. Your search still works without the search enhancer — it just loses graph-aware features.
Active Plugins¶
| Plugin | Type | What It Does |
|---|---|---|
| health | Service | Heartbeat monitoring, capability probes, GPU VRAM tracking |
| ollama | Enhancer (think) | Local LLM via Ollama — models, generate, embeddings |
| comfyui | Enhancer (draw) | GPU image/video/music generation with 8 style presets |
| voice-api | Enhancer (speak+listen) | TTS (F5-TTS) and STT (Whisper) |
| applio | Service | AI voice conversion |
| notifications | Service | Vault + Telegram push notifications |
| telegram | Service | Two-way Telegram bot — commands in, notifications out |
| blender | Service | 3D cable routing via Blender automation |
How Enhancers Work¶
When the Ollama plugin starts, it registers itself as a provider for the think capability at priority 0 (highest). The capability's provider chain becomes:
think: ollama → openai → claude-cli → human
If Ollama crashes mid-request, the capability automatically falls back to OpenAI. Apps never see the failure — they just get a response.
# This code never changes regardless of which providers are available
result = await self.think("Analyze this code", domain="code")
Plugin Configuration¶
Plugins are configured in emptyos.toml:
[plugins.ollama]
url = "http://localhost:11434"
[plugins.comfyui]
url = "http://127.0.0.1:8188"
launcher = "D:/ComfyUI/run.bat"
[plugins.telegram]
token = "bot-token-here"
allowed_users = [123456789]
Plugins with a launcher config can auto-start their external service when EmptyOS boots.
Auto-Start Pattern¶
External services use embedded runtimes with headless launch:
# Plugin auto_start() — no visible windows
subprocess.Popen(
[python_exe, "-s", main_py, "--flags"],
cwd=launcher_dir,
creationflags=subprocess.CREATE_NO_WINDOW,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
The health plugin polls each service endpoint until ready, with a 60-second timeout.
Writing a Plugin¶
class MyPlugin(BasePlugin):
async def on_start(self):
# Register as a service
self.register_service("my_service", self)
# Or inject into a capability (enhancer pattern)
self.inject_provider("think", MyProvider(), priority=0)
async def on_stop(self):
pass
Place in plugins/my-plugin/ with a plugin.py and manifest. The plugin loader discovers it automatically.