Resolve adapters by what is mounted, and say why each kind is not loaded

runtime.py resolved adapters at 7 call sites and only ever looked in the
adapter library, so an adapter attached to a cartridge (cartridge.py:230
copies it into <cartridge>/adapters/{kind}/) was never loaded by anything.
Resolution now lives in adapters.py: it reads the mounted cartridge set,
prefers a mounted cartridge's copy over the library, and answers every one
of the six kinds with a state the facts support — loaded, available,
deferred (base_model mismatch or missing weights, both models named in the
reason), missing — plus a shadowed row for each adapter that lost the
choice. A mounted cartridge is the scenario selection; neither manifest
carries a scenario field, so none was invented.

Two bugs the new tests caught: the preference sort lost reverse=True, so a
library adapter beat a mounted cartridge's copy, and the load branch had
never been executed by a test. Stub weights now prove peft is asked to
attach, that the returned model is put in eval mode, and that the endpoint
reports loaded until unload clears it.

Faces: GET /api/runtime/adapters, `ely-eye adapters`, and a log line naming
the reason whenever a generation runs without an adapter.

Gates: 222 passed; ruff clean; mypy cold cache on the 7 touched files gives
the same 2 errors as HEAD's runtime.py, and pyright gives the same 2 as
HEAD — both are qwen_vl_utils typing artifacts (process_vision_info returns
2 or 3 values depending on return_video_kwargs; the product takes 2).
This commit is contained in:
ZacharyZhang-NY committed 2026-10-07 23:09:28 -04:00
1 parent 9f0f7993e5
commit 2fbb4af96b
8 files changed
+585 -86

No files matched your search

+9
View File
@@ -6,6 +6,7 @@ from pathlib import Path
import typer
import uvicorn
from .adapters import plan as adapter_plan
from .api import create_app
from .benchmarks import (
benchmark_prediction_report,
@@ -475,5 +476,13 @@ def attach_adapter(cartridge_id: str, adapter_dir: Path) -> None:
typer.echo(manifest.model_dump_json(indent=2))
@app.command("adapters")
def adapter_status() -> None:
"""Which adapter the runtime would load, and why each other kind would not."""
settings = get_settings()
report = adapter_plan(settings, Database(settings))
typer.echo(json_dump(report.model_dump(mode="json")))
if __name__ == "__main__":
app()