Adding New Runtimes¶
Step-by-step guide to adding a new runtime adapter.
The Open/Closed Rule¶
Adding a new runtime = one new adapter class + one entry-point line. No core edits.
Step 1: Create the Adapter¶
# src/modeldock/adapters/runtimes/lmstudio.py
from __future__ import annotations
from modeldock.adapters.runtimes.base import BaseRuntime
from modeldock.domain.model import ModelRef, RuntimeBackend
from modeldock.ports.progress import ProgressPort
class LMStudioRuntime(BaseRuntime):
backend = RuntimeBackend.LMSTUDIO
def is_available(self) -> bool:
"""Check if LM Studio is installed and running."""
...
def list_installed(self) -> list[ModelRef]:
"""Query LM Studio for installed models."""
...
def is_installed(self, ref: ModelRef) -> bool:
"""Check if a specific model is installed."""
...
def pull(self, ref: ModelRef, progress: ProgressPort) -> PullResult:
"""Download/install a model."""
...
def remove(self, ref: ModelRef) -> None:
"""Uninstall a model."""
...
def get_model_client(self, ref: ModelRef) -> ModelClient:
"""Return a ready-to-use client."""
...
def default_tag_for(self, spec: ModelSpec) -> str:
"""Resolve the default variant tag."""
...
Step 2: Register via Entry Point¶
In pyproject.toml:
[project.entry-points."modeldock.runtimes"]
lmstudio = "modeldock.adapters.runtimes.lmstudio:LMStudioRuntime"
Step 3: Add Port-Contract Tests¶
Extend the shared test suite parameterized over all adapters:
# tests/unit/test_runtime_contract.py
@pytest.mark.parametrize("runtime", [OllamaRuntime(), LMStudioRuntime()])
def test_list_installed(runtime):
"""All runtimes must return a list from list_installed."""
result = runtime.list_installed()
assert isinstance(result, list)
Ensure they pass:
Step 4: Document Backend-Specific Notes¶
Create docs/sdk/<runtime>.md with:
- Installation requirements
- Host/connection details
- Capability differences
- Any limitations
Step 5: Do NOT Touch Core/CLI/API¶
The extension is self-contained. Do not modify:
modeldock/core/modeldock/cli/modeldock/__init__.py
Step 6 (optional): Add a Live Catalog Provider¶
If the runtime has its own online model catalog (or one it borrows, like LM Studio and llama.cpp both borrowing the Hugging Face Hub), you can wire it into md.search()/md.list()/md.recommend() the same way — one more entry-point line, no core edits:
[project.entry-points."modeldock.catalog_providers"]
lmstudio = "modeldock_lmstudio.catalog:build_catalog"
The entry point resolves to a callable (cache_dir: Path) -> RegistryPort — a plain function, or a class whose constructor takes only cache_dir:
# modeldock_lmstudio/catalog.py
from pathlib import Path
from modeldock.ports.registry import RegistryPort
def build_catalog(cache_dir: Path) -> RegistryPort:
"""Return a RegistryPort backed by this runtime's own catalog."""
return MyLiveCatalog(cache_dir)
ModelManager._resolve_backend_catalog resolves it through CatalogProviderRegistry (adapters/registry/catalog_registry.py) and merges it with the general catalog via CompositeRegistry, so results surface alongside the shared Ollama-named catalog rather than replacing it. Without a catalog provider, the runtime's models_for_category/ models_for_capability (Step 1) still work for install_category() — this step only affects general discovery. See Architecture.md §9/§14 and adapters/registry/huggingface_catalog.py for a real example.
Extension Checklist¶
- Create
modeldock/adapters/runtimes/<name>.pyimplementingRuntimePort - Subclass
BaseRuntimefor shared logic - Add entry point in
pyproject.toml - Add/extend port-contract tests
- Document backend-specific notes
- (Optional) Add a
modeldock.catalog_providersentry point for live discovery - Do NOT touch
core/,cli/, or public API
Next Steps¶
- Runtime Adapters — design details
- Port Interfaces — the contract