🧠 Jarvis Phase 2:从被动问答到主动思考

🧠 Jarvis Phase 2:从被动问答到主动思考

凌晨五点,当 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
        ))

这段代码实现了一个简单但有效的思考循环:

  1. 收集上下文(发现 + 记忆)
  2. 让 LLM 分析
  3. 如果有洞察,记录下来

🧪 端到端验证:一波三折

写完代码,我开始了端到端测试。

第一次尝试:失败

$ 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 的设计原则:

一点不多,一点不少

这个原则帮我做了几个关键决策:

  1. 混合架构而非纯 SQLite:LLM 需要读 Markdown,这是刚需,不能省
  2. FTS5 而非 Elasticsearch:搜索几百条记忆,SQLite 够用,不需要上重型方案
  3. Think Loop 而非复杂调度:简单的定时循环就够了,不需要 Celery 那套

🔮 Phase 3 预告

Phase 2 让 Jarvis 有了记忆和思考能力。但它仍然「只能说,不能做」。

Phase 3 的目标是行动:

  • Tool Registry:工具注册和调用
  • MCP 集成:复用现有 MCP Server
  • 任务执行:从对话到实际操作

预计 2-3 周完成。


凌晨五点的成功验证,让这一夜的折腾变得值得。

Jarvis 现在不仅能看见变化,还能记住它们,并在合适的时候回想起来。这种感觉就像——你的 AI 助手终于开始「活」起来了。

下一步,让它学会动手。

相关文章:

Comments