ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

告别AI对话垃圾堆:用Markdown和Agent打造个人记忆日历

告别AI对话垃圾堆:用Markdown和Agent打造个人记忆日历

1. 为什么AI对话历史记录用起来像垃圾堆

先说说我自己的真实处境。我日常同时开着四五个AI对话窗口,写代码的、查资料的、改文案的、做技术方案评审的,每个窗口里都堆着几十上百轮对话。用了一周之后我发现一个很尴尬的事实:我明明记得三天前跟某个模型讨论过一个特别妙的架构思路,但当我真正需要把它翻出来的时候,我只能在那个无限滚动的侧边栏里一条一条往下扒,标题全是"新对话""关于XX的讨论""帮我优化一下"这种毫无信息量的东西。

这不是我一个人的问题。我身边做开发的朋友、做产品的朋友,几乎所有人都在抱怨同一件事:AI对话的历史记录功能,本质上就是一个只进不出的黑洞。它把所有对话都存下来了,但它从来没有帮你"记住"任何东西。存储和记忆,这是两码事。

1.1 存储不等于记忆:一个被混淆的核心概念

我们先把这件事说透。现在主流AI产品的历史记录,做的是存储:把每一轮对话的原始文本按时间顺序存进数据库,然后在侧边栏按时间倒序列出来。这个设计在对话数量少的时候没问题,一旦超过某个阈值,它就彻底失效了。

为什么失效?因为人类的记忆不是按时间索引的,是按主题、场景和关联索引的。你回想一件事的时候,脑子里冒出来的不是"那是上周二下午三点",而是"就是那次讨论缓存穿透的时候"。时间只是记忆的一个维度,而且往往是最不重要的那个维度。

我做过一个粗略统计:我一周产生的AI对话大概在80到120轮之间,其中真正有复用价值的(也就是我后来会回头去查的)大概只有15%左右。但为了找到这15%,我需要在100%的记录里翻找。这个信噪比低到让人放弃。

1.2 现有方案的三个死穴

我把市面上常见的几种"管理AI记录"的做法都试了一遍,总结下来有三个绕不过去的死穴。

第一个死穴是标题自动生成质量参差不齐。有些产品会根据首轮对话自动起标题,但往往起得又长又泛,比如"关于Python异步编程中事件循环机制的讨论与优化建议",这种标题在列表里占两行,扫一眼根本抓不住重点。更糟的是,很多对话中途话题会漂移,标题却永远停在第一轮。

第二个死穴是搜索只能搜关键词,搜不了意图。我想找"那个讲数据库连接池配置的对话",但对话里可能压根没出现"连接池"三个字,用的是"pool""连接复用""资源管理"这些词。关键词搜索在这种情况下直接歇菜。

第三个死穴是跨平台割裂。我在A工具里的对话、B工具里的对话、本地跑的Agent对话,各自为政,没有任何一个地方能把它们统一起来。这就导致我的"记忆"被切成了好几块,每块都不完整。

1.3 我想要的"记忆日历"到底长什么样

想清楚这些问题之后,我给自己定了一个明确的目标:我要的不是一个更好的历史记录列表,而是一套以日期为骨架、以主题为血肉、以Markdown为载体的个人记忆系统。

具体来说,它要满足几个硬性条件。第一,每天一个文件,用日期命名,这样时间维度天然清晰。第二,每个文件内部按主题分块,而不是按对话轮次流水账。第三,所有内容都是纯Markdown,这样我可以用任何编辑器打开、可以用Git做版本管理、可以全文检索、可以随时导出成别的格式。第四,要有一个Agent帮我把原始对话"消化"成结构化笔记,而不是让我手动整理。

这套东西我断断续续搭了两周,中间踩了不少坑,也推翻过好几版设计。下面我把完整的思路和实现过程拆开讲,包括我为什么这么设计、每一步具体怎么做、以及哪些地方我走了弯路。

2. 记忆日历的底层数据结构设计

动手写代码之前,我花了整整一个晚上只做一件事:设计数据结构。经验告诉我,这类个人知识系统的成败,80%取决于一开始的数据结构设计,剩下20%才是代码实现。结构设计错了,后面怎么改都是打补丁。

2.1 为什么选"一天一文件"而不是"一主题一文件"

我最初的想法是"一个主题一个文件",比如python-async.md、database-pool.md这样。听起来很合理对吧?但我很快发现两个问题。

第一,主题是会重叠和演化的。今天讨论的"异步编程"可能明天就跟"连接池"混在一起了,你没法干净地切分。第二,主题文件会无限膨胀,最后每个文件都变成几千行的大杂烩,跟流水账没区别。

改成"一天一文件"之后,边界天然清晰:今天产生的所有对话,不管什么主题,都归到今天的文件里。文件大小可控(一天的量再大也有限),而且时间线一目了然。更重要的是,它符合我前面说的"记忆日历"这个隐喻——翻日历一样翻自己的思考记录。

文件命名我用的是YYYY-MM-DD.md,比如2025-01-15.md。这个格式的好处是,按文件名排序就是按时间排序,不需要任何额外的索引。

2.2 文件内部的三层结构

每个日期文件内部,我设计了三个层次,从粗到细。

最上层是当日概览,用一段话总结今天主要聊了什么,相当于给未来的自己一个"目录"。这一层是可选的,但强烈建议保留,因为它能让你在几秒钟内判断"今天有没有我需要的东西"。

中间层是主题区块,每个区块用二级或三级标题标注,标题就是主题名。一个日期文件里可能有3到8个主题区块。这是检索的主要抓手。

最底层是要点与原文引用,每个主题区块下面是具体的结论、代码片段、关键决策,以及必要的原始对话摘录。注意,这里不是把原始对话全贴进来,而是提炼后的要点加上关键原文。

我用一个简化的例子说明这个结构:

# 2025-01-15 ## 概览 今天主要解决了异步任务队列的并发控制问题,另外调研了Markdown渲染方案。 ## 异步任务队列的并发控制 - 结论:用信号量控制并发数,比线程池更灵活 - 关键代码: ```python sem = asyncio.Semaphore(10) async def worker(task): async with sem: await process(task)
  • 踩坑:Semaphore要在事件循环内创建,不能跨loop复用

Markdown渲染方案调研

  • 对比了三个库,最终倾向markdown-it
  • 原因:插件生态好,支持自定义规则
这个结构看起来简单,但它解决了一个核心问题:**未来的你,可以在不读原始对话的情况下,快速获取当时的结论**。原始对话是原料,这个文件是成品。 ### 2.3 用标签做跨日期的横向索引 一天一文件解决了时间维度,但主题维度怎么办?比如我想看"所有关于数据库的讨论",它们散落在几十个日期文件里。 我的解法是**标签**。在每个主题区块的标题后面,用`#标签`的形式标注。比如`## 异步任务队列的并发控制 #python #async #并发`。这样我可以用全文搜索`#async`,一次性把所有相关区块捞出来。 标签体系我刻意保持克制,只用了大概二十个高频标签,分三类:技术栈(#python #rust #sql)、问题类型(#性能 #架构 #调试)、项目名(#项目A #项目B)。标签太多等于没有标签,这个道理跟文件夹分类是一样的。 > 提示:标签不要用中文和英文混着来,检索的时候容易漏。我统一用英文小写,中文主题就靠标题里的中文关键词搜。 ### 2.4 为什么坚持纯Markdown而不是数据库 有人可能会问,搞这么复杂,为什么不直接上个SQLite或者用Notion这类工具? 我的理由有三个。第一,**Markdown是纯文本,永远不会被某个工具绑架**。十年后我还能用记事本打开这些文件,但某个App可能早就倒闭了。第二,**Git天然适配**。我整个记忆日历就是一个Git仓库,每天自动commit一次,任何改动都有历史记录,还能diff。第三,**全文检索工具现成**。ripgrep、grep、各种编辑器的搜索,都能直接作用于Markdown文件,我不需要自己写搜索。 数据库方案我也试过,最大的问题是"看不见"。数据躺在db文件里,我没法用眼睛扫,没法用编辑器直接改,每次查东西都要写查询语句。对于个人知识管理这种高度依赖"随手翻看"的场景,纯文本的可见性比结构化查询重要得多。 ## 3. 让Agent自动把对话"消化"成笔记 数据结构定好之后,最大的工作量在于:**谁来把原始对话整理成上面那个结构**。手动整理?我试过,坚持了三天就放弃了,太累。所以这一步必须交给Agent自动化。 ### 3.1 整理任务的拆解:Agent到底要做什么 我把"整理"这个动作拆成了四个子任务,每个子任务对应Agent的一次处理。 第一个子任务是**判断对话是否有价值**。不是每轮对话都值得记录,很多就是闲聊或者一次性的问答。Agent需要判断这段对话是否包含可复用的结论、代码、决策或调研信息。没价值的直接跳过。 第二个子任务是**提取主题**。一段对话里可能涉及多个主题,Agent需要把它们切分开,给每个主题起一个准确的标题。 第三个子任务是**提炼要点**。把对话里的结论、代码、关键决策抽出来,去掉寒暄、重复和无关内容。 第四个子任务是**生成标签**。根据主题内容打上合适的标签。 这四个子任务,我一开始想用一个prompt全搞定,结果效果很差。后来改成**分步处理**,每一步一个独立的prompt,质量立刻上来了。这个经验很重要:**复杂任务不要指望一个prompt解决,拆成流水线**。 ### 3.2 分步处理的Prompt设计 我把每一步的prompt都固化下来,存在一个配置文件里。下面是我实际在用的几个prompt的核心逻辑。 判断价值这一步,我让Agent输出一个0到1的分数加理由:

你是一个对话价值评估器。判断以下AI对话是否包含值得长期保存的信息。 值得保存的标准:包含可复用的技术结论、代码片段、架构决策、调研对比、踩坑经验。 不值得保存的标准:纯闲聊、一次性事实查询、情绪表达、无结论的讨论。 输出格式:{"score": 0.0-1.0, "reason": "简短理由"}

提取主题这一步,我要求Agent输出结构化的JSON:

从以下对话中提取所有独立主题。每个主题包含:

  • title: 主题标题,不超过20字,要具体
  • tags: 3-5个标签,英文小写
  • key_points: 要点列表,每条不超过50字
  • code_snippets: 代码片段列表(如果有)
  • raw_quotes: 值得保留的原文引用(如果有) 输出JSON数组。
这里有个关键细节:**我要求Agent输出JSON而不是Markdown**。因为JSON可以被程序解析,我再写一个渲染器把JSON转成Markdown。这样职责分离,Agent只管提取,渲染交给代码。如果让Agent直接输出Markdown,格式经常不稳定,有时候多一个空行有时候少一个标题,很难统一。 ### 3.3 处理流程的编排 整个流程我用一个Python脚本串起来,逻辑是这样的: 1. 从各个AI工具导出当天的对话记录(大部分工具支持导出,不支持的我就手动复制) 2. 把原始对话按会话切分 3. 对每个会话调用"价值判断"prompt,低于阈值的丢弃 4. 对有价值的会话调用"主题提取"prompt,拿到JSON 5. 把所有JSON合并,按主题去重 6. 调用渲染器生成当天的Markdown文件 7. Git commit 这个流程我封装成了一个命令,每天睡前跑一次。整个处理过程大概需要一两分钟,取决于当天对话量。 ```python def process_daily_conversations(raw_dir, output_dir): conversations = load_conversations(raw_dir) valuable = [c for c in conversations if judge_value(c) > 0.6] all_topics = [] for conv in valuable: topics = extract_topics(conv) all_topics.extend(topics) merged = dedupe_topics(all_topics) markdown = render_markdown(merged) save_daily_file(output_dir, markdown)

3.4 踩过的坑:Agent整理出来的东西不能全信

这里必须说一个我踩得最深的坑。我一开始完全信任Agent的整理结果,结果过了一个月回头看,发现有些文件里的"结论"根本是Agent自己脑补的,原始对话里压根没这个结论。

问题出在哪?出在提炼这个动作本身就有信息损失和幻觉风险。Agent在"总结"的时候,会不自觉地补充它认为合理的细节,而这些细节可能并不存在于原始对话中。

我的解法是保留原文引用。在提取要点的时候,我强制要求Agent对每个关键结论附上一段原始对话的引用(raw_quotes字段)。这样我在回看的时候,可以快速核对"这个结论到底是不是当时说的"。虽然文件会变长一些,但可信度大大提升。

另一个坑是主题去重。同一天的多个会话可能讨论同一个主题,Agent提取出来的标题措辞不一样,但内容重复。我一开始用标题字符串匹配去重,效果很差。后来改成用语义相似度去重,把标题向量化之后算余弦相似度,超过阈值的合并。这个改动让文件干净了很多。

4. 检索与回看:让记忆真正能被调用

文件生成出来只是第一步,真正决定这套系统有没有用的,是你能不能快速找到你要的东西。我在这上面也折腾了好几轮。

4.1 三层检索策略

我把检索分成了三个层次,对应不同的使用场景。

第一层是按日期直接翻。当我知道大概是哪天讨论的,直接打开那天的文件。这是最快的,因为文件名就是日期,排序天然正确。

第二层是按标签搜。当我知道主题但不知道日期,用ripgrep搜标签。比如rg "#async" memory/,一秒出结果。

第三层是按语义搜。当我只记得大概意思,连关键词都想不起来,就用语义搜索。这一层我搭了一个简单的向量检索:把所有主题区块的标题和要点向量化,存进一个本地向量库,查询的时候算相似度。

# 按标签搜 rg "#database" ~/memory/ # 按关键词搜 rg "连接池" ~/memory/ # 按日期范围搜 rg --files ~/memory/ | grep "2025-01"

三层配合下来,我基本没有找不到的记录。大部分时候第一层和第二层就够了,第三层是兜底。

4.2 语义检索的最小实现

语义检索听起来高大上,其实最小实现很简单。我用的是一个本地embedding模型,把每个主题区块的文本转成向量,存成一个numpy数组,查询的时候算余弦相似度取top-k。

import numpy as np from sentence_transformers import SentenceTransformer model = SentenceTransformer('all-MiniLM-L6-v2') def build_index(memory_dir): chunks = [] for file in glob(f"{memory_dir}/*.md"): for block in split_by_topic(file): vec = model.encode(block['text']) chunks.append({'text': block['text'], 'vec': vec, 'file': file}) return chunks def search(query, chunks, top_k=5): q_vec = model.encode(query) scores = [np.dot(q_vec, c['vec']) / (np.linalg.norm(q_vec) * np.linalg.norm(c['vec'])) for c in chunks] idx = np.argsort(scores)[::-1][:top_k] return [chunks[i] for i in idx]

这个实现不到三十行,但效果出奇地好。我把它包成了一个命令行工具,输入一句话就能找到相关的历史记录。

4.3 回看时的"上下文重建"

找到记录之后还有一个问题:单看一个主题区块,往往缺上下文。比如我看到"用信号量控制并发"这个结论,但当时为什么不用线程池、遇到了什么具体问题,这些背景信息可能在同一个文件的其他区块里。

我的解法是在每个主题区块末尾加一个关联链接,指向同一天或相近日期里相关的其他区块。这个关联我让Agent在提取主题的时候顺便生成,用[[2025-01-15#异步任务队列]]这种wiki链接的格式。这样我在回看的时候可以顺着链接跳转,重建完整的上下文。

这个设计借鉴了Obsidian的双链思路,但我没有用Obsidian,而是自己用正则解析这些链接,在渲染的时候转成可点击的锚点。轻量,够用。

5. 跨工具统一:把散落的对话收拢到一处

前面讲的都是"有了对话之后怎么整理",但还有个前置问题:对话散落在各个工具里,怎么统一收集。这个问题不解决,记忆日历就是残缺的。

5.1 各工具的导出能力盘点

我把自己在用的工具盘了一遍,导出能力大致分三档。

第一档是原生支持导出,能直接导出成Markdown或JSON。这类最省事,写个脚本定时拉取就行。

第二档是有API但没导出功能,需要自己调API把对话拉下来。这类稍微麻烦点,但也能自动化。

第三档是啥都没有,只能手动复制粘贴。这类我就用一个浏览器书签脚本,选中对话内容一键复制成Markdown格式,然后粘到一个inbox文件里,等晚上统一处理。

工具类型导出方式自动化程度我的处理策略
支持导出原生导出全自动定时脚本拉取
有API调API半自动脚本+手动触发
无导出手动复制手动书签脚本+inbox

5.2 统一格式:所有对话先转成中间格式

不管从哪个工具来的对话,我都会先转成一个统一的中间格式,再进入处理流水线。这个中间格式很简单,就是一个JSON对象:

{ "source": "tool_name", "date": "2025-01-15", "messages": [ {"role": "user", "content": "..."}, {"role": "assistant", "content": "..."} ] }

统一格式的好处是,后面的价值判断、主题提取、渲染这些步骤,完全不用关心对话是从哪来的。加一个新工具,只需要写一个转换器把它的格式转成这个中间格式,其他都不用动。这就是适配器模式的经典应用。

5.3 处理"对话漂移"的实用技巧

实际用下来,我发现一个很普遍的现象:一段对话里话题会漂移。开始聊A,聊着聊着变成B,最后又绕回A。这种对话如果整体提取主题,会得到一个四不像的标题。

我的处理办法是按话题切换点切分。具体做法是让Agent在提取主题的时候,同时输出每个主题对应的消息区间(比如"messages 1-8")。如果一段对话被切成多个主题,就分别处理。这样每个主题区块都是干净的。

这个切分逻辑我一开始想用规则做(比如检测"换个话题"这种关键词),但效果不好,因为话题切换往往很隐蔽。最后还是交给Agent判断,在prompt里明确要求"如果对话中途话题发生明显变化,请切分成多个主题"。

6. 实际运行一个月后的效果与调整

这套系统我从搭好到现在跑了一个多月,积累了几十个日期文件。说说真实的使用感受和后续调整。

6.1 哪些设计经受住了考验

一天一文件这个设计完全正确。我现在回看的时候,先看日期,再看概览,基本能定位到我要的东西。文件数量增长也很健康,一个月三十个文件,一年三百多个,完全在可控范围。

纯Markdown也是对的。我用ripgrep搜东西的速度,比任何App内置搜索都快。而且我随时可以用VS Code打开整个文件夹,用它的全局搜索和预览功能,体验很好。

分步处理的流水线设计也验证了。虽然比单个prompt麻烦,但稳定性和质量高太多。特别是JSON中间格式这一层,让整个流程非常可控。

6.2 哪些地方我做了调整

标签体系我简化了。一开始我设计了很细的标签,结果发现维护成本太高,Agent打的标签也经常不一致。后来砍到只剩十几个核心标签,反而更好用。

概览部分我改成了必填。一开始觉得概览可有可无,后来发现它是快速定位的关键。现在概览是Agent必须生成的,而且我要求它用一句话概括当天最重要的那件事。

增加了"待办"区块。有些对话里会产生待办事项,比如"下次要试试XX方案"。我让Agent把这些单独抽出来,放在文件末尾的待办区块里。这样我翻文件的时候能顺便看到有哪些事没做。

6.3 一个意外的收获:记忆日历变成了复盘工具

这是我没预料到的。因为每天都有记录,我周末翻一遍这一周的文件,就能清楚地看到自己这一周在想什么、解决了什么问题、有哪些想法没落地。这比任何时间管理工具都直观。

有一次我翻到两周前的一个技术方案讨论,当时觉得可行,后来搁置了。翻到的时候我重新评估了一下,发现还是值得做,就捡起来了。如果没有这个记录,这个想法就永远丢了。

6.4 给想搭类似系统的人的建议

如果你也想搭一套,我的建议是从最小可用版本开始。不要一上来就搞向量检索、搞自动化流水线。先用最简单的方案:每天手动建一个Markdown文件,把当天的对话要点手动记进去。坚持一周,你会对"自己需要什么样的结构"有真实的感受,然后再逐步自动化。

工具是次要的,坚持记录才是核心。我见过太多人花大力气搭了一套完美的系统,结果用了三天就不记了。反而是那些结构简单、随手就能记的方案,能长期跑下去。

另外,不要追求记录的完整性。不是每轮对话都要记,只记那些你未来可能会回看的。我现在的标准是:如果一个结论我一周后还会想知道,就记;如果只是当下的一次性问答,就让它过去。记忆这件事,筛选比收集重要。

最后分享一个我最近加的小功能:每周日晚上,我让Agent把这一周所有日期文件的概览汇总成一个周报,再让另一个Agent基于周报生成一份"本周思考总结"。这个总结我存在一个单独的weekly/目录里。这样我就有了日、周两个粒度的记忆,回看的时候可以先看周报定位到大概哪一周,再翻到具体某一天。这个双层结构用下来很舒服,推荐你也试试。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进