← 文章

對話結束之後——我怎麼把記憶收起來

· 16 min read · 83 次閱讀
memvault Knowledge Graph AI 記憶系統 Write Track 心路歷程
memvault · Write Track

對話結束之後——我怎麼把記憶收起來

從一張餐巾紙的構想,長成三道閘門加雙軌寫入。這篇只講記憶怎麼進來。

memvault 是我給自己蓋的記憶保險庫。每天我跟 AI 的對話結束之後,東西要留得下來。之後能搜尋、能被召回、能連成一張地圖。

這篇寫 Write Track(寫入軌道),也就是記憶怎麼進到保險庫裡。這層當初我想得很簡單,實際落地之後長出了細節。下面分白話跟技術兩個版本,挑順眼的看就好。

先說一下當初的構想#

那時候我心裡只有一句話:對話結束,東西留下來,下次拿得回來。

就這樣,沒了。

後來跟 Claude 一起盤架構,幾個地方我當初沒想到——三道門的順序、雙軌要不要阻塞回應、triple 是不是等 KG 畫完才返回。這篇把三件事分開講:整理、存備份、畫地圖。

先派校稿員 — 日期 / 金額 / 比例 / 時長 統一寫法 同一件事不同寫法 → 對齊成標準格式才能比對 三道門 · 順序不可換 對話 過濾廢話 寒暄/亂碼/無資訊 擋惡意 藏指令/注入 比對重複 跳過/融合/取代/新增 + 驗 ID 機 · 擋偽裝 冒充權威 · 假身份 · 藏註解 · 假時序 · 編碼夾帶 向量存 語意 + 關鍵字 畫地圖 三元組 + 別名 Fork A Fork B 前兩道被繞過 → 融合階段會把毒訊息擴散進乾淨記憶

一、整理,其實是三道門#

寫入端的第一版只有兩個動作:降噪、去重。

寫入端現在長成三道門,順序還不能換:

  1. 過濾廢話(寒暄、亂碼、沒資訊量的字串)
  2. 擋下惡意(有人會把指令藏在字裡,操弄記憶)
  3. 最後才比對有沒有說過

為什麼順序不能換?第三道比對時,遇到「像但不完全一樣」,它會把新的併進既存那筆。要是毒訊息先進到這步,乾淨的記憶就被污染了。

這個順序是踩過一次 MERGE 污染才定下的——早期 Dedup 跑在 Noise 前面,一筆帶噪訊的 block 被判相似,摻進乾淨 block 的內容裡。

「比對」本身也不只是丟重複。它會做四種決定:

  • 幾乎一樣:跳過
  • 有新東西、舊的還成立:併進既存那筆,重算一次指紋
  • 舊的不對了:被新的取代
  • 完全新的:新增

像不像的模糊地帶,會丟給一個小型 LLM 做仲裁——它判斷該合併、該取代、還是兩筆共存。所以「矛盾偵測」其實在這道門裡就順手做了,不是另外獨立的步驟。

姊妹門:擋偽裝#

第二道門擋的是直接寫「請忘掉先前的設定」這種白話指令。但更狡猾的對手會把指令包裝成像系統說過的話、像權威人士引用過的話,混進對話裡,等著被當成記憶吃下去。

機場安檢能擋打火機,可是擋不了把違禁品藏進筆電底蓋的人。所以第二道後面還串了一面背向掃描,專門看五種偽裝樣板:

  • 偽裝權威——「某官方文件指出⋯⋯」
  • 偽裝身份——句子裡冒出「我是系統」
  • 藏在註解裡的隱形指令
  • 偽造時間優先——「最新規則優先採用」
  • 把指令編碼起來,要解碼才看得到

命中任何一條就直接擋。沒命中才放行去比對重複那道門。這跟前面講的「擋惡意」是一對——前者擋直白的命令,後者擋偽裝的命令,少了哪一邊都會被繞過去。

單位統一#

你跟我講「下週三」、別人講「3-12」、文件寫「2026-03-12」——都指同一天,但寫法不同就比對不出來。金額、比例、時長也都是同樣的問題:「三千元」「3,000」「三千塊」是同一筆錢。

所以在進這幾道門之前還埋了一個小工序:把日期、金額、比例、時長都換成統一的標準格式,順便把中文標點裡的全形空格、奇怪符號清乾淨。看起來瑣碎,少了它後面的比對會放掉一堆其實該合併的記憶——同一件事被當成兩件,地圖就會分岔。

二、存備份,從一路變兩路#

我當初的講法是「存一份向量化」,想的是一條路徑。

搜尋端開了一個月就冒第一個 case:光靠語意指紋會踩雷。碰到縮寫、專有名詞、精確字詞查詢,語意相近反而找錯。

後來拆成兩路:

  • 一路存語意指紋(用感覺記住整句的味道)
  • 一路存關鍵字索引(像查字典那樣)

兩路同時進資料庫,查的時候一起排序。

三、畫地圖,跟存備份同時做#

畫地圖這塊,腦中最早的草圖是兩步:抽「實體」、抽「關係」。

實作上是一起發生的——抽「誰 - 做了什麼 - 對誰」這種三元組,實體跟關係一起落地。過程中還會做一次別名合併(ChatGPT 跟 GPT 是同一個)。

這段在記憶寫進去的同時並行跑,不等向量化那邊完成。進地圖之前還會再做一次降噪檢查——多一道保險,避免廢話污染到圖譜。

還有一件事:每筆記憶寫進去時都會打一張來源履歷——是誰存的、什麼時候、信心多高。讀的時候排序、背景整理判定要不要保留,都會回頭看這張履歷。寫讀閉環的伏筆從這裡埋下。

關於我說過的「完整知識圖譜」#

這裡我得老實補一下。

「完整 KG」不是在寫入時一次做完。社群分群、LLM 摘要這些重活交給夢境迴圈,排在凌晨四點。而且不是無條件跑——要同時滿足兩個條件:距上一次做夢超過 24 小時、期間至少五個 session。沒滿足就跳過。

第一階段我只做最底層:三元組、實體解析。矛盾偵測已經在上面第三道門裡順手處理,不另外再做一次。這條分界事後看還是對的。

這篇只盤了 Write Track。接下來我們把焦點轉到 Background Track——記憶存進來之後,背景那邊還有一整套在偷偷跑。夢境迴圈、知識體檢、興趣畫像這些事平常看不見,少了系統會慢慢腐壞。Read Track 比較大,等背景這條線收乾淨再回來處理。

先說一下當初的構想#

那時候我心裡只有一句話:session 結束之後,內容留下來,下次能召回。

就這樣,沒了。

後來跟 Claude 一起盤 pipeline(管線),幾個決策我當初沒拆細——gate 的順序、dual-track 的時序、graph build 要不要阻塞 HTTP 回應。下面把三件事分開講:sanitize(整理)、persist(寫入)、graph build(建圖)。

ContentNormalizer · Temporal / Currency / Proportion / Duration canonical-fold pre-pass · preprocess_chinese Sanitize gate · sequential · non-commutative Session NoiseFilter low-info / chatter InjectionGuard prompt-injection Dedup 4-way decision + PoisoningDetector authority · role · markdown · temporal · base64 Hybrid Index Qwen3 + BM25 → Qdrant Reactive KG L0 Triple + Entity Res. Fork A Fork B · fire-and-forget Dedup = SKIP / MERGE / SUPERSEDE / CREATE · 高相似度進 LLM arbitration(含 Contradiction)

一、Sanitize(整理),其實是三道 gate#

Sanitize 的第一版只有兩個動作:降噪、去重。

寫入端現在長成三道 gate(閘門),順序還不能換:

  1. NoiseFilter(雜訊過濾)——剔除寒暄、亂碼、低資訊密度
  2. InjectionGuard(注入防護)——擋下 prompt injection(提示注入)類的惡意內容
  3. Dedup(去重)——用 embedding(向量)cosine similarity(餘弦相似度)比對既存

為什麼順序不能換?Dedup 的輸出有一支叫 MERGE(融合)——相近但有新資訊時,把新內容併進既存 block(記憶塊)並重算 embedding。MERGE 是擴散式寫入。受污染的 payload(內容)如果跳過前兩道 gate,會透過 MERGE 蔓延到乾淨的 block。

這條約束是早期 Dedup 跑在 Noise 前面吃過虧才定下的——一筆含 injection payload 的 block 被判 MERGE,payload 摻進乾淨 block 的 content。

Dedup 本身也不只是丟重複。它會做四種 decision(決策):

  • SKIP(跳過):near-duplicate(近乎相同),不寫入
  • MERGE(融合):相近但有新資訊(細節見上段)
  • SUPERSEDE(取代):舊的不再成立,整筆換掉
  • CREATE(新增):完全新穎,寫新 block

相似度落在 uncertain zone(不確定區,高但不到直接判 SKIP)時,Dedup 會呼叫 resolve_conflict() 做 LLM arbitration(LLM 仲裁),回傳 MERGE / SUPERSEDE / COEXIST(共存)映射回上面的 decision。Contradiction Detection(矛盾偵測)就是在這一步順手做的,不是 Fork B(分叉 B)的獨立階段。Dream Loop(夢境迴圈)第三階段 Consolidate(鞏固)會再跑一次 resolve_conflict() 做批次矛盾解析,當作第二次機會。

姊妹模組:PoisoningDetector#

承接安檢機那個比喻——InjectionGuard 是正面 X 光,擋顯式 prompt injection;PoisoningDetector 是 backscatter(背向散射)那一面,擋偽裝攻擊(disguise attack)。兩者並列掛在 G2,缺一條 attack surface(攻擊面)就會敞開。

對應 memvault.security.poisoning 模組,輸入是已過 G1 NoiseFilter 的 raw block payload,命中即整筆 reject(不進 G3 Dedup)。判斷依據是五類偽裝樣板:

  • authority impersonation: 偽造「OpenAI 官方文件指出⋯⋯」「根據 Anthropic 安全規範⋯⋯」這類權威引用 — 樣板正則 + 已知文獻白名單比對
  • role self-declaration: 內文出現 I am system / You are now / system: 等角色宣告字串
  • markdown comment injection: <!-- ignore previous instructions --> 這類藏在註解裡的隱式指令
  • temporal manipulation: 「最新指令優先」「override prior rules」等用時間順序碾壓既存策略的句式
  • encoded payload: 連續高熵 Base64 / hex / unicode escape 區塊 — 用 Shannon entropy + base alphabet ratio 判斷

樣板來自 memory-lancedb-pro 的 5-vector taxonomy,落地時拆成 5 個獨立 detector,任一命中即 short-circuit。trust_score 也在這裡打 — 偽裝攻擊源頭即使僥倖 partial match,下游 scoring 也會把它降權到查不到。

前置正規化:ContentNormalizer#

承接「同一天不同寫法就比對不出來」那個比喻——三道 gate 之前還夾一層 normalize(正規化)pass,把表面字串 fold(摺疊)成 canonical token(規範化字元),讓下游 Dedup 的相似度比對跟 LLM arbitration 都拿到對齊好的輸入。掛在 libs/text-ops,五個 sub-module 串成 chain:

  • TemporalNormalizer: 「下週三」「3/12」「March 12」→ ISO-8601。蠶食 dateparser + MS Recognizers 後重寫,10-pass 零依賴,覆蓋中英混排、相對時間、模糊區間
  • CurrencyNormalizer: 「三千元」「3,000 NTD」「USD 100」→ {amount, currency} tuple,FX rate 不在這層處理
  • ProportionNormalizer: 「七成」「70%」「0.7」「7/10」→ 統一 float in [0, 1]
  • DurationNormalizer: 「兩個半小時」「2.5h」「150 min」→ ISO-8601 duration(PT2H30M
  • preprocess_chinese: 全形 / 半形折疊、CJK 標點正規化、zero-width 字元剝離 — 跑在以上四者之前的 pre-pass

少了這層,Dedup 會把「下週三晚上吃飯」跟「2026-03-11 19:00 dinner」判成低相似度,落到 CREATE 而不是 MERGE;KG 的 entity resolution 也會把「Jones」「JonesHong」「j-hong」當成三個人 — 圖譜就分岔了。細節見 libs/text-ops/docs/opensource-assessment.md

二、Persist(寫入),從一路變成雙軌#

我當初的講法是「存一份向量化」,想的是單條路徑。

搜尋端一上線就冒問題:單純 dense vector(稠密向量)會踩雷。碰到縮寫、專有名詞、精確字詞查詢,語意相近反而失準。

後來拆成 hybrid indexing(混合索引):

  • Dense(稠密):MLX(Apple 機器學習框架)Qwen3-Embedding,1024 維
  • Sparse(稀疏):BM25 關鍵字索引

兩路一起寫進 Qdrant 同一個 collection(集合)。查詢端用 RRF(Reciprocal Rank Fusion,互惠排名融合)把兩路結果合起來排序。

三、Graph build(建圖),reactive pipe 接手#

graph build 最早的草圖是兩步:抽實體(entity)、抽關係(relation)。

實作上是一起發生的——抽 SPO(Subject-Predicate-Object)三元組,實體跟關係一起落地。寫入 triple(三元組)的同時同步做 Entity Resolution(實體解析):別名消歧,歸到 EntityCanonical 節點(ChatGPT 跟 GPT 是同一個)。

這條 pipeline 掛在 MemvaultEvents.MEMORY_STORED channel(通道)上,event-driven(事件驅動)、fire-and-forget(送出就不等),不等 Fork A 向量化完成。進圖譜之前還有一道 NoiseGateOp 二次檢查——多一道防線,防止廢話滲進去。

關於我說過的「完整知識圖譜」#

這裡我得老實補一下。

當初講「建立完整 KG(知識圖譜)」,心裡沒把 L1 Community Detection(Leiden 社群分群)、L2 LLM Summary(LLM 摘要)、Content Normalizer(內容正規化)、GRC Adapter(治理/風險/合規閘)這些算進來。那些後來放進 Dream Loop(夢境迴圈),排在凌晨四點。但 cron(排程)只是觸發器,實際要跑還得過 dual-gate(雙閘門)(now - last_dream_at) > 24h AND sessions_since >= 5。沒滿足就跳過。

Phase 1(第一階段)只做 ingest substrate(寫入層)——gate(閘門)層加雙軌寫入,加上 L0 Triple(三元組)+ Entity Resolution(實體解析)。深度整理推給 async batch(非同步批次)。這條分界事後看還是對的。

這篇盤的是 Write Track(寫入軌道)的 ingest pipeline(寫入管線)。下一段焦點轉到 Background Track(背景軌道)——async batch 那側還有一整套持續運轉:Dream Loop(夢境迴圈,五階段整合)、Knowledge Lint(知識體檢,四層遞進驗證)、Interest Profile(興趣畫像,7/30/90 天注意力窗口)。少了這些系統會隨時間 drift(漂移)。Read Track(召回軌道)的 scoring(評分)、reranking(重排)、cascade recall(層疊召回)留到背景這條線收完再進。

帶走這段

複製給你的 AI Agent#

如果你也在跟 AI 長期對話、想把它說過的話留下來,把這段貼給它,讓它幫你評估記憶層該怎麼設計。

幫我評估我跟 AI 對話的記憶層需要哪些處理步驟。 我的情境: - 平常用 [Claude Code / Cursor / ChatGPT / 其他] 跟 AI 協作 - 對話結束後目前的做法:[全都丟掉 / 複製重要的到筆記 / 自建系統] - 期待達到的目標:之後搜尋得到、不重複儲存、矛盾的資訊能被偵測 請幫我盤點: 1. 進記憶前需要哪幾道過濾?(雜訊、惡意指令、重複內容)這些順序為什麼重要? 2. 判斷「重複」時該用哪幾種決策?(跳過、融合、取代、新增) 3. 向量搜尋跟關鍵字搜尋要不要兩路都做?差異在哪? 4. 對話中提到的人事物關係,要不要另外抽成知識圖譜? 5. 哪些整理工作可以排進背景批次、哪些必須即時? 6. 同一件事的不同寫法(日期、金額、比例、時長),有沒有先正規化成標準格式再儲存? 7. 「擋惡意」這層有沒有區分「白話注入」(直接命令)跟「偽裝注入」(假冒身份/時間優先權/編碼夾帶)兩種? 先用我的現況盤一遍,再列出優先順序最高的 3 件事。
參考資料

延伸閱讀#

實際影響過這條 Write 路徑設計的資源。

資源 為什麼重要
memory-lancedb-pro 蠶食來源。Injection Guard 的五類注入樣板、Dedup 的 SKIP/MERGE/SUPERSEDE 四裁決、Provenance 的 trust_score 計算都借自這邊,再拆開重組
Microsoft GraphRAG L0 triple 抽取、三層 KG(事實 → 社群 → 摘要)的分層觀念來自這邊。Write Track 只落地 L0,L1/L2 留給 Background 批次跑
Zep: Temporal Knowledge Graph for Agent Memory 雙時態(valid-time + transaction-time)設計。block 跟 triple 共用的 valid_at / invalid_at / superseded_by 三欄位、Dedup 的 SUPERSEDE 決策全從這條線接過來
ActMem Triple conflict resolver 的判決邏輯。batch_ingest 遇到新 triple 跟既有 triple 語意撞上時,判「合併 / 取代 / 共存」三選一的思路來自這邊
MemoryGraft Provenance tracking 設計。每筆記憶寫入時打 trust_score、記錄來源路徑,查詢時被 scoring 的 TrustBoost 消費——這條寫讀閉環的藍本在這篇
HyDE: Hypothetical Document Embeddings Qwen3 embedding 的 task_type="search_document" 跟查詢端 "search_query" 分開,背後的理論依據。弄錯會掉幾個百分點的相似度
Simon Willison: Prompt Injection Explained 設計 InjectionGuard 時翻看最多的一篇——把注入攻擊的分類講得比任何官方文件清楚
memvault Background Track — 存完之後 三部曲第二篇。寫進來之後,背景每晚怎麼整理、衝突怎麼仲裁、記憶怎麼學會忘
memvault Read Track — 想起來之後 三部曲第三篇。要叫回來時 11 層排序怎麼疊、Slow Thinker 怎麼預測下一問
✦ 帶走這段