mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-17 11:07:11 +08:00
Add a self-evolution subsystem that reviews idle conversations in an isolated agent and durably learns from them — patching/creating skills, finishing unfinished tasks, and backfilling missed memory. - Trigger: background idle scan, fires when a session is idle >= N min AND (>= N turns OR context usage > 80%). In-memory cursor reviews only new messages so a session never re-learns old content. - Isolated review agent: same model, restricted toolset, hard write-guard confining edits to the workspace (built-in skills are protected). - Safety: file-level backup before edits + evolution_undo tool; notify the user ONLY when a workspace file actually changed (no-nag rule); capped concurrency. - Records to memory/evolution/<date>.md, surfaced in the memory UI's renamed "Self-Evolution" tab (merged with dream diaries). - Hide internal [SCHEDULED]/[EVOLUTION]/backup_id markers from chat history display (also fixes scheduler marker leakage) while keeping them in stored content for undo. - Flat config: self_evolution_enabled (default off until release), self_evolution_idle_minutes (15), self_evolution_min_turns (6). - Tests: tests/test_evolution.py (stub + real model modes, 7 scenarios).
56 lines
2.0 KiB
Python
56 lines
2.0 KiB
Python
"""Self-evolution record log.
|
|
|
|
Session-level evolutions are appended to their OWN per-day file under
|
|
``memory/evolution/YYYY-MM-DD.md`` (separate from the nightly Deep Dream diary
|
|
in ``memory/dreams/``). Each day's file accumulates one short section per
|
|
evolution pass — tagged with a timestamp and a backup id for undo — so the
|
|
memory UI can surface "what the agent learned/changed today" on one timeline
|
|
without ever mixing into the dream diary or the main conversation memory.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from common.log import logger
|
|
|
|
|
|
def _evolution_dir(workspace_dir: Path, user_id: Optional[str] = None) -> Path:
|
|
base = Path(workspace_dir) / "memory"
|
|
if user_id:
|
|
return base / "users" / user_id / "evolution"
|
|
return base / "evolution"
|
|
|
|
|
|
def append_session_evolution(
|
|
workspace_dir: Path,
|
|
summary: str,
|
|
backup_id: Optional[str] = None,
|
|
user_id: Optional[str] = None,
|
|
) -> None:
|
|
"""Append a session-evolution entry to today's evolution log."""
|
|
if not summary or not summary.strip():
|
|
return
|
|
try:
|
|
evo_dir = _evolution_dir(workspace_dir, user_id)
|
|
evo_dir.mkdir(parents=True, exist_ok=True)
|
|
today = datetime.now().strftime("%Y-%m-%d")
|
|
log_file = evo_dir / f"{today}.md"
|
|
|
|
ts = datetime.now().strftime("%H:%M")
|
|
header = f"## {ts}"
|
|
body = summary.strip()
|
|
if backup_id:
|
|
body += f"\n\n_backup_id: {backup_id}_"
|
|
|
|
# Create with a title if the file is new, otherwise append a section.
|
|
if not log_file.exists():
|
|
log_file.write_text(f"# Self-Evolution: {today}\n\n", encoding="utf-8")
|
|
with open(log_file, "a", encoding="utf-8") as f:
|
|
f.write(f"\n{header}\n\n{body}\n")
|
|
logger.info(f"[Evolution] Recorded session evolution to {log_file.name}")
|
|
except Exception as e:
|
|
logger.warning(f"[Evolution] Failed to record session evolution: {e}")
|