From 1ca72c07c4e36164693c13d34b1ce898fcdb319b Mon Sep 17 00:00:00 2001 From: 0xhis <0xhis@users.noreply.github.com> Date: Sat, 21 Mar 2026 00:52:31 -0700 Subject: [PATCH 1/2] feat(agent): structured task format and workflow improvements - Replace free-form task description with structured XML format () in StrixAgent for clearer LLM parsing - Replace verbose with compact format to reduce token overhead in inter-agent communication - Add corrective message when agents respond with plain text instead of tool calls, enforcing tool-call-only behavior - Simplify thinking_blocks type annotation in AgentState - Add pattern to clean_content() for hidden XML cleanup --- strix/agents/StrixAgent/strix_agent.py | 42 +++++++++++++++++--------- strix/agents/base_agent.py | 41 +++++++++++-------------- strix/agents/state.py | 4 +-- strix/llm/utils.py | 1 + 4 files changed, 48 insertions(+), 40 deletions(-) diff --git a/strix/agents/StrixAgent/strix_agent.py b/strix/agents/StrixAgent/strix_agent.py index 1519ed512..2c74cd146 100644 --- a/strix/agents/StrixAgent/strix_agent.py +++ b/strix/agents/StrixAgent/strix_agent.py @@ -57,31 +57,45 @@ async def execute_scan(self, scan_config: dict[str, Any]) -> dict[str, Any]: # elif target_type == "ip_address": ip_addresses.append(details["target_ip"]) - task_parts = [] + target_lines = [] if repositories: - task_parts.append("\n\nRepositories:") for repo in repositories: if repo["workspace_path"]: - task_parts.append(f"- {repo['url']} (available at: {repo['workspace_path']})") + target_lines.append(f' {repo["url"]} (code at: {repo["workspace_path"]})') else: - task_parts.append(f"- {repo['url']}") + target_lines.append(f' {repo["url"]}') if local_code: - task_parts.append("\n\nLocal Codebases:") - task_parts.extend( - f"- {code['path']} (available at: {code['workspace_path']})" for code in local_code - ) + for code in local_code: + target_lines.append(f' {code["path"]} (code at: {code["workspace_path"]})') if urls: - task_parts.append("\n\nURLs:") - task_parts.extend(f"- {url}" for url in urls) + for url in urls: + target_lines.append(f' {url}') if ip_addresses: - task_parts.append("\n\nIP Addresses:") - task_parts.extend(f"- {ip}" for ip in ip_addresses) - - task_description = " ".join(task_parts) + for ip in ip_addresses: + target_lines.append(f' {ip}') + + targets_block = "\n".join(target_lines) + + has_code = bool(repositories or local_code) + has_urls = bool(urls or ip_addresses) + if has_code and has_urls: + mode = "COMBINED MODE (code + deployed target)" + elif has_code: + mode = "WHITE-BOX (source code provided)" + else: + mode = "BLACK-BOX (URL/domain targets)" + + task_description = ( + f"\n" + f"\n{targets_block}\n\n" + f"{mode}\n" + f"Begin security assessment NOW. Your first tool call must be create_agent to spawn context-gathering subagents for the targets listed above. Do NOT call wait_for_message — the targets are already specified.\n" + f"" + ) if user_instructions: task_description += f"\n\nSpecial instructions: {user_instructions}" diff --git a/strix/agents/base_agent.py b/strix/agents/base_agent.py index 74fe21ef5..7767a5b63 100644 --- a/strix/agents/base_agent.py +++ b/strix/agents/base_agent.py @@ -410,6 +410,17 @@ async def _process_iteration(self, tracer: Optional["Tracer"]) -> bool | None: if actions: return await self._execute_actions(actions, tracer) + corrective_message = ( + "You responded with plain text instead of a tool call. " + "While the agent loop is running, EVERY response MUST be a tool call. " + "Do NOT send plain text messages. Act via tools:\n" + "- Use the think tool to reason through problems\n" + "- Use create_agent to spawn subagents for testing\n" + "- Use terminal_execute to run commands\n" + "- Use wait_for_message ONLY when waiting for subagent results\n" + "Review your task and take action now." + ) + self.state.add_message("user", corrective_message) return None async def _execute_actions(self, actions: list[Any], tracer: Optional["Tracer"]) -> bool: @@ -484,33 +495,17 @@ def _check_agent_messages(self, state: AgentState) -> None: # noqa: PLR0912 sender_name = "User" state.add_message("user", message.get("content", "")) else: + sender_name = sender_id or "Unknown" if sender_id and sender_id in _agent_graph.get("nodes", {}): sender_name = _agent_graph["nodes"][sender_id]["name"] - message_content = f""" - - You have received a message from another agent. You should acknowledge - this message and respond appropriately based on its content. However, DO NOT echo - back or repeat the entire message structure in your response. Simply process the - content and respond naturally as/if needed. - - - {sender_name} - {sender_id} - - - {message.get("message_type", "information")} - {message.get("priority", "normal")} - {message.get("timestamp", "")} - - + message_content = f""" {message.get("content", "")} - - - This message was delivered during your task execution. - Please acknowledge and respond if needed. - -""" +""" state.add_message("user", message_content.strip()) message["read"] = True diff --git a/strix/agents/state.py b/strix/agents/state.py index da04ee7f9..5e84d0f50 100644 --- a/strix/agents/state.py +++ b/strix/agents/state.py @@ -44,9 +44,7 @@ def increment_iteration(self) -> None: self.iteration += 1 self.last_updated = datetime.now(UTC).isoformat() - def add_message( - self, role: str, content: Any, thinking_blocks: list[dict[str, Any]] | None = None - ) -> None: + def add_message(self, role: str, content: Any, thinking_blocks: list | None = None) -> None: message = {"role": role, "content": content} if thinking_blocks: message["thinking_blocks"] = thinking_blocks diff --git a/strix/llm/utils.py b/strix/llm/utils.py index cb61a81ed..b3da25164 100644 --- a/strix/llm/utils.py +++ b/strix/llm/utils.py @@ -150,6 +150,7 @@ def clean_content(content: str) -> str: hidden_xml_patterns = [ r".*?", + r"]*>.*?", r".*?", ] for pattern in hidden_xml_patterns: From 6f71f5fdb81804e236b72ea11f98b547f624481d Mon Sep 17 00:00:00 2001 From: 0xhis <0xhis@users.noreply.github.com> Date: Sat, 21 Mar 2026 01:32:27 -0700 Subject: [PATCH 2/2] fix(agent): escape XML values, CDATA content, generic corrective message - Add html.escape() to target values in (URLs, paths, IPs) - Escape sender_name/sender_id in attributes - CDATA-wrap message content in to handle any text - Make corrective message generic (no StrixAgent-specific tool names) --- strix/agents/StrixAgent/strix_agent.py | 14 ++++++++------ strix/agents/base_agent.py | 18 ++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/strix/agents/StrixAgent/strix_agent.py b/strix/agents/StrixAgent/strix_agent.py index 2c74cd146..e76ef2a8d 100644 --- a/strix/agents/StrixAgent/strix_agent.py +++ b/strix/agents/StrixAgent/strix_agent.py @@ -1,5 +1,7 @@ from typing import Any +import html + from strix.agents.base_agent import BaseAgent from strix.llm.config import LLMConfig @@ -59,24 +61,24 @@ async def execute_scan(self, scan_config: dict[str, Any]) -> dict[str, Any]: # target_lines = [] - if repositories: +if repositories: for repo in repositories: if repo["workspace_path"]: - target_lines.append(f' {repo["url"]} (code at: {repo["workspace_path"]})') + target_lines.append(f' {html.escape(repo["url"])} (code at: {html.escape(repo["workspace_path"])})') else: - target_lines.append(f' {repo["url"]}') + target_lines.append(f' {html.escape(repo["url"])}') if local_code: for code in local_code: - target_lines.append(f' {code["path"]} (code at: {code["workspace_path"]})') + target_lines.append(f' {html.escape(code["path"])} (code at: {html.escape(code["workspace_path"])})') if urls: for url in urls: - target_lines.append(f' {url}') + target_lines.append(f' {html.escape(url)}') if ip_addresses: for ip in ip_addresses: - target_lines.append(f' {ip}') + target_lines.append(f' {html.escape(ip)}') targets_block = "\n".join(target_lines) diff --git a/strix/agents/base_agent.py b/strix/agents/base_agent.py index 7767a5b63..e5e191046 100644 --- a/strix/agents/base_agent.py +++ b/strix/agents/base_agent.py @@ -1,5 +1,6 @@ import asyncio import contextlib +import html import logging from typing import TYPE_CHECKING, Any, Optional @@ -413,11 +414,7 @@ async def _process_iteration(self, tracer: Optional["Tracer"]) -> bool | None: corrective_message = ( "You responded with plain text instead of a tool call. " "While the agent loop is running, EVERY response MUST be a tool call. " - "Do NOT send plain text messages. Act via tools:\n" - "- Use the think tool to reason through problems\n" - "- Use create_agent to spawn subagents for testing\n" - "- Use terminal_execute to run commands\n" - "- Use wait_for_message ONLY when waiting for subagent results\n" + "Do NOT send plain text messages. Act via your available tools. " "Review your task and take action now." ) self.state.add_message("user", corrective_message) @@ -499,12 +496,13 @@ def _check_agent_messages(self, state: AgentState) -> None: # noqa: PLR0912 if sender_id and sender_id in _agent_graph.get("nodes", {}): sender_name = _agent_graph["nodes"][sender_id]["name"] + content = message.get("content", "") message_content = f""" -{message.get("content", "")} +from="{html.escape(sender_name)}" +id="{html.escape(str(sender_id))}" +type="{html.escape(message.get("message_type", "information"))}" +priority="{html.escape(message.get("priority", "normal"))}"> + """ state.add_message("user", message_content.strip())