Agents graph sweep: status taxonomy, stop_agent safety, skill validation

- view_agent_graph status summary now derives buckets from the canonical
  Status literal via get_args, so adding a new status in core.agents
  auto-flows into the summary. The previous hardcoded five-bucket list
  silently omitted "failed" — buckets stopped summing to total whenever
  an agent failed.

- stop_agent rejects targets that are already in a terminal status
  (completed / stopped / crashed / failed) with a model-readable error
  pointing at view_agent_graph and send_message_to_agent. request_stop
  unconditionally overwrites status, so without this guard calling
  stop_agent on a completed agent erased the "completed" history.

- StopAgentRenderer added — was falling back to the generic key/value
  renderer; the rest of the agents_graph tools have purpose-built ones.

- agent_finish root-rejection payload trimmed from
  {success, agent_completed, error, parent_notified} to {success, error}.
  The lifecycle gate only reads success+agent_completed and they were
  always False/False on this branch, so the extra fields were dead weight.

- wait_for_message renames its top-level outcome field from "status" to
  "wait_outcome" — "status" overloaded with the coordinator's agent
  status literal (which also has "stopped" as a value, different
  meaning). Redundant "agent_waiting" boolean dropped (true iff
  wait_outcome == "waiting"). Consumer at factory._wait_tool_parked
  updated to match.

- send_message_to_agent now refuses self-send with a pointer at think /
  agent_finish / finish_scan instead of looping a message into your
  own session.

- SendMessageToAgentRenderer read args.get("agent_id") but the tool's
  param is target_agent_id, so the TUI silently never showed the target.
  Fixed.

- Restored skill validation lost during the SDK migration: skills
  module re-exports get_all_skill_names and validate_requested_skills
  (excluding internal scan_modes/coordination categories from the
  user-selectable set). create_agent now validates skills before
  spawning instead of silently accepting unknown names.
This commit is contained in:
0xallam
2026-05-25 01:35:57 -07:00
parent 6ad709e5a7
commit 4f0cb71aeb
4 changed files with 150 additions and 26 deletions
@@ -61,12 +61,12 @@ class SendMessageToAgentRenderer(BaseToolRenderer):
status = tool_data.get("status", "unknown")
message = args.get("message", "")
agent_id = args.get("agent_id", "")
target_agent_id = args.get("target_agent_id", "")
text = Text()
text.append("", style="#60a5fa")
if agent_id:
text.append(f"to {agent_id}", style="dim")
if target_agent_id:
text.append(f"to {target_agent_id}", style="dim")
else:
text.append("sending message", style="dim")
@@ -138,3 +138,38 @@ class WaitForMessageRenderer(BaseToolRenderer):
css_classes = cls.get_css_classes(status)
return Static(text, classes=css_classes)
@register_tool_renderer
class StopAgentRenderer(BaseToolRenderer):
tool_name: ClassVar[str] = "stop_agent"
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
@classmethod
def render(cls, tool_data: dict[str, Any]) -> Static:
args = tool_data.get("args", {})
result = tool_data.get("result")
status = tool_data.get("status", "unknown")
target_agent_id = args.get("target_agent_id", "")
cascade = args.get("cascade", True)
reason = args.get("reason", "")
text = Text()
text.append("", style="#ef4444")
text.append("stopping", style="dim")
if target_agent_id:
text.append(f" {target_agent_id}", style="bold #ef4444")
if cascade:
text.append(" + descendants", style="dim italic")
if reason:
text.append("\n ")
text.append(reason, style="dim")
if isinstance(result, dict) and result.get("success") is False and result.get("error"):
text.append("\n ")
text.append(str(result["error"]), style="#ef4444")
css_classes = cls.get_css_classes(status)
return Static(text, classes=css_classes)