Skip to content
Closed
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
134 changes: 130 additions & 4 deletions docs/advanced/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,24 @@ Configure Strix using environment variables or a config file.

## LLM Configuration

<ParamField path="STRIX_LLM" type="string" required>
Model name in LiteLLM format (e.g., `openai/gpt-5.4`, `anthropic/claude-sonnet-4-6`).
<ParamField path="STRIX_ORCHESTRATOR_MODEL" type="string" required>
Model for the root/orchestrator in LiteLLM format (for example,
`openai/gpt-5.4`). `STRIX_LLM` remains a backward-compatible alias.
</ParamField>

<ParamField path="STRIX_SUBAGENT_MODEL" type="string">
Default model for child agents. If omitted, children use the orchestrator model.
</ParamField>

<ParamField path="SUBAGENT_LLM_API_KEY" type="string">
API key for the subagent provider. Strix mirrors it to the provider-specific
environment variable LiteLLM expects. It is optional when both routes use the
same provider/key or the provider uses ambient/local authentication.
</ParamField>

<ParamField path="STRIX_SUBAGENT_REASONING_EFFORT" type="string">
Reasoning effort for default and skill-routed subagents. Falls back to
`STRIX_REASONING_EFFORT` when omitted.
</ParamField>

<ParamField path="LLM_API_KEY" type="string">
Expand All @@ -35,6 +51,111 @@ Configure Strix using environment variables or a config file.
Timeout in seconds for memory compression operations (context summarization).
</ParamField>

### Skill-based child routing

Use the structured `llm.skill_model_routes` config field to select models based
on the skills injected when a child is spawned. Rules are evaluated in order;
the first match wins. A route accepts a concise single `skill`, or an
`operator` of `AND`, `OR`, or `ANY`. `ANY` matches a child with any injected
skill. An explicit model passed to `create_agent` takes precedence over routes.

```json
{
"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"}
]
}
}
```

### Per-model budgets and fallback chains

Set `llm.model_budgets_usd` and `llm.model_fallbacks` to move every agent using
an exhausted model to its next configured model. Budgets are cumulative per
model across the whole scan, so the same rule covers the orchestrator, default
children, explicit child routes, and skill routes. Fallback targets can have
their own budget and fallback, creating any number of layers.

```json
{
"llm": {
"model": "openai/gpt-5.4",
"subagent_model": "z-ai/glm-4.7",
"skill_model_routes": [
{"skill": "xss", "model": "openai/gpt-5.4"}
],
"model_budgets_usd": {
"openai/gpt-5.4": 20.0,
"z-ai/glm-4.7": 10.0
},
"model_fallbacks": {
"openai/gpt-5.4": "z-ai/glm-4.7",
"z-ai/glm-4.7": "deepseek/deepseek-chat"
}
}
}
```

A fallback is checked after a response returns, so a call can slightly
overshoot its model budget. Strix discards that boundary response before any of
its tool calls execute, updates the agent's persisted active model, and replays
the unchanged session with the fallback. This avoids duplicate side effects and
keeps scan resume on the selected fallback layer. A model budget without a
fallback only tracks usage; it does not stop the scan. The scan-wide
`--max-budget-usd` remains a hard stop and takes precedence.

The TUI's bottom-right panel displays cumulative tokens and estimated cost for
each model actually used, including fallback models.

### Independent finding verification

<ParamField path="STRIX_VERIFY_FINDINGS" default="false" type="boolean">
Require an independent model to try to refute every candidate before it is
persisted. Off by default. Rejected findings and verifier errors fail closed.
</ParamField>

<ParamField path="STRIX_VERIFICATION_MODEL" type="string">
Model used by the independent verifier. Required when verification is enabled.
</ParamField>

<ParamField path="VERIFICATION_LLM_API_KEY" type="string">
Optional provider key for the verification model.
</ParamField>

<ParamField path="STRIX_VERIFICATION_REASONING_EFFORT" default="high" type="string">
Reasoning effort for the verifier.
</ParamField>

Verification calls are recorded in the same `llm_usage` ledger and count toward
`--max-budget-usd`, along with root, child, and deduplication calls.

### Dedicated deduplication model

Finding deduplication is a cheap, structured classification task. By default it
runs on the orchestrator model, but you can route it to a smaller/cheaper model
without affecting the agents that do the actual testing.

<ParamField path="STRIX_DEDUPE_MODEL" type="string">
Model used to judge whether a candidate finding duplicates an existing report.
Falls back to the orchestrator model (`STRIX_ORCHESTRATOR_MODEL` / `STRIX_LLM`)
when unset.
</ParamField>

<ParamField path="DEDUPE_LLM_API_KEY" type="string">
Optional provider key for the deduplication model.
</ParamField>

<ParamField path="STRIX_DEDUPE_REASONING_EFFORT" type="string">
Reasoning effort for the deduplication model. Defaults to the model's own
baseline when unset.
</ParamField>

## Optional Features

<ParamField path="PERPLEXITY_API_KEY" type="string">
Expand Down Expand Up @@ -106,9 +227,14 @@ strix --target ./app --config /path/to/config.json
```json
{
"env": {
"STRIX_LLM": "openai/gpt-5.4",
"LLM_API_KEY": "sk-...",
"STRIX_ORCHESTRATOR_MODEL": "openai/gpt-5.4",
"STRIX_SUBAGENT_MODEL": "deepseek/deepseek-chat",
"LLM_API_KEY": "sk-main-...",
"SUBAGENT_LLM_API_KEY": "sk-child-...",
"STRIX_REASONING_EFFORT": "high"
},
"verification": {
"enabled": false
}
}
```
Expand Down
8 changes: 7 additions & 1 deletion docs/usage/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,15 @@ strix (--target <target> | --target-list <path> | --mount <path>) [options]
Path to a custom config file (JSON) to use instead of `~/.strix/cli-config.json`.
</ParamField>

<ParamField path="--subagent-model" type="string">
Override the config's default child-agent model for this run. Ordered
skill-specific routes in the config remain more specific and take precedence.
</ParamField>

<ParamField path="--max-budget-usd" type="number">
Maximum LLM spend in USD for the whole scan, counted cumulatively across the
root agent and every child agent. The budget is checked after each model
root agent, every child agent, deduplication, and independent finding
verification. The budget is checked after each model
response; once the running cost reaches the threshold, the scan stops cleanly
with a `stopped` status (not a failure) and the sandbox is torn down.

Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ ignore = [
# a runtime ``Callable`` annotation on ``vulnerability_found_callback``.
"strix/report/state.py" = ["TC003", "PLR0912", "PLR0915", "E501", "PERF401"]
"strix/report/usage.py" = ["PLC0415"]
# Lazy imports of strix.core.hooks / strix.config.models avoid a circular
# dependency between the report pipeline and the runner/config layers.
"strix/report/dedupe.py" = ["PLC0415"]
"strix/report/verification.py" = ["PLC0415"]
"strix/config/models.py" = ["PLC0415"]
# Interface utility branches per scope-mode / target-type combination;
# splitting would obscure the decision tree without simplifying it.
Expand Down
54 changes: 50 additions & 4 deletions strix/agents/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
from typing import TYPE_CHECKING, Any

from agents.agent import ToolsToFinalOutputResult
from agents.model_settings import ModelSettings
from agents.sandbox import SandboxAgent
from agents.sandbox.capabilities import Filesystem, Shell
from agents.sandbox.errors import InvalidManifestPathError
from agents.tool import CustomTool, FunctionTool, Tool
from pydantic import ValidationError

from strix.agents.prompt import render_system_prompt
from strix.core.inputs import make_model_settings
from strix.core.model_routing import chain_uses_chat_completions_tools, resolve_budget_model
from strix.tools.agents_graph.tools import (
agent_finish,
create_agent,
Expand Down Expand Up @@ -60,6 +63,8 @@
from agents import RunContextWrapper
from agents.tool import FunctionToolResult

from strix.config.settings import ReasoningEffort, Settings, SkillModelRoute


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -408,6 +413,8 @@ def build_strix_agent(
is_whitebox: bool = False,
interactive: bool = False,
chat_completions_tools: bool = False,
model: str | None = None,
model_settings: ModelSettings | None = None,
system_prompt_context: dict[str, Any] | None = None,
extra_tools: Sequence[Tool] | None = None,
instructions_override: str | None = None,
Expand All @@ -417,6 +424,8 @@ def build_strix_agent(
Args:
chat_completions_tools: Wrap SDK custom tools as function tools
when the selected backend cannot accept Responses custom tools.
model: Per-agent model route. When omitted, the run default is used.
model_settings: Settings resolved for this agent's own model.
extra_tools: Additional tools for this scan agent only, on top of any
registered via ``register_agent_tools``.
instructions_override: Use this verbatim as the system prompt instead
Expand Down Expand Up @@ -456,7 +465,8 @@ def build_strix_agent(
instructions=instructions,
tools=tools,
tool_use_behavior=_finish_tool_use_behavior,
model=None,
model=model,
model_settings=model_settings or ModelSettings(),
capabilities=[
Filesystem(
configure_tools=(
Expand All @@ -474,10 +484,11 @@ def build_strix_agent(

def make_child_factory(
*,
settings: Settings,
default_model: str,
scan_mode: str = "deep",
is_whitebox: bool = False,
interactive: bool = False,
chat_completions_tools: bool = False,
system_prompt_context: dict[str, Any] | None = None,
) -> Any:
"""Return the runner-owned builder used by ``spawn_child_agent``.
Expand All @@ -487,15 +498,50 @@ def make_child_factory(
without the graph tool knowing about runner internals.
"""

def _factory(*, name: str, skills: list[str]) -> SandboxAgent[Any]:
llm = settings.llm

def _matching_route(skills: list[str]) -> SkillModelRoute | None:
return next((route for route in llm.skill_model_routes if route.matches(skills)), None)

def _factory(
*,
name: str,
skills: list[str],
model: str | None = None,
) -> SandboxAgent[Any]:
route = _matching_route(skills) if model is None else None
configured_model = (
model
or (route.model if route is not None else None)
or llm.subagent_model
or default_model
).strip()
resolved_model = resolve_budget_model(configured_model, llm)
reasoning: ReasoningEffort | None = (
route.reasoning_effort
if route is not None and route.reasoning_effort is not None
else llm.subagent_reasoning_effort
)
if reasoning is None:
reasoning = llm.reasoning_effort
child_model_settings = make_model_settings(
reasoning,
model_name=resolved_model,
force_required_tool_choice=llm.force_required_tool_choice,
)
return build_strix_agent(
name=name,
skills=skills,
is_root=False,
scan_mode=scan_mode,
is_whitebox=is_whitebox,
interactive=interactive,
chat_completions_tools=chat_completions_tools,
chat_completions_tools=chain_uses_chat_completions_tools(
configured_model,
settings,
),
model=resolved_model,
model_settings=child_model_settings,
system_prompt_context=system_prompt_context,
)

Expand Down
8 changes: 8 additions & 0 deletions strix/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,27 @@
persist_current,
)
from strix.config.settings import (
DedupeSettings,
FindingVerificationSettings,
IntegrationSettings,
LlmSettings,
ReasoningEffort,
RuntimeSettings,
Settings,
SkillModelRoute,
TelemetrySettings,
)


__all__ = [
"DedupeSettings",
"FindingVerificationSettings",
"IntegrationSettings",
"LlmSettings",
"ReasoningEffort",
"RuntimeSettings",
"Settings",
"SkillModelRoute",
"TelemetrySettings",
"apply_config_override",
"load_settings",
Expand Down
Loading