685 lines
17 KiB
Markdown
685 lines
17 KiB
Markdown
---
|
||
version: 1.0
|
||
last-updated: 2026-02-24
|
||
status: design
|
||
---
|
||
|
||
# Agent 自我进化架构 - 存储实现
|
||
|
||
## 概述
|
||
|
||
本文档详细描述 Qiming Agent 记忆存储的实现方案,基于 Markdown 文件格式,提供人类可读、Git 友好、简单可靠的存储机制。
|
||
|
||
---
|
||
|
||
## 设计原则
|
||
|
||
参考 OpenClaw 的 Markdown 存储方案,核心优势:
|
||
|
||
1. **人类可读** - 直接编辑,无需工具
|
||
2. **Git 友好** - 版本控制天然支持
|
||
3. **渐进加载** - Frontmatter → Body → References
|
||
4. **简单可靠** - 无需数据库,文件系统即存储
|
||
|
||
---
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
~/.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(灵魂文件)
|
||
|
||
```markdown
|
||
---
|
||
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`:
|
||
|
||
```markdown
|
||
---
|
||
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`:
|
||
|
||
```markdown
|
||
---
|
||
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`:
|
||
|
||
```markdown
|
||
---
|
||
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`:
|
||
|
||
```markdown
|
||
---
|
||
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`:
|
||
|
||
```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"]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 索引更新策略
|
||
|
||
```typescript
|
||
// 每次写入记忆后更新索引
|
||
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);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 读写接口
|
||
|
||
### 写入(编码)
|
||
|
||
```typescript
|
||
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 || '无'}
|
||
`;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 检索(读取)
|
||
|
||
```typescript
|
||
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);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 记忆清理策略
|
||
|
||
```typescript
|
||
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 条
|
||
|
||
- **升级阶段**:记忆数量 > 500 条时
|
||
- 引入嵌入向量(embedding)进行语义检索
|
||
- 保持 Markdown 格式不变,向量作为补充索引
|
||
|
||
### 向量检索方案
|
||
|
||
```typescript
|
||
// 记忆编码时添加嵌入向量
|
||
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[]; // 新增:嵌入向量
|
||
}
|
||
```
|
||
|
||
### 检索接口升级
|
||
|
||
```typescript
|
||
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 |
|
||
|
||
---
|
||
|
||
## 相关文档
|
||
|
||
- [总览](./OVERVIEW.md) - 产品定位、核心原则
|
||
- [核心组件](./COMPONENTS.md) - Memory、Skill Creator、EvoMap、Soul.md
|
||
- [循环流程](./LOOP.md) - 完整循环流程、接口定义
|
||
- [隔离策略](./ISOLATION.md) - 三区模型、环境变量
|