fix(evolution): skip idle review while a turn is running

This commit is contained in:
zhayujie
2026-06-15 20:33:52 +08:00
parent 2397ea019e
commit e74906fbec
4 changed files with 66 additions and 0 deletions

View File

@@ -55,6 +55,10 @@ class ChatService:
context = self._build_context(query, session_id, channel_type)
self._attach_context_aware_tools(agent, context)
# Mark this session as mid-run so the self-evolution idle scan does not
# fire concurrently when a single turn runs longer than idle_minutes.
self._mark_run_active(agent, True)
# State shared between the event callback and this method
state = _StreamState()
@@ -205,6 +209,8 @@ class ChatService:
logger.info("[ChatService] Cleared agent message history after executor recovery")
raise
finally:
# Clear the mid-run flag so idle scans can review this session again.
self._mark_run_active(agent, False)
# Release cancel token to keep the registry bounded.
if session_id:
try:
@@ -314,6 +320,15 @@ class ChatService:
except Exception as e:
logger.warning(f"[ChatService] Failed to attach context to scheduler: {e}")
@staticmethod
def _mark_run_active(agent, active):
"""Toggle the self-evolution mid-run flag for this session's agent."""
try:
from agent.evolution.trigger import mark_run_active
mark_run_active(agent, active)
except Exception:
pass
@staticmethod
def _note_evolution_turn(agent, context):
"""Record a user turn so the self-evolution idle trigger has signal."""

View File

@@ -164,7 +164,24 @@ def build_review_user_message(transcript: str, protected_skills: list = None) ->
"rare last resorts — stay [SILENT] unless there is a clear, durable signal "
"not already covered."
f"{protected_note}\n"
f"{_language_instruction()}\n"
"<transcript>\n"
f"{transcript}\n"
"</transcript>"
)
def _language_instruction() -> str:
"""Force the user-facing summary into the configured UI language.
The summary must match what the user reads, so resolve the language from
i18n rather than letting the model guess from the (English-heavy) prompt.
"""
try:
from common import i18n
lang_name = "中文" if i18n.is_zh() else "English"
except Exception:
lang_name = "the user's language"
return (
f"\nIMPORTANT: if you produce a summary, write it ENTIRELY in {lang_name}. "
)

View File

@@ -73,6 +73,20 @@ def note_user_turn(agent, channel_type: str = "", receiver: str = "") -> None:
pass
def mark_run_active(agent, active: bool) -> None:
"""Flag whether the agent is mid-run, so idle scans skip a busy session.
Without this, a single run that lasts longer than idle_minutes would let
the scanner fire an evolution pass concurrently with the live turn.
"""
try:
agent._evo_run_active = bool(active)
if active:
agent._evo_last_active = time.time()
except Exception:
pass
def start_evolution_trigger(agent_bridge) -> None:
"""Start the idle-scan thread once per process (idempotent)."""
if getattr(agent_bridge, "_evolution_trigger_started", False):
@@ -105,6 +119,10 @@ def _scan_once(agent_bridge, cfg) -> None:
sessions = list(getattr(agent_bridge, "agents", {}).items())
for session_id, agent in sessions:
try:
# Skip sessions whose agent is mid-run: a long turn must not be
# reviewed while it is still producing the answer.
if getattr(agent, "_evo_run_active", False):
continue
last_active = getattr(agent, "_evo_last_active", 0)
turns = int(getattr(agent, "_evo_turns", 0))
# Enough signal = enough turns OR enough context pressure.

View File

@@ -524,6 +524,15 @@ class AgentBridge:
session_id, query, context, clear_history
)
# Mark this session as mid-run so the self-evolution idle scan does
# not fire concurrently when a single turn runs longer than
# idle_minutes.
try:
from agent.evolution.trigger import mark_run_active
mark_run_active(agent, True)
except Exception:
pass
try:
# Use agent's run_stream method with event handler
response = agent.run_stream(
@@ -533,6 +542,13 @@ class AgentBridge:
cancel_event=cancel_event,
)
finally:
# Clear the mid-run flag so idle scans can review this session.
try:
from agent.evolution.trigger import mark_run_active
mark_run_active(agent, False)
except Exception:
pass
# Restore original tools
if context and context.get("is_scheduled_task"):
agent.tools = original_tools