From cd334c176df9a757c1a90f0a08da12a12fa6a79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E7=81=B5?= Date: Tue, 24 Mar 2026 10:00:56 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=A0=20=E7=82=BC=E9=87=91=E5=B7=A5?= =?UTF-8?q?=E5=9D=8A=E9=A6=96=E4=B8=AA=20Skill:=20Memory=20Management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 来源:龙虾茶馆 #32 共建成果(54条评论精华) 主创:@adminlove520 贡献者:@fatfingererr @andyyuzy-76 @wangray @fridayyi 包含: - 四层记忆架构(.abstract → MEMORY.md → 日记 → 长期知识) - CRUD 验证流程 - 温度模型自动遗忘 + P-Tag 生命周期 - Pre-compaction Flush 防丢数据 - 记忆健康检查脚本 - 自动归档冷数据脚本 Closes #32 --- skills/memory-management/SKILL.md | 181 ++++++++++++++++++ .../scripts/memory_archive.py | 144 ++++++++++++++ .../memory-management/scripts/memory_check.sh | 101 ++++++++++ 3 files changed, 426 insertions(+) create mode 100644 skills/memory-management/SKILL.md create mode 100644 skills/memory-management/scripts/memory_archive.py create mode 100755 skills/memory-management/scripts/memory_check.sh diff --git a/skills/memory-management/SKILL.md b/skills/memory-management/SKILL.md new file mode 100644 index 00000000..d25ebe47 --- /dev/null +++ b/skills/memory-management/SKILL.md @@ -0,0 +1,181 @@ +# 🧠 Memory Management — OpenClaw 记忆管理 Skill + +> 来源:龙虾茶馆 #32 共建 | 主创:@adminlove520 | 贡献者:@fatfingererr @andyyuzy-76 @wangray +> +> 集合了 54 条实战评论的精华,不是理论,全是踩过坑验证过的方案。 + +## 适用场景 + +当你的 Agent 出现以下症状时使用此 Skill: +- 重启后失忆,不记得上次聊了什么 +- 记忆文件越来越大,启动变慢 +- 重要决策被 compaction 吞掉 +- 不知道该记什么、忘什么 + +## 四层记忆架构 + +``` +Layer 0: .abstract (~1KB) → 核心身份,秒加载 +Layer 1: NOW.md / MEMORY.md → 当前状态索引 (<10KB) +Layer 2: memory/YYYY-MM-DD.md → 每日日志,追加式 +Layer 3: lessons/ decisions/ → 长期知识,结构化 +``` + +### Layer 0 — .abstract 秒加载 + +把核心身份 + 关键人 + 关键偏好压缩到 ~1KB: + +```markdown +# agent-main + +**身份**: [一句话] +**性格**: [关键词] +**主人**: [名字 + 联系方式] +**核心规则**: [3-5 条] +``` + +启动时先读这个,0.5 秒内就知道自己是谁。 + +### Layer 1 — 状态索引 + +**MEMORY.md** — 核心知识索引,<10KB,主会话启动必读。 + +规则: +- 只存索引和摘要,不存原始数据 +- 定期压缩,删除过时条目 +- 是"目录"不是"仓库" + +**NOW.md**(可选)— 当前活跃焦点,覆写式。 +- ⚠️ 每次覆写前备份:`cp NOW.md NOW.md.bak` +- 适合需要"交接文档"的场景 + +### Layer 2 — 每日日志 + +``` +memory/ +├── 2026-03-24.md +├── 2026-03-23.md +└── ... +``` + +规则: +- **只追加,不修改** — 禁止 Edit 修改已有日记 +- **YAML frontmatter** 存 L0 摘要,便于快速扫描 +- 今天 + 昨天的日记补全启动上下文 + +### Layer 3 — 长期知识 + +``` +memory/ +├── lessons/ # 可复用经验 +├── decisions/ # 战略决策(永不归档) +├── people/ # 人物画像(永不归档) +└── preferences/ # 用户偏好 +``` + +## CRUD 验证流程 + +写入知识文件前必须验证: + +``` +新信息到来 + │ + ├─ 读取目标文件当前内容 + │ + ├─ 比较新知识与已有内容 + │ ├─ 无关 → ADD (追加) + │ ├─ 重复 → NOOP (跳过) + │ ├─ 更新 → UPDATE (新版追加,旧版标记) + │ └─ 矛盾 → CONFLICT (两版保留,待裁决) + │ + └─ 更新 frontmatter 中的 last_verified +``` + +**关键:不要靠 Agent 自觉,用脚本自动跑。** + +## 温度模型 — 自动遗忘 + +``` +Temperature = w_age × age_score + w_ref × ref_score + w_pri × priority_score +``` + +| 温度 | 状态 | 处理 | +|------|------|------| +| 🔥 Hot (T > 0.7) | 活跃 | 保持索引 | +| 🌤 Warm (0.3 < T ≤ 0.7) | 降温 | 保留但降权 | +| 🧊 Cold (T ≤ 0.3) | 冷却 | 移至 .archive/ | + +**豁免规则**:decisions/ 和 people/ 永不归档。 + +### P-Tag 生命周期 + +| 标签 | 保留期 | 示例 | +|------|--------|------| +| P0 | 永久 | 架构决策、核心身份 | +| P1 | 30 天 | 项目进度、阶段性结论 | +| P2 | 7 天 | 临时任务、调试记录 | + +## Pre-compaction Flush + +Context window 快满时,主动保存关键信息: + +**触发条件**: +- 对话超过 50 轮 +- 收到 compaction 信号 +- 长任务即将结束 + +**动作**: +1. 提取当前上下文中的关键决策 +2. 写入 memory/YYYY-MM-DD.md +3. 更新 MEMORY.md 索引 + +> "被动等 Agent 记得存 vs 主动在压力前 flush —— 后者可靠性高一个量级。" — @fatfingererr + +## 启动加载策略 + +| Session 类型 | 加载内容 | Token 预算 | +|-------------|---------|-----------| +| 主会话 | .abstract → MEMORY.md → 今日日记 | ~3000 | +| 群聊/轻量 | .abstract → SOUL.md | ~500 | +| 子任务 | .abstract 只读 | ~200 | + +## 三机制联动 + +| 机制 | 触发 | 职责 | +|------|------|------| +| Pre-compaction Flush | context 压力 | 关键信息不丢 | +| P-Tag 自动归档 | 每日 cron | 冷数据自动清理 | +| Heartbeat 巡查 | 每 2-3 天 | 兜底 + 健康检查 | + +## 检索策略 + +1. **L0**: 扫 .abstract → 核心身份(~1KB) +2. **L1**: 读 MEMORY.md → 定位目标文件 +3. **L2**: 直接读取目标文件 +4. **L3**: memory_search 语义检索(兜底) + +> memory_search 是向量语义检索,不是 grep。搜"怎么部署"能命中"VPS 安装步骤"。 + +## 快速上手 + +1. 创建目录结构: +```bash +mkdir -p memory/{lessons,decisions,people,preferences} +``` + +2. 写 .abstract(~1KB 核心身份) + +3. 在 AGENTS.md 里加规则: +```markdown +## 记忆 +- 启动先读 .abstract → MEMORY.md +- 写入前 CRUD 验证 +- 日记只追加不修改 +- decisions/ 和 people/ 永不归档 +``` + +4. 设置 cron 自动归档冷数据 + +--- + +**龙虾茶馆 #32 共建成果** | 炼金工坊出品 🦞 diff --git a/skills/memory-management/scripts/memory_archive.py b/skills/memory-management/scripts/memory_archive.py new file mode 100644 index 00000000..6b5ca02b --- /dev/null +++ b/skills/memory-management/scripts/memory_archive.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +""" +记忆自动归档脚本 — 基于温度模型 + P-Tag +来源:龙虾茶馆 #32 共建 + +用法: + python3 memory_archive.py [memory_dir] + python3 memory_archive.py memory/ --dry-run # 只预览不操作 +""" + +import os +import sys +import shutil +import time +import re +from pathlib import Path +from datetime import datetime, timedelta + +MEMORY_DIR = Path(sys.argv[1] if len(sys.argv) > 1 and not sys.argv[1].startswith("-") else "memory") +DRY_RUN = "--dry-run" in sys.argv +ARCHIVE_DIR = MEMORY_DIR / ".archive" + +# 豁免目录 — 永不归档 +EXEMPT_DIRS = {"decisions", "people", ".archive"} + +# P-Tag 保留期(天) +PTAG_RETENTION = { + "P0": float("inf"), # 永久 + "P1": 30, + "P2": 7, +} + +# 温度权重 +W_AGE = 0.4 +W_REF = 0.3 +W_PRI = 0.3 + +def get_file_age_days(path): + mtime = os.path.getmtime(path) + return (time.time() - mtime) / 86400 + +def get_ptag(path): + """从 frontmatter 提取 P-Tag""" + try: + with open(path) as f: + content = f.read(500) + match = re.search(r'p-?tag:\s*(P\d)', content, re.IGNORECASE) + if match: + return match.group(1).upper() + except: + pass + return None + +def calculate_temperature(path): + """计算记忆温度""" + age_days = get_file_age_days(path) + + # Age score: 越新越热 + if age_days < 3: age_score = 1.0 + elif age_days < 7: age_score = 0.8 + elif age_days < 14: age_score = 0.5 + elif age_days < 30: age_score = 0.3 + else: age_score = 0.1 + + # Reference score: 文件大小作为参考频率的粗略估计 + size = os.path.getsize(path) + if size > 5000: ref_score = 0.8 + elif size > 1000: ref_score = 0.5 + else: ref_score = 0.3 + + # Priority score: P-Tag + ptag = get_ptag(path) + if ptag == "P0": pri_score = 1.0 + elif ptag == "P1": pri_score = 0.6 + elif ptag == "P2": pri_score = 0.3 + else: pri_score = 0.5 # 默认中等 + + return W_AGE * age_score + W_REF * ref_score + W_PRI * pri_score + +def should_archive(path): + """判断是否需要归档""" + # 检查豁免 + for exempt in EXEMPT_DIRS: + if exempt in path.parts: + return False, "exempt_dir" + + # P-Tag 检查 + ptag = get_ptag(path) + if ptag: + retention = PTAG_RETENTION.get(ptag, 30) + age = get_file_age_days(path) + if age > retention: + return True, f"{ptag} expired ({age:.0f}d > {retention}d)" + return False, f"{ptag} active" + + # 温度检查 + temp = calculate_temperature(path) + if temp <= 0.3: + return True, f"cold (T={temp:.2f})" + return False, f"warm (T={temp:.2f})" + +def archive_file(path): + """归档文件""" + rel = path.relative_to(MEMORY_DIR) + dest = ARCHIVE_DIR / rel + dest.parent.mkdir(parents=True, exist_ok=True) + if DRY_RUN: + print(f" [DRY RUN] 归档: {rel} → .archive/{rel}") + else: + shutil.move(str(path), str(dest)) + print(f" ✅ 归档: {rel} → .archive/{rel}") + +def main(): + if not MEMORY_DIR.exists(): + print(f"❌ 目录不存在: {MEMORY_DIR}") + sys.exit(1) + + print(f"🧠 记忆归档{'(预览模式)' if DRY_RUN else ''}") + print(f" 目录: {MEMORY_DIR}") + print("━━━━━━━━━━━━━━━━━━━━") + + archived = 0 + kept = 0 + + for path in sorted(MEMORY_DIR.rglob("*.md")): + if ".archive" in path.parts: + continue + + do_archive, reason = should_archive(path) + rel = path.relative_to(MEMORY_DIR) + + if do_archive: + archive_file(path) + archived += 1 + else: + kept += 1 + + print(f"\n━━━━━━━━━━━━━━━━━━━━") + print(f"📊 结果: 保留 {kept}, 归档 {archived}") + if DRY_RUN and archived > 0: + print(f"💡 去掉 --dry-run 执行实际归档") + +if __name__ == "__main__": + main() diff --git a/skills/memory-management/scripts/memory_check.sh b/skills/memory-management/scripts/memory_check.sh new file mode 100755 index 00000000..e495ea27 --- /dev/null +++ b/skills/memory-management/scripts/memory_check.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# 记忆系统健康检查脚本 +# 来源:龙虾茶馆 #32 共建 + +set -euo pipefail + +MEMORY_DIR="${1:-memory}" +MEMORY_INDEX="${2:-MEMORY.md}" + +echo "🧠 记忆系统健康检查" +echo "━━━━━━━━━━━━━━━━━━━━" + +ISSUES=0 + +# 1. 检查目录结构 +echo -e "\n📁 目录结构..." +for dir in lessons decisions people preferences; do + if [ -d "$MEMORY_DIR/$dir" ]; then + echo " ✅ $MEMORY_DIR/$dir" + else + echo " ❌ $MEMORY_DIR/$dir 不存在" + ISSUES=$((ISSUES + 1)) + fi +done + +# 2. 检查核心文件 +echo -e "\n📄 核心文件..." +for f in "$MEMORY_INDEX" SOUL.md AGENTS.md; do + if [ -f "$f" ]; then + SIZE=$(wc -c < "$f") + echo " ✅ $f (${SIZE} bytes)" + if [ "$f" = "$MEMORY_INDEX" ] && [ "$SIZE" -gt 10240 ]; then + echo " ⚠️ $f 超过 10KB,建议压缩" + ISSUES=$((ISSUES + 1)) + fi + else + echo " ❌ $f 不存在" + ISSUES=$((ISSUES + 1)) + fi +done + +# 3. 检查 .abstract +echo -e "\n🚀 .abstract 秒加载..." +ABSTRACT=$(find . -name ".abstract" -maxdepth 2 2>/dev/null | head -1) +if [ -n "$ABSTRACT" ]; then + SIZE=$(wc -c < "$ABSTRACT") + echo " ✅ $ABSTRACT (${SIZE} bytes)" + if [ "$SIZE" -gt 2048 ]; then + echo " ⚠️ .abstract 超过 2KB,建议精简" + fi +else + echo " ℹ️ 无 .abstract 文件(可选,建议创建)" +fi + +# 4. 检查日记 +echo -e "\n📅 日记文件..." +TODAY=$(date +%Y-%m-%d) +YESTERDAY=$(date -d "yesterday" +%Y-%m-%d 2>/dev/null || date -v-1d +%Y-%m-%d 2>/dev/null || echo "unknown") +DIARY_COUNT=$(find "$MEMORY_DIR" -name "????-??-??.md" 2>/dev/null | wc -l) +echo " 📊 共 $DIARY_COUNT 篇日记" +if [ -f "$MEMORY_DIR/$TODAY.md" ]; then + echo " ✅ 今日日记已创建" +else + echo " ℹ️ 今日日记未创建" +fi + +# 5. 温度检查 — 找冷数据 +echo -e "\n🌡️ 冷数据检查..." +COLD_COUNT=0 +while IFS= read -r f; do + MTIME=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f" 2>/dev/null || echo 0) + NOW_TS=$(date +%s) + AGE_DAYS=$(( (NOW_TS - MTIME) / 86400 )) + if [ "$AGE_DAYS" -gt 30 ]; then + COLD_COUNT=$((COLD_COUNT + 1)) + fi +done < <(find "$MEMORY_DIR" -name "*.md" -not -path "*/.archive/*" -not -path "*/decisions/*" -not -path "*/people/*" 2>/dev/null) + +if [ "$COLD_COUNT" -gt 0 ]; then + echo " 🧊 $COLD_COUNT 个文件超过 30 天未更新(排除 decisions/ 和 people/)" + echo " 💡 考虑归档到 .archive/" +else + echo " ✅ 无冷数据" +fi + +# 6. 总 token 估算 +echo -e "\n📊 Token 估算..." +TOTAL_BYTES=$(find "$MEMORY_DIR" -name "*.md" -not -path "*/.archive/*" 2>/dev/null -exec cat {} + | wc -c) +EST_TOKENS=$((TOTAL_BYTES / 4)) +echo " 活跃记忆总量: ~${EST_TOKENS} tokens (${TOTAL_BYTES} bytes)" +if [ "$EST_TOKENS" -gt 50000 ]; then + echo " ⚠️ 记忆量较大,建议清理冷数据" +fi + +# 汇总 +echo -e "\n━━━━━━━━━━━━━━━━━━━━" +if [ "$ISSUES" -eq 0 ]; then + echo "✅ 记忆系统健康" +else + echo "⚠️ 发现 $ISSUES 个问题,建议修复" +fi