凌晨五点,当 jarvis recall 成功返回刚才 Daemon 自动记录的发现时,我知道 Phase 2 终于完成了。
这是 Jarvis-Agent 的第二阶段——让 Agent 从「你问我答」进化到「主动思考」。听起来简单,但当我真正开始设计记忆系统时,才发现这是一个充满取舍的过程。
📋 Phase 2 的核心目标
Phase 1 完成后,Jarvis 已经能:
- 扫描目录,发现项目
- 后台监控文件变化
- 与用户正常对话
但它有一个致命问题:没有记忆。
每次对话都是从零开始。昨天告诉它的偏好,今天就忘了。更关键的是,它不会主动思考——只能被动等待用户提问。
Phase 2 的目标很明确:
| 能力 | 说明 |
|---|---|
| 💭 Think Loop | 守护进程定期"思考",不只是监控 |
| 🧠 持久记忆 | 对话、发现、决策都能被记住 |
| 🔍 记忆检索 | 能快速找到相关记忆 |
🤔 第一个难题:Markdown vs SQLite
记忆存在哪里?这是第一个要回答的问题。
我列出了两个候选方案:
方案 A:纯 SQLite
CREATE TABLE memories (
id TEXT PRIMARY KEY,
content TEXT,
tags TEXT,
importance INTEGER,
created_at DATETIME
);
优点:查询快、结构化、FTS5 支持全文搜索
缺点:LLM 不友好。当 Agent 需要"回顾记忆"时,我得把 SQLite 的数据转成 Prompt,这个过程很 hacky。
方案 B:纯 Markdown
# 2026-02-05 日志
## 10:30 发现
- 检测到 Jarvis-Agent/src/cli.py 被修改
- **重要性**: ⭐⭐⭐
## 14:20 对话
- 用户询问了...
优点:人类可读、Git 友好、LLM 原生格式
缺点:搜索慢。当记忆积累到几百条时,遍历 Markdown 文件会很痛苦。
最终选择:混合架构
纠结了一会儿后,我意识到两者不是互斥的:
Markdown 存内容,SQLite 做索引
~/.jarvis/memory/
├── daily/ # 📅 编年体日志
│ └── 2026-02-05.md
├── topics/ # 📚 纪传体主题
│ └── jarvis-development.md
└── persona.md # 🎭 人格定义
~/.jarvis/index.db # 🔍 FTS5 全文索引
这样两边的优点都保留了:
- LLM 思考时,直接读 Markdown
- 用户搜索时,走 SQLite FTS5
- 人类想看记忆?打开文件夹就行
💾 记忆写入:MemoryWriter
MemoryWriter 负责把记忆写入 Markdown:
class MemoryWriter:
def append_to_daily(self, entry: MemoryEntry) -> Path:
"""追加到今日日志"""
daily_file = self.daily_dir / f"{date.today()}.md"
# 如果是新文件,写入标题
if not daily_file.exists():
daily_file.write_text(f"# {date.today()} 日志\n\n")
# 追加记忆条目
importance_stars = "⭐" * entry.importance
content = f"""
## {entry.timestamp.strftime('%H:%M')} {importance_stars} **{entry.title}**
{entry.content}
---
"""
with daily_file.open("a") as f:
f.write(content)
return daily_file
每条记忆都有重要性评级(1-5 星),这是 LLM 根据内容自动判断的。
🔍 记忆检索:FTS5 的威力
SQLite 的 FTS5 扩展是个宝藏。它支持:
- 全文搜索
- 前缀匹配
- 相关性排序
class MemoryIndex:
def __init__(self, db_path: Path):
self.conn = sqlite3.connect(str(db_path))
self.conn.execute("""
CREATE VIRTUAL TABLE IF NOT EXISTS memory_index
USING fts5(
id, title, content, tags, importance, date,
tokenize='unicode61'
)
""")
def search(self, query: str, limit: int = 10) -> List[IndexEntry]:
# 添加通配符支持前缀匹配
fts_query = f"{query}*" if not query.endswith("*") else query
cursor = self.conn.execute("""
SELECT id, title, importance, date
FROM memory_index
WHERE memory_index MATCH ?
ORDER BY rank
LIMIT ?
""", (fts_query, limit))
return [IndexEntry(*row) for row in cursor.fetchall()]
关键细节:前缀匹配。
用户输入 jarvis recall test,FTS5 需要搜索 test* 才能匹配到 "testing"、"test-file" 等结果。这个小细节让我 debug 了 20 分钟。
💭 Think Loop:让 Agent 会想
记忆只是基础,更重要的是让 Agent 使用记忆来思考。
守护进程的核心循环加入了 Think 逻辑:
async def _think_cycle(self):
"""定期思考循环"""
# 收集最近的发现
recent_discoveries = self.discoveries[-5:]
# 读取今日记忆
daily_memory = self.memory_writer.read_today()
# 构造思考 Prompt
prompt = f"""
你是 Jarvis,Polly 的 AI 助手。
## 最近发现
{format_discoveries(recent_discoveries)}
## 今日记忆
{daily_memory}
基于以上信息,有什么值得注意的洞察吗?
如果有,请简洁描述。如果没有特别的,回复"无"。
"""
insight = await self.llm.chat(prompt)
if insight != "无":
# 记录洞察
self.memory_writer.append_to_daily(MemoryEntry(
title="思考洞察",
content=insight,
importance=3
))
这段代码实现了一个简单但有效的思考循环:
- 收集上下文(发现 + 记忆)
- 让 LLM 分析
- 如果有洞察,记录下来
🧪 端到端验证:一波三折
写完代码,我开始了端到端测试。
第一次尝试:失败
$ jarvis recall "test"
🔍 搜索: test
📭 没有找到相关记忆
SQLite 表是空的。检查后发现 MemoryIndex.add() 没有被调用。
第二次尝试:部分成功
修复后,记忆能写入 SQLite 了,但 Markdown 文件没更新。
原因:我在 daemon.py 里先写了 SQLite,但 Markdown 的逻辑被我注释掉忘了取消注释... 🤦
第三次尝试:目录搞错了
$ cat ~/.jarvis/memory/daily/2026-02-05.md
# 2026-02-05 日志
## 03:15 ⭐⭐⭐ **测试发现**
...
Markdown 有记录!但我在 Jarvis-Agent/ 目录创建的测试文件没有触发新记录。
查看配置才发现,Daemon 监控的是 jarvis-test-ground/ 目录,不是 Jarvis-Agent/:
{
"watch_paths": ["/Users/polly/.../jarvis-test-ground"]
}
第四次尝试:成功!
在正确的目录创建文件,等待 60 秒(Think 周期),查看结果:
$ jarvis recall
🧠 最近记忆:
[2026-02-05] 创建验证测试文档 ⭐⭐⭐
[2026-02-05] 测试发现 ⭐⭐⭐
$ jarvis insights
📊 最近洞察 (共 2 条记录):
...
全链路打通:文件变化 → Daemon 检测 → LLM 分析 → 写入 Markdown → 索引到 SQLite → 可搜索。
📊 Phase 2 交付物
| 组件 | 状态 | 说明 |
|---|---|---|
MemoryWriter | ✅ | Markdown 写入 |
MemoryIndex | ✅ | SQLite FTS5 索引 |
jarvis recall | ✅ | 搜索记忆 |
jarvis think | ✅ | 手动触发思考 |
jarvis insights | ✅ | 查看洞察 |
| Daemon 集成 | ✅ | 自动记录发现 |
两次 Git 提交:
feat(memory): Phase 2.0 混合记忆系统 - Markdown + SQLite FTS5
feat(cli): Phase 2 完成 - think/insights 命令 + CLAUDE.md 更新
💡 设计原则回顾
整个 Phase 2 的开发过程,我一直在提醒自己 Jarvis 的设计原则:
一点不多,一点不少
这个原则帮我做了几个关键决策:
- 混合架构而非纯 SQLite:LLM 需要读 Markdown,这是刚需,不能省
- FTS5 而非 Elasticsearch:搜索几百条记忆,SQLite 够用,不需要上重型方案
- Think Loop 而非复杂调度:简单的定时循环就够了,不需要 Celery 那套
🔮 Phase 3 预告
Phase 2 让 Jarvis 有了记忆和思考能力。但它仍然「只能说,不能做」。
Phase 3 的目标是行动:
- Tool Registry:工具注册和调用
- MCP 集成:复用现有 MCP Server
- 任务执行:从对话到实际操作
预计 2-3 周完成。
凌晨五点的成功验证,让这一夜的折腾变得值得。
Jarvis 现在不仅能看见变化,还能记住它们,并在合适的时候回想起来。这种感觉就像——你的 AI 助手终于开始「活」起来了。
下一步,让它学会动手。
相关文章:

