From d633ed6e519aab366743857154c16728682df26a Mon Sep 17 00:00:00 2001 From: Chris Alexander <2815297+chris-alexander@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:59:02 +0000 Subject: [PATCH] fix(subagent): avoid missing from_legacy call --- nanobot/agent/subagent.py | 79 ++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/nanobot/agent/subagent.py b/nanobot/agent/subagent.py index 3d61962..d6cbe2d 100644 --- a/nanobot/agent/subagent.py +++ b/nanobot/agent/subagent.py @@ -36,6 +36,7 @@ class SubagentManager: restrict_to_workspace: bool = False, ): from nanobot.config.schema import ExecToolConfig, WebSearchConfig + self.provider = provider self.workspace = workspace self.bus = bus @@ -63,9 +64,7 @@ class SubagentManager: display_label = label or task[:30] + ("..." if len(task) > 30 else "") origin = {"channel": origin_channel, "chat_id": origin_chat_id} - bg_task = asyncio.create_task( - self._run_subagent(task_id, task, display_label, origin) - ) + bg_task = asyncio.create_task(self._run_subagent(task_id, task, display_label, origin)) self._running_tasks[task_id] = bg_task if session_key: self._session_tasks.setdefault(session_key, set()).add(task_id) @@ -100,15 +99,17 @@ class SubagentManager: tools.register(WriteFileTool(workspace=self.workspace, allowed_dir=allowed_dir)) tools.register(EditFileTool(workspace=self.workspace, allowed_dir=allowed_dir)) tools.register(ListDirTool(workspace=self.workspace, allowed_dir=allowed_dir)) - tools.register(ExecTool( - working_dir=str(self.workspace), - timeout=self.exec_config.timeout, - restrict_to_workspace=self.restrict_to_workspace, - path_append=self.exec_config.path_append, - )) + tools.register( + ExecTool( + working_dir=str(self.workspace), + timeout=self.exec_config.timeout, + restrict_to_workspace=self.restrict_to_workspace, + path_append=self.exec_config.path_append, + ) + ) tools.register(WebSearchTool(config=self.web_search_config, proxy=self.web_proxy)) tools.register(WebFetchTool(proxy=self.web_proxy)) - + system_prompt = self._build_subagent_prompt() messages: list[dict[str, Any]] = [ {"role": "system", "content": system_prompt}, @@ -145,23 +146,32 @@ class SubagentManager: } for tc in response.tool_calls ] - messages.append({ - "role": "assistant", - "content": response.content or "", - "tool_calls": tool_call_dicts, - }) + messages.append( + { + "role": "assistant", + "content": response.content or "", + "tool_calls": tool_call_dicts, + } + ) # Execute tools for tool_call in response.tool_calls: args_str = json.dumps(tool_call.arguments, ensure_ascii=False) - logger.debug("Subagent [{}] executing: {} with arguments: {}", task_id, tool_call.name, args_str) + logger.debug( + "Subagent [{}] executing: {} with arguments: {}", + task_id, + tool_call.name, + args_str, + ) result = await tools.execute(tool_call.name, tool_call.arguments) - messages.append({ - "role": "tool", - "tool_call_id": tool_call.id, - "name": tool_call.name, - "content": result, - }) + messages.append( + { + "role": "tool", + "tool_call_id": tool_call.id, + "name": tool_call.name, + "content": result, + } + ) else: final_result = response.content break @@ -207,15 +217,18 @@ Summarize this naturally for the user. Keep it brief (1-2 sentences). Do not men ) await self.bus.publish_inbound(msg) - logger.debug("Subagent [{}] announced result to {}:{}", task_id, origin['channel'], origin['chat_id']) - + logger.debug( + "Subagent [{}] announced result to {}:{}", task_id, origin["channel"], origin["chat_id"] + ) + def _build_subagent_prompt(self) -> str: """Build a focused system prompt for the subagent.""" from nanobot.agent.context import ContextBuilder from nanobot.agent.skills import SkillsLoader time_ctx = ContextBuilder._build_runtime_context(None, None) - parts = [f"""# Subagent + parts = [ + f"""# Subagent {time_ctx} @@ -223,18 +236,24 @@ You are a subagent spawned by the main agent to complete a specific task. Stay focused on the assigned task. Your final response will be reported back to the main agent. ## Workspace -{self.workspace}"""] +{self.workspace}""" + ] skills_summary = SkillsLoader(self.workspace).build_skills_summary() if skills_summary: - parts.append(f"## Skills\n\nRead SKILL.md with read_file to use a skill.\n\n{skills_summary}") + parts.append( + f"## Skills\n\nRead SKILL.md with read_file to use a skill.\n\n{skills_summary}" + ) return "\n\n".join(parts) - + async def cancel_by_session(self, session_key: str) -> int: """Cancel all subagents for the given session. Returns count cancelled.""" - tasks = [self._running_tasks[tid] for tid in self._session_tasks.get(session_key, []) - if tid in self._running_tasks and not self._running_tasks[tid].done()] + tasks = [ + self._running_tasks[tid] + for tid in self._session_tasks.get(session_key, []) + if tid in self._running_tasks and not self._running_tasks[tid].done() + ] for t in tasks: t.cancel() if tasks: