mirror of
https://github.com/usestrix/strix.git
synced 2026-09-23 00:41:50 +00:00
Fix sub-agents not re-spawning after resume
Root causes: 1. Resume message said "re-spawn sub-agents" but didn't say WHICH ones or that old agent IDs are dead — LLM tried to interact with old IDs and got confused. 2. send_message_to_agent returned unhelpful "not found" error when the LLM used old (dead) agent IDs after resume. Fixes: - _build_resume_context_message / _inject_resume_context_message now accept the full checkpoint_data object and extract tracer_agents to list every non-completed sub-agent by name and task. The LLM now knows exactly which agents to re-spawn. - Message explicitly forbids interacting with any agent ID from history and instructs the LLM to call view_agent_graph first. - send_message_to_agent returns an actionable error when target is not found: explains it may be a dead session ID and tells the LLM to use view_agent_graph then create_agent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
66166d5d68
commit
87078de4f1
3 changed files with 83 additions and 20 deletions
|
|
@ -97,22 +97,52 @@ def _replay_previous_output(
|
|||
console.print()
|
||||
|
||||
|
||||
def _build_resume_context_message(state: Any, iteration: int) -> None:
|
||||
def _build_resume_context_message(state: Any, checkpoint_data: Any) -> None:
|
||||
"""Inject a user message telling the LLM it was interrupted and must continue.
|
||||
|
||||
Added for Resume Feature — prevents the model from calling finish_scan or
|
||||
agent_finish just because the message history ends abruptly (e.g. a dangling
|
||||
sub-agent tool call that never got a result).
|
||||
agent_finish just because the history ends abruptly, and explicitly lists
|
||||
which sub-agents were alive so the LLM knows what to re-spawn.
|
||||
"""
|
||||
iteration = checkpoint_data.iteration
|
||||
|
||||
# Collect sub-agents that were NOT completed at the time of interruption
|
||||
# so the LLM knows exactly what to re-create.
|
||||
dead_sub_agents = []
|
||||
for agent_id, node in (checkpoint_data.tracer_agents or {}).items():
|
||||
if node.get("parent_id") is None:
|
||||
continue # skip root agent
|
||||
status = node.get("status", "unknown")
|
||||
if status not in ("completed", "finished", "stopped", "error", "failed"):
|
||||
dead_sub_agents.append({
|
||||
"name": node.get("name", "sub-agent"),
|
||||
"task": (node.get("task") or "")[:300],
|
||||
"status": status,
|
||||
})
|
||||
|
||||
sub_agent_section = ""
|
||||
if dead_sub_agents:
|
||||
lines = [
|
||||
"\n\nThe following sub-agents were ACTIVE at the time of interruption. "
|
||||
"They no longer exist — their agent IDs are completely invalid. "
|
||||
"Re-spawn each one if their work is not yet reflected in the findings above:"
|
||||
]
|
||||
for sa in dead_sub_agents:
|
||||
lines.append(f" • {sa['name']} (was doing: {sa['task']})")
|
||||
sub_agent_section = "\n".join(lines)
|
||||
|
||||
msg = (
|
||||
f"[SYSTEM - SCAN RESUMED]\n"
|
||||
f"This penetration test was interrupted at iteration {iteration}. "
|
||||
f"All sub-agents that were running have been terminated along with their sandbox. "
|
||||
f"A fresh sandbox will be created automatically. "
|
||||
f"Review the conversation history above to understand what has already been done, "
|
||||
f"then CONTINUE the penetration test from where it left off. "
|
||||
f"Do NOT call finish_scan or agent_finish unless all testing is genuinely complete. "
|
||||
f"Re-spawn any sub-agents needed to continue uncompleted work."
|
||||
f"ALL previous sub-agents have been terminated and their agent IDs no longer exist in the graph. "
|
||||
f"A fresh sandbox will be created automatically.\n\n"
|
||||
f"CRITICAL: Do NOT attempt to send_message_to_agent or interact with ANY agent ID "
|
||||
f"that appears in the conversation history above — every one of those IDs is now dead. "
|
||||
f"Call view_agent_graph to see the current graph (only you, the root agent, exist now)."
|
||||
f"{sub_agent_section}\n\n"
|
||||
f"Review the history to understand what was done, then CONTINUE the penetration test. "
|
||||
f"Re-spawn sub-agents for any incomplete work. "
|
||||
f"Do NOT call finish_scan unless all testing is genuinely complete."
|
||||
)
|
||||
state.add_message("user", msg)
|
||||
|
||||
|
|
@ -156,7 +186,7 @@ async def run_cli(args: Any) -> None: # noqa: PLR0915
|
|||
# or agent_finish just because the history ended abruptly.
|
||||
# Without this the model sees a dangling tool call (sub-agent that was
|
||||
# killed mid-execution) and may decide the task is complete or broken.
|
||||
_build_resume_context_message(resumed_state, checkpoint_data.iteration)
|
||||
_build_resume_context_message(resumed_state, checkpoint_data)
|
||||
|
||||
start_text = Text()
|
||||
if is_resuming:
|
||||
|
|
|
|||
|
|
@ -41,21 +41,49 @@ from strix.telemetry.tracer import Tracer, set_global_tracer
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _inject_resume_context_message(state: Any, iteration: int) -> None:
|
||||
def _inject_resume_context_message(state: Any, checkpoint_data: Any) -> None:
|
||||
"""Inject a user message telling the LLM it was interrupted and must continue.
|
||||
|
||||
Added for Resume Feature — prevents the model from calling finish_scan or
|
||||
agent_finish just because the message history ends abruptly.
|
||||
agent_finish just because the history ends abruptly, and explicitly lists
|
||||
which sub-agents were alive so the LLM knows what to re-spawn.
|
||||
"""
|
||||
iteration = checkpoint_data.iteration
|
||||
|
||||
dead_sub_agents = []
|
||||
for agent_id, node in (checkpoint_data.tracer_agents or {}).items():
|
||||
if node.get("parent_id") is None:
|
||||
continue
|
||||
status = node.get("status", "unknown")
|
||||
if status not in ("completed", "finished", "stopped", "error", "failed"):
|
||||
dead_sub_agents.append({
|
||||
"name": node.get("name", "sub-agent"),
|
||||
"task": (node.get("task") or "")[:300],
|
||||
})
|
||||
|
||||
sub_agent_section = ""
|
||||
if dead_sub_agents:
|
||||
lines = [
|
||||
"\n\nThe following sub-agents were ACTIVE at the time of interruption. "
|
||||
"They no longer exist — their agent IDs are completely invalid. "
|
||||
"Re-spawn each one if their work is not yet reflected in the findings above:"
|
||||
]
|
||||
for sa in dead_sub_agents:
|
||||
lines.append(f" • {sa['name']} (was doing: {sa['task']})")
|
||||
sub_agent_section = "\n".join(lines)
|
||||
|
||||
msg = (
|
||||
f"[SYSTEM - SCAN RESUMED]\n"
|
||||
f"This penetration test was interrupted at iteration {iteration}. "
|
||||
f"All sub-agents that were running have been terminated along with their sandbox. "
|
||||
f"A fresh sandbox will be created automatically. "
|
||||
f"Review the conversation history above to understand what has already been done, "
|
||||
f"then CONTINUE the penetration test from where it left off. "
|
||||
f"Do NOT call finish_scan or agent_finish unless all testing is genuinely complete. "
|
||||
f"Re-spawn any sub-agents needed to continue uncompleted work."
|
||||
f"ALL previous sub-agents have been terminated and their agent IDs no longer exist in the graph. "
|
||||
f"A fresh sandbox will be created automatically.\n\n"
|
||||
f"CRITICAL: Do NOT attempt to send_message_to_agent or interact with ANY agent ID "
|
||||
f"that appears in the conversation history above — every one of those IDs is now dead. "
|
||||
f"Call view_agent_graph to see the current graph (only you, the root agent, exist now)."
|
||||
f"{sub_agent_section}\n\n"
|
||||
f"Review the history to understand what was done, then CONTINUE the penetration test. "
|
||||
f"Re-spawn sub-agents for any incomplete work. "
|
||||
f"Do NOT call finish_scan unless all testing is genuinely complete."
|
||||
)
|
||||
state.add_message("user", msg)
|
||||
|
||||
|
|
@ -812,7 +840,7 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||
resumed_state.llm_failed = False
|
||||
# Inject resume-context message so the LLM does NOT call finish_scan
|
||||
# or agent_finish just because the history ended abruptly.
|
||||
_inject_resume_context_message(resumed_state, _cp.iteration)
|
||||
_inject_resume_context_message(resumed_state, _cp)
|
||||
config["state"] = resumed_state
|
||||
|
||||
_mgr = getattr(args, "_checkpoint_manager", None)
|
||||
|
|
|
|||
|
|
@ -304,7 +304,12 @@ def send_message_to_agent(
|
|||
if target_agent_id not in _agent_graph["nodes"]:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Target agent '{target_agent_id}' not found in graph",
|
||||
"error": (
|
||||
f"Target agent '{target_agent_id}' not found in the agent graph. "
|
||||
"This ID may be from a previous session that was interrupted. "
|
||||
"Call view_agent_graph to see which agents currently exist, "
|
||||
"then use create_agent to re-spawn any needed sub-agents."
|
||||
),
|
||||
"message_id": None,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue