mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-19 12:47:25 +08:00
feat(evolution): give review agent full context, add knowledge signal, polish UX
This commit is contained in:
@@ -14,7 +14,7 @@ from typing import Any
|
|||||||
# until release; enable via ``self_evolution_enabled``.
|
# until release; enable via ``self_evolution_enabled``.
|
||||||
DEFAULT_ENABLED = False
|
DEFAULT_ENABLED = False
|
||||||
DEFAULT_IDLE_MINUTES = 15
|
DEFAULT_IDLE_MINUTES = 15
|
||||||
DEFAULT_MIN_TURNS = 6
|
DEFAULT_MIN_TURNS = 8
|
||||||
# Max review steps for the isolated evolution agent. Kept small (not exposed as
|
# Max review steps for the isolated evolution agent. Kept small (not exposed as
|
||||||
# config): the review is meant to be cheap and focused, not a long autonomous run.
|
# config): the review is meant to be cheap and focused, not a long autonomous run.
|
||||||
DEFAULT_MAX_STEPS = 12
|
DEFAULT_MAX_STEPS = 12
|
||||||
|
|||||||
@@ -175,6 +175,9 @@ _WATCH_SUBDIRS = ("MEMORY.md", "skills", "knowledge", "output")
|
|||||||
# Subpaths under memory/ to ignore: evolution's own bookkeeping + the nightly
|
# Subpaths under memory/ to ignore: evolution's own bookkeeping + the nightly
|
||||||
# dream diary, none of which count as a user-facing change signal.
|
# dream diary, none of which count as a user-facing change signal.
|
||||||
_MEMORY_IGNORE = (".evolution_backups", "dreams", "evolution")
|
_MEMORY_IGNORE = (".evolution_backups", "dreams", "evolution")
|
||||||
|
# Files the skill subsystem maintains automatically (the enable/disable index).
|
||||||
|
# Not an evolution result, so a rewrite must not count as a change signal.
|
||||||
|
_WATCH_IGNORE_NAMES = ("skills_config.json",)
|
||||||
|
|
||||||
|
|
||||||
def _workspace_snapshot(workspace_dir) -> dict:
|
def _workspace_snapshot(workspace_dir) -> dict:
|
||||||
@@ -195,6 +198,8 @@ def _workspace_snapshot(workspace_dir) -> dict:
|
|||||||
for p in root.rglob("*"):
|
for p in root.rglob("*"):
|
||||||
if not p.is_file():
|
if not p.is_file():
|
||||||
continue
|
continue
|
||||||
|
if p.name in _WATCH_IGNORE_NAMES:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
st = p.stat()
|
st = p.stat()
|
||||||
snap[str(p.relative_to(ws))] = (st.st_mtime, st.st_size)
|
snap[str(p.relative_to(ws))] = (st.st_mtime, st.st_size)
|
||||||
@@ -269,10 +274,8 @@ def run_evolution_for_session(
|
|||||||
new_messages = all_messages[done:]
|
new_messages = all_messages[done:]
|
||||||
transcript = _build_transcript(new_messages)
|
transcript = _build_transcript(new_messages)
|
||||||
if not transcript.strip():
|
if not transcript.strip():
|
||||||
# Routine no-op: the per-minute scan hits every idle session, so keep
|
# Routine no-op: the per-minute scan hits every idle session. Advance
|
||||||
# this at debug to avoid spamming the log.
|
# the cursor so we don't re-scan the same tail; no log (pure noise).
|
||||||
logger.debug(f"[Evolution] session={session_id}: no new messages, skip")
|
|
||||||
# Advance the cursor anyway so we don't re-scan the same tail.
|
|
||||||
agent._evo_done_msg_count = total_msgs
|
agent._evo_done_msg_count = total_msgs
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -334,17 +337,23 @@ def run_evolution_for_session(
|
|||||||
str(workspace_dir),
|
str(workspace_dir),
|
||||||
)
|
)
|
||||||
review_agent = agent_bridge.create_agent(
|
review_agent = agent_bridge.create_agent(
|
||||||
system_prompt=EVOLUTION_SYSTEM_PROMPT,
|
system_prompt="",
|
||||||
tools=review_tools,
|
tools=review_tools,
|
||||||
description="Self-evolution review agent",
|
description="Self-evolution review agent",
|
||||||
max_steps=cfg.max_steps,
|
max_steps=cfg.max_steps,
|
||||||
workspace_dir=str(workspace_dir),
|
workspace_dir=str(workspace_dir),
|
||||||
skill_manager=getattr(agent, "skill_manager", None),
|
skill_manager=getattr(agent, "skill_manager", None),
|
||||||
memory_manager=getattr(agent, "memory_manager", None),
|
memory_manager=getattr(agent, "memory_manager", None),
|
||||||
enable_skills=False,
|
enable_skills=True,
|
||||||
|
runtime_info=getattr(agent, "runtime_info", None),
|
||||||
)
|
)
|
||||||
# Reuse the live model so it follows the user's configured model.
|
# Reuse the live model so it follows the user's configured model.
|
||||||
review_agent.model = agent.model
|
review_agent.model = agent.model
|
||||||
|
# Inject the evolution task brief AFTER the full system prompt: the agent
|
||||||
|
# gets the full context (tools, workspace, user preferences, memory, time)
|
||||||
|
# AND its evolution-specific instructions on top, instead of one
|
||||||
|
# overwriting the other.
|
||||||
|
review_agent.extra_system_suffix = EVOLUTION_SYSTEM_PROMPT
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[Evolution] backup {backup_id} ({_backup_n} files) → running review agent"
|
f"[Evolution] backup {backup_id} ({_backup_n} files) → running review agent"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ language (instructed at the end of the prompt).
|
|||||||
|
|
||||||
Design goals (see ref/hermes-agent background_review for inspiration):
|
Design goals (see ref/hermes-agent background_review for inspiration):
|
||||||
- Default to doing NOTHING. Evolution is the exception, not the rule.
|
- Default to doing NOTHING. Evolution is the exception, not the rule.
|
||||||
- Three signal types: memory, skill, unfinished task.
|
- Signal types: skill, unfinished task, memory, knowledge.
|
||||||
- An explicit "do NOT capture" list to avoid self-poisoning over time.
|
- An explicit "do NOT capture" list to avoid self-poisoning over time.
|
||||||
- Generic examples only — never bake in domain-specific business terms.
|
- Generic examples only — never bake in domain-specific business terms.
|
||||||
"""
|
"""
|
||||||
@@ -72,12 +72,11 @@ them. When their signal is clear, act; do not be shy here.
|
|||||||
reply/decision, do NOTHING and stay [SILENT] — do not nag or ping the user.
|
reply/decision, do NOTHING and stay [SILENT] — do not nag or ping the user.
|
||||||
You only ever notify the user as a side effect of having actually done work.
|
You only ever notify the user as a side effect of having actually done work.
|
||||||
|
|
||||||
3. MEMORY — LAST resort, and you are only a SAFETY NET here, not the primary
|
3. MEMORY — RARE, last resort. Default to writing NOTHING here. The main
|
||||||
writer. The main assistant already writes memory DURING the conversation, and
|
assistant already writes memory during the chat, and a nightly pass plus
|
||||||
a nightly pass consolidates daily notes into long-term memory. Prefer fixing
|
context-overflow saves are dedicated safety nets — so memory is almost always
|
||||||
a skill (above) over writing memory whenever the fact belongs in a skill.
|
already covered without you. Skip unless the main assistant clearly missed a
|
||||||
Act ONLY on something the main assistant clearly MISSED that does not belong
|
durable fact that belongs in no skill AND would visibly change future replies.
|
||||||
in any skill.
|
|
||||||
- MEMORY.md is the curated long-term index, auto-loaded into EVERY future
|
- MEMORY.md is the curated long-term index, auto-loaded into EVERY future
|
||||||
conversation. Treat it as precious: edit it in place to CORRECT a wrong
|
conversation. Treat it as precious: edit it in place to CORRECT a wrong
|
||||||
fact, or append a new durable preference/decision/lesson — but do so
|
fact, or append a new durable preference/decision/lesson — but do so
|
||||||
@@ -92,6 +91,12 @@ them. When their signal is clear, act; do not be shy here.
|
|||||||
- If it is already captured anywhere (check MEMORY.md AND the daily file
|
- If it is already captured anywhere (check MEMORY.md AND the daily file
|
||||||
first), do NOTHING.
|
first), do NOTHING.
|
||||||
|
|
||||||
|
4. KNOWLEDGE — only if the conversation produced durable, reusable reference
|
||||||
|
knowledge on a topic (the kind worth looking up again) that the main
|
||||||
|
assistant did NOT already save to `knowledge/`. Add or update the relevant
|
||||||
|
file there. Like memory, this is the exception: skip routine Q&A, and if the
|
||||||
|
topic is already covered in `knowledge/`, do NOTHING rather than duplicate.
|
||||||
|
|
||||||
# Do NOT capture (these poison future behavior)
|
# Do NOT capture (these poison future behavior)
|
||||||
|
|
||||||
- Environment failures: missing binaries, unset credentials, uninstalled
|
- Environment failures: missing binaries, unset credentials, uninstalled
|
||||||
@@ -140,9 +145,6 @@ def build_review_user_message(transcript: str, protected_skills: list = None) ->
|
|||||||
``protected_skills`` lists skill names that must never be edited (built-in
|
``protected_skills`` lists skill names that must never be edited (built-in
|
||||||
skills shipped with the product). Surfaced so the agent avoids them.
|
skills shipped with the product). Surfaced so the agent avoids them.
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
|
||||||
today = datetime.now().strftime("%Y-%m-%d")
|
|
||||||
|
|
||||||
protected_note = ""
|
protected_note = ""
|
||||||
if protected_skills:
|
if protected_skills:
|
||||||
names = ", ".join(sorted(protected_skills))
|
names = ", ".join(sorted(protected_skills))
|
||||||
@@ -152,12 +154,10 @@ def build_review_user_message(transcript: str, protected_skills: list = None) ->
|
|||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
"Here is the conversation transcript that just went idle. Review it per "
|
"Here is the conversation transcript that just went idle. Review it per "
|
||||||
"your instructions and act on any clear signal. Prefer fixing a skill at "
|
"your instructions. Acting is the exception: the main value is fixing or "
|
||||||
"its source over writing memory whenever the fact belongs in a skill.\n"
|
"creating a skill and finishing promised work. Memory and knowledge are "
|
||||||
f"Today is {today}. Only if a fact genuinely belongs in memory (and not "
|
"rare last resorts — stay [SILENT] unless there is a clear, durable signal "
|
||||||
f"in a skill): append one short bullet to the daily file "
|
"not already covered."
|
||||||
f"`memory/{today}.md` for a new fact, or edit MEMORY.md in place to "
|
|
||||||
f"correct an existing wrong fact."
|
|
||||||
f"{protected_note}\n"
|
f"{protected_note}\n"
|
||||||
"<transcript>\n"
|
"<transcript>\n"
|
||||||
f"{transcript}\n"
|
f"{transcript}\n"
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ class Agent:
|
|||||||
self.workspace_dir = workspace_dir # Workspace directory
|
self.workspace_dir = workspace_dir # Workspace directory
|
||||||
self.enable_skills = enable_skills # Skills enabled flag
|
self.enable_skills = enable_skills # Skills enabled flag
|
||||||
self.runtime_info = runtime_info # Runtime info for dynamic time update
|
self.runtime_info = runtime_info # Runtime info for dynamic time update
|
||||||
|
# Optional extra instructions appended AFTER the rebuilt full system
|
||||||
|
# prompt. Used by the self-evolution review agent to add its task brief
|
||||||
|
# on top of the full context (tools, workspace, user preferences, time)
|
||||||
|
# so it both follows the user's preferences and knows its evolution job.
|
||||||
|
self.extra_system_suffix = None
|
||||||
|
|
||||||
# Initialize skill manager
|
# Initialize skill manager
|
||||||
self.skill_manager = None
|
self.skill_manager = None
|
||||||
@@ -120,15 +125,20 @@ class Agent:
|
|||||||
except Exception:
|
except Exception:
|
||||||
lang = "zh"
|
lang = "zh"
|
||||||
builder = PromptBuilder(workspace_dir=self.workspace_dir or "", language=lang)
|
builder = PromptBuilder(workspace_dir=self.workspace_dir or "", language=lang)
|
||||||
return builder.build(
|
full = builder.build(
|
||||||
tools=self.tools,
|
tools=self.tools,
|
||||||
context_files=context_files,
|
context_files=context_files,
|
||||||
skill_manager=self.skill_manager,
|
skill_manager=self.skill_manager,
|
||||||
memory_manager=self.memory_manager,
|
memory_manager=self.memory_manager,
|
||||||
runtime_info=self.runtime_info,
|
runtime_info=self.runtime_info,
|
||||||
)
|
)
|
||||||
|
if self.extra_system_suffix:
|
||||||
|
full = f"{full}\n\n{self.extra_system_suffix}"
|
||||||
|
return full
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Failed to rebuild system prompt, using cached version: {e}")
|
logger.warning(f"Failed to rebuild system prompt, using cached version: {e}")
|
||||||
|
if self.extra_system_suffix:
|
||||||
|
return f"{self.system_prompt}\n\n{self.extra_system_suffix}"
|
||||||
return self.system_prompt
|
return self.system_prompt
|
||||||
|
|
||||||
def refresh_skills(self):
|
def refresh_skills(self):
|
||||||
|
|||||||
@@ -524,6 +524,14 @@ class AgentInitializer:
|
|||||||
logger.debug("[AgentInitializer] WebSearch skipped - no search provider configured")
|
logger.debug("[AgentInitializer] WebSearch skipped - no search provider configured")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Skip evolution_undo when self-evolution is disabled: with no
|
||||||
|
# evolution there is nothing to roll back, so the tool is dead weight.
|
||||||
|
if tool_name == "evolution_undo":
|
||||||
|
from agent.evolution.config import get_evolution_config
|
||||||
|
if not get_evolution_config().enabled:
|
||||||
|
logger.debug("[AgentInitializer] evolution_undo skipped - self-evolution disabled")
|
||||||
|
continue
|
||||||
|
|
||||||
# Special handling for EnvConfig tool
|
# Special handling for EnvConfig tool
|
||||||
if tool_name == "env_config":
|
if tool_name == "env_config":
|
||||||
from agent.tools import EnvConfig
|
from agent.tools import EnvConfig
|
||||||
|
|||||||
@@ -772,7 +772,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button id="memory-tab-dreams" onclick="switchMemoryTab('dreams')"
|
<button id="memory-tab-dreams" onclick="switchMemoryTab('dreams')"
|
||||||
class="memory-tab px-3 py-1.5 rounded-md text-xs font-medium cursor-pointer transition-colors duration-150">
|
class="memory-tab px-3 py-1.5 rounded-md text-xs font-medium cursor-pointer transition-colors duration-150">
|
||||||
<i class="fas fa-seedling mr-1.5"></i><span data-i18n="memory_tab_dreams">自我进化</span>
|
<i class="fas fa-seedling mr-1.5"></i><span data-i18n="memory_tab_dreams">自进化</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ const I18N = {
|
|||||||
config_max_turns: '最大记忆轮次', config_max_turns_hint: '一问一答为一轮,超过后会智能压缩处理',
|
config_max_turns: '最大记忆轮次', config_max_turns_hint: '一问一答为一轮,超过后会智能压缩处理',
|
||||||
config_max_steps: '最大执行步数', config_max_steps_hint: '单次对话中 Agent 最多调用工具的次数',
|
config_max_steps: '最大执行步数', config_max_steps_hint: '单次对话中 Agent 最多调用工具的次数',
|
||||||
config_enable_thinking: '深度思考', config_enable_thinking_hint: '是否启用深度思考模式',
|
config_enable_thinking: '深度思考', config_enable_thinking_hint: '是否启用深度思考模式',
|
||||||
config_self_evolution: '自我进化', config_self_evolution_hint: '会话空闲后自动复盘,沉淀记忆、优化技能、处理未完成事项',
|
config_self_evolution: '自进化', config_self_evolution_hint: '会话空闲后自动复盘,沉淀记忆、优化技能、处理未完成事项',
|
||||||
evolution_badge: '自主学习',
|
evolution_badge: '自主学习',
|
||||||
config_channel_type: '通道类型',
|
config_channel_type: '通道类型',
|
||||||
config_provider: '模型厂商', config_model_name: '模型',
|
config_provider: '模型厂商', config_model_name: '模型',
|
||||||
@@ -142,7 +142,7 @@ const I18N = {
|
|||||||
skills_section_title: '技能', skill_enable: '启用', skill_disable: '禁用',
|
skills_section_title: '技能', skill_enable: '启用', skill_disable: '禁用',
|
||||||
skill_toggle_error: '操作失败,请稍后再试',
|
skill_toggle_error: '操作失败,请稍后再试',
|
||||||
memory_title: '记忆管理', memory_desc: '查看 Agent 记忆文件和内容',
|
memory_title: '记忆管理', memory_desc: '查看 Agent 记忆文件和内容',
|
||||||
memory_tab_files: '记忆文件', memory_tab_dreams: '自我进化',
|
memory_tab_files: '记忆文件', memory_tab_dreams: '自进化',
|
||||||
memory_loading: '加载记忆文件中...', memory_loading_desc: '记忆文件将显示在此处',
|
memory_loading: '加载记忆文件中...', memory_loading_desc: '记忆文件将显示在此处',
|
||||||
memory_back: '返回列表',
|
memory_back: '返回列表',
|
||||||
memory_col_name: '文件名', memory_col_type: '类型', memory_col_size: '大小', memory_col_updated: '更新时间',
|
memory_col_name: '文件名', memory_col_type: '类型', memory_col_size: '大小', memory_col_updated: '更新时间',
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ available_setting = {
|
|||||||
# Self-evolution: review idle conversations to learn memory/skills. Flat keys.
|
# Self-evolution: review idle conversations to learn memory/skills. Flat keys.
|
||||||
"self_evolution_enabled": False, # master switch (off until release)
|
"self_evolution_enabled": False, # master switch (off until release)
|
||||||
"self_evolution_idle_minutes": 15, # idle time before a session is reviewed
|
"self_evolution_idle_minutes": 15, # idle time before a session is reviewed
|
||||||
"self_evolution_min_turns": 6, # min user turns (or context pressure) to trigger
|
"self_evolution_min_turns": 8, # min user turns (or context pressure) to trigger
|
||||||
"skill": {}, # Per-skill runtime config; nested keys flatten to SKILL_<NAME>_<KEY> env vars at startup
|
"skill": {}, # Per-skill runtime config; nested keys flatten to SKILL_<NAME>_<KEY> env vars at startup
|
||||||
"mcp_servers": [], # MCP server list; each entry supports type "stdio" (local process) or "sse" (remote URL)
|
"mcp_servers": [], # MCP server list; each entry supports type "stdio" (local process) or "sse" (remote URL)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Self-Evolution focuses on three things:
|
|||||||
| Goal | Description |
|
| Goal | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **Consolidate memory** | Record important preferences, decisions, and facts from the conversation, filling in what the main chat may have missed |
|
| **Consolidate memory** | Record important preferences, decisions, and facts from the conversation, filling in what the main chat may have missed |
|
||||||
| **Improve skills** | When a skill shows a problem in use (such as a wrong setting or a missing step), fix the skill file directly instead of just noting it; create a new skill when one is genuinely needed |
|
| **Improve skills** | ① When a skill shows a problem in use (such as a wrong setting or a missing step), fix the skill file directly; ② when a reusable workflow emerges, turn it into a new skill so it can be reused next time |
|
||||||
| **Follow up on unfinished tasks** | Spot the to-dos left in a conversation and finish them when possible |
|
| **Follow up on unfinished tasks** | Spot the to-dos left in a conversation and finish them when possible |
|
||||||
|
|
||||||
Once a review is done, if it actually changed something, the Agent tells you in a single line what it just learned and what it adjusted, so you can decide whether to roll it back.
|
Once a review is done, if it actually changed something, the Agent tells you in a single line what it just learned and what it adjusted, so you can decide whether to roll it back.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: 自我进化
|
title: 自进化
|
||||||
description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优化技能、处理未完成事项
|
description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优化技能、处理未完成事项
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -7,19 +7,20 @@ description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优
|
|||||||
|
|
||||||
### 简介
|
### 简介
|
||||||
|
|
||||||
自我进化(Self-Evolution)让 Agent 不止于"完成单次任务",而是能在与你的相处中持续成长。每段对话告一段落后,它会自动"回头复盘"一次:把值得记住的沉淀为长期记忆、把使用中暴露的问题修进技能、把没做完的事情接着推进。久而久之,Agent 会越来越懂你的偏好、越来越少重复犯错、越来越主动地把事情收尾,而这一切都在后台静默完成,只有真正做了事情时才会简短地告诉你。
|
自进化(Self-Evolution)让 Agent 不止于"完成单次任务",而是能在与你的相处中持续成长。每段对话告一段落后,它会自动"回头复盘"一次:把使用中暴露的问题修进技能、把没做完的事情接着推进,并把值得记住的沉淀进记忆与知识库。久而久之,Agent 会越来越懂你的偏好、越来越少重复犯错、越来越主动地把事情收尾,而这一切都在后台静默完成,只有真正做了事情时才会简短地告诉你。
|
||||||
|
|
||||||
> 它与[梦境蒸馏](/zh/memory/deep-dream)互补:梦境蒸馏负责整理记忆本身,自我进化则在记忆之外,进一步优化技能、推进未完成的任务,让 Agent 的能力随使用不断打磨。
|
> 它与[梦境蒸馏](/zh/memory/deep-dream)互补:梦境蒸馏负责整理记忆本身,自进化则在记忆之外,进一步优化技能、推进未完成的任务,让 Agent 的能力随使用不断打磨。
|
||||||
|
|
||||||
### 三个目标
|
### 几个目标
|
||||||
|
|
||||||
自我进化围绕三件事工作:
|
自进化围绕以下几件事工作,并以「优化技能、处理未完成事项」为主,「沉淀记忆、知识」作为主对话的查缺补漏:
|
||||||
|
|
||||||
| 目标 | 说明 |
|
| 目标 | 说明 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **沉淀记忆** | 把对话中重要的偏好、决策、事实补记到记忆中,作为主对话的查缺补漏 |
|
| **优化技能** | ① 技能在使用中暴露问题(如配置错误、步骤缺失)时,直接修正技能文件;② 出现一套可复用的流程时,主动固化为新技能,下次直接调用 |
|
||||||
| **优化技能** | 当某个技能在使用中暴露出问题(如配置错误、步骤缺失),直接修正技能文件,而不只是记一笔;也可在需要时创建新技能 |
|
|
||||||
| **处理未完成事项** | 识别对话中遗留的待办,在能完成时直接完成 |
|
| **处理未完成事项** | 识别对话中遗留的待办,在能完成时直接完成 |
|
||||||
|
| **沉淀记忆** | 把对话中重要的偏好、决策、事实补记到记忆中,作为主对话的查缺补漏 |
|
||||||
|
| **沉淀知识** | 把对话中产生的、值得日后查阅的可复用知识补充进知识库(主对话遗漏时) |
|
||||||
|
|
||||||
复盘完成后,如果确实做了改动,Agent 会在对话中用一句话告诉你"刚刚自主学习了什么、调整了哪里",方便你判断是否需要回滚。
|
复盘完成后,如果确实做了改动,Agent 会在对话中用一句话告诉你"刚刚自主学习了什么、调整了哪里",方便你判断是否需要回滚。
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优
|
|||||||
|
|
||||||
### 触发时机
|
### 触发时机
|
||||||
|
|
||||||
自我进化不是定时执行,而是在**一段对话自然结束、进入空闲后**才触发,避免打断正在进行的交流。需要同时满足:
|
自进化不是定时执行,而是在**一段对话自然结束、进入空闲后**才触发,避免打断正在进行的交流。需要同时满足:
|
||||||
|
|
||||||
- **对话已空闲** — 距离最后一次互动超过设定的空闲时长(默认 15 分钟)
|
- **对话已空闲** — 距离最后一次互动超过设定的空闲时长(默认 15 分钟)
|
||||||
- **对话有足够内容** — 自上次进化以来累积了足够轮次,或上下文已接近容量上限
|
- **对话有足够内容** — 自上次进化以来累积了足够轮次,或上下文已接近容量上限
|
||||||
@@ -36,11 +37,11 @@ description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优
|
|||||||
|
|
||||||
### 相关配置
|
### 相关配置
|
||||||
|
|
||||||
自我进化默认关闭,可在 Web 控制台「配置 → Agent 配置」中通过开关启用(位于"深度思考"下方),也可在配置文件中调整:
|
自进化默认关闭,可在 Web 控制台「配置 → Agent 配置」中通过开关启用(位于"深度思考"下方),也可在配置文件中调整:
|
||||||
|
|
||||||
| 参数 | 说明 | 默认值 |
|
| 参数 | 说明 | 默认值 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `self_evolution_enabled` | 是否启用自我进化 | `false` |
|
| `self_evolution_enabled` | 是否启用自进化 | `false` |
|
||||||
| `self_evolution_idle_minutes` | 对话空闲多久后触发(分钟) | `15` |
|
| `self_evolution_idle_minutes` | 对话空闲多久后触发(分钟) | `15` |
|
||||||
| `self_evolution_min_turns` | 触发所需的最少对话轮次 | `6` |
|
| `self_evolution_min_turns` | 触发所需的最少对话轮次 | `6` |
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优
|
|||||||
|
|
||||||
### 进化记录
|
### 进化记录
|
||||||
|
|
||||||
每次进化的过程和结果会按日期记录在 `memory/evolution/YYYY-MM-DD.md` 中,可在 Web 控制台的「记忆管理 → 自我进化」tab 中查看。该 tab 同时汇总了自我进化记录与梦境日记,方便统一回顾 Agent 的成长轨迹。
|
每次进化的过程和结果会按日期记录在 `memory/evolution/YYYY-MM-DD.md` 中,可在 Web 控制台的「记忆管理 → 自进化」tab 中查看。该 tab 同时汇总了自进化记录与梦境日记,方便统一回顾 Agent 的成长轨迹。
|
||||||
|
|
||||||
### 如何回滚
|
### 如何回滚
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优
|
|||||||
|
|
||||||
## 实现设计
|
## 实现设计
|
||||||
|
|
||||||
自我进化复用了系统已有的能力,保持轻量:
|
自进化复用了系统已有的能力,保持轻量:
|
||||||
|
|
||||||
- **隔离执行**:每次复盘都启动一个独立的、临时的复盘任务,使用与主对话相同的模型,但拥有受限的工具集(只能读上下文、改记忆与技能文件)。它不会污染主对话的上下文,也不会影响主对话的性能。
|
- **隔离执行**:每次复盘都启动一个独立的、临时的复盘任务,使用与主对话相同的模型,但拥有受限的工具集(只能读上下文、改记忆与技能文件)。它不会污染主对话的上下文,也不会影响主对话的性能。
|
||||||
- **基于备份的撤销**:进化前对相关文件做快照备份,撤销时按备份还原,因此每一次改动都可追溯、可逆。
|
- **基于备份的撤销**:进化前对相关文件做快照备份,撤销时按备份还原,因此每一次改动都可追溯、可逆。
|
||||||
@@ -66,7 +67,7 @@ description: Self-Evolution — 会话空闲后自动复盘,沉淀记忆、优
|
|||||||
|
|
||||||
### 克制与安全
|
### 克制与安全
|
||||||
|
|
||||||
自我进化的设计原则是"必要时执行,减少打扰":
|
自进化的设计原则是"必要时执行,减少打扰":
|
||||||
|
|
||||||
| 机制 | 说明 |
|
| 机制 | 说明 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
|||||||
@@ -195,6 +195,55 @@ def scenario_silent():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def scenario_silent_qa():
|
||||||
|
"""A normal knowledge Q&A -> nothing durable, should stay SILENT."""
|
||||||
|
return {
|
||||||
|
"name": "普通问答 (should stay SILENT)",
|
||||||
|
"goal": "none",
|
||||||
|
"turns": [
|
||||||
|
("Python 里 list 和 tuple 有什么区别?",
|
||||||
|
"主要区别:list 可变、用 [];tuple 不可变、用 ()。tuple 更省内存、可作字典键。"),
|
||||||
|
("那什么时候该用 tuple?", "当数据不应被修改、或要做字典键/集合元素时用 tuple。"),
|
||||||
|
("懂了,谢谢", "不客气。"),
|
||||||
|
],
|
||||||
|
"scripted": "[SILENT]",
|
||||||
|
"on_edit": None,
|
||||||
|
"expect_evolved": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def scenario_silent_transient():
|
||||||
|
"""User shares transient, non-durable info -> should stay SILENT."""
|
||||||
|
return {
|
||||||
|
"name": "临时信息 (should stay SILENT)",
|
||||||
|
"goal": "none",
|
||||||
|
"turns": [
|
||||||
|
("帮我看下今天天气适合跑步吗,深圳", "深圳今天多云 26°C,傍晚湿度高,清晨或晚上跑步比较合适。"),
|
||||||
|
("那我晚上去吧", "好的,记得补水。"),
|
||||||
|
("行", "👍"),
|
||||||
|
],
|
||||||
|
"scripted": "[SILENT]",
|
||||||
|
"on_edit": None,
|
||||||
|
"expect_evolved": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def scenario_silent_advice():
|
||||||
|
"""User asks for one-off advice, no reusable workflow -> should stay SILENT."""
|
||||||
|
return {
|
||||||
|
"name": "一次性建议 (should stay SILENT)",
|
||||||
|
"goal": "none",
|
||||||
|
"turns": [
|
||||||
|
("给我起三个适合咖啡馆的名字", "可以考虑:① 拾光咖啡 ② 角落 Corner ③ 慢半拍。"),
|
||||||
|
("第二个不错", "嗯,「角落 Corner」简洁好记。"),
|
||||||
|
("就用这个了", "好的,祝开业顺利。"),
|
||||||
|
],
|
||||||
|
"scripted": "[SILENT]",
|
||||||
|
"on_edit": None,
|
||||||
|
"expect_evolved": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def scenario_memory_preference():
|
def scenario_memory_preference():
|
||||||
"""User states a durable working preference -> update MEMORY.md."""
|
"""User states a durable working preference -> update MEMORY.md."""
|
||||||
def edit(ws):
|
def edit(ws):
|
||||||
@@ -475,6 +524,9 @@ def scenario_unfinished_task():
|
|||||||
|
|
||||||
SCENARIOS = [
|
SCENARIOS = [
|
||||||
scenario_silent,
|
scenario_silent,
|
||||||
|
scenario_silent_qa,
|
||||||
|
scenario_silent_transient,
|
||||||
|
scenario_silent_advice,
|
||||||
scenario_memory_preference,
|
scenario_memory_preference,
|
||||||
scenario_memory_correction,
|
scenario_memory_correction,
|
||||||
scenario_skill_gap,
|
scenario_skill_gap,
|
||||||
@@ -791,6 +843,12 @@ def run_real():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
if "--debug" in sys.argv:
|
||||||
|
import logging
|
||||||
|
from common.log import logger as _cow_logger
|
||||||
|
_cow_logger.setLevel(logging.DEBUG)
|
||||||
|
for _h in _cow_logger.handlers:
|
||||||
|
_h.setLevel(logging.DEBUG)
|
||||||
if "--real" in sys.argv:
|
if "--real" in sys.argv:
|
||||||
run_real()
|
run_real()
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user