From f993d9809b201c03771b901e906461c958591df3 Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:27:15 +0530 Subject: [PATCH 01/12] Add settings loading and chat completions logic Load settings and determine chat completions tool usage based on API type. --- strix/agents/factory.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/strix/agents/factory.py b/strix/agents/factory.py index b2fcbf08c..a707914e4 100644 --- a/strix/agents/factory.py +++ b/strix/agents/factory.py @@ -671,6 +671,12 @@ def build_strix_agent( instructions_override: Use this verbatim as the system prompt instead of rendering the built-in scan prompt. """ + settings = load_settings() + if settings.llm.api_type == "chat_completions": + chat_completions_tools = True + elif settings.llm.api_type == "responses": + chat_completions_tools = False + if instructions_override is not None: instructions = instructions_override else: @@ -686,7 +692,6 @@ def build_strix_agent( agent_tools = [*_EXTRA_TOOLS, *(extra_tools or [])] if interactive: - # Yielding to the user is only meaningful when one is attached. agent_tools.append(respond_to_user) if is_root: tools: list[Tool] = [*_BASE_TOOLS, *agent_tools, finish_scan] From 541549e1316988a009827cf51c8a43f56a75aa90 Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Sat, 19 Sep 2026 23:49:50 +0530 Subject: [PATCH 02/12] feat(cli): add --subagent-model argument to parser (#1339) - Register --subagent-model CLI argument with default None - Restore persisted subagent_model in _load_resume_state - Relates to #1339 --- strix/interface/cli_args.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/strix/interface/cli_args.py b/strix/interface/cli_args.py index 340a5d471..ef329ada8 100644 --- a/strix/interface/cli_args.py +++ b/strix/interface/cli_args.py @@ -201,6 +201,17 @@ Strix Cloud: ), ) + parser.add_argument( + "--subagent-model", + dest="subagent_model", + type=str, + default=None, + help=( + "Model identifier to use for spawned subagents (e.g. claude-3-5-haiku, " + "gpt-4o-mini). If not specified, subagents inherit the root agent model." + ), + ) + parser.add_argument( "--scope-mode", type=str, @@ -465,3 +476,7 @@ def _load_resume_state(args: argparse.Namespace, parser: argparse.ArgumentParser persisted_scan_mode = state.get("scan_mode") if persisted_scan_mode and args.scan_mode == "deep": args.scan_mode = persisted_scan_mode + persisted_subagent_model = state.get("subagent_model") + if not getattr(args, "subagent_model", None) and persisted_subagent_model: + args.subagent_model = persisted_subagent_model + From e7352e90910fe04088d8310761e8561d39eed871 Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Sat, 19 Sep 2026 23:55:45 +0530 Subject: [PATCH 03/12] test(cli): add unit tests for --subagent-model parsing and resume (#1339) - Verify default subagent_model parses as None - Verify explicit --subagent-model argument ingestion - Verify state persistence across resumed scan sessions - Relates to #1339 --- tests/test_cli_subagent_model.py | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/test_cli_subagent_model.py diff --git a/tests/test_cli_subagent_model.py b/tests/test_cli_subagent_model.py new file mode 100644 index 000000000..cfcc75791 --- /dev/null +++ b/tests/test_cli_subagent_model.py @@ -0,0 +1,48 @@ +import argparse +from pathlib import Path +import pytest + +from strix.interface.cli_args import parse_arguments, _load_resume_state + + +def test_subagent_model_default_none(monkeypatch): + """Verify --subagent-model defaults to None when omitted.""" + monkeypatch.setattr("sys.argv", ["strix", "--target", "https://example.com"]) + args = parse_arguments() + assert args.subagent_model is None + + +def test_subagent_model_flag_parsed(monkeypatch): + """Verify --subagent-model accepts a model name.""" + monkeypatch.setattr( + "sys.argv", + ["strix", "--target", "https://example.com", "--subagent-model", "claude-3-5-haiku"], + ) + args = parse_arguments() + assert args.subagent_model == "claude-3-5-haiku" + + +def test_resume_restores_subagent_model(monkeypatch, tmp_path): + """Verify _load_resume_state restores persisted subagent_model.""" + fake_state = { + "targets_info": [{"type": "domain", "original": "example.com"}], + "subagent_model": "gpt-4o-mini", + "scan_mode": "deep", + } + + args = argparse.Namespace( + resume="test-run", + subagent_model=None, + scan_mode="deep", + diff_scope="auto", + targets_info=[], + workspace_mount=None, + ) + parser = argparse.ArgumentParser() + + monkeypatch.setattr("strix.report.writer.read_run_record", lambda run_dir: fake_state) + monkeypatch.setattr("strix.core_paths.run_dir_for", lambda r: tmp_path) + monkeypatch.setattr(Path, "exists", lambda self: True) + + _load_resume_state(args, parser) + assert args.subagent_model == "gpt-4o-mini" From 2d847b05d6ea756284fa4e4f583c82406c5ee83a Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Sun, 20 Sep 2026 00:02:00 +0530 Subject: [PATCH 04/12] Refactor agent tools initialization logic Removed conditional logic for chat completions tools based on settings. --- strix/agents/factory.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/strix/agents/factory.py b/strix/agents/factory.py index a707914e4..b2fcbf08c 100644 --- a/strix/agents/factory.py +++ b/strix/agents/factory.py @@ -671,12 +671,6 @@ def build_strix_agent( instructions_override: Use this verbatim as the system prompt instead of rendering the built-in scan prompt. """ - settings = load_settings() - if settings.llm.api_type == "chat_completions": - chat_completions_tools = True - elif settings.llm.api_type == "responses": - chat_completions_tools = False - if instructions_override is not None: instructions = instructions_override else: @@ -692,6 +686,7 @@ def build_strix_agent( agent_tools = [*_EXTRA_TOOLS, *(extra_tools or [])] if interactive: + # Yielding to the user is only meaningful when one is attached. agent_tools.append(respond_to_user) if is_root: tools: list[Tool] = [*_BASE_TOOLS, *agent_tools, finish_scan] From e804cc7606bea2a1daa10b2ee00117370ab09cb5 Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Sun, 20 Sep 2026 00:08:05 +0530 Subject: [PATCH 05/12] Update strix/interface/cli_args.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- strix/interface/cli_args.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/strix/interface/cli_args.py b/strix/interface/cli_args.py index ef329ada8..473630a3c 100644 --- a/strix/interface/cli_args.py +++ b/strix/interface/cli_args.py @@ -201,7 +201,7 @@ Strix Cloud: ), ) - parser.add_argument( + parser.add_argument( "--subagent-model", dest="subagent_model", type=str, @@ -210,7 +210,7 @@ Strix Cloud: "Model identifier to use for spawned subagents (e.g. claude-3-5-haiku, " "gpt-4o-mini). If not specified, subagents inherit the root agent model." ), - ) + ) parser.add_argument( "--scope-mode", From d223646984b463e89509815e63d737772909a3c8 Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Sun, 20 Sep 2026 00:21:28 +0530 Subject: [PATCH 06/12] feat(cli): wire subagent_model into scan_config and run_strix_scan (#1339) - Add subagent_model to scan_config so it persists via report_state.save_run_data - Pass subagent_model parameter into run_strix_scan - Resolves #1339 --- strix/interface/cli.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/strix/interface/cli.py b/strix/interface/cli.py index 684805d02..a9fe3facb 100644 --- a/strix/interface/cli.py +++ b/strix/interface/cli.py @@ -85,11 +85,12 @@ async def run_cli(args: Any) -> None: # noqa: PLR0915 scan_mode = getattr(args, "scan_mode", "deep") - scan_config: dict[str, Any] = { + scan_config: dict[str, Any] = { "scan_id": args.run_name, "targets": args.targets_info, - "user_instructions": args.instruction or "", + "user_instruction": args.instruction or "", "run_name": args.run_name, + "subagent_model": getattr(args, "subagent_model", None), "diff_scope": getattr(args, "diff_scope", {"active": False}), "scan_mode": scan_mode, "non_interactive": bool(getattr(args, "non_interactive", False)), From d87a44abc4bc1f307460cb76405b4abd8503ba5f Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Sun, 20 Sep 2026 00:42:51 +0530 Subject: [PATCH 07/12] feat(agents): support model override in agent factory and child builders (#1339) - Add model parameter to build_strix_agent - Add subagent_model parameter to make_child_factory - Forward subagent_model override to child agent instances - Closes #1339 --- strix/agents/factory.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/strix/agents/factory.py b/strix/agents/factory.py index b2fcbf08c..13e786015 100644 --- a/strix/agents/factory.py +++ b/strix/agents/factory.py @@ -650,6 +650,7 @@ def build_strix_agent( skills: list[str] | None = None, is_root: bool, scan_mode: str = "deep", + model: str | None = None, is_whitebox: bool = False, is_diff_scoped: bool = False, interactive: bool = False, @@ -715,7 +716,7 @@ def build_strix_agent( instructions=instructions, tools=tools, tool_use_behavior=_finish_tool_use_behavior, - model=None, + model=model, capabilities=[ Filesystem( configure_tools=_make_filesystem_configurator( @@ -742,6 +743,7 @@ def make_child_factory( chat_completions_tools: bool = False, strict_tool_schemas: bool = True, system_prompt_context: dict[str, Any] | None = None, + subagent_model: str | None = None, ) -> Any: """Return the runner-owned builder used by ``spawn_child_agent``. @@ -755,6 +757,7 @@ def make_child_factory( name=name, skills=skills, is_root=False, + model=subagent_model, scan_mode=scan_mode, is_whitebox=is_whitebox, is_diff_scoped=is_diff_scoped, From d0a61239b7d8f257af89936d00ccd87b05818c61 Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Sun, 20 Sep 2026 00:54:37 +0530 Subject: [PATCH 08/12] feat(runner): plumb subagent_model through run_strix_scan to make_child_factory (#1339) feat(runner): plumb subagent_model through run_strix_scan to make_child_factory (#1339) --- strix/core/runner.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/strix/core/runner.py b/strix/core/runner.py index b40a36b0e..c4329075b 100644 --- a/strix/core/runner.py +++ b/strix/core/runner.py @@ -206,6 +206,7 @@ async def run_strix_scan( status_sink: StatusSink | None = None, mcp_connection_requests: list[McpConnectionRequest] | None = None, mcp_status_sink: McpStatusSink | None = None, + subagent_model: str | None = None, ) -> RunResultBase | None: """Run or resume one Strix scan against a sandbox. @@ -502,14 +503,15 @@ async def run_strix_scan( ) child_agent_builder = make_child_factory( - scan_mode=scan_mode, - is_whitebox=is_whitebox, - is_diff_scoped=is_diff_scoped, - interactive=interactive, - chat_completions_tools=chat_completions_tools, - strict_tool_schemas=strict_tool_schemas, - system_prompt_context=scope_context, - ) + scan_mode=scan_mode, + is_whitebox=is_whitebox, + is_diff_scoped=is_diff_scoped, + interactive=interactive, + chat_completions_tools=chat_completions_tools, + strict_tool_schemas=strict_tool_schemas, + system_prompt_context=scope_context, + subagent_model=subagent_model or scan_config.get("subagent_model"), + ) async def spawn_child_agent(**kwargs: Any) -> dict[str, Any]: return await start_child_agent( From bf76507348a4993ef4ef28af70fa17d63ce3c8e7 Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:52:33 +0530 Subject: [PATCH 09/12] fix(runner): remove duplicate call and clean indentation for make_child_factory (#1339) - Remove duplicate parameter block and misplaced parenthesis in run_strix_scan - Ensure subagent_model is passed cleanly to make_child_factory - Resolves mypy/pytest syntax failure on line 534 --- strix/core/runner.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/strix/core/runner.py b/strix/core/runner.py index b8b9781d9..e58b3302f 100644 --- a/strix/core/runner.py +++ b/strix/core/runner.py @@ -530,16 +530,8 @@ async def run_strix_scan( strict_tool_schemas=strict_tool_schemas, system_prompt_context=scope_context, subagent_model=subagent_model or scan_config.get("subagent_model"), + supports_images=supports_images, ) - scan_mode=scan_mode, - is_whitebox=is_whitebox, - is_diff_scoped=is_diff_scoped, - interactive=interactive, - chat_completions_tools=chat_completions_tools, - strict_tool_schemas=strict_tool_schemas, - system_prompt_context=scope_context, - supports_images=supports_images, - ) async def spawn_child_agent(**kwargs: Any) -> dict[str, Any]: return await start_child_agent( From 88ad2a7c6f83c99f644e70bfcc12ec775ea5fa2a Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Mon, 12 Oct 2026 10:42:55 +0000 Subject: [PATCH 10/12] fix(runner, tests): clean runner syntax and update resume test mocks (#1339) - Remove duplicate parameter block and misplaced parenthesis in strix/core/runner.py - Update tests/test_cli_subagent_model.py to import load_resume_state directly - Fix mock Namespace attributes (instruction, local_sources) and target details for resume testing --- tests/test_cli_subagent_model.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_cli_subagent_model.py b/tests/test_cli_subagent_model.py index cfcc75791..5199b181e 100644 --- a/tests/test_cli_subagent_model.py +++ b/tests/test_cli_subagent_model.py @@ -2,7 +2,7 @@ import argparse from pathlib import Path import pytest -from strix.interface.cli_args import parse_arguments, _load_resume_state +from strix.interface.cli_args import parse_arguments, load_resume_state def test_subagent_model_default_none(monkeypatch): @@ -25,7 +25,7 @@ def test_subagent_model_flag_parsed(monkeypatch): def test_resume_restores_subagent_model(monkeypatch, tmp_path): """Verify _load_resume_state restores persisted subagent_model.""" fake_state = { - "targets_info": [{"type": "domain", "original": "example.com"}], + "targets_info": [{"type": "domain", "original": "example.com", "details": {}}], "subagent_model": "gpt-4o-mini", "scan_mode": "deep", } @@ -37,12 +37,14 @@ def test_resume_restores_subagent_model(monkeypatch, tmp_path): diff_scope="auto", targets_info=[], workspace_mount=None, + instruction=None, + local_sources=[], ) parser = argparse.ArgumentParser() monkeypatch.setattr("strix.report.writer.read_run_record", lambda run_dir: fake_state) - monkeypatch.setattr("strix.core_paths.run_dir_for", lambda r: tmp_path) + monkeypatch.setattr("strix.interface.cli_args.run_dir_for", lambda r: tmp_path) monkeypatch.setattr(Path, "exists", lambda self: True) - _load_resume_state(args, parser) + load_resume_state(args) assert args.subagent_model == "gpt-4o-mini" From f3a8fad9cafa198663d307a82a7e909703ad19a3 Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:56:37 +0000 Subject: [PATCH 11/12] fix(cli, cost): fix cli scan_config indentation and candidate model cost resolution (#1339) - Fix 4-space indentation alignment for scan_config in strix/interface/cli.py - Try raw candidate before resolved aliases in _estimate_response_cost in strix/report/state.py --- strix/interface/cli.py | 2 +- strix/report/state.py | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/strix/interface/cli.py b/strix/interface/cli.py index 2b89b67cf..c28d385c3 100644 --- a/strix/interface/cli.py +++ b/strix/interface/cli.py @@ -85,7 +85,7 @@ async def run_cli(args: Any) -> None: # noqa: PLR0915 scan_mode = getattr(args, "scan_mode", "deep") - scan_config: dict[str, Any] = { + scan_config: dict[str, Any] = { "scan_id": args.run_name, "targets": args.targets_info, "user_instruction": args.instruction or "", diff --git a/strix/report/state.py b/strix/report/state.py index fd0ebd944..569461bdc 100644 --- a/strix/report/state.py +++ b/strix/report/state.py @@ -1163,20 +1163,22 @@ def _estimate_response_cost(kwargs: Any, completion_response: Any) -> float | No candidates.append(model.rsplit("/", 1)[-1]) for candidate in candidates: + models_to_try = [candidate] resolved = resolve_litellm_model(candidate) - if not resolved: - continue - try: - value = completion_cost( - completion_response={"model": resolved, "usage": usage_payload}, - model=resolved, - ) - except Exception: # nosec B112 # noqa: BLE001, S112 - continue - if isinstance(value, int | float) and value > 0: - return float(value) - return None + if resolved and resolved not in models_to_try: + models_to_try.append(resolved) + for target_model in models_to_try: + try: + value = completion_cost( + completion_response={"model": target_model, "usage": usage_payload}, + model=target_model, + ) + except Exception: # nosec B112 # noqa: BLE001, S112 + continue + if isinstance(value, int | float) and value > 0: + return float(value) + return None def _usage_payload(completion_response: Any) -> dict[str, Any] | None: """Token counts as a plain dict, detached from the response's provider metadata.""" From 3b924a24a5f5a7347c1831f6a48c35e4c0fa26a4 Mon Sep 17 00:00:00 2001 From: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com> Date: Mon, 12 Oct 2026 12:06:34 +0000 Subject: [PATCH 12/12] style: fix ruff linting errors and formatting (#1339) - Remove trailing whitespace and empty line spaces across agents factory and cli args - Fix unused variables and lambda parameter names in subagent model CLI tests --- strix/agents/factory.py | 4 ++-- strix/core/runner.py | 20 ++++++++++---------- strix/interface/cli_args.py | 2 +- strix/report/state.py | 1 + tests/test_cli_subagent_model.py | 10 ++++------ 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/strix/agents/factory.py b/strix/agents/factory.py index 93e864011..eb6ad0e29 100644 --- a/strix/agents/factory.py +++ b/strix/agents/factory.py @@ -764,7 +764,7 @@ def make_child_factory( chat_completions_tools: bool = False, strict_tool_schemas: bool = True, system_prompt_context: dict[str, Any] | None = None, - subagent_model: str | None = None, + subagent_model: str | None = None, supports_images: bool = True, ) -> Any: """Return the runner-owned builder used by ``spawn_child_agent``. @@ -779,7 +779,7 @@ def make_child_factory( name=name, skills=skills, is_root=False, - model=subagent_model, + model=subagent_model, scan_mode=scan_mode, is_whitebox=is_whitebox, is_diff_scoped=is_diff_scoped, diff --git a/strix/core/runner.py b/strix/core/runner.py index e58b3302f..bd8cd8b2d 100644 --- a/strix/core/runner.py +++ b/strix/core/runner.py @@ -522,16 +522,16 @@ async def run_strix_scan( ) child_agent_builder = make_child_factory( - scan_mode=scan_mode, - is_whitebox=is_whitebox, - is_diff_scoped=is_diff_scoped, - interactive=interactive, - chat_completions_tools=chat_completions_tools, - strict_tool_schemas=strict_tool_schemas, - system_prompt_context=scope_context, - subagent_model=subagent_model or scan_config.get("subagent_model"), - supports_images=supports_images, - ) + scan_mode=scan_mode, + is_whitebox=is_whitebox, + is_diff_scoped=is_diff_scoped, + interactive=interactive, + chat_completions_tools=chat_completions_tools, + strict_tool_schemas=strict_tool_schemas, + system_prompt_context=scope_context, + subagent_model=subagent_model or scan_config.get("subagent_model"), + supports_images=supports_images, + ) async def spawn_child_agent(**kwargs: Any) -> dict[str, Any]: return await start_child_agent( diff --git a/strix/interface/cli_args.py b/strix/interface/cli_args.py index 497dcf863..88ca29844 100644 --- a/strix/interface/cli_args.py +++ b/strix/interface/cli_args.py @@ -546,7 +546,7 @@ def load_resume_state(args: argparse.Namespace) -> None: persisted_subagent_model = state.get("subagent_model") if not getattr(args, "subagent_model", None) and persisted_subagent_model: args.subagent_model = persisted_subagent_model - + agents_path = runtime_state_dir(run_dir) / "agents.json" if not agents_path.exists(): raise ResumeError( diff --git a/strix/report/state.py b/strix/report/state.py index 569461bdc..8fa4e6a1c 100644 --- a/strix/report/state.py +++ b/strix/report/state.py @@ -1180,6 +1180,7 @@ def _estimate_response_cost(kwargs: Any, completion_response: Any) -> float | No return float(value) return None + def _usage_payload(completion_response: Any) -> dict[str, Any] | None: """Token counts as a plain dict, detached from the response's provider metadata.""" usage: Any = getattr(completion_response, "usage", None) diff --git a/tests/test_cli_subagent_model.py b/tests/test_cli_subagent_model.py index 5199b181e..49e938058 100644 --- a/tests/test_cli_subagent_model.py +++ b/tests/test_cli_subagent_model.py @@ -1,8 +1,7 @@ import argparse from pathlib import Path -import pytest -from strix.interface.cli_args import parse_arguments, load_resume_state +from strix.interface.cli_args import load_resume_state, parse_arguments def test_subagent_model_default_none(monkeypatch): @@ -40,11 +39,10 @@ def test_resume_restores_subagent_model(monkeypatch, tmp_path): instruction=None, local_sources=[], ) - parser = argparse.ArgumentParser() - monkeypatch.setattr("strix.report.writer.read_run_record", lambda run_dir: fake_state) - monkeypatch.setattr("strix.interface.cli_args.run_dir_for", lambda r: tmp_path) - monkeypatch.setattr(Path, "exists", lambda self: True) + monkeypatch.setattr("strix.report.writer.read_run_record", lambda _run_dir: fake_state) + monkeypatch.setattr("strix.interface.cli_args.run_dir_for", lambda _r: tmp_path) + monkeypatch.setattr(Path, "exists", lambda _self: True) load_resume_state(args) assert args.subagent_model == "gpt-4o-mini"