mirror of
https://github.com/usestrix/strix.git
synced 2026-09-12 23:01:05 +00:00
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
This commit is contained in:
parent
15c95718e6
commit
3a8d319f7f
4 changed files with 62 additions and 47 deletions
|
|
@ -97,35 +97,48 @@ class StrixAgent(BaseAgent):
|
|||
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' <target type="repository">{repo["url"]} (code at: {repo["workspace_path"]})</target>'
|
||||
)
|
||||
else:
|
||||
task_parts.append(f"- {repo['url']}")
|
||||
target_lines.append(f' <target type="repository">{repo["url"]}</target>')
|
||||
|
||||
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' <target type="local_code">{code["path"]} (code at: {code["workspace_path"]})</target>'
|
||||
)
|
||||
|
||||
if urls:
|
||||
task_parts.append("\n\nURLs:")
|
||||
task_parts.extend(f"- {url}" for url in urls)
|
||||
for url in urls:
|
||||
target_lines.append(f' <target type="url">{url}</target>')
|
||||
|
||||
if ip_addresses:
|
||||
task_parts.append("\n\nIP Addresses:")
|
||||
task_parts.extend(f"- {ip}" for ip in ip_addresses)
|
||||
for ip in ip_addresses:
|
||||
target_lines.append(f' <target type="ip">{ip}</target>')
|
||||
|
||||
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)"
|
||||
|
||||
diff_scope_section = ""
|
||||
if diff_scope.get("active"):
|
||||
task_parts.append("\n\nScope Constraints:")
|
||||
task_parts.append(
|
||||
"- Pull request diff-scope mode is active. Prioritize changed files "
|
||||
"and use other files only for context."
|
||||
scope_lines = ["<diff_scope>"]
|
||||
scope_lines.append(
|
||||
" <note>Pull request diff-scope mode is active. Prioritize changed files "
|
||||
"and use other files only for context.</note>"
|
||||
)
|
||||
for repo_scope in diff_scope.get("repos", []):
|
||||
repo_label = (
|
||||
|
|
@ -135,15 +148,23 @@ class StrixAgent(BaseAgent):
|
|||
)
|
||||
changed_count = repo_scope.get("analyzable_files_count", 0)
|
||||
deleted_count = repo_scope.get("deleted_files_count", 0)
|
||||
task_parts.append(
|
||||
f"- {repo_label}: {changed_count} changed file(s) in primary scope"
|
||||
scope_lines.append(
|
||||
f' <repo name="{repo_label}">{changed_count} changed file(s) in primary scope</repo>'
|
||||
)
|
||||
if deleted_count:
|
||||
task_parts.append(
|
||||
f"- {repo_label}: {deleted_count} deleted file(s) are context-only"
|
||||
scope_lines.append(
|
||||
f' <repo name="{repo_label}">{deleted_count} deleted file(s) are context-only</repo>'
|
||||
)
|
||||
scope_lines.append("</diff_scope>")
|
||||
diff_scope_section = "\n" + "\n".join(scope_lines) + "\n"
|
||||
|
||||
task_description = " ".join(task_parts)
|
||||
task_description = (
|
||||
f"<scan_task>\n"
|
||||
f"<targets>\n{targets_block}\n</targets>\n"
|
||||
f"<mode>{mode}</mode>\n"
|
||||
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"
|
||||
f"{diff_scope_section}</scan_task>"
|
||||
)
|
||||
|
||||
if user_instructions:
|
||||
task_description += f"\n\nSpecial instructions: {user_instructions}"
|
||||
|
|
|
|||
|
|
@ -411,6 +411,17 @@ class BaseAgent(metaclass=AgentMeta):
|
|||
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:
|
||||
|
|
@ -485,33 +496,17 @@ class BaseAgent(metaclass=AgentMeta):
|
|||
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"""<inter_agent_message>
|
||||
<delivery_notice>
|
||||
<important>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.</important>
|
||||
</delivery_notice>
|
||||
<sender>
|
||||
<agent_name>{sender_name}</agent_name>
|
||||
<agent_id>{sender_id}</agent_id>
|
||||
</sender>
|
||||
<message_metadata>
|
||||
<type>{message.get("message_type", "information")}</type>
|
||||
<priority>{message.get("priority", "normal")}</priority>
|
||||
<timestamp>{message.get("timestamp", "")}</timestamp>
|
||||
</message_metadata>
|
||||
<content>
|
||||
message_content = f"""<agent_message
|
||||
from="{sender_name}"
|
||||
id="{sender_id}"
|
||||
type="{message.get("message_type", "information")}"
|
||||
priority="{message.get("priority", "normal")}">
|
||||
{message.get("content", "")}
|
||||
</content>
|
||||
<delivery_info>
|
||||
<note>This message was delivered during your task execution.
|
||||
Please acknowledge and respond if needed.</note>
|
||||
</delivery_info>
|
||||
</inter_agent_message>"""
|
||||
</agent_message>"""
|
||||
state.add_message("user", message_content.strip())
|
||||
|
||||
message["read"] = True
|
||||
|
|
|
|||
|
|
@ -44,9 +44,7 @@ class AgentState(BaseModel):
|
|||
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
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ def clean_content(content: str) -> str:
|
|||
|
||||
hidden_xml_patterns = [
|
||||
r"<inter_agent_message>.*?</inter_agent_message>",
|
||||
r"<agent_message\b[^>]*>.*?</agent_message>",
|
||||
r"<agent_completion_report>.*?</agent_completion_report>",
|
||||
]
|
||||
for pattern in hidden_xml_patterns:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue