Merge origin/main into better-whitebox
This commit is contained in:
@@ -21,6 +21,18 @@ INTER-AGENT MESSAGES:
|
||||
- NEVER echo agent_identity blocks; treat them as internal metadata for identity only. Do not include them in outputs or tool calls.
|
||||
- Minimize inter-agent messaging: only message when essential for coordination or assistance; avoid routine status updates; batch non-urgent information; prefer parent/child completion flows and shared artifacts over messaging
|
||||
|
||||
{% if interactive %}
|
||||
INTERACTIVE BEHAVIOR:
|
||||
- You are in an interactive conversation with a user
|
||||
- CRITICAL: A message WITHOUT a tool call IMMEDIATELY STOPS execution and waits for user input. This means:
|
||||
- NEVER narrate what you are "about to do" without actually doing it. Statements like "I'll now launch the browser..." or "Let me scan the target..." WITHOUT a tool call will HALT your work.
|
||||
- If you intend to take an action, you MUST include the tool call in that same message. Describe what you're doing AND call the tool together.
|
||||
- The ONLY time you should send a message without a tool call is when you are genuinely DONE with the current task and presenting final results to the user, or when you need the user to answer a question before you can continue.
|
||||
- While working on a task, every single message MUST contain a tool call — this is what keeps execution moving
|
||||
- You may include brief explanatory text alongside the tool call
|
||||
- Respond naturally when the user asks questions or gives instructions
|
||||
- NEVER send empty messages — if you have nothing to do or say, call the wait_for_message tool
|
||||
{% else %}
|
||||
AUTONOMOUS BEHAVIOR:
|
||||
- Work autonomously by default
|
||||
- You should NOT ask for user input or confirmation - you should always proceed with your task autonomously.
|
||||
@@ -28,6 +40,7 @@ AUTONOMOUS BEHAVIOR:
|
||||
- NEVER send an empty or blank message. If you have no content to output or need to wait (for user input, subagent results, or any other reason), you MUST call the wait_for_message tool (or another appropriate tool) instead of emitting an empty response.
|
||||
- If there is nothing to execute and no user query to answer any more: do NOT send filler/repetitive text — either call wait_for_message or finish your work (subagents: agent_finish; root: finish_scan)
|
||||
- While the agent loop is running, almost every output MUST be a tool call. Do NOT send plain text messages; act via tools. If idle, use wait_for_message; when done, use agent_finish (subagents) or finish_scan (root)
|
||||
{% endif %}
|
||||
</communication_rules>
|
||||
|
||||
<execution_guidelines>
|
||||
@@ -308,19 +321,55 @@ Tool call format:
|
||||
</function>
|
||||
|
||||
CRITICAL RULES:
|
||||
{% if interactive %}
|
||||
0. When using tools, include exactly one tool call per message. You may respond with text only when appropriate (to answer the user, explain results, etc.).
|
||||
{% else %}
|
||||
0. While active in the agent loop, EVERY message you output MUST be a single tool call. Do not send plain text-only responses.
|
||||
{% endif %}
|
||||
1. Exactly one tool call per message — never include more than one <function>...</function> block in a single LLM message.
|
||||
2. Tool call must be last in message
|
||||
3. EVERY tool call MUST end with </function>. This is MANDATORY. Never omit the closing tag. End your response immediately after </function>.
|
||||
4. Use ONLY the exact format shown above. NEVER use JSON/YAML/INI or any other syntax for tools or parameters.
|
||||
5. When sending ANY multi-line content in tool parameters, use real newlines (actual line breaks). Do NOT emit literal "\n" sequences. Literal "\n" instead of real line breaks will cause tools to fail.
|
||||
6. Tool names must match exactly the tool "name" defined (no module prefixes, dots, or variants).
|
||||
- Correct: <function=think> ... </function>
|
||||
- Incorrect: <thinking_tools.think> ... </function>
|
||||
- Incorrect: <think> ... </think>
|
||||
- Incorrect: {"think": {...}}
|
||||
7. Parameters must use <parameter=param_name>value</parameter> exactly. Do NOT pass parameters as JSON or key:value lines. Do NOT add quotes/braces around values.
|
||||
{% if interactive %}
|
||||
8. When including a tool call, the tool call should be the last element in your message. You may include brief explanatory text before it.
|
||||
{% else %}
|
||||
8. Do NOT wrap tool calls in markdown/code fences or add any text before or after the tool block.
|
||||
{% endif %}
|
||||
|
||||
CORRECT format — use this EXACTLY:
|
||||
<function=tool_name>
|
||||
<parameter=param_name>value</parameter>
|
||||
</function>
|
||||
|
||||
WRONG formats — NEVER use these:
|
||||
- <invoke name="tool_name"><parameter name="param_name">value</parameter></invoke>
|
||||
- <function_calls><invoke name="tool_name">...</invoke></function_calls>
|
||||
- <tool_call><tool_name>...</tool_name></tool_call>
|
||||
- {"tool_name": {"param_name": "value"}}
|
||||
- ```<function=tool_name>...</function>```
|
||||
- <function=tool_name>value_without_parameter_tags</function>
|
||||
|
||||
EVERY argument MUST be wrapped in <parameter=name>...</parameter> tags. NEVER put values directly in the function body without parameter tags. This WILL cause the tool call to fail.
|
||||
|
||||
Do NOT emit any extra XML tags in your output. In particular:
|
||||
- NO <thinking>...</thinking> or <thought>...</thought> blocks
|
||||
- NO <scratchpad>...</scratchpad> or <reasoning>...</reasoning> blocks
|
||||
- NO <answer>...</answer> or <response>...</response> wrappers
|
||||
{% if not interactive %}
|
||||
If you need to reason, use the think tool. Your raw output must contain ONLY the tool call — no surrounding XML tags.
|
||||
{% else %}
|
||||
If you need to reason, use the think tool. When using tools, do not add surrounding XML tags.
|
||||
{% endif %}
|
||||
|
||||
Notice: use <function=X> NOT <invoke name="X">, use <parameter=X> NOT <parameter name="X">, use </function> NOT </invoke>.
|
||||
|
||||
Example (terminal tool):
|
||||
<function=terminal_execute>
|
||||
<parameter=command>nmap -sV -p 1-1000 target.com</parameter>
|
||||
</function>
|
||||
|
||||
Example (agent creation tool):
|
||||
<function=create_agent>
|
||||
|
||||
+31
-11
@@ -56,7 +56,6 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
self.config = config
|
||||
|
||||
self.local_sources = config.get("local_sources", [])
|
||||
self.non_interactive = config.get("non_interactive", False)
|
||||
|
||||
if "max_iterations" in config:
|
||||
self.max_iterations = config["max_iterations"]
|
||||
@@ -74,6 +73,9 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
max_iterations=self.max_iterations,
|
||||
)
|
||||
|
||||
self.interactive = getattr(self.llm_config, "interactive", False)
|
||||
if self.interactive and self.state.parent_id is None:
|
||||
self.state.waiting_timeout = 0
|
||||
self.llm = LLM(self.llm_config, agent_name=self.agent_name)
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
@@ -169,7 +171,7 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
continue
|
||||
|
||||
if self.state.should_stop():
|
||||
if self.non_interactive:
|
||||
if not self.interactive:
|
||||
return self.state.final_result or {}
|
||||
await self._enter_waiting_state(tracer)
|
||||
continue
|
||||
@@ -213,8 +215,12 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
should_finish = await iteration_task
|
||||
self._current_task = None
|
||||
|
||||
if should_finish is None and self.interactive:
|
||||
await self._enter_waiting_state(tracer, text_response=True)
|
||||
continue
|
||||
|
||||
if should_finish:
|
||||
if self.non_interactive:
|
||||
if not self.interactive:
|
||||
self.state.set_completed({"success": True})
|
||||
if tracer:
|
||||
tracer.update_agent_status(self.state.agent_id, "completed")
|
||||
@@ -230,7 +236,7 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
self.state.add_message(
|
||||
"assistant", f"{partial_content}\n\n[ABORTED BY USER]"
|
||||
)
|
||||
if self.non_interactive:
|
||||
if not self.interactive:
|
||||
raise
|
||||
await self._enter_waiting_state(tracer, error_occurred=False, was_cancelled=True)
|
||||
continue
|
||||
@@ -243,7 +249,7 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
|
||||
except (RuntimeError, ValueError, TypeError) as e:
|
||||
if not await self._handle_iteration_error(e, tracer):
|
||||
if self.non_interactive:
|
||||
if not self.interactive:
|
||||
self.state.set_completed({"success": False, "error": str(e)})
|
||||
if tracer:
|
||||
tracer.update_agent_status(self.state.agent_id, "failed")
|
||||
@@ -283,11 +289,14 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
task_completed: bool = False,
|
||||
error_occurred: bool = False,
|
||||
was_cancelled: bool = False,
|
||||
text_response: bool = False,
|
||||
) -> None:
|
||||
self.state.enter_waiting_state()
|
||||
|
||||
if tracer:
|
||||
if task_completed:
|
||||
if text_response:
|
||||
tracer.update_agent_status(self.state.agent_id, "waiting_for_input")
|
||||
elif task_completed:
|
||||
tracer.update_agent_status(self.state.agent_id, "completed")
|
||||
elif error_occurred:
|
||||
tracer.update_agent_status(self.state.agent_id, "error")
|
||||
@@ -296,6 +305,9 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
else:
|
||||
tracer.update_agent_status(self.state.agent_id, "stopped")
|
||||
|
||||
if text_response:
|
||||
return
|
||||
|
||||
if task_completed:
|
||||
self.state.add_message(
|
||||
"assistant",
|
||||
@@ -333,6 +345,14 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
|
||||
if "agent_id" in sandbox_info:
|
||||
self.state.sandbox_info["agent_id"] = sandbox_info["agent_id"]
|
||||
|
||||
caido_port = sandbox_info.get("caido_port")
|
||||
if caido_port:
|
||||
from strix.telemetry.tracer import get_global_tracer
|
||||
|
||||
tracer = get_global_tracer()
|
||||
if tracer:
|
||||
tracer.caido_url = f"localhost:{caido_port}"
|
||||
except Exception as e:
|
||||
from strix.telemetry import posthog
|
||||
|
||||
@@ -344,7 +364,7 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
|
||||
self.state.add_message("user", task)
|
||||
|
||||
async def _process_iteration(self, tracer: Optional["Tracer"]) -> bool:
|
||||
async def _process_iteration(self, tracer: Optional["Tracer"]) -> bool | None:
|
||||
final_response = None
|
||||
|
||||
async for response in self.llm.generate(self.state.get_conversation_history()):
|
||||
@@ -390,7 +410,7 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
if actions:
|
||||
return await self._execute_actions(actions, tracer)
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
async def _execute_actions(self, actions: list[Any], tracer: Optional["Tracer"]) -> bool:
|
||||
"""Execute actions and return True if agent should finish."""
|
||||
@@ -418,7 +438,7 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
self.state.set_completed({"success": True})
|
||||
if tracer:
|
||||
tracer.update_agent_status(self.state.agent_id, "completed")
|
||||
if self.non_interactive and self.state.parent_id is None:
|
||||
if not self.interactive and self.state.parent_id is None:
|
||||
return True
|
||||
return True
|
||||
|
||||
@@ -518,7 +538,7 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
error_details = error.details
|
||||
self.state.add_error(error_msg)
|
||||
|
||||
if self.non_interactive:
|
||||
if not self.interactive:
|
||||
self.state.set_completed({"success": False, "error": error_msg})
|
||||
if tracer:
|
||||
tracer.update_agent_status(self.state.agent_id, "failed", error_msg)
|
||||
@@ -553,7 +573,7 @@ class BaseAgent(metaclass=AgentMeta):
|
||||
error_details = getattr(error, "details", None)
|
||||
self.state.add_error(error_msg)
|
||||
|
||||
if self.non_interactive:
|
||||
if not self.interactive:
|
||||
self.state.set_completed({"success": False, "error": error_msg})
|
||||
if tracer:
|
||||
tracer.update_agent_status(self.state.agent_id, "failed", error_msg)
|
||||
|
||||
@@ -25,6 +25,7 @@ class AgentState(BaseModel):
|
||||
waiting_for_input: bool = False
|
||||
llm_failed: bool = False
|
||||
waiting_start_time: datetime | None = None
|
||||
waiting_timeout: int = 600
|
||||
final_result: dict[str, Any] | None = None
|
||||
max_iterations_warning_sent: bool = False
|
||||
|
||||
@@ -116,6 +117,9 @@ class AgentState(BaseModel):
|
||||
return self.iteration >= int(self.max_iterations * threshold)
|
||||
|
||||
def has_waiting_timeout(self) -> bool:
|
||||
if self.waiting_timeout == 0:
|
||||
return False
|
||||
|
||||
if not self.waiting_for_input or not self.waiting_start_time:
|
||||
return False
|
||||
|
||||
@@ -128,7 +132,7 @@ class AgentState(BaseModel):
|
||||
return False
|
||||
|
||||
elapsed = (datetime.now(UTC) - self.waiting_start_time).total_seconds()
|
||||
return elapsed > 600
|
||||
return elapsed > self.waiting_timeout
|
||||
|
||||
def has_empty_last_messages(self, count: int = 3) -> bool:
|
||||
if len(self.messages) < count:
|
||||
|
||||
Reference in New Issue
Block a user