@@ -45,6 +45,13 @@ async def execute(cls, tool, run_context, **tool_args):
4545
4646 """
4747 if isinstance (tool , HandoffTool ):
48+ is_bg = tool_args .pop ("background_task" , False )
49+ if is_bg :
50+ async for r in cls ._execute_handoff_background (
51+ tool , run_context , ** tool_args
52+ ):
53+ yield r
54+ return
4855 async for r in cls ._execute_handoff (tool , run_context , ** tool_args ):
4956 yield r
5057 return
@@ -147,19 +154,93 @@ async def _execute_handoff(
147154 )
148155
149156 @classmethod
150- async def _execute_background (
157+ async def _execute_handoff_background (
151158 cls ,
152- tool : FunctionTool ,
159+ tool : HandoffTool ,
160+ run_context : ContextWrapper [AstrAgentContext ],
161+ ** tool_args ,
162+ ):
163+ """Execute a handoff as a background task.
164+
165+ Immediately yields a success response with a task_id, then runs
166+ the subagent asynchronously. When the subagent finishes, a
167+ ``CronMessageEvent`` is created so the main LLM can inform the
168+ user of the result – the same pattern used by
169+ ``_execute_background`` for regular background tasks.
170+ """
171+ task_id = uuid .uuid4 ().hex
172+
173+ async def _run_handoff_in_background () -> None :
174+ try :
175+ await cls ._do_handoff_background (
176+ tool = tool ,
177+ run_context = run_context ,
178+ task_id = task_id ,
179+ ** tool_args ,
180+ )
181+ except Exception as e : # noqa: BLE001
182+ logger .error (
183+ f"Background handoff { task_id } ({ tool .name } ) failed: { e !s} " ,
184+ exc_info = True ,
185+ )
186+
187+ asyncio .create_task (_run_handoff_in_background ())
188+
189+ text_content = mcp .types .TextContent (
190+ type = "text" ,
191+ text = (
192+ f"Background task dedicated to subagent '{ tool .agent .name } ' submitted. task_id={ task_id } . "
193+ f"The subagent '{ tool .agent .name } ' is working on the task on hehalf you. "
194+ f"You will be notified when it finishes."
195+ ),
196+ )
197+ yield mcp .types .CallToolResult (content = [text_content ])
198+
199+ @classmethod
200+ async def _do_handoff_background (
201+ cls ,
202+ tool : HandoffTool ,
153203 run_context : ContextWrapper [AstrAgentContext ],
154204 task_id : str ,
155205 ** tool_args ,
156206 ) -> None :
157- from astrbot .core .astr_main_agent import (
158- MainAgentBuildConfig ,
159- _get_session_conv ,
160- build_main_agent ,
207+ """Run the subagent handoff and, on completion, wake the main agent."""
208+ result_text = ""
209+ try :
210+ async for r in cls ._execute_handoff (tool , run_context , ** tool_args ):
211+ if isinstance (r , mcp .types .CallToolResult ):
212+ for content in r .content :
213+ if isinstance (content , mcp .types .TextContent ):
214+ result_text += content .text + "\n "
215+ except Exception as e :
216+ result_text = (
217+ f"error: Background task execution failed, internal error: { e !s} "
218+ )
219+
220+ event = run_context .context .event
221+
222+ await cls ._wake_main_agent_for_background_result (
223+ run_context = run_context ,
224+ task_id = task_id ,
225+ tool_name = tool .name ,
226+ result_text = result_text ,
227+ tool_args = tool_args ,
228+ note = (
229+ event .get_extra ("background_note" )
230+ or f"Background task for subagent '{ tool .agent .name } ' finished."
231+ ),
232+ summary_name = f"Dedicated to subagent `{ tool .agent .name } `" ,
233+ extra_result_fields = {"subagent_name" : tool .agent .name },
161234 )
162235
236+ @classmethod
237+ async def _execute_background (
238+ cls ,
239+ tool : FunctionTool ,
240+ run_context : ContextWrapper [AstrAgentContext ],
241+ task_id : str ,
242+ ** tool_args ,
243+ ) -> None :
163244 # run the tool
164245 result_text = ""
165246 try :
@@ -178,20 +259,52 @@ async def _execute_background(
178259 )
179260
180261 event = run_context .context .event
181- ctx = run_context .context .context
182262
183- note = (
184- event .get_extra ("background_note" )
185- or f"Background task { tool .name } finished."
263+ await cls ._wake_main_agent_for_background_result (
264+ run_context = run_context ,
265+ task_id = task_id ,
266+ tool_name = tool .name ,
267+ result_text = result_text ,
268+ tool_args = tool_args ,
269+ note = (
270+ event .get_extra ("background_note" )
271+ or f"Background task { tool .name } finished."
272+ ),
273+ summary_name = tool .name ,
186274 )
187- extras = {
188- "background_task_result" : {
189- "task_id" : task_id ,
190- "tool_name" : tool .name ,
191- "result" : result_text or "" ,
192- "tool_args" : tool_args ,
193- }
275+
276+ @classmethod
277+ async def _wake_main_agent_for_background_result (
278+ cls ,
279+ run_context : ContextWrapper [AstrAgentContext ],
280+ * ,
281+ task_id : str ,
282+ tool_name : str ,
283+ result_text : str ,
284+ tool_args : dict [str , T .Any ],
285+ note : str ,
286+ summary_name : str ,
287+ extra_result_fields : dict [str , T .Any ] | None = None ,
288+ ) -> None :
289+ from astrbot .core .astr_main_agent import (
290+ MainAgentBuildConfig ,
291+ _get_session_conv ,
292+ build_main_agent ,
293+ )
294+
295+ event = run_context .context .event
296+ ctx = run_context .context .context
297+
298+ task_result = {
299+ "task_id" : task_id ,
300+ "tool_name" : tool_name ,
301+ "result" : result_text or "" ,
302+ "tool_args" : tool_args ,
194303 }
304+ if extra_result_fields :
305+ task_result .update (extra_result_fields )
306+ extras = {"background_task_result" : task_result }
307+
195308 session = MessageSession .from_str (event .unified_msg_origin )
196309 cron_event = CronMessageEvent (
197310 context = ctx ,
@@ -222,8 +335,11 @@ async def _execute_background(
222335 )
223336 req .prompt = (
224337 "Proceed according to your system instructions. "
225- "Output using same language as previous conversation."
226- " After completing your task, summarize and output your actions and results."
338+ "Output using same language as previous conversation. "
339+ "If you need to deliver the result to the user immediately, "
340+ "you MUST use `send_message_to_user` tool to send the message directly to the user, "
341+ "otherwise the user will not see the result. "
342+ "After completing your task, summarize and output your actions and results. "
227343 )
228344 if not req .func_tool :
229345 req .func_tool = ToolSet ()
@@ -233,7 +349,7 @@ async def _execute_background(
233349 event = cron_event , plugin_context = ctx , config = config , req = req
234350 )
235351 if not result :
236- logger .error ("Failed to build main agent for background task job ." )
352+ logger .error (f "Failed to build main agent for background task { tool_name } ." )
237353 return
238354
239355 runner = result .agent_runner
@@ -243,7 +359,7 @@ async def _execute_background(
243359 llm_resp = runner .get_final_llm_resp ()
244360 task_meta = extras .get ("background_task_result" , {})
245361 summary_note = (
246- f"[BackgroundTask] { task_meta . get ( 'tool_name' , tool . name ) } "
362+ f"[BackgroundTask] { summary_name } "
247363 f"(task_id={ task_meta .get ('task_id' , task_id )} ) finished. "
248364 f"Result: { task_meta .get ('result' ) or result_text or 'no content' } "
249365 )
0 commit comments