想起來之後——AI Agent 怎麼越用越懂我
記憶要怎麼從保險庫裡叫出來?七個組件、一條管線,跨 session 召回的完整路徑。
存得到不難。難的是:在我需要的時候,對的那筆記憶會自己浮上來。
—— 做完 Write Track 那天下午,我在終端機前寫下的起點
前兩篇講了記憶怎麼存、怎麼忘。兩條線接上,保險庫會自己整理。但還差臨門一腳——怎麼把它叫回來。
這其實是最早遇到的問題。Write Track 寫完隔天,我開了新對話,試著問 AI:「上週那個 OCR 專案進度如何?」它回不出來。資料明明在,卻找不到。
那一刻我知道,光「存起來」是不夠的。
其實 memvault 一開始的念頭,是從 @AI超元域 那邊來的。他在 openclaw 課程提過一句「讓 agent 越用越懂我」,我看完真的感同身受,一直想建立一套輔助工具。後來我從 openclaw 退回 claude code,想說不如就先從 claude code 搭起,建立 memvault 雛形。再後來看到他介紹 LanceDB Pro,也借鑑了其中很多精妙的設計,一點一滴把 memvault 完善起來。真心感謝。
「叫得回」這條路,才是整個記憶系統的核心。這篇會拆解它——七個組件怎麼分工、背後的設計哲學,還有幾條仍在打磨的邊界。
這條路徑現在每天在跑。Write Track 寫完那天問不出來的 OCR 進度,現在跨 session、跨幾週問一次就接得上。
先說一下當初的構想#
我最初想得很單純:收進來,建索引,查的時候找最像的那幾筆就好。
做下去一週就發現不夠。「我那個任務做到哪了」「最近聊了什麼」「你覺得我下一步該怎麼走」——這三個問題用同一條路徑去查,都會走偏。
三個問法,三條查法——這是 Read Track 的底層骨架,下面一節節拆開講。
一、先猜你在問哪種問題#
「上週那個 OCR 做到哪了?」「最近聊什麼?」「我在想要不要做 X,你覺得?」——這三種問法要走三條路。第一步是 agent 得先聽懂你在問的是哪一種。
問進度、問回憶、問建議,三個 intent 走三種權重、走三種深度。一開始我沒分,結果「任務」這個詞會 match 到三個禮拜前的無關對話。
解法是加一層意圖分類。原則很土——先用關鍵字掃一遍(零成本),再算一下你這句話的語意向量(幾毫秒),兩個都說不準才叫 LLM 判一次(貴很多)。大部分問題在前兩步就分好了,LLM 只在真的搞不清楚時才動。
分對之後,下游每一層的權重會自動調——問進度時時間排序加重、問定義時語意相似度加重、問建議時 trigger 延伸思考那條分岔。同一個 agent,因為你問法不同,背後拿的記憶也不同。
這是為什麼一換新 session 問「上週那個 OCR 搞到哪了」agent 能接得上——不是它記憶力強,是它先判準你在問什麼。
但「你在問什麼」其實還能再細一層——同樣一句「最近怎麼樣」,工作狂的我問的是進度,週末散步時的我問的是回顧。光看字面分不出來。
所以在分類之上又疊了一層「服務生記得熟客口味」式的個人化偏好。系統會看你最近七天、三十天、九十天比較常問哪一類問題,把這個偏好混進判斷。常翻舊帳的人,「最近怎麼樣」會被偏向回顧;常追進度的人,會被偏向進度。同樣一句話,餵給不同的你,走的路不一樣。
這層偏好跟前一篇講的興趣畫像是同一個源頭——你看過什麼、停留多久、回頭幾次,都會慢慢勾出一張屬於你的口味。意圖分類負責「字面在問什麼」,這層負責「這個你最常想知道什麼」,兩個合起來才接得近你真正想要的答案。
二、同時用兩種方式找:意思跟關鍵字#
找記憶有兩種方法,各自會漏。
- 只搜語意:你問「MLX」,記憶裡寫「Apple Silicon 推理框架」——語意對得上、關鍵字不對。但遇到縮寫、人名、專案代號會失手
- 只搜關鍵字:精準字面匹配很強,但使用者換個問法就找不到
所以兩邊同時跑。一邊算語意像不像、一邊算關鍵字匹配度,兩條排行榜出來後用 RRF(倒數排名融合)合起來——每個 ranker 的排名換算成分數,誰讓這筆記憶排得越前、貢獻越多分。用排名投票而不是分數投票,因為兩種分數尺度不同,直接加會偏掉。
這步純粹是「撈候選」。找出來的可能有一百筆。下一步才是挑。
三、挑出最對的幾筆:排序疊了十一層#
撈出一百筆候選,要餵給 agent 的可能只有 15 筆。怎麼挑?
目前疊了十一層訊號。每一層對每筆候選加一個乘數:
- 時間——越新越加分。衰減曲線不是直線往下掉,是 Weibull 形狀:剛存的一兩天衰減慢,一個月後掉比較快,再久就進入低而穩定的尾巴
- 信任度——你手動寫的比 AI 自動生成的加分
- 用過幾次——常被翻的加分,冷門的扣分。常被叫出來看的記憶衰減會慢(偶爾某筆兩三年前的東西還能出線,就是這個邏輯)
- 圖中心性——在知識網路裡被引用多的加分
- 語意接近度——跟問題越像的加分
- 分數下限——太低的直接淘汰
- 去重——兩筆太像的,降分後再比
這 11 層不是隨興疊出來的——每一層對應一個檢索或記憶研究裡的成熟概念(時間衰減、信任、圖中心性、遺忘曲線、語意、噪聲、去重)。我做的事不是發明新方法,是把十幾篇論文的訊號挑一挑,組成一套適合個人記憶場景的排序。
核心設計理念有兩條:
第一,不同意圖用不同權重。查事實重信任、查探索重時間、找實體重語意。同一批候選,因為你問法不同,排名結果可能整個翻掉。這就是為什麼第一步要先分意圖——意圖錯了,權重就錯,後面排得再精也沒意義。
第二,寫跟讀是閉環,不是兩條獨立管線。Write Track 寫入時會幫每筆記憶打上來源可信度;Read Track 排序時讀這個分數當加權。更關鍵的是反向:每次你讀到某筆記憶,系統會回頭把「被讀過」這件事寫進那筆記憶本身,下次它的壽命就會更長。寫時多付一點,讀時少算一點;讀的行為又回頭延長記憶壽命。一般 RAG 沒這條線——它們的向量庫是靜態的,寫進去就不動了。
還在磨:排序穩定性#
這套多層排序的難點是各層權重會互相拉扯——動一個,其他幾個指標可能連鎖偏移。目前靠一組 golden query 守著(幾十條已知正解的問題),每次調權重先跑一輪看有沒有退化。大部分情境下跑得穩,但要讓它在所有意圖、所有時間尺度下都不走味,這部分還在持續調校。
四、精挑那一關:注意力閘門#
排序排完還有最後一次精排。用的是更貴的模型——把你的問題跟候選記憶丟在一起聯合打分,比純比對準得多。
問題是每筆都要跑一次模型,慢且貴。後來加了一個注意力閘門——幾個跳過規則:候選太少(兩三筆)、前幾筆分數已經拉開、所有候選都擠在一個小區間,就不跑精排。不是每個問題都值得全程跑完。
另外如果精排模型連續失敗三次,會自動退回去走純排序 10 分鐘,不硬撐——像保險絲。
五、問得深才走的分岔:快思慢想借過來#
上面那四層對「問進度」「問回憶」這種確定性高的問題就夠了。但「我在想要不要做 X,你覺得」這種——單靠「找最像的段落」答不出什麼有用的東西。因為 agent 只會給通用建議,像網路文章那種。
真正有用的答案要從你的脈絡長出來——你過去做過什麼、最近關注什麼、哪些概念跟 X 相連。
所以 Read Track 有一條比較慢的分岔——從命中的概念出發,沿著知識圖譜走幾步,把鄰近的記憶也撈回來。這條路徑比較貴,淺問題不走,只有判斷你在問需要延伸思考的問題時才啟動。
這個「快 / 慢」分法不是我想出來的——借自 Kahneman 的《快思慢想》。人的大腦本來就有兩個系統:一個快、直覺、省力;一個慢、推理、費工。記憶系統不該把每個問題都當費力題,也不該把每題都當直覺題。所以 Read Track 把這兩條路並陳,讓問題自己決定走哪邊,不用使用者手選。
問「我在想要不要做 X,你覺得」,拿到的不是公版建議,是帶著你上個月做過 Y、今年提過 Z 的那種答覆。這條路徑的終點。
一個容易混淆的地方#
Read Track 裡有兩個「慢」,不是同一件事。
第一個慢——就是上面講的「慢問走慢路」。你問了需要延伸思考的問題,系統當場決定多走一條分岔去圖譜撈鄰居。這條是 Kahneman 雙系統的落地:快問快答、慢問慢答。
第二個慢——叫 Slow Thinker,在背景偷偷跑。它不處理你眼前的這次問答,而是一邊聽你講話一邊猜「你下一個可能會問什麼」,先把可能的答案撈好放在 cache 裡等你。這條的靈感是 Salesforce 的 VoiceAgentRAG。
前者是「當下」,後者是「預測」。剛好兩個都叫慢,但一個在前台、一個在背景,實作也在不同檔案。
六、最後一哩路:打包給 agent 看#
排序完、挑了前 15 筆。還沒完。
agent 的 context 有上限。15 筆全文塞進去,可能已經爆了 token。這時要做兩件事:取捨和整形。
取捨——長的段落只給摘要,不給全文。整形——依照呼叫端是 Hook、API、CLI,排版不一樣。同一批記憶,走 API 的給 JSON,走聊天介面的排成可讀的卡片。
這一步容易被當成裝飾——其實決定了最後 agent 看得到多少。早期直接截前 15 筆丟進 context,常被一個長 block 吃掉一半 token。加了 budget-aware packing 之後,token 利用率從 60% 拉到 85%。再好的排序,擠不進 AI 的腦袋也沒用。
進貨檢驗夠了嗎?出貨還要再一道#
記憶在存進保險庫之前已經過一次安檢——把那種「忽略前面的指令」「現在改說...」之類想偷渡進來操控 agent 的字串擋在外面。Write Track 那篇講過。
但只在進貨時檢一次不夠。原因有兩個:第一,安檢規則會升級,三個月前進來的記憶用的是舊規則;第二,攻擊手法每個月都在翻新,今天放行的字串,下個月可能就是新型炸彈。
所以查出來、要打包給 agent 之前,再過一道濾網——進貨時擋過一次,出貨時再擋一次。雙安檢。看到危險的句型,當場改寫或丟掉。背景偷偷預存好的那批答案也走同一道——存的時候濾、要拿出來用之前再濾。麻煩,但少爺餵給 agent 的那一口要乾淨。
七、Read Track 的取捨#
這條 pipeline 現在是我每天工作流的底層——跨 session 召回、問進度、問建議都在走它。盤一下目前長成什麼樣:6 種意圖分類、11 層 scoring、2 道 rerank gate(attention gate + circuit breaker)、Cascade Recall 三模式(LOCAL / GLOBAL / HYBRID)、budget-aware output formatter。每一層都有 golden query 回歸測試守著,Dream Loop 每天凌晨 4 點在背景自動整理記憶。
但它不是無懈可擊。誠實列幾個已知邊界:
- 延遲 vs 召回率——深召回(走圖譜那條)慢,動輒幾百毫秒。只在需要延伸思考的問題才走,是妥協
- 精挑細選那層計算貴——注意力閘門幫忙省掉一些,但該跑的時候還是要跑。當它走保險絲退回的時候,結果品質會看得出差
- 被壓到冰封層的記憶叫不回全文——只剩摘要。法律追溯用沒問題,重建當時脈絡就不夠
- 意圖分類錯了,整條管線跑錯方向——目前靠動態信心閾值降低發生率,事後回退機制還在找更穩的做法
- 自評層擋不住全部幻覺——slow 模式會跑 CRAG 自評,fast 模式省掉這層;代價是 fast 路徑下幻覺可能漏網
每一項都是成本跟效果的交換——系統能跑、也在跑,上面這些是已知邊界,不是隨時會炸的坑。攤出來的意義在於:每個「找到」背後的代價都寫在紙上,不藏起來。
八、貫穿的幾條設計心法#
上面七節講了很多機制,真正撐住這套系統的其實是幾個反覆出現的哲學:
- 快思慢想——不是每個問題都要盡全力。問的深淺由問題自己決定,不用使用者手選檔位
- 寫的時候多付一點,查的時候不用算——
trust_score、access_count、三元組雙投影,這些寫入時的「多此一舉」,都是為了查的時候能少跑一層 - 讀的動作會回寫記憶壽命——你每問一次,命中的那筆記憶會被延長有效期。不用的自然褪色,常回訪的自然留下。像走過的路不會長草
- intent 決定一切——不同問法走不同檢索策略、不同排序權重、不同深度。同一個 agent 對不同問題根本是不同的檢索器
- trade-off 要寫在紙上——不能假裝沒有代價。寫下來,自己看得到、之後的我也看得到
九、想起來不夠 — 在你開口前就到位#
到這裡為止講的都是「你問了,系統去找」。但好的服務生不是只回應點單——他會看到你杯子空了,主動把水倒上。Read Track 還有一個在背景偷偷做這件事的傢伙。
它做五件事:
- 聽你正在講什麼——盯著當下這場對話,從最近幾句話判斷你接下來最可能問什麼
- 順一條邏輯動線——把猜出來的那幾個可能問題,按照人類自然會問下去的順序排好
- 偷偷先去找答案——挑前幾個最可能的問題,背景跑一次完整的搜尋,搜出來的記憶先放在手邊
- 有節制地動——不是每句話都觸發。對話太短不動、最近講的東西明顯偏離不動、剛剛才預測過不重複動。怕白燒
- 等你真的開口——你問下去那一刻,先去手邊那批備料看看有沒有對得上的;對得上就直接用,省掉一次完整搜尋;對不上就走正常流程,沒損失
這就是料理長備好下一道菜的邏輯。客人還沒點,但前菜上完之後通常會點主菜,先把可能的兩三道備料切好放在台上。客人真的點了,端出來只要幾秒;客人改點別的,那批備料下一桌可能還用得上,最差就是丟掉。
它跟前面講的「快思慢想分岔」很容易搞混。那個是你問下去之後當場決定要不要多走一條深路;這個是你還沒問之前就已經偷偷在動。一個在前台、一個在背景,剛好兩個都被叫做「慢」,但做的事情完全不同。
有用嗎?對得上的時候,從問到答幾乎沒延遲——備料早就切好了。對不上的時候,那批備料就是浪費。所以「有節制地動」很重要——對話太短不動、跑題不動、剛動過不再動。寧可少猜,不要硬猜。
還有一件事:背景搜出來的那批備料,要進儲藏室之前要先消毒一次,要從儲藏室拿出來給 agent 看之前再消毒一次。前面講的那道雙安檢,這條備料線一樣得走。不能因為是「自己人」備的就鬆懈。
到這裡 Read Track 就是現在的全貌了:聽得懂、找得到、排得對、挑得準、塞得進、來得及。每一條都還在打磨,但骨架已經立起來,每天在跑。
Read Track 盤到這。意圖、找、排、挑、塞、消毒、預取——七個組件、一條管線。
它不會幫我「記住該記住的事」,那是 Write 跟 Background 的責任。它只負責一件事:在我需要那筆記憶的時候,對的那筆自己浮上來。
開頭那句「存得到不難,難的是讓對的那筆自己浮上來」——現在浮得上來。
架構總覽#
這套 pipeline 借自 10+ 篇研究:HippoRAG 的 PPR、LightRAG 的雙模式檢索、GraphRAG 的三層 KG、HyDE 的雙 task 設計、CRAG 的自評閘、Kahneman 的 fast / slow 分工、VoiceAgentRAG 的預取、Weibull 衰減曲線、RRF 融合、Attention-Residual 的 intent-dependent 權重。下面每個組件都會點出對應出處。
Read Track 的入口是 recall(query, context)。不論呼叫端是 Hook(鉤子)、MCP(Model Context Protocol)、CLI(命令列)、API(應用介面),走同一條管線。差別只在最後 Output Formatter(輸出格式化器)序列化的格式。
管線骨架:QueryClassify(意圖分類)→ Fast Search(快速召回)→ (Cascade Recall(連鎖召回), slow only) → Output Formatter。Scoring(評分)和 Reranking(重新排序)不是獨立 stage(階段)——它們嵌在 qdrant_search() 裡面(services.py)。
一、Query Router 意圖分類#
六個 intent(意圖):entity_lookup、factual、conceptual、exploratory、cross_domain、unknown(query_archetypes.py)。判定走雙軌:
- 關鍵字匹配:
~0ms,純正則 / 詞表命中 - 語意向量:
~5ms,把 query 算成 embedding(嵌入向量)跟 archetype(原型)向量比對
兩邊的信心分數融合。融合後仍低於動態閾值,才觸發 LLM(~500ms)。query_router.py 裡的 QueryClassifyOp 把這整條融合寫成一個 Operator(操作子)——Slow Thinker(慢思考)預取結果也從這一層注入。
分類結果決定下游三件事:LayerPlan(分層計畫,查哪幾層)、ScoringConfig(評分設定,11 stages 的權重向量)、RetrievalMode(檢索模式,LOCAL 本地 / GLOBAL 全局 / HYBRID 混合)。
Personalized Router:意圖之上的個人偏好層#
純 archetype 分類有一個盲點——同樣 query「最近怎麼樣」,對 attention profile 偏 factual 的使用者跟偏 exploratory 的使用者,正解 intent 不同。memvault.query.personalized_router 在 QueryClassifyOp 之後加一層 re-weight:
- 從
attention_profile拉 7 / 30 / 90 天 intent 分布(與 BG Track 的 Interest Profile 同源,attention_tracker.py寫入) - 對六個 intent 的 archetype 信心分數做 prior 加權:
p_final = p_classify × (1 + α × intent_freq_norm),α 預設 0.25 - 低樣本守門:
profile.sample_count < 50直接 bypass(避免冷啟動偏移) - 輸出
PersonalizedIntent,下游ScoringConfig/RetrievalMode拿這個值,不拿原始 archetype 結果
這條跟 Background Track 的 Interest Profile 是同一筆 attention_profile 在讀。寫入由 BG 算、查詢時 Read 消費——又是一條 Write-Read 閉環。
二、Fast Search:Qdrant Hybrid(混合索引)+ RRF#
qdrant_search()(services.py 附近)是 Fast Search 必跑的核心。Qdrant 同時拿 dense(稠密向量,Qwen3 0.6B MLX, 1024d)跟 sparse(稀疏向量,BM25 per-service avgdl)查,再用 Reciprocal Rank Fusion(倒數排名融合,RRF)合併:
score_rrf(doc) = Σ_ranker 1 / (k + rank_in_that_ranker)
k 預設 60。用排名投票不用分數投票——dense 跟 sparse 分數尺度完全不同,直接加會被分數大的 ranker(排序器)壓過去。
注意:Scoring 跟 Reranking 都是嵌在 qdrant_search() 裡的,不是獨立 stage。這個設計是故意的——Scoring 要用到的 access_count、provenance、attention_profile 都在 qdrant_search 的作用域(scope)裡,拉出去成獨立 stage 要多搬很多 state(狀態)。
三、11-Stage Scoring Pipeline#
scoring_pipeline.py,每 stage 是一個 ScoringOp(評分操作子,走 Operator 協議):
1. RecencyBoost (時效加權) × (1 + 0.15 × e^(-age/14))
2. ImportanceWeight (信心加權) × (0.7 + 0.3 × confidence)
3. TrustBoost (信任加權) × (1 - 0.3 × (1 - trust))
4. FeedbackBoost (回饋加權) × (1 + 0.15 × tanh(net/3))
5. LengthNorm (長度正規化) ÷ (1 + 0.3 × |log2(len/500)|)
6. WeibullDecay (威布爾衰減) 4-tier: Core 180d · Hot 60d · Warm 30d · Cold 14d
7. PPRBoost (圖中心加權) × (1 + 0.3 × ppr_score) # HippoRAG-inspired
8. SemanticBoost (語意加權) × (1 + 0.3 × cosine_sim)
9. MinScoreGate (分數下限閘) hard filter < 0.10
10. NoiseFilter (雜訊過濾) 7 類 quarantine tag
11. PairwiseDedup (成對去重) cosine > 0.85 → ×0.5 then min_score
Weibull(威布爾分佈)4-tier 由 confidence(信心度)決定——高 confidence 進 Core tier(核心層),慢慢衰減;低 confidence 進 Cold tier(冷層),14 天就掉一半。
access_count(存取次數)會拉長 effective half-life(有效半衰期,最多 10x 上限)——這是 Read 跟 Write 的閉環其中一條:讀的行為回寫 access_count、延長壽命(這條在 Write Track 的 Provenance 段 落提過)。
intent-dependent(意圖相關)權重:entity_lookup 把 SemanticBoost 拉到 0.5;exploratory 把 Recency 拉高;factual 把 Trust 再加重。
早期我全部 intent 共用一套權重——結果 factual 跟 exploratory 的 top-10 長得幾 乎一樣,重疊 8 筆。分 intent 之後才分得開。
四、Jina v3 Reranker + Attention Gate#
reranker.py。Cross-encoder(交叉編碼器)0.6B MLX(rerank_bridge.py),query(查詢)跟 doc(文件)聯合打分,比純雙塔(dual-encoder)準得多。
問題是貴——每筆都要跑一次模型。所以前面有個 Attention Gate(注意力閘門), 三個 skip 規則:
- 候選 ≤ 2 件——直接 return(回傳),不 rerank(重排)
- 主導分數:頭兩筆分差 > threshold(閾值)——結果已經明確,不 rerank
- 緊密集群:所有候選分數都擠在一小區間——rerank 也分不出來,跳過
靈感來自 TurboQuant+ 的 early return(提早回傳)策略。
Circuit Breaker(斷路器):3 次連續失敗進入 600s recovery,這段時間直接繞過 reranker(重排模型)走純 scoring。
Score Blending(分數融合):預設 0.3 × scoring + 0.7 × rerank,但會隨 intent 變——entity_lookup 的 0.2/0.8 給 rerank 更多權;exploratory 的 0.5/0.5 讓 scoring 回到一半,因為 exploratory 的答案沒有絕對對錯,原始分更重要。
五、Cascade Recall(slow intent only)#
先註解血統:這個「fast / slow 二分」骨架來自 Kahneman 雙系統(System 1 快思 / System 2 慢想)。早期版本讓使用者手選 fast 或 slow,後來 refactor 成由 intent 決定(choose_thinking_mode()),但「快慢兩條路」這個分工本身沒換。
kg_services.py 裡的 cascade_recall()(連鎖召回)。Fast path(快速路徑)永遠跑,Cascade 只有 slow intent(慢意圖)才啟動——不是二選一。Fast Search 的主結果(schema 欄位 cards)跟 Cascade Recall 擴散出來的補充結果(cascade_cards)併入同一個 response(回應)。
內部分兩路:
- GLOBAL(全局):L2 Summary(LLM 預生成的社群摘要)+ L1 Community(Leiden 萊登演算法分群結果)。這兩層不走 scoring pipeline——它們是預先壓縮好的 view(視圖),直接拉就行
- LOCAL(本地):L0 Triple + PPR(Personalized PageRank)Walk(圖走訪)。從命中的實體出發,沿著圖譜走幾步,阻尼 0.85。鄰近的 triple 擴散回來
Mode 由 intent 決定:entity_lookup/factual 走 LOCAL(要精準)、conceptual/exploratory 走 GLOBAL(要鳥瞰)、cross_domain 走 HYBRID(混合,全搜)。
L0/L1/L2 本身不走 scoring。只有 Blocks(記憶塊)層還是走完整 qdrant_search() 的 scoring + reranking——這是為了讓 cascade_cards 裡的 Blocks 部分跟 cards 走一樣的排序邏輯,不會出現 fast 跟 cascade 同一筆記憶分數矛盾的情況。
容易混淆的點——「Cascade Recall 的 slow」跟下一段 panel-end 會提到的「Slow Thinker」不是同一件事。前者是 Kahneman 血統的查詢當下要不要深挖;後者是 VoiceAgentRAG 血統的背景預測下一問、事先撈好放 cache。兩個剛好都叫 slow,實作在不同檔案(kg_services.py vs slow_thinker.py)。
Pipeline 順序的脆弱性#
11 個 stage 的順序有依賴關係。RecencyBoost 在 TrustBoost 前面,是因為某次 dream loop 合併後的 trust 會被新舊記憶的時序覆寫,必須先拿時間校正。
依賴順序的 pipeline 天生脆——換兩個 stage 的位置就可能讓 top-k(前 k 筆)全變。目前靠一組迴歸測試守著:幾十條 golden query(黃金查詢)有預期排序,每次動權重前先跑一輪,top-k 差異超過閾值就擋下。現況堪用,要讓它更 robust(穩健)仍在調校中。
六、Output Formatter 與 token 預算#
管線最後一步。所有入口(Hook、API、MCP、CLI)走同一條 pipeline(管線)——差別只在這裡 :format(格式)是序列化參數(text / json / cards),跟上游檢索策略完全無關。
token 預算逼出三件事:
- 主結果
cards跟擴散結果cascade_cards的 budget(token 預算)拆分(通常 60/40) - 超長 block(記憶塊)先 attach(附加)摘要,原文放
expand(展開)欄位讓 agent 自己決定要不要挖 - 重複的段落(cosine > 0.9)只保留分數最高那筆
這層之前我低估了。直接截 top-15(前 15 筆)丟進 context(上下文),常被一個長 block 吃掉一半 token,後面的記憶全擠不進去。加了 budget-aware packing(預算感知打包)之後,平均利用率從 60% 拉到 85%。
Read-Time Sanitize:與 Write Injection Guard 對稱#
Write Track 在落地前跑一次 injection_guard.is_unsafe(),但只擋寫入時的規則版本——三個月前入庫的 block 用的是舊版規則,攻擊樣本也在演化。所以 Read 端對稱跑一次:memvault.security.read_sanitize。
- 對所有
cards/cascade_cards在 Output Formatter 之前掃is_unsafe_for_injection()(共用 Write 端的 ruleset,每次部署同步) - 命中規則的 block 走
sanitize_for_injection():危險 token sequence 用[REDACTED]占位,保留語意上下文,不直接整筆丟(避免 top-k 稀釋) - Slow Thinker 的 prefetch cache 同樣套用——進 cache 前濾一次(write-time)、出 cache 給 agent 前再濾一次(read-time),雙層防禦避免 cache 變成繞過 read sanitize 的後門
- 命中事件寫
audit_log,可追溯哪筆舊 block 觸發新版規則 → 反向驅動 Write 端 ruleset 升級
不對稱的安檢是漏的——只在寫入端守,等於假設規則永遠不會升級、攻擊永遠不會演化。Write × Read 雙端各跑一次,才能讓老 block 也吃到新規則。
七、Trade-off 清單#
到現在還沒完全解決的,大概這幾個:
| 問題 | 現況 |
|---|---|
| 延遲 vs 召回率 | slow path(慢路徑)對 p95(95 分位延遲)影響大。現用 intent 閘門,只有真的需要延伸思考的問題才走 |
| Reranker 計算貴 | Attention Gate 擋掉約 30% 的 rerank 請求;Circuit Breaker 失敗隔離 600s |
| Cold / Frozen tier 只剩 summary | 全文被壓縮後叫不回。規劃中的 tier upgrade 可讓高頻被叫的 cold block(冷記憶塊)回升 warm(溫) |
| 意圖誤判 | 整條 pipeline 走錯路。目前靠動態信心閾值降低誤判率 ,但沒有好的事後回退 |
| CRAG 自評漏網 | deep 模式(深度模式)才跑 CRAG;fast 模式(快速模式)省了這一層,代價是 fast 路徑下幻覺可能漏網 |
每一項都是成本跟效果的交換。這條路徑沒辦法做到又快又準又完整。挑兩個。
八、貫穿的設計決策#
上面七節的機制底下有五條反覆出現的設計原則:
- Intent-driven everywhere:
QueryClassifyOp的 intent 向下游派送到 scoring weights、retrieval mode、score blending ratio、cascade layer routing。同一個 query 對不同 intent 走完全不同的路徑 - Write-Read 閉環:
trust_score寫入時由source_tracker打分、讀取時由TrustBoostOp消費;access_count讀取時回寫、下一次讀取影響WeibullDecay的 effective half-life。這條閉環跨 Write / Read Track,不是單向 pipeline - Kahneman fast / slow:
thinking_mode = fast / slow把 Kahneman 雙系統落地。slow 比 fast 多跑 Cascade Recall + CRAG 自評,不是二選一——slow = fast + extras - Defense in depth:Write Track 三 gate 攔截 + injection guard,Read Track attention gate 跳過無意義 rerank + circuit breaker 隔離重排失敗。每層允許下一層 fail 而不整條壞
- Trade-off on paper:延遲 vs 召回、計算成本 vs 品質、冷儲存 vs 全文還原——每條 trade-off 都明寫在 §七,不假裝沒有
九、Slow Thinker:Predictive Prefetch#
料理長備好下一道菜——這條的技術名字叫 Slow Thinker,靈感來自 Salesforce AI Research 的 VoiceAgentRAG(雙代理:背景 Slow Thinker + 前台 Fast Talker + FAISS speculative cache)。
memvault.slow_thinker 是一條獨立的後台 pipeline,不阻塞主 recall(),事件源是 conversation.utterance.appended。
五個元件#
- NextQuestionPredictor:消費最近 N=8 句 utterance,呼叫小型 LLM 產出 top-3 預測 query。Prompt 限制 query 必須具體可查(不允許「然後呢」這種空 query)
- PredictionPipeline:5 個 Op 串起——
ContextSampler→QuestionGenerator→QuestionRanker(按對話自然延續度排序)→QuestionDeduplicator(與最近 5 分鐘已預測的去重)→QuestionEmitter。每步可單獨換實作 - SpeculativeFetcher:拿 top-1 / top-2 預測 query 跑完整
recall()(含 11-stage scoring + reranker + cascade,視 intent 而定),結果寫進 Redismemvault:prefetch:{user_id}:{query_hash},TTL 300s。Fire-and-forget,失敗不重試(下一輪 utterance 會再產一批) - Admission Control:5 條過濾規則,全過才放行 prefetch
conversation.length < 3 turns→ skip(樣本太少)topic_drift_score > 0.7→ skip(剛跑題,舊 context 沒參考價值)last_predict_age < 30s→ skip(剛剛才動過,避免反覆 fetch)predicted_query.confidence < 0.4→ skip(小型 LLM 也不確定,硬跑只是燒 token)min_sample_threshold:使用者conversation_count < 10整條 Slow Thinker bypass(冷啟動期不開)
- 注入點:在
QueryClassifyOp內。實際recall()進來時,先看memvault:prefetch:{user_id}:{query_hash},hit 就直接拿 prefetched 結果走 Output Formatter(省掉中間整條 pipeline);miss 就走原本流程,零代價降級
跟 Read-Time Sanitize 的關係#
Prefetch cache 是 Sanitize 的雙層套用區——進 cache 前 sanitize_for_injection()(write-time),出 cache 給 agent 前 Output Formatter 再 sanitize 一次(read-time)。Cache 不能是繞過 Read 端 sanitize 的後門,這是設計時就鎖死的不變式(invariant)。
為什麼這個設計#
VoiceAgentRAG 的核心觀察:語音 agent 的 inter-utterance 間隔(人開始講話到講完)足夠跑一次完整 RAG。把這段「人類在講話的時間」拿去 speculatively fetch,hit 的時候 latency 從 ~800ms 砍到 < 100ms(Redis 命中 + format)。memvault 的對話介面也吃同一個時間窗。
Trade-off 也明寫:miss rate 觀測值約 55–65%(top-3 預測命中其中一個),意思是 35–45% 的 prefetch 是純白燒。這是「願意花閒置 token 換 hit 時的零延遲」的明確選擇——所以 Admission Control 的 5 條規則是這個元件的命脈,不是 nice-to-have。
Read Track 三部曲到這收尾:意圖 → 召回 → 排序 → 重排 → 圖譜 → 打包 → 雙端 sanitize → 預測式預取。每一層都有 fallback、有 trade-off、有 golden query 守著,每天在跑。
Read Track 拆完。QueryClassify → fast/slow split → 11-stage scoring → reranker → OutputFormatter → read_sanitize → SlowThinker prefetch loop——七個組件、一條 entry-agnostic pipeline。
它不負責「memvault 有沒有記住該記住的事」,那是 Write Track 跟 Dream Loop 的責任。它只負責一件事:給定 query,把跨 session 對的那筆記憶 surface 上來。
真正花時間調的不是新組件,是 scoring 權重、prefetch 命中率、cascade 邊界判定這些長期參數。三部曲到此。
給 AI Agent 的健檢提示#
如果你也在打造自己的 AI 記憶庫或 RAG 系統,可以把這段提示詞交給你的 AI 助理,請它幫你審視「讀取」路徑的設計是否周全。
延伸閱讀#
實際影響過這條 Read Track 設計的資源。
| 資源 | 為什麼重要 |
|---|---|
| HippoRAG (NeurIPS'24) | PPR Boost 和從命中實體沿圖走路的策略。Cascade Recall 的 LOCAL mode 基本上是這篇的落地 |
| LightRAG | LOCAL / GLOBAL / HYBRID 的命名跟 intent → mode 對應規則直接借自這邊 |
| Microsoft GraphRAG | L0 / L1 / L2 三層、用 L2 社群摘要做 zero-latency recall 的概念來源。Read 的 GLOBAL 模式吃的就是預先生成好的 L2 view |
| CRAG: Corrective Retrieval-Augmented Generation | Slow path 才跑的自評層。用 CRAG 的 evaluator 在 rerank 後做品質閘——fast 模式為了省 token 不跑這層 |
| Attention-Residual for Intent-Dependent Scoring | 11-stage scoring 裡 intent-dependent 權重的理論依據——不同 intent 調不同信號的權重向量,不是每個問題用同一套分數 |
| Cormack et al. 2009: Reciprocal Rank Fusion | Dense + Sparse 雙 ranker 合併排名的原始做法。Fast Search 的 RRF 融合直接採用這公式,k=60 也是這篇的默認值 |
| TheTom/turboquant_plus | Attention-gated Reranking 的早期 return 策略來自這裡。我的三個 skip 規則(候選太少、分數主導、緊密集群)用它的判斷邏輯重寫 |
| Kahneman — Maps of Bounded Rationality (Nobel Lecture) | Fast / Slow 二分的理論骨架。thinking_mode 本來要使用者自己選,後來重構成由 intent 自動決定,但 System 1 / System 2 的分工本身沒換。Cascade Recall 只在 slow mode 啟動就是這條血統 |
| VoiceAgentRAG (Salesforce AI Research) | Slow Thinker / 預取的靈感來源。雙代理架構(背景 Slow Thinker + 前台 Fast Talker + FAISS cache)對應我這邊的 slow_thinker.py + QueryJournal 預測 + Redis speculative cache |
| win4r / memory-lancedb-pro (LanceDB Pro autoRecall) | memvault 整套設計最早的啟發。autoRecall 的「主動注入」哲學(在 agent 思考前先把相關記憶塞進 context)、五層檢索 + 三層升遷 + Weibull 衰減,都在 memvault 這邊長出各自的對應實作。由 YouTube 頻道 AI超元域 介紹 openclaw 時引入 |
| memvault Write Track — 對話結束之後 | 三部曲第一篇。讀之前要先有東西讀——三道 gate、雙軌寫入、來源履歷怎麼打 |
| memvault Background Track — 存完之後 | 三部曲第二篇。Read 用到的 L1/L2 摘要、PPR、Interest Profile,都是 Background 跑出來的 |
推薦閱讀
memvault 全景:三條軌道 × 三層心法
memvault 三部曲總集 — 把寫入、整理、召回三條軌道串成一張地圖,補上 CLT 認知負荷、4 Event Flows、RxJS Reactive 三層跨軌心法
存完之後——我怎麼讓記憶學會忘
memvault 的 Background Track 心路歷程。每晚四點的夢境迴圈、知識體檢、興趣畫像——收進保險庫還不夠,要會忘掉過時的才健康。
對話結束之後——我怎麼把記憶收起來
memvault 的 Write Track 心路歷程。從一張餐巾紙的構想,長成三道閘門加雙軌寫入。