diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/skills/review_worker.py b/offline-backend-20260512/backend/packages/harness/deerflow/skills/review_worker.py index 2b7ba663..99e886ea 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/skills/review_worker.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/skills/review_worker.py @@ -108,17 +108,28 @@ _REVIEW_SYSTEM_PROMPT = textwrap.dedent("""\ - The frontmatter block must start with `---` on its own line and end with `---`. - Do NOT add extra keys like "tags", "category", "type", etc. + ## Avoiding duplicate-creation errors + Before calling skill_manage with action="create": + 1. Always call skill_list first. + 2. If a skill with the same name already exists, use action="patch" instead. + Creating over an existing skill will raise an error. + 3. If you get a tool error, read the message carefully and fix the issue + (wrong name, bad frontmatter, duplicate, etc.) before retrying. + Be conservative. Fewer high-quality skills beat many noisy ones. """) async def _run_review(thread_id: str, messages: list[BaseMessage], app_config: "AppConfig") -> None: - """Core review logic: spin up a minimal React agent and invoke it.""" - from langgraph.prebuilt import create_react_agent + """Core review logic: run a minimal lead agent with full middleware support. - from deerflow.models.factory import create_chat_model - from deerflow.tools.builtins.skill_tools import skill_list_tool - from deerflow.tools.skill_manage_tool import skill_manage_tool + Reuses make_lead_agent so that ToolErrorHandlingMiddleware, LLMErrorHandlingMiddleware, + and the rest of the middleware chain handle all error cases uniformly — no custom + handle_tool_errors hack needed. + """ + from langchain_core.runnables import RunnableConfig + + from deerflow.agents.lead_agent.agent import make_lead_agent evolution_cfg = app_config.skill_evolution timeout = evolution_cfg.review_worker.timeout_seconds @@ -126,26 +137,36 @@ async def _run_review(thread_id: str, messages: list[BaseMessage], app_config: " model_name = evolution_cfg.review_worker.model_name conversation_text = _format_messages_for_review(messages, max_messages) - - try: - model = create_chat_model(model_name) - except Exception: - logger.warning("skill_review_worker: could not load model %r, aborting review", model_name) - return - - tools = [skill_list_tool, skill_manage_tool] - agent = create_react_agent(model, tools, prompt=_REVIEW_SYSTEM_PROMPT) - user_message = ( - "Review this conversation and apply any skill changes you deem appropriate.\n\n" + _REVIEW_SYSTEM_PROMPT + + "\n\nReview this conversation and apply any skill changes you deem appropriate.\n\n" f"\n{conversation_text}\n" ) + # Use a synthetic thread id so this review run is isolated from the source thread. + review_thread_id = f"__skill-review__{thread_id}" + + configurable: dict = { + "thread_id": review_thread_id, + "is_bootstrap": True, # skip MemoryMiddleware injection + "is_scheduled_run": True, # skip TitleMiddleware + "subagent_enabled": False, # no task-delegation tool + "app_config": app_config, + } + if model_name: + configurable["model_name"] = model_name + + run_config = RunnableConfig(configurable=configurable) + agent = make_lead_agent(run_config) + try: await asyncio.wait_for( asyncio.get_event_loop().run_in_executor( None, - lambda: agent.invoke({"messages": [("user", user_message)]}), + lambda: agent.invoke( + {"messages": [("user", user_message)]}, + config=run_config, + ), ), timeout=timeout, )