Skip to content

fix(delegation): pass target_model to resolve_runtime_provider in _resolve_delegation_credentials#15320

Closed
Emidomenge wants to merge 1 commit into
NousResearch:mainfrom
Emidomenge:fix/delegation-target-model-opencode
Closed

fix(delegation): pass target_model to resolve_runtime_provider in _resolve_delegation_credentials#15320
Emidomenge wants to merge 1 commit into
NousResearch:mainfrom
Emidomenge:fix/delegation-target-model-opencode

Conversation

@Emidomenge

Copy link
Copy Markdown
Contributor

Problem

When delegation.model differs from model.default on opencode-go or opencode-zen, delegate_task fails with a 404. _resolve_delegation_credentials calls resolve_runtime_provider(requested=configured_provider) without a target_model, so api_mode is computed from model_cfg.get("default") — the main model — instead of the delegation model.

Example: model.default=minimax-m2.7 (→ anthropic_messages) + delegation.model=glm-5.1 (→ chat_completions). The subagent inherits anthropic_messages, /v1 is stripped from the base URL, and the request hits https://opencode.ai/zen/go/messages instead of https://opencode.ai/zen/go/v1/chat/completions404.

Fix

resolve_runtime_provider already accepts target_model for this exact purpose (see its docstring: "Callers performing an explicit mid-session model switch should pass the new model here"). The delegation path just wasn't passing it.

# Before
runtime = resolve_runtime_provider(requested=configured_provider)
# After
runtime = resolve_runtime_provider(requested=configured_provider, target_model=configured_model)

Testing

Verified locally with opencode-go, model.default=minimax-m2.7, delegation.model=glm-5.1:

  • Before: api_mode=anthropic_messages, base_url=https://opencode.ai/zen/go → 404
  • After: api_mode=chat_completions, base_url=https://opencode.ai/zen/go/v1 → success

Closes #15319
Related: #13678

…solve_delegation_credentials

When delegation.model differs from model.default and the provider is
opencode-go or opencode-zen, the wrong api_mode is computed because
resolve_runtime_provider falls back to model_cfg.get('default') — the
main model — instead of the configured delegation model.

For example, with model.default=minimax-m2.7 (anthropic_messages) and
delegation.model=glm-5.1 (chat_completions), subagents get
anthropic_messages, which strips /v1 from the base URL and causes a 404.

resolve_runtime_provider already accepts target_model for exactly this
purpose; _resolve_delegation_credentials just wasn't passing it.

Fixes NousResearch#15319
Related: NousResearch#13678
@ether-btc

Copy link
Copy Markdown
Contributor

Code Review: fix(delegation): pass target_model to resolve_runtime_provider

Verdict: Approve with nits

This is a correct, minimal fix for a real bug. The change is one argument addition that wires an already-available parameter through to where it's needed. The fix is obviously safe — resolve_runtime_provider already accepts target_model with a None default that preserves existing behavior.


Correctness Analysis

The bug is real and well-diagnosed. _resolve_delegation_credentials (line 2241) calls resolve_runtime_provider(requested=configured_provider) without passing the delegation model. resolve_runtime_provider then falls back to model_cfg.get("default") — the main session model — to compute api_mode. When the delegation model requires a different transport (e.g., glm-5.1 -> chat_completions vs minimax-m2.7 -> anthropic_messages), the subagent gets the wrong api_mode and a mangled base URL.

The fix is correct. configured_model is already resolved at line 2190 from cfg.get("model") and is in scope at line 2241. Passing it as target_model means resolve_runtime_provider uses the delegation model for api_mode computation instead of the stale config default. This matches the documented intent of target_model (line 856-862 in runtime_provider.py):

Callers performing an explicit mid-session model switch should pass the new model here so api_mode is derived from the model they are switching TO, not the stale persisted default.

Call path verified: target_model flows through to:

  1. _resolve_runtime_from_pool_entry (line 980) -> uses it for effective_model (line 195)
  2. _resolve_azure_foundry_runtime (line 897) -> uses it for Azure model-family inference (line 650)
  3. Generic provider fallback (line 1222) -> uses it for opencode_model_api_mode() routing

All three paths fall back to model_cfg.get("default") when target_model is None, so no regression for callers that don't pass it.

Edge case: configured_model is None. Line 2190: str(cfg.get("model") or "").strip() or None. When delegation.model is empty, configured_model is None, which passes target_model=None -> identical to current behavior. Safe.


Issues Found

# Severity File Issue
1 High tests/tools/test_delegate.py:630 Existing test will break. test_provider_resolves_full_credentials asserts mock_resolve.assert_called_once_with(requested="openrouter") but after this PR the call includes target_model="google/gemini-3-flash-preview". Same issue at line 691 (test_nous_provider_resolves_nous_credentials). Both tests will fail with AssertionError: Expected call: resolve_runtime_provider(requested='openrouter') Actual call: resolve_runtime_provider(requested='openrouter', target_model='google/gemini-3-flash-preview').
2 Medium PR branch 431 commits behind main. The merge-base is 6f1eed39 (Apr 26); main is now at 98d75dea. Needs rebase before merge. There have been significant changes to delegate_tool.py since — at minimum, verify the line numbers haven't shifted.
3 Low PR diff No test for the actual bug. The PR adds the fix but no test that verifies target_model is passed. A test like test_delegation_model_determines_api_mode that sets cfg={"model": "glm-5.1", "provider": "opencode-go"} and asserts mock_resolve is called with target_model="glm-5.1" would prevent regression.

Nit

The PR description focuses on opencode-go / opencode-zen but the bug affects any provider where api_mode varies by model (Minimax, xAI codex_responses, Azure Foundry). The title could be broader: fix(delegation): pass target_model so api_mode matches delegation model, not config default. Not blocking — just improves discoverability.


Scope Check

  • No credentials, private keys, or wallet addresses in the diff
  • Single-file change, single-function modification
  • No new dependencies
  • No config changes required

Recommendation

Approve after:

  1. Fix the two failing tests (lines 630, 691) — update assert_called_once_with to include target_model
  2. Rebase onto current main
  3. (Nice-to-have) Add a positive test for the bug scenario

The fix itself is correct and minimal. The missing test updates should be straightforward.

@Emidomenge

Copy link
Copy Markdown
Contributor Author

If this has already been fixed in latest version. This can be closed

@teknium1

teknium1 commented May 4, 2026

Copy link
Copy Markdown
Contributor

Salvaged via #19623 onto current main - your commit authorship was preserved. Thanks!

@teknium1 teknium1 closed this May 4, 2026
Tranquil-Flow added a commit to Tranquil-Flow/hermes-agent that referenced this pull request May 25, 2026
…on (NousResearch#15319)

When delegation.model differs from the parent model (e.g., delegating
to claude-opus-4-6 on an opencode-zen provider while the parent uses
a chat_completions model), resolve_runtime_provider needs the target
model to select the correct api_mode.  Without it, the delegate
inherits the parent's api_mode and gets 404 errors.

Supersedes NousResearch#15320.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P1 High — major feature broken, no workaround tool/delegate Subagent delegation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

delegate_task 404 when delegation model differs from main model on opencode-go/opencode-zen

4 participants