LightVela

USER.md 與 MEMORY.md 為什麼不能合併?

摘要

USER.mdMEMORY.md 是 Hermes Agent 長期記憶的"兩份貼身筆記",它們不能合併的原因有三條硬約束:一、容量各自獨立(USER.md 約 1,375 字符 / 500 tokens;MEMORY.md 約 2,200 字符 / 800 tokens),互不擠壓;二、加載策略不同(兩份都每次會話必載,但語義角色完全不同——一份穩定描述"你是誰",一份高頻記錄"我們做過什麼");三、更新語義不同(USER.md 偏好類字段以"覆蓋舊值"為主,MEMORY.md 以"追加 + 按需 consolidate"為主)。合併成一份 NOTES.md 會同時破壞這三條約束,是典型的"看起來更簡單,實際用起來更糟"。


引子:一個正經的反問

《Hermes Agent 為什麼能"記住你"?拆解長期記憶系統的工作原理》裏講過,Hermes 的分層記憶裏,USER.mdMEMORY.md 加起來只有 ~3,575 字符 / ~1,300 tokens——放在動輒十萬上下文窗口的時代,這個空間簡直過分節制

於是很多人第一反應是三個問題:

  • 為什麼不合成一個 NOTES.md
  • 為什麼不放大到幾十 KB?
  • 為什麼這兩個文件的容量還差一截(2,200 vs 1,375)?

這些問題其實指向的是同一個答案:USER.mdMEMORY.md 是兩種性質不同的記憶,塞在一起會互相拖累。下面把這條設計原則拆成三層來看。


一、USER.md:穩定、結構化的用户畫像

USER.md關於"你"的穩定描述

  • Identity:名字、職業、所在地。
  • Preferences:溝通風格、常用技術棧。
  • Constraints:作息、語言、禁忌話題。
  • Working style:喜歡結論先行還是過程先行。
  • Long-term goals:長期在做什麼、追求什麼。

它的關鍵特徵

  • 穩定:幾天甚至幾周才會更新一次。
  • 結構化:分節、條目化、字段化。
  • 必載:每次會話開始自動加載進系統提示。
  • 有硬上限:~1,375 字符 / ~500 tokens。

你可以把 USER.md 想成 Agent 貼在顯示器邊框上的便利貼——不多,但抬眼就看到,抬眼就用得上。


二、MEMORY.md:可增長、按需召回的事實檔案

MEMORY.md 存的是你和 Agent"一起幹過什麼"

  • 項目狀態:agent-demo 前端使用本地開發端口。
  • 結論決策:上週把 hero 區改為動態漸變。
  • 具體事件:上週和 A 團隊定了 Q4 會做 X。
  • 特定知識點:這個 API 的鑑權走內部 SDK 而非公開接口。

它的關鍵特徵

  • 動態:幾乎每次實質會話都會寫入 1-2 條。
  • 條目式:一條一條追加,方便召回。
  • 必載 + 按需 FTS5 命中:常用條目每次會話必載,歷史條目通過 FTS5 命中拉入。
  • 典型 8–15 條:目標條目數 8-15 條、總量 ~2,200 字符 / ~800 tokens。

你可以把 MEMORY.md 想成 桌上的日記本——比便利貼多,但仍然寫得剋制,為的是"翻起來快"。


三、"為什麼不合成一個 NOTES.md?"—— 三條硬約束

有人會説:"既然都是每次必載的 Markdown,為什麼不合並?"

不能合併的三條硬約束如下,每條都獨立到足夠讓這個方案破產。

理由 1:容量各自獨立,不互相擠壓

USER.md 500 tokens、MEMORY.md 800 tokens。合併之後,一份 1,300 tokens 的 NOTES.md 會立刻遇到一個問題:MEMORY.md 快滿時,Agent 是否要為"再記一條事件"而刪掉一條"用户是誰"的畫像?

答案顯然是不該——但一旦合併,這個決策就必須做,且每次記錄都做。分開之後,兩個字段池各自演化,MEMORY.md 滿了只擠 MEMORY.md 自己,不會污染畫像。

理由 2:加載策略語義不同

兩個文件確實都是每次會話必載,但它們在系統提示裏扮演的角色完全不同:

  • USER.md 段回答的是 "你面對的是誰",用於給 Agent 定調(語氣、結論粒度、技術棧假設)。
  • MEMORY.md 段回答的是 "你們一起做過什麼",用於給 Agent 提供事實抓手。

如果合併,兩段內容會互相稀釋——Agent 無法快速判斷"這段話是我要遵循的溝通風格,還是我要引用的過往事實"。

理由 3:更新語義不同

  • USER.md 偏好字段以"覆蓋舊值"為主:用户説"以後別再總結了",直接覆蓋 preference 裏的相關行。
  • MEMORY.md 以"追加 + 按需 consolidate"為主:事件是時間序列,歷史不能被隨意抹掉;容量滿了則通過下面的錯誤響應觸發主動整合:
{
  "success": false,
  "error": "Memory at 2,100/2,200 chars. Consolidate now...",
  "current_entries": [...],
  "usage": "2,100/2,200"
}

合併之後,add / replace / remove 三種原子操作要在同一個文件上並存,實現和用户心智負擔都會同時變糟


四、一個具體的例子

假設你告訴 Agent:

"我叫 Jasmin,是一名前端工程師,喜歡結論先行的溝通風格。最近在做 agent-demo 項目(一個 AI Agent 應用)的國際站,用 Next.js。上週我們把 hero 區改成了動態漸變。"

一個訓練良好的 Hermes Agent 會做這樣的拆分:

寫入 USER.md

## Identity
- Name: Jasmin
- Role: 前端工程師

## Preferences
- 溝通風格:結論先行
- 常用技術棧:Next.js

寫入 MEMORY.md

- [2026-07-30] agent-demo 項目:國際站 hero 區改為動態漸變。
- 相關項目:agent-demo / 國際站 (global region)

看到區別沒?

  • "你是什麼樣的人"進畫像,"我們一起做了什麼"進檔案。
  • 前者只會隨着你本人變化(換工作、換團隊)才會改;後者隨着每一天的對話都在增長。
  • 分開寫,才能各自演化。

五、跳過寫入的 5 條黑名單

Hermes 也不是什麼信息都往 USER.md 塞。它有一份"跳過黑名單":

  1. 一次性情緒:"今天頭有點疼"——不是穩定屬性。
  2. 臨時任務參數:"這次幫我把標題改成 X"——用完即棄。
  3. 模糊態度:"我大概比較喜歡簡潔一點"——不夠可複用(要麼加限定,要麼不記)。
  4. 可從對話上下文實時推斷的信息——沒必要落盤。
  5. 敏感信息(如未經用户顯式確認的郵箱、密碼、地址)——涉及隱私默認不寫。

有了這份黑名單,USER.md 才能維持"500 tokens 且高質量"的密度。


六、這對產品意味着什麼?

對 Agent 產品而言,USER.md / MEMORY.md 的分層不只是工程細節,它決定了三件事:

  1. 能不能給用户看"你對我的印象" —— 需要一份穩定、可讀、可編輯的畫像(USER.md 的語義)。
  2. 能不能長期不失憶 —— 需要一份可增長、可召回的事實檔案(MEMORY.md 的語義)。
  3. 成本能不能被約束 —— 分層加載才能既"記得你是誰"又不燒掉一整個 context。

七、LightVela:把這份"兩筆賬"做成產品

LightVela 內部的 Agent 記憶體系,借鑑的正是這種分層思路。Hermes 的 USER.md / MEMORY.md 是給開發者看的 Markdown,LightVela 把它做成兩件普通用户能直接操作的東西:

  • 畫像層可視化:用户能在界面上直接看到"Agent 記得我的哪些畫像"、"這些畫像來自哪次對話",並支持一鍵修正。畫像不再是黑盒 USER.md
  • 事實層可組織:事實記憶不只是一堆條目,而是能被打上項目、任務、時間等標籤,方便召回與清理。團隊場景下還能把"團隊記憶"和"個人記憶"分層管理。
  • 最短路徑:如果你被"分層記憶"這套思路打動,但不想自己去改 ~/.hermes/USER.md、維護 FTS5、備份 SQLite,LightVela 是把這套思路直接產品化後端給你的最短路徑。

一句話:Hermes 給了"分層記憶"的工程藍本,LightVela 把它做成每個人都用得上的產品體驗。


小結

  • USER.md:穩定、結構化、每次必載的用户畫像(~500 tokens,8-15 條畫像字段)。
  • MEMORY.md:可增長、按需召回的事實檔案(~800 tokens,8-15 條事件條目)。
  • 二者不能合併的三條硬約束:容量互不擠壓 / 加載語義不同 / 更新語義不同
  • 分層是好 Agent 記憶系統的底層能力,也是 LightVela 記憶能力產品化的起點。