Skip to content

Commit 3a8d319

Browse files
0xhisST-2
authored andcommitted
feat(agent): structured task format and workflow improvements
- Replace free-form task description with structured XML format (<scan_task><targets><mode>) in StrixAgent for clearer LLM parsing - Replace verbose <inter_agent_message> with compact <agent_message> 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 <agent_message> pattern to clean_content() for hidden XML cleanup
1 parent 15c9571 commit 3a8d319

4 files changed

Lines changed: 63 additions & 48 deletions

File tree

strix/agents/StrixAgent/strix_agent.py

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -97,35 +97,48 @@ async def execute_scan(self, scan_config: dict[str, Any]) -> dict[str, Any]: #
9797
elif target_type == "ip_address":
9898
ip_addresses.append(details["target_ip"])
9999

100-
task_parts = []
100+
target_lines = []
101101

102102
if repositories:
103-
task_parts.append("\n\nRepositories:")
104103
for repo in repositories:
105104
if repo["workspace_path"]:
106-
task_parts.append(f"- {repo['url']} (available at: {repo['workspace_path']})")
105+
target_lines.append(
106+
f' <target type="repository">{repo["url"]} (code at: {repo["workspace_path"]})</target>'
107+
)
107108
else:
108-
task_parts.append(f"- {repo['url']}")
109+
target_lines.append(f' <target type="repository">{repo["url"]}</target>')
109110

110111
if local_code:
111-
task_parts.append("\n\nLocal Codebases:")
112-
task_parts.extend(
113-
f"- {code['path']} (available at: {code['workspace_path']})" for code in local_code
114-
)
112+
for code in local_code:
113+
target_lines.append(
114+
f' <target type="local_code">{code["path"]} (code at: {code["workspace_path"]})</target>'
115+
)
115116

116117
if urls:
117-
task_parts.append("\n\nURLs:")
118-
task_parts.extend(f"- {url}" for url in urls)
118+
for url in urls:
119+
target_lines.append(f' <target type="url">{url}</target>')
119120

120121
if ip_addresses:
121-
task_parts.append("\n\nIP Addresses:")
122-
task_parts.extend(f"- {ip}" for ip in ip_addresses)
122+
for ip in ip_addresses:
123+
target_lines.append(f' <target type="ip">{ip}</target>')
123124

125+
targets_block = "\n".join(target_lines)
126+
127+
has_code = bool(repositories or local_code)
128+
has_urls = bool(urls or ip_addresses)
129+
if has_code and has_urls:
130+
mode = "COMBINED MODE (code + deployed target)"
131+
elif has_code:
132+
mode = "WHITE-BOX (source code provided)"
133+
else:
134+
mode = "BLACK-BOX (URL/domain targets)"
135+
136+
diff_scope_section = ""
124137
if diff_scope.get("active"):
125-
task_parts.append("\n\nScope Constraints:")
126-
task_parts.append(
127-
"- Pull request diff-scope mode is active. Prioritize changed files "
128-
"and use other files only for context."
138+
scope_lines = ["<diff_scope>"]
139+
scope_lines.append(
140+
" <note>Pull request diff-scope mode is active. Prioritize changed files "
141+
"and use other files only for context.</note>"
129142
)
130143
for repo_scope in diff_scope.get("repos", []):
131144
repo_label = (
@@ -135,15 +148,23 @@ async def execute_scan(self, scan_config: dict[str, Any]) -> dict[str, Any]: #
135148
)
136149
changed_count = repo_scope.get("analyzable_files_count", 0)
137150
deleted_count = repo_scope.get("deleted_files_count", 0)
138-
task_parts.append(
139-
f"- {repo_label}: {changed_count} changed file(s) in primary scope"
151+
scope_lines.append(
152+
f' <repo name="{repo_label}">{changed_count} changed file(s) in primary scope</repo>'
140153
)
141154
if deleted_count:
142-
task_parts.append(
143-
f"- {repo_label}: {deleted_count} deleted file(s) are context-only"
155+
scope_lines.append(
156+
f' <repo name="{repo_label}">{deleted_count} deleted file(s) are context-only</repo>'
144157
)
145-
146-
task_description = " ".join(task_parts)
158+
scope_lines.append("</diff_scope>")
159+
diff_scope_section = "\n" + "\n".join(scope_lines) + "\n"
160+
161+
task_description = (
162+
f"<scan_task>\n"
163+
f"<targets>\n{targets_block}\n</targets>\n"
164+
f"<mode>{mode}</mode>\n"
165+
f"<action>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.</action>\n"
166+
f"{diff_scope_section}</scan_task>"
167+
)
147168

148169
if user_instructions:
149170
task_description += f"\n\nSpecial instructions: {user_instructions}"

strix/agents/base_agent.py

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,17 @@ async def _process_iteration(self, tracer: Optional["Tracer"]) -> bool | None:
411411
if actions:
412412
return await self._execute_actions(actions, tracer)
413413

414+
corrective_message = (
415+
"You responded with plain text instead of a tool call. "
416+
"While the agent loop is running, EVERY response MUST be a tool call. "
417+
"Do NOT send plain text messages. Act via tools:\n"
418+
"- Use the think tool to reason through problems\n"
419+
"- Use create_agent to spawn subagents for testing\n"
420+
"- Use terminal_execute to run commands\n"
421+
"- Use wait_for_message ONLY when waiting for subagent results\n"
422+
"Review your task and take action now."
423+
)
424+
self.state.add_message("user", corrective_message)
414425
return None
415426

416427
async def _execute_actions(self, actions: list[Any], tracer: Optional["Tracer"]) -> bool:
@@ -485,33 +496,17 @@ def _check_agent_messages(self, state: AgentState) -> None: # noqa: PLR0912
485496
sender_name = "User"
486497
state.add_message("user", message.get("content", ""))
487498
else:
499+
sender_name = sender_id or "Unknown"
488500
if sender_id and sender_id in _agent_graph.get("nodes", {}):
489501
sender_name = _agent_graph["nodes"][sender_id]["name"]
490502

491-
message_content = f"""<inter_agent_message>
492-
<delivery_notice>
493-
<important>You have received a message from another agent. You should acknowledge
494-
this message and respond appropriately based on its content. However, DO NOT echo
495-
back or repeat the entire message structure in your response. Simply process the
496-
content and respond naturally as/if needed.</important>
497-
</delivery_notice>
498-
<sender>
499-
<agent_name>{sender_name}</agent_name>
500-
<agent_id>{sender_id}</agent_id>
501-
</sender>
502-
<message_metadata>
503-
<type>{message.get("message_type", "information")}</type>
504-
<priority>{message.get("priority", "normal")}</priority>
505-
<timestamp>{message.get("timestamp", "")}</timestamp>
506-
</message_metadata>
507-
<content>
503+
message_content = f"""<agent_message
504+
from="{sender_name}"
505+
id="{sender_id}"
506+
type="{message.get("message_type", "information")}"
507+
priority="{message.get("priority", "normal")}">
508508
{message.get("content", "")}
509-
</content>
510-
<delivery_info>
511-
<note>This message was delivered during your task execution.
512-
Please acknowledge and respond if needed.</note>
513-
</delivery_info>
514-
</inter_agent_message>"""
509+
</agent_message>"""
515510
state.add_message("user", message_content.strip())
516511

517512
message["read"] = True

strix/agents/state.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ def increment_iteration(self) -> None:
4444
self.iteration += 1
4545
self.last_updated = datetime.now(UTC).isoformat()
4646

47-
def add_message(
48-
self, role: str, content: Any, thinking_blocks: list[dict[str, Any]] | None = None
49-
) -> None:
47+
def add_message(self, role: str, content: Any, thinking_blocks: list | None = None) -> None:
5048
message = {"role": role, "content": content}
5149
if thinking_blocks:
5250
message["thinking_blocks"] = thinking_blocks

strix/llm/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ def clean_content(content: str) -> str:
150150

151151
hidden_xml_patterns = [
152152
r"<inter_agent_message>.*?</inter_agent_message>",
153+
r"<agent_message\b[^>]*>.*?</agent_message>",
153154
r"<agent_completion_report>.*?</agent_completion_report>",
154155
]
155156
for pattern in hidden_xml_patterns:

0 commit comments

Comments
 (0)