feat(routing): per-agent models and skill-based child routing#824
feat(routing): per-agent models and skill-based child routing#824kusonooyasumi wants to merge 2 commits into
Conversation
Give each agent its own model instead of forcing one model for the whole scan. RunConfig no longer pins a model/settings, so the root agent and every child carry their own resolved model + reasoning settings. - STRIX_ORCHESTRATOR_MODEL (STRIX_LLM kept as a back-compat alias) and STRIX_SUBAGENT_MODEL for a default child model. - llm.skill_model_routes: ordered rules that pick a child model from the skills injected at spawn time (single 'skill' or AND/OR/ANY over 'skills'), each with an optional reasoning effort. First match wins; an explicit model passed to create_agent takes precedence. - Per-child chat-completions tool-schema detection and resume persistence of each child's resolved model. - --subagent-model CLI override and provider-aware API key mirroring so differently-routed providers don't leak the orchestrator credential.
ac98a94 to
7a1b9cf
Compare
Greptile SummaryThis PR adds model selection and routing for individual agents. The main changes are:
Confidence Score: 4/5The child credential paths need fixes before merging.
strix/config/models.py and the explicit child-model creation path
|
| Filename | Overview |
|---|---|
| strix/agents/factory.py | Adds per-agent model settings, route precedence, and model-specific tool schemas. |
| strix/config/settings.py | Adds validated child-model settings and ordered skill-route rules. |
| strix/config/loader.py | Loads structured configuration while preserving environment precedence and existing sections. |
| strix/config/models.py | Adds child credential mirroring, but same-provider keys and explicit model overrides are not handled correctly. |
| strix/core/execution.py | Propagates explicit child models and restores persisted model selections. |
| strix/interface/main.py | Adds the child-model CLI override and warms all statically configured models. |
| strix/report/usage.py | Tracks native per-agent costs and allocates unassigned LiteLLM callback costs. |
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
strix/config/models.py:129-133
**Child Key Cannot Replace Root Key**
When the orchestrator and child use the same provider with different keys, the root key is mirrored first and `_mirror_api_key_to_provider_env()` uses `setdefault`, so `SUBAGENT_LLM_API_KEY` cannot replace it. Child requests then run with the orchestrator account, permissions, and billing identity instead of the configured child credential.
### Issue 2 of 2
strix/config/models.py:128-133
**Explicit Routes Miss Credential Setup**
Credential mirroring only covers `subagent_model` and configured skill routes. A child created with the new explicit `create_agent(model=...)` override can select an unlisted provider, but `SUBAGENT_LLM_API_KEY` is never mirrored for that model, so its first request fails authentication unless the provider variable already exists independently.
Reviews (1): Last reviewed commit: "feat(routing): per-agent models and skil..." | Re-trigger Greptile
| key = llm.subagent_api_key or ( | ||
| llm.api_key if _same_provider(model_name, llm.model) else None | ||
| ) | ||
| if key: | ||
| _mirror_api_key_to_provider_env(model_name, key) |
There was a problem hiding this comment.
Child Key Cannot Replace Root Key
When the orchestrator and child use the same provider with different keys, the root key is mirrored first and _mirror_api_key_to_provider_env() uses setdefault, so SUBAGENT_LLM_API_KEY cannot replace it. Child requests then run with the orchestrator account, permissions, and billing identity instead of the configured child credential.
Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/config/models.py
Line: 129-133
Comment:
**Child Key Cannot Replace Root Key**
When the orchestrator and child use the same provider with different keys, the root key is mirrored first and `_mirror_api_key_to_provider_env()` uses `setdefault`, so `SUBAGENT_LLM_API_KEY` cannot replace it. Child requests then run with the orchestrator account, permissions, and billing identity instead of the configured child credential.
How can I resolve this? If you propose a fix, please make it concise.| for model_name in _subagent_model_names(settings): | ||
| key = llm.subagent_api_key or ( | ||
| llm.api_key if _same_provider(model_name, llm.model) else None | ||
| ) | ||
| if key: | ||
| _mirror_api_key_to_provider_env(model_name, key) |
There was a problem hiding this comment.
Explicit Routes Miss Credential Setup
Credential mirroring only covers subagent_model and configured skill routes. A child created with the new explicit create_agent(model=...) override can select an unlisted provider, but SUBAGENT_LLM_API_KEY is never mirrored for that model, so its first request fails authentication unless the provider variable already exists independently.
Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/config/models.py
Line: 128-133
Comment:
**Explicit Routes Miss Credential Setup**
Credential mirroring only covers `subagent_model` and configured skill routes. A child created with the new explicit `create_agent(model=...)` override can select an unlisted provider, but `SUBAGENT_LLM_API_KEY` is never mirrored for that model, so its first request fails authentication unless the provider variable already exists independently.
How can I resolve this? If you propose a fix, please make it concise.Env vars are global per provider, so mirroring a subagent key via os.environ.setdefault could never override the orchestrator key for a same-provider child, and an explicit create_agent(model=...) to an unlisted provider got no key at all. Pass each child's resolved credential as a per-call api_key in model_settings.extra_args instead: SUBAGENT_LLM_API_KEY when set, else the orchestrator key only for same-provider children, else ambient auth. This covers routed, default, and explicit-model children.
Addressed review feedback — child credentialsTwo related credential bugs were flagged (both rooted in
Fix ( New tests cover explicit-key precedence, same-vs-cross-provider reuse, and that the key reaches |
Summary
Foundational change for fine-grained model control: each agent gets its own model instead of the whole scan being pinned to one model. Adds a default subagent model and ordered skill-based routing so specialist child agents can run on different models.
This is PR 1 of a split series. PR 2 (budget fallbacks) stacks on this one; the verification (#822) and dedupe (#823) PRs are independent.
What changes for users
STRIX_LLMset, every agent uses it exactly as before.STRIX_LLMstill works — it's now a back-compat alias forSTRIX_ORCHESTRATOR_MODEL.Config
STRIX_ORCHESTRATOR_MODEL/STRIX_LLMSTRIX_SUBAGENT_MODELSTRIX_SUBAGENT_REASONING_EFFORTSUBAGENT_LLM_API_KEY--subagent-modelSkill routing is configured via the structured
llm.skill_model_routeslist (ordered; first match wins):{ "llm": { "model": "openai/gpt-5.4", "subagent_model": "deepseek/deepseek-chat", "skill_model_routes": [ {"skill": "business_logic", "model": "anthropic/claude-sonnet-4-6"}, {"operator": "AND", "skills": ["oauth", "authentication_jwt"], "model": "openai/gpt-5.4"}, {"operator": "OR", "skills": ["xss", "sql_injection"], "model": "dashscope/qwen-plus"}, {"operator": "ANY", "model": "deepseek/deepseek-chat"} ] } }Precedence: explicit
create_agent(model=...)→ matching skill route →subagent_model→ orchestrator model.Key implementation notes
RunConfig.model/model_settingsare nowNone. This is essential — RunConfig values override every per-agent route. EachSandboxAgentcarries its own model + settings instead.chat_completionstool-schema detection is computed per child from its resolved model.Test plan
pytest tests/test_model_routing.py— build-agent model/settings, skill-route operators, factory precedence + per-model schema, orchestrator fallback, and per-agent usage recording.ruff+mypyclean. Also fixes the pre-existingtest_runner_root_prompt.pymock (missingruntime).