From 67e5b361cf58e34b9eb65d59bb2fd54f8a958978 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 8 May 2026 22:59:14 -0700 Subject: [PATCH] fix(examples-chat-python): research tool passes subagent_type so SubagentTracker registers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The @ngaf/langgraph SubagentTracker silently skips tool calls whose args do not include a valid `subagent_type` string (matching the canonical LangGraph `task` tool shape). Without it, `agent.subagents()` stays empty and `` never surfaces a card during the research run. Adds `subagent_type: str = "research"` to the tool signature (consumed only as a UI hint — the body deletes it before invoking the subgraph) and instructs the parent in SYSTEM_PROMPT to pass it. The result: the SubagentTracker registers the subagent on tool-call dispatch, the panel mounts during the active window, and (per the existing primitive's active-only filter) the card hides cleanly once the subagent completes. Verified live: agent.subagents() reports size=1 with the research tool call id and status transitions through to 'complete' as the subagent returns. End-state panel wrapper still renders because the tracker map retains the entry; the inner @for filters complete subagents out. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/chat/python/src/graph.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/examples/chat/python/src/graph.py b/examples/chat/python/src/graph.py index 207cccf7e..19b8e3773 100644 --- a/examples/chat/python/src/graph.py +++ b/examples/chat/python/src/graph.py @@ -58,10 +58,11 @@ "When the user asks for in-depth research on a focused topic (history, " "motivation, comparison, deep-dive on something they want explained), " "call the `research` tool to dispatch a subagent that focuses on that " - "topic. Pass the topic verbatim or as a concise rephrasing. Use the " - "subagent's returned summary to compose your final answer. Do not " - "call `research` for trivial chit-chat or simple lookups — those are " - "handled by `search_documents`." + "topic. Pass the topic verbatim or as a concise rephrasing, and pass " + "`subagent_type=\"research\"` so the UI surfaces a subagent card while " + "the child runs. Use the subagent's returned summary to compose your " + "final answer. Do not call `research` for trivial chit-chat or simple " + "lookups — those are handled by `search_documents`." ) # Reasoning-capable model prefixes. We only attach the ``reasoning`` @@ -178,11 +179,20 @@ async def research_node(state: ResearchState) -> dict: @tool -async def research(topic: str) -> str: +async def research(topic: str, subagent_type: str = "research") -> str: """Dispatch a research subagent to gather facts on a focused topic. The subagent returns a concise summary; pass that summary back to the user, citing it with the inline citation syntax if appropriate. + + `subagent_type` is a free-form label the parent uses to identify the + subagent in the UI (the @ngaf/langgraph SubagentTracker keys on it + to populate `agent.subagents()` for the chat-subagents primitive). + Always pass a stable identifier like "research". """ + # subagent_type is intentionally accepted but unused server-side — + # it travels in the tool call args so the SubagentTracker can + # register the dispatch and surface a card while the child graph runs. + del subagent_type result = await research_subgraph.ainvoke({"topic": topic, "messages": []}) msgs = result.get("messages") if isinstance(result, dict) else None if not msgs: