fix(cron): resolve provider with the job's effective model; default dashboard cron creates to the backend's own profile

Two follow-ups to the per-job model pin surface (#67472 / #49948 review):

- cron/scheduler.py: pass target_model=<effective job model> to
  resolve_runtime_provider() on the primary path, so providers with
  model-specific api_mode routing derive the mode from the model the job
  actually runs (per-job pin > env > config default) instead of the stale
  persisted default. The auth-fallback path already did this for its
  fb_model.

- hermes_cli/web_server.py: POST /api/cron/jobs (and its sync worker) no
  longer hardcodes profile="default" when the request carries no profile
  param. A pool backend scoped to a named profile now resolves its own
  profile via get_active_profile_name(), so pre-profileScoped desktop
  clients can't write a named profile's job into ~/.hermes. Unscoped /
  custom HERMES_HOME keeps the legacy default fallback.

Tests: target_model capture test on run_job; two profile-default tests on
the create endpoint.
This commit is contained in:
teknium1 2026-07-19 05:55:30 -07:00 committed by Teknium
parent 6e676c768c
commit 786df3ca6c
4 changed files with 117 additions and 3 deletions

View file

@ -334,3 +334,42 @@ class TestModelDriftGuard:
)
assert agent_constructed is True
assert success is True
class TestRuntimeResolutionTargetModel:
"""run_job must resolve the primary provider against the model the job
will actually run (per-job pin > env > config default), so providers with
model-specific api_mode routing (e.g. OpenCode Zen/Go) pick the mode for
the pinned model instead of the stale persisted default."""
def test_primary_resolution_passes_effective_model(self, tmp_path):
job = _base_job(model="my-pinned-model", provider="openrouter")
captured = {}
def _capture(**kwargs):
captured.update(kwargs)
return {
"api_key": "test-key",
"base_url": "https://example.invalid/v1",
"provider": "openrouter",
"api_mode": "chat_completions",
}
fake_db = MagicMock()
with patch("cron.scheduler._hermes_home", tmp_path), \
patch("cron.scheduler._resolve_origin", return_value=None), \
patch("hermes_cli.env_loader.load_hermes_dotenv"), \
patch("hermes_cli.env_loader.reset_secret_source_cache"), \
patch("hermes_state.SessionDB", return_value=fake_db), \
patch(
"hermes_cli.runtime_provider.resolve_runtime_provider",
side_effect=_capture,
), \
patch("run_agent.AIAgent") as mock_agent_cls:
mock_agent = MagicMock()
mock_agent.run_conversation.return_value = {"final_response": "ok"}
mock_agent_cls.return_value = mock_agent
run_job(job)
assert captured.get("target_model") == "my-pinned-model"
assert captured.get("requested") == "openrouter"

View file

@ -735,3 +735,53 @@ async def test_cron_profile_validation_errors(isolated_profiles):
with pytest.raises(HTTPException) as missing:
await web_server.list_cron_jobs(profile="missing_profile")
assert missing.value.status_code == 404
@pytest.mark.asyncio
async def test_create_cron_job_without_profile_uses_backend_own_profile(
isolated_profiles, monkeypatch
):
"""A pool backend scoped to a named profile must not default creates to
``~/.hermes`` when the request carries no explicit ``profile`` (the
Desktop app's pre-profileScoped clients sent none)."""
from hermes_cli import web_server
monkeypatch.setenv(
"HERMES_HOME", str(isolated_profiles["worker_alpha"])
)
job = await web_server.create_cron_job(
web_server.CronJobCreate(
prompt="runs in my own profile",
schedule="every 1h",
name="own-profile-job",
),
profile=None,
)
assert job["profile"] == "worker_alpha"
assert (isolated_profiles["worker_alpha"] / "cron" / "jobs.json").exists()
assert not (isolated_profiles["default"] / "cron" / "jobs.json").exists()
@pytest.mark.asyncio
async def test_create_cron_job_without_profile_defaults_when_unscoped(
isolated_profiles, monkeypatch
):
"""HERMES_HOME at the default home (or unrecognized) keeps the legacy
``default`` fallback."""
from hermes_cli import web_server
monkeypatch.setenv("HERMES_HOME", str(isolated_profiles["default"]))
job = await web_server.create_cron_job(
web_server.CronJobCreate(
prompt="runs in default",
schedule="every 1h",
name="default-job",
),
profile=None,
)
assert job["profile"] == "default"
assert (isolated_profiles["default"] / "cron" / "jobs.json").exists()