17 KiB
17 KiB
version, last-updated, status
| version | last-updated | status |
|---|---|---|
| 1.0 | 2026-02-24 | design |
Agent 自我进化架构 - 存储实现
概述
本文档详细描述 Qiming Agent 记忆存储的实现方案,基于 Markdown 文件格式,提供人类可读、Git 友好、简单可靠的存储机制。
设计原则
参考 OpenClaw 的 Markdown 存储方案,核心优势:
- 人类可读 - 直接编辑,无需工具
- Git 友好 - 版本控制天然支持
- 渐进加载 - Frontmatter → Body → References
- 简单可靠 - 无需数据库,文件系统即存储
目录结构
~/.qimingclaw/
├── soul/ # Agent 自我认知
│ ├── soul.md # 主灵魂文件
│ ├── principles.md # 学习到的原则
│ └── anti-patterns.md # 避免的陷阱
│
├── memory/ # 记忆存储
│ ├── short-term.md # 当前会话记忆(每次会话覆盖)
│ ├── successes/ # 成功经验
│ │ ├── 2024-02-24-parse-json.md
│ │ ├── 2024-02-24-install-tool.md
│ │ └── ...
│ ├── failures/ # 失败教训
│ │ ├── 2024-02-23-pip-failed.md
│ │ └── ...
│ ├── insights/ # 洞察
│ │ ├── pattern-uv-better.md
│ │ └── ...
│ └── index.json # 检索索引
│
├── skills/ # 技能库
│ ├── core/ # 核心技能(内置)
│ │ ├── file-read/SKILL.md
│ │ ├── file-write/SKILL.md
│ │ └── ...
│ └── learned/ # 学到的技能
│ ├── parse-json/SKILL.md
│ ├── install-uv/SKILL.md
│ └── ...
│
└── evo-map/ # 进化图谱
├── decisions/ # 决策树
│ ├── install-python.md
│ ├── parse-json.md
│ └── ...
└── patterns/ # 模式库
├── success-patterns.md
└── failure-patterns.md
文件格式设计
1. Soul.md(灵魂文件)
---
name: soul
version: 3
last-updated: 2024-02-24
---
# Soul.md - Agent 自我认知
## 身份
我是 Qiming Agent,一个可以自我进化的 AI 助手。
## 核心原则
1. 用户目标优先
2. 优先使用验证过的方法
3. 失败时尝试备选方案
4. 记录所有尝试以供学习
## 我的能力
- [x] 文件操作
- [x] 代码执行
- [x] 工具安装
- [x] 自我诊断
## 我的限制
- [ ] 不能写入系统目录
- [ ] 网络下载需要用户确认
- [ ] 资源使用有限制
## 学习到的经验
### 成功模式
- `2024-02-24`: 使用 uv pip 安装 Python 包,98% 成功率
- 参考: `memory/successes/2024-02-24-install-uv.md`
- `2024-02-24`: 使用 node 内置 JSON 解析,避免 jq 依赖
### 失败教训
- `2024-02-23`: 尝试直接写入 /usr/lib 失败 → 使用工作区
### 技能清单
- `parse-json` - JSON 文件解析(已学会)
- `install-uv` - Python 包安装(已学会)
- `grep-log` - 日志文件分析(已学会)
## 统计数据
- 总任务数: 1,234
- 成功率: 87.5%
- 最常用方法: uv pip install (45次)
- 最省时方法: node JSON parse (平均 0.5s)
2. 成功记忆文件
memory/successes/2024-02-24-parse-json.md:
---
type: success
task: parse-json
confidence: 0.98
created: 2024-02-24T10:30:00Z
---
# JSON 文件解析成功案例
## 任务
解析用户指定的 JSON 文件并提取特定字段
## 使用的方案
使用 Node.js 内置 `JSON.parse()` 方法
## 执行步骤
\`\`\`bash
node -e "const fs = require('fs'); const data = JSON.parse(fs.readFileSync('${file}', 'utf8')); console.log(JSON.stringify(data, null, 2));"
\`\`\`
## 结果
- 成功解析: `data.json`
- 耗时: 0.5s
- 输出格式正确
## 为什么成功
- Node.js 是 Electron 内置,无需安装
- 不依赖外部工具如 jq
- 处理大文件也很快
## 相关技能
- `skills/learned/parse-json/SKILL.md`
3. 失败记忆文件
memory/failures/2024-02-23-pip-failed.md:
---
type: failure
task: install-python-package
created: 2024-02-23T15:20:00Z
---
# pip install 失败案例
## 任务
安装 Python 包 `requests`
## 尝试的方案
\`\`\`bash
pip install requests
\`\`\`
## 失败原因
- `pip: command not found` - 系统 Python 未配置或不存在
- 即使 pip 存在,可能污染系统 Python 环境
## 正确的方案
参考 `evo-map/decisions/install-python.md`,应该使用:
\`\`\`bash
uv pip install requests
\`\`\`
## 学到的教训
- 优先使用 uv(应用内打包)
- 避免使用系统 pip
- 参考 EvoMap 中的决策树
## 相关记录
- 修复后的成功: `memory/successes/2024-02-24-install-uv.md`
4. 技能文件
skills/learned/parse-json/SKILL.md:
---
name: parse-json
description: 解析 JSON 文件,提取字段,格式化输出。当需要处理 JSON 文件时使用。
confidence: 0.98
created: 2024-02-24
version: 2
---
# JSON 文件解析技能
## 触发条件
当用户需要:
- 读取 JSON 文件
- 提取 JSON 中的字段
- 格式化 JSON 输出
- 验证 JSON 语法
## 推荐方法
### 方法 1: Node.js(推荐)
\`\`\`bash
node -e "const fs = require('fs'); const data = JSON.parse(fs.readFileSync('${file}', 'utf8')); console.log(JSON.stringify(data.${field}, null, 2));"
\`\`\`
- 成功率: 98%
- 优点: Node.js 内置,无需依赖
- 缺点: 简单提取很方便
### 方法 2: jq(备选)
\`\`\`bash
jq '.field' < file.json
\`\`\`
- 成功率: 60%
- 优点: 功能强大
- 缺点: 需要安装 jq
## 常用模式
### 提取单个字段
\`\`\`bash
node -e "console.log(JSON.parse(require('fs').readFileSync('${file}')).${field})"
\`\`\`
### 格式化输出
\`\`\`bash
node -e "console.log(JSON.stringify(JSON.parse(require('fs').readFileSync('${file}')), null, 2))"
\`\`\`
## 相关记忆
- 成功案例: `memory/successes/2024-02-24-parse-json.md`
5. EvoMap 决策文件
evo-map/decisions/install-python.md:
---
task: install-python-package
last-updated: 2024-02-24
---
# Python 包安装决策
## 决策树
\`\`\`
需要安装 Python 包
│
├─ 方案 A: uv pip install
│ 置信度: 95%
│ 成功率: 98%
│ 预期时间: 5s
│ 证据: 45 次成功 / 1 次失败
│ 推荐: ✅ 首选
│
├─ 方案 B: pip install
│ 置信度: 70%
│ 成功率: 80%
│ 预期时间: 10s
│ 证据: 30 次成功 / 7 次失败
│ 下一步: 若失败 → 切换到 uv
│
└─ 方案 C: 系统包管理器
置信度: 50%
成功率: 60%
预期时间: 30s
证据: 10 次成功 / 8 次失败
备注: 最后手段,可能需要 sudo
\`\`\`
## 选择逻辑
1. 优先使用 `uv pip install`
2. 如果 uv 不可用,尝试 `pip install`
3. 如果都失败,提示用户手动安装
## 相关记录
- `memory/successes/2024-02-24-install-uv.md`
- `memory/failures/2024-02-23-pip-failed.md`
索引机制
简单文件索引
memory/index.json:
{
"successes": [
{
"file": "successes/2024-02-24-parse-json.md",
"task": "parse-json",
"confidence": 0.98,
"timestamp": "2024-02-24T10:30:00Z",
"keywords": ["json", "parse", "node"]
},
{
"file": "successes/2024-02-24-install-uv.md",
"task": "install-python-package",
"confidence": 0.95,
"timestamp": "2024-02-24T09:15:00Z",
"keywords": ["python", "uv", "install"]
}
],
"failures": [
{
"file": "failures/2024-02-23-pip-failed.md",
"task": "install-python-package",
"timestamp": "2024-02-23T15:20:00Z",
"keywords": ["python", "pip", "failed"]
}
]
}
索引更新策略
// 每次写入记忆后更新索引
async function updateIndex(type: 'successes' | 'failures', filepath: string) {
const index = await loadIndex();
const frontmatter = await extractFrontmatter(filepath);
index[type].push({
file: filepath,
task: frontmatter.task,
confidence: frontmatter.confidence || 0,
timestamp: frontmatter.created,
keywords: extractKeywords(frontmatter),
});
await fs.writeFile('memory/index.json', JSON.stringify(index, null, 2));
}
// 快速检索:只读索引,不需要遍历文件
async function searchByTask(task: string): Promise<string[]> {
const index = await loadIndex();
return index.successes
.filter(m => m.task === task || m.keywords.includes(task))
.sort((a, b) => b.confidence - a.confidence)
.map(m => m.file);
}
读写接口
写入(编码)
interface MemoryWriter {
// 写入成功记忆
writeSuccess(memory: SuccessMemory): Promise<void>;
// 写入失败记忆
writeFailure(memory: FailureMemory): Promise<void>;
// 更新 Soul.md
updateSoul(update: SoulUpdate): Promise<void>;
}
class MarkdownMemoryWriter implements MemoryWriter {
async writeSuccess(memory: SuccessMemory): Promise<void> {
const filename = `memory/successes/${memory.date}-${memory.slug}.md`;
const content = this.formatSuccessMarkdown(memory);
await fs.writeFile(filename, content, 'utf-8');
// 更新索引
await this.updateIndex('successes', filename);
}
private formatSuccessMarkdown(memory: SuccessMemory): string {
return `---
type: success
task: ${memory.task}
confidence: ${memory.confidence}
created: ${memory.timestamp}
---
# ${memory.title}
## 任务
${memory.description}
## 使用的方案
\`\`\`bash
${memory.command}
\`\`\`
## 结果
${memory.result}
## 为什么成功
${memory.reasoning}
## 相关技能
- ${memory.relatedSkill || '无'}
`;
}
}
检索(读取)
interface MemoryReader {
// 语义检索(基于 frontmatter)
search(query: string): Promise<MemoryFile[]>;
// 读取特定记忆
read(path: string): Promise<MemoryContent>;
// 获取相关决策
getDecision(task: string): Promise<DecisionNode>;
}
class MarkdownMemoryReader implements MemoryReader {
async search(query: string): Promise<MemoryFile[]> {
// 1. 遍历 memory 目录
const files = await this.getAllMemoryFiles();
// 2. 读取 frontmatter
const results: MemoryFile[] = [];
for (const file of files) {
const frontmatter = await this.extractFrontmatter(file);
// 3. 匹配查询
if (this.matches(query, frontmatter)) {
results.push({ file, frontmatter });
}
}
// 4. 按置信度/时间排序
return results.sort((a, b) => b.confidence - a.confidence);
}
private matches(query: string, frontmatter: Frontmatter): boolean {
const { task, type, keywords } = frontmatter;
return (
task?.includes(query) ||
type === query ||
keywords?.some((k: string) => k.includes(query))
);
}
async getDecision(task: string): Promise<DecisionNode> {
const decisionFile = `evo-map/decisions/${this.slugify(task)}.md`;
if (await fs.exists(decisionFile)) {
return this.parseDecisionFile(await fs.readFile(decisionFile, 'utf-8'));
}
// 回退到通用决策
return this.getGenericDecision(task);
}
}
记忆清理策略
interface MemoryCleanupPolicy {
maxShortTermEntries: number; // 最多 50 条
maxLongTermEntries: number; // 最多 1000 条
maxAge: number; // 90 天
lowConfidenceThreshold: number; // 置信度 < 0.3
}
async function cleanupMemory(policy: MemoryCleanupPolicy): Promise<CleanupResult> {
const index = await loadIndex();
const now = Date.now();
const toDelete: string[] = [];
// 1. 清理过期的低置信度记忆
for (const memory of index.successes) {
const age = now - new Date(memory.timestamp).getTime();
const ageDays = age / (1000 * 60 * 60 * 24);
if (ageDays > policy.maxAge || memory.confidence < policy.lowConfidenceThreshold) {
toDelete.push(memory.file);
}
}
// 2. 限制总数量
const sorted = [...index.successes].sort((a, b) =>
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
);
if (sorted.length > policy.maxLongTermEntries) {
const excess = sorted.slice(policy.maxLongTermEntries);
toDelete.push(...excess.map(m => m.file));
}
// 3. 执行删除
for (const file of toDelete) {
await fs.unlink(`memory/${file}`);
}
// 4. 更新索引
await rebuildIndex();
return { deleted: toDelete.length, remaining: index.successes.length - toDelete.length };
}
向量检索(可选升级)
当记忆数量超过 500 条时,可以引入语义向量检索以提升召回精度:
升级时机
-
当前阶段:使用关键词匹配(已实现)
- 基于 frontmatter 的
task、type、keywords字段 - 适用于记忆数量 < 500 条
- 基于 frontmatter 的
-
升级阶段:记忆数量 > 500 条时
- 引入嵌入向量(embedding)进行语义检索
- 保持 Markdown 格式不变,向量作为补充索引
向量检索方案
// 记忆编码时添加嵌入向量
interface EncodedMemory {
id: string;
type: 'success' | 'failure' | 'insight';
embedding?: number[]; // 语义向量(可选)
context: {
task: string;
environment: string;
constraints: string[];
};
// ... 其他字段
}
// 更新后的索引
interface MemoryIndex {
successes: MemoryIndexEntry[];
failures: MemoryIndexEntry[];
}
interface MemoryIndexEntry {
file: string;
task: string;
confidence: number;
timestamp: string;
keywords: string[];
embedding?: number[]; // 新增:嵌入向量
}
检索接口升级
class MarkdownMemoryReader implements MemoryReader {
async search(query: string): Promise<MemoryFile[]> {
const files = await this.getAllMemoryFiles();
// 初期:关键词匹配
const results: MemoryFile[] = [];
for (const file of files) {
const frontmatter = await this.extractFrontmatter(file);
if (this.matchesKeyword(query, frontmatter)) {
results.push({ file, frontmatter });
}
}
// 后期:记忆 > 500 条时使用向量检索
if (files.length > 500) {
const queryEmbedding = await this.generateEmbedding(query);
return this.searchBySimilarity(queryEmbedding, files);
}
return results.sort((a, b) => b.confidence - a.confidence);
}
// 语义相似度检索(待实现)
private async searchBySimilarity(
queryEmbedding: number[],
files: string[]
): Promise<MemoryFile[]> {
const results: Array<{ file: string; score: number }> = [];
for (const file of files) {
const entry = await this.getIndexEntry(file);
if (entry.embedding) {
const score = this.cosineSimilarity(queryEmbedding, entry.embedding);
results.push({ file, score });
}
}
return results
.filter(r => r.score > 0.7) // 相似度阈值
.sort((a, b) => b.score - a.score)
.slice(0, 10); // 返回 top-10
}
private cosineSimilarity(a: number[], b: number[]): number {
const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
return dotProduct / (magnitudeA * magnitudeB);
}
}
向量存储
向量可以存储在以下位置:
~/.qimingclaw/
├── memory/
│ ├── index.json # 原有索引
│ └── embeddings.json # 新增:向量索引(可选)
│ # 或者使用 SQLite 存储
├── memory.db # 新增:SQLite 向量数据库(可选)
实现优先级
| 阶段 | 记忆数量 | 检索方式 | 优先级 |
|---|---|---|---|
| P0 | < 500 | 关键词匹配 | 已实现 |
| P1 | 500-2000 | 关键词 + 向量混合 | 可选 |
| P2 | > 2000 | 纯向量检索 | 待定 |
优势对比
| 特性 | Markdown 方案 | 数据库方案 |
|---|---|---|
| 可读性 | ✅ 人类可读 | ❌ 需要工具 |
| 版本控制 | ✅ Git 友好 | ⚠️ 需要 migration |
| 可移植性 | ✅ 纯文件 | ❌ 依赖软件 |
| 搜索 | ⚠️ 需索引 | ✅ SQL 查询 |
| 复杂度 | ✅ 简单 | ❌ 复杂 |
| 调试 | ✅ 直接查看 | ❌ 需要 query |