diff --git a/strix/agents/prompt.py b/strix/agents/prompt.py index 5d902176..0626a069 100644 --- a/strix/agents/prompt.py +++ b/strix/agents/prompt.py @@ -7,6 +7,7 @@ from typing import Any from jinja2 import Environment, FileSystemLoader, select_autoescape +from strix.config import load_settings from strix.skills import get_available_skills, load_skills from strix.utils.resource_paths import get_strix_resource_path @@ -16,6 +17,16 @@ logger = logging.getLogger(__name__) _PROMPT_DIRNAME = "prompts" +# Appended to the root coordinator's skill when STRIX_DISABLE_FIX_AGENTS is +# set, overriding the "Fix agent provides remediation" delegation step. +_DISABLE_FIX_AGENTS_DIRECTIVE = ( + "\n\n## Fix Agents Disabled\n\n" + "Do NOT spawn dedicated fix or remediation subagents. Remediation is " + "captured inline in each report's `remediation_steps` field, which is " + "sufficient. A separate agent that only writes fixes wastes tokens and " + "concurrency, so skip that delegation step entirely." +) + def _resolve_skills( *, @@ -85,6 +96,12 @@ def render_system_prompt( is_root=is_root, ) skill_content = load_skills(skills_to_load) + if ( + is_root + and "root_agent" in skill_content + and load_settings().agents.disable_fix_agents + ): + skill_content["root_agent"] += _DISABLE_FIX_AGENTS_DIRECTIVE env.globals["get_skill"] = lambda name: skill_content.get(name, "") rendered = env.get_template("system_prompt.jinja").render( diff --git a/strix/config/__init__.py b/strix/config/__init__.py index fda68602..ca27b7de 100644 --- a/strix/config/__init__.py +++ b/strix/config/__init__.py @@ -3,9 +3,9 @@ Public surface: - :class:`Settings` — composite model. Get via :func:`load_settings`. -- :class:`LlmSettings`, :class:`RuntimeSettings`, :class:`TelemetrySettings`, - :class:`IntegrationSettings` — sub-models, attribute-accessed off - ``Settings``. +- :class:`LlmSettings`, :class:`RuntimeSettings`, :class:`AgentSettings`, + :class:`TelemetrySettings`, :class:`IntegrationSettings` — sub-models, + attribute-accessed off ``Settings``. - :func:`load_settings` — memoized resolve (env > JSON file > defaults). - :func:`apply_config_override` — switch the JSON source to a custom path. - :func:`persist_current` — write currently-set env vars to the active file. @@ -17,6 +17,7 @@ from strix.config.loader import ( persist_current, ) from strix.config.settings import ( + AgentSettings, IntegrationSettings, LlmSettings, RuntimeSettings, @@ -26,6 +27,7 @@ from strix.config.settings import ( __all__ = [ + "AgentSettings", "IntegrationSettings", "LlmSettings", "RuntimeSettings", diff --git a/strix/config/settings.py b/strix/config/settings.py index 91fbdef1..0b2d3159 100644 --- a/strix/config/settings.py +++ b/strix/config/settings.py @@ -54,6 +54,15 @@ class RuntimeSettings(BaseSettings): max_local_copy_mb: int = Field(default=1024, alias="STRIX_MAX_LOCAL_COPY_MB") +class AgentSettings(BaseSettings): + model_config = _BASE_CONFIG + + # When true, the root coordinator is instructed not to spawn dedicated + # fix/remediation subagents; remediation still lands in each report's + # ``remediation_steps``. Off by default (no behavior change for others). + disable_fix_agents: bool = Field(default=False, alias="STRIX_DISABLE_FIX_AGENTS") + + class TelemetrySettings(BaseSettings): model_config = _BASE_CONFIG @@ -71,5 +80,6 @@ class Settings(BaseSettings): llm: LlmSettings = Field(default_factory=LlmSettings) runtime: RuntimeSettings = Field(default_factory=RuntimeSettings) + agents: AgentSettings = Field(default_factory=AgentSettings) telemetry: TelemetrySettings = Field(default_factory=TelemetrySettings) integrations: IntegrationSettings = Field(default_factory=IntegrationSettings)