Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions backend/druks/harnesses/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,9 @@ class Harness(ABC):
name: str
# Harness identity + shipped config, the seed source for the per-harness
# ``HarnessSettings`` row the operator then tunes. Subclasses set provider,
# model_prefixes, models and default_model; effort/timeout default to the
# shipped values.
# models and default_model; effort/timeout default to the shipped values.
provider: ClassVar[str]
# The model-name namespaces this harness runs — a model routes to the harness
# that owns its namespace, so a new model in a known one runs with no release.
model_prefixes: ClassVar[tuple[str, ...]]
# Suggested models for the settings picker and the ``default_model`` seed —
# advisory: any string in a matching namespace runs.
# Suggested models for the settings picker and the ``default_model`` seed.
models: ClassVar[tuple[str, ...]]
default_model: ClassVar[str]
# The provider's model-list endpoint the picker refresh fetches.
Expand All @@ -87,12 +82,6 @@ class Harness(ABC):
_TOKEN_URL: str
_CLIENT_ID: str

@classmethod
def has_model(cls, model: str) -> bool:
"""Whether ``model`` runs on this harness — matched by name namespace, so
a new model in a known namespace routes with no release."""
return model == cls.name or model.startswith(cls.model_prefixes)

def __init__(
self,
*,
Expand Down
1 change: 0 additions & 1 deletion backend/druks/harnesses/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ class ClaudeHarness(Harness):
# (``--output-format stream-json``), so the transcript is the stdout.
name = "claude"
provider = "anthropic"
model_prefixes = ("claude-",)
models = ("claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5")
default_model = "claude-opus-4-7"
model_discovery_url = "https://api.anthropic.com/v1/models?limit=100"
Expand Down
1 change: 0 additions & 1 deletion backend/druks/harnesses/codex.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,6 @@ class CodexHarness(Harness):
# symmetric with claude. session.jsonl is still snapshotted for cost.
name = "codex"
provider = "openai"
model_prefixes = ("gpt-", "o1", "o3", "o4")
models = ("gpt-5.5",)
default_model = "gpt-5.5"
# ``client_version`` is required and lower-bounds the list (the server
Expand Down
16 changes: 10 additions & 6 deletions backend/druks/harnesses/registry.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from druks.user_settings.models import HarnessSettings

from .base import Harness
from .exceptions import HarnessError

Expand All @@ -17,10 +19,12 @@ def get_harness(name: str) -> type[Harness] | None:


def get_harness_for_model(model: str) -> type[Harness]:
"""The harness that runs ``model``, matched by name namespace — a model in a
known namespace routes even if it postdates this release. A miss means no
installed harness owns its namespace."""
for harness in get_harnesses():
if harness.has_model(model):
return harness
"""The harness that runs ``model`` from its fetched-or-shipped model list.

A miss raises loudly; namespace-shaped models do not route until a fetch
stores them on the harness settings row.
"""
for row in HarnessSettings.all():
if model == row.name or any(m["id"] == model for m in row.allowed_models):
return row.harness
raise HarnessError(f"no harness runs model {model!r}")
4 changes: 2 additions & 2 deletions backend/druks/harnesses/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ def read_result_json(output_path: Path, *, name: str) -> dict[str, Any]:
except FileNotFoundError as error:
raise HarnessError(f"{name} did not write result JSON.") from error

if text.startswith("```"):
if text[:3] == "```":
lines = text.splitlines()
if lines and lines[0].startswith("```"):
if lines and lines[0][:3] == "```":
lines = lines[1:]
if lines and lines[-1].strip() == "```":
lines = lines[:-1]
Expand Down
32 changes: 22 additions & 10 deletions backend/druks/user_settings/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
from druks.durable.engine import apply_schedules
from druks.extensions.loader import get_extension, iter_extensions
from druks.extensions.registry import workflows
from druks.harnesses.exceptions import HarnessError
from druks.harnesses.models import HarnessConnection
from druks.harnesses.registry import get_harnesses
from druks.harnesses.registry import get_harness_for_model, get_harnesses
from druks.notifications.models import Destination

from . import reads
Expand Down Expand Up @@ -67,11 +68,19 @@ async def update_harness_settings(
) -> HarnessResponse:
harness, row = _resolve_harness(name)
updates = body.model_dump(exclude_unset=True, by_alias=False)
if "model" in updates and not harness.has_model(updates["model"]):
raise HTTPException(
status_code=422,
detail=f"{updates['model']!r} is not a {harness.name} model.",
)
if "model" in updates:
try:
resolved = get_harness_for_model(updates["model"])
except HarnessError as exc:
raise HTTPException(
status_code=422,
detail=f"{updates['model']!r} is not a {harness.name} model.",
) from exc
if resolved.name != harness.name:
raise HTTPException(
status_code=422,
detail=f"{updates['model']!r} is not a {harness.name} model.",
)
_validate_effort(updates.get("effort"))
_validate_timeout(updates.get("timeout"))
if updates:
Expand Down Expand Up @@ -114,14 +123,17 @@ async def get_extension_settings() -> ExtensionsSettingsResponse:


# An agent's model override is client data — reject a model no installed harness
# can run (nothing owns its namespace). A model new to a known namespace passes,
# so new models need no release.
# lists.
def _validate_model(value: str | None) -> None:
if value is not None and not any(harness.has_model(value) for harness in get_harnesses()):
if value is None:
return
try:
get_harness_for_model(value)
except HarnessError as exc:
raise HTTPException(
status_code=422,
detail=f"No installed harness runs model {value!r}.",
)
) from exc


def _validate_effort(value: str | None) -> None:
Expand Down
35 changes: 27 additions & 8 deletions backend/tests/test_harness_model_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,40 @@
from druks.harnesses.codex import CodexHarness
from druks.harnesses.exceptions import HarnessError
from druks.harnesses.registry import get_harness_for_model
from druks.user_settings.models import HarnessSettings
from druks.user_settings.routes import _validate_model
from fastapi import HTTPException


def test_new_model_in_known_namespace_routes_without_a_release():
# Models absent from the shipped ``models`` tuples still route by namespace,
# so a provider's new model runs the day it ships.
assert get_harness_for_model("claude-opus-5") is ClaudeHarness
assert get_harness_for_model("gpt-6") is CodexHarness
assert get_harness_for_model("o4-mini") is CodexHarness
def test_shipped_tuple_fallback_routes_shipped_models(db_session):
assert get_harness_for_model("claude-opus-4-7") is ClaudeHarness
assert get_harness_for_model("gpt-5.5") is CodexHarness


def test_family_token_routes_to_its_harness():
def test_fetched_list_routes_provider_models(db_session):
HarnessSettings.require("claude").models_fetched = [
{"id": "claude-fable-5", "label": "Claude Fable 5"}
]
db_session.flush()

assert get_harness_for_model("claude-fable-5") is ClaudeHarness


def test_bare_harness_name_routes(db_session):
assert get_harness_for_model("claude") is ClaudeHarness
assert get_harness_for_model("codex") is CodexHarness


def test_unroutable_model_raises():
def test_unknown_model_raises_harness_error(db_session):
with pytest.raises(HarnessError):
get_harness_for_model("llama-3-70b")
with pytest.raises(HarnessError):
get_harness_for_model("claude-opus-99")


def test_settings_reject_model_missing_from_lists_returns_422(db_session):
with pytest.raises(HTTPException) as error:
_validate_model("llama-3-70b")

assert error.value.status_code == 422
assert error.value.detail == "No installed harness runs model 'llama-3-70b'."