#!/usr/bin/env python3
|
"""
|
memory-md-learning 学习记录脚本
|
记录陷阱、教训和解决方案到长期记忆和 MEMORY.md
|
"""
|
|
import os
|
import re
|
import sys
|
import argparse
|
from datetime import datetime
|
from pathlib import Path
|
|
# 配置
|
MEMORY_FILE = Path.home() / ".openclaw" / "workspace" / "MEMORY.md"
|
ARCHIVE_THRESHOLD_KB = 50
|
|
|
def read_memory_file():
|
"""读取 memory.md 文件内容"""
|
if not MEMORY_FILE.exists():
|
return ""
|
return MEMORY_FILE.read_text(encoding='utf-8')
|
|
|
def write_memory_file(content):
|
"""写入 memory.md 文件"""
|
MEMORY_FILE.parent.mkdir(parents=True, exist_ok=True)
|
MEMORY_FILE.write_text(content, encoding='utf-8')
|
|
|
def get_file_size():
|
"""获取文件大小(字节)"""
|
if not MEMORY_FILE.exists():
|
return 0
|
return MEMORY_FILE.stat().st_size
|
|
|
def extract_learning_entries(content):
|
"""从内容中提取学习事件条目"""
|
learning_events = []
|
|
lines = content.split('\n')
|
in_learning = False
|
|
for line in lines:
|
stripped = line.strip()
|
|
# 检测学习事件区块
|
if '学习事件' in stripped and stripped.startswith('#'):
|
in_learning = True
|
continue
|
elif stripped.startswith('#') and in_learning:
|
in_learning = False
|
|
# 收集学习事件
|
if in_learning and (stripped.startswith('-') or stripped.startswith('*')):
|
if len(stripped) > 10:
|
learning_events.append(line)
|
|
return learning_events
|
|
|
def store_technical_memory(trap, cause, fix, prevent):
|
"""
|
存储技术层记忆
|
格式:陷阱:[现象]。原因:[根本原因]。修复:[解决方案]。预防:[如何避免]
|
"""
|
content = f"陷阱:{trap}。原因:{cause}。修复:{fix}。预防:{prevent}"
|
|
# 使用 memory_store 工具存储
|
# 注意:实际调用需要通过外部机制,这里返回内容供调用方使用
|
return {
|
'content': content,
|
'category': 'fact',
|
'importance': 0.8,
|
'keywords': extract_keywords(content)
|
}
|
|
|
def store_principle_memory(principle, trigger, action, tag):
|
"""
|
存储原则层记忆
|
格式:决策原则([标签]):[行为准则]。触发条件:[何时]。行动:[做什么]
|
"""
|
content = f"决策原则({tag}):{principle}。触发条件:{trigger}。行动:{action}"
|
|
return {
|
'content': content,
|
'category': 'decision',
|
'importance': 0.85,
|
'keywords': extract_keywords(content)
|
}
|
|
|
def extract_keywords(text):
|
"""从文本中提取关键词"""
|
# 简单实现:提取中文字符和英文单词
|
import re
|
chinese_chars = re.findall(r'[\u4e00-\u9fff]{2,}', text)
|
english_words = re.findall(r'[a-zA-Z]{3,}', text)
|
return list(set(chinese_chars + english_words))[:10] # 最多10个关键词
|
|
|
def verify_memory_recall(keywords, timeout=5):
|
"""
|
验证记忆是否能被正确召回
|
返回: {'verified': bool, 'results': list}
|
"""
|
# 实际实现中需要调用 memory_recall
|
# 这里返回模拟结果
|
return {
|
'verified': True,
|
'results': [],
|
'message': '记忆召回验证需要调用 memory_recall 工具'
|
}
|
|
|
def update_memory_md_learning(summary, keywords):
|
"""
|
更新 MEMORY.md 的学习事件区块
|
"""
|
content = read_memory_file()
|
if not content:
|
# 如果文件不存在,先创建一个基础结构
|
content = generate_base_content()
|
|
# 检查是否已有学习事件区块
|
if '## 📚 学习事件' not in content:
|
# 在学习事件区块不存在时,在文件末尾添加
|
learning_section = f"""
|
|
---
|
|
## 📚 学习事件
|
|
> 记录从陷阱和教训中学习的经验。
|
> 所有学习记录永久保留,可使用 memory-md-archive 技能归档瘦身。
|
|
"""
|
# 在归档提示之前插入
|
if '---' in content and '*文件大小:' in content:
|
# 找到最后一个分隔符之前
|
last_sep = content.rfind('---\n\n*文件大小:')
|
if last_sep > 0:
|
content = content[:last_sep] + learning_section + content[last_sep:]
|
else:
|
content = content + learning_section
|
else:
|
content = content + learning_section
|
|
# 提取现有的学习事件
|
existing_events = extract_learning_entries(content)
|
|
# 添加新的学习事件
|
now = datetime.now()
|
date_str = now.strftime('%Y-%m-%d')
|
time_str = now.strftime('%H:%M')
|
new_event = f"- {date_str} {time_str} | {summary} | {keywords}"
|
|
# 检查是否已存在
|
if any(new_event.strip() in existing.strip() for existing in existing_events):
|
return {'added': False, 'message': '学习事件已存在'}
|
|
# 找到学习事件区块并插入新事件
|
lines = content.split('\n')
|
new_lines = []
|
in_learning = False
|
learning_inserted = False
|
|
for i, line in enumerate(lines):
|
stripped = line.strip()
|
|
# 检测学习事件区块开始
|
if '## 📚 学习事件' in stripped:
|
in_learning = True
|
new_lines.append(line)
|
continue
|
|
# 检测学习事件区块结束(遇到下一个标题或文件末尾信息)
|
if in_learning and stripped.startswith('#') and '学习事件' not in stripped:
|
if not learning_inserted:
|
new_lines.append(new_event)
|
learning_inserted = True
|
in_learning = False
|
new_lines.append(line)
|
continue
|
|
# 在学习事件区块内,跳过空行后插入新事件
|
if in_learning and not learning_inserted:
|
if stripped == '' and i > 0 and '记录从陷阱' not in lines[i-1] and '所有学习记录' not in lines[i-1]:
|
new_lines.append(new_event)
|
new_lines.append('')
|
learning_inserted = True
|
continue
|
|
new_lines.append(line)
|
|
# 如果在文件末尾,确保插入了事件
|
if in_learning and not learning_inserted:
|
new_lines.append(new_event)
|
|
# 更新文件大小信息
|
new_content = '\n'.join(new_lines)
|
|
# 更新文件大小统计
|
write_memory_file(new_content)
|
new_size = get_file_size()
|
size_kb = round(new_size / 1024, 2)
|
|
# 更新底部统计信息
|
new_content = update_footer_stats(new_content, size_kb)
|
write_memory_file(new_content)
|
|
return {
|
'added': True,
|
'event': new_event,
|
'size_kb': size_kb,
|
'archive_suggested': size_kb > ARCHIVE_THRESHOLD_KB
|
}
|
|
|
def update_footer_stats(content, size_kb):
|
"""更新文件底部的统计信息"""
|
# 统计学习事件数量
|
learning_events = extract_learning_entries(content)
|
learning_count = len(learning_events)
|
|
# 替换或添加归档提示
|
if '*归档提示:' in content:
|
# 替换现有提示
|
content = re.sub(
|
r'\*归档提示:.*\*',
|
f"*归档提示: 文件较大时请使用 memory-md-archive 技能归档(当前 {size_kb}KB,{learning_count} 条学习事件)*",
|
content
|
)
|
elif '---' in content and '*文件大小:' in content:
|
# 在文件大小行后添加
|
content = content.replace(
|
'*维护脚本:',
|
f"*归档提示: 文件较大时请使用 memory-md-archive 技能归档(当前 {size_kb}KB,{learning_count} 条学习事件)*\n*维护脚本:"
|
)
|
|
return content
|
|
|
def generate_base_content():
|
"""生成基础的 memory.md 内容"""
|
today = datetime.now().strftime('%Y-%m-%d')
|
return f"""# MEMORY.md - 热记忆 / 活跃记忆
|
|
> 本文件记录近期发生的重要事情,详细信息可通过记忆检索获取。
|
|
---
|
|
## 🔔 重要事件
|
|
> 记录具有全局长期性影响的重要决策和事件。
|
> 添加重要事件时会告知用户。
|
|
<!-- 重要事件在此添加 -->
|
|
---
|
|
## 📅 事件流水
|
|
> 按天分组,每天主要事情的概要。
|
> 所有记录永久保留,可使用 memory-md-archive 技能归档瘦身。
|
|
### {today}
|
|
- {today} --:-- | 暂无记录 | --
|
|
---
|
|
*文件大小: ~0.5KB | 事件数: 0*
|
*维护脚本: `memory-md-hot/scripts/daily_maintenance.py`*
|
*归档提示: 文件较大时请使用 memory-md-archive 技能归档*
|
"""
|
|
|
def record_learning(trap, cause, fix, prevent, principle, trigger, action, tag, summary, keywords):
|
"""
|
记录学习的主函数
|
|
Returns:
|
dict: 操作结果
|
"""
|
result = {
|
'success': False,
|
'technical_memory': None,
|
'principle_memory': None,
|
'learning_event': None,
|
'recall_verified': False,
|
'messages': []
|
}
|
|
# 1. 准备技术层记忆
|
technical = store_technical_memory(trap, cause, fix, prevent)
|
result['technical_memory'] = technical
|
result['messages'].append(f"技术层记忆已准备: {technical['content'][:50]}...")
|
|
# 2. 准备原则层记忆
|
principle_mem = store_principle_memory(principle, trigger, action, tag)
|
result['principle_memory'] = principle_mem
|
result['messages'].append(f"原则层记忆已准备: {principle_mem['content'][:50]}...")
|
|
# 3. 更新 MEMORY.md 学习事件
|
learning_result = update_memory_md_learning(summary, keywords)
|
result['learning_event'] = learning_result
|
if learning_result['added']:
|
result['messages'].append(f"学习事件已记录: {learning_result['event']}")
|
else:
|
result['messages'].append(f"学习事件: {learning_result['message']}")
|
|
# 4. 验证记忆召回(模拟)
|
all_keywords = technical['keywords'] + principle_mem['keywords']
|
verify_result = verify_memory_recall(all_keywords)
|
result['recall_verified'] = verify_result['verified']
|
result['messages'].append(f"记忆召回验证: {verify_result['message']}")
|
|
# 5. 归档提示
|
if learning_result.get('archive_suggested'):
|
result['messages'].append(f"提示: 文件大小 {learning_result['size_kb']}KB,建议使用 memory-md-archive 技能归档")
|
|
result['success'] = True
|
return result
|
|
|
def main():
|
"""主函数 - 供命令行调用"""
|
parser = argparse.ArgumentParser(description='记录学习成果')
|
parser.add_argument('--trap', required=True, help='陷阱现象描述')
|
parser.add_argument('--cause', required=True, help='根本原因分析')
|
parser.add_argument('--fix', required=True, help='解决方案')
|
parser.add_argument('--prevent', required=True, help='预防措施')
|
parser.add_argument('--principle', required=True, help='行为准则')
|
parser.add_argument('--trigger', required=True, help='触发条件')
|
parser.add_argument('--action', required=True, help='具体行动')
|
parser.add_argument('--tag', required=True, help='原则标签')
|
parser.add_argument('--summary', required=True, help='一句话概要')
|
parser.add_argument('--keywords', required=True, help='关键词,逗号分隔')
|
|
args = parser.parse_args()
|
|
result = record_learning(
|
trap=args.trap,
|
cause=args.cause,
|
fix=args.fix,
|
prevent=args.prevent,
|
principle=args.principle,
|
trigger=args.trigger,
|
action=args.action,
|
tag=args.tag,
|
summary=args.summary,
|
keywords=args.keywords
|
)
|
|
if result['success']:
|
print("✅ 学习记录完成")
|
print(f"\n技术层记忆:")
|
print(f" {result['technical_memory']['content']}")
|
print(f"\n原则层记忆:")
|
print(f" {result['principle_memory']['content']}")
|
print(f"\n学习事件:")
|
if result['learning_event']['added']:
|
print(f" {result['learning_event']['event']}")
|
else:
|
print(f" {result['learning_event']['message']}")
|
print(f"\n记忆召回验证: {'通过' if result['recall_verified'] else '待验证'}")
|
print("\n注意:技术层和原则层记忆需要调用 memory_store 工具实际存储到长期记忆")
|
for msg in result['messages']:
|
if '提示' in msg:
|
print(f"\n{msg}")
|
else:
|
print("❌ 学习记录失败")
|
for msg in result['messages']:
|
print(f" - {msg}")
|
sys.exit(1)
|
|
|
if __name__ == "__main__":
|
main()
|