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",