Skip to content

feat(routing): per-agent models and skill-based child routing#824

Open
kusonooyasumi wants to merge 2 commits into
usestrix:mainfrom
kusonooyasumi:pr1-model-routing
Open

feat(routing): per-agent models and skill-based child routing#824
kusonooyasumi wants to merge 2 commits into
usestrix:mainfrom
kusonooyasumi:pr1-model-routing

Conversation

@kusonooyasumi

Copy link
Copy Markdown
Contributor

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

  • Nothing by default. With only STRIX_LLM set, every agent uses it exactly as before.
  • STRIX_LLM still works — it's now a back-compat alias for STRIX_ORCHESTRATOR_MODEL.
  • Opt-in: give subagents their own model, and route specific skills to specific models.

Config

Env var Meaning
STRIX_ORCHESTRATOR_MODEL / STRIX_LLM Root/orchestrator model (required)
STRIX_SUBAGENT_MODEL Default child-agent model (falls back to orchestrator)
STRIX_SUBAGENT_REASONING_EFFORT Reasoning effort for children
SUBAGENT_LLM_API_KEY Optional provider key for the subagent provider
--subagent-model One-run CLI override of the default child model

Skill routing is configured via the structured llm.skill_model_routes list (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_settings are now None. This is essential — RunConfig values override every per-agent route. Each SandboxAgent carries its own model + settings instead.
  • Resolved child models are persisted per agent so a resumed scan reuses the same model.
  • chat_completions tool-schema detection is computed per child from its resolved model.
  • Provider-aware API-key mirroring stops the orchestrator credential leaking into a differently-routed child provider.

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.
  • Full suite green (248), ruff + mypy clean. Also fixes the pre-existing test_runner_root_prompt.py mock (missing runtime).

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.
@kusonooyasumi
kusonooyasumi marked this pull request as ready for review July 21, 2026 07:02
@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds model selection and routing for individual agents. The main changes are:

  • Per-agent model and reasoning settings.
  • Ordered skill-based child routing with explicit overrides.
  • Persisted child models for resumed scans.
  • Structured configuration and a child-model CLI option.
  • Provider-aware credentials, model warm-up, and per-agent usage reporting.

Confidence Score: 4/5

The child credential paths need fixes before merging.

  • Same-provider child requests can use the orchestrator key instead of the configured child key.
  • Explicit child models can select a provider that never receives the configured child credential.
  • The routing, persistence, and structured configuration paths otherwise appear consistent.

strix/config/models.py and the explicit child-model creation path

Security Review

A distinct child key cannot replace an already mirrored orchestrator key for the same provider. Child requests can therefore run with the wrong account or permissions.

Important Files Changed

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

Comment thread strix/config/models.py
Comment on lines +129 to +133
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security 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.

Comment thread strix/config/models.py
Comment on lines +128 to +133
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.
@kusonooyasumi

Copy link
Copy Markdown
Contributor Author

Addressed review feedback — child credentials

Two related credential bugs were flagged (both rooted in _mirror_api_key_to_provider_env() using os.environ.setdefault):

  1. Child key can't replace root key (same provider). Provider env vars are global, so once configure_sdk_model_defaults() mirrored the orchestrator key first, SUBAGENT_LLM_API_KEY for the same provider was silently dropped — children ran on the orchestrator account.
  2. Explicit create_agent(model=...) routes missed credential setup. A child selecting an unlisted provider never got SUBAGENT_LLM_API_KEY mirrored.

Fix (fix(routing): give child agents per-call credentials): stop relying on global env for per-route keys. Each child now sends its credential as a per-call api_key in model_settings.extra_args (which LiteLLM honors over env). _resolve_child_api_key picks SUBAGENT_LLM_API_KEY when set, else the orchestrator key only for same-provider children, else ambient auth. This runs for routed, default, and explicit-model children — fixing both issues. The key stays in memory (not persisted to the session snapshot).

New tests cover explicit-key precedence, same-vs-cross-provider reuse, and that the key reaches extra_args.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant