我给 CLI 加了个「长期记忆」,1200 行代码让 AI 记住你的所有偏好

------零依赖、纯 Markdown、无需数据库,开源 AI 编程助手 IfAI v0.4.7 记忆系统架构深度解析


你有没有遇到过这种场景:每次打开 AI 编程助手,都要重复告诉它「我用 TypeScript」「数据库用 PostgreSQL」「测试用 vitest」。说完一百遍之后你开始怀疑------这东西到底有没有在听?

我也受够了。所以我在开源项目 IfAI 里做了一个决定:让 CLI 自己记住一切

不是简单的配置文件,不是硬编码的偏好列表,而是一套完整的跨会话持久化记忆系统------AI 在对话中主动识别、保存、提取你的偏好,下次打开自动加载,零配置、零干预。

今天我把这套系统的核心架构开源出来,聊聊技术选型、踩过的坑,以及为什么「纯 Markdown 文件」可能是本地 AI 记忆的最佳载体。


一、为什么现有方案都不够好?

主流 AI 助手的「记忆」方案大致分三种:

方案 代表 问题
系统提示词硬编码 Cursor Rules 每次改都要手动编辑,无法动态更新
向量数据库 + RAG ChatGPT Memory 重依赖、高延迟、本地部署复杂
对话历史回溯 绝大多数工具 token 爆炸、成本失控、噪声太多

我的需求很明确:

  1. 零新依赖------不引入 SQLite、不引入向量数据库
  2. 人类可读------用户能用任何文本编辑器打开、修改
  3. 自动去重------AI 重复提取同一偏好时,更新日期而非新增行
  4. 性能极致------记忆注入延迟 < 1ms,不影响会话启动速度
  5. 编译时类型安全------路径拼写错误在编译期就报错,不是运行时爆炸

最终选型:纯 Markdown 文件 + Rust 编译时宏 + 两层记忆架构


二、空间隐喻:用「建筑」组织记忆

这是我自认为最得意的设计。

传统的 key-value 记忆太扁平,无法表达层次关系。我借鉴了 MemGPT 的分层思想,但做了极大简化------不用数据库,用 Markdown 标题层级

shell 复制代码
~/.ifai/memories.md

## Preferences                    ← Hall(大厅)
### programming-languages         ← Room(房间)
- [2026-05-10] 用户喜欢使用 Rust 编程
- [2026-05-10] 项目使用 TypeScript 和 Rust

## Decisions                      ← 另一个 Hall
### architecture
- [2026-05-10] 采用事件驱动架构

更进一步,我引入了 Wing(建筑) 的概念,支持 3 层路径:

bash 复制代码
Project/Preferences/programming-languages
  ↑        ↑              ↑
Wing     Hall           Room

多项目、多用户的完整空间结构:

scss 复制代码
project/ifai (Wing)           user/alice (Wing)
  ├── Preferences (Hall)        ├── Preferences (Hall)
  │   ├── programming-languages  │   ├── communication-style (Room)
  │   └── ui-themes            │   └── communication-channels
  ├── Project Knowledge (Hall)  └── ...
  ├── Decisions (Hall)
  └── Workflow Patterns (Hall)

project/website-a (Wing)      user/bob (Wing)
  ├── Preferences (Hall)        ├── Preferences (Hall)
  └── ...                       └── ...

同一个分类(如 Preferences)可以区分为「项目级别」和「用户级别」,互不干扰。

伪代码------路径解析

rust 复制代码
// 输入: "Project/Preferences/programming-languages"
// 输出: section_title = "## Project\n### Preferences\n#### programming-languages"

fn parse_path(path: &str) -> MemoryPath {
    let parts: Vec<&str> = path.split('/').collect();
    match parts.len() {
        2 => MemoryPath { wing: None, hall: parts[0], room: Some(parts[1]) },
        3 => MemoryPath { wing: Some(parts[0]), hall: parts[1], room: Some(parts[2]) },
        _ => Err("路径必须是 2 层或 3 层")
    }
}

fn section_title(path: &MemoryPath) -> String {
    match &path.wing {
        Some(wing) => format!("## {}\n### {}\n#### {}", wing, path.hall, path.room),
        None      => format!("## {}\n### {}", path.hall, path.room),
    }
}

关键点:macro_rules! 在编译时生成 enum 和 FromStr 实现 ,拼写错误在 cargo build 时就暴露:

rust 复制代码
declare_halls! {
    Preferences : "Preferences";
    ProjectKnowledge : "Project Knowledge";
    Decisions : "Decisions";
    WorkflowPatterns : "Workflow Patterns";
}
// 编译时自动生成: enum MemoryHall { Preferences, ProjectKnowledge, ... }
// 编译时自动生成: impl FromStr for MemoryHall { ... }
// 传入 "Prefernces"(拼错)→ 编译失败 ✓

三、两层记忆架构

整个系统的数据流如下:

javascript 复制代码
┌─────────────────────────────────────────────────────────────┐
│                     热记忆 (Core Memory)                    │
│  ~/.ifai/memories.md                                         │
│  - 始终注入 system prompt                                     │
│  - ≤ 2000 tokens (约 4KB)                                    │
│  - AI 可通过 MemorySave 工具实时修改                           │
│  - 4 个 section: Preferences / ProjectKnowledge /            │
│    Decisions / WorkflowPatterns                              │
└─────────────────────────────────────────────────────────────┘
                              ↓ 注入
┌─────────────────────────────────────────────────────────────┐
│  System Prompt + [USER_MEMORY]                               │
│  ┌───────────────────────────────────────────────┐          │
│  │ [USER_MEMORY]                                  │          │
│  │ <memories.md 完整内容>                          │          │
│  │ [/USER_MEMORY]                                 │          │
│  └───────────────────────────────────────────────┘          │
└─────────────────────────────────────────────────────────────┘
                              ↓ 归档
┌─────────────────────────────────────────────────────────────┐
│                  冷记忆 (Archival Storage)                  │
│  ~/.ifai/sessions/YYYY-MM-DD-{id}.md                        │
│  - 按会话归档,人类可浏览                                    │
│  - 不自动注入,避免 token 浪费                                │
│  - 未来可加语义搜索,作为 RAG 数据源                          │
└─────────────────────────────────────────────────────────────┘

TUI 和 GUI 共享同一套 memory 模块,通过统一的 API 读写:

scss 复制代码
┌─────────────────┐      ┌─────────────────┐
│   TUI 入口       │      │   GUI 入口       │
│  (CLI / ratatui) │      │  (Tauri / React) │
└────────┬────────┘      └────────┬────────┘
         │                        │
         ├────────────┬───────────┘
         ↓            ↓
    ┌─────────────────────────┐
    │     memory 模块 (lib)    │
    │  ├─ io.rs (读写)        │
    │  ├─ tool.rs (工具)      │
    │  └─ extractor.rs (提取)  │
    └─────────────────────────┘
         ↑            ↑
         │            │
    ┌────┴────┐  ┌───┴────┐
    │注入到    │  │注册到  │
    │system   │  │Tool    │
    │prompt   │  │Registry│
    └─────────┘  └────────┘

每次会话启动时,memories.md 被整体注入到 system prompt 末尾:

rust 复制代码
fn inject_memories(system_prompt: &str) -> String {
    let memories = load("~/.ifai/memories.md");
    format!("{}\n\n[USER_MEMORY]\n{}\n[/USER_MEMORY]", system_prompt, memories)
}

性能实测:19 KB 文件注入耗时 18μs(微秒),远低于 100ms 目标。原因是纯字符串操作,没有任何序列化/反序列化开销。

冷记忆(Cold Memory)

存储位置:~/.ifai/sessions/YYYY-MM-DD-{id}.md

每次会话结束时,自动生成人类可读的会话摘要:

markdown 复制代码
# Session: 2026-05-10-abc123

## Summary
用户请求实现记忆系统外部化提示词功能...

## Token Usage
- Input: 12,450
- Output: 3,200

## Memories Extracted
- Preferences: 用户喜欢使用 Rust
- Decisions: 采用纯 Markdown 作为记忆载体

冷记忆不自动注入 system prompt(避免 token 浪费),但未来可以支持语义搜索,作为 RAG 的数据源。


四、AI 主动保存:MemorySave 工具

这不是被动记忆------AI 在对话中主动识别你的偏好并调用工具保存:

bash 复制代码
你:记住,我喜欢用 Rust 写后端
AI:✓ Saved to Preferences/programming-languages: 用户喜欢使用 Rust 编程

工具定义极简:

json 复制代码
{
  "name": "MemorySave",
  "parameters": {
    "path": "Preferences/programming-languages",
    "content": "用户喜欢使用 Rust 编程"
  }
}

核心写入逻辑------带去重的 section 追加

rust 复制代码
fn append_to_section(memories: &str, section: &str, entry: &str) -> String {
    let content = extract_content_without_date(entry);  // "用户喜欢使用 Rust 编程"

    // 在 section 范围内查找重复
    if section_contains(section, content) {
        replace_entry_with_new_date(section, entry)  // 更新日期,不新增
    } else {
        append_new_entry(section, entry)             // 追加新行
    }
}

去重规则:忽略日期,只比较内容。这样 AI 每次确认同一偏好时,只会更新时间戳,文件保持干净。

权限设计 :MemorySave 被标记为 riskLevel: low, requiresApproval: false,完全自动执行,不弹出确认框。理由很简单------写一个本地 Markdown 文件,风险极低,打断对话流程才是真正的成本。


五、会话后批量提取

对话结束时,自动触发记忆提取流程:

ini 复制代码
对话结束 → 判断是否值得提取(≥3 轮对话 or 有工具调用)
         → 生成对话摘要
         → 调用 LLM(temperature=0)提取新记忆
         → 合并到 memories.md(带去重)
         → 保存会话摘要到 sessions/

提取 prompt 模板外部化~/.ifai/prompts/memory/extract.md,用户可以完全自定义提取规则:

markdown 复制代码
你是一个专业的记忆提取助手。

## 提取规则
1. 用户偏好:编程语言、工具、框架
2. 重要决策:架构决策、技术选型
3. 领域知识:项目特定的业务逻辑

## 输出格式
**[Preferences]** 使用 TypeScript 而非 JavaScript
**[Decisions]** 采用 PostgreSQL 作为主数据库

优先级:外部文件 > 内置默认 > 不提取。


六、工程细节:为什么是 Rust + Markdown?

为什么不用 JSON/YAML/TOML?

  • Markdown 人类可读性最好(任何编辑器都能打开)
  • ## 层级天然表达树形结构
  • 便于 AI 直接生成和修改(LLM 对 Markdown 的训练数据最多)
  • 便于 git diff 和版本管理

为什么不用 SQLite?

  • 零依赖原则:不增加编译时间和二进制大小
  • 人类可读:memories.md vs sqlite3 memories.db "SELECT * FROM memories"
  • 便于备份:直接复制文件即可
  • 便于调试:出问题时 cat 一下就知道发生了什么

为什么用 macro_rules! 而不是 proc-macro?

  • macro_rules! 是 Rust 内置,零编译开销
  • proc-macro 需要单独的 crate,增加编译时间
  • 适度元编程,不过度设计

性能数据

指标 数值
记忆注入延迟(19 KB) 18μs
记忆注入延迟(冷启动) 45μs
去重追加操作 < 1μs
核心代码量 ~1230 行
测试代码量 ~390 行
新增依赖 0

七、实际效果

部署后,最直观的变化是:再也不用重复说同样的话了

bash 复制代码
# 第一次会话
你:我喜欢用 Rust,项目用 Tauri
AI:✓ Saved to Preferences/programming-languages: 用户喜欢使用 Rust
    ✓ Saved to ProjectKnowledge/tech-stack: 项目使用 Tauri 框架

# 第二次会话(新开的)
你:帮我写个 API 接口
AI:好的,我用 Rust + Tauri 来实现...  (自动使用记忆中的偏好)

记忆文件本身就是最好的文档------打开 ~/.ifai/memories.md,你的所有偏好、决策、知识一目了然。


八、与主流产品的记忆能力对比

2025-2026 年,持久化记忆正从「噱头」变为 AI 编程助手的标配能力。IfAI 的方案在架构层面有几个独到之处:

维度 IfAI v0.4.7 Trae (字节) 通义灵码 (阿里) Cursor Windsurf
存储方式 纯 Markdown 本地文件 本地(私有格式) 云端(百炼平台) .cursorrules 文本文件 本地(私有格式)
人类可读可编辑 ✅ 任何编辑器直接改 ❌ 黑盒 ❌ 云端管理 ✅ 纯文本 ❌ 黑盒
自动记忆提取 ✅ AI 主动 + 会话后批量 ✅ 自动捕捉 ✅ 自动整理更新 ❌ 需手动维护 ✅ 自动
AI 主动保存 ✅ MemorySave 工具 ✅ 自动捕捉 ⚠️ 部分支持 ✅ Cascade
跨会话共享 ✅ 热记忆始终注入 ✅ 全局 + 项目双层 ✅ 云端同步 ⚠️ 规则文件持久 ✅ Memories
空间结构 ✅ Wing/Hall/Room 三层隐喻 ✅ 全局/项目两层 ✅ 个人/工程/问题分类 ❌ 扁平 ⚠️ 有限
自动去重 ✅ 内容比对 + 日期更新 ❌ 未知 ⚠️ 自动整理 ❌ 未知
TUI + GUI 共享 ✅ 同一文件跨界面 N/A(仅 IDE) ⚠️ 插件 + IDE N/A(仅 IDE) N/A(仅 IDE)
零新增依赖 ✅ 纯标准库 N/A N/A N/A N/A
注入延迟 18μs 未知 未知 ~0(文件读取) 未知
开源透明 ✅ 完整开源 ❌ 闭源 ❌ 闭源 ❌ 闭源 ❌ 闭源

IfAI 的三个差异化优势

  1. 透明可控 :记忆就是 Markdown 文件,用户能用 cat 查看、用 vim 编辑、用 git 追踪变更。竞品要么云端黑盒,要么私有格式不可读。

  2. 声明式空间隐喻 :Wing/Hall/Room 三层路径不是简单的标签分类,而是编译时类型安全的层级结构------拼错路径名,cargo build 直接报错。

  3. CLI 原生 :IfAI 是目前唯一一个在终端 TUI 中实现完整持久化记忆的编程助手。其他产品都是 GUI IDE 形态,没有命令行体验。

竞品的优势

  • Trae 的产品化体验最成熟,UI 管理 > IfAI
  • 通义灵码的云端同步适合企业多设备场景
  • Cursor 的代码索引和短期上下文理解仍然最强

九、总结

这套系统的核心哲学是:用最简单的技术,解决最实际的问题

  • 没有向量数据库,但够用
  • 没有复杂的嵌入模型,但够精准
  • 没有花哨的 UI,但人类可读
  • 零新依赖,1200 行核心代码,95% 测试覆盖率

有时候,最好的架构不是选最前沿的技术栈,而是选最不容易出错的那条路

v0.4.7 版本中,这套记忆系统已完整集成到 GUI 端------TUI 和图形界面共享同一份 memories.md,AI 在任意界面保存的记忆,另一个界面立即可用。会话归档(冷记忆)也在两端同步生效。

如果你也在做 AI 工具,希望这个思路能给你一些启发。代码已开源,欢迎 star 和 PR。


项目地址github.com/peterfei/if... 版本 :v0.4.7 核心技术:Rust / Tauri / macro_rules! / 纯 Markdown / 零依赖

如果觉得有用,欢迎转发给你的技术团队。

相关推荐
Niubility2 小时前
AI 让一个人干一家公司?现实卡在 Vibe Coding 这一关
ai编程·claude·vibecoding
刀法如飞2 小时前
Rust数组去重的20种实现方式,AI时代用不同思路解决问题
人工智能·算法·ai编程
喜欢打篮球的普通人3 小时前
claude code 基础分享
ai编程
w1wi3 小时前
【Vibe Coding】TCP/UDP包篡改重放工具
人工智能·网络协议·tcp/ip·ai·udp·ai编程
程序新视界4 小时前
Claude Code的一次真实项目实践体验
ai编程·claude
sg_knight5 小时前
第一次用 OpenClaw,我让它 3 分钟写了个小工具
算法·llm·agent·ai编程·openclaw
甲维斯5 小时前
98%命中率!Claude+Opus4.7也太强了吧!
人工智能·ai编程
开原第一保镖5 小时前
从“让 AI 写代码”到“把 AI 接入研发流程”:一次 Agentic Coding 实践复盘
aigc·openai·ai编程
__WanG5 小时前
Claude Code 多模型网关部署教程:从零实现多厂商大模型并行调度
ai·大模型·ai编程