refactor(skill-review): use create_agent with skill_evolution disabled to allow autonomous skill creation

This commit is contained in:
xuhd committed 2026-06-05 13:32:19 +08:00
1 parent b5d80025e9
commit 3955bd72b5
1 file changed
+57 -29
@@ -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"<conversation>\n{conversation_text}\n</conversation>"
# 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"<conversation>\n{conversation_text}\n</conversation>"
),
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",