Coverage for merco/agents/subagent.py: 100%
56 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 14:04 +0800
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 14:04 +0800
1"""SubAgentManager — 子代理派发"""
2from __future__ import annotations
4import logging
5from typing import TYPE_CHECKING
7if TYPE_CHECKING:
8 from merco.core.agent import Agent
9 from merco.agents.profile import AgentProfileRegistry
11logger = logging.getLogger("merco.agents.subagent")
14class SubAgentManager:
15 """子代理派发管理器"""
17 def __init__(self, parent: "Agent", profile_registry: "AgentProfileRegistry" = None):
18 self._parent = parent
19 self._profiles = profile_registry
20 self._active: dict[str, "Agent"] = {}
22 async def dispatch(self, todo_id: str, prompt: str, agent_name: str = "default") -> str:
23 """派发子代理执行任务,返回 subagent_id"""
24 # 1. 创建子 Agent
25 sub_agent = await self._create_sub_agent(agent_name)
27 # 2. 更新 Todo 状态
28 self._parent.todo_manager.update(todo_id, status="in_progress", assigned_to=sub_agent.session.id)
30 # 3. 执行子代理
31 try:
32 result = await sub_agent.run(prompt)
33 # 4. 更新 Todo 结果
34 self._parent.todo_manager.update(todo_id, status="completed", result=result)
35 except Exception as e:
36 logger.warning("子代理执行失败: %s", e)
37 self._parent.todo_manager.update(todo_id, status="failed", result=str(e))
38 result = f"Error: {e}"
40 # 5. 结果注入父 context
41 self._inject_result_to_parent(todo_id, result)
43 # 6. 触发事件
44 await self._parent.hooks.emit("subagent.completed", todo_id=todo_id, result=result)
46 return sub_agent.session.id
48 async def _create_sub_agent(self, agent_name: str) -> "Agent":
49 """创建子代理,根据 profile 配置 prompt/tools/model/limits"""
50 from merco.core.agent import Agent
51 from merco.core.session import Session
52 from merco.agents.profile import ProfilePromptChunk
53 from merco.tools.registry import ToolRegistry
55 # 查找 profile
56 profile = None
57 if self._profiles:
58 profile = self._profiles.get(agent_name) or self._profiles.get("default")
60 config = self._parent.config
61 tool_registry = self._parent.tool_registry
63 if profile:
64 # model override
65 if profile.model:
66 import copy
67 config = copy.deepcopy(config)
68 config.model.provider = profile.model.get("provider", config.model.provider)
69 config.model.model = profile.model.get("model", config.model.model)
71 # tools allowlist
72 if profile.tools:
73 tool_registry = ToolRegistry()
74 for name in profile.tools:
75 tool = self._parent.tool_registry.get(name)
76 if tool:
77 tool_registry.register(tool)
79 sub_agent = await Agent.create(config=config, tool_registry=tool_registry)
80 # 强制新 session(Agent.create 会 resume_or_create 恢复父会话)
81 sub_agent.session = Session(store=sub_agent._session_store)
82 sub_agent._session_store.create_session(sub_agent.session.id)
84 if profile:
85 sub_agent.prompt_builder.use(ProfilePromptChunk(profile))
86 if profile.limits.get("max_tool_calls"):
87 sub_agent.config.max_tool_calls = profile.limits["max_tool_calls"]
88 sub_agent._max_tool_calls = profile.limits["max_tool_calls"]
90 self._active[sub_agent.session.id] = sub_agent
91 return sub_agent
93 def _inject_result_to_parent(self, todo_id: str, result: str):
94 """把子代理结果注入父代理的 context"""
95 self._parent.context.add({
96 "role": "tool",
97 "content": f"[Todo {todo_id}] 子代理结果:\n{result}",
98 "tool_call_id": f"todo_{todo_id}",
99 })