Extending Jarvis¶
Jarvis uses a plugin architecture built on abstract Python interfaces (ABCs). Every extension point follows the same pattern:
- Implement an abstract interface (e.g.,
IJarvisCommand,IJarvisAgent) - Place the file in the corresponding package directory
- Done --- the discovery system finds it automatically via Python reflection
There is no registration step, no config file to edit, no decorator to apply. Drop a Python file in the right directory and it is live.
How It Works¶
Under the hood, Jarvis uses Python's pkgutil and importlib to scan package directories at startup. It finds every class that implements the expected interface, instantiates it, validates any required secrets, and registers it. This happens automatically for commands, agents, device managers, device families, and prompt providers.
For STT, TTS, and wake response providers, discovery is manual --- you specify the provider in your node's config file. But the implementation pattern is identical: subclass the ABC, implement the required methods, and you are done.
Extension Points¶
| Extension Point | Interface | Package Directory | Runs On | Discovery |
|---|---|---|---|---|
| Commands | IJarvisCommand |
commands/ |
Node | CommandDiscoveryService |
| Agents | IJarvisAgent |
agents/ |
Node | AgentDiscoveryService |
| Prompt Providers | IJarvisPromptProvider |
app/core/prompt_providers/ |
Command Center | PromptProviderFactory |
| Device Managers | IJarvisDeviceManager |
device_managers/ |
Node | DeviceManagerDiscoveryService |
| Device Protocols | DeviceProtocol |
device_families/ |
Node | DeviceFamilyDiscoveryService |
| STT Providers | IJarvisSpeechToTextProvider |
stt_providers/ |
Node | Manual config |
| TTS Providers | IJarvisTextToSpeechProvider |
tts_providers/ |
Node | Manual config |
| Wake Response Providers | IJarvisWakeResponseProvider |
wake_response_providers/ |
Node | Manual config |
Architecture Diagram¶
The following diagram shows where each extension point runs and how the pieces connect:
graph TB
subgraph Node ["Node (jarvis-node-setup)"]
direction TB
CMD["Commands<br/><small>IJarvisCommand</small>"]
AGT["Agents<br/><small>IJarvisAgent</small>"]
DM["Device Managers<br/><small>IJarvisDeviceManager</small>"]
DF["Device Families<br/><small>DeviceProtocol</small>"]
STT["STT Providers<br/><small>IJarvisSpeechToTextProvider</small>"]
TTS["TTS Providers<br/><small>IJarvisTextToSpeechProvider</small>"]
WR["Wake Responses<br/><small>IJarvisWakeResponseProvider</small>"]
CDS["CommandDiscoveryService"]
ADS["AgentDiscoveryService"]
DMDS["DeviceManagerDiscoveryService"]
DFDS["DeviceFamilyDiscoveryService"]
CDS -->|scans| CMD
ADS -->|scans| AGT
DMDS -->|scans| DM
DFDS -->|scans| DF
end
subgraph CC ["Command Center (jarvis-command-center)"]
direction TB
PP["Prompt Providers<br/><small>IJarvisPromptProvider</small>"]
PPF["PromptProviderFactory"]
PPF -->|scans| PP
end
Node -- "voice/text commands" --> CC
CC -- "parsed intent + args" --> Node
Common Patterns¶
Most extension points share a set of common patterns that make the plugin system consistent and predictable.
Required Secrets¶
Many plugins need API keys or credentials. The required_secrets property declares what a plugin needs, and the discovery system validates availability before registering it:
@property
def required_secrets(self) -> list[str]:
return ["GOVEE_API_KEY"]
def validate_secrets(self) -> bool:
"""Return True if all required secrets are available."""
for secret in self.required_secrets:
if not self.secret_service.get_secret(secret):
return False
return True
If secrets are missing, the plugin is skipped with a log warning rather than crashing the service. This allows optional plugins to coexist with required ones.
The name Property¶
Every plugin has a name property that serves as its unique identifier. This is how the system refers to plugins in logs, configuration, and the command registry:
Graceful Failure¶
All discovery services catch ImportError and other exceptions during scanning. If a plugin file has an unmet dependency (e.g., a pip package not installed), the file is skipped and a warning is logged. This means you can have plugins with optional dependencies without breaking the system.
Thread Safety¶
Discovery services use threading.RLock for thread-safe access to the plugin registry. This is important because the background refresh thread (used by CommandDiscoveryService) can update the registry while other threads are reading it.
Getting Started¶
The fastest way to extend Jarvis is to add a new command. See the Commands guide for a step-by-step walkthrough.
For deeper understanding of how plugins are found and loaded, see the Discovery System documentation.