From 3955bd72b5e9c839e34052fe72d81485e74c90d1 Mon Sep 17 00:00:00 2001 From: xuhd Date: Wed, 3 Jun 2026 17:04:05 +0800 Subject: [PATCH] refactor(skill-review): use create_agent with skill_evolution disabled to allow autonomous skill creation --- .../harness/deerflow/skills/review_worker.py | 86 ++++++++++++------- 1 file changed, 57 insertions(+), 29 deletions(-) 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 75005616..aebc67c4 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 @@ -123,15 +123,26 @@ _REVIEW_SYSTEM_PROMPT = textwrap.dedent("""\ async def _run_review(thread_id: str, messages: list[BaseMessage], app_config: "AppConfig") -> None: - """Core review logic: run a minimal lead agent with full middleware support. + """Core review logic: create_agent with full middleware chain, no 'silent creation' constraint. - 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. + Uses create_agent (the deerflow agent factory) directly so we can: + - Reuse the full middleware chain (ToolErrorHandlingMiddleware, etc.) + - Reuse the full tool set + - But pass app_config with skill_evolution.enabled=False to strip the + "Never create skills silently without user confirmation" constraint + from the system prompt. + Review instructions are injected via PromptPrefixMiddleware through + additional_kwargs["prompt_prefix"] on the HumanMessage. """ - from langchain_core.runnables import RunnableConfig + from langchain.agents import create_agent + from langchain_core.messages import HumanMessage - from deerflow.agents.lead_agent.agent import make_lead_agent + from deerflow.agents.lead_agent.prompt import apply_prompt_template + from deerflow.agents.middlewares.prompt_prefix_middleware import PromptPrefixMiddleware + from deerflow.agents.middlewares.tool_error_handling_middleware import build_lead_runtime_middlewares + from deerflow.agents.thread_state import ThreadState + from deerflow.models import create_chat_model + from deerflow.tools.tools import get_available_tools evolution_cfg = app_config.skill_evolution timeout = evolution_cfg.review_worker.timeout_seconds @@ -139,36 +150,53 @@ 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) - user_message = ( - _REVIEW_SYSTEM_PROMPT - + "\n\nReview this conversation and apply any skill changes you deem appropriate.\n\n" - f"\n{conversation_text}\n" + + # Disable skill_evolution so the system prompt does NOT include + # "Never create skills silently without user confirmation". + # Also prevents SkillReviewMiddleware from registering (no recursive reviews). + review_app_config = app_config.model_copy(deep=True) + review_app_config.skill_evolution.enabled = False + + try: + model = create_chat_model(name=model_name, app_config=review_app_config) + except Exception: + logger.warning("skill_review_worker: could not load model %r, aborting review", model_name) + return + + tools = get_available_tools(app_config=review_app_config, subagent_enabled=False) + middlewares = build_lead_runtime_middlewares(app_config=review_app_config) + middlewares.append(PromptPrefixMiddleware()) + + system_prompt = apply_prompt_template( + subagent_enabled=False, + max_concurrent_subagents=0, + app_config=review_app_config, + memory_injection_enabled=False, ) - # Use a synthetic thread id so this review run is isolated from the source thread. - review_thread_id = f"__skill-review__{thread_id}" + agent = create_agent( + model=model, + tools=tools, + middleware=middlewares, + system_prompt=system_prompt, + state_schema=ThreadState, + ) - 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) + # Review instructions arrive as prompt_prefix: PromptPrefixMiddleware prepends + # them before every model call without persisting them to state. + first_message = HumanMessage( + content=( + "Review this conversation and apply any skill changes you deem appropriate.\n\n" + f"\n{conversation_text}\n" + ), + additional_kwargs={"prompt_prefix": _REVIEW_SYSTEM_PROMPT}, + ) try: result = await asyncio.wait_for( asyncio.get_event_loop().run_in_executor( None, - lambda: agent.invoke( - {"messages": [("user", user_message)]}, - config=run_config, - ), + lambda: agent.invoke({"messages": [first_message]}), ), timeout=timeout, ) @@ -178,7 +206,7 @@ async def _run_review(thread_id: str, messages: list[BaseMessage], app_config: " for msg in reversed(final_msgs): content = getattr(msg, "content", None) if content and isinstance(content, str) and content.strip(): - final_text = content.strip()[:300] + final_text = content.strip()[:1000] break logger.info( "skill_review_worker: review completed for thread %s — agent said: %s",