摘要
本文基于 OpenClaw 4.9 的更新内容,系统拆解其"做梦(Dreaming)"记忆整合系统:浅睡眠 / REM / 深睡眠三阶段管线、Jaccard 去重、加权打分与历史回填机制,并结合多模型评测、安全加固等特性,给出一个可在自研 Agent 中复用的实现思路与 Python API 示例(基于薛定猫 AI 平台 xuedingmao.com)。
一、背景介绍:为什么智能体需要"做梦"
在大部分应用里,LLM Agent 的"记忆"通常是:
- 一份长期记忆:如
memory.md记录用户偏好、约定事实 - 一堆短期交互日志:按天滚动的对话/笔记文件
常见问题:
-
短期记忆只向前,不向内演化
每日笔记不断堆积,但系统只自动加载"今天+昨天",更早的内容闲置,无法在推理中被利用。
-
长期记忆需要人工整理
memory.md只在显式指令下更新,导致两周前、一个月前反复出现的偏好/模式并不会自动沉淀。 -
缺乏"选择性遗忘"和"抽象提炼"机制
结果是要么上下文暴涨变贵,要么有用经验被淹没在噪音中。
OpenClaw 4.9 引入的 Dreaming 系统,实际上就是一个面向 Agent 的"睡眠期记忆巩固架构":在你不使用 Agent 时(默认凌晨 3 点,托管 cron),后台自动完成记忆抽取、去重、聚类与晋升。
从工程视角看,它解决的是两个核心问题:
- 如何从大量 session log 中提炼出稳定、可复用的"事实/偏好"
- 如何在控制成本的前提下,让 Agent 的能力随时间"复利式增强"
二、核心原理:三阶段 Dreaming 管线与加权评分
2.1 三阶段睡眠模型
Dreaming 被拆成三个阶段,对应大脑睡眠类比,但本质是一个异步 ETL + Ranking 流程。
1)Light Sleep(浅睡眠):数据摄入与去重
- 读取近期每日记忆文件(如最近 N 天的日记 / session log)
- 切分为 snippet(摘要片段),存入
memory/.dreams/session_corpus/ - 通过 Jaccard 相似度 去重,阈值 0.9:
- 两条文本的 token 集合交并比 ≥ 0.9,则保留一条
- 记录 "light phase signal hits" 作为候选加分信号
特点:
只做"暂存 + 标记",不写入 memory.md。
2)REM Sleep:主题聚合与候选抽取
- Lookback 窗口默认 7 天:回顾过去 7 天的 staged material
- 对各 snippet 进行概念标注/标签化(concept tags)
- 统计标签频率,识别"跨 session 反复出现"的主题
- 如:用户多次提到"我用的是 VSCode + Python 3.11"
- 被多次提及的 idea/topic 会被标记为 candidate truth
- 记录 REM signal hits,为候选在最后排名加分
- 将观测写入 dreaming trail(类似梦境日志)
3)Deep Sleep:加权评分与晋升
深睡眠阶段是真正写入长期记忆的决策步骤。
- 收集所有 candidate(来自 Light+REM)
- 使用包含 6 个加权信号的 scoring 算法:
| 信号 | 权重 | 含义简述 |
|---|---|---|
| Relevance | 0.30 | 与用户/任务目标的相关性 |
| Frequency | 0.24 | 在多日、多 session 中出现的频率 |
| Query Diversity | 0.15 | 在不同问题类型下反复出现 |
| Recency | 0.15 | 最近出现的时间是否较近 |
| Consolidation | 0.10 | 在前几轮 Dreaming 中是否也被选为候选 |
| Conceptual Richness | 0.06 | 信息是否足够"原子且具体"、非空洞抽象 |
超过阈值的候选会被"晋升"到 memory.md,成为长期记忆的一部分。
2.2 Backfill Lane:历史数据的再加工
早期版本 Dreaming 仅处理近期笔记,历史文件长期闲置。
4.9 新增 backfill lane:
- 可以将 Dreaming 管线指向旧的历史 notes
- 把历史日志按同一流程重新"重放"与抽取
- 等价于给你的 Agent 做一次"过去半年履历扫描"
同时增加:
- Diary commit & reset 流程:
- 如果有不希望晋升的暂存条目,可重置 dreaming state 重新开始
2.3 提取逻辑优化:抑制噪音、保留原子事实
新版 durable fact extraction 重点做了两件事:
- 更好地过滤"操作噪音":如临时 debug、路径/ID 噪音等
- 更偏向保留"原子、具体"的事实:
- 例如"用户默认使用 '黑暗主题 + 14px 字号 + JetBrains Mono'"
而不是"用户喜欢好看的界面"这类模糊抽象
- 例如"用户默认使用 '黑暗主题 + 14px 字号 + JetBrains Mono'"
这会显著提升长期记忆的"可执行性"和调用价值。
三、实战演示:在自研 Agent 中实现类 Dreaming 记忆整合
下面给出一个简化版 Dreaming 管线示例,核心目标:
- 从多天对话日志中抽取"可晋升的事实/偏好"
- 使用 LLM 作为"事实提取 +标签生成+打分器"
- 通过 Jaccard 去重 + Python 实现评分逻辑
为方便落地,示例使用 薛定猫 AI 平台(xuedingmao.com) 的 OpenAI 兼容 API,默认模型为 claude-sonnet-4-6。
说明:xuedingmao 提供统一的 OpenAI 风格接口,聚合了 GPT-5.4、Claude 4.6、Gemini 3 Pro 等 500+ 模型,适合作为多模型评测/路由的统一接入层。
3.1 环境准备
bash
pip install requests python-dotenv scikit-learn
.env 中配置:
env
XUEDINGMAO_API_KEY=你的_api_key
XUEDINGMAO_BASE_URL=https://xuedingmao.com
3.2 Python 代码示例:简化版 Dreaming 管线
python
import os
import json
import glob
from dotenv import load_dotenv
import requests
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics import jaccard_score
import numpy as np
from datetime import datetime, timedelta
load_dotenv()
API_KEY = os.getenv("XUEDINGMAO_API_KEY")
BASE_URL = os.getenv("XUEDINGMAO_BASE_URL", "https://xuedingmao.com")
MODEL = "claude-sonnet-4-6"
def call_llm(system_prompt, user_prompt):
"""
调用薛定猫 AI 平台(OpenAI 兼容模式)的通用函数
"""
url = f"{BASE_URL}/v1/chat/completions"
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
data = {
"model": MODEL,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
"temperature": 0.1,
}
resp = requests.post(url, headers=headers, json=data, timeout=60)
resp.raise_for_status()
return resp.json()["choices"][0]["message"]["content"]
# ---------- 1. Light Sleep:加载多日日志 + Jaccard 去重 ----------
def load_daily_notes(folder, days_lookback=7):
"""
从指定文件夹读取最近 N 天的日记文件
文件命名约定: YYYY-MM-DD.md
"""
cutoff = datetime.now() - timedelta(days=days_lookback)
files = glob.glob(os.path.join(folder, "*.md"))
selected = []
for f in files:
base = os.path.basename(f).split(".")[0]
try:
dt = datetime.strptime(base, "%Y-%m-%d")
except ValueError:
continue
if dt >= cutoff:
selected.append(f)
selected.sort()
return selected
def jaccard_dedup(snippets, threshold=0.9):
"""
对文本片段进行 Jaccard 去重,保留代表性片段
"""
if not snippets:
return []
vec = CountVectorizer().fit_transform(snippets)
mat = vec.toarray().astype(bool)
keep = []
for i, row in enumerate(mat):
duplicate = False
for j in keep:
score = jaccard_score(row, mat[j])
if score >= threshold:
duplicate = True
break
if not duplicate:
keep.append(i)
return [snippets[i] for i in keep]
def extract_snippets_from_text(text):
"""
使用 LLM 将长文本切分为若干"可候选的事实/偏好"片段
返回一个列表,每个元素是一条短句描述
"""
system_prompt = "你是一个帮助从对话日志中抽取候选事实和偏好的助手。"
user_prompt = (
"以下是最近几天的对话/笔记内容,请提取 5-15 条简短、原子、可复用的事实或偏好,"
"每条不超过 100 字,以 JSON 数组返回,每个元素为字符串。\n\n" + text
)
content = call_llm(system_prompt, user_prompt)
try:
return json.loads(content)
except json.JSONDecodeError:
# 简单回退:按行分割
return [line.strip() for line in content.splitlines() if line.strip()]
def light_sleep_stage(notes_folder, days_lookback=7):
files = load_daily_notes(notes_folder, days_lookback)
raw_snippets = []
for f in files:
with open(f, "r", encoding="utf-8") as rf:
raw_snippets.append(rf.read())
if not raw_snippets:
return []
# 合并多日文本给 LLM 做一次 snippet 抽取
combined = "\n\n".join(raw_snippets)
snippets = extract_snippets_from_text(combined)
deduped = jaccard_dedup(snippets, threshold=0.9)
return deduped
# ---------- 2. REM Sleep:主题标签 + 候选抽取 ----------
def tag_and_group_snippets(snippets):
"""
使用 LLM 为每个 snippet 生成标签,并根据标签聚合。
返回: {tag: [snippets...]}
"""
system_prompt = "你是一个为用户事实片段打标签并按主题聚类的助手。"
user_prompt = (
"下面是若干条用户相关事实/偏好,请为每条生成 1-3 个主题标签(中文),"
"并按标签聚类,以 JSON 形式返回: {tag: [snippet1, snippet2, ...]}。\n\n"
+ json.dumps(snippets, ensure_ascii=False, indent=2)
)
content = call_llm(system_prompt, user_prompt)
try:
groups = json.loads(content)
return groups
except json.JSONDecodeError:
return {"未分类": snippets}
def rem_sleep_stage(snippets):
"""
REM 阶段:按主题聚类,统计频次,生成 candidate truths。
"""
groups = tag_and_group_snippets(snippets)
candidates = []
for tag, items in groups.items():
if len(items) <= 1:
continue # 出现频次太低,可视为噪音
# 将同主题的多条 snippet 汇总成候选事实
merged_fact = f"主题:{tag};总结:{';'.join(items)}"
candidates.append({
"tag": tag,
"snippets": items,
"merged_fact": merged_fact,
"frequency": len(items),
})
return candidates
# ---------- 3. Deep Sleep:加权评分 + 晋升到长期记忆 ----------
def score_candidates(candidates, relevance_weight=0.30, freq_weight=0.24,
diversity_weight=0.15, recency_weight=0.15,
consolidation_weight=0.10, richness_weight=0.06):
"""
用 LLM 评估每个候选:相关性、查询多样性、时间性、巩固程度、丰富度。
这里示例中简化:只用 LLM 返回各项 0-1 分,再按权重求和。
"""
system_prompt = (
"你是一个为候选用户长期记忆打分的评估器。"
"对于每条候选事实,请根据以下维度打 0~1 分:"
"relevance, diversity, recency, consolidation, richness。"
"返回 JSON 数组,每个元素形如:{"
"\"relevance\": 0.8, \"diversity\": 0.6, \"recency\": 0.7,"
"\"consolidation\": 0.5, \"richness\": 0.9"
"}。频率 frequency 已在外部提供,无需打分。"
)
descriptions = [c["merged_fact"] for c in candidates]
user_prompt = json.dumps(descriptions, ensure_ascii=False, indent=2)
content = call_llm(system_prompt, user_prompt)
try:
scores_list = json.loads(content)
except json.JSONDecodeError:
# 回退:全部给中性分
scores_list = [{"relevance": 0.5, "diversity": 0.5,
"recency": 0.5, "consolidation": 0.5,
"richness": 0.5} for _ in candidates]
# 归一化频率
freqs = np.array([c["frequency"] for c in candidates], dtype=float)
if freqs.max() > 0:
freqs = freqs / freqs.max()
else:
freqs = np.zeros_like(freqs)
results = []
for c, s, f_norm in zip(candidates, scores_list, freqs):
total_score = (
s.get("relevance", 0) * relevance_weight +
f_norm * freq_weight +
s.get("diversity", 0) * diversity_weight +
s.get("recency", 0) * recency_weight +
s.get("consolidation", 0) * consolidation_weight +
s.get("richness", 0) * richness_weight
)
c["score"] = total_score
results.append(c)
return results
def promote_to_long_term_memory(scored_candidates, memory_md_path,
threshold=0.6, max_items=20):
"""
将超过阈值的候选事实写入 memory.md(追加方式)
"""
scored_candidates.sort(key=lambda x: x["score"], reverse=True)
selected = [c for c in scored_candidates if c["score"] >= threshold]
selected = selected[:max_items]
if not selected:
return
# 读现有长期记忆,避免重复写入
existing = ""
if os.path.exists(memory_md_path):
with open(memory_md_path, "r", encoding="utf-8") as rf:
existing = rf.read()
with open(memory_md_path, "a", encoding="utf-8") as wf:
wf.write("\n\n## Dreaming Consolidation - {}\n".format(
datetime.now().strftime("%Y-%m-%d %H:%M")
))
for c in selected:
if c["merged_fact"] in existing:
continue
wf.write(f"- {c['merged_fact']} (score={c['score']:.2f})\n")
def run_dreaming_pipeline(notes_folder, memory_md_path, days_lookback=7):
"""
一次完整的 Dreaming 流程:浅睡眠 -> REM -> 深睡眠
可挂载到 cron,每天凌晨执行一次。
"""
snippets = light_sleep_stage(notes_folder, days_lookback)
if not snippets:
print("No snippets extracted. Skip.")
return
candidates = rem_sleep_stage(snippets)
if not candidates:
print("No candidates from REM stage. Skip.")
return
scored = score_candidates(candidates)
promote_to_long_term_memory(scored, memory_md_path)
print("Dreaming pipeline finished.")
if __name__ == "__main__":
# 示例:notes_folder 存放你的 daily notes,memory.md 为长期记忆文件
NOTES_FOLDER = "./memory/daily"
MEMORY_MD = "./memory/memory.md"
run_dreaming_pipeline(NOTES_FOLDER, MEMORY_MD, days_lookback=7)
你可以将 run_dreaming_pipeline 挂在系统 cron,实现类似 OpenClaw 的"凌晨做梦"。
四、注意事项与工程实践建议
4.1 安全与可信输入
视频中另一重要更新是:远程节点输出标记为不受信任并过滤 ,以及阻止不受信任的 workspace.env 覆盖安全配置。
在自研 Agent/工具链时建议:
- 将"本地工具执行结果 / 远程节点输出"视为
untrusted input - 在进入 LLM 上下文前明确标注来源,并做必要的 sanitization
- 对任何可能影响系统配置的输入做白名单过滤,而非黑名单
4.2 多模型评估与人格一致性
Character Vibes QA 系统提供了一个很值得借鉴的思路:
- 使用统一 persona 文件(如
persona.md)定义 Agent 角色 - 对同一组测试 prompt,分别调用多个模型(GPT 系列 / Claude / Gemini / 本地 Llama...)
- 自动生成 side-by-side 报告,对比:
- 语气、风格、一致性
- 工具调用行为
- 安全合规表现
这与我们在上文代码中使用的 统一 API 接入层 非常契合。
在实际项目中,可以直接基于薛定猫 AI:
- 同一接口下切换不同模型(GPT-5.4 / Claude 4.6 / Gemini 3 Pro / 本地模型代理)
- 快速完成模型选型与 A/B 测试,而不用改动业务逻辑代码
4.3 移动端与多渠道路由
- Android 配对完整重构:更可靠的二维码绑定、错误状态恢复与电量优化
- Slack / Matrix / Telegram 路由修复:解决消息泄露与重复发送
如果你的 Agent 是"多渠道消息枢纽",务必:
- 明确 session 归属:每个 channel / thread 对应独立上下文
- 做好消息路由隔离,避免"跨群串话"
- 在日志中记录
channel_id, session_id, user_id,方便审计与 Debug
4.4 本地模型推理链可视化
对于通过本地 Llama 模型(如 Ollama)运行的 Agent,4.9 支持在 UI 中显式展示"思考过程"。
在自研系统中,建议将:
reasoning_tracetool_call_logmemory_read/write trace
统一序列化保存,既方便调试,也有助于解释智能体行为。
五、技术资源与平台选型建议
在构建具备"做梦记忆 + 多模型评估 + 安全加固"的 Agent 系统时,底层模型与 API 平台的选型非常关键,建议关注:
-
模型覆盖与更新速度
是否能第一时间接入新出的前沿模型(如 GPT-5.4、Claude 4.6、Gemini 3 Pro 等),可以直接影响多模型评估的有效性。
-
统一 API 接口与路由能力
对于类似 Character Vibes QA 的场景,使用统一的 OpenAI 兼容接口能显著降低代码复杂度。
上文示例使用的 薛定猫 AI(xuedingmao.com) 就是这种模式:
- 聚合 500+ 模型,支持主流商业 / 开源模型
- 统一接入方式,切换模型只需改一个参数
- 适合构建多模型路由、评测与 fallback 策略
-
稳定性与生产可用性
尤其是 Dreaming 这类后台 cron 任务,对 API 稳定性和限流策略很敏感,推荐优先选择在生产环境实测稳定的平台。
结语
Dreaming 系统的价值不在于"多了一个记忆文件",而在于它为 Agent 引入了一个持续、自动、可解释的记忆演化机制:
- 每天只做一点点抽取与整理
- 三个月后 Agent 对你的业务与习惯有"质变级理解"
- 比起每次从空白上下文起步的 Agent,有明显竞争优势
如果你正在自研 Agent,不妨将本文的简化 Dreaming 管线接入现有项目,再配合多模型评测与安全加固,逐步搭建属于自己的"生产级智能体基座"。
#AI #大模型 #Python #机器学习 #技术实战