Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "strix-agent"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
description = "Open-source AI Hackers for your apps"
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -43,6 +43,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)
|
||||
- A text-only turn — even one — IMMEDIATELY ends the scan/run with no report written. The lifecycle tools (``finish_scan`` for root, ``agent_finish`` for subagents) are the ONLY valid way to terminate. If you find yourself wanting to say "Done!" or "Scan complete" without a tool call, call the lifecycle tool instead — the report and termination signal both flow through it.
|
||||
{% endif %}
|
||||
</communication_rules>
|
||||
|
||||
|
||||
@@ -109,13 +109,19 @@ def build_scope_context(scan_config: dict[str, Any]) -> dict[str, Any]:
|
||||
def make_model_settings(
|
||||
reasoning_effort: ReasoningEffort | None,
|
||||
) -> ModelSettings:
|
||||
# Anthropic + DeepSeek thinking reject ``tool_choice="required"`` outright
|
||||
# when reasoning is enabled; OpenAI o-series accepts both but doesn't need
|
||||
# the safety net. When reasoning is on we let the model self-select tools
|
||||
# and rely on the system prompt + the ``_finish_tool_use_behavior`` callback
|
||||
# to keep the loop converging on a lifecycle tool.
|
||||
use_reasoning = reasoning_effort is not None and reasoning_effort != "none"
|
||||
model_settings = ModelSettings(
|
||||
parallel_tool_calls=False,
|
||||
tool_choice="required",
|
||||
tool_choice=None if use_reasoning else "required",
|
||||
retry=DEFAULT_MODEL_RETRY,
|
||||
include_usage=True,
|
||||
)
|
||||
if reasoning_effort is not None:
|
||||
if use_reasoning:
|
||||
model_settings = model_settings.resolve(
|
||||
ModelSettings(reasoning=Reasoning(effort=reasoning_effort)),
|
||||
)
|
||||
|
||||
+22
-1
@@ -261,7 +261,7 @@ async def run_strix_scan(
|
||||
async with coordinator._lock:
|
||||
root_status = coordinator.statuses.get(root_id)
|
||||
|
||||
return await run_agent_loop(
|
||||
result = await run_agent_loop(
|
||||
agent=root_agent,
|
||||
initial_input=initial_input,
|
||||
run_config=run_config,
|
||||
@@ -275,6 +275,27 @@ async def run_strix_scan(
|
||||
event_sink=event_sink,
|
||||
hooks=hooks,
|
||||
)
|
||||
if not interactive and result is not None:
|
||||
final = getattr(result, "final_output", None)
|
||||
scan_completed = False
|
||||
if isinstance(final, str):
|
||||
try:
|
||||
parsed = json.loads(final)
|
||||
scan_completed = bool(isinstance(parsed, dict) and parsed.get("scan_completed"))
|
||||
except (ValueError, TypeError):
|
||||
scan_completed = False
|
||||
elif isinstance(final, dict):
|
||||
scan_completed = bool(final.get("scan_completed"))
|
||||
if not scan_completed:
|
||||
logger.error(
|
||||
"Scan %s ended without calling finish_scan. The agent "
|
||||
"emitted a text-only turn instead of a lifecycle tool call, "
|
||||
"so no executive report was written. Final output (first "
|
||||
"300 chars): %r",
|
||||
scan_id,
|
||||
str(final)[:300],
|
||||
)
|
||||
return result # noqa: TRY300
|
||||
except BaseException:
|
||||
logger.exception("Strix scan %s failed", scan_id)
|
||||
if root_id is not None:
|
||||
|
||||
Reference in New Issue
Block a user